hwmTimers

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

  1. // ==UserScript==
  2. // @name hwmTimers
  3. // @namespace Tamozhnya1
  4. // @author xo4yxa, Demin, перф, CheckT, Tamozhnya1
  5. // @description Таймеры здоровья, гильдии рабочих, кузнецов, наёмников, охотников, воров/рейнджеров, лидеров
  6. // @version 13.8
  7. // @include *heroeswm.ru/*
  8. // @include *lordswm.com/*
  9. // @exclude */rightcol.php*
  10. // @exclude */ch_box.php*
  11. // @exclude */chat*
  12. // @exclude */ticker.html*
  13. // @exclude */frames*
  14. // @exclude */brd.php*
  15. // @grant GM_deleteValue
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM.xmlHttpRequest
  19. // @grant GM.notification
  20. // @license MIT
  21. // ==/UserScript==
  22.  
  23. // авторы xo4yxa (2008-2009), Demin (2010-2015), перф (2017, 2020-2023), CheckT (2017-2019), Tamozhnya1 (2023-2025)
  24. // (c) 2008-2009, xo4yxa homepage http://hwm.xo4yxa.ru/js/timerestore/
  25. // Счетчики здоровья, манны, смены, охоты
  26. // Скрипт добавляет счетчики, отсчитывающие время:
  27. // - до полного восстановления здоровья и маны,
  28. // - до начала смены Гильдии Рабочих,
  29. // - до окончания работ в Кузнице (с "К" можно перейти в Кузницу),
  30. // - до конца/начала задания в Гильдии Наемников (с "ГН" можно перейти в здание Гильдии Наемников),
  31. // - может просто быть секундомером, что может пригодится для отслеживания времени охоты в ГО (нажатие на "ГО" сбрасывает данный счетчик в 00:00).
  32.  
  33. // Текущая версия 0.14
  34. // версия 0.14
  35. // * исправлен таймер для кузницы
  36. // + в исключения добавлена страница батлчата
  37. // версия 0.13
  38. // + предупреждение о завершении работ в Кузнице
  39. // + нажатием на "К" можно включить/отключить показ предупреждения для Кузницы (красная "К" - предупреждение будет, обычная "К" - не будет)
  40. // * роль ссылки на кузницу, выполняют цифры для таймера кузницы
  41. // * переработан механизм обнуления таймера ГО
  42. // + правый клик по "ГО" обнуляет и останавливает данный таймер, левый клик обнуляет, а если был остановлен, то и запускает его
  43. // версия 0.12
  44. // * исправлено отображение панели при просмотре фотоальбомов
  45. // * теперь панель не отображается на заглавной странице игры
  46. // + нажатием на "ГР" можно включить/отключить показ предупреждения ГР (красная "ГР" - предупреждение будет, обычная "ГР" - не будет)
  47. // * предупреждение ГР появляется только на одной из вкладок
  48. // * перемещена панель таймеров, новое место между панелью ресурсов и панелью меню, и теперь не сдвигает содержимое страницы вниз
  49. // * счетчик ГО не сбрасывается в 00:00 на каждой странице при новой установке
  50.  
  51. // (c) 2010-2015, demin ( https://www.heroeswm.ru/pl_info.php?id=15091 ) homepage https://gf.qytechs.cn/users/1602-demin
  52. // С gf.qytechs.cn скрипт почему-то удален. Остался на https://userscripts-mirror.org/scripts/show/92571
  53. // Предыстория
  54.  
  55. // # Дата: 2009-07-20 12:32:32
  56. // # От: demin
  57. // # Кому: xo4yxa
  58. // # Тема: скрипт таймеры
  59.  
  60. // привет, давно жду когда добавишь в этот скрипт подхватывание времени до конца работы с /home.php, если в таймере 00:00. каждый день прихожу на работу, таймер нулевой, т.к. устраивался с дома.
  61. // может добавишь?
  62.  
  63. // # Дата: 2009-07-20 12:34:28
  64. // # От: xo4yxa
  65. // # Кому: demin
  66. // # Тема: Re: скрипт таймеры
  67.  
  68. // Привет
  69. // да, обдумываю такой прием, но пока тока в думках, не до скриптов...
  70.  
  71. // =======
  72.  
  73. // Прошел год.. На днях изучил ява скрипт и дописал.
  74.  
  75.  
  76. // История версий
  77.  
  78. // [4.7]
  79. // [*] правка англ. строк в настройках
  80. // [*] добавлено мигание "Нет рабочих мест" при нулевом таймере
  81.  
  82. // [4.6] 22.04.14
  83. // [*] фикс для фф 3.6 скрытия таймера охоты
  84.  
  85. // [4.5] 30.03.14
  86. // [+] синхронизация с новым таймером ГО (изменения в игре от 25.03.2014)
  87. // [+] добавлена опция в настройках: Скрывать "Следующая охота будет доступна через .."
  88. // [*] изменение ГО: генерация мобов происходит при заходе на карту. если, не заходя на карту, инициировать перемещение с нулевым таймером - таймер не будет запущен, т.к. мобы встретят по прибытии
  89. // [*] исправлен баг: при истечении премиума и покупки его на другом компе, на первом компе выскакивало сообщение, что премиум истек, хотя уже вновь куплен
  90. // [*] исправление: перестало работать мигание "Вы уже устроены" при нулевом таймере, переписано на css3
  91.  
  92. // [4.4] 14.03.14
  93. // [*] корректировка кода показа настроек, распределение z-index между скриптами
  94.  
  95. // [4.3] 24.12.13
  96. // [*] новогодний фикс
  97.  
  98. // [4.2] 19.10.13
  99. // [*] фикс определения активного штрафа трудоголика в сзязи с изменением строки штрафа
  100.  
  101. // [4.0] 19.06.13
  102. // [*] фикс таймера ГО при побеге мобов
  103.  
  104. // [3.8-3.9] 07.06.13
  105. // [+] таймер ГН вычисляет время через репутацию
  106. // [*] исправлен баг на com (thx Lord STB)
  107.  
  108. // [3.7] 31.05.13
  109. // [*] скрипт изменен под новый код флешек игры
  110.  
  111. // [3.5-3.6] 29.05.13
  112. // [*] скрипт изменен под новый код флешек игры
  113. // [+] для lordswm.com исправлено время окончания премиума (дата пишется по-другому) и окончания лицензии охотника (thx ototo)
  114.  
  115. // [3.4] 19.05.13
  116. // [+] при наведении на таймер ГР показывает количество оставшихся трудоустройуств до штрафа трудоголика
  117. // [*] для com фикс одной из строк времени ГН (thx todesh)
  118.  
  119. // [3.3] 04.05.13
  120. // [+] при наведении на таймер ГР показывает наличие премиума + по истечении будет уведомление
  121.  
  122. // [3.2] 03.05.13
  123. // [+] добавлено определение наличия лицензии О или МО (в здании ГО) с уменьшением времени между охотами - благодарность за предоставление данных ShoniUA
  124. // [+] при наведении на таймер ГО показывает наличие лицензии + по истечении будет уведомление
  125.  
  126. // [3.0] 20.04.13-28.04.13
  127. // [+] дополнительная опция отключения всех уведомлений о штрафе трудоголика
  128. // [+] выделение цветом "Вы уже устроены" если таймер ГР равен 00:00
  129. // [+] фикс ГК
  130.  
  131. // [2.3] 19.04.13
  132. // [*] исправлен баг, влияющий на отсчет ГВ (ГРж) и ГО - огромная благодарность за помощь ВалиЕЦ
  133. // [+] теперь таймеры хп и маны фиксируются строго по центру под соответствующими флешками
  134.  
  135. // [2.0] 09.04.13
  136. // [+] дописан код подсчета времени до штрафа трудоголика
  137. // [+] дописан код регистрации парных охот в ГО
  138. // [+] реакция ГО на переход
  139.  
  140. // [1.13] 08.04.13 - beta
  141. // [+] объединены два алгоритма окончания боя
  142. // [+] добавлен подсчет времени до штрафа трудоголика (пока без обнуления после победного боя)
  143.  
  144. // [1.10-1.12] 20.03.13 - 06.04.13 - beta
  145. // [+] косметическая правка половины кода, переписано большинство алгоритмов
  146. // [+] изменен таймер ГО
  147. // [*] изменен таймер ГК
  148. // [+] изменен таймер ГРж (все благодарности Чеширский КотЪ)
  149. // [*] изменены настройки
  150. // [+] в настройках добавлена возможность скрывать "ненужные" таймеры
  151. // [+] автоматическое определение активного премиум аккунта (с корректировкой времени Гильдий)
  152.  
  153. // [1.00] 17.01.12
  154. // [+] два алгоритма окончания боя (см. настройки)
  155. // [+] поддержка lordswm.com (огромная благодарность Циник за помощь в переводе)
  156. // [+] единоразовое предупреждение о восстановлении армии (таймер здоровья)
  157.  
  158. // [0.27] 06.01.12
  159. // [*] удалена опция уведомления о восстановленном здоровье после победы в ГВ/ГРж (изменения в игре от 2011-12-14)
  160. // [+] Гильдия Рейнджеров: обновление, опция автовступления в бой (см. настройки) (за тестирование благодарю --BAPBAP-- Кожаное_лицо l-xXx-l Кофе)
  161. // [*] изменения в настройках
  162. // [*] изменения в таймере ГО (реакция на переход и др.)
  163.  
  164. // [0.26] 23.11.11
  165. // [+] добавлен таймер Гильдии Рейнджеров
  166. // [+] полная поддержка браузеров Хром и др.
  167.  
  168. // [0.25] 22.06.11
  169. // [*] откат скрипта от таймера ивента
  170.  
  171. // [0.24] 19.06.11
  172. // [*] временное изменение таймера ГО под ивент - отсчет 30 минут между нападениями на Хранителей леса. После окончания ивента будет произведен откат скрипта.
  173.  
  174. // [0.23] 25.03.11
  175. // [*] обновление под FF 4.0
  176.  
  177. // [0.22] 15.03.11
  178. // [*] фикс таймера ГВ - если Готовность армии более 3% при выходе из боя, бой считается победным
  179. // [+] добавлено окно настроек скрипта (обнуление таймеров, настройки ГВ) - модуль настроек взят из скрипта hwmtakeoffon автора xo4yxa
  180.  
  181. // [0.21] 02.03.11
  182. // [*] возможность полного отключения таймера ГО
  183.  
  184. // [0.20] 01.02.11
  185. // [+] Изменения в таймере кузни: добавлен расчет дней (огромная благодарность Вещий_Олег за помощь в тестировании).
  186. // [+] скрипт не требует изменений при игре с ЗЕРКАЛА героев
  187. // [+] возможность играть за нескольких персов с зеркала героев в одном браузере
  188.  
  189. // [0.19] 29.01.11
  190. // [+] Изменения в таймере кузни: поиск двух окончаний работ (при возможности одновременно чинить и улучшать артефакты), подхватывание наименьшего времени.
  191.  
  192. // [0.18] 20.01.11
  193. // [+] поддержка различных БРАУЗЕРОВ
  194. // [*] исправлено подхватывание времени ГН задания "армия" до принятия задания
  195. // [*] оттестировано окончание времени работ в кузнице (Гильдия Кузнецов)
  196.  
  197. // [0.17] 07.01.11
  198. // [+] таймер ГО реагирует на нейтралов
  199.  
  200. // [0.16] 05.01.11
  201. // [+] добавлен таймер Гильдии Воров (после боя гильдии воров необходимо нажать "Вернуться в игру")
  202. // [*] добавлено уведомление об окончании ожидания между засадами в Гильдии Воров
  203. // [+] добавлено уведомление о задании ГН
  204. // [*] роль ссылки на ГН, выполняют цифры таймера ГН
  205. // [+] ко времени ожидания между заданиями ГН добавляется 1 минута
  206. // [*] таймер ГО при установке скрипта более не покажет 100500 дней
  207. // [*] исправлены некоторые грамматические ошибки
  208.  
  209. // [0.15]
  210. // [*] скрипт изменен под новый год (по окончании НГ переустановка не потребуется)
  211. // [*] в исключаемые страницы добавлены страницы квестов
  212.  
  213. // [0.14]
  214. // [+] обновленный скрипт hwm time restore (автор xo4yxa)
  215. // [+] подхватывает время устройства на работу с home.php, если таймер нулевой
  216. // [+] совместим с последней версией скрипта HWM_Time_Seconds
  217.  
  218.  
  219. // (c) 2017, перф. 10.10.2017 v.5.8: *вместо nick привзяка к id_payler из рекордов охоты; изменение алгоритма получения уровня здоровья.
  220. // (c) 2018, CheckT v.6.0+: Исправления и рефакторинг homepage https://gf.qytechs.cn/ru/scripts/35221-hwm-time-restore
  221. // 6.0.5 Возвращена настройка "не сигналить о событиях".
  222. // 6.0.6 Починена функциональность загружаемого звука и звука на выздоровление
  223. // 6.0.7 Починена функциональность сохранения настроек "скрывать охоту" и трудоголика
  224. // 25.11.2018 v 6.0.9
  225. // - Починен таймер ГВ
  226. // - Починен таймер трудоголика (скорее всего)
  227. // - Починен таймер Абу-Бекра
  228. // - Добавлена настройка "Запретить повторные сигналы в течении 30 секунд"
  229. // Если открыто несколько вкладок, все они могут просигналить, причём с некоторой задержкой, так что получится очередь сигналов.
  230. // Эта настройка позволяет подавить повторные сигналы на 30 секунд.
  231. // Время задаётся в сеуундах в начале скрипта параметром
  232. // disable_alarm_delay
  233. // - Можно задать разный звук для таймеров ГР, ГО+ГН+ГВ, ГК, здоровья
  234. // todo:
  235. // Звук в Firefox не проигрывается. Причина выясняется.
  236.  
  237. // v6.0.10- небольшое исправление инициализации звука
  238. // v6.0.11- Починен таймер трудоголика
  239. // v6.0.12- Обнаружение боя раздельно для разных персов (актуально для игры с допами)
  240. // v6.0.13
  241. // - Поддержка для режима "не показывать вкладки".
  242. // При этом настройки будут общими для всех персов с такими настройками на этом компьютере.
  243. // Если нужны раздельные настройки, заведите быструю ссылку с любым текстом и ссылкой
  244. // /pl_hunter_stat.php?id=(ид)
  245. // например
  246. // /pl_hunter_stat.php?id=7146446
  247. // v6.0.14- Улучшена обработка режима "не показывать вкладки". Теперь должно нормально работать
  248. // v6.0.15- Исправлено улучшение обработка режима "не показывать вкладки". Теперь должно нормально работать
  249. // v6.1.0- Исправлено замеченные ошибки, немного оптимизировано.
  250. // v6.1.1
  251. // - Бои Гильдии Лидеров не сбрасывают таймер трудоголика
  252. // - Добавлен @homepage
  253. // - Исправление конфликтов с другими скриптами
  254. // v6.1.2
  255. // - Исправлено определение Блага Абу-Бекра
  256. // - Бои Гильдии Лидеров: Культа Солнца не сбрасывают таймер трудоголика
  257. // 2018.12.27 v6.1.4- Бои Гильдии Лидеров: Охота на Гринча не сбрасывают таймер трудоголика
  258. // 2019.01.03 v6.1.5- Удалена конвертация старых настроек
  259. // 2019.01.09 v6.1.6- Исправлен таймер здоровья с бонусом НГ+домом
  260.  
  261. // (c) 2020, перф. 10.12.2018 v.6.2: Изменение обнаружения охоты
  262. // (c) 2022, перф. 08.12.2022 v.6.3: Изменение обнаружения таймера охоты, v.6.4: Таймер ГР в "Новом оформлении страницы персонажа".
  263. // (c) 01.02.2023 6.4.4, перф: изменение кода отображения времени следующей охоты на странице карты. homepage https://gf.qytechs.cn/ru/scripts/418440-hwm-time-restore
  264. // [6.2] 10.12.2020 [*] Изменение обнаружения охоты.
  265. // [6.3] 08.12.2022 [*] Изменение обнаружения таймера охоты, из-за антигринда.
  266. // [6.4] 08.12.2022 [*] Таймер ГР в "Новом оформлении страницы персонажа".
  267. // [6.4.4] 01.03.2023 [*] Изменение кода отображения времени следующей охоты на странице карты.
  268.  
  269. // 01.12.2023 Tamozhnya1 7.0 Добавлена работоспособность с новой шапкой и новой страницей персонажа.
  270. // Из hwmTransporter взял определение разультатов боя из таблички результатов в конце боя, определение здоровья, анализ протокола боев для таймера ГВ.
  271. // В способы оповещения добавлены уведомления Windiws
  272. // Аудио можно прогрывать и останавливать
  273. // Добавлены таймеры лидеров и защит
  274. // Глобально переработал код скрипта, приближая его к модели MVC, и к стандартам промышленного программирования, путем правильного именования программных объектов в соответствие с их назначением в предметной области
  275.  
  276. // 10.9 - если таймер лидеров стоит, а задания не все, перезапускаем
  277.  
  278. // TODO
  279. // 1) После новогодних праздников в новом интерфейсе может случиться крах из-за того, что в нем неизвестно как определять каталог картинок (а может он останется новогодним) (РЕАЛИЗОВАНО)
  280. // 2) После каждого проигрыша армии в ГН таймер начинает отсчет и репутация падает. Т.о. после последнего боя таймер неточен из-за неправильной репутации
  281. // 3) Главная неприятность- периодически не запскаются таймеры ГО, ГН. Причина то, что при входе в бой обработчик, висящий на кнопке, не всегда успевает до того, как админский обработчик запустит бой.
  282. // Я перевесил свой обработчик на image, но кажется это не помогло.
  283.  
  284. const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
  285. if(!playerIdMatch) {
  286. return;
  287. }
  288. const PlayerId = playerIdMatch[1];
  289. const lang = document.documentElement.lang || (location.hostname == "www.lordswm.com" ? "en" : "ru");
  290. const isEn = lang == "en";
  291. const isHeartOnPage = document.querySelector("canvas#heart") || document.querySelector("div#heart_js_mobile") ? true : false;
  292. const isMobileInterface = document.querySelector("div#btnMenuGlobal") ? true : false;
  293. const isMobileDevice = mobileCheck(); // Там нет мышки
  294. const isNewInterface = document.querySelector("div#hwm_header") ? true : false;
  295. const isNewPersonPage = document.querySelector("div#hwm_no_zoom") ? true : false;
  296. const mooving = location.pathname == '/map.php' && !document.getElementById("map_right_block");
  297. const win = window.wrappedJSObject || unsafeWindow;
  298. const BattleResult = { NotFound: 0, Win: 1, Fail: 2 };
  299. const timerNames = ["health", "work", "smith", "merc", "hunt", "thief", "leader", "watcher", "defence", "mana"];
  300. const timeFormats = { full: 1, hoursOrSeconds: 2, secondsLastMinute: 3 };
  301. const timerSettings = {
  302. leader: {
  303. timeFormat: timeFormats.hoursOrSeconds
  304. },
  305. watcher: {
  306. timeFormat: timeFormats.hoursOrSeconds
  307. },
  308. defence: {
  309. timeFormat: timeFormats.secondsLastMinute,
  310. isShowNotEmptyOnly: true
  311. }
  312. };
  313. const audioScenaries = ["health", "work", "smith", "warlike", "leader", "watcher", "defence"];
  314. const timersAudioMap = {"health": "health", "work": "work", "smith": "smith", "merc": "warlike", "hunt": "warlike", "thief": "warlike", "leader": "leader", "watcher": "watcher", "defence": "defence"};
  315. const fractions = isEn ? ["Knight", "Necromancer", "Wizard", "Elf", "Barbarian", "Dark elf", "Demon", "Dwarf", "Tribal", "Pharaoh"] : ["Рыцарь", "Некромант", "Маг", "Эльф", "Варвар", "Темный эльф", "Демон", "Гном", "Степной варвар", "Фараон"];
  316. const playingAudios = {};
  317. let defaultAudio;
  318. let texts;
  319. const isDevMode = false;
  320. let maxLeaderTasks = 3;
  321. let maxWatchersStars = 9;
  322. const resourcesPath = `${location.protocol}//${location.host.replace("www", "dcdn")}`;
  323. const resourcesPath1 = `${location.protocol}//${location.host.replace("www", "dcdn1")}`;
  324. const resourcesPath2 = `${location.protocol}//${location.host.replace("www", "dcdn2")}`;
  325. const resourcesPath3 = `${location.protocol}//${location.host.replace("www", "dcdn3")}`;
  326. const roulettePng = `${resourcesPath}/i/new_top/_panelRoulette.png`;
  327. const duelsPng = `${resourcesPath3}/i/bselect/duels.png?v=3b`;
  328. const groupsPng = `${resourcesPath}/i/bselect/groups.png?v=3b`;
  329. const turnir_icoPng = `${resourcesPath}/i/mobile_view/icons_add/turnir_ico.png`;
  330. if(location.pathname == "/home.php" || location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
  331. if(isNewPersonPage) {
  332. const levelInfoCell = Array.from(document.querySelectorAll("div.home_pers_info")).find(x => x.innerHTML.includes(isEn ? "Combat level" : "Боевой уровень"));
  333. if(levelInfoCell) {
  334. setPlayerValue("PlayerLevel", parseInt(levelInfoCell.querySelector("div[id=bartext] > span").innerText));
  335. }
  336. } else {
  337. const levelExec = new RegExp(`<b>${isEn ? "Combat level" : "Боевой уровень"}: (\\d+?)<\\/b>`).exec(document.documentElement.innerHTML);
  338. if(levelExec) {
  339. setPlayerValue("PlayerLevel", parseInt(levelExec[1]) || 1);
  340. }
  341. }
  342. }
  343. const PlayerLevel = parseInt(getPlayerValue("PlayerLevel", 1));
  344. addStyle(`
  345. .alarm-text {
  346. animation-duration: 1000ms;
  347. animation-name: blink;
  348. animation-iteration-count: infinite;
  349. animation-direction: alternate;
  350. }
  351. @keyframes blink {
  352. 0% { opacity: 0; }
  353. 30% { opacity: 0.8; }
  354. 60% { opacity: 1; }
  355. 100% { opacity: 1; }
  356. }
  357. `);
  358. _NABEG=2;_GN_OTRYAD=5;_GN_MONSTER=7;_GN_NABEGI=8;_GN_ZASHITA=10;_GN_ARMY=12;_MAL_TOUR=14;_THIEF_WAR=16;_SURVIVAL=20;_NEWGROUP=21;
  359. _ELEMENTALS=22;_GNOMES=23;_NEWKZS=24;NEWKZS=24;_NEWKZS_T=25;NEWKZS_T=25;_NEWTHIEF=26;_NEWCARAVAN=27;_NEWGNCARAVAN=29;_SURVIVALGN=28;
  360. _TUNNEL=30;_SEA=32;_HELL=33;_CASTLEWALLS=35;_UNIWAR=36;_DIFFTUR=37;_UNIWARCARAVAN=38;_PVPGUILDTEST=39;_PVPGUILD=40;_BALANCED_EVENT=41;
  361. _NECR_EVENT=42;_NECR_EVENT2=43;_HELLOWEEN=44;_SURVIVAL_GNOM=45;_DEMON_EVENT=46;_DEMON_EVENT2=47;_DEMON_EVENT3=48;_DEMON_EVENT4=49;_PVEDUEL=50;_DEMONVALENTIN=51;
  362. _QUICKTOUR=52;_BARBTE_ATTACK=53;_BARBTE_DEEP=54;_BARBTE_BOSS=55;_TRANSEVENT=56;_STEPEVENT=57;_STEPEVENT2=58;_KZS_PVE=59;_2TUR=60;_RANGER=61;
  363. _PRAET=62;_RANGER_TEST=63;_SUN_EVENT1=64;_SUN_EVENT2=65;_NEWCARAVAN2=66;_23ATTACK=67;_2TU_FAST=68;_SV_ATTACK=69;_KILLER_BOT=70;_SV_DUEL=71;
  364. _SV_WAR=72;_FAST_TEST=73;_TRUE_EVENT=74;_TIKVA_BOT=75;_TIKVA_ATTACK=76;_ELKA_DEFENSE=77;_PPE_EVENT=78;_ALTNECR_EVENT=79;_CLAN_SUR_DEF=80;_CLAN_SUR_ATT=81;
  365. _QUESTWAR=82;_BARBNEW_DEEP=83;_BARBNEW_BOSS=84;_ELKA_RESCUE=85;_REGWAR1=86;_REGWAR2=87;_CLAN_SUR_CAPT=88;_CLAN_SUR_DEF_PVP=89;_TRUE_TOUR=90;_NOOB_DUEL=91;
  366. _ALTMAG_EVENT=92;_ALTELF_EVENT=93;_NEWPORTAL_EVENT=94;_UNIGUILD=95;_PIRATE_EVENT=96;_TOUR_EVENT=97;_PAST_EVENT=98;_GOLD_EVENT=99;_FAST_TEST2=100;_OHOTA_EVENT=101;
  367. _BUNT_EVENT=102;_ZASADA_EVENT=103;_CLAN_NEW_PVP=104;_SURV_DEEP=105;_SURV_DEEP_BOSS=106;_2AND3_EVENT=107;_CASTLE_EVENT=108;_CARAVAN_EVENT=109;_CAMPAIGN_WAR=110;_NY2016=111;
  368. _ALTTE_EVENT=112;_PVP_EVENT=113;_ALTTE2_EVENT=114;_PIRATE_NEW_EVENT=115;_PVP_KR_EVENT=116;_CATCH_EVENT=117;_PVP_DIAGONAL_EVENT=118;_VILLAGE_EVENT=119;_TRAVEL_EVENT=120;_CASTLE_BATTLE2X2=121;
  369. _PVP_BOT=122;_PIRATE_SELF_EVENT=123;_2ZASADA_EVENT=124;_NEWCARAVAN3=125;_ONEDAY_EVENT=126;_CRE_EVENT=127;_GL_EVENT=128;_1ZASADA_EVENT=129;_NYGL2018_EVENT=130;_EGYPT_EVENT=131;
  370. _GL_DWARF_EVENT=132;_NAIM_MAP_EVENT=133;_2BOT_TUR=134;_CRE_SPEC=135;_CRE_INSERT=136;_CRE_TOUR=137;_GNOM_EVENT=138;_MAPHERO_EVENT=138;_NEWCRE_EVENT=139;_NEWOHOTA_EVENT=140;
  371. _2SURVIVAL=141;_ADVENTURE_EVENT=142;_AMBUSHHERO_EVENT=143;_FRACTION_EVENT=144;_PVP_KZS=145;_REAPING_MAP_EVENT=147;
  372.  
  373. const periodicEvents = getPeriodicEvents().filter(x => Number(x.period) > 0 && x.text && (x.isWinNotification || x.isSoundNotification));
  374. periodicEvents.forEach(x => x.eventsArray = getEventsArray(x));
  375. //console.log(periodicEvents);
  376.  
  377. main();
  378. function main() {
  379. addStyle(`
  380. .button-62 {
  381. background: linear-gradient(to bottom right, #E47B8E, #FF9A5A);
  382. border: 0;
  383. border-radius: 5px;
  384. color: #FFFFFF;
  385. cursor: pointer;
  386. display: inline-block;
  387. font-family: -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  388. font-size: 16px;
  389. font-weight: 500;
  390. outline: transparent;
  391. padding: 0 5px;
  392. text-align: center;
  393. text-decoration: none;
  394. transition: box-shadow .2s ease-in-out;
  395. user-select: none;
  396. -webkit-user-select: none;
  397. touch-action: manipulation;
  398. white-space: nowrap;
  399. }
  400.  
  401. .button-62:not([disabled]):focus {
  402. box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
  403. }
  404.  
  405. .button-62:not([disabled]):hover {
  406. box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
  407. }
  408. .button-62:disabled,button[disabled] {
  409. background: linear-gradient(177.9deg, rgb(58, 62, 88) 3.6%, rgb(119, 127, 148) 105.8%);
  410. }
  411. table.smithTable {
  412. width: 100%;
  413. background: BurlyWood;
  414. border: 5px solid BurlyWood;
  415. border-radius: 5px;
  416. margin-top: 1px;
  417. }
  418. table.smithTable th {
  419. border: 1px none #f5c137;
  420. overflow: hidden;
  421. text-align: center;
  422. font-size: 11px;
  423. }
  424. table.smithTable td {
  425. border: 1px none #f5c137;
  426. overflow: hidden;
  427. text-align: center;
  428. }
  429. table.smithTable tr:nth-child(odd) {
  430. background: Wheat;
  431. }
  432. table.smithTable tr:nth-child(even) {
  433. background: white;
  434. }
  435. .waiting {
  436. cursor: wait;
  437. }
  438. .not-allowed {
  439. cursor: not-allowed;
  440. }
  441. `);
  442. initUserName();
  443. verifyOptionKeys();
  444. texts = setTexts();
  445. requestServerTime();
  446. checkPremiumAccount();
  447. preloadDefaultAudio();
  448. if(location.pathname == '/war.php') {
  449. inBattle(); // в бою
  450. return;
  451. }
  452. if(!isHeartOnPage) {
  453. return;
  454. }
  455.  
  456. const [army_percent] = healthTimer();
  457. manaTimer();
  458. if(location.pathname == "/home.php") {
  459. setPlayerValue("IsDeer", document.querySelector("img[src*='deer2.png']") ? true : false);
  460. }
  461. checkHuntLicense();
  462. checkWork();
  463. checkWorkaholic();
  464. checkMercenary();
  465. checkLeaders();
  466. checkWatchers();
  467. checkRangerGuild();
  468. checkModWorkebench();
  469. checkDefences();
  470. checkAmbushResult();
  471. if(location.pathname == '/map.php') {
  472. checkThiefAmbush();
  473. checkRangerAmbush();
  474. checkMapHunter();
  475. }
  476. createTimersPanel();
  477. timersPanelDataBind();
  478. tick();
  479. skillPotions();
  480. if(getPlayerBool("viewNativeTimer") && !isMobileInterface) {
  481. const container = isNewInterface ? document.querySelector("div.sh_MenuPanel") : document.querySelector("table#main_top_table>tbody>tr>td>table>tbody>tr:nth-of-type(3)>td>table>tbody>tr>td:nth-last-of-type(1)");
  482. const containerRect = container.getBoundingClientRect();
  483. addStyle(`
  484. .nativeTimerViewer {
  485. position: absolute;
  486. width: 80px;
  487. height: auto;
  488. color: white;
  489. font-size: 18px;
  490. font-weight: bold;
  491. box-shadow: 2px 3px 20px black, 0 0 60px #8a4d0f inset;
  492. background: #fffef0;
  493. text-align: center;
  494. }
  495. `);
  496. if(isNewInterface) {
  497. addStyle(`
  498. .nativeTimerViewer {
  499. top: ${containerRect.height}px;
  500. left: ${containerRect.width}px;
  501. }
  502. `);
  503. } else {
  504. addStyle(`
  505. .nativeTimerViewer {
  506. top: ${0}px;
  507. right: ${0}px;
  508. }
  509. `);
  510. }
  511. if(win.Timer) {
  512. const nativeTimerViewer = addElement("div", { class: "nativeTimerViewer", title: isEn ? "Native refresh timer. Pause/Run." : "Встроенный таймер обновления. Остановить/Запустить." }, container);
  513. nativeTimerViewer.addEventListener("click", function() { if(win.Timer >= 0) { clearTimeout(win.Timer); win.Timer = undefined; } else { win.Refresh(); } });
  514. if(!isNewInterface) {
  515. container.style.position = "relative";
  516. }
  517. tickNativeTimer();
  518. }
  519. }
  520. }
  521. function tickNativeTimer() {
  522. setTimeout(tickNativeTimer, 1000);
  523. const nativeTimerViewer = document.querySelector("div.nativeTimerViewer");
  524. if(getPlayerBool("viewNativeTimer")) {
  525. if(win.Timer) {
  526. const container = isNewInterface ? document.querySelector("div.sh_MenuPanel") : document.querySelector("table#main_top_table>tbody>tr>td>table>tbody>tr:nth-of-type(3)>td>table>tbody>tr>td:nth-last-of-type(1)");
  527. const containerRect = container.getBoundingClientRect();
  528. // nativeTimerViewer.style.top = `${containerRect.height}px`;
  529. // nativeTimerViewer.style.left = `${containerRect.width}px`;
  530. nativeTimerViewer.innerText = win.Delta.toLocaleString();
  531. }
  532. } else {
  533. nativeTimerViewer.style.display = "none";
  534. }
  535. }
  536. function skillPotions() {
  537. if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
  538. // Обновлям данные о выпитых зельях фракции
  539. // Skill potion for Dwarf faction till 2024-02-06 11:07
  540. // Зелье фракции Гном до 06-02-24 11:07
  541. const skillPotionExpirationRegExp = isEn ? new RegExp("Skill potion for (Knight|Necromancer|Wizard|Elf|Barbarian|Dark elf|Demon|Dwarf|Tribal|Pharaoh) faction till (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})", "g") : new RegExp("Зелье фракции (Рыцарь|Некромант|Маг|Эльф|Варвар|Темный эльф|Демон|Гном|Степной варвар|Фараон) до (\\d{2}-\\d{2}-\\d{2} \\d{2}:\\d{2})", "g");
  542. let skillPotionExpirationRegExpResult;
  543. while(skillPotionExpirationRegExpResult = skillPotionExpirationRegExp.exec(document.body.innerHTML)) {
  544. const fractionName = skillPotionExpirationRegExpResult[1];
  545. const expirationDateText = skillPotionExpirationRegExpResult[2];
  546. //console.log(`fractionName: ${fractionName}, expirationDateText: ${expirationDateText}, ${parseDate(expirationDateText, true).toLocaleString()}`);
  547. setPlayerValue(`SkillPotionExpirationTime${fractionName}`, parseDate(expirationDateText, true).getTime() + 60000);
  548. }
  549. }
  550. if(location.pathname == "/home.php") {
  551. if(isNewPersonPage) {
  552. const fractionsSpans = Array.from(document.querySelectorAll("div.home_left_column > div:first-child div[id=row] span.home_guild_text"));
  553. //console.log(fractionsSpans)
  554. fractionsSpans.forEach(x => {
  555. const skillPotionExpirationTime = getPlayerValue(`SkillPotionExpirationTime${x.innerText}`);
  556. if(skillPotionExpirationTime) {
  557. x.insertAdjacentHTML("afterend", `<img style="vertical-align: text-top;" width="20px" height="20px" src="${resourcesPath3}/i/artifacts/skill_drink_b.png" title="${(isEn ? "Skill potion till" : "Зелье фракции до") + " " + new Date(parseInt(skillPotionExpirationTime)).toLocaleString() }">`);
  558. }
  559. });
  560. } else {
  561. const cells = Array.from(document.querySelectorAll("td"));
  562. const skillInfoCell = cells.find(x => x.innerHTML.includes(isEn ? "Knight:" : "Рыцарь:") && x.innerHTML.includes(isEn ? "Enchanters' guild" : "Гильдия Оружейников") && !x.innerHTML.includes("<td"));
  563. if(!skillInfoCell) {
  564. return;
  565. }
  566. for(const fraction of fractions) {
  567. const skillPotionExpirationTime = getPlayerValue(`SkillPotionExpirationTime${fraction}`);
  568. if(skillPotionExpirationTime) {
  569. const fractionNode = findSequentialByValue(skillInfoCell, x => x.nodeName == "#text" && x.textContent.includes(fraction));
  570. //console.log(fractionNode)
  571. addElement("img", { style: "vertical-align: sub; width: 16px; height: 16px;", src: `${resourcesPath3}/i/artifacts/skill_drink_b.png`, title: (isEn ? "Skill potion till" : "Зелье фракции до") + " " + new Date(parseInt(skillPotionExpirationTime)).toLocaleString() }, fractionNode, "afterend");
  572. }
  573. }
  574. }
  575. }
  576. for(const fraction of fractions) {
  577. const skillPotionExpirationTime = getPlayerValue(`SkillPotionExpirationTime${fraction}`);
  578. if(skillPotionExpirationTime && parseInt(skillPotionExpirationTime) < getServerTime()) {
  579. GM.notification(isEn ? `The potion of the ${fraction} faction has expired` : `Истекло время действия зелья фракции ${fraction}`, "ГВД", `${resourcesPath3}/i/artifacts/skill_drink_b.png`);
  580. deletePlayerValue(`SkillPotionExpirationTime${fraction}`);
  581. }
  582. }
  583. }
  584. function verifyOptionKeys() {
  585. const defaultOptions = {
  586. healthNotification: false,
  587. healthSound: "",
  588.  
  589. isShowWorkTimer: true,
  590. workNotification: true,
  591. workSound: "",
  592. enrollNumber: "0",
  593. showWorkaholicAlarmLastTwoEnrolls: true,
  594. disableWorkaholicAlarm: false,
  595.  
  596. isShowSmithTimer: true,
  597. smithNotification: true,
  598. smithSound: "",
  599.  
  600. isShowMercTimer: true,
  601. mercNotification: true,
  602. warlikeSound: "",
  603.  
  604. isShowHuntTimer: true,
  605. huntNotification: true,
  606. huntLicenseRate: "1",
  607. huntLicenseExpirationTime: "0",
  608. huntLicenseText: "",
  609.  
  610. isShowThiefTimer: true,
  611. thiefNotification: true,
  612. thiefOrRanger: false, // false is thief
  613. joinRangerBattle: false,
  614.  
  615. isShowLeaderTimer: true,
  616. leaderNotification: true,
  617. leaderSound: "",
  618.  
  619. isShowWatcherTimer: false,
  620. watcherNotification: true,
  621. watcherSound: "",
  622.  
  623. isShowDefenceTimer: false,
  624. defenceNotification: true,
  625. defenceSound: "",
  626.  
  627. customTimeRate: "1",
  628. abuBlessRate: "1",
  629. abuBlessExpirationTime: "0",
  630. abuBlessInfo: ""
  631. };
  632. const options = JSON.parse(getPlayerValue("hwmTimersOptions", JSON.stringify(defaultOptions)));
  633. Object.keys(defaultOptions).forEach(x => {
  634. if(!options.hasOwnProperty(x)) {
  635. options[x] = defaultOptions[x];
  636. }
  637. if(options[x] == "yes") {
  638. options[x] = true;
  639. }
  640. if(options[x] == "no") {
  641. options[x] = false;
  642. }
  643. if(options[x] == "1" && (x.startsWith("isShow") || ["showWorkaholicAlarmLastTwoEnrolls", "joinRangerBattle", "thiefOrRanger", "disableWorkaholicAlarm"].includes(x))) {
  644. options[x] = true;
  645. }
  646. if(options[x] == "0" && (x.startsWith("isShow") || ["showWorkaholicAlarmLastTwoEnrolls", "joinRangerBattle", "thiefOrRanger", "disableWorkaholicAlarm"].includes(x))) {
  647. options[x] = false;
  648. }
  649. });
  650. Object.keys(options).forEach(x => { if(!defaultOptions.hasOwnProperty(x)) { delete options[x]; } });
  651. setPlayerValue("hwmTimersOptions", JSON.stringify(options));
  652. //console.log(options);
  653. //console.log(Object.keys(options).reduce((t, x) => t + `${x}: ${typeof x}, ${options[x]}, ${typeof options[x]}\n`, ""))
  654. }
  655. function inBattle() {
  656. if(/warlog\|0/.exec(document.querySelector("html").innerHTML)) {
  657. //flash & html: warlog|0| -> бой происходит сейчас, warlog|1| -> запись боя, |player|7146446| -> id текущего игрока
  658. const playerIdExec = /\|player\|(\d+)\|/.exec(document.querySelector("html").innerHTML);
  659. if(playerIdExec && playerIdExec[1] == PlayerId) {
  660. const finalResultDiv = document.getElementById("finalresult_text");
  661. if(finalResultDiv.innerHTML.length <= 10) {
  662. observe(finalResultDiv, parseBattleResultPanel);
  663. }
  664. const battleMinute = new Date(getServerTime());
  665. battleMinute.setSeconds(0, 0);
  666. //console.log(`enableBattleNotification: ${getPlayerBool("enableBattleNotification")}, enableBattleSoundNotification: ${getPlayerBool("enableBattleSoundNotification")}, battleNotificationTime: ${new Date(parseInt(getPlayerValue("battleNotificationTime", 0))).toLocaleString()}, battleMinute: ${battleMinute.toLocaleString()}`);
  667. if(document.hidden && (getPlayerBool("enableBattleNotification") || getPlayerBool("enableBattleSoundNotification")) && parseInt(getPlayerValue("battleNotificationTime", 0)) < battleMinute.getTime()) {
  668. setPlayerValue("battleNotificationTime", battleMinute.getTime());
  669. if(getPlayerBool("enableBattleNotification")) {
  670. GM.notification(isEn ? "Battle started" : "Битва началась", "ГВД", "https://dcdn.heroeswm.ru/i/new_top/_panelBattles.png", function() { window.focus(); });
  671. }
  672. if(getPlayerBool("enableBattleSoundNotification")) {
  673. new Audio("http://commondatastorage.googleapis.com/codeskulptor-assets/week7-brrring.m4a").play();
  674. }
  675. }
  676. }
  677. }
  678. }
  679. function parseBattleResultPanel() {
  680. const finalResultDiv = document.getElementById("finalresult_text");
  681. if(finalResultDiv.innerHTML.length > 10) {
  682. const bolds = finalResultDiv.querySelectorAll("font b");
  683. let result = "fail";
  684. for(const bold of bolds) {
  685. if(bold.innerHTML == (isEn ? "Victorious:" : "Победившая сторона:")) {
  686. //console.log(`${bold.parentNode.nextSibling.nextSibling.firstChild.innerText}, UserName: ${getPlayerValue("UserName")}`);
  687. if(bold.parentNode.nextSibling.nextSibling.firstChild.innerText == getPlayerValue("UserName")) {
  688. result = "win";
  689. }
  690. break;
  691. }
  692. }
  693. let expirience = 0;
  694. let skill = 0;
  695. const myResult = finalResultDiv.innerHTML.split("<br>").find(x => x.includes(getPlayerValue("UserName")));
  696. if(myResult) {
  697. const myResultExec = new RegExp(`(\\d+) ${isEn ? "exp" : "опыт"}.+ (\\d+\\.?\\d*) ${isEn ? "skill" : "умен"}`).exec(myResult);
  698. if(myResultExec) {
  699. expirience = parseInt(myResultExec[1]);
  700. skill = parseFloat(myResultExec[2]);
  701. }
  702. }
  703. checkBattleResults(result, expirience, skill);
  704. return true; // Сигнал обзёрверу, чтоб заканчивал обработку
  705. }
  706. }
  707. function checkBattleResults(result, expirience, skill) {
  708. console.log(`btype: ${win.btype}, battleType: ${getPlayerValue("battleType", "")}, result: ${result}, expirience: ${expirience}, skill: ${skill}`);
  709. if(win.btype == _NEWCARAVAN2 || win.btype == _RANGER || getPlayerValue("battleType", "") == "thief") {
  710. if(result == "fail") {
  711. setPlayerValue("thiefTimeoutEnd", calcThiefTimeoutEnd());
  712. } else {
  713. deletePlayerValue("thiefTimeoutEnd");
  714. }
  715. }
  716. if(getPlayerValue("battleType", "") == "hunt") {
  717. setHuntTimeout();
  718. }
  719. if(isMercBattle()) {
  720. setMercTimeout(result);
  721. }
  722. if(isLeaderBattle()) {
  723. setLeaderTimeout(result);
  724. }
  725. if(result == "win" && !isLeaderBattle()) {
  726. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  727. let enrollNumber = parseInt(options.enrollNumber);
  728. if(skill >= 0.5) {
  729. enrollNumber = 0;
  730. } else if(skill > 0) {
  731. enrollNumber -= Math.floor(skill / 0.05);
  732. enrollNumber = Math.max(enrollNumber, 0);
  733. }
  734. if(!skill) {
  735. enrollNumber = 0;
  736. }
  737. updateOption("enrollNumber", enrollNumber);
  738. }
  739. if(result == "win" && isWatcherBattle()) {
  740. // Бои ГС
  741. let starsGained = 0;
  742. const finalResultDiv = document.getElementById("finalresult_text");
  743. if(finalResultDiv.innerHTML.indexOf(isEn ? "You managed to improve your result" : "Вы улучшили свой результат") === -1) {
  744. starsGained = document.querySelectorAll("div#finish_stars img[src*='/i/combat/star.png']").length;
  745. } else {
  746. const pointsGainedRegExp = isEn ? /,\s(.+)\sWG/ : /,\s(.+)\sочк/;
  747. const pointsGainedArr = pointsGainedRegExp.exec(finalResultDiv.innerHTML);
  748. if(pointsGainedArr) {
  749. switch(pointsGainedArr[1]) {
  750. case "0.2":
  751. case "0.3":
  752. starsGained = 1;
  753. break;
  754. case "0.5":
  755. starsGained = 2;
  756. break;
  757. }
  758. }
  759. }
  760. if(starsGained > 0) {
  761. const todayWatchersResults = JSON.parse(getPlayerValue("TodayWatchersResults", `{ "requestTime": 0, "playerLevel": ${PlayerLevel}, "starsGained": 0, "starsLeft": ${maxWatchersStars} }`));
  762. todayWatchersResults.starsGained += starsGained;
  763. todayWatchersResults.starsLeft -= starsGained;
  764. console.log(todayWatchersResults);
  765. setPlayerValue("TodayWatchersResults", JSON.stringify(todayWatchersResults));
  766. }
  767. }
  768. deletePlayerValue("battleType");
  769. }
  770. function isMercBattle() {
  771. return win.btype == _GN_OTRYAD || win.btype == _GN_MONSTER || win.btype == _GN_NABEGI || win.btype == _GN_ZASHITA || win.btype == _GN_ARMY || win.btype == _SURVIVALGN || win.btype == _NEWGNCARAVAN;
  772. }
  773. function isLeaderBattle() {
  774. return win.btype == _CRE_EVENT || win.btype == _GL_EVENT || win.btype == _GL_DWARF_EVENT || win.btype == _NYGL2018_EVENT || win.btype == _CRE_SPEC || win.btype == _CRE_INSERT || win.btype == _CRE_TOUR || win.btype == _NEWCRE_EVENT;
  775. }
  776. function isWatcherBattle() {
  777. //95, 97, 102, 101, 98, 96, 99, 103, 107, 108, 109
  778. return win.btype == _UNIGUILD || win.btype == _PIRATE_EVENT || win.btype == _TOUR_EVENT || win.btype == _PAST_EVENT || win.btype == _GOLD_EVENT || win.btype == _OHOTA_EVENT || win.btype == _BUNT_EVENT || win.btype == _ZASADA_EVENT || win.btype == _2AND3_EVENT || win.btype == _CASTLE_EVENT || win.btype == _CARAVAN_EVENT;
  779. }
  780. function calcThiefTimeoutEnd(fromTime = getServerTime()) {
  781. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  782. return fromTime + 60 * 60000 * options.customTimeRate * (getPlayerBool("IsDeer") ? 0.6 : 1) * options.abuBlessRate;
  783. }
  784. async function preloadDefaultAudio() {
  785. 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==');
  786. defaultAudio.preload = 'auto';
  787. }
  788. function setTexts() {
  789. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  790. var obj;
  791. if(isEn) {
  792. obj = {
  793. healthNotificationEnabled: 'Army restore alarm on',
  794. onceHealthNotificationEnabled: 'Alarm once at army restore',
  795. workNotificationEnabled: 'Workshift alarm on',
  796. notificationDisabled: 'Alarm off',
  797. smithNotificationEnabled: 'Blacksmith alarm on',
  798. mercNotificationEnabled: 'Mercenaries Guild alarm on',
  799. defenceNotificationEnabled: 'Defence begin alarm on',
  800. regexp_timegn0: /Come back in (\d+) minutes\./,
  801. regexp_timegn1: /\. Time left: (\d+) minutes\./,
  802. regexp_timegn2: /ou have (\d+) minutes left/,
  803. regexp_timegn3: /\. Time left: (\d+) minutes\./,
  804. regexp_timegn4: /still have (\d+) minutes/,
  805. regexp_timegn5: /you still have \d+ attempts and (\d+) minutes/,
  806. huntNotificationEnabled: 'Hunters Guild alarm on',
  807. nativeHuntTimerText: 'Next hunt available in',
  808. thiefNotificationEnabled: !options.thiefOrRanger ? 'Thieves Guild alarm on' : 'Rangers Guild alarm on',
  809. leaderNotificationEnabled: "Leader's Guild alarm on",
  810. watcherNotificationEnabled: "Watcher's Guild alarm on",
  811. regexp_timegre: /Come in (\d+) min/,
  812. enrollAgainIn: /You may enroll again in (\d+) min/,
  813. workPlace: 'Work place:',
  814. workMessage: 'LG: You may enroll again',
  815. smithMessage: 'BS: Blacksmith works are finished',
  816. mercMessage: 'MG: Mercenaries Guild has a quest for you',
  817. huntMessage: 'HG: You notice traces ...',
  818. leaderMessage: 'LG: One more task enabled',
  819. watcherMessage: 'WG: new tasks available',
  820. defenceMessage: 'Defence begining',
  821. thiefMessage: !options.thiefOrRanger ? 'TG: You may set an ambush' : 'RG: Rangers Guild has a quest for you',
  822. signalSound: 'Audio file ',
  823. alarm_mode: '<b>Timer alarm mode</b>:',
  824. alarm_mode_sound: 'audio',
  825. alarm_mode_alert: 'message',
  826. alarm_mode_both: 'notification',
  827. alarm_mode_none: 'off',
  828. healthTitle: 'health',
  829. warlikeTitle: 'MHT(R)G',
  830. leaderTitle: 'LG',
  831. watcherTitle: 'WG',
  832. defenceTitle: "defence",
  833. workTimerPanelCaption: 'LG',
  834. smithTimerPanelCaption: 'BS',
  835. smithWelcome: 'To Blacksmith',
  836. mercTimerPanelCaption: 'MG',
  837. mercWelcome: 'To Mercenaries\' Guild',
  838. huntTimerPanelCaption: 'HG',
  839. huntTimerPanelTitle: 'To Hunters\' Guild',
  840. thiefTimerPanelCaption: !options.thiefOrRanger ? 'TG' : 'RG',
  841. thiefWelcome: !options.thiefOrRanger ? 'To Thieves\' Guild' : 'To Rangers Guild post',
  842. leaderWelcome: "To leaders guild",
  843. leaderTimerPanelCaption: "LG",
  844. watcherTimerPanelCaption: "WG",
  845. defenceTimerPanelCaption: "D",
  846. defenceWelcome: 'Defences',
  847. watcherWelcome: "Watchers' guild",
  848. manaWelcome: 'Settings',
  849. successfullyEnrolled: 'You have successfully enrolled',
  850. currentlyUnemployed: 'You are currently unemployed',
  851. regexp_map_go: 'During the journey you have access to the',
  852. huntLicenseExpirationMessage: 'The license expires ',
  853. resetTimersTitle: 'Reset all timers',
  854. setOnceThiefTimeout: 'Set TG/RG timer for once to',
  855. cusomRateTitle: 'Quests HG, MG, TG, RG more often',
  856. joinRangerBattleText: 'Immediately initiate Rangers\' guild battle on arrival',
  857. isShowTimersTitle: 'Show timers',
  858. showWorkaholicAlarmLastTwoEnrollsTitle: 'Notify about workaholic penalty only 2 workshifts away',
  859. disableWorkaholicAlarmTitle: 'Turn off workaholic penalty notifications',
  860. huntLicenseAuto: '<b>Hunter license</b> is detected automatically in Hunters\' Guild',
  861. hide: 'Hide',
  862. alreadyEemployed: 'You are already employed\.',
  863. passedLessThanOneHour: 'Less than one hour passed since last enrollment\. Please wait\.',
  864. noVacancies: 'No vacancies\.'
  865. };
  866. } else {
  867. obj = {
  868. healthNotificationEnabled: 'Будет предупреждение о восстановлении армии',
  869. onceHealthNotificationEnabled: 'Установить единоразово предупреждение о восстановлении армии',
  870. workNotificationEnabled: 'Будет предупреждение о конце рабочего часа',
  871. notificationDisabled: 'Не будет предупреждения',
  872. smithNotificationEnabled: 'Будет предупреждение о завершении работ в Кузнице',
  873. mercNotificationEnabled: 'Будет предупреждение Гильдии Наемников',
  874. defenceNotificationEnabled: 'Будет предупреждение о начале защиты',
  875. regexp_timegn0: /Приходи через (\d+) мин/,
  876. regexp_timegn1: /Осталось времени: (\d+) минут/,
  877. regexp_timegn2: /тебя осталось (\d+) минут/,
  878. regexp_timegn3: /у тебя еще есть (\d+) минут/,
  879. regexp_timegn4: /\. Осталось (\d+) минут\./,
  880. regexp_timegn5: /осталось \d+ попыток и (\d+) минут/,
  881. huntNotificationEnabled: 'Будет предупреждение Гильдии Охотников',
  882. nativeHuntTimerText: 'Следующая охота будет доступна через',
  883. thiefNotificationEnabled: !options.thiefOrRanger ? 'Будет предупреждение Гильдии Воров' : 'Будет предупреждение Гильдии Рейнджеров',
  884. leaderNotificationEnabled: 'Будет предупреждение Гильдии лидеров',
  885. watcherNotificationEnabled: 'Будет предупреждение Гильдии стражей',
  886. regexp_timegre: /приходи через (\d+) мин/,
  887. enrollAgainIn: /Вы можете устроиться на работу через (\d+)/,
  888. workPlace: 'Место работы:',
  889. workMessage: 'ГР: Пора на работу',
  890. smithMessage: 'ГК: Работа в Кузнице завершена',
  891. mercMessage: 'ГН: Для Вас есть задание в Гильдии Наемников',
  892. huntMessage: 'ГО: Вы увидели следы ...',
  893. leaderMessage: 'ГЛ: Ещё одно задание доступно',
  894. watcherMessage: 'ГС: задания обновились',
  895. defenceMessage: 'Защита началась',
  896. thiefMessage: !options.thiefOrRanger ? 'ГВ: Вы можете устроить засаду' : 'ГРж: Есть задание в Гильдии Рейнджеров',
  897. signalSound: 'Звук сигнала ',
  898. alarm_mode: '<b>Режим оповещения</b> окончания таймера:',
  899. alarm_mode_sound: 'звук',
  900. alarm_mode_alert: 'сообщение',
  901. alarm_mode_both: 'оповещение',
  902. alarm_mode_none: 'отключен',
  903. healthTitle: 'здоровья',
  904. warlikeTitle: 'ГОНВ(Рж)',
  905. leaderTitle: 'ГЛ',
  906. watcherTitle: 'ГС',
  907. defenceTitle: "защиты",
  908. workTimerPanelCaption: 'ГР',
  909. smithTimerPanelCaption: 'ГК',
  910. smithWelcome: 'В Кузницу',
  911. mercTimerPanelCaption: 'ГН',
  912. mercWelcome: 'В здание Гильдии Наемников',
  913. huntTimerPanelCaption: 'ГО',
  914. huntTimerPanelTitle: 'В здание Гильдии Охотников',
  915. thiefTimerPanelCaption: !options.thiefOrRanger ? 'ГВ' : 'ГРж',
  916. thiefWelcome: !options.thiefOrRanger ? 'В здание Гильдии Воров' : 'В здание Гильдии Рейнджеров',
  917. leaderWelcome: "В гильдию лидеров",
  918. leaderTimerPanelCaption: "ГЛ",
  919. watcherTimerPanelCaption: "ГС",
  920. defenceTimerPanelCaption: "З",
  921. defenceWelcome: 'Защиты',
  922. watcherWelcome: "Гильдия стражей",
  923. manaWelcome: 'Настройки',
  924. successfullyEnrolled: 'Вы устроены на работу',
  925. currentlyUnemployed: 'Вы нигде не работаете',
  926. regexp_map_go: 'Во время пути Вам доступны',
  927. huntLicenseExpirationMessage: 'Лицензия истекает ',
  928. resetTimersTitle: 'Обнулить все таймеры',
  929. setOnceThiefTimeout: 'Единоразово установить таймер ГВ/ГРж равным',
  930. cusomRateTitle: 'Задания ГО, ГН, ГВ, ГРж чаще на',
  931. joinRangerBattleText: 'По прибытии вступать в бои Гильдии Рейнджеров',
  932. isShowTimersTitle: 'Отображать',
  933. showWorkaholicAlarmLastTwoEnrollsTitle: 'Показывать штраф трудоголика только за 2 часа',
  934. disableWorkaholicAlarmTitle: 'Отключить уведомления о штрафе трудоголика',
  935. huntLicenseAuto: '<b>Лицензия охотника</b> определяется автоматически (в Гильдии Охотников)',
  936. hide: 'Скрывать',
  937. alreadyEemployed: 'Вы уже устроены\.',
  938. passedLessThanOneHour: 'Прошло меньше часа с последнего устройства на работу\. Ждите\.',
  939. noVacancies: 'Нет рабочих мест\.'
  940. };
  941. }
  942. return obj;
  943. }
  944. function createTimersPanel() {
  945. let newYearSuffix = document.querySelector("img[src*='i/top_ny']") || document.querySelector("img[src*='i/new_top_ny']") ? "_" : ""; // если новый год
  946. let folder = newYearSuffix ? `${resourcesPath2}/i/top_ny_rus/line/` : `${resourcesPath2}/i/top/line/`;
  947. const img_link = document.querySelector("img[src*='i/top'][src*='/line/t_end']");
  948. if(img_link) {
  949. folder = /(\S*\/line\/)/.exec(img_link.src)[1];
  950. } else {
  951. folder = `${resourcesPath2}/i/top_ny_rus/line/`; // Оставим для новой шапки синий, новогодний цвет
  952. newYearSuffix = "_";
  953. }
  954. //console.log(`folder: ${folder}, newYearSuffix: ${newYearSuffix}, ${folder}t_end${newYearSuffix}.jpg`); // folder: https://dcdn2.heroeswm.ru/i/top_ny_rus/line/, newYearSuffix: _
  955.  
  956. let container;
  957. let timersContainerWidth;
  958. let zIndex = 0;
  959. let leftMargin = 0;
  960. let topMargin = 0;
  961. let height;
  962. let widthStyle = "";
  963. let minWidthStyle = "";
  964. let background = "";
  965. let boxShadow = "";
  966. if(isMobileInterface) {
  967. const panelResourses = document.querySelector("div#panel_resourses");
  968. if(!panelResourses) {
  969. return;
  970. }
  971. container = panelResourses;
  972. const gold = panelResourses.querySelectorAll("div.panel_res_link.panel_res_link_add")[1];
  973. leftMargin = gold.getBoundingClientRect().right - panelResourses.getBoundingClientRect().left + 2;
  974. height = panelResourses.getBoundingClientRect().height - 2;
  975. background = `background: url(${folder}t_com_bkg${newYearSuffix}.jpg);`;
  976. boxShadow = `box-shadow: inset 0 0 0 1px #e2b77d, inset 0 0 4px rgba(0,0,0,.5), inset 0 -${parseInt(panelResourses.getBoundingClientRect().height/2)}px 10px rgba(0,0,0,.5), 0 1px 7px rgba(0,0,0,.7);`;
  977. } else if(isNewInterface) {
  978. container = document.querySelector("div.sh_MenuPanel");
  979. const shContainer = document.querySelector("div.sh_container");
  980. timersContainerWidth = shContainer.getBoundingClientRect().width - 30 * 2;
  981. minWidthStyle = `text-align: center; min-width: ${timersContainerWidth}px;`
  982. topMargin = -65;
  983. leftMargin = (container.getBoundingClientRect().width - timersContainerWidth) / 2;
  984. zIndex = 2;
  985. height = 22;
  986. document.querySelector("div#ResourcesPanel").style.height = "22px";
  987. background = `background-color: #4f76a7;`;
  988. boxShadow = "box-shadow: inset 0 0 0 1px #e2b77d, inset 0 0 4px rgba(0,0,0,.5), inset 0 -12px 10px rgba(0,0,0,.5), 0 1px 7px rgba(0,0,0,.7);";
  989. } else {
  990. const dragonLeft = document.querySelector("img[src*='i/top'][src*='/dragon__left']"); // https://dcdn1.heroeswm.ru/i/top_ny_rus/dragon__left_.jpg
  991. const dragonRight = document.querySelector("img[src*='i/top'][src*='/dragon__right']");
  992. container = dragonLeft.parentNode;
  993. timersContainerWidth = dragonRight.getBoundingClientRect().left - dragonLeft.getBoundingClientRect().left + 124;
  994. widthStyle = `width: ${timersContainerWidth}px;`;
  995. topMargin = -26;
  996. leftMargin = -43;
  997. height = 22;
  998. background = `background: url(${folder}t_com_bkg${newYearSuffix}.jpg);`;
  999. boxShadow = "box-shadow: inset 0 0 0 1px #e2b77d;";
  1000. }
  1001. addStyle(`
  1002. .timers-container {
  1003. position: absolute;
  1004. margin: ${topMargin}px 0px 0px ${leftMargin}px;
  1005. text-align: center;
  1006. z-index: ${zIndex};
  1007. }
  1008. .timers-table * {
  1009. font-size: 11px;
  1010. height: ${height}px;
  1011. color: #f5c137;
  1012. }
  1013. .timers-cell {
  1014. text-align: center;
  1015. ${background}
  1016. ${boxShadow}
  1017. white-space: nowrap;
  1018. font-weight: bold;
  1019. border-radius: 5px;
  1020. }
  1021. .timers-cell span {
  1022. cursor: pointer;
  1023. }
  1024. .timers-cell a {
  1025. text-decoration: none;
  1026. }
  1027. `);
  1028. let timersHtml = "";
  1029. for(const timer of timerNames.map(x => { return { name: `${x}Timer`, caption: texts[`${x}TimerPanelCaption`] ? texts[`${x}TimerPanelCaption`] + ": " : "" }; })) {
  1030. timersHtml += `<td id="${timer.name}Cell" class="timers-cell"><span id="${timer.name}PanelCaption">${timer.caption}</span><a id="${timer.name}Panel">00:00</a></td>`;
  1031. }
  1032. const timersPanel = addElement("div", { class: "timers-container", innerHTML: `<table cellpadding=0 cellspacing=0 align="center" class="timers-table" style="${widthStyle}${minWidthStyle}"><tr>${timersHtml}</tr></table>` }, container);
  1033. timersPanel.querySelector('#healthTimerPanel').addEventListener("click", function() { updateOption("healthNotification", x => !x.healthNotification); timersPanelDataBind(); });
  1034. timersPanel.querySelector('#workTimerPanelCaption').addEventListener("click", function() { updateOption("workNotification", x => !x.workNotification); timersPanelDataBind(); });
  1035. timersPanel.querySelector('#smithTimerPanelCaption').addEventListener("click", function() { updateOption("smithNotification", x => !x.smithNotification); timersPanelDataBind(); });
  1036. timersPanel.querySelector('#mercTimerPanelCaption').addEventListener("click", function() { updateOption("mercNotification", x => !x.mercNotification); timersPanelDataBind(); });
  1037. timersPanel.querySelector('#huntTimerPanelCaption').addEventListener("click", function() { updateOption("huntNotification", x => !x.huntNotification); timersPanelDataBind(); });
  1038. timersPanel.querySelector('#thiefTimerPanelCaption').addEventListener("click", function() { updateOption("thiefNotification", x => !x.thiefNotification); timersPanelDataBind(); });
  1039. timersPanel.querySelector('#leaderTimerPanelCaption').addEventListener("click", function() { updateOption("leaderNotification", x => !x.leaderNotification); timersPanelDataBind(); });
  1040. timersPanel.querySelector('#watcherTimerPanelCaption').addEventListener("click", function() { updateOption("watcherNotification", x => !x.watcherNotification); timersPanelDataBind(); });
  1041. timersPanel.querySelector('#defenceTimerPanelCaption').addEventListener("click", function() { updateOption("defenceNotification", x => !x.defenceNotification); timersPanelDataBind(); });
  1042.  
  1043. timersPanel.querySelector("#manaTimerPanel").addEventListener("click", settings);
  1044. }
  1045. function timersPanelDataBind() {
  1046. if(!document.getElementById("manaTimerPanel")) {
  1047. return; // timersPanel ещё не создана
  1048. }
  1049. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1050. const leaderTasks = JSON.parse(getPlayerValue("leaderTasks", "[3, 3]"));
  1051. const todayWatchersResults = JSON.parse(getPlayerValue("TodayWatchersResults", `{ "starsGained": 0, "starsLeft": ${maxWatchersStars} }`));
  1052. const timersData = {
  1053. "health": {
  1054. panelTitle: options.healthNotification ? texts.healthNotificationEnabled : texts.onceHealthNotificationEnabled,
  1055. color: options.healthNotification ? '#ff9c00' : '#f5c137'
  1056. },
  1057. "work": {
  1058. panelTitle: [options.abuBlessInfo, getWorkaholicPenaltyText()].join('\n'),
  1059. panelReference: getPlayerValue("LastWorkObjectId") ? `object-info.php?id=${getPlayerValue("LastWorkObjectId")}` : undefined,
  1060. isHideable: true,
  1061. color: !options.disableWorkaholicAlarm && options.enrollNumber > 8 ? '#ff9c00' : '#f5c137'
  1062. },
  1063. "smith": {
  1064. panelReference: "/mod_workbench.php?type=repair",
  1065. isHideable: true
  1066. },
  1067. "merc": {
  1068. panelReference: "/mercenary_guild.php",
  1069. isHideable: true
  1070. },
  1071. "hunt": {
  1072. panelTitle: [texts.huntTimerPanelTitle, options.huntLicenseText == "" ? "" : texts.huntLicenseExpirationMessage + options.huntLicenseText].join('\n'),
  1073. panelReference: "/hunter_guild.php",
  1074. isHideable: true
  1075. },
  1076. "thief": {
  1077. panelTitle: !options.thiefOrRanger ? `${isEn ? "Refresh from war log" : "Обновить из протокола боев"}` : `${isEn ? "To ganger's guild" : "В гильдию рейнджеров"}`,
  1078. //panelReference: !options.thiefOrRanger ? "/thief_guild.php" : "/ranger_guild.php",
  1079. panelReference: !options.thiefOrRanger ? "" : "/ranger_guild.php",
  1080. isHideable: true,
  1081. clickHandler: !options.thiefOrRanger ? function(e) { e.preventDefault(); checkAmbushResult(true); } : null
  1082. },
  1083. "leader": {
  1084. panelTitle: `${isEn ? "Tasks available" : "Доступно заданий"}: ${leaderTasks[0]}`,
  1085. panelReference: "/leader_guild.php",
  1086. isHideable: true,
  1087. color: leaderTasks[0] == maxLeaderTasks ? '#FF0000' : '#f5c137'
  1088. },
  1089. "watcher": {
  1090. panelTitle: isEn ? `Stars received today: ${todayWatchersResults.starsGained} from ${todayWatchersResults.starsGained + todayWatchersResults.starsLeft}` : `Получено звёзд сегодня: ${todayWatchersResults.starsGained} из ${todayWatchersResults.starsGained + todayWatchersResults.starsLeft}`,
  1091. panelReference: "/task_guild.php",
  1092. isHideable: true
  1093. },
  1094. "defence": {
  1095. panelReference: "/mapwars.php",
  1096. isHideable: true,
  1097. },
  1098. "mana": { }
  1099. };
  1100. for(const timer in timersData) {
  1101. const timerData = timersData[timer];
  1102. if(timerData.isHideable) {
  1103. const timerPanelCaption = document.getElementById(`${timer}TimerPanelCaption`);
  1104. timerPanelCaption.style.color = options[`${timer}Notification`] ? '#FF0000' : '#f5c137';
  1105. timerPanelCaption.title = options[`${timer}Notification`] ? texts[`${timer}NotificationEnabled`] : texts.notificationDisabled;
  1106. let isShow = options[`isShow${firstUpper(timer)}Timer`];
  1107. const isShowNotEmptyOnly = timerSettings[timer] && timerSettings[timer].isShowNotEmptyOnly;
  1108. if(isShowNotEmptyOnly && !getSecondsLeft(timer)) {
  1109. isShow = false;
  1110. }
  1111. document.getElementById(`${timer}TimerCell`).style.display = isShow ? '' : "none";
  1112. if(timerData.panelCaption) {
  1113. timerPanelCaption.innerText = timerData.panelCaption;
  1114. }
  1115. }
  1116. const timerPanel = document.getElementById(`${timer}TimerPanel`);
  1117. timerPanel.href = timerData.panelReference || "javascript: void(0);";
  1118. timerPanel.title = timerData.panelTitle || texts[`${timer}Welcome`];
  1119. if(timerData.clickHandler) {
  1120. timerPanel.addEventListener("click", timerData.clickHandler);
  1121. }
  1122. if(timerData.color) {
  1123. timerPanel.style.color = timerData.color;
  1124. }
  1125. }
  1126. }
  1127. function firstUpper(str) { return str[0].toUpperCase() + str.slice(1); }
  1128. function timersDataBind() {
  1129. timerNames.forEach(x => {
  1130. document.getElementById(`${x}TimerPanel`).innerHTML = secondsFormat(getSecondsLeft(x), timerSettings[x]?.timeFormat ?? timeFormats.full)
  1131. //+ (x == "leader" ? `-${JSON.parse(getPlayerValue("leaderTasks", "[3, 3]"))[0]}` : "");
  1132. });
  1133. }
  1134. function checkWork() {
  1135. if(location.pathname == '/object_do.php' || location.pathname == '/object-info.php') {
  1136. if(document.body.innerHTML.match(texts.successfullyEnrolled)) {
  1137. setWorkTimeoutEnd();
  1138. }
  1139. }
  1140. if(location.pathname == '/home.php') {
  1141. if(document.body.innerHTML.match(texts.currentlyUnemployed)) {
  1142. deletePlayerValue("workTimeoutEnd");
  1143. return;
  1144. }
  1145. //const currentlyEmployedAt = isEn ? "Currently employed at:" : "Место работы:";
  1146. const currentlyEmployedAt = isEn ? "employed" : "работы";
  1147. const workObjectRef = Array.from(document.querySelectorAll("a[href^='object-info.php']")).find(x => getParent(x, isNewPersonPage ? "span" : "td").innerHTML.includes(currentlyEmployedAt));
  1148. //console.log(workObjectRef)
  1149. const workObjectId = workObjectRef ? getUrlParamValue(workObjectRef.href, "id") : "";
  1150. // подхватывание времени окончания работы с home.php и его проверка
  1151. const enrollAgainInExec = texts.enrollAgainIn.exec(document.body.innerHTML);
  1152. if(enrollAgainInExec) {
  1153. setWorkTimeoutEnd(getServerTime() + (Number(enrollAgainInExec[1]) + 1) * 60000, workObjectId);
  1154. } else {
  1155. const enrollTimeExec = new RegExp(` ${isEn ? "since" : "с"} (\\d{1,2}:\\d{1,2})`).exec(document.body.innerHTML);
  1156. if(enrollTimeExec) {
  1157. setWorkTimeoutEnd(parseDate(enrollTimeExec[1], false, true).getTime() + (60 + 1) * 60000, workObjectId);
  1158. }
  1159. }
  1160. }
  1161. }
  1162. function checkWorkaholic() {
  1163. if(location.pathname == '/object-info.php') {
  1164. var parent_trud = document.querySelector("a[href*='objectworkers.php']");
  1165. if(parent_trud) {
  1166. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1167. const workaholicPenaltyExec = new RegExp(`\\*\\&nbsp;(0\\.?\\d?) ${isEn ? "workaholic penalty" : "штраф трудоголика"}`).exec(document.body.innerHTML);
  1168. // отработано смен
  1169. let enrollNumber = Number(options.enrollNumber);
  1170. if(workaholicPenaltyExec) {
  1171. var workaholicPenalty = Number(workaholicPenaltyExec[1]);
  1172. if(workaholicPenalty == 0.8) {
  1173. enrollNumber = 9;
  1174. } else if(workaholicPenalty == 0.7) {
  1175. enrollNumber = 10;
  1176. } else if(workaholicPenalty == 0.6) {
  1177. enrollNumber = 11;
  1178. } else if(workaholicPenalty == 0.5) {
  1179. enrollNumber = 12;
  1180. } else if(workaholicPenalty == 0.4) {
  1181. enrollNumber = 13;
  1182. } else if(workaholicPenalty == 0.2) {
  1183. enrollNumber = 14;
  1184. } else if(workaholicPenalty == 0.1 && enrollNumber < 15) {
  1185. enrollNumber = 15;
  1186. } else if(workaholicPenalty == 0 && enrollNumber < 48) {
  1187. enrollNumber = 48;
  1188. }
  1189. } else if(enrollNumber > 8) {
  1190. enrollNumber = 8;
  1191. }
  1192. //console.log(`oldEnrollNumber: ${options.enrollNumber}, enrollNumber: ${enrollNumber}, workaholicPenalty: ${workaholicPenalty}`);
  1193. updateOption("enrollNumber", enrollNumber);
  1194. if(!options.disableWorkaholicAlarm && (!options.showWorkaholicAlarmLastTwoEnrolls || enrollNumber >= 7)) {
  1195. addElement('span', { innerHTML: getWorkaholicPenaltyText(), style: enrollNumber >= 7 ? "color: red; font-weight: bold;" : "" }, parent_trud.parentNode.previousSibling.previousSibling, "beforebegin");
  1196. }
  1197. // замена "Уже устроен"
  1198. parent_trud = document.querySelector("a[href*='objectworkers.php']").parentNode.parentNode;
  1199. if(getServerTime() > parseInt(getPlayerValue("workTimeoutEnd", 0)) && (parent_trud.innerHTML.match(texts.alreadyEemployed) || (texts.alreadyEemployed = parent_trud.innerHTML.match(texts.passedLessThanOneHour)) || (texts.alreadyEemployed = parent_trud.innerHTML.match(texts.noVacancies)))) {
  1200. 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>');
  1201. }
  1202. }
  1203. }
  1204. }
  1205. function getWorkaholicPenaltyText() {
  1206. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1207. const enrollNumber = Number(options.enrollNumber);
  1208. const limits = [9, 11, 48];
  1209. if(enrollNumber < limits[0]) {
  1210. return isEn ? `Workaholic penalty through ${limits[0] - enrollNumber} enrollments` : `Штраф трудоголика через ${limits[0] - enrollNumber} устройств`;
  1211. } else if(enrollNumber < limits[1]) {
  1212. return isEn ? `It will be impossible to get a job in production through ${limits[1] - enrollNumber} enrollments` : `Невозможно устроиться на производство будет через ${limits[1] - enrollNumber} устройств`;
  1213. } else if(enrollNumber < limits[2]) {
  1214. return isEn ? `The opportunity to get a job is ending through ${limits[2] - enrollNumber} enrollments` : `Возможность устраиваться на работу заканчивается через ${limits[2] - enrollNumber} устройств`;
  1215. }
  1216. return "";
  1217. }
  1218. function setWorkTimeoutEnd(workTimeoutEnd, workObjectId) {
  1219. workTimeoutEnd = workTimeoutEnd || getServerTime() + 60 * 60000;
  1220. workObjectId = workObjectId || getUrlParamValue(location.href, "id");
  1221. const oldValue = parseInt(getPlayerValue("workTimeoutEnd", 0));
  1222. if(Math.abs(oldValue - workTimeoutEnd) > 120000) {
  1223. setPlayerValue("workTimeoutEnd", workTimeoutEnd);
  1224. setPlayerValue("LastWorkObjectId", workObjectId);
  1225. updateOption("enrollNumber", x => Number(x.enrollNumber) + 1);
  1226. }
  1227. }
  1228. function checkPremiumAccount() {
  1229. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1230. // проверка наличия эффекта блага АБУ Бекра (премиум аккаунт) // skipn=1 это 'Ознакомился' или 'Got it!'
  1231. if(location.pathname == '/home.php' && document.querySelector("img[src*='i/icons/attr_defense.png']") && !document.querySelector("a[href*='home.php?skipn=1']")) {
  1232. const starImage = document.querySelector("img[src$='i/star_extend.png']") || document.querySelector("img[src$='i/star.png']");
  1233. options.abuBlessRate = starImage ? "0.7" : "1";
  1234. options.abuBlessExpirationTime = '0';
  1235. options.abuBlessInfo = starImage ? (starImage.title || starImage.getAttribute("hint")) : '';
  1236. if(starImage) {
  1237. starImage.align = "absmiddle";
  1238. const time_prem = /(\d+-\d+-\d+ \d+:\d+)/.exec(options.abuBlessInfo);
  1239. if(time_prem) {
  1240. const abuEnd = parseDate(time_prem[1], true);
  1241. options.abuBlessExpirationTime = abuEnd.getTime();
  1242. }
  1243. }
  1244. }
  1245. if(options.abuBlessInfo && Number(options.abuBlessExpirationTime) < getServerTime()) {
  1246. options.abuBlessRate = '1';
  1247. options.abuBlessExpirationTime = '0';
  1248. options.abuBlessInfo = '';
  1249. }
  1250. if(parseInt(options.abuBlessExpirationTime) > getServerTime()) {
  1251. maxLeaderTasks = 4;
  1252. maxWatchersStars = 12;
  1253. }
  1254. setPlayerValue("hwmTimersOptions", JSON.stringify(options));
  1255. }
  1256. function checkHuntLicense() {
  1257. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1258. var form_f2 = document.querySelector("form[name='f2']");
  1259. if(location.pathname == '/hunter_guild.php' && form_f2) {
  1260. while(form_f2.tagName != 'TR') {
  1261. form_f2 = form_f2.parentNode;
  1262. }
  1263. options.huntLicenseRate = '1';
  1264. options.huntLicenseExpirationTime = '0';
  1265. options.huntLicenseText = '';
  1266. if(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/.exec(form_f2.innerHTML)) {
  1267. if(!form_f2.querySelector("input[type='submit'][onclick*='confirm']")) {
  1268. // лицензия МО
  1269. options.huntLicenseRate = '' + (50 / 100);
  1270. } else {
  1271. // лицензия О
  1272. options.huntLicenseRate = '' + (75 / 100);
  1273. }
  1274. const forms = form_f2.querySelectorAll("td");
  1275. var time_lic_mo_max = 0;
  1276. for(const form of forms) {
  1277. if(form.innerHTML.indexOf("<td") != -1) {
  1278. continue;
  1279. }
  1280. var time_lic_mo = /(\d+-\d+-\d+ \d+:\d+)/.exec(form.innerHTML);
  1281. if(time_lic_mo) {
  1282. const licEndTime = parseDate(time_lic_mo[1], true).getTime();
  1283. if(licEndTime > time_lic_mo_max) {
  1284. time_lic_mo_max = licEndTime;
  1285. options.huntLicenseExpirationTime = licEndTime;
  1286. options.huntLicenseText = time_lic_mo[0];
  1287. }
  1288. }
  1289. }
  1290. }
  1291. }
  1292. if(options.huntLicenseText && Number(options.huntLicenseExpirationTime) < getServerTime()) {
  1293. // лицензия охотника истекла
  1294. options.huntLicenseRate = '1';
  1295. options.huntLicenseExpirationTime = '0';
  1296. options.huntLicenseText = '';
  1297. }
  1298. setPlayerValue("hwmTimersOptions", JSON.stringify(options));
  1299. }
  1300. function setHuntTimeout(restSeconds, rate = 1) {
  1301. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1302. restSeconds = restSeconds || ((getGameDate().getUTCHours() < 8 ? 20 : 40) * rate * 60 * options.customTimeRate * (getPlayerBool("IsDeer") ? 0.6 : 1) * options.abuBlessRate * options.huntLicenseRate);
  1303. setPlayerValue("huntTimeoutEnd", getServerTime() + restSeconds * 1000);
  1304. }
  1305. function skipHunt() {
  1306. setHuntTimeout(undefined, 0.5);
  1307. const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
  1308. if(map_hunt_block_div) {
  1309. observe(map_hunt_block_div, toggleNativeHuntTimerPanel, true);
  1310. }
  1311. }
  1312. function checkMercenary() {
  1313. if(location.pathname == '/mercenary_guild.php') {
  1314. const mercReputation = parseFloat(new RegExp(`${isEn ? "Reputation" : "Репутация"}: <b>([\\d\\.]+)`).exec(document.body.innerHTML)[1]);
  1315. setPlayerValue("mercReputation", mercReputation);
  1316. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1317. const mercTimeout = (40 - mercReputation * 2) * options.customTimeRate * (getPlayerBool("IsDeer") ? 0.6 : 1) * options.abuBlessRate * 60000;
  1318. let newMercTaskRestTimeExec;
  1319. if(document.querySelector("a[href^='/mercenary_guild.php?action=accept']")) {
  1320. deletePlayerValue("mercTimeoutEnd");
  1321. } 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))) {
  1322. let restTimeout = Number(newMercTaskRestTimeExec[1]);
  1323. if(texts.regexp_timegn0.exec(document.body.innerHTML) && (restTimeout == 19 || restTimeout == 13)) {
  1324. restTimeout++;
  1325. }
  1326. setMercTimeout(null, restTimeout * 60000);
  1327. }
  1328. }
  1329. }
  1330. function setMercTimeout(battleResult = null, timeout = undefined) {
  1331. console.log(`battleResult: ${battleResult}, timeout: ${timeout}`);
  1332. let mercReputation = parseFloat(getPlayerValue("mercReputation", 0));
  1333. if(battleResult == "win") {
  1334. mercReputation = Math.min(mercReputation + 0.5, 10);
  1335. setPlayerValue("mercReputation", mercReputation);
  1336. }
  1337. if(battleResult == "fail") {
  1338. mercReputation = Math.max(mercReputation - 1, 0);
  1339. setPlayerValue("mercReputation", mercReputation);
  1340. }
  1341. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1342. timeout = timeout || (40 - mercReputation * 2) * options.customTimeRate * (getPlayerBool("IsDeer") ? 0.6 : 1) * options.abuBlessRate * 60000;
  1343. const newTimeoutEnd = getServerTime() + timeout;
  1344. if(Math.abs(parseInt(getPlayerValue("mercTimeoutEnd", 0)) - newTimeoutEnd) > 70000) {
  1345. setPlayerValue("mercTimeoutEnd", newTimeoutEnd);
  1346. }
  1347. }
  1348. function checkLeaders() {
  1349. //deletePlayerValue("leaderTimeoutEnd");
  1350. const leaderTasks = JSON.parse(getPlayerValue("leaderTasks", "[3, 3]"));
  1351. if(location.pathname == '/leader_guild.php' || !getPlayerValue("leaderTimeoutEnd") && leaderTasks[0] < maxLeaderTasks) {
  1352. checkLeadersCore(); // Если таймер остановился, а заданий меньше максимума, обновим информацию. Или мы на странице лидеров
  1353. }
  1354. }
  1355. async function checkLeadersCore() {
  1356. const doc = location.pathname == '/leader_guild.php' ? document : await getRequest('/leader_guild.php');
  1357. const restTimeExec = /var Delta2 = (\d+);/.exec(doc.body.innerHTML); //var Delta2 = 6674;
  1358. const restTime = restTimeExec ? (getServerTime() + parseInt(restTimeExec[1]) * 1000) : 0;
  1359. if(restTime > 0) {
  1360. refreshTimeout("leader", restTime);
  1361. } else {
  1362. deleteValue("leaderTimeoutEnd");
  1363. }
  1364. const taskContainer = Array.from(doc.querySelectorAll("div")).find(x => x.innerHTML.startsWith(isEn ? "Challenges available" : "Доступно заданий"));
  1365. const tasksExec = new RegExp(`(\\d) ${isEn ? "of" : "из"} (\\d)`).exec(taskContainer.firstChild.textContent);
  1366. //console.log(tasksExec)
  1367. setPlayerValue("leaderTasks", JSON.stringify([parseInt(tasksExec[1]), parseInt(tasksExec[2])]));
  1368. setPlayerValue("leaderGoalsNumber", Array.from(doc.querySelectorAll("form[name=f] > input[type=submit]")).length);
  1369.  
  1370. timersPanelDataBind();
  1371. //console.log(`leaderGoalsNumber: ${getPlayerValue("leaderGoalsNumber")}, leaderTasks: ${getPlayerValue("leaderTasks")}, TimeoutEnd: ${getPlayerValue("leaderTimeoutEnd")}`); // , ${new Date(parseInt(getPlayerValue("leaderTimeoutEnd"))).toLocaleString()}
  1372. }
  1373. function checkWatchers() {
  1374. refreshTimeout("watcher", tomorrow().getTime());
  1375. const todayWatchersResults = JSON.parse(getPlayerValue("TodayWatchersResults", `{ "requestTime": 0, "playerLevel": ${PlayerLevel}, "starsGained": 0, "starsLeft": ${maxWatchersStars} }`));
  1376. //console.log(todayWatchersResults)
  1377. if(location.pathname == '/task_guild.php' || todayWatchersResults.requestTime + 3600000 < getServerTime() && todayWatchersResults.starsLeft > 0 || todayWatchersResults.requestTime < today().getTime() || todayWatchersResults.playerLevel < PlayerLevel) {
  1378. checkWatchersCore();
  1379. }
  1380. }
  1381. async function checkWatchersCore() {
  1382. const doc = location.pathname == '/task_guild.php' ? document : await getRequest('/task_guild.php');
  1383. const starsGained = doc.querySelectorAll("img[src*='/i/zvezda.png']").length;
  1384. const starsLeft = doc.querySelectorAll("img[src*='/i/zvezda_empty.png']").length;
  1385. setPlayerValue("TodayWatchersResults", JSON.stringify({ requestTime: getServerTime(), playerLevel: PlayerLevel, starsGained: starsGained, starsLeft: starsLeft }));
  1386. timersPanelDataBind();
  1387. //console.log({ requestTime: getServerTime(), playerLevel: PlayerLevel, starsGained: starsGained, starsLeft: starsLeft })
  1388. }
  1389. function setLeaderTimeout(result) {
  1390. const leaderGoalsNumber = parseInt(getPlayerValue("leaderGoalsNumber", 0));
  1391. if(result == "win" || result == "fail" && leaderGoalsNumber == 1) {
  1392. // Если победили или проиграли, когда на выбор оставалась одна цель. Тогда уменьшаем количество доступных заданий. А если их был максимум, и таймер стоял, то запустим таймер.
  1393. const leaderTasks = JSON.parse(getPlayerValue("leaderTasks", "[3, 3]"));
  1394. let tasksNumber = leaderTasks[0];
  1395. const maxTasksNumber = leaderTasks[1];
  1396. if(tasksNumber == maxTasksNumber) {
  1397. setPlayerValue("leaderTimeoutEnd", getServerTime() + 3600000 * 3);
  1398. }
  1399. tasksNumber = Math.max(tasksNumber - 1, 0);
  1400. setPlayerValue("leaderTasks", JSON.stringify([tasksNumber, maxTasksNumber]));
  1401. setPlayerValue("leaderGoalsNumber", 3); // Также сбрасываем счетчик доступных целей
  1402. }
  1403. }
  1404. function refreshTimeout(timer, newTime) {
  1405. const savedTimeoutEnd = getPlayerValue(`${timer}TimeoutEnd`);
  1406. if(!savedTimeoutEnd || Math.abs(newTime - parseInt(savedTimeoutEnd)) / 1000 / 60 > 2) {
  1407. setPlayerValue(`${timer}TimeoutEnd`, newTime);
  1408. }
  1409. //console.log(new Date(parseInt(getPlayerValue(`${timer}TimeoutEnd`, 0))));
  1410. }
  1411. function checkRangerGuild() {
  1412. if(location.pathname == '/ranger_guild.php') {
  1413. if(document.querySelector("a[href^='ranger_guild.php?action=accept']")) {
  1414. deletePlayerValue("thiefTimeoutEnd");
  1415. updateOption("thiefOrRanger", true);
  1416. }
  1417. var time_gv = texts.regexp_timegre.exec(document.body.innerHTML);
  1418. if(time_gv) {
  1419. time_gv = Number(time_gv[1]) * 60000; // в миллисекундах
  1420. const now = getServerTime();
  1421. var time_gv_temp = time_gv - Math.abs(parseInt(getPlayerValue("thiefTimeoutEnd", 0)) - now);
  1422. if(Math.abs(time_gv_temp) > 70000) {
  1423. setPlayerValue("thiefTimeoutEnd", now + time_gv)
  1424. updateOption("thiefOrRanger", true);
  1425. }
  1426. }
  1427. }
  1428. if(location.pathname == '/ranger_list.php') {
  1429. var link_ranger_attack = document.querySelectorAll("a[href^='ranger_attack.php?join']");
  1430. if(link_ranger_attack.length > 0) {
  1431. deletePlayerValue("thiefTimeoutEnd");
  1432. updateOption("thiefOrRanger", true);
  1433. for(const link_ranger_attackItem of link_ranger_attack) {
  1434. link_ranger_attackItem.addEventListener("click", function() { setPlayerValue("battleType", 'thief'); });
  1435. }
  1436. }
  1437. }
  1438. }
  1439. function checkModWorkebench() {
  1440. if(location.pathname == '/mod_workbench.php') {
  1441. parseSmithPage(document);
  1442. }
  1443. }
  1444. function parseSmithPage(doc) {
  1445. const allb = doc.querySelectorAll("b");
  1446. for(const bold of allb) {
  1447. if(bold.innerText.includes(isEn ? "Under repair" : "В ремонте")) {
  1448. var repairData = bold.innerText;
  1449. break;
  1450. }
  1451. }
  1452. if(repairData) {
  1453. const repairEnd = parseTimeoutEnd(repairData, isEn ? {Hours: "h.", Minutes: "min."} : {Hours: "ч.", Minutes: "мин."}, 59);
  1454. refreshTimeout("smith", repairEnd.getTime());
  1455. } else {
  1456. deletePlayerValue("smithTimeoutEnd");
  1457. }
  1458. }
  1459. function parseTimeoutEnd(text, masks, defaultSeconds = 0) {
  1460. const restTime = { Hours: 0, Minutes: 0, Seconds: defaultSeconds };
  1461. for(const mask in masks) {
  1462. const regex = new RegExp(`(\\d{1,2}) ${masks[mask]}`);
  1463. const regexResult = regex.exec(text);
  1464. if(regexResult) {
  1465. restTime[mask] = parseInt(regexResult[1]);
  1466. }
  1467. }
  1468. //console.log(text);
  1469. //console.log(restTime);
  1470. const timeEnd = new Date(getServerTime());
  1471. timeEnd.setHours(timeEnd.getHours() + restTime.Hours, timeEnd.getMinutes() + restTime.Minutes, timeEnd.getSeconds() + restTime.Seconds);
  1472. return timeEnd;
  1473. }
  1474. function checkThiefAmbush() {
  1475. const thief_ambush_cancel = document.querySelector("a[href^='thief_ambush_cancel.php']");
  1476. const form_thief_ambush = document.querySelector("form[action='thief_ambush.php']");
  1477. if(thief_ambush_cancel || form_thief_ambush) {
  1478. deletePlayerValue("thiefTimeoutEnd");
  1479. updateOption("thiefOrRanger", false);
  1480. }
  1481. if(thief_ambush_cancel) {
  1482. setPlayerValue("battleType", "thief"); // Сидим в засаде, будет воровской бой
  1483. }
  1484. }
  1485. function checkRangerAmbush() {
  1486. var form_ranger_attack = document.querySelector("form[action='ranger_attack.php']");
  1487. if(form_ranger_attack) {
  1488. deletePlayerValue("thiefTimeoutEnd");
  1489. updateOption("thiefOrRanger", true);
  1490. form_ranger_attack.querySelector("input[type='submit']").addEventListener("click", function() { setPlayerValue("battleType", 'thief'); });
  1491. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1492. if(options.joinRangerBattle) {
  1493. setTimeout(function() { setPlayerValue("battleType", 'thief'); form_ranger_attack.submit(); }, 500);
  1494. }
  1495. }
  1496. }
  1497. function checkMapHunter() {
  1498. const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
  1499. if(map_hunt_block_div) {
  1500. const nativeHuntTimer = map_hunt_block_div.querySelector("div#next_ht_new");
  1501. if(nativeHuntTimer) {
  1502. setHuntTimeout(win.MapHunterDelta);
  1503. } else {
  1504. deletePlayerValue("huntTimeoutEnd");
  1505. const attackButtons = map_hunt_block_div.querySelectorAll(`div[hint='${isEn ? "Attack" : "Напасть"}'] > img`);
  1506. for(const button of attackButtons) {
  1507. //console.log(button)
  1508. button.addEventListener("click", function() { setPlayerValue("battleType", "hunt"); });
  1509. }
  1510. const skipButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Pass" : "Пройти"}'] > img`);
  1511. for(const button of skipButtons) {
  1512. button.addEventListener("click", function() { skipHunt(); });
  1513. }
  1514. const callButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Ask" : "Позвать"}'] > img`);
  1515. for(const button of callButtons) {
  1516. button.addEventListener("click", function() { setTimeout(function() {
  1517. const askForms = Array.from(document.querySelectorAll("form[action='map.php']"));
  1518. askForms.forEach(x => x.querySelector("input[type='submit']").addEventListener("click", function() { setPlayerValue("battleType", "hunt"); })); }
  1519. , 200); });
  1520. }
  1521. }
  1522. }
  1523. if(mooving && !getPlayerValue("huntTimeoutEnd")) {
  1524. skipHunt();
  1525. }
  1526. toggleNativeHuntTimerPanel();
  1527. }
  1528. function toggleNativeHuntTimerPanel() {
  1529. const next_ht_new = document.querySelector("div#next_ht_new");
  1530. if(next_ht_new) {
  1531. document.querySelector("div#map_hunt_block_div").style.display = getPlayerBool("HideNativeHuntTimer") ? "none" : "";
  1532. }
  1533. }
  1534. function secondsFormat(secondsLeft, timeFormat = timeFormats.full) {
  1535. if(!secondsLeft || secondsLeft < 0) {
  1536. return timeFormat == timeFormats.secondsLastMinute ? "00" : "00:00";
  1537. }
  1538. const days = Math.floor(secondsLeft / 86400);
  1539. const hours = Math.floor((secondsLeft - days * 86400) / 3600);
  1540. const minutes = Math.floor((secondsLeft - days * 86400 - hours * 3600) / 60);
  1541. const seconds = secondsLeft % 60;
  1542. //console.log(`timeFormat: ${timeFormat}, days: ${days}, hours: ${hours}`)
  1543. return (days === 0 ? '' : ((days < 10) ? '0' : '') + days + ':')
  1544. + (days === 0 && hours === 0 ? '' : hours.toString().padStart(2, "0") + ':')
  1545. + (timeFormat == timeFormats.secondsLastMinute && secondsLeft < 60 ? "" : minutes.toString().padStart(2, "0"))
  1546. + ((timeFormat == timeFormats.secondsLastMinute && secondsLeft >= 60 || timeFormat == timeFormats.hoursOrSeconds && (days > 0 || hours > 0)) ? "" : (timeFormat == timeFormats.secondsLastMinute && secondsLeft < 60 ? "" : ':') + seconds.toString().padStart(2, "0"));
  1547. }
  1548. function getSecondsLeft(timerName) {
  1549. //console.log(`${timerName}: ${getPlayerValue(`${timerName}TimeoutEnd`)}`)
  1550. if(getPlayerValue(`${timerName}TimeoutEnd`)) {
  1551. const result = Math.round((parseInt(getPlayerValue(`${timerName}TimeoutEnd`)) - getServerTime()) / 1000);
  1552. if(result >= 0) {
  1553. return result;
  1554. }
  1555. }
  1556. }
  1557. function tick() {
  1558. timersDataBind();
  1559. for(const timerName of timerNames) {
  1560. const secondsLeft = getSecondsLeft(timerName);
  1561. if(secondsLeft == 0) {
  1562. deletePlayerValue(`${timerName}TimeoutEnd`);
  1563. signal(timerName);
  1564. } else if(!secondsLeft && getPlayerValue(`${timerName}TimeoutEnd`)) {
  1565. deletePlayerValue(`${timerName}TimeoutEnd`);
  1566. }
  1567. if(timerName == "defence" && secondsLeft <= 60) {
  1568. const timerPanel = document.getElementById(`defenceTimerPanel`);
  1569. if(!timerPanel.classList.contains("alarm-text")) {
  1570. timerPanel.classList.add("alarm-text");
  1571. timerPanel.style.color = "#FF0000";
  1572. }
  1573. }
  1574. }
  1575. processPeriodicEvents();
  1576. setTimeout(tick, 1000);
  1577. }
  1578. async function signal(timerName) {
  1579. if(timerName == "merc") {
  1580. if(location.pathname == "/mercenary_guild.php" && getPlayerBool("reloadMercPageAfterTimeElapsed")) {
  1581. //console.log(`timerName: ${timerName}, location.pathname: ${location.pathname}, reloadMercPageAfterTimeElapsed: ${getPlayerBool("reloadMercPageAfterTimeElapsed")}`);
  1582. window.location.href = window.location.href; //location.reload();
  1583. }
  1584. }
  1585. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1586. if(!options[`${timerName}Notification`]) {
  1587. return;
  1588. }
  1589. if(getServerTime() < parseInt(getPlayerValue(`${timerName}LastNotificationTime`, 0)) + 60000) {
  1590. return;
  1591. }
  1592. setPlayerValue(`${timerName}LastNotificationTime`, getServerTime());
  1593. switch(getPlayerValue("NotificationType", '0')) {
  1594. case '0':
  1595. toggleAudio(timersAudioMap[timerName]);
  1596. break;
  1597. case '1':
  1598. alert(texts[`${timerName}Message`]);
  1599. break;
  1600. case '2':
  1601. GM.notification(texts[`${timerName}Message`], "ГВД", `${resourcesPath}/i/fast_t/fast_1x1_t.png`, function() { window.focus(); });
  1602. break;
  1603. }
  1604. if(timerName == 'health') {
  1605. updateOption("healthNotification", false);
  1606. timersPanelDataBind();
  1607. }
  1608. if(timerName == "leader") {
  1609. const leaderTasks = JSON.parse(getPlayerValue("leaderTasks", "[3, 3]"));
  1610. let tasksNumber = leaderTasks[0];
  1611. const maxTasksNumber = leaderTasks[1];
  1612. if(tasksNumber < maxTasksNumber) {
  1613. tasksNumber++;
  1614. setPlayerValue("leaderTasks", JSON.stringify([tasksNumber, maxTasksNumber]));
  1615. timersPanelDataBind();
  1616. if(tasksNumber < maxTasksNumber) {
  1617. setPlayerValue(`${timerName}TimeoutEnd`, getServerTime() + 3600000 * 3);
  1618. }
  1619. }
  1620. }
  1621. if(timerName == "watcher") {
  1622. deletePlayerValue("TodayWatchersResults");
  1623. }
  1624. }
  1625. function settings() {
  1626. const panelId = GM_info.script.name + "Settings";
  1627. if(showPupupPanel(panelId)) {
  1628. return;
  1629. }
  1630. const bgcinnerHTML = `
  1631. <tr>
  1632. <td><b>${texts.isShowTimersTitle}:</b>&nbsp;&nbsp;${texts.workTimerPanelCaption}:<input id=isShowWorkTimerCheckbox type=checkbox>
  1633. &nbsp;&nbsp;${texts.smithTimerPanelCaption}:<input id=isShowSmithTimerCheckbox type=checkbox>
  1634. &nbsp;&nbsp;${texts.mercTimerPanelCaption}:<input id=isShowMercTimerCheckbox type=checkbox>
  1635. &nbsp;&nbsp;${texts.huntTimerPanelCaption}:<input id=isShowHuntTimerCheckbox type=checkbox>
  1636. &nbsp;&nbsp;${texts.thiefTimerPanelCaption}:<input id=isShowThiefTimerCheckbox type=checkbox>
  1637. &nbsp;&nbsp;${texts.leaderTimerPanelCaption}:<input id=isShowLeaderTimerCheckbox type=checkbox>
  1638. &nbsp;&nbsp;${texts.watcherTimerPanelCaption}:<input id=isShowWatcherTimerCheckbox type=checkbox>
  1639. &nbsp;&nbsp;${texts.defenceTimerPanelCaption}:<input id=isShowDefenceTimerCheckbox type=checkbox>
  1640. </td>
  1641. </tr>
  1642. <tr>
  1643. <td><b>${isEn ? "Notify" : "Оповещать"}:</b>&nbsp;&nbsp;${isEn ? "Hl" : "Зд"}:<input id=healthNotificationCheckbox type=checkbox>
  1644. &nbsp;&nbsp;${texts.workTimerPanelCaption}:<input id=workNotificationCheckbox type=checkbox>
  1645. &nbsp;&nbsp;${texts.smithTimerPanelCaption}:<input id=smithNotificationCheckbox type=checkbox>
  1646. &nbsp;&nbsp;${texts.mercTimerPanelCaption}:<input id=mercNotificationCheckbox type=checkbox>
  1647. &nbsp;&nbsp;${texts.huntTimerPanelCaption}:<input id=huntNotificationCheckbox type=checkbox>
  1648. &nbsp;&nbsp;${texts.thiefTimerPanelCaption}:<input id=thiefNotificationCheckbox type=checkbox>
  1649. &nbsp;&nbsp;${texts.leaderTimerPanelCaption}:<input id=leaderNotificationCheckbox type=checkbox>
  1650. &nbsp;&nbsp;${texts.watcherTimerPanelCaption}:<input id=watcherNotificationCheckbox type=checkbox>
  1651. &nbsp;&nbsp;${texts.defenceTimerPanelCaption}:<input id=defenceNotificationCheckbox type=checkbox>
  1652. </td>
  1653. </tr>
  1654. <tr>
  1655. <td><b>${texts.joinRangerBattleText}:</b> <input id="joinRangerBattleCheckbox" type=checkbox></td>
  1656. </tr>
  1657. <tr>
  1658. <td><b>${texts.hide}</b> "<i>${texts.nativeHuntTimerText} ..</i>": <input id=hideNativeHuntTimerCheckbox type=checkbox></td>
  1659. </tr>
  1660. <tr>
  1661. <td><b>${texts.disableWorkaholicAlarmTitle}:</b> <input id=disableWorkaholicAlarmCheckbox type=checkbox></td>
  1662. </tr>
  1663. <tr>
  1664. <td><b>${texts.showWorkaholicAlarmLastTwoEnrollsTitle}:<b> <input id=showWorkaholicAlarmLastTwoEnrollsCheckbox type=checkbox></td>
  1665. </tr>
  1666. <tr>
  1667. <td><b>${isEn ? "View native timer" : "Отображать встроеный таймер"}:<b> <input id=viewNativeTimerCheckbox type=checkbox></td>
  1668. </tr>
  1669. <tr>
  1670. <td><b>${isEn ? "Refresh Mercenary Guild Page When Timer Expires" : "Обновить страницу гильдии наёмников по истечению таймера"}:<b> <input id=reloadMercPageAfterTimeElapsedCheckbox type=checkbox></td>
  1671. </tr>
  1672. <tr>
  1673. <td id="twmTimersSettingsAbuText"></td>
  1674. </tr>
  1675. <tr>
  1676. <td id="deerText"></td>
  1677. </tr>
  1678. <tr>
  1679. <td>${texts.huntLicenseAuto}</td>
  1680. </tr>
  1681. <tr>
  1682. <td>${texts.cusomRateTitle} <input id="cusomRateInput" type="number" style="width: 50px;" maxlength="2" onfocus="this.select();"> <b>%</b></td>
  1683. </tr>
  1684. <tr>
  1685. <td>${texts.setOnceThiefTimeout} <input id="onceThiefTimeoutInput" type="number" style="width: 50px;" maxlength="2" onfocus="this.select();"> <b>min</b>
  1686. <input type="button" class="button-62" id="setOnceThiefTimerButton" value="ok"></td>
  1687. </tr>
  1688. <tr>
  1689. <td> <input type="button" class="button-62" id="resetTimersButton" value="${texts.resetTimersTitle}">&nbsp;&nbsp;&nbsp;</td>
  1690. </tr>
  1691. <tr>
  1692. <td>${texts.alarm_mode} <input type="radio" name="notificationTypeOption" id="notificationType0Option">${texts.alarm_mode_sound}
  1693. <input type="radio" name="notificationTypeOption" id="notificationType1Option">${texts.alarm_mode_alert}
  1694. <input type="radio" name="notificationTypeOption" id="notificationType2Option">${texts.alarm_mode_both}
  1695. <input type="radio" name="notificationTypeOption" id="notificationType3Option">${texts.alarm_mode_none}
  1696. </td>
  1697. </tr>
  1698. <tr>
  1699. <td>
  1700. <table>
  1701. <tr>
  1702. <td>${texts.signalSound + texts.healthTitle}</td>
  1703. <td><input size=55 type="text" id="healthSoundInput"></td>
  1704. <td><input size=55 type="button" class="button-62" id="healthSoundInputPlay"></td>
  1705. </tr>
  1706. <tr>
  1707. <td>${texts.signalSound + texts.workTimerPanelCaption}</td>
  1708. <td><input size=55 type="text" id="workSoundInput"></td>
  1709. <td><input size=55 type="button" class="button-62" id="workSoundInputPlay"></td>
  1710. </tr>
  1711. <tr>
  1712. <td>${texts.signalSound + texts.smithTimerPanelCaption}</td>
  1713. <td><input size=55 type="text" id="smithSoundInput"></td>
  1714. <td><input size=55 type="button" class="button-62" id="smithSoundInputPlay"></td>
  1715. </tr>
  1716. <tr>
  1717. <td>${texts.signalSound + texts.warlikeTitle}</td>
  1718. <td><input size=55 type="text" id="warlikeSoundInput"></td>
  1719. <td><input size=55 type="button" class="button-62" id="warlikeSoundInputPlay"></td>
  1720. </tr>
  1721. <tr>
  1722. <td>${texts.signalSound + texts.leaderTitle}</td>
  1723. <td><input size=55 type="text" id="leaderSoundInput"></td>
  1724. <td><input size=55 type="button" class="button-62" id="leaderSoundInputPlay"></td>
  1725. </tr>
  1726. <tr>
  1727. <td>${texts.signalSound + texts.watcherTitle}</td>
  1728. <td><input size=55 type="text" id="watcherSoundInput"></td>
  1729. <td><input size=55 type="button" class="button-62" id="watcherSoundInputPlay"></td>
  1730. </tr>
  1731. <tr>
  1732. <td>${texts.signalSound + texts.defenceTitle}</td>
  1733. <td><input size=55 type="text" id="defenceSoundInput"></td>
  1734. <td><input size=55 type="button" class="button-62" id="defenceSoundInputPlay"></td>
  1735. </tr>
  1736. </table>
  1737. </td>
  1738. </tr>
  1739. <tr>
  1740. <td style="font-size: 12px; font-weight: bold; text-align: left;">
  1741. ${isEn ? "Battle notification" : "Оповещение о битве"}: <label for=battleSoundNotificationCheckbox>${isEn ? "sound" : "звуковое"}</label><input id=battleSoundNotificationCheckbox type=checkbox> <label for=battleNotificationCheckbox>${isEn ? "win" : "вин"}</label><input id=battleNotificationCheckbox type=checkbox>
  1742. </td>
  1743. </tr>
  1744. <tr>
  1745. <td style="font-size: 14px; font-weight: bold; text-align: center;">
  1746. ${isEn ? "Periodical notifications" : "Периодические оповещения"} <input type=button class="button-62" id=addPeriodicEventButton value="${isEn ? "Add" : "Добавить"}" />
  1747. </td>
  1748. </tr>
  1749. <tr>
  1750. <td>
  1751. <table id=periodicEventsTable></table>
  1752. </td>
  1753. </tr>
  1754. `;
  1755. const optionsContainer = addElement("table", { innerHTML: bgcinnerHTML, style: "width: 100%;" });
  1756.  
  1757. optionsContainer.querySelector("#resetTimersButton").addEventListener("click", resetTimers);
  1758. optionsContainer.querySelector("#setOnceThiefTimerButton").addEventListener("click", function() { if(Number(document.getElementById("onceThiefTimeoutInput").value) >= 0) { setPlayerValue("thiefTimeoutEnd", getServerTime() + document.getElementById("onceThiefTimeoutInput").value * 60000); } });
  1759. optionsContainer.querySelector("#cusomRateInput").addEventListener("change", function() { updateOption("customTimeRate", (100 - this.value) / 100); settingsDataBind(); });
  1760. optionsContainer.querySelector("#joinRangerBattleCheckbox").addEventListener("click", function() { updateOption("joinRangerBattle", this.checked); });
  1761. optionsContainer.querySelector("#hideNativeHuntTimerCheckbox").addEventListener("click", function() { setPlayerValue("HideNativeHuntTimer", this.checked); toggleNativeHuntTimerPanel(); }, false);
  1762. optionsContainer.querySelector("#showWorkaholicAlarmLastTwoEnrollsCheckbox").addEventListener("click", function() { updateOption("showWorkaholicAlarmLastTwoEnrolls", this.checked); });
  1763. optionsContainer.querySelector("#viewNativeTimerCheckbox").addEventListener("click", function() { setPlayerValue("viewNativeTimer", this.checked); });
  1764. optionsContainer.querySelector("#reloadMercPageAfterTimeElapsedCheckbox").addEventListener("click", function() { setPlayerValue("reloadMercPageAfterTimeElapsed", this.checked); });
  1765. optionsContainer.querySelector("#disableWorkaholicAlarmCheckbox").addEventListener("click", function() { updateOption("disableWorkaholicAlarm", this.checked); });
  1766.  
  1767. optionsContainer.querySelector("#isShowWorkTimerCheckbox").addEventListener("click", function() { updateOption("isShowWorkTimer", this.checked); timersPanelDataBind(); });
  1768. optionsContainer.querySelector("#isShowSmithTimerCheckbox").addEventListener("click", function() { updateOption("isShowSmithTimer", this.checked); timersPanelDataBind(); });
  1769. optionsContainer.querySelector("#isShowMercTimerCheckbox").addEventListener("click", function() { updateOption("isShowMercTimer", this.checked); timersPanelDataBind(); });
  1770. optionsContainer.querySelector("#isShowHuntTimerCheckbox").addEventListener("click", function() { updateOption("isShowHuntTimer", this.checked); timersPanelDataBind(); });
  1771. optionsContainer.querySelector("#isShowThiefTimerCheckbox").addEventListener("click", function() { updateOption("isShowThiefTimer", this.checked); timersPanelDataBind(); });
  1772. optionsContainer.querySelector("#isShowLeaderTimerCheckbox").addEventListener("click", function() { updateOption("isShowLeaderTimer", this.checked); timersPanelDataBind(); });
  1773. optionsContainer.querySelector("#isShowWatcherTimerCheckbox").addEventListener("click", function() { updateOption("isShowWatcherTimer", this.checked); timersPanelDataBind(); });
  1774. optionsContainer.querySelector("#isShowDefenceTimerCheckbox").addEventListener("click", function() { updateOption("isShowDefenceTimer", this.checked); timersPanelDataBind(); });
  1775. optionsContainer.querySelector("#battleSoundNotificationCheckbox").checked = getPlayerBool("enableBattleSoundNotification");
  1776. optionsContainer.querySelector("#battleNotificationCheckbox").checked = getPlayerBool("enableBattleNotification");
  1777. optionsContainer.querySelector("#battleSoundNotificationCheckbox").addEventListener("change", function() { setPlayerValue("enableBattleSoundNotification", this.checked); });
  1778. optionsContainer.querySelector("#battleNotificationCheckbox").addEventListener("change", function() { setPlayerValue("enableBattleNotification", this.checked); });
  1779. optionsContainer.querySelector("#healthNotificationCheckbox").addEventListener("click", function() { updateOption("healthNotification", this.checked); });
  1780. optionsContainer.querySelector("#workNotificationCheckbox").addEventListener("click", function() { updateOption("workNotification", this.checked); });
  1781. optionsContainer.querySelector("#smithNotificationCheckbox").addEventListener("click", function() { updateOption("smithNotification", this.checked); });
  1782. optionsContainer.querySelector("#mercNotificationCheckbox").addEventListener("click", function() { updateOption("mercNotification", this.checked); });
  1783. optionsContainer.querySelector("#huntNotificationCheckbox").addEventListener("click", function() { updateOption("huntNotification", this.checked); });
  1784. optionsContainer.querySelector("#thiefNotificationCheckbox").addEventListener("click", function() { updateOption("thiefNotification", this.checked); });
  1785. optionsContainer.querySelector("#leaderNotificationCheckbox").addEventListener("click", function() { updateOption("leaderNotification", this.checked); });
  1786. optionsContainer.querySelector("#watcherNotificationCheckbox").addEventListener("click", function() { updateOption("watcherNotification", this.checked); });
  1787. optionsContainer.querySelector("#defenceNotificationCheckbox").addEventListener("click", function() { updateOption("defenceNotification", this.checked); });
  1788.  
  1789. optionsContainer.querySelector("#workSoundInput").addEventListener("change", function() { updateOption("workSound", this.value.trim()); });
  1790. optionsContainer.querySelector("#warlikeSoundInput").addEventListener("change", function() { updateOption("warlikeSound", this.value.trim()); });
  1791. optionsContainer.querySelector("#leaderSoundInput").addEventListener("change", function() { updateOption("leaderSound", this.value.trim()); });
  1792. optionsContainer.querySelector("#watcherSoundInput").addEventListener("change", function() { updateOption("watcherSound", this.value.trim()); });
  1793. optionsContainer.querySelector("#defenceSoundInput").addEventListener("change", function() { updateOption("defenceSound", this.value.trim()); });
  1794. optionsContainer.querySelector("#smithSoundInput").addEventListener("change", function() { updateOption("smithSound", this.value.trim()); });
  1795. optionsContainer.querySelector("#healthSoundInput").addEventListener("change", function() { updateOption("healthSound", this.value.trim()); });
  1796.  
  1797. optionsContainer.querySelector("#healthSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1798. optionsContainer.querySelector("#workSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1799. optionsContainer.querySelector("#smithSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1800. optionsContainer.querySelector("#warlikeSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1801. optionsContainer.querySelector("#leaderSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1802. optionsContainer.querySelector("#watcherSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1803. optionsContainer.querySelector("#defenceSoundInputPlay").addEventListener("click", buttonToggleAudio);
  1804.  
  1805. optionsContainer.querySelector("#notificationType0Option").addEventListener("click", function() { if(this.checked) { setPlayerValue("NotificationType", "0"); } });
  1806. optionsContainer.querySelector("#notificationType1Option").addEventListener("click", function() { if(this.checked) { setPlayerValue("NotificationType", "1"); } });
  1807. optionsContainer.querySelector("#notificationType2Option").addEventListener("click", function() { if(this.checked) { setPlayerValue("NotificationType", "2"); } });
  1808. optionsContainer.querySelector("#notificationType3Option").addEventListener("click", function() { if(this.checked) { setPlayerValue("NotificationType", "3"); } });
  1809. optionsContainer.querySelector(`#notificationType${getPlayerValue("NotificationType", 0)}Option`).checked = true;
  1810.  
  1811. createPupupPanel(panelId, getScriptReferenceHtml() + " " + getSendErrorMailReferenceHtml(), [[optionsContainer]]);
  1812.  
  1813. settingsDataBind();
  1814. optionsContainer.querySelector("#addPeriodicEventButton").addEventListener("click", function() { addPeriodicEvents(); periodicEventsTableDataBind(); });
  1815. }
  1816. function settingsDataBind() {
  1817. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1818. document.getElementById("isShowWorkTimerCheckbox").checked = options.isShowWorkTimer;
  1819. document.getElementById("isShowSmithTimerCheckbox").checked = options.isShowSmithTimer;
  1820. document.getElementById("isShowMercTimerCheckbox").checked = options.isShowMercTimer;
  1821. document.getElementById("isShowHuntTimerCheckbox").checked = options.isShowHuntTimer;
  1822. document.getElementById("isShowThiefTimerCheckbox").checked = options.isShowThiefTimer;
  1823. document.getElementById("isShowLeaderTimerCheckbox").checked = options.isShowLeaderTimer;
  1824. document.getElementById("isShowWatcherTimerCheckbox").checked = options.isShowWatcherTimer;
  1825. document.getElementById("isShowDefenceTimerCheckbox").checked = options.isShowDefenceTimer;
  1826.  
  1827. document.getElementById("healthNotificationCheckbox").checked = options.healthNotification;
  1828. document.getElementById("workNotificationCheckbox").checked = options.workNotification;
  1829. document.getElementById("smithNotificationCheckbox").checked = options.smithNotification;
  1830. document.getElementById("mercNotificationCheckbox").checked = options.mercNotification;
  1831. document.getElementById("huntNotificationCheckbox").checked = options.huntNotification;
  1832. document.getElementById("thiefNotificationCheckbox").checked = options.thiefNotification;
  1833. document.getElementById("leaderNotificationCheckbox").checked = options.leaderNotification;
  1834. document.getElementById("watcherNotificationCheckbox").checked = options.watcherNotification;
  1835. document.getElementById("defenceNotificationCheckbox").checked = options.defenceNotification;
  1836.  
  1837. document.getElementById("joinRangerBattleCheckbox").checked = options.joinRangerBattle;
  1838. document.getElementById("hideNativeHuntTimerCheckbox").checked = getPlayerBool("HideNativeHuntTimer");
  1839. document.getElementById("disableWorkaholicAlarmCheckbox").checked = options.disableWorkaholicAlarm;
  1840. document.getElementById("showWorkaholicAlarmLastTwoEnrollsCheckbox").checked = options.showWorkaholicAlarmLastTwoEnrolls;
  1841. document.getElementById("viewNativeTimerCheckbox").checked = getPlayerBool("viewNativeTimer");
  1842. document.getElementById("reloadMercPageAfterTimeElapsedCheckbox").checked = getPlayerBool("reloadMercPageAfterTimeElapsed");
  1843.  
  1844. document.getElementById("deerText").innerHTML = getPlayerBool("IsDeer") ? `${isEn ? `Deer Yasha. HG, MG, TG, RG tasks 40% more often` : `Олень Яша. Задания ГО, ГН, ГВ, ГРж на 40% чаще`}` : "";
  1845. document.getElementById("twmTimersSettingsAbuText").innerHTML = options.abuBlessInfo;
  1846.  
  1847. document.getElementById("cusomRateInput").value = 100 - options.customTimeRate * 100;
  1848. document.getElementById("onceThiefTimeoutInput").value = 60 * options.customTimeRate * options.abuBlessRate;
  1849.  
  1850. for(const audioScenario of audioScenaries) {
  1851. document.getElementById(`${audioScenario}SoundInput`).value = options[`${audioScenario}Sound`];
  1852. const audioPlayed = playingAudios.hasOwnProperty(audioScenario);
  1853. document.getElementById(`${audioScenario}SoundInputPlay`).value = audioPlayed ? "Stop" : "Play";
  1854. if(audioPlayed) {
  1855. playingAudios[audioScenario].addEventListener("ended", () => { document.getElementById(`${audioScenario}SoundInputPlay`).value = "Play"; }, true);
  1856. }
  1857. }
  1858. periodicEventsTableDataBind();
  1859. }
  1860. function initAudio(soundUrl, onEnded) {
  1861. let audio = defaultAudio;
  1862. if(soundUrl) {
  1863. audio = new Audio();
  1864. audio.src = soundUrl;
  1865. audio.preload = 'auto';
  1866. }
  1867. if(onEnded) {
  1868. audio.addEventListener("ended", onEnded, true);
  1869. }
  1870. return audio;
  1871. }
  1872. function buttonToggleAudio(event) { toggleAudio(event.target.id.replace("SoundInputPlay", "")); }
  1873. async function toggleAudio(audioScenarioName) {
  1874. const playButton = document.getElementById(`${audioScenarioName}SoundInputPlay`);
  1875. const isPlaying = playingAudios.hasOwnProperty(audioScenarioName);
  1876. if(isPlaying) {
  1877. playingAudios[audioScenarioName].pause();
  1878. delete playingAudios[audioScenarioName];
  1879. } else {
  1880. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1881. const audio = initAudio(options[`${audioScenarioName}Sound`], () => { if(playButton) playButton.value = "Play"; delete playingAudios[audioScenarioName]; });
  1882. playingAudios[audioScenarioName] = audio;
  1883. audio.play();
  1884. }
  1885. if(playButton) {
  1886. playButton.value = isPlaying ? "Play" : "Stop";
  1887. }
  1888. }
  1889. function updateOption(key, val) {
  1890. const options = JSON.parse(getPlayerValue("hwmTimersOptions"));
  1891. if(typeof val == "function") {
  1892. options[key] = val(options);
  1893. } else {
  1894. options[key] = val;
  1895. }
  1896. setPlayerValue("hwmTimersOptions", JSON.stringify(options));
  1897. }
  1898. function resetTimers() {
  1899. deletePlayerValue("workTimeoutEnd");
  1900. deletePlayerValue("smithTimeoutEnd");
  1901. deletePlayerValue("mercTimeoutEnd");
  1902. deletePlayerValue("huntTimeoutEnd");
  1903. deletePlayerValue("thiefTimeoutEnd");
  1904. deletePlayerValue("leaderTimeoutEnd");
  1905. }
  1906. async function checkDefences() {
  1907. 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;
  1908. const defenceTimeoutEnd = getPlayerValue("defenceTimeoutEnd");
  1909. //console.log(`defenceWaiting: ${defenceWaiting}, defenceTimeoutEnd: ${(new Date(parseInt(defenceTimeoutEnd || 0))).toLocaleString()} ${defenceTimeoutEnd}`);
  1910. if(defenceWaiting && !defenceTimeoutEnd) {
  1911. const defenceTime = await findDefences();
  1912. setPlayerValue("defenceTimeoutEnd", defenceTime);
  1913. timersPanelDataBind();
  1914. //console.log(`defenceTime: ${defenceTime}, ${new Date(defenceTime).toLocaleString()}`);
  1915. }
  1916. if(!defenceWaiting && defenceTimeoutEnd) {
  1917. deletePlayerValue("defenceTimeoutEnd");
  1918. }
  1919. }
  1920. async function findDefences() {
  1921. const doc = location.pathname == "/mapwars.php" ? document : await getRequest("/mapwars.php");
  1922. const defenceTable = doc.querySelector("body > center > table:nth-child(2) * table * table.wbwhite");
  1923. if(defenceTable) {
  1924. for(const row of defenceTable.rows) {
  1925. const enlistingRegExp = new RegExp(`${isEn ? "Enlisting in defense possible at" : "вступление на защиту с"} (\\d{1,2}:\\d{1,2})`);
  1926. const defenceEndExec = enlistingRegExp.exec(row.cells[2].innerHTML);
  1927. if(defenceEndExec) {
  1928. return parseDate(defenceEndExec[1], true).getTime();
  1929. }
  1930. }
  1931. }
  1932. }
  1933. function getPeriodicEvents() {
  1934. const periodicEvents = JSON.parse(getPlayerValue("periodicEvents", "[]"));
  1935. if(periodicEvents.length == 0) {
  1936. periodicEvents.push({ id: 1, text: isEn ? "Duels C`sg" : "Дуэли ГТ", start: 29, period: 30, notify: 2, notificationImage: duelsPng, soundUrl: "http://commondatastorage.googleapis.com/codeskulptor-assets/week7-brrring.m4a" });
  1937. periodicEvents.push({ id: 2, text: isEn ? "Group battles C`sg" : "Групповые бои ГТ", start: 0, period: 30, notify: 2, notificationImage: groupsPng, soundUrl: "http://commondatastorage.googleapis.com/codeskulptor-assets/week7-brrring.m4a" });
  1938. periodicEvents.push({ id: 3, text: isEn ? "Roulette spin" : "Вращение рулетки", start: 0, period: 5, notify: 2, notificationImage: roulettePng, soundUrl: "http://commondatastorage.googleapis.com/codeskulptor-assets/week7-brrring.m4a" });
  1939. periodicEvents.push({ id: 4, text: isEn ? "MT" : "МТ", start: 10, period: 30, notify: 2, notificationImage: turnir_icoPng, soundUrl: "http://commondatastorage.googleapis.com/codeskulptor-assets/week7-brrring.m4a" });
  1940. setPlayerValue("periodicEvents", JSON.stringify(periodicEvents));
  1941. }
  1942. return periodicEvents;
  1943. }
  1944. function periodicEventsTableDataBind() {
  1945. const table = document.querySelector("#periodicEventsTable");
  1946. table.innerHTML = "";
  1947. const headers = [{ name: "text", title: isEn ? "Notification text" : "Текст сообщения" },
  1948. { name: "start", title: isEn ? "Count begin (min.)" : "Начало отсчёта (мин.)" },
  1949. { name: "period", title: isEn ? "Period (min.)" : "Период (мин.)" },
  1950. { name: "notify", title: isEn ? "Notification time (min. before event)" : "Время оповещения (мин. до события)" },
  1951. { name: "notificationImage", title: isEn ? "Notification image" : "Значок сообщения" },
  1952. { name: "soundUrl", title: isEn ? "Sound URL" : "URL звука" },
  1953. { name: "isWinNotification", title: isEn ? "Notification<br>snd / win" : "Оповещение<br>зв. / вин." },
  1954. { name: "delete", title: isEn ? "Del." : "Уд." }
  1955. ];
  1956.  
  1957. const row = addElement("tr", {}, table);
  1958. for(const header of headers) {
  1959. const cell = addElement("th", { innerHTML: header.title, style: "font-weight: bold; font-size: 14px; text-align: center;" }, row);
  1960. }
  1961. const periodicEvents = getPeriodicEvents();
  1962. for(const periodicEvent of periodicEvents) {
  1963. const row = addElement("tr", { periodicEventId: periodicEvent.id }, table);
  1964. row.innerHTML = `
  1965. <td style="text-align: center;">
  1966. <input name="text" type="text" value="${periodicEvent.text || ""}" style="width: 150px;" />
  1967. </td>
  1968. <td style="text-align: center;">
  1969. <input name="start" type="number" value="${periodicEvent.start}" style="width: 50px;" />
  1970. </td>
  1971. <td style="text-align: center;">
  1972. <input name="period" type="number" value="${periodicEvent.period}" style="width: 50px;" />
  1973. </td>
  1974. <td style="text-align: center;">
  1975. <input name="notify" type="number" value="${periodicEvent.notify}" style="width: 50px;" />
  1976. </td>
  1977. <td style="text-align: center;">
  1978. <input name="notificationImage" type="text" value="${periodicEvent.notificationImage || ""}" style="width: 150px;" />
  1979. </td>
  1980. <td style="text-align: center;">
  1981. <input name="soundUrl" type="text" value="${periodicEvent.soundUrl || ""}" style="width: 150px;" />
  1982. </td>
  1983. <td style="text-align: center;">
  1984. <input type=checkbox name="isSoundNotification" ${periodicEvent.isSoundNotification ? "checked" : ""} />
  1985. <input type=checkbox name="isWinNotification" ${periodicEvent.isWinNotification ? "checked" : ""} />
  1986. </td>
  1987. <td style="text-align: center;">
  1988. <span name="deleteSpan" class="button-62">&times;</span>
  1989. </td>
  1990. `;
  1991. [...row.querySelectorAll("input")].forEach(x => x.addEventListener("change", function(e) { changePeriodicEvents(periodicEvent.id, this.name, this.type == "checkbox" ? this.checked : (this.type == "number" ? Number(this.value) : this.value)); }));
  1992. row.querySelector("span[name=deleteSpan]").addEventListener("click", function() { deletePeriodicEvents(periodicEvent.id); periodicEventsTableDataBind(); });
  1993. }
  1994. }
  1995. function changePeriodicEvents(periodicEventId, fieldName, value) {
  1996. const periodicEvents = JSON.parse(getPlayerValue("periodicEvents", "[]"));
  1997. const periodicEvent = periodicEvents.find(x => x.id == periodicEventId);
  1998. periodicEvent[fieldName] = value;
  1999. setPlayerValue("periodicEvents", JSON.stringify(periodicEvents));
  2000. //console.log(periodicEvent);
  2001. }
  2002. function addPeriodicEvents() {
  2003. const periodicEvents = JSON.parse(getPlayerValue("periodicEvents", "[]"));
  2004. periodicEvents.push({ id: Date.now(), start: 0, period: 0, notify: 0 });
  2005. setPlayerValue("periodicEvents", JSON.stringify(periodicEvents));
  2006. }
  2007. function deletePeriodicEvents(id) {
  2008. const periodicEvents = JSON.parse(getPlayerValue("periodicEvents", "[]"));
  2009. const periodicEventIndex = periodicEvents.findIndex(x => x.id == id);
  2010. periodicEvents.splice(periodicEventIndex, 1);
  2011. setPlayerValue("periodicEvents", JSON.stringify(periodicEvents));
  2012. }
  2013. function processPeriodicEvents() {
  2014. //console.log(periodicEvents);
  2015. const serverTime = getServerTime();
  2016. const serverDate = new Date(serverTime);
  2017. serverDate.setSeconds(0, 0);
  2018. const minutes = serverDate.getMinutes();
  2019. const currentNotifications = [];
  2020. for(const periodicEvent of periodicEvents) {
  2021. const nearestEvent = periodicEvent.eventsArray.find(x => x >= minutes);
  2022. if(nearestEvent == minutes) {
  2023. const lastNotificationDate = new Date(parseInt(getPlayerValue(`periodicEventLastNotificationTime${periodicEvent.id}`, 0)));
  2024. //console.log(`lastNotificationTime: ${getPlayerValue(`periodicEventLastNotificationTime${periodicEvent.id}`)}, lastNotificationDate: ${lastNotificationDate.toLocaleString()}, serverDate: ${serverDate.toLocaleString()}, ${!isValidDate(lastNotificationDate) || lastNotificationDate < serverDate}`);
  2025. if(!isValidDate(lastNotificationDate) || lastNotificationDate < serverDate) {
  2026. setPlayerValue(`periodicEventLastNotificationTime${periodicEvent.id}`, serverDate.getTime());
  2027. currentNotifications.push(periodicEvent);
  2028. }
  2029. }
  2030. }
  2031. if(currentNotifications.length > 0) {
  2032. const evenText = currentNotifications.filter(x => x.isWinNotification).map(x => isEn ? `${x.text} in ${x.notify} min.` : `${x.text} через ${x.notify} мин.`).join("\r\n");
  2033. const notificationImage = currentNotifications.find(x => x.isWinNotification && x.notificationImage)?.notificationImage || `${resourcesPath}/i/fast_t/fast_1x1_t.png`;
  2034. if(evenText) {
  2035. GM.notification(evenText, "ГВД", notificationImage, function() { window.focus(); });
  2036. }
  2037. const soundUrl = currentNotifications.find(x => x.isSoundNotification && x.soundUrl)?.soundUrl || "";
  2038. if(soundUrl) {
  2039. new Audio(soundUrl).play();
  2040. }
  2041. }
  2042. }
  2043. function getPositiveModule(value, module) {
  2044. let result = value % module;
  2045. if(result < 0) {
  2046. result += module;
  2047. }
  2048. return result;
  2049. }
  2050. // Returns int array between 0 and 59 minutes
  2051. function getEventsArray(periodicEvent) {
  2052. const period = Number(periodicEvent.period);
  2053. if(isNaN(period) || period <= 0) {
  2054. return [];
  2055. }
  2056. const result = [];
  2057. const eventsAmount = 60 / period;
  2058. //console.log(`eventsAmount: ${eventsAmount}`);
  2059. for(let i = 0; i < eventsAmount; i++) {
  2060. result.push(getPositiveModule(Number(periodicEvent.start) - Number(periodicEvent.notify) + period * i, 60));
  2061. }
  2062. result.sort(function(a, b) { return a - b; });
  2063. //console.log(periodicEvent);
  2064. //console.log(result);
  2065. return result;
  2066. }
  2067. function isValidDate(d) { return d instanceof Date && !isNaN(d); }
  2068. // HWM API
  2069. async function checkAmbushResult(force = false) {
  2070. if(location.pathname == '/pl_warlog.php') {
  2071. const page = getUrlParamValue(location.href, "page");
  2072. const id = getUrlParamValue(location.href, "id");
  2073. if(id == PlayerId && (!page || page == 0)) {
  2074. var doc = document;
  2075. }
  2076. }
  2077. if(doc || force) {
  2078. doc = doc || await getRequest(`/pl_warlog.php?id=${PlayerId}`);
  2079. processWarlog(doc);
  2080. }
  2081. }
  2082. function processWarlog(doc) {
  2083. const lastAmbushRef = Array.from(doc.querySelectorAll("a[href*='warlog.php?warid=']")).find(x => {
  2084. if(x.nextSibling.textContent == ": • " || x.nextSibling.nextSibling.textContent == ": • ") {
  2085. return true;
  2086. }
  2087. const rowElements = getSequentialsUntil(x, "br");
  2088. //console.log(rowElements);
  2089. const ranger = rowElements.find(y => y.textContent.includes(isEn ? "Ranger" : "Рейнджер"));
  2090. //console.log(`ranger: ${ranger}`);
  2091. return ranger ? true : false;
  2092. });
  2093. let lastAmbushResult = BattleResult.NotFound;
  2094. if(lastAmbushRef) {
  2095. let currentElement = lastAmbushRef;
  2096. lastAmbushResult = BattleResult.Fail;
  2097. while(currentElement && currentElement.tagName.toLowerCase() != "br") {
  2098. //console.log(currentElement);
  2099. if(currentElement.tagName.toLowerCase() == "b" && currentElement.innerHTML.includes(getPlayerValue("UserName"))) {
  2100. lastAmbushResult = BattleResult.Win;
  2101. break;
  2102. }
  2103. currentElement = nextSequentialElement(currentElement);
  2104. }
  2105. var lastAmbushTime = parseDate(lastAmbushRef.innerText, false, true).getTime();
  2106. var newAmbushSuspendExpireDate = calcThiefTimeoutEnd(lastAmbushTime) + 60000;
  2107. }
  2108. // Если есть неустаревшее поражение
  2109. if(lastAmbushResult == BattleResult.Fail && newAmbushSuspendExpireDate > getServerTime()) {
  2110. //console.log(`lastAmbushResult: ${lastAmbushResult}, lastAmbushTime: ${lastAmbushTime}, thiefTimeoutEnd: ${new Date(parseInt(getPlayerValue("thiefTimeoutEnd"))).toLocaleString()}, newAmbushSuspendExpireDate: ${new Date(newAmbushSuspendExpireDate).toLocaleString()}`);
  2111. // Если нет старого значения AmbushSuspendExpireDate или расхождение с новым из лога больше минуты, то установим новое
  2112. if(!getPlayerValue("thiefTimeoutEnd") || Math.abs(parseInt(getPlayerValue("thiefTimeoutEnd")) - newAmbushSuspendExpireDate) > 60 * 1000) {
  2113. setPlayerValue("thiefTimeoutEnd", newAmbushSuspendExpireDate);
  2114. }
  2115. } else {
  2116. deletePlayerValue("thiefTimeoutEnd");
  2117. }
  2118. }
  2119. // API
  2120. function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
  2121. function addStyle(css) { addElement("style", { type: "text/css", innerHTML: css }, document.head); }
  2122. function getParent(element, parentType, number = 1) {
  2123. if(!element) {
  2124. return;
  2125. }
  2126. let result = element;
  2127. let foundNumber = 0;
  2128. while(result = result.parentNode) {
  2129. if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
  2130. foundNumber++;
  2131. if(foundNumber == number) {
  2132. return result;
  2133. }
  2134. }
  2135. }
  2136. }
  2137. function getServerTime() { return Date.now() - parseInt(getValue("ClientServerTimeDifference", 0)); }
  2138. function getGameDate() { return new Date(getServerTime() + 10800000); } // Игра в интерфейсе всегда показывает московское время // Это та дата, которая в toUTCString покажет время по москве
  2139. function today() { const now = new Date(getServerTime()); now.setHours(0, 0, 0, 0); return now; }
  2140. function tomorrow() { const today1 = today(); today1.setDate(today1.getDate() + 1); return today1; }
  2141. async function requestServerTime() {
  2142. if(parseInt(getValue("LastClientServerTimeDifferenceRequestDate", 0)) + 60 * 60 * 1000 < Date.now()) {
  2143. setValue("LastClientServerTimeDifferenceRequestDate", Date.now());
  2144. const responseText = await getRequestText("/time.php");
  2145. const responseParcing = /now (\d+)/.exec(responseText); //responseText: now 1681711364 17-04-23 09:02
  2146. if(responseParcing) {
  2147. setValue("ClientServerTimeDifference", Date.now() - parseInt(responseParcing[1]) * 1000);
  2148. }
  2149. } else {
  2150. setTimeout(requestServerTime, 60 * 60 * 1000);
  2151. }
  2152. }
  2153. function observe(target, handler, once = false) {
  2154. const config = { childList: true, subtree: true };
  2155. const ob = new MutationObserver(async function(mut, observer) {
  2156. observer.disconnect();
  2157. let handled = false;
  2158. if(handler.constructor.name === 'AsyncFunction') {
  2159. handled = await handler();
  2160. } else {
  2161. handled = handler();
  2162. }
  2163. if(!once && !handled) {
  2164. observer.observe(target, config);
  2165. }
  2166. });
  2167. ob.observe(target, config);
  2168. }
  2169. function healthTimer() {
  2170. if(isHeartOnPage) {
  2171. const health_amount = document.getElementById("health_amount");
  2172. const heart_js_mobile_click = document.getElementById("heart_js_mobile_click");
  2173. let heart; // 78 %
  2174. let maxHeart; // 100 %
  2175. let timeHeart; // 405 сек.
  2176. if(heart_js_mobile_click) {
  2177. //var hwm_heart=19;var hwm_max_heart=100;var hwm_time_heart=360;
  2178. heart = win.hwm_heart;
  2179. maxHeart = win.hwm_max_heart;
  2180. timeHeart = win.hwm_time_heart;
  2181. } else if(health_amount) {
  2182. const res = /top_line_draw_canvas_heart\((\d+), (\d+), ([\d\.]+)\);/.exec(document.body.innerHTML); // top_line_draw_canvas_heart(0, 100, 405.5);
  2183. if(res) {
  2184. heart = parseInt(res[1]);
  2185. maxHeart = parseInt(res[2]);
  2186. timeHeart = parseFloat(res[3]);
  2187. }
  2188. } else {
  2189. heart = win.heart;
  2190. maxHeart = win.max_heart;
  2191. timeHeart = win.time_heart;
  2192. }
  2193. //console.log(`healthTimer heart: ${heart}, maxHeart: ${maxHeart}, timeHeart: ${timeHeart}`);
  2194. let restSeconds = timeHeart * (maxHeart - heart) / maxHeart;
  2195. if(restSeconds > 0) {
  2196. setPlayerValue("healthTimeoutEnd", getServerTime() + restSeconds * 1000);
  2197. } else {
  2198. deletePlayerValue("healthTimeoutEnd");
  2199. }
  2200. return [heart, maxHeart, timeHeart];
  2201. }
  2202. }
  2203. function manaTimer() {
  2204. if(isHeartOnPage) {
  2205. const mana_amount = document.getElementById("mana_amount");
  2206. // var mana=15;
  2207. // var max_mana=40;
  2208. // var time_mana=900;
  2209. let mana = 10; // 15
  2210. let maxMana = 10; // 40
  2211. let timeMana = 900; // 900 сек.
  2212. if(mana_amount) {
  2213. // const res = /top_line_draw_canvas_heart\((\d+), (\d+), (\d+)\);/.exec(document.body.innerHTML); // top_line_draw_canvas_heart(0, 100, 405);
  2214. // if(res) {
  2215. // mana = parseInt(res[1]);
  2216. // maxMana = parseInt(res[2]);
  2217. // timeMana = parseInt(res[3]);
  2218. // }
  2219. } else {
  2220. mana = win.mana;
  2221. maxMana = win.max_mana;
  2222. timeMana = win.time_mana;
  2223. }
  2224. //console.log(`manaTimer mana: ${mana}, maxMana: ${maxMana}, timeMana: ${timeMana}`);
  2225. let restSeconds = timeMana * (maxMana - mana) / maxMana;
  2226. //mana+max_mana/time_mana*((curTime-startTime)/1000
  2227. if(restSeconds > 0) {
  2228. setPlayerValue("manaTimeoutEnd", getServerTime() + restSeconds * 1000);
  2229. } else {
  2230. deletePlayerValue("manaTimeoutEnd");
  2231. }
  2232. return [mana, maxMana, timeMana];
  2233. }
  2234. }
  2235. async function initUserName() {
  2236. if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
  2237. //console.log(document.querySelector("h1").innerText)
  2238. setPlayerValue("UserName", document.querySelector("h1").innerText);
  2239. }
  2240. if(location.pathname == "/home.php") {
  2241. //console.log(document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText)
  2242. const userNameRef = document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`);
  2243. if(userNameRef) {
  2244. setPlayerValue("UserName", userNameRef.innerText);
  2245. }
  2246. }
  2247. if(!getPlayerValue("UserName")) {
  2248. const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
  2249. setPlayerValue("UserName", doc.querySelector("h1").innerText);
  2250. }
  2251. }
  2252. // dateString - игровое время, взятое со страниц игры. Оно всегда московское
  2253. // Как результат возвращаем серверную дату
  2254. function parseDate(dateString, isFuture = false, isPast = false) {
  2255. //console.log(dateString)
  2256. if(!dateString) {
  2257. return;
  2258. }
  2259. const dateStrings = dateString.split(" ");
  2260.  
  2261. let hours = 0;
  2262. let minutes = 0;
  2263. let seconds = 0;
  2264. const gameDate = getGameDate();
  2265. let year = gameDate.getUTCFullYear();
  2266. let month = gameDate.getUTCMonth();
  2267. let day = gameDate.getUTCDate();
  2268. const timePart = dateStrings.find(x => x.includes(":"));
  2269. if(timePart) {
  2270. var time = timePart.split(":");
  2271. hours = parseInt(time[0]);
  2272. minutes = parseInt(time[1]);
  2273. if(time.length > 2) {
  2274. seconds = parseInt(time[2]);
  2275. }
  2276. if(dateStrings.length == 1) {
  2277. let result = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
  2278. if(isPast && result > gameDate) {
  2279. result.setUTCDate(result.getUTCDate() - 1);
  2280. }
  2281. if(isFuture && result < gameDate) {
  2282. result.setUTCDate(result.getUTCDate() + 1);
  2283. }
  2284. //console.log(`result: ${result}, gameDate: ${gameDate}`)
  2285. result.setUTCHours(result.getUTCHours() - 3);
  2286. return result;
  2287. }
  2288. }
  2289.  
  2290. const datePart = dateStrings.find(x => x.includes("-"));
  2291. if(datePart) {
  2292. const date = datePart.split("-");
  2293. month = parseInt(date[isEn ? (date.length == 3 ? 1 : 0) : 1]) - 1;
  2294. day = parseInt(date[isEn ? (date.length == 3 ? 2 : 1) : 0]);
  2295. if(date.length == 3) {
  2296. const yearText = isEn ? date[0] : date[2];
  2297. year = parseInt(yearText);
  2298. if(yearText.length < 4) {
  2299. year += Math.floor(gameDate.getUTCFullYear() / 1000) * 1000;
  2300. }
  2301. } else {
  2302. if(isFuture && month == 0 && gameDate.getUTCMonth() == 11) {
  2303. year += 1;
  2304. }
  2305. }
  2306. }
  2307. if(dateStrings.length > 2) {
  2308. const letterDateExec = /(\d{2}):(\d{2}) (\d{2}) (.{3,4})/.exec(dateString);
  2309. if(letterDateExec) {
  2310. //console.log(letterDateExec)
  2311. day = parseInt(letterDateExec[3]);
  2312. //const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
  2313. const monthShortNames = ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сент', 'окт', 'ноя', 'дек'];
  2314. month = monthShortNames.findIndex(x => x.toLowerCase() == letterDateExec[4].toLowerCase());
  2315. if(isPast && Date.UTC(year, month, day, hours, minutes, seconds) > gameDate.getTime()) {
  2316. year -= 1;
  2317. }
  2318. }
  2319. }
  2320. //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]))}`);
  2321. let result = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
  2322. result.setUTCHours(result.getUTCHours() - 3);
  2323. return result;
  2324. }
  2325. function getRequest(url) {
  2326. return new Promise((resolve, reject) => {
  2327. GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: "text/html; charset=windows-1251",
  2328. onload: function(response) { resolve((new DOMParser).parseFromString(response.responseText, "text/html")); },
  2329. onerror: function(error) { reject(error); }
  2330. });
  2331. });
  2332. }
  2333. function getRequestText(url, overrideMimeType = "text/html; charset=windows-1251") {
  2334. return new Promise((resolve, reject) => {
  2335. GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: overrideMimeType,
  2336. onload: function(response) { resolve(response.responseText); },
  2337. onerror: function(error) { reject(error); }
  2338. });
  2339. });
  2340. }
  2341. function getValue(key, defaultValue) { return GM_getValue(key, defaultValue); };
  2342. function setValue(key, value) { GM_setValue(key, value); };
  2343. function deleteValue(key) { return GM_deleteValue(key); };
  2344. function getPlayerValue(key, defaultValue) { return getValue(`${key}${PlayerId}`, defaultValue); };
  2345. function setPlayerValue(key, value) { setValue(`${key}${PlayerId}`, value); };
  2346. function deletePlayerValue(key) { return deleteValue(`${key}${PlayerId}`); };
  2347. function getPlayerBool(valueName, defaultValue = false) { return getBool(valueName + PlayerId, defaultValue); }
  2348. function getBool(valueName, defaultValue = false) {
  2349. const value = getValue(valueName);
  2350. //console.log(`valueName: ${valueName}, value: ${value}, ${typeof(value)}`)
  2351. if(value != undefined) {
  2352. if(typeof(value) == "string") {
  2353. return value == "true";
  2354. }
  2355. if(typeof(value) == "boolean") {
  2356. return value;
  2357. }
  2358. }
  2359. return defaultValue;
  2360. }
  2361. function getNearestAncestorSibling(node) {
  2362. let parentNode = node;
  2363. while((parentNode = parentNode.parentNode)) {
  2364. if(parentNode.nextSibling) {
  2365. return parentNode.nextSibling;
  2366. }
  2367. }
  2368. }
  2369. function getNearestAncestorElementSibling(node) {
  2370. let parentNode = node;
  2371. while((parentNode = parentNode.parentNode)) {
  2372. if(parentNode.nextElementSibling) {
  2373. return parentNode.nextElementSibling;
  2374. }
  2375. }
  2376. }
  2377. function nextSequential(node) { return node.firstChild || node.nextSibling || getNearestAncestorSibling(node); }
  2378. function nextSequentialElement(element) { return element.firstElementChild || element.nextElementSibling || getNearestAncestorElementSibling(element); }
  2379. function getSequentialsUntil(firstElement, lastElementTagName) {
  2380. let currentElement = firstElement;
  2381. const resultElements = [currentElement];
  2382. while((currentElement = nextSequential(currentElement)) && currentElement.nodeName.toLowerCase() != lastElementTagName.toLowerCase()) {
  2383. resultElements.push(currentElement);
  2384. }
  2385. if(currentElement) {
  2386. resultElements.push(currentElement);
  2387. }
  2388. return resultElements;
  2389. }
  2390. function findSequentialByValue(firstElement, selector) {
  2391. let currentElement = firstElement;
  2392. let result;
  2393. while((currentElement = nextSequential(currentElement))) {
  2394. if(selector(currentElement)) {
  2395. return currentElement;
  2396. }
  2397. }
  2398. }
  2399. function getScriptLastAuthor() {
  2400. let authors = GM_info.script.author;
  2401. if(!authors) {
  2402. const authorsMatch = GM_info.scriptMetaStr.match(/@author(.+)\n/);
  2403. authors = authorsMatch ? authorsMatch[1] : "";
  2404. }
  2405. const authorsArr = authors.split(",").map(x => x.trim()).filter(x => x);
  2406. return authorsArr[authorsArr.length - 1];
  2407. }
  2408. function getDownloadUrl() {
  2409. let result = GM_info.script.downloadURL;
  2410. if(!result) {
  2411. const downloadURLMatch = GM_info.scriptMetaStr.match(/@downloadURL(.+)\n/);
  2412. result = downloadURLMatch ? downloadURLMatch[1] : "";
  2413. result = result.trim();
  2414. }
  2415. return result;
  2416. }
  2417. function getScriptReferenceHtml() { return `<a href="${getDownloadUrl()}" title="${isEn ? "Check for update" : "Проверить обновление скрипта"}" target=_blanc>${GM_info.script.name} ${GM_info.script.version}</a>`; }
  2418. function getSendErrorMailReferenceHtml() { return `<a href="sms-create.php?mailto=${getScriptLastAuthor()}&subject=${isEn ? "Error in" : "Ошибка в"} ${GM_info.script.name} ${GM_info.script.version} (${GM_info.scriptHandler} ${GM_info.version})" target=_blanc>${isEn ? "Bug report" : "Сообщить об ошибке"}</a>`; }
  2419. function addElement(type, data = {}, parent = undefined, insertPosition = "beforeend") {
  2420. const el = document.createElement(type);
  2421. for(const key in data) {
  2422. if(key == "innerText" || key == "innerHTML") {
  2423. el[key] = data[key];
  2424. } else {
  2425. el.setAttribute(key, data[key]);
  2426. }
  2427. }
  2428. if(parent) {
  2429. if(parent.insertAdjacentElement) {
  2430. parent.insertAdjacentElement(insertPosition, el);
  2431. } else if(parent.parentNode) {
  2432. switch(insertPosition) {
  2433. case "beforebegin":
  2434. parent.parentNode.insertBefore(el, parent);
  2435. break;
  2436. case "afterend":
  2437. parent.parentNode.insertBefore(el, parent.nextSibling);
  2438. break;
  2439. }
  2440. }
  2441. }
  2442. return el;
  2443. }
  2444. function createPupupPanel(panelName, panelTitle, fieldsMap, panelToggleHandler) {
  2445. const backgroundPopupPanel = addElement("div", { id: panelName, style: "position: fixed; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgb(0,0,0); background-color: rgba(0,0,0,0.4); z-index: 200;" }, document.body);
  2446. backgroundPopupPanel.addEventListener("click", function(e) { if(e.target == this) { hidePupupPanel(panelName, panelToggleHandler); }});
  2447. const topStyle = isMobileDevice ? "" : "top: 50%; transform: translateY(-50%);";
  2448. const contentDiv = addElement("div", { style: `${topStyle} padding: 5px; display: flex; flex-wrap: wrap; position: relative; margin: auto; padding: 0; width: fit-content; background-image: linear-gradient(to right, #eea2a2 0%, #bbc1bf 19%, #57c6e1 42%, #b49fda 79%, #7ac5d8 100%); border: 1mm ridge rgb(211, 220, 50);` }, backgroundPopupPanel);
  2449. if(panelTitle) {
  2450. addElement("b", { innerHTML: panelTitle, style: "text-align: center; margin: auto; width: 90%; display: block;" }, contentDiv);
  2451. }
  2452. const divClose = addElement("span", { id: panelName + "close", title: isEn ? "Close" : "Закрыть", innerHTML: "&times;", style: "cursor: pointer; font-size: 20px; font-weight: bold;" }, contentDiv);
  2453. divClose.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });
  2454.  
  2455. addElement("div", { style: "flex-basis: 100%; height: 0;"}, contentDiv);
  2456.  
  2457. if(fieldsMap) {
  2458. let contentTable = addElement("table", { style: "flex-basis: 100%; width: min-content;"}, contentDiv);
  2459. for(const rowData of fieldsMap) {
  2460. if(rowData.length == 0) { // Спомощью передачи пустой стороки-массива, указываем, что надо начать новую таблицу после брейка
  2461. addElement("div", { style: "flex-basis: 100%; height: 0;"}, contentDiv);
  2462. contentTable = addElement("table", undefined, contentDiv);
  2463. continue;
  2464. }
  2465. const row = addElement("tr", undefined, contentTable);
  2466. for(const cellData of rowData) {
  2467. const cell = addElement("td", undefined, row);
  2468. if(cellData) {
  2469. if(typeof(cellData) == "string") {
  2470. cell.innerText = cellData;
  2471. } else {
  2472. cell.appendChild(cellData);
  2473. }
  2474. }
  2475. }
  2476. }
  2477. }
  2478. if(panelToggleHandler) {
  2479. panelToggleHandler(true);
  2480. }
  2481. return contentDiv;
  2482. }
  2483. function showPupupPanel(panelName, panelToggleHandler) {
  2484. const backgroundPopupPanel = document.getElementById(panelName);
  2485. if(backgroundPopupPanel) {
  2486. backgroundPopupPanel.style.display = '';
  2487. if(panelToggleHandler) {
  2488. panelToggleHandler(true);
  2489. }
  2490. return true;
  2491. }
  2492. return false;
  2493. }
  2494. function hidePupupPanel(panelName, panelToggleHandler) {
  2495. const backgroundPopupPanel = document.getElementById(panelName);
  2496. backgroundPopupPanel.style.display = 'none';
  2497. if(panelToggleHandler) {
  2498. panelToggleHandler(false);
  2499. }
  2500. }
  2501. function mobileCheck() {
  2502. let check = false;
  2503. (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
  2504. return check;
  2505. };

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址