HeroWarsHelper

Automation of actions for the game Hero Wars

目前為 2023-06-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name			HeroWarsHelper
// @name:en			HeroWarsHelper
// @name:ru			HeroWarsHelper
// @namespace		HeroWarsHelper
// @version			2.079
// @description		Automation of actions for the game Hero Wars
// @description:en	Automation of actions for the game Hero Wars
// @description:ru	Автоматизация действий для игры Хроники Хаоса
// @author			ZingerY
// @license 		Copyright ZingerY
// @homepage		http://ilovemycomp.narod.ru/HeroWarsHelper.user.js
// @icon			http://ilovemycomp.narod.ru/VaultBoyIco16.ico
// @icon64			http://ilovemycomp.narod.ru/VaultBoyIco64.png
// @encoding		utf-8
// @include			https://*.nextersglobal.com/*
// @include			https://*.hero-wars*.com/*
// @match			https://www.solfors.com/
// @match			https://t.me/s/hw_ru
// @run-at			document-start
// ==/UserScript==

(function() {
	/**
	 * Start script
	 *
	 * Стартуем скрипт
	 */
	console.log('Start ' + GM_info.script.name + ', v' + GM_info.script.version);
	/**
	 * Script info
	 *
	 * Информация о скрипте
	 */
	const scriptInfo = (({name, version, author, homepage, lastModified}, updateUrl, source) =>
		({name, version, author, homepage, lastModified, updateUrl, source}))
		(GM_info.script, GM_info.scriptUpdateURL, arguments.callee.toString());
	/**
	 * If we are on the gifts page, then we collect and send them to the server
	 *
	 * Если находимся на странице подарков, то собираем и отправляем их на сервер
	 */
	if (['www.solfors.com', 't.me'].includes(location.host)) {
		setTimeout(sendCodes, 2000);
		return;
	}
	/**
	 * Information for completing daily quests
	 *
	 * Информация для выполнения ежендевных квестов
	 */
	const questsInfo = {};
	/**
	 * Is the game data loaded
	 *
	 * Загружены ли данные игры
	 */
	let isLoadGame = false;
	/**
	 * Headers of the last request
	 *
	 * Заголовки последнего запроса
	 */
	let lastHeaders = {};
	/**
	 * Information about sent gifts
	 *
	 * Информация об отправленных подарках
	 */
	let freebieCheckInfo = null;
	/**
	 * User data
	 *
	 * Данные пользователя
	 */
	let userInfo;
	/**
	 * Original methods for working with AJAX
	 *
	 * Оригинальные методы для работы с AJAX
	 */
	const original = {
		open: XMLHttpRequest.prototype.open,
		send: XMLHttpRequest.prototype.send,
		setRequestHeader: XMLHttpRequest.prototype.setRequestHeader,
	};
	/**
	 * Decoder for converting byte data to JSON string
	 *
	 * Декодер для перобразования байтовых данных в JSON строку
	 */
	const decoder = new TextDecoder("utf-8");
	/**
	 * Stores a history of requests
	 *
	 * Хранит историю запросов
	 */
	let requestHistory = {};
	/**
	 * URL for API requests
	 *
	 * URL для запросов к API
	 */
	let apiUrl = '';

	/**
	 * Connecting to the game code
	 *
	 * Подключение к коду игры
	 */
	this.cheats = new hackGame();
	/**
	 * The function of calculating the results of the battle
	 *
	 * Функция расчета результатов боя
	 */
	this.BattleCalc = cheats.BattleCalc;
	/**
	 * Sending a request available through the console
	 *
	 * Отправка запроса доступная через консоль
	 */
	this.SendRequest = send;
	/**
	 * Simple combat calculation available through the console
	 *
	 * Простой расчет боя доступный через консоль
	 */
	this.Calc = function (data) {
		const type = getBattleType(data?.type);
		return new Promise((resolve, reject) => {
			try {
				BattleCalc(data, type, resolve);
			} catch (e) {
				reject(e);
			}
		})
	}
	/**
	 * Short asynchronous request
	 * Usage example (returns information about a character):
	 * const userInfo = await Send('{"calls":[{"name":"userGetInfo","args":{},"ident":"body"}]}')
	 *
	 * Короткий асинхронный запрос
	 * Пример использования (возвращает информацию о персонаже):
	 * const userInfo = await Send('{"calls":[{"name":"userGetInfo","args":{},"ident":"body"}]}')
	*/
	this.Send = function (json, pr) {
		return new Promise((resolve, reject) => {
			try {
				send(json, resolve, pr);
			} catch (e) {
				reject(e);
			}
		})
	}

	const i18nLangData = {
		/* English translation by BaBa */
		en: {
			/* Checkboxes */
			SKIP_FIGHTS: 'Skip battle',
			SKIP_FIGHTS_TITLE: 'Skip battle in Outland and the arena of the titans, auto-pass in the tower and campaign',
			ENDLESS_CARDS: 'Endless cards',
			ENDLESS_CARDS_TITLE: 'Endless Prediction Cards',
			AUTO_EXPEDITION: 'Auto Expedition',
			AUTO_EXPEDITION_TITLE: 'Auto-sending expeditions',
			CANCEL_FIGHT: 'Cancel battle',
			CANCEL_FIGHT_TITLE: 'The possibility of canceling the battle on VG',
			GIFTS: 'Gifts',
			GIFTS_TITLE: 'Collect gifts automatically',
			BATTLE_RECALCULATION: 'Battle recalculation',
			BATTLE_RECALCULATION_TITLE: 'Preliminary calculation of the battle',
			QUANTITY_CONTROL: 'Quantity control',
			QUANTITY_CONTROL_TITLE: 'Ability to specify the number of opened "lootboxes"',
			REPEAT_CAMPAIGN: 'Repeat missions',
			REPEAT_CAMPAIGN_TITLE: 'Auto-repeat battles in the campaign',
			DISABLE_DONAT: 'Disable donation',
			DISABLE_DONAT_TITLE: 'Removes all donation offers',
			DAILY_QUESTS: 'Daily quests',
			DAILY_QUESTS_TITLE: 'Complete daily quests',
			AUTO_QUIZ: 'AutoQuiz',
			AUTO_QUIZ_TITLE: 'Automatically receive correct answers to quiz questions',
			/* Input fields */
			HOW_MUCH_TITANITE: 'How much titanite to farm',
			COMBAT_SPEED: 'Combat Speed Multiplier',
			NUMBER_OF_TEST: 'Number of test fights',
			NUMBER_OF_AUTO_BATTLE: 'Number of auto-battle attempts',
			/* Buttons */
			RUN_SCRIPT: 'Run the',
			TO_DO_EVERYTHING: 'Do All',
			TO_DO_EVERYTHING_TITLE: 'Perform multiple actions of your choice',
			OUTLAND: 'Outland',
			OUTLAND_TITLE: 'Collect Outland',
			TITAN_ARENA: 'ToE',
			TITAN_ARENA_TITLE: 'Complete the titan arena',
			DUNGEON: 'Dungeon',
			DUNGEON_TITLE: 'Go through the dungeon',
			TOWER: 'Tower',
			TOWER_TITLE: 'Pass the tower',
			EXPEDITIONS: 'Expeditions',
			EXPEDITIONS_TITLE: 'Sending and collecting expeditions',
			SYNC: 'Sync',
			SYNC_TITLE: 'Partial synchronization of game data without reloading the page',
			ARCHDEMON: 'Archdemon',
			ARCHDEMON_TITLE: 'Hitting kills and collecting rewards',
			ESTER_EGGS: 'Easter eggs',
			ESTER_EGGS_TITLE: 'Collect all Easter eggs or rewards',
			REWARDS: 'Rewards',
			REWARDS_TITLE: 'Collect all quest rewards',
			MAIL: 'Mail',
			MAIL_TITLE: 'Collect all mail, except letters with energy and charges of the portal',
			MINIONS: 'Minions',
			MINIONS_TITLE: 'Attack minions with saved packs',
			ADVENTURE: 'Adventure',
			ADVENTURE_TITLE: 'Passes the adventure along the specified route',
			STORM: 'Storm',
			STORM_TITLE: 'Passes the Storm along the specified route',
			SANCTUARY: 'Sanctuary',
			SANCTUARY_TITLE: 'Fast travel to Sanctuary',
			GUILD_WAR: 'Guild War',
			GUILD_WAR_TITLE: 'Fast travel to Guild War',
			/* Misc */
			BOTTOM_URLS: '<a href="https://t.me/+0oMwICyV1aQ1MDAy" target="_blank">tg</a>',
			GIFTS_SENT: 'Gifts sent!',
			DO_YOU_WANT: "Do you really want to do this?",
			BTN_RUN: 'Run',
			BTN_CANCEL: 'Cancel',
			BTN_OK: 'OK',
			MSG_HAVE_BEEN_DEFEATED: 'You have been defeated!',
			BTN_AUTO: 'Auto',
			MSG_YOU_APPLIED: 'You applied',
			MSG_DAMAGE: 'damage',
			MSG_CANCEL_AND_STAT: 'Cancel and show statistic',
			MSG_REPEAT_MISSION: 'Repeat the mission?',
			BTN_REPEAT: 'Repeat',
			BTN_NO: 'No',
			MSG_SPECIFY_QUANT: 'Specify Quantity:',
			BTN_OPEN: 'Open',
			QUESTION_COPY: 'Question copied to clipboard',
			ANSWER_KNOWN: 'The answer is known',
			ANSWER_NOT_KNOWN: 'ATTENTION THE ANSWER IS NOT KNOWN',
			BEING_RECALC: 'The battle is being recalculated',
			THIS_TIME: 'This time',
			VICTORY: 'VICTORY',
			DEFEAT: 'DEFEAT',
			CHANCE_TO_WIN: "Chance to win",
			OPEN_DOLLS: 'nesting dolls recursively',
			SENT_QUESTION: 'Question sent',
			SETTINGS: 'Settings',
			MSG_BAN_ATTENTION: '<p style="color:red;">Using this feature may result in a ban.</p> Continue?',
			BTN_YES_I_AGREE: 'Yes, I understand the risks!',
			BTN_NO_I_AM_AGAINST: 'No, I refuse it!',
			VALUES: 'Values',
			EXPEDITIONS_SENT: 'Expeditions sent',
			TITANIT: 'Titanit',
			COMPLETED: 'completed',
			FLOOR: 'Floor',
			LEVEL: 'Уровень',
			BATTLES: 'battles',
			EVENT: 'Event',
			NOT_AVAILABLE: 'not available',
			NO_HEROES: 'No heroes',
			DAMAGE_AMOUNT: 'Damage amount',
			NOTHING_TO_COLLECT: 'Nothing to collect',
			COLLECTED: 'Collected',
			REWARD: 'rewards',
			REMAINING_ATTEMPTS: 'Remaining attempts',
			BATTLES_CANCELED: 'Battles canceled',
			MINION_RAID: 'Minion Raid',
			STOPPED: 'Stopped',
			REPETITIONS: 'Repetitions',
			MISSIONS_PASSED: 'Missions passed',
			STOP: 'stop',
			TOTAL_OPEN: 'Total open',
			OPEN: 'Open',
			ROUND_STAT: 'Damage statistics for ',
			BATTLE: 'battles',
			MINIMUM: 'Minimum',
			MAXIMUM: 'Maximum',
			AVERAGE: 'Average',
			NOT_THIS_TIME: 'Not this time',
			RETRY_LIMIT_EXCEEDED: 'Retry limit exceeded',
			SUCCESS: 'Success',
			RECEIVED: 'Received',
			LETTERS: 'letters',
			PORTALS: 'portals',
			ATTEMPTS: 'attempts',
			/* Quests */
			QUEST_10001: 'Upgrade the skills of heroes 3 times',
			QUEST_10002: 'Complete 10 missions',
			QUEST_10003: 'Complete 3 heroic missions',
			QUEST_10004: 'Fight 3 times in the Arena or Grand Arena',
			QUEST_10006: 'Use the exchange of emeralds 1 time',
			QUEST_10007: 'Open 1 chest',
			QUEST_10016: 'Send gifts to guildmates',
			QUEST_10018: 'Use an experience potion',
			QUEST_10019: 'Open 1 chest in the Tower',
			QUEST_10020: 'Open 3 chests in Outland',
			QUEST_10021: 'Collect 75 Titanite in the Guild Dungeon',
			QUEST_10021: 'Collect 150 Titanite in the Guild Dungeon',
			QUEST_10023: 'Upgrade Gift of the Elements by 1 level',
			QUEST_10024: 'Level up any artifact once',
			QUEST_10025: 'Start Expedition 1',
			QUEST_10026: 'Start 4 Expeditions',
			QUEST_10027: 'Win 1 battle of the Tournament of Elements',
			QUEST_10028: 'Level up any titan artifact',
			QUEST_10029: 'Unlock the Orb of Titan Artifacts',
			QUEST_10030: 'Upgrade any Skin of any hero 1 time',
			QUEST_10031: 'Win 6 battles of the Tournament of Elements',
			QUEST_10043: 'Start or Join an Adventure',
			QUEST_10044: 'Use Summon Pets 1 time',
			QUEST_10046: 'Open 3 chests in Adventure',
			QUEST_10047: 'Get 150 Guild Activity Points',
			NOTHING_TO_DO: 'Nothing to do',
			YOU_CAN_COMPLETE: 'You can complete quests',
			BTN_DO_IT: 'Do it',
			NOT_QUEST_COMPLETED: 'Not a single quest completed',
			COMPLETED_QUESTS: 'Completed quests',
			/* everything button */
			ASSEMBLE_OUTLAND: 'Assemble Outland',
			PASS_THE_TOWER: 'Pass the tower',
			CHECK_EXPEDITIONS: 'Check Expeditions',
			COMPLETE_TOE: 'Complete ToE',
			COMPLETE_DUNGEON: 'Complete the dungeon',
			COLLECT_MAIL: 'Collect mail',
			COLLECT_MISC: 'Collect Easter Eggs, Skin Gems, Arena Keys and Coins',
			COLLECT_QUEST_REWARDS: 'Collect quest rewards',
			MAKE_A_SYNC: 'Make a sync',

			RUN_FUNCTION: 'Run the following functions?',
			BTN_GO: 'Go!',
			PERFORMED: 'Performed',
			DONE: 'Done',
			ERRORS_OCCURRES: 'Errors occurred while executing',
			COPY_ERROR: 'Copy error information to clipboard',
			BTN_YES: 'Yes',
			ALL_TASK_COMPLETED: 'All tasks completed',

			UNKNOWN: 'unknown',
			ENTER_THE_PATH: 'Enter the path of the adventure separated by commas',
			START_ADVENTURE: 'Start your adventure along this path!',
			BTN_CANCELED: 'Canceled',
			MUST_TWO_POINTS: 'The path must contain at least 2 points.',
			MUST_ONLY_NUMBERS: 'The path must contain only numbers and commas',
			NOT_ON_AN_ADVENTURE: 'You are not on an adventure',
			YOU_IN_NOT_ON_THE_WAY: 'Your location is not on the way',
			ATTEMPTS_NOT_ENOUGH: 'Your attempts are not enough to complete the path, continue?',
			YES_CONTINUE: 'Yes, continue!',
			NOT_ENOUGH_AP: 'Not enough action points',
			ATTEMPTS_ARE_OVER: 'The attempts are over',
			MOVES: 'Moves',
			BUFF_GET_ERROR: 'Buff getting error',
			BATTLE_END_ERROR: 'Battle end error',
			AUTOBOT: 'Autobot',
			FAILED_TO_WIN_AUTO: 'Failed to win the auto battle',
			ERROR_OF_THE_BATTLE_COPY: 'An error occurred during the passage of the battle<br>Copy the error to the clipboard?',
			ERROR_DURING_THE_BATTLE: 'Error during the battle',
			NO_CHANCE_WIN: 'No chance of winning this fight: 0/',
			LOST_HEROES: 'You have won, but you have lost one or several heroes',
			VICTORY_IMPOSSIBLE: 'Is victory impossible, should we focus on the result?',
			FIND_COEFF: 'Find the coefficient greater than',
			BTN_PASS: 'PASS',
			BRAWLS: 'Brawls',
			BRAWLS_TITLE: 'Activates the ability to auto-brawl',
			START_AUTO_BRAWLS: 'Start Auto Brawls?',
			LOSSES: 'Losses',
			WINS: 'Wins',
			FIGHTS: 'Fights',
			STAGE: 'Stage',
			DONT_HAVE_LIVES: 'You don\'t have lives',
		},
		ru: {
			/* Чекбоксы */
			SKIP_FIGHTS: 'Пропуск боев',
			SKIP_FIGHTS_TITLE: 'Пропуск боев в запределье и арене титанов, автопропуск в башне и кампании',
			ENDLESS_CARDS: 'Бесконечные карты',
			ENDLESS_CARDS_TITLE: 'Бесконечные карты предсказаний',
			AUTO_EXPEDITION: 'АвтоЭкспедиции',
			AUTO_EXPEDITION_TITLE: 'Автоотправка экспедиций',
			CANCEL_FIGHT: 'Отмена боя',
			CANCEL_FIGHT_TITLE: 'Возможность отмены боя на ВГ, СМ и в Асгарде',
			GIFTS: 'Подарки',
			GIFTS_TITLE: 'Собирать подарки автоматически',
			BATTLE_RECALCULATION: 'Прерасчет боя',
			BATTLE_RECALCULATION_TITLE: 'Предварительный расчет боя',
			QUANTITY_CONTROL: 'Контроль кол-ва',
			QUANTITY_CONTROL_TITLE: 'Возможность указывать количество открываемых "лутбоксов"',
			REPEAT_CAMPAIGN: 'Повтор в компании',
			REPEAT_CAMPAIGN_TITLE: 'Автоповтор боев в кампании',
			DISABLE_DONAT: 'Отключить донат',
			DISABLE_DONAT_TITLE: 'Убирает все предложения доната',
			DAILY_QUESTS: 'Ежедневные квесты',
			DAILY_QUESTS_TITLE: 'Выполнять ежедневные квесты',
			AUTO_QUIZ: 'АвтоВикторина',
			AUTO_QUIZ_TITLE: 'Автоматическое получение правильных ответов на вопросы викторины',
			/* Поля ввода */
			HOW_MUCH_TITANITE: 'Сколько фармим титанита',
			COMBAT_SPEED: 'Множитель ускорения боя',
			NUMBER_OF_TEST: 'Количество тестовых боев',
			NUMBER_OF_AUTO_BATTLE: 'Количество попыток автобоев',
			/* Кнопки */
			RUN_SCRIPT: 'Запустить скрипт',
			TO_DO_EVERYTHING: 'Сделать все',
			TO_DO_EVERYTHING_TITLE: 'Выполнить несколько действий',
			OUTLAND: 'Запределье',
			OUTLAND_TITLE: 'Собрать Запределье',
			TITAN_ARENA: 'Турнир Стихий',
			TITAN_ARENA_TITLE: 'Автопрохождение Турнира Стихий',
			DUNGEON: 'Подземелье',
			DUNGEON_TITLE: 'Автопрохождение подземелья',
			TOWER: 'Башня',
			TOWER_TITLE: 'Автопрохождение башни',
			EXPEDITIONS: 'Экспедиции',
			EXPEDITIONS_TITLE: 'Отправка и сбор экспедиций',
			SYNC: 'Синхронизация',
			SYNC_TITLE: 'Частичная синхронизация данных игры без перезагрузки сатраницы',
			ARCHDEMON: 'Архидемон',
			ARCHDEMON_TITLE: 'Набивает килы и собирает награду',
			ESTER_EGGS: 'Пасхалки',
			ESTER_EGGS_TITLE: 'Собрать все пасхалки или награды',
			REWARDS: 'Награды',
			REWARDS_TITLE: 'Собрать все награды за задания',
			MAIL: 'Почта',
			MAIL_TITLE: 'Собрать всю почту, кроме писем с энергией и зарядами портала',
			MINIONS: 'Прислужники',
			MINIONS_TITLE: 'Атакует прислужников сохраннеными пачками',
			ADVENTURE: 'Приключение',
			ADVENTURE_TITLE: 'Проходит приключение по указанному маршруту',
			STORM: 'Приключение',
			STORM_TITLE: 'Проходит приключение по указанному маршруту',
			SANCTUARY: 'Святилище',
			SANCTUARY_TITLE: 'Быстрый переход к Святилищу',
			GUILD_WAR: 'Война гильдий',
			GUILD_WAR_TITLE: 'Быстрый переход к Войне гильдий',
			/* Разное */
			BOTTOM_URLS: '<a href="https://t.me/+q6gAGCRpwyFkNTYy" target="_blank">tg</a> <a href="https://vk.com/invite/YNPxKGX" target="_blank">vk</a>',
			GIFTS_SENT: 'Подарки отправлены!',
			DO_YOU_WANT: "Вы действительно хотите это сделать?",
			BTN_RUN: 'Запускай',
			BTN_CANCEL: 'Отмена',
			BTN_OK: 'Ок',
			MSG_HAVE_BEEN_DEFEATED: 'Вы потерпели поражение!',
			BTN_AUTO: 'Авто',
			MSG_YOU_APPLIED: 'Вы нанесли',
			MSG_DAMAGE: 'урона',
			MSG_CANCEL_AND_STAT: 'Отменить и показать Статистику',
			MSG_REPEAT_MISSION: 'Повторить миссию?',
			BTN_REPEAT: 'Повторить',
			BTN_NO: 'Нет',
			MSG_SPECIFY_QUANT: 'Указать количество:',
			BTN_OPEN: 'Открыть',
			QUESTION_COPY: 'Вопрос скопирован в буфер обмена',
			ANSWER_KNOWN: 'Ответ известен',
			ANSWER_NOT_KNOWN: 'ВНИМАНИЕ ОТВЕТ НЕ ИЗВЕСТЕН',
			BEING_RECALC: 'Идет прерасчет боя',
			THIS_TIME: 'На этот раз',
			VICTORY: 'ПОБЕДА',
			DEFEAT: 'ПОРАЖЕНИЕ',
			CHANCE_TO_WIN: 'Шансы на победу',
			OPEN_DOLLS: 'матрешек рекурсивно',
			SENT_QUESTION: 'Вопрос отправлен',
			SETTINGS: 'Настройки',
			MSG_BAN_ATTENTION: '<p style="color:red;">Использование этой функции может привести к бану.</p> Продолжить?',
			BTN_YES_I_AGREE: 'Да, я беру на себя все риски!',
			BTN_NO_I_AM_AGAINST: 'Нет, я отказываюсь от этого!',
			VALUES: 'Значения',
			EXPEDITIONS_SENT: 'Экспедиции отправлены',
			TITANIT: 'Титанит',
			COMPLETED: 'завершено',
			FLOOR: 'Этаж',
			LEVEL: 'Уровень',
			BATTLES: 'бои',
			EVENT: 'Эвент',
			NOT_AVAILABLE: 'недоступен',
			NO_HEROES: 'Нет героев',
			DAMAGE_AMOUNT: 'Количество урона',
			NOTHING_TO_COLLECT: 'Нечего собирать',
			COLLECTED: 'Собрано',
			REWARD: 'наград',
			REMAINING_ATTEMPTS: 'Осталось попыток',
			BATTLES_CANCELED: 'Битв отменено',
			MINION_RAID: 'Рейд прислужников',
			STOPPED: 'Остановлено',
			REPETITIONS: 'Повторений',
			MISSIONS_PASSED: 'Миссий пройдено',
			STOP: 'остановить',
			TOTAL_OPEN: 'Всего открыто',
			OPEN: 'Открыто',
			ROUND_STAT: 'Статистика урона за',
			BATTLE: 'боев',
			MINIMUM: 'Минимальный',
			MAXIMUM: 'Максимальный',
			AVERAGE: 'Средний',
			NOT_THIS_TIME: 'Не в этот раз',
			RETRY_LIMIT_EXCEEDED: 'Превышен лимит попыток',
			SUCCESS: 'Успех',
			RECEIVED: 'Получено',
			LETTERS: 'писем',
			PORTALS: 'порталов',
			ATTEMPTS: 'попыток',
			QUEST_10001: 'Улучши умения героев 3 раза',
			QUEST_10002: 'Пройди 10 миссий',
			QUEST_10003: 'Пройди 3 героические миссии',
			QUEST_10004: 'Сразись 3 раза на Арене или Гранд Арене',
			QUEST_10006: 'Используй обмен изумрудов 1 раз',
			QUEST_10007: 'Открой 1 сундук',
			QUEST_10016: 'Отправь подарки согильдийцам',
			QUEST_10018: 'Используй зелье опыта',
			QUEST_10019: 'Открой 1 сундук в Башне',
			QUEST_10020: 'Открой 3 сундука в Запределье',
			QUEST_10021: 'Собери 75 Титанита в Подземелье Гильдии',
			QUEST_10021: 'Собери 150 Титанита в Подземелье Гильдии',
			QUEST_10023: 'Прокачай Дар Стихий на 1 уровень',
			QUEST_10024: 'Повысь уровень любого артефакта один раз',
			QUEST_10025: 'Начни 1 Экспедицию',
			QUEST_10026: 'Начни 4 Экспедиции',
			QUEST_10027: 'Победи в 1 бою Турнира Стихий',
			QUEST_10028: 'Повысь уровень любого артефакта титанов',
			QUEST_10029: 'Открой сферу артефактов титанов',
			QUEST_10030: 'Улучши облик любого героя 1 раз',
			QUEST_10031: 'Победи в 6 боях Турнира Стихий',
			QUEST_10043: 'Начни или присоеденись к Приключению',
			QUEST_10044: 'Воспользуйся призывом питомцев 1 раз',
			QUEST_10046: 'Открой 3 сундука в Приключениях',
			QUEST_10047: 'Набери 150 очков активности в Гильдии',
			NOTHING_TO_DO: 'Нечего выполнять',
			YOU_CAN_COMPLETE: 'Можно выполнить квесты',
			BTN_DO_IT: 'Выполняй',
			NOT_QUEST_COMPLETED: 'Ни одного квеста не выполенно',
			COMPLETED_QUESTS: 'Выполенно квестов',
			/* everything button */
			ASSEMBLE_OUTLAND: 'Собрать Запределье',
			PASS_THE_TOWER: 'Пройти башню',
			CHECK_EXPEDITIONS: 'Проверить экспедиции',
			COMPLETE_TOE: 'Пройти Турнир Стихий',
			COMPLETE_DUNGEON: 'Пройти подземелье',
			COLLECT_MAIL: 'Собрать почту',
			COLLECT_MISC: 'Собрать пасхалки, камни облика, ключи и монеты арены',
			COLLECT_QUEST_REWARDS: 'Собрать награды за квесты',
			MAKE_A_SYNC: 'Сделать синхронизацю',

			RUN_FUNCTION: 'Выполнить следующие функции?',
			BTN_GO: 'Погнали!',
			PERFORMED: 'Выполняется',
			DONE: 'Выполено',
			ERRORS_OCCURRES: 'Призошли ошибки при выполнении',
			COPY_ERROR: 'Скопировать в буфер информацию об ошибке',
			BTN_YES: 'Да',
			ALL_TASK_COMPLETED: 'Все задачи выполнены',

			UNKNOWN: 'Неизвестно',
			ENTER_THE_PATH: 'Введите путь приключения через запятые',
			START_ADVENTURE: 'Начать приключение по этому пути!',
			BTN_CANCELED: 'Отменено',
			MUST_TWO_POINTS: 'Путь должен состоять минимум из 2х точек',
			MUST_ONLY_NUMBERS: 'Путь должен содержать только цифры и запятые',
			NOT_ON_AN_ADVENTURE: 'Вы не в приключении',
			YOU_IN_NOT_ON_THE_WAY: 'Указанный путь должен включать точку вашего положения',
			ATTEMPTS_NOT_ENOUGH: 'Ваших попыток не достаточно для завершения пути, продолжить?',
			YES_CONTINUE: 'Да, продолжай!',
			NOT_ENOUGH_AP: 'Попыток не достаточно',
			ATTEMPTS_ARE_OVER: 'Попытки закончились',
			MOVES: 'Ходы',
			BUFF_GET_ERROR: 'Ошибка при получении бафа',
			BATTLE_END_ERROR: 'Ошибка завершения боя',
			AUTOBOT: 'АвтоБой',
			FAILED_TO_WIN_AUTO: 'Не удалось победить в автобою',
			ERROR_OF_THE_BATTLE_COPY: 'Призошли ошибка в процессе прохождения боя<br>Скопировать ошибку в буфер обмена?',
			ERROR_DURING_THE_BATTLE: 'Ошибка в процессе прохождения боя',
			NO_CHANCE_WIN: 'Нет шансов победить в этом бою: 0/',
			LOST_HEROES: 'Вы победили, но потеряли одного или несколько героев!',
			VICTORY_IMPOSSIBLE: 'Победа не возможна, бъем на результат?',
			FIND_COEFF: 'Поиск коэффициента больше чем',
			BTN_PASS: 'ПРОПУСК',
			BRAWLS: 'Потасовки',
			BRAWLS_TITLE: 'Включает возможность автопотасовок',
			START_AUTO_BRAWLS: 'Запустить Автопотасовки?',
			LOSSES: 'Поражений',
			WINS: 'Побед',
			FIGHTS: 'Боев',
			STAGE: 'Стадия',
			DONT_HAVE_LIVES: 'У Вас нет жизней',
		}
	}

	let selectLang = 'en';
	function checkLang() {
		const html = document.querySelector('html')
		const isFacebook = location.pathname.includes('facebook');
		const browserLang = (navigator.language || navigator.userLanguage).substr(0, 2);
		if (
			(html && html.lang == 'ru') || 
			(isFacebook && browserLang == 'ru')
		) {
			selectLang = 'ru';
		} else {
			selectLang = 'en';
		}
	}
	checkLang();

	this.I18N = function (constant) {
		if (constant && constant in i18nLangData[selectLang]) {
			return i18nLangData[selectLang][constant];
		}
		return `% ${constant} %`;
	};

	/**
	 * Checkboxes
	 *
	 * Чекбоксы
	 */
	const checkboxes = {
		passBattle: {
			label: I18N('SKIP_FIGHTS'),
			cbox: null,
			title: I18N('SKIP_FIGHTS_TITLE'),
			default: false
		},
		endlessCards: {
			label: I18N('ENDLESS_CARDS'),
			cbox: null,
			title: I18N('ENDLESS_CARDS_TITLE'),
			default: true
		},
		sendExpedition: {
			label: I18N('AUTO_EXPEDITION'),
			cbox: null,
			title: I18N('AUTO_EXPEDITION_TITLE'),
			default: true
		},
		cancelBattleBan: {
			label: I18N('CANCEL_FIGHT'),
			cbox: null,
			title: I18N('CANCEL_FIGHT_TITLE'),
			default: false,
		},
		preCalcBattle: {
			label: I18N('BATTLE_RECALCULATION'),
			cbox: null,
			title: I18N('BATTLE_RECALCULATION_TITLE'),
			default: false
		},
		countControl: {
			label: I18N('QUANTITY_CONTROL'),
			cbox: null,
			title: I18N('QUANTITY_CONTROL_TITLE'),
			default: true
		},
		repeatMission: {
			label: I18N('REPEAT_CAMPAIGN'),
			cbox: null,
			title: I18N('REPEAT_CAMPAIGN_TITLE'),
			default: false
		},
		noOfferDonat: {
			label: I18N('DISABLE_DONAT'),
			cbox: null,
			title: I18N('DISABLE_DONAT_TITLE'),
			/**
			 * A crutch to get the field before getting the character id
			 *
			 * Костыль чтоб получать поле до получения id персонажа
			 */
			default: (() => {
				$result = false;
				try {
					$result = JSON.parse(localStorage[GM_info.script.name + ':noOfferDonat'])
				} catch(e) {
					$result = false;
				}
				return $result || false;
			})(),
		},
		dailyQuests: {
			label: I18N('DAILY_QUESTS'),
			cbox: null,
			title: I18N('DAILY_QUESTS_TITLE'),
			default: false
		},
		autoBrawls: {
			label: I18N('BRAWLS'),
			cbox: null,
			title: I18N('BRAWLS_TITLE'),
			default: (() => {
				$result = false;
				try {
					$result = JSON.parse(localStorage[GM_info.script.name + ':autoBrawls'])
				} catch (e) {
					$result = false;
				}
				return $result || false;
			})(),
		}
		/*
		getAnswer: {
			label: I18N('AUTO_QUIZ'),
			cbox: null,
			title: I18N('AUTO_QUIZ_TITLE'),
			default: false
		}
		*/
	};
	/**
	 * Get checkbox state
	 *
	 * Получить состояние чекбокса
	 */
	function isChecked(checkBox) {
		return checkboxes[checkBox].cbox?.checked;
	}
	/**
	 * Input fields
	 *
	 * Поля ввода
	 */
	const inputs = {
		countTitanit: {
			input: null,
			title: I18N('HOW_MUCH_TITANITE'),
			default: 150,
		},
		speedBattle: {
			input: null,
			title: I18N('COMBAT_SPEED'),
			default: 5,
		},
		countTestBattle: {
			input: null,
			title: I18N('NUMBER_OF_TEST'),
			default: 10,
		},
		countAutoBattle: {
			input: null,
			title: I18N('NUMBER_OF_AUTO_BATTLE'),
			default: 10,
		}
	}
	/**
	 * Checks the checkbox
	 *
	 * Поплучить данные поля ввода
	 */
	function getInput(inputName) {
		return inputs[inputName].input.value;
	}
	/**
	 * Button List
	 *
	 * Список кнопочек
	 */
	const buttons = {
		getOutland: {
			name: I18N('TO_DO_EVERYTHING'),
			title: I18N('TO_DO_EVERYTHING_TITLE'),
			func: testDoYourBest,
		},
		/*
		getOutland: {
			name: I18N('OUTLAND'),
			title: I18N('OUTLAND_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('OUTLAND')}?`, getOutland);
			},
		},
		*/
		testTitanArena: {
			name: I18N('TITAN_ARENA'),
			title: I18N('TITAN_ARENA_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('TITAN_ARENA')}?`, testTitanArena);
			},
		},
		testDungeon: {
			name: I18N('DUNGEON'),
			title: I18N('DUNGEON_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('DUNGEON')}?`, testDungeon);
			},
		},
		/*
		testTower: {
			name: I18N('TOWER'),
			title: I18N('TOWER_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('TOWER')}?`, testTower);
			},
		},
		*/
		sendExpedition: {
			name: I18N('EXPEDITIONS'),
			title: I18N('EXPEDITIONS_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('EXPEDITIONS')}?`, checkExpedition);
			},
		},
		newDay: {
			name: I18N('SYNC'),
			title: I18N('SYNC_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('SYNC')}?`, cheats.refreshGame);
			},
		},
		/*
		bossRatingEvent: {
			name: I18N('ARCHDEMON'),
			title: I18N('ARCHDEMON_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('ARCHDEMON')}?`, bossRatingEvent);
			},
		},
		*/
		/*
		offerFarmAllReward: {
			name: I18N('ESTER_EGGS'),
			title: I18N('ESTER_EGGS_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('ESTER_EGGS')}?`, offerFarmAllReward);
			},
		},
		*/
		questAllFarm: {
			name: I18N('REWARDS'),
			title: I18N('REWARDS_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('REWARDS')}?`, questAllFarm);
			},
		},
		mailGetAll: {
			name: I18N('MAIL'),
			title: I18N('MAIL_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('MAIL')}?`, mailGetAll);
			},
		},
		testRaidNodes: {
			name: I18N('MINIONS'),
			title: I18N('MINIONS_TITLE'),
			func: function () {
				confShow(`${I18N('RUN_SCRIPT')} ${I18N('MINIONS')}?`, testRaidNodes);
			},
		},
		testAdventure: {
			name: I18N('ADVENTURE'),
			title: I18N('ADVENTURE_TITLE'),
			func: () => {
				testAdventure();
			},
		},
		/*
		testSoloAdventure: {
			name: I18N('STORM'),
			title: I18N('STORM_TITLE'),
			func: () => {
				testAdventure('solo');
			},
		},
		*/
		goToSanctuary: {
			name: I18N('SANCTUARY'),
			title: I18N('SANCTUARY_TITLE'),
			func: cheats.goSanctuary,
		},
		goToClanWar: {
			name: I18N('GUILD_WAR'),
			title: I18N('GUILD_WAR_TITLE'),
			func: cheats.goClanWar,
		},
	}
	/**
	 * Display buttons
	 *
	 * Вывести кнопочки
	 */
	function addControlButtons() {
		for (let name in buttons) {
			button = buttons[name];
			button['button'] = scriptMenu.addButton(button.name, button.func, button.title);
		}
	}
	/**
	 * Adds links
	 *
	 * Добавляет ссылки
	 */
	function addBottomUrls() {
		scriptMenu.addHeader(I18N('BOTTOM_URLS'));
	}
	/**
	 * Stop repetition of the mission
	 *
	 * Остановить повтор миссии
	 */
	let isStopSendMission = false;
	/**
	 * There is a repetition of the mission
	 *
	 * Идет повтор миссии
	 */
	let isSendsMission = false;
	/**
	 * Data on the past mission
	 *
	 * Данные о прошедшей мисии
	 */
	let lastMissionStart = {}

	/**
	 * Data on the past attack on the boss
	 *
	 * Данные о прошедшей атаке на босса
	 */
	let lastBossBattle = {}
	/**
	 * Data for calculating the last battle with the boss
	 *
	 * Данные для расчете последнего боя с боссом
	 */
	let lastBossBattleInfo = null;
	/**
	 * Ability to cancel the battle in Asgard
	 *
	 * Возможность отменить бой в Астгарде
	 */
	let isCancalBossBattle = true;
	/**
	 * Information about the last battle
	 *
	 * Данные о прошедшей битве
	 */
	let lastBattleArg = {}
	/**
	 * The name of the function of the beginning of the battle
	 *
	 * Имя функции начала боя
	 */
	let nameFuncStartBattle = '';
	/**
	 * The name of the function of the end of the battle
	 *
	 * Имя функции конца боя
	 */
	let nameFuncEndBattle = '';
	/**
	 * Data for calculating the last battle
	 *
	 * Данные для расчета последнего боя
	 */
	let lastBattleInfo = null;
	/**
	 * The ability to cancel the battle
	 *
	 * Возможность отменить бой
	 */
	let isCancalBattle = true;

	/**
	 * Certificator of the last open nesting doll
	 *
	 * Идетификатор последней открытой матрешки
	 */
	let lastRussianDollId = null;
	/**
	 * Cancel the training guide
	 *
	 * Отменить обучающее руководство
	 */
	this.isCanceledTutorial = false;

	/**
	 * Data from the last question of the quiz
	 *
	 * Данные последнего вопроса викторины
	 */
	let lastQuestion = null;
	/**
	 * Answer to the last question of the quiz
	 *
	 * Ответ на последний вопрос викторины
	 */
	let lastAnswer = null;
	/**
	 * Flag for opening keys or titan artifact spheres
	 *
	 * Флаг открытия ключей или сфер артефактов титанов
	 */
	let artifactChestOpen = false;
	/**
	 * The name of the function to open keys or orbs of titan artifacts
	 *
	 * Имя функции открытия ключей или сфер артефактов титанов
	 */
	let artifactChestOpenCallName = '';

	/**
	 * Brawl pack
	 *
	 * Пачка для потасовок 
	 */
	let brawlsPack = null;
	/**
	 * Autobrawl started
	 *
	 * Автопотасовка запущена
	 */
	let isBrawlsAutoStart = false;

	/**
	 * Copies the text to the clipboard
	 *
	 * Копирует тест в буфер обмена
	 * @param {*} text copied text // копируемый текст
	 */
	function copyText(text) {
		let copyTextarea = document.createElement("textarea");
		copyTextarea.style.opacity = "0";
		copyTextarea.textContent = text;
		document.body.appendChild(copyTextarea);
		copyTextarea.select();
		document.execCommand("copy");
		document.body.removeChild(copyTextarea);
		delete copyTextarea;
	}
	/**
	 * Returns the history of requests
	 *
	 * Возвращает историю запросов
	 */
	this.getRequestHistory = function() {
		return requestHistory;
	}
	/**
	 * Generates a random integer from min to max
	 *
	 * Гененирует случайное целое число от min до max
	 */
	const random = function (min, max) {
		return Math.floor(Math.random() * (max - min + 1) + min);
	}
	/**
	 * Clearing the request history
	 *
	 * Очистка истоии запросов
	 */
	setInterval(function () {
		let now = Date.now();
		for (let i in requestHistory) {
			if (now - i > 300000) {
				delete requestHistory[i];
			}
		}
	}, 300000);
	/**
	 * DOM Loading Event page
	 *
	 * Событие загрузки DOM дерева страницы
	 */
	document.addEventListener("DOMContentLoaded", () => {
		/**
		 * Create the script interface
		 *
		 * Создание интерфеса скрипта
		 */
		createInterface();
	});
	/**
	 * Gift codes collecting and sending codes
	 *
	 * Сбор и отправка кодов подарков
	 */
	function sendCodes() {
		let codes = [], count = 0;
		if (!localStorage['giftSendIds']) {
			localStorage['giftSendIds'] = '';
		}
		document.querySelectorAll('a[target="_blank"]').forEach(e => {
			let url = e?.href;
			if (!url) return;
			url = new URL(url);
			let giftId = url.searchParams.get('gift_id');
			if (!giftId || localStorage['giftSendIds'].includes(giftId)) return;
			localStorage['giftSendIds'] += ';' + giftId;
			codes.push(giftId);
			count++;
		});

		if (codes.length) {
			localStorage['giftSendIds'] = localStorage['giftSendIds'].split(';').splice(-50).join(';');
			sendGiftsCodes(codes);
		}

		if (!count) {
			setTimeout(sendCodes, 2000);
		}
	}
	/**
	 * Checking sent codes
	 *
	 * Проверка отправленных кодов
	 */
	function checkSendGifts() {
		if (!freebieCheckInfo) {
			return;
		}

		let giftId = freebieCheckInfo.args.giftId;
		let valName = 'giftSendIds_' + userInfo.id;
		localStorage[valName] = localStorage[valName] ?? '';
		if (!localStorage[valName].includes(giftId)) {
			localStorage[valName] += ';' + giftId;
			sendGiftsCodes([giftId]);
		}
	}
	/**
	 * Sending codes
	 *
	 * Отправка кодов
	 */
	function sendGiftsCodes(codes) {
		fetch('https://zingery.ru/heroes/setGifts.php', {
			method: 'POST',
			body: JSON.stringify(codes)
		}).then(
			response => response.json()
		).then(
			data => {
				if (data.result) {
					console.log(I18N('GIFTS_SENT'));
				}
			}
		)
	}
	/**
	 * Displays the dialog box
	 *
	 * Отображает диалоговое окно
	 */
	function confShow(message, yesCallback, noCallback) {
		let buts = [];
		message = message || I18N('DO_YOU_WANT');
		noCallback = noCallback || (() => {});
		if (yesCallback) {
			buts = [
				{ msg: I18N('BTN_RUN'), result: true},
				{ msg: I18N('BTN_CANCEL'), result: false},
			]
		} else {
			yesCallback = () => {};
			buts = [
				{ msg: I18N('BTN_OK'), result: true},
			];
		}
		popup.confirm(message, buts).then((e) => {
			if (e) {
				yesCallback();
			} else {
				noCallback();
			}
		});
	}
	/**
	 * Overriding/Proxying the Ajax Request Creation Method
	 *
	 * Переопределяем/проксируем метод создания Ajax запроса
	 */
	XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
		this.uniqid = Date.now();
		this.errorRequest = false;
		if (method == 'POST' && url.includes('.nextersglobal.com/api/') && /api\/$/.test(url)) {
			if (!apiUrl) {
				apiUrl = url;
				socialInfo = /heroes-(.+?)\./.exec(apiUrl);
				sNetwork = socialInfo ? socialInfo[1] : 'vk';
			}
			requestHistory[this.uniqid] = {
				method,
				url,
				error: [],
				headers: {},
				request: null,
				response: null,
				signature: [],
				calls: {},
			};
		} else if (method == 'POST' && url.includes('error.nextersglobal.com/client/')) {
			this.errorRequest = true;
		}
		return original.open.call(this, method, url, async, user, password);
	};
	/**
	 * Overriding/Proxying the header setting method for the AJAX request
	 *
	 * Переопределяем/проксируем метод установки заголовков для AJAX запроса
	 */
	XMLHttpRequest.prototype.setRequestHeader = function (name, value, check) {
		if (this.uniqid in requestHistory) {
			requestHistory[this.uniqid].headers[name] = value;
		} else {
			check = true;
		}

		if (name == 'X-Auth-Signature') {
			requestHistory[this.uniqid].signature.push(value);
			if (!check) {
				return;
			}
		}

		return original.setRequestHeader.call(this, name, value);
	};
	/**
	 * Overriding/Proxying the AJAX Request Sending Method
	 *
	 * Переопределяем/проксируем метод отправки AJAX запроса
	 */
	XMLHttpRequest.prototype.send = async function (sourceData) {
		if (this.uniqid in requestHistory) {
			let tempData = null;
			if (getClass(sourceData) == "ArrayBuffer") {
				tempData = decoder.decode(sourceData);
			} else {
				tempData = sourceData;
			}
			requestHistory[this.uniqid].request = tempData;
			let headers = requestHistory[this.uniqid].headers;
			lastHeaders = Object.assign({}, headers);
			/**
			 * Game loading event
			 *
			 * Событие загрузки игры
			 */
			if (headers["X-Request-Id"] > 2 && !isLoadGame) {
				isLoadGame = true;
				await openOrMigrateDatabase(userInfo.id);
				addControls();
				addControlButtons();
				addBottomUrls();

				if (isChecked('sendExpedition')) {
					checkExpedition();
				}

				checkSendGifts();
				getAutoGifts();

				cheats.activateHacks();
			
				justInfo();
				if (isChecked('dailyQuests')) {
					testDailyQuests();
				}
			}
			/**
			 * Outgoing request data processing
			 *
			 * Обработка данных исходящего запроса
			 */
			sourceData = await checkChangeSend.call(this, sourceData, tempData);
			/**
			 * Handling incoming request data
			 *
			 * Обработка данных входящего запроса
			 */
			const oldReady = this.onreadystatechange;
			this.onreadystatechange = function (e) {
				if(this.readyState == 4 && this.status == 200) {
					isTextResponse = this.responseType != "json";
					let response = isTextResponse ? this.responseText : this.response;
					requestHistory[this.uniqid].response = response;
					/**
					 * Replacing incoming request data
					 *
					 * Заменна данных входящего запроса
					 */
					if (isTextResponse) {
						checkChangeResponse.call(this, response);
					}
					/**
					 * A function to run after the request is executed
					 *
					 * Функция запускаемая после выполения запроса
					 */
					if (typeof this.onReadySuccess == 'function') {
						setTimeout(this.onReadySuccess, 500);
					}
				}
				if (oldReady) {
					return oldReady.apply(this, arguments);
				}
			}
		}
		if (this.errorRequest) {
			const oldReady = this.onreadystatechange;
			this.onreadystatechange = function () {
				Object.defineProperty(this, 'status', {
					writable: true
				});
				this.status = 200;
				Object.defineProperty(this, 'readyState', {
					writable: true
				});
				this.readyState = 4;
				Object.defineProperty(this, 'responseText', {
					writable: true
				});
				this.responseText = JSON.stringify({
					"result": true
				});
				return oldReady.apply(this, arguments);
			}
			this.onreadystatechange();
		} else {
			return original.send.call(this, sourceData);
		}
	};
	/**
	 * Processing and substitution of outgoing data
	 *
	 * Обработка и подмена исходящих данных
	 */
	async function checkChangeSend(sourceData, tempData) {
		try {
			/**
			 * A function that replaces battle data with incorrect ones to cancel combatя
			 *
			 * Функция заменяющая данные боя на неверные для отмены боя
			 */
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			/**
			 * Dialog window 2
			 *
			 * Диалоговое окно 2
			 */
			const showMsg = async function (msg, ansF, ansS) {
				if (typeof popup == 'object') {
					return await popup.confirm(msg, [
						{msg: ansF, result: false},
						{msg: ansS, result: true},
					]);
				} else {
					return !confirm(`${msg}\n ${ansF} (${I18N('BTN_OK')})\n ${ansS} (${I18N('BTN_CANCEL')})`);
				}
			}
			/**
			 * Dialog window 3
			 *
			 * Диалоговое окно 3
			 */
			const showMsgs = async function (msg, ansF, ansS, ansT) {
				return await popup.confirm(msg, [
					{msg: ansF, result: 0},
					{msg: ansS, result: 1},
					{msg: ansT, result: 2},
				]);
			}

			let changeRequest = false;
			testData = JSON.parse(tempData);
			for (const call of testData.calls) {
				if (!artifactChestOpen) {
					requestHistory[this.uniqid].calls[call.name] = call.ident;
				}
				/**
				 * Cancellation of the battle in adventures, on VG and with minions of Asgard
				 * Отмена боя в приключениях, на ВГ и с прислужниками Асгарда
				 */
				if ((call.name == 'adventure_endBattle' ||
					call.name == 'adventureSolo_endBattle' ||
					call.name == 'clanWarEndBattle' && isChecked('cancelBattleBan') ||
					call.name == 'crossClanWar_endBattle' && isChecked('cancelBattleBan') ||
					call.name == 'brawl_endBattle' ||
					call.name == 'towerEndBattle' ||
					call.name == 'clanRaid_endNodeBattle') &&
					isCancalBattle) {
					nameFuncEndBattle = call.name;
					if (!call.args.result.win) {
						let resultPopup = false;
						if (call.name == 'adventure_endBattle' ||
							call.name == 'adventureSolo_endBattle') {
							resultPopup = await showMsgs(I18N('MSG_HAVE_BEEN_DEFEATED'), I18N('BTN_OK'), I18N('BTN_CANCEL'), I18N('BTN_AUTO'));
						} else {
							resultPopup = await showMsg(I18N('MSG_HAVE_BEEN_DEFEATED'), I18N('BTN_OK'), I18N('BTN_CANCEL'));
						}
						if (resultPopup) {
							fixBattle(call.args.progress[0].attackers.heroes);
							fixBattle(call.args.progress[0].defenders.heroes);
							changeRequest = true;
							if (resultPopup > 1) {
								this.onReadySuccess = testAutoBattle;
								// setTimeout(bossBattle, 1000);
							}
						}
					} else if (call.args.result.stars < 3 && call.name == 'towerEndBattle') {
						resultPopup = await showMsg(I18N('LOST_HEROES'), I18N('BTN_OK'), I18N('BTN_CANCEL'));
						if (resultPopup) {
							fixBattle(call.args.progress[0].attackers.heroes);
							fixBattle(call.args.progress[0].defenders.heroes);
							changeRequest = true;
						}
					}

					if (isChecked('autoBrawls') && !isBrawlsAutoStart && call.name == 'brawl_endBattle') {
						if (await popup.confirm(I18N('START_AUTO_BRAWLS'), [
							{ msg: I18N('BTN_NO'), result: false },
							{ msg: I18N('BTN_YES'), result: true },
						])) {
							this.onReadySuccess = testBrawls;
							isBrawlsAutoStart = true;
						}
					}
				}
				/** 
				 * Save pack for Brawls
				 * 
				 * Сохраняем пачку для потасовок
				 */
				if (call.name == 'brawl_startBattle') {
					console.log(JSON.stringify(call.args));
					brawlsPack = call.args.heroes;
				}
				/**
				 * Canceled fight in Asgard
				 * Отмена боя в Асгарде
				 */
				if (call.name == 'clanRaid_endBossBattle' &&
					isCancalBossBattle &&
					isChecked('cancelBattleBan')) {
					bossDamage = call.args.progress[0].defenders.heroes[1].extra;
					sumDamage = bossDamage.damageTaken + bossDamage.damageTakenNextLevel;
					let resultPopup = await showMsgs(
						`${I18N('MSG_YOU_APPLIED')} ${sumDamage.toLocaleString()} ${I18N('MSG_DAMAGE')}.`,
						I18N('BTN_OK'), I18N('BTN_CANCEL'), I18N('MSG_CANCEL_AND_STAT'))
					if (resultPopup) {
						fixBattle(call.args.progress[0].attackers.heroes);
						fixBattle(call.args.progress[0].defenders.heroes);
						changeRequest = true;
						if (resultPopup > 1) {
							this.onReadySuccess = testBossBattle;
							// setTimeout(bossBattle, 1000);
						}
					}
				}
				/**
				 * Save the Asgard Boss Attack Pack
				 * Сохраняем пачку для атаки босса Асгарда
				 */
				if (call.name == 'clanRaid_startBossBattle') {
					lastBossBattle = call.args;
				}
				/**
				 * Saving the request to start the last battle
				 * Сохранение запроса начала последнего боя 
				 */
				if (call.name == 'clanWarAttack' ||
					call.name == 'crossClanWar_startBattle' ||
					call.name == 'adventure_turnStartBattle') {
					nameFuncStartBattle = call.name;
					lastBattleArg = call.args;
				}
				/**
				 * Disable spending divination cards
				 * Отключить трату карт предсказаний
				 */
				if (call.name == 'dungeonEndBattle') {
					if (isChecked('endlessCards') && call.args.isRaid) {
						delete call.args.isRaid;
						changeRequest = true;
					}
				}
				/**
				 * Quiz Answer
				 * Ответ на викторину
				 */
				if (call.name == 'quizAnswer') {
					/**
					 * Automatically changes the answer to the correct one if there is one.
					 * Автоматически меняет ответ на правильный если он есть
					 */
					if (lastAnswer && isChecked('getAnswer')) {
						call.args.answerId = lastAnswer;
						lastAnswer = null;
						changeRequest = true;
					}
				}
				/**
				 * Present
				 * Подарки
				 */
				if (call.name == 'freebieCheck') {
					freebieCheckInfo = call;
				}
				/**
				 * Getting mission data for auto-repeat
				 * Получение данных миссии для автоповтора
				 */
				if (isChecked('repeatMission') &&
					call.name == 'missionEnd') {
					let missionInfo = {
						id: call.args.id,
						result: call.args.result,
						heroes: call.args.progress[0].attackers.heroes,
						count: 0,
					}
					setTimeout(async () => {
						if (!isSendsMission && await popup.confirm(I18N('MSG_REPEAT_MISSION'), [
								{ msg: I18N('BTN_REPEAT'), result: true},
								{ msg: I18N('BTN_NO'), result: false},
							])) {
							isStopSendMission = false;
							isSendsMission = true;
							sendsMission(missionInfo);
						}
					}, 0);
				}
				/**
				 * Getting mission data
				 * Получение данных миссии
				 */
				if (call.name == 'missionStart') {
					lastMissionStart = call.args;
				}
				/**
				 * Specify the quantity for Titan Orbs and Pet Eggs
				 * Указать количество для сфер титанов и яиц петов
				 */
				if (isChecked('countControl') &&
					(call.name == 'pet_chestOpen' ||
					call.name == 'titanUseSummonCircle') &&
					call.args.amount > 1) {
					call.args.amount = 1;
					const result = await popup.confirm(I18N('MSG_SPECIFY_QUANT'), [
						{ msg: I18N('BTN_OPEN'), isInput: true, default: call.args.amount},
						]);
					if (result) {
						call.args.amount = result;
						changeRequest = true;
					}
				}
				/**
				 * Specify the amount for keys and spheres of titan artifacts
				 * Указать колличество для ключей и сфер артефактов титанов
				 */
				if (isChecked('countControl') &&
					(call.name == 'artifactChestOpen' ||
					call.name == 'titanArtifactChestOpen') &&
					call.args.amount > 1 &&
					call.args.free &&
					!changeRequest) {
					artifactChestOpenCallName = call.name;
					let result = await popup.confirm(I18N('MSG_SPECIFY_QUANT'), [
						{ msg: I18N('BTN_OPEN'), isInput: true, default: call.args.amount },
					]);
					if (result) {
						let sphere = result < 10 ? 1 : 10;

						call.args.amount = sphere;
						result -= sphere;

						for (let count = result; count > 0; count -= sphere) {
							if (count < 10) sphere = 1;
							const ident = artifactChestOpenCallName + "_" + count;
							testData.calls.push({
								name: artifactChestOpenCallName,
								args: {
									amount: sphere,
									free: true,
								},
								ident: ident
							});
							if (!Array.isArray(requestHistory[this.uniqid].calls[call.name])) {
								requestHistory[this.uniqid].calls[call.name] = [requestHistory[this.uniqid].calls[call.name]];
							}
							requestHistory[this.uniqid].calls[call.name].push(ident);
						}

						artifactChestOpen = true;
						changeRequest = true;
					}
				}
				if (call.name == 'consumableUseLootBox') {
					lastRussianDollId = call.args.libId;
					/**
					 * Specify quantity for gold caskets
					 * Указать количество для золотых шкатулок
					 */
					if (isChecked('countControl') &&
						call.args.libId == 148 &&
						call.args.amount > 1) {
						const result = await popup.confirm(I18N('MSG_SPECIFY_QUANT'), [
							{ msg: I18N('BTN_OPEN'), isInput: true, default: call.args.amount},
						]);
						call.args.amount = result;
						changeRequest = true;
					}
				}
			}

			let headers = requestHistory[this.uniqid].headers;
			if (changeRequest) {
				sourceData = JSON.stringify(testData);
				headers['X-Auth-Signature'] = getSignature(headers, sourceData);
			}

			let signature = headers['X-Auth-Signature'];
			if (signature) {
				this.setRequestHeader('X-Auth-Signature', signature, true);
			}
		} catch (err) {
			console.log("Request(send, " + this.uniqid + "):\n", sourceData, "Error:\n", err);
		}
		return sourceData;
	}
	/**
	 * Processing and substitution of incoming data
	 *
	 * Обработка и подмена входящих данных
	 */
	async function checkChangeResponse(response) {
		try {
			isChange = false;
			let nowTime = Math.round(Date.now() / 1000);
			callsIdent = requestHistory[this.uniqid].calls;
			respond = JSON.parse(response);
			/**
			 * If the request returned an error removes the error (removes synchronization errors)
			 * Если запрос вернул ошибку удаляет ошибку (убирает ошибки синхронизации)
			 */
			if (respond.error) {
				isChange = true;
				console.error(respond.error);
				delete respond.error;
				respond.results = [];
			}
			let mainReward = null;
			const allReward = {};
			for (const call of respond.results) {
				/**
				 * Getting a user ID
				 * Получение идетификатора пользователя
				 */
				if (call.ident == callsIdent['registration']) {
					userId = call.result.response.userId;
				}
				/**
				 * Endless lives in brawls
				 * Бесконечные жизни в потасовках
				 */
				if (getSaveVal('autoBrawls') && call.ident == callsIdent['brawl_getInfo']) {
					brawl = call.result.response;
					if (brawl) {
						brawl.boughtEndlessLivesToday = 1;
						isChange = true;
					}
				}
				/**
				 * Hiding donation offers
				 * Скрываем предложения доната
				 */
				if (call.ident == callsIdent['billingGetAll'] && getSaveVal('noOfferDonat')) {
					const billings = call.result.response?.billings;
					const bundle = call.result.response?.bundle;
					if (billings && bundle) {
						call.result.response.billings = [];
						call.result.response.bundle = [];
						isChange = true;
					}
				}
				/**
				 * Hiding donation offers
				 * Скрываем предложения доната
				 */
				if (call.ident == callsIdent['offerGetAll'] && getSaveVal('noOfferDonat')) {
					const offers = call.result.response;
					if (offers) {
						call.result.response = offers.filter(e => !['addBilling', 'bundleCarousel'].includes(e.type));
						isChange = true;
					}
				}
				/**
				 * Copies a quiz question to the clipboard
				 * Копирует вопрос викторины в буфер обмена и получает на него ответ если есть
				 */
				if (call.ident == callsIdent['quizGetNewQuestion']) {
					let quest = call.result.response;
					console.log(quest.question);
					copyText(quest.question);
					setProgress(I18N('QUESTION_COPY'), true);
					lastQuestion = quest;
					if (isChecked('getAnswer')) {
						const answer = await getAnswer(lastQuestion);
						if (answer) {
							lastAnswer = answer;
							console.log(answer);
							setProgress(`${I18N('ANSWER_KNOWN')}: ${answer}`, true);
						} else {
							setProgress(I18N('ANSWER_NOT_KNOWN'), true);
						}
					}
				}
				/**
				 * Submits a question with an answer to the database
				 * Отправляет вопрос с ответом в базу данных
				 */
				if (call.ident == callsIdent['quizAnswer']) {
					const answer = call.result.response;
					if (lastQuestion) {
						const answerInfo = {
							answer,
							question: lastQuestion
						}
						lastQuestion = null;
						setTimeout(sendAnswerInfo, 0, answerInfo);
					}
				}
				/**
				 * Get user data
				 * Получить даныне пользователя
				 */
				if (call.ident == callsIdent['userGetInfo']) {
					let user = call.result.response;
					userInfo = Object.assign({}, user);
					delete userInfo.refillable;
					if (!questsInfo['userGetInfo']) {
						questsInfo['userGetInfo'] = user;
					}
				}
				/**
				 * Start of the battle for recalculation
				 * Начало боя для прерасчета
				 */
				if ((call.ident == callsIdent['clanWarAttack'] ||
					call.ident == callsIdent['crossClanWar_startBattle'] ||
					call.ident == callsIdent['battleGetReplay'] ||
					call.ident == callsIdent['adventure_turnStartBattle']) &&
					isChecked('preCalcBattle')) {
					setProgress('Идет прерасчет боя');
					let battle = call.result.response.battle || call.result.response.replay;
					lastBattleInfo = battle;
					console.log(battle.type);
					function getBattleInfo(battle, isRandSeed) {
						return new Promise(function (resolve) {
							if (isRandSeed) {
								battle.seed = Math.floor(Date.now() / 1000) + random(0, 1e3);
							}
							BattleCalc(battle, getBattleType(battle.type), e => resolve(e.result.win));
						});
					}
					let actions = [getBattleInfo(battle, false)]
					const countTestBattle = getInput('countTestBattle');
					for (let i = 0; i < countTestBattle; i++) {
						actions.push(getBattleInfo(battle, true));
					}
					Promise.all(actions)
						.then(e => {
							let firstBattle = e.shift();
							let countWin = e.reduce((w, s) => w + s);
							setProgress(`${I18N('THIS_TIME')} ${(firstBattle ? I18N('VICTORY') : I18N('DEFEAT'))} ${I18N('CHANCE_TO_WIN')}: ${Math.floor(countWin / e.length * 100)}% (${e.length})`, false, hideProgress)
						});
				}
				/**
				 * Start of the Asgard boss fight
				 * Начало боя с боссом Асгарда
				 */
				if (call.ident == callsIdent['clanRaid_startBossBattle']) {
					lastBossBattleInfo = call.result.response.battle;
				}
				/**
				 * Cancel tutorial
				 * Отмена туториала
				 */
				if (isCanceledTutorial && call.ident == callsIdent['tutorialGetInfo']) {
					let chains = call.result.response.chains;
					for (let n in chains) {
						chains[n] = 9999;
					}
					isChange = true;
				}
				/**
				 * Opening keys and spheres of titan artifacts
				 * Открытие ключей и сфер артефактов титанов
				 */
				if (artifactChestOpen &&
					(call.ident == callsIdent[artifactChestOpenCallName] ||
						(callsIdent[artifactChestOpenCallName] && callsIdent[artifactChestOpenCallName].includes(call.ident)))) {
					let reward = call.result.response[artifactChestOpenCallName == 'artifactChestOpen' ? 'chestReward' : 'reward'];

					reward.forEach(e => {
						for (let f in e) {
							if (!allReward[f]) {
								allReward[f] = {};
							}
							for (let o in e[f]) {
								if (!allReward[f][o]) {
									allReward[f][o] = e[f][o];
								} else {
									allReward[f][o] += e[f][o];
								}
							}
						}
					});

					if (!call.ident.includes(artifactChestOpenCallName)) {
						mainReward = call.result.response;
					}
				}
				/**
				 * Auto-repeat opening matryoshkas
				 * АвтоПовтор открытия матрешек
				 */
				if (isChecked('countControl') && call.ident == callsIdent['consumableUseLootBox']) {
					let lootBox = call.result.response;
					let newCount = 0;
					for (let n of lootBox) {
						if (n?.consumable && n.consumable[lastRussianDollId]) {
							newCount += n.consumable[lastRussianDollId]
						}
					}
					if (newCount && await popup.confirm(`${I18N('BTN_OPEN')} ${newCount} ${I18N('OPEN_DOLLS')}?`, [
							{ msg: I18N('BTN_OPEN'), result: true},
							{ msg: I18N('BTN_NO'), result: false},
						])) {
						openRussianDoll(lastRussianDollId, newCount);
					}
				}
				/**
				 * Getting data on quests
				 * Получение данных по квестам
				 */
				if (call.ident == callsIdent['questGetAll']) {
					if (!questsInfo['questGetAll']) {
						questsInfo['questGetAll'] = call.result.response;
					}
				}
				/**
				 * Getting Inventory Data for Quests
				 * Получение данных инвентаря для квестов
				 */
				if (call.ident == callsIdent['inventoryGet']) {
					if (!questsInfo['inventoryGet']) {
						questsInfo['inventoryGet'] = call.result.response;
					}
				}
				/**
				 * Obtaining Hero Data for Quests
				 * Получение данных героев для квестов
				 */
				if (call.ident == callsIdent['heroGetAll']) {
					if (!questsInfo['heroGetAll']) {
						questsInfo['heroGetAll'] = call.result.response;
					}
				}
				/**
				 * Obtaining titan data for quests
				 * Получение данных титанов для квестов
				 */
				if (call.ident == callsIdent['titanGetAll']) {
					if (!questsInfo['titanGetAll']) {
						questsInfo['titanGetAll'] = call.result.response;
					}
				}
			}

			if (mainReward && artifactChestOpen) {
				console.log(allReward);
				mainReward[artifactChestOpenCallName == 'artifactChestOpen' ? 'chestReward' : 'reward'] = [allReward];
				artifactChestOpen = false;
				artifactChestOpenCallName = '';
				isChange = true;
			}
		} catch(err) {
			console.log("Request(response, " + this.uniqid + "):\n", "Error:\n", response, err);
		}

		if (isChange) {
			Object.defineProperty(this, 'responseText', {
				writable: true
			});
			this.responseText = JSON.stringify(respond);
		}
	}

	/**
	 * Request an answer to a question
	 *
	 * Запрос ответа на вопрос
	 */
	async function getAnswer(question) {
		return new Promise((resolve, reject) => {
			fetch('https://zingery.ru/heroes/getAnswer.php', {
				method: 'POST',
				body: JSON.stringify(question)
			}).then(
				response => response.json()
			).then(
				data => {
					if (data.result) {
						resolve(data.result);
					} else {
						resolve(false);
					}
				}
			).catch((error) => {
				console.error(error);
				resolve(false);
			});
		})
	}

	/**
	 * Submitting a question and answer to a database
	 *
	 * Отправка вопроса и ответа в базу данных
	 */
	function sendAnswerInfo(answerInfo) {
		fetch('https://zingery.ru/heroes/setAnswer.php', {
			method: 'POST',
			body: JSON.stringify(answerInfo)
		}).then(
			response => response.json()
		).then(
			data => {
				if (data.result) {
					console.log(I18N('SENT_QUESTION'));
				}
			}
		)
	}

	/**
	 * Returns the battle type by preset type
	 *
	 * Возвращает тип боя по типу пресета
	 */
	function getBattleType(strBattleType) {
		switch (strBattleType) {
			case "invasion":
				return "get_invasion";
			case "titan_pvp_manual":
				return "get_titanPvpManual";
			case "titan_pvp":
				return "get_titanPvp";
			case "titan_clan_pvp":
			case "clan_pvp_titan":
			case "clan_global_pvp_titan":
			case "brawl_titan":
			case "challenge_titan":
				return "get_titanClanPvp";
			case "clan_raid": // Asgard Boss // Босс асгарда
			case "adventure": // Adventures // Приключения
			case "clan_global_pvp":
			case "clan_pvp":
			case "challenge":
			case "grand":
			case "arena":
				return "get_clanPvp";
			case "titan_tower":
				return "get_titan";
			case "tower":
				return "get_tower";
			case "pve":
				return "get_pve";
			case "pvp_manual":
				return "get_pvpManual";
			case "pvp":
				return "get_pvp";
			case "core":
				return "get_core";
			default:
				return "get_clanPvp";
		}
	}
	/**
	 * Returns the class name of the passed object
	 *
	 * Возвращает название класса переданного объекта
	 */
	function getClass(obj) {
		return {}.toString.call(obj).slice(8, -1);
	}
	/**
	 * Calculates the request signature
	 *
	 * Расчитывает сигнатуру запроса
	 */
	this.getSignature = function(headers, data) {
		let signatureStr = [headers["X-Request-Id"], headers["X-Auth-Token"], headers["X-Auth-Session-Id"], data, 'LIBRARY-VERSION=1'].join(':');
		return md5(signatureStr);
	}
	/**
	 * Creates an interface
	 *
	 * Создает интерфейс
	 */
	function createInterface() {
		scriptMenu.init({
			showMenu: true
		});
		scriptMenu.addHeader(GM_info.script.name, justInfo);
		scriptMenu.addHeader('v' + GM_info.script.version);
	}

	function addControls() {
		const checkboxDetails = scriptMenu.addDetails(I18N('SETTINGS'));
		for (let name in checkboxes) {
			checkboxes[name].cbox = scriptMenu.addCheckbox(checkboxes[name].label, checkboxes[name].title, checkboxDetails);
			/**
			 * Getting the state of checkboxes from storage
			 * Получаем состояние чекбоксов из storage
			 */
			let val = storage.get(name, null);
			if (val != null) {
				checkboxes[name].cbox.checked = val;
			} else {
				storage.set(name, checkboxes[name].default);
				checkboxes[name].cbox.checked = checkboxes[name].default;
			}
			/**
			 * Tracing the change event of the checkbox for writing to storage
			 * Отсеживание события изменения чекбокса для записи в storage
			 */
			checkboxes[name].cbox.dataset['name'] = name;
			checkboxes[name].cbox.addEventListener('change', async function (event) {
				const nameCheckbox = this.dataset['name'];
				if (this.checked && nameCheckbox == 'cancelBattleBan') {
					this.checked = false;
					if (await popup.confirm(I18N('MSG_BAN_ATTENTION'), [
						{ msg: I18N('BTN_NO_I_AM_AGAINST'), result: true },
						{ msg: I18N('BTN_YES_I_AGREE'), result: false },
					])) {
						return;
					}
					this.checked = true;
				}
				storage.set(nameCheckbox, this.checked);
			})
		}

		const inputDetails = scriptMenu.addDetails(I18N('VALUES'));
		for (let name in inputs) {
			inputs[name].input = scriptMenu.addInputText(inputs[name].title, false, inputDetails);
			/**
			 * Get inputText state from storage
			 * Получаем состояние inputText из storage
			 */
			let val = storage.get(name, null);
			if (val != null) {
				inputs[name].input.value = val;
			} else {
				storage.set(name, inputs[name].default);
				inputs[name].input.value = inputs[name].default;
			}
			/**
			 * Tracing a field change event for a record in storage
			 * Отсеживание события изменения поля для записи в storage
			 */
			inputs[name].input.dataset['name'] = name;
			inputs[name].input.addEventListener('input', function () {
				const inputName = this.dataset['name'];
				let value = +this.value;
				if (!value || Number.isNaN(value)) {
					value = storage.get(inputName, inputs[inputName].default);
					inputs[name].input.value = value;
				}
				storage.set(inputName, value);
			})
		}
	}
	/**
	 * Calculates HASH MD5 from string
	 *
	 * Расчитывает HASH MD5 из строки
	 */
	function md5(r){for(var a=(r,n,t,e,o,u)=>f(c(f(f(n,r),f(e,u)),o),t),n=(r,n,t,e,o,u,f)=>a(n&t|~n&e,r,n,o,u,f),t=(r,n,t,e,o,u,f)=>a(n&e|t&~e,r,n,o,u,f),e=(r,n,t,e,o,u,f)=>a(n^t^e,r,n,o,u,f),o=(r,n,t,e,o,u,f)=>a(t^(n|~e),r,n,o,u,f),f=function(r,n){var t=(65535&r)+(65535&n);return(r>>16)+(n>>16)+(t>>16)<<16|65535&t},c=(r,n)=>r<<n|r>>>32-n,u=Array(r.length>>2),h=0;h<u.length;h++)u[h]=0;for(h=0;h<8*r.length;h+=8)u[h>>5]|=(255&r.charCodeAt(h/8))<<h%32;len=8*r.length,u[len>>5]|=128<<len%32,u[14+(len+64>>>9<<4)]=len;var l=1732584193,i=-271733879,g=-1732584194,v=271733878;for(h=0;h<u.length;h+=16){var A=l,d=i,C=g,m=v;i=o(i=o(i=o(i=o(i=e(i=e(i=e(i=e(i=t(i=t(i=t(i=t(i=n(i=n(i=n(i=n(i,g=n(g,v=n(v,l=n(l,i,g,v,u[h+0],7,-680876936),i,g,u[h+1],12,-389564586),l,i,u[h+2],17,606105819),v,l,u[h+3],22,-1044525330),g=n(g,v=n(v,l=n(l,i,g,v,u[h+4],7,-176418897),i,g,u[h+5],12,1200080426),l,i,u[h+6],17,-1473231341),v,l,u[h+7],22,-45705983),g=n(g,v=n(v,l=n(l,i,g,v,u[h+8],7,1770035416),i,g,u[h+9],12,-1958414417),l,i,u[h+10],17,-42063),v,l,u[h+11],22,-1990404162),g=n(g,v=n(v,l=n(l,i,g,v,u[h+12],7,1804603682),i,g,u[h+13],12,-40341101),l,i,u[h+14],17,-1502002290),v,l,u[h+15],22,1236535329),g=t(g,v=t(v,l=t(l,i,g,v,u[h+1],5,-165796510),i,g,u[h+6],9,-1069501632),l,i,u[h+11],14,643717713),v,l,u[h+0],20,-373897302),g=t(g,v=t(v,l=t(l,i,g,v,u[h+5],5,-701558691),i,g,u[h+10],9,38016083),l,i,u[h+15],14,-660478335),v,l,u[h+4],20,-405537848),g=t(g,v=t(v,l=t(l,i,g,v,u[h+9],5,568446438),i,g,u[h+14],9,-1019803690),l,i,u[h+3],14,-187363961),v,l,u[h+8],20,1163531501),g=t(g,v=t(v,l=t(l,i,g,v,u[h+13],5,-1444681467),i,g,u[h+2],9,-51403784),l,i,u[h+7],14,1735328473),v,l,u[h+12],20,-1926607734),g=e(g,v=e(v,l=e(l,i,g,v,u[h+5],4,-378558),i,g,u[h+8],11,-2022574463),l,i,u[h+11],16,1839030562),v,l,u[h+14],23,-35309556),g=e(g,v=e(v,l=e(l,i,g,v,u[h+1],4,-1530992060),i,g,u[h+4],11,1272893353),l,i,u[h+7],16,-155497632),v,l,u[h+10],23,-1094730640),g=e(g,v=e(v,l=e(l,i,g,v,u[h+13],4,681279174),i,g,u[h+0],11,-358537222),l,i,u[h+3],16,-722521979),v,l,u[h+6],23,76029189),g=e(g,v=e(v,l=e(l,i,g,v,u[h+9],4,-640364487),i,g,u[h+12],11,-421815835),l,i,u[h+15],16,530742520),v,l,u[h+2],23,-995338651),g=o(g,v=o(v,l=o(l,i,g,v,u[h+0],6,-198630844),i,g,u[h+7],10,1126891415),l,i,u[h+14],15,-1416354905),v,l,u[h+5],21,-57434055),g=o(g,v=o(v,l=o(l,i,g,v,u[h+12],6,1700485571),i,g,u[h+3],10,-1894986606),l,i,u[h+10],15,-1051523),v,l,u[h+1],21,-2054922799),g=o(g,v=o(v,l=o(l,i,g,v,u[h+8],6,1873313359),i,g,u[h+15],10,-30611744),l,i,u[h+6],15,-1560198380),v,l,u[h+13],21,1309151649),g=o(g,v=o(v,l=o(l,i,g,v,u[h+4],6,-145523070),i,g,u[h+11],10,-1120210379),l,i,u[h+2],15,718787259),v,l,u[h+9],21,-343485551),l=f(l,A),i=f(i,d),g=f(g,C),v=f(v,m)}var y=Array(l,i,g,v),b="";for(h=0;h<32*y.length;h+=8)b+=String.fromCharCode(y[h>>5]>>>h%32&255);var S="0123456789abcdef",j="";for(h=0;h<b.length;h++)u=b.charCodeAt(h),j+=S.charAt(u>>>4&15)+S.charAt(15&u);return j}
	/**
	 * Script for beautiful dialog boxes
	 *
	 * Скрипт для красивых диалоговых окошек
	 */
	const popup = new (function () {
		this.popUp,
			this.downer,
			this.middle,
			this.msgText,
			this.buttons = [];
		this.checkboxes = [];

		function init() {
			addStyle();
			addBlocks();
		}

		const addStyle = () => {
			let style = document.createElement('style');
			style.innerText = `
		.PopUp_ {
			position: absolute;
			min-width: 300px;
			max-width: 500px;
			max-height: 400px;
			background-color: #190e08e6;
			z-index: 10001;
			top: 169px;
			left: 345px;
			border: 3px #ce9767 solid;
			border-radius: 10px;
			display: flex;
			flex-direction: column;
			justify-content: space-around;
			padding: 15px 12px;
		}

		.PopUp_back {
			position: absolute;
			background-color: #00000066;
			width: 100%;
			height: 100%;
			z-index: 10000;
			top: 0;
			left: 0;
		}

		.PopUp_blocks {
			width: 100%;
			height: 50%;
			display: flex;
			justify-content: space-evenly;
			align-items: center;
			flex-wrap: wrap;
			justify-content: center;
		}

		.PopUp_blocks:last-child {
			margin-top: 25px;
		}

		.PopUp_buttons {
			display: flex;
			margin: 10px 12px;
			flex-direction: column;
		}

		.PopUp_button {
			background-color: #52A81C;
			border-radius: 5px;
			box-shadow: inset 0px -4px 10px, inset 0px 3px 2px #99fe20, 0px 0px 4px, 0px -3px 1px #d7b275, 0px 0px 0px 3px #ce9767;
			cursor: pointer;
			padding: 5px 18px 8px;
		}

		.PopUp_input {
			text-align: center;
			font-size: 16px;
			height: 27px;
			border: 1px solid #cf9250;
			border-radius: 9px 9px 0px 0px;
			background: transparent;
			color: #fce1ac;
			padding: 1px 10px;
			box-sizing: border-box;
			box-shadow: 0px 0px 4px, 0px 0px 0px 3px #ce9767;
		}

		.PopUp_checkboxes {
			display: flex;
			flex-direction: column;
			margin: 15px 15px -5px 15px;
			align-items: flex-start;
		}

		.PopUp_ContCheckbox {
			margin: 2px 0px;
		}

		.PopUp_checkbox {
			position: absolute;
			z-index: -1;
			opacity: 0;
		}
		.PopUp_checkbox+label {
			display: inline-flex;
			align-items: center;
			user-select: none;

			font-size: 15px;
			font-family: sans-serif;
			font-weight: 600;
			font-stretch: condensed;
			letter-spacing: 1px;
			color: #fce1ac;
			text-shadow: 0px 0px 1px;
		}
		.PopUp_checkbox+label::before {
			content: '';
			display: inline-block;
			width: 20px;
			height: 20px;
			border: 1px solid #cf9250;
			border-radius: 7px;
			margin-right: 7px;
		}
		.PopUp_checkbox:checked+label::before {
			background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2388cb13' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
		}

		.PopUp_input::placeholder {
			color: #fce1ac75;
		}

		.PopUp_input:focus {
			outline: 0;
		}

		.PopUp_input + .PopUp_button {
			border-radius: 0px 0px 5px 5px;
			padding: 2px 18px 5px;
		}

		.PopUp_button:hover {
			filter: brightness(1.2);
		}

		.PopUp_text {
			font-size: 22px;
			font-family: sans-serif;
			font-weight: 600;
			font-stretch: condensed;
			letter-spacing: 1px;
			text-align: center;
		}

		.PopUp_buttonText {
			color: #E4FF4C;
			text-shadow: 0px 1px 2px black;
		}

		.PopUp_msgText {
			color: #FDE5B6;
			text-shadow: 0px 0px 2px;
		}

		.PopUp_hideBlock {
			display: none;
		}
		`;
			document.head.appendChild(style);
		}

		const addBlocks = () => {
			this.back = document.createElement('div');
			this.back.classList.add('PopUp_back');
			this.back.classList.add('PopUp_hideBlock');
			document.body.append(this.back);

			this.popUp = document.createElement('div');
			this.popUp.classList.add('PopUp_');
			this.back.append(this.popUp);

			let upper = document.createElement('div')
			upper.classList.add('PopUp_blocks');
			this.popUp.append(upper);

			this.middle = document.createElement('div')
			this.middle.classList.add('PopUp_blocks');
			this.middle.classList.add('PopUp_checkboxes');
			this.popUp.append(this.middle);

			this.downer = document.createElement('div')
			this.downer.classList.add('PopUp_blocks');
			this.popUp.append(this.downer);

			this.msgText = document.createElement('div');
			this.msgText.classList.add('PopUp_text', 'PopUp_msgText');
			upper.append(this.msgText);
		}

		this.showBack = function () {
			this.back.classList.remove('PopUp_hideBlock');
		}

		this.hideBack = function () {
			this.back.classList.add('PopUp_hideBlock');
		}

		this.show = function () {
			if (this.checkboxes.length) {
				this.middle.classList.remove('PopUp_hideBlock');
			}
			this.showBack();
			this.popUp.classList.remove('PopUp_hideBlock');
			this.popUp.style.left = (window.innerWidth - this.popUp.offsetWidth) / 2 + 'px';
			this.popUp.style.top = (window.innerHeight - this.popUp.offsetHeight) / 3 + 'px';
		}

		this.hide = function () {
			this.hideBack();
			this.popUp.classList.add('PopUp_hideBlock');
		}

		this.addButton = (option, buttonClick) => {
			const contButton = document.createElement('div');
			contButton.classList.add('PopUp_buttons');
			this.downer.append(contButton);

			let inputField = {
				value: option.result || option.default
			}
			if (option.isInput) {
				inputField = document.createElement('input');
				inputField.type = 'text';
				if (option.placeholder) {
					inputField.placeholder = option.placeholder;
				}
				if (option.default) {
					inputField.value = option.default;
				}
				inputField.classList.add('PopUp_input');
				contButton.append(inputField);
			}

			const button = document.createElement('div');
			button.classList.add('PopUp_button');
			contButton.append(button);

			button.addEventListener('click', () => {
				let result = '';
				if (option.isInput) {
					result = inputField.value;
				}
				buttonClick(result);
			});

			const buttonText = document.createElement('div');
			buttonText.classList.add('PopUp_text', 'PopUp_buttonText');
			buttonText.innerText = option.msg;
			button.append(buttonText);

			this.buttons.push(contButton);
		}

		this.clearButtons = () => {
			while (this.buttons.length) {
				this.buttons.pop().remove();
			}
		}

		this.addCheckBox = (checkBox) => {
			const contCheckbox = document.createElement('div');
			contCheckbox.classList.add('PopUp_ContCheckbox');
			this.middle.append(contCheckbox);

			const checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.id = 'PopUpCheckbox' + this.checkboxes.length;
			checkbox.dataset.name = checkBox.name;
			checkbox.checked = checkBox.checked;
			checkbox.label = checkBox.label;
			checkbox.classList.add('PopUp_checkbox');
			contCheckbox.appendChild(checkbox)

			const checkboxLabel = document.createElement('label');
			checkboxLabel.innerText = checkBox.label;
			checkboxLabel.setAttribute('for', checkbox.id);
			contCheckbox.appendChild(checkboxLabel);

			this.checkboxes.push(checkbox);
		}

		this.clearCheckBox = () => {
			this.middle.classList.add('PopUp_hideBlock');
			while (this.checkboxes.length) {
				this.checkboxes.pop().parentNode.remove();
			}
		}

		this.setMsgText = (text) => {
			this.msgText.innerHTML = text;
		}

		this.getCheckBoxes = () => {
			const checkBoxes = [];

			for (const checkBox of this.checkboxes) {
				checkBoxes.push({
					name: checkBox.dataset.name,
					label: checkBox.label,
					checked: checkBox.checked
				});
			}

			return checkBoxes;
		}

		this.confirm = async (msg, buttOpt, checkBoxes = []) => {
			this.clearButtons();
			this.clearCheckBox();
			return new Promise((complete, failed) => {
				this.setMsgText(msg);
				if (!buttOpt) {
					buttOpt = [{ msg: 'Ok', result: true, isInput: false }];
				}
				for (const checkBox of checkBoxes) {
					this.addCheckBox(checkBox);
				}
				for (let butt of buttOpt) {
					this.addButton(butt, (result) => {
						result = result || butt.result;
						complete(result);
						popup.hide();
					});
				}
				this.show();
			});
		}

		document.addEventListener('DOMContentLoaded', init);
	});
	/**
	 * Script control panel
	 *
	 * Панель управления скриптом
	 */
	const scriptMenu = new (function () {

		this.mainMenu,
			this.buttons = [],
			this.checkboxes = [];
		this.option = {
			showMenu: false,
			showDetails: {}
		};

		this.init = function (option = {}) {
			this.option = Object.assign(this.option, option);
			this.option.showDetails = this.loadShowDetails();
			addStyle();
			addBlocks();
		}

		const addStyle = () => {
			style = document.createElement('style');
			style.innerText = `
		.scriptMenu_status {
			position: absolute;
			z-index: 10001;
			/* max-height: 30px; */
			top: -1px;
			left: 30%;
			cursor: pointer;
			border-radius: 0px 0px 10px 10px;
			background: #190e08e6;
			border: 1px #ce9767 solid;
			font-size: 18px;
			font-family: sans-serif;
			font-weight: 600;
			font-stretch: condensed;
			letter-spacing: 1px;
			color: #fce1ac;
			text-shadow: 0px 0px 1px;
			transition: 0.5s;
			padding: 2px 10px 3px;
		}
		.scriptMenu_statusHide {
			top: -35px;
			height: 30px;
			overflow: hidden;
		}
		.scriptMenu_label {
			position: absolute;
			top: 30%;
			left: -4px;
			z-index: 9999;
			cursor: pointer;
			width: 30px;
			height: 30px;
			background: radial-gradient(circle, #47a41b 0%, #1a2f04 100%);
			border: 1px solid #1a2f04;
			border-radius: 5px;
			box-shadow:
			inset 0px 2px 4px #83ce26,
			inset 0px -4px 6px #1a2f04,
			0px 0px 2px black,
			0px 0px 0px 2px	#ce9767;
		}
		.scriptMenu_label:hover {
		filter: brightness(1.2);
		}
		.scriptMenu_arrowLabel {
			width: 100%;
			height: 100%;
			background-size: 75%;
			background-position: center;
			background-repeat: no-repeat;
			background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%2388cb13' d='M7.596 7.304a.802.802 0 0 1 0 1.392l-6.363 3.692C.713 12.69 0 12.345 0 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692Z'/%3e%3cpath fill='%2388cb13' d='M15.596 7.304a.802.802 0 0 1 0 1.392l-6.363 3.692C8.713 12.69 8 12.345 8 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692Z'/%3e%3c/svg%3e");
			box-shadow: 0px 1px 2px #000;
			border-radius: 5px;
			filter: drop-shadow(0px 1px 2px #000D);
		}
		.scriptMenu_main {
			position: absolute;
			max-width: 285px;
			z-index: 9999;
			top: 50%;
			transform: translateY(-50%);
			background: #190e08e6;
			border: 1px #ce9767 solid;
			border-radius: 0px 10px 10px 0px;
			border-left: none;
			padding: 5px 10px 5px 5px;
			box-sizing: border-box;
			font-size: 15px;
			font-family: sans-serif;
			font-weight: 600;
			font-stretch: condensed;
			letter-spacing: 1px;
			color: #fce1ac;
			text-shadow: 0px 0px 1px;
			transition: 1s;
			display: flex;
			flex-direction: column;
			flex-wrap: nowrap;
		}
		.scriptMenu_showMenu {
			display: none;
		}
		.scriptMenu_showMenu:checked~.scriptMenu_main {
			left: 0px;
		}
		.scriptMenu_showMenu:not(:checked)~.scriptMenu_main {
			left: -300px;
		}
		.scriptMenu_divInput {
			margin: 2px;
		}
		.scriptMenu_divInputText {
			margin: 2px;
			align-self: center;
			display: flex;
		}
		.scriptMenu_checkbox {
			position: absolute;
			z-index: -1;
			opacity: 0;
		}
		.scriptMenu_checkbox+label {
			display: inline-flex;
			align-items: center;
			user-select: none;
		}
		.scriptMenu_checkbox+label::before {
			content: '';
			display: inline-block;
			width: 20px;
			height: 20px;
			border: 1px solid #cf9250;
			border-radius: 7px;
			margin-right: 7px;
		}
		.scriptMenu_checkbox:checked+label::before {
			background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2388cb13' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
		}
		.scriptMenu_close {
			width: 40px;
			height: 40px;
			position: absolute;
			right: -18px;
			top: -18px;
			border: 3px solid #c18550;
			border-radius: 20px;
			background: radial-gradient(circle, rgba(190,30,35,1) 0%, rgba(0,0,0,1) 100%);
			background-position-y: 3px;
			box-shadow: -1px 1px 3px black;
			cursor: pointer;
			box-sizing: border-box;
		}
		.scriptMenu_close:hover {
			filter: brightness(1.2);
		}
		.scriptMenu_crossClose {
			width: 100%;
			height: 100%;
			background-size: 65%;
			background-position: center;
			background-repeat: no-repeat;
			background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%23f4cd73' d='M 0.826 12.559 C 0.431 12.963 3.346 15.374 3.74 14.97 C 4.215 15.173 8.167 10.457 7.804 10.302 C 7.893 10.376 11.454 14.64 11.525 14.372 C 12.134 15.042 15.118 12.086 14.638 11.689 C 14.416 11.21 10.263 7.477 10.402 7.832 C 10.358 7.815 11.731 7.101 14.872 3.114 C 14.698 2.145 13.024 1.074 12.093 1.019 C 11.438 0.861 8.014 5.259 8.035 5.531 C 7.86 5.082 3.61 1.186 3.522 1.59 C 2.973 1.027 0.916 4.611 1.17 4.873 C 0.728 4.914 5.088 7.961 5.61 7.995 C 5.225 7.532 0.622 12.315 0.826 12.559 Z'/%3e%3c/svg%3e")
		}
		.scriptMenu_button {
			user-select: none;
			border-radius: 5px;
			cursor: pointer;
			padding: 5px 14px 8px;
			margin: 4px;
			background: radial-gradient(circle, rgba(165,120,56,1) 80%, rgba(0,0,0,1) 110%);
			box-shadow: inset 0px -4px 6px #442901, inset 0px 1px 6px #442901, inset 0px 0px 6px, 0px 0px 4px, 0px 0px 0px 2px #ce9767;
		}
		.scriptMenu_button:hover {
			filter: brightness(1.2);
		}
		.scriptMenu_buttonText {
			color: #fce5b7;
			text-shadow: 0px 1px 2px black;
			text-align: center;
		}
		.scriptMenu_header {
			text-align: center;
			align-self: center;
			font-size: 15px;
			margin: 0px 15px;
		}
		.scriptMenu_header a {
			color: #fce5b7;
			text-decoration: none;
		}
		.scriptMenu_InputText {
			text-align: center;
			width: 130px;
			height: 24px;
			border: 1px solid #cf9250;
			border-radius: 9px;
			background: transparent;
			color: #fce1ac;
			padding: 0px 10px;
			box-sizing: border-box;
		}
		.scriptMenu_InputText:focus {
			filter: brightness(1.2);
			outline: 0;
		}
		.scriptMenu_InputText::placeholder {
			color: #fce1ac75;
		}
		.scriptMenu_Summary {
			cursor: pointer;
			margin-left: 7px;
		}
		.scriptMenu_Details {
			align-self: center;
		}
	`;
			document.head.appendChild(style);
		}

		const addBlocks = () => {
			const main = document.createElement('div');
			document.body.appendChild(main);

			this.status = document.createElement('div');
			this.status.classList.add('scriptMenu_status');
			this.setStatus('');
			main.appendChild(this.status);

			const label = document.createElement('label');
			label.classList.add('scriptMenu_label');
			label.setAttribute('for', 'checkbox_showMenu');
			main.appendChild(label);

			const arrowLabel = document.createElement('div');
			arrowLabel.classList.add('scriptMenu_arrowLabel');
			label.appendChild(arrowLabel);

			const checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.id = 'checkbox_showMenu';
			checkbox.checked = this.option.showMenu;
			checkbox.classList.add('scriptMenu_showMenu');
			main.appendChild(checkbox);

			this.mainMenu = document.createElement('div');
			this.mainMenu.classList.add('scriptMenu_main');
			main.appendChild(this.mainMenu);

			const closeButton = document.createElement('label');
			closeButton.classList.add('scriptMenu_close');
			closeButton.setAttribute('for', 'checkbox_showMenu');
			this.mainMenu.appendChild(closeButton);

			const crossClose = document.createElement('div');
			crossClose.classList.add('scriptMenu_crossClose');
			closeButton.appendChild(crossClose);
		}

		this.setStatus = (text, onclick) => {
			if (!text) {
				this.status.classList.add('scriptMenu_statusHide');
			} else {
				this.status.classList.remove('scriptMenu_statusHide');
				this.status.innerHTML = text;
			}

			if (typeof onclick == 'function') {
				this.status.addEventListener("click", onclick, {
					once: true
				});
			}
		}

		/**
		 * Adding a text element
		 *
		 * Добавление текстового элемента
		 * @param {String} text text // текст
		 * @param {Function} func Click function // функция по клику
		 * @param {HTMLDivElement} main parent // родитель
		 */
		this.addHeader = (text, func, main) => {
			main = main || this.mainMenu;
			const header = document.createElement('div');
			header.classList.add('scriptMenu_header');
			header.innerHTML = text;
			if (typeof func == 'function') {
				header.addEventListener('click', func);
			}
			main.appendChild(header);
		}

		/**
		 * Adding a button
		 *
		 * Добавление кнопки
		 * @param {String} text
		 * @param {Function} func
		 * @param {String} title
		 * @param {HTMLDivElement} main parent // родитель
		 */
		this.addButton = (text, func, title, main) => {
			main = main || this.mainMenu;
			const button = document.createElement('div');
			button.classList.add('scriptMenu_button');
			button.title = title;
			button.addEventListener('click', func);
			main.appendChild(button);

			const buttonText = document.createElement('div');
			buttonText.classList.add('scriptMenu_buttonText');
			buttonText.innerText = text;
			button.appendChild(buttonText);
			this.buttons.push(button);

			return button;
		}

		/**
		 * Adding checkbox
		 *
		 * Добавление чекбокса
		 * @param {String} label
		 * @param {String} title
		 * @param {HTMLDivElement} main parent // родитель
		 * @returns
		 */
		this.addCheckbox = (label, title, main) => {
			main = main || this.mainMenu;
			const divCheckbox = document.createElement('div');
			divCheckbox.classList.add('scriptMenu_divInput');
			divCheckbox.title = title;
			main.appendChild(divCheckbox);

			const checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.id = 'scriptMenuCheckbox' + this.checkboxes.length;
			checkbox.classList.add('scriptMenu_checkbox');
			divCheckbox.appendChild(checkbox)

			const checkboxLabel = document.createElement('label');
			checkboxLabel.innerText = label;
			checkboxLabel.setAttribute('for', checkbox.id);
			divCheckbox.appendChild(checkboxLabel);

			this.checkboxes.push(checkbox);
			return checkbox;
		}

		/**
		 * Adding input field
		 *
		 * Добавление поля ввода
		 * @param {String} title
		 * @param {String} placeholder
		 * @param {HTMLDivElement} main parent // родитель
		 * @returns
		 */
		this.addInputText = (title, placeholder, main) => {
			main = main || this.mainMenu;
			const divInputText = document.createElement('div');
			divInputText.classList.add('scriptMenu_divInputText');
			divInputText.title = title;
			main.appendChild(divInputText);

			const newInputText = document.createElement('input');
			newInputText.type = 'text';
			if (placeholder) {
				newInputText.placeholder = placeholder;
			}
			newInputText.classList.add('scriptMenu_InputText');
			divInputText.appendChild(newInputText)
			return newInputText;
		}

		/**
		 * Adds a dropdown block
		 *
		 * Добавляет раскрывающийся блок
		 * @param {String} summary
		 * @param {String} name
		 * @returns
		 */
		this.addDetails = (summaryText, name = null) => {
			const details = document.createElement('details');
			details.classList.add('scriptMenu_Details');
			this.mainMenu.appendChild(details);

			const summary = document.createElement('summary');
			summary.classList.add('scriptMenu_Summary');
			summary.innerText = summaryText;
			if (name) {
				const self = this;
				details.open = this.option.showDetails[name];
				details.dataset.name = name;
				summary.addEventListener('click', () => {
					self.option.showDetails[details.dataset.name] = !details.open;
					self.saveShowDetails(self.option.showDetails);
				});
			}
			details.appendChild(summary);

			return details;
		}

		/**
		 * Saving the expanded state of the details blocks
		 *
		 * Сохранение состояния развенутости блоков details
		 * @param {*} value
		 */
		this.saveShowDetails = (value) => {
			localStorage.setItem('scriptMenu_showDetails', JSON.stringify(value));
		}

		/**
		 * Loading the state of expanded blocks details
		 *
		 * Загрузка состояния развенутости блоков details
		 * @returns
		 */
		this.loadShowDetails = () => {
			let showDetails = localStorage.getItem('scriptMenu_showDetails');

			if (!showDetails) {
				return {};
			}

			try {
				showDetails = JSON.parse(showDetails);
			} catch (e) {
				return {};
			}

			return showDetails;
		}
	});
	/**
	 * Database
	 *
	 * База данных
	 */
	class Database {
		constructor(dbName, storeName) {
			this.dbName = dbName;
			this.storeName = storeName;
			this.db = null;
		}

		async open() {
			return new Promise((resolve, reject) => {
				const request = indexedDB.open(this.dbName);

				request.onerror = () => {
					reject(new Error(`Failed to open database ${this.dbName}`));
				};

				request.onsuccess = () => {
					this.db = request.result;
					resolve();
				};

				request.onupgradeneeded = (event) => {
					const db = event.target.result;
					if (!db.objectStoreNames.contains(this.storeName)) {
						db.createObjectStore(this.storeName);
					}
				};
			});
		}

		async set(key, value) {
			return new Promise((resolve, reject) => {
				const transaction = this.db.transaction([this.storeName], 'readwrite');
				const store = transaction.objectStore(this.storeName);
				const request = store.put(value, key);

				request.onerror = () => {
					reject(new Error(`Failed to save value with key ${key}`));
				};

				request.onsuccess = () => {
					resolve();
				};
			});
		}

		async get(key, def) {
			return new Promise((resolve, reject) => {
				const transaction = this.db.transaction([this.storeName], 'readonly');
				const store = transaction.objectStore(this.storeName);
				const request = store.get(key);

				request.onerror = () => {
					resolve(def);
				};

				request.onsuccess = () => {
					resolve(request.result);
				};
			});
		}

		async delete(key) {
			return new Promise((resolve, reject) => {
				const transaction = this.db.transaction([this.storeName], 'readwrite');
				const store = transaction.objectStore(this.storeName);
				const request = store.delete(key);

				request.onerror = () => {
					reject(new Error(`Failed to delete value with key ${key}`));
				};

				request.onsuccess = () => {
					resolve();
				};
			});
		}
	}
	/**
	 * Returns the stored value
	 *
	 * Возвращает сохраненное значение
	 */
	function getSaveVal(saveName, def) {
		const result = storage.get(saveName, def);
		return result;
	}
	/**
	 * Stores value
	 *
	 * Сохраняет значение
	 */
	function setSaveVal(saveName, value) {
		storage.set(saveName, value);
	}
	/**
	 * Database initialization
	 *
	 * Инициализация базы данных
	 */
	const db = new Database(GM_info.script.name, 'settings');
	/**
	 * Data store
	 *
	 * Хранилище данных
	 */
	const storage = {
		userId: 0,
		/**
		 * Default values
		 *
		 * Значения по умолчанию
		 */
		values: [
			...Object.entries(checkboxes).map(e => ({ [e[0]]: e[1].default })),
			...Object.entries(inputs).map(e => ({ [e[0]]: e[1].default })),
		].reduce((acc, obj) => ({ ...acc, ...obj }), {}),
		name: GM_info.script.name,
		get: function (key, def) {
			if (key in this.values) {
				return this.values[key];
			}
			return def;
		},
		set: function (key, value) {
			this.values[key] = value;
			db.set(this.userId, this.values).catch(
				e => null
			);
			localStorage[this.name + ':' + key] = value;
		},
		delete: function (key) {
			delete this.values[key];
			db.set(this.userId, this.values);
			delete localStorage[this.name + ':' + key];
		}
	}
	/**
	 * Returns all keys from localStorage that start with prefix (for migration)
	 *
	 * Возвращает все ключи из localStorage которые начинаются с prefix (для миграции)
	 */
	function getAllValuesStartingWith(prefix) {
		const values = [];
		for (let i = 0; i < localStorage.length; i++) {
			const key = localStorage.key(i);
			if (key.startsWith(prefix)) {
				const val = localStorage.getItem(key);
				const keyValue = key.split(':')[1];
				values.push({ key: keyValue, val });
			}
		}
		return values;
	}
	/**
	 * Opens or migrates to a database
	 *
	 * Открывает или мигрирует в базу данных 
	 */
	async function openOrMigrateDatabase(userId) {
		storage.userId = userId;
		try {
			await db.open();
		} catch(e) {
			return;
		}
		let settings = await db.get(userId, false);

		if (settings) {
			storage.values = settings;
			return;
		}

		const values = getAllValuesStartingWith(GM_info.script.name);
		for (const value of values) {
			let val = null;
			try {
				val = JSON.parse(value.val);
			} catch {
				break;
			}
			storage.values[value.key] = val;
		}
		await db.set(userId, storage.values);
	}
	/**
	 * Sending expeditions
	 *
	 * Отправка экспедиций
	 */
	function checkExpedition() {
		return new Promise((resolve, reject) => {
			const expedition = new Expedition(resolve, reject);
			expedition.start();
		});
	}

	class Expedition {
		checkExpedInfo = {
			calls: [{
				name: "expeditionGet",
				args: {},
				ident: "expeditionGet"
			}, {
				name: "heroGetAll",
				args: {},
				ident: "heroGetAll"
			}]
		}

		constructor(resolve, reject) {
			this.resolve = resolve;
			this.reject = reject;
		}

		async start() {
			const data = await Send(JSON.stringify(this.checkExpedInfo));

			const expedInfo = data.results[0].result.response;
			const dataHeroes = data.results[1].result.response;
			const dataExped = { useHeroes: [], exped: [] };
			const calls = [];

			/**
			 * Adding expeditions to collect
			 * Добавляем экспедиции для сбора
			 */
			for (var n in expedInfo) {
				const exped = expedInfo[n];
				const dateNow = (Date.now() / 1000);
				if (exped.status == 2 && exped.endTime != 0 && dateNow > exped.endTime) {
					calls.push({
						name: "expeditionFarm",
						args: { expeditionId: exped.id },
						ident: "expeditionFarm_" + exped.id
					});
				} else {
					dataExped.useHeroes = dataExped.useHeroes.concat(exped.heroes);
				}
				if (exped.status == 1) {
					dataExped.exped.push({ id: exped.id, power: exped.power });
				}
			}
			dataExped.exped = dataExped.exped.sort((a, b) => (b.power - a.power));

			/**
			 * Putting together a list of heroes
			 * Собираем список героев
			 */
			const heroesArr = [];
			for (let n in dataHeroes) {
				const hero = dataHeroes[n];
				if (hero.xp > 0 && !dataExped.useHeroes.includes(hero.id)) {
					heroesArr.push({ id: hero.id, power: hero.power })
				}
			}

			/**
			 * Adding expeditions to send
			 * Добавляем экспедиции для отправки
			 */
			heroesArr.sort((a, b) => (a.power - b.power));
			for (const exped of dataExped.exped) {
				let heroesIds = this.selectionHeroes(heroesArr, exped.power);
				if (heroesIds && heroesIds.length > 4) {
					for (let q in heroesArr) {
						if (heroesIds.includes(heroesArr[q].id)) {
							delete heroesArr[q];
						}
					}
					calls.push({
						name: "expeditionSendHeroes",
						args: {
							expeditionId: exped.id,
							heroes: heroesIds
						},
						ident: "expeditionSendHeroes_" + exped.id
					});
				}
			}

			await Send(JSON.stringify({ calls }));
			this.end();
		}

		/**
		 * Selection of heroes for expeditions
		 *
		 * Подбор героев для экспедиций
		 */
		selectionHeroes(heroes, power) {
			const resultHeroers = [];
			const heroesIds = [];
			for (let q = 0; q < 5; q++) {
				for (let i in heroes) {
					let hero = heroes[i];
					if (heroesIds.includes(hero.id)) {
						continue;
					}

					const summ = resultHeroers.reduce((acc, hero) => acc + hero.power, 0);
					const need = Math.round((power - summ) / (5 - resultHeroers.length));
					if (hero.power > need) {
						resultHeroers.push(hero);
						heroesIds.push(hero.id);
						break;
					}
				}
			}

			const summ = resultHeroers.reduce((acc, hero) => acc + hero.power, 0);
			if (summ < power) {
				return false;
			}
			return heroesIds;
		}

		/**
		 * Ends expedition script
		 *
		 * Завершает скрипт экспедиции
		 */
		end() {
			setProgress(I18N('EXPEDITIONS_SENT'), true);
			this.resolve()
		}
	}
	/**
	 * Sending a request
	 *
	 * Отправка запроса
	 */
	function send(json, callback, pr) {
		/**
		 * We get the headlines of the previous intercepted request
		 * Получаем заголовки предыдущего перехваченого запроса
		 */
		let headers = lastHeaders;
		/**
		 * We increase the header of the query Certifier by 1
		 * Увеличиваем заголовок идетификатора запроса на 1
		 */
		headers["X-Request-Id"]++;
		/**
		 * We calculate the title with the signature
		 * Расчитываем заголовок с сигнатурой
		 */
		headers["X-Auth-Signature"] = getSignature(headers, json);
		/**
		 * Create a new ajax request
		 * Создаем новый AJAX запрос
		 */
		let xhr = new XMLHttpRequest;
		/**
		 * Indicate the previously saved URL for API queries
		 * Указываем ранее сохраненный URL для API запросов
		 */
		xhr.open('POST', apiUrl, true);
		/**
		 * Add the function to the event change event
		 * Добавляем функцию к событию смены статуса запроса
		 */
		xhr.onreadystatechange = function() {
			/**
			 * If the result of the request is obtained, we call the flask function
			 * Если результат запроса получен вызываем колбек функцию
			 */
			if(xhr.readyState == 4) {
				let randTimeout = Math.random() * 200 + 200;
				setTimeout(callback, randTimeout, xhr.response, pr);
			}
		};
		/**
		 * Indicate the type of request
		 * Указываем тип запроса
		 */
		xhr.responseType = 'json';
		/**
		 * We set the request headers
		 * Задаем заголовки запроса
		 */
		for(let nameHeader in headers) {
			let head = headers[nameHeader];
			xhr.setRequestHeader(nameHeader, head);
		}
		/**
		 * Sending a request
		 * Отправляем запрос
		 */
		xhr.send(json);
	}

	/**
	 * Walkthrough of the dungeon
	 *
	 * Прохождение подземелья
	 */
	function testDungeon() {
		return new Promise((resolve, reject) => {
			const dung = new executeDungeon(resolve, reject);
			const titanit = getInput('countTitanit');
			dung.start(titanit);
		});
	}

	/**
	 * Walkthrough of the dungeon
	 *
	 * Прохождение подземелья
	 */
	function executeDungeon(resolve, reject) {
		dungeonActivity = 0;
		maxDungeonActivity = 150;

		titanGetAll = [];

		teams = {
			heroes: [],
			earth: [],
			fire: [],
			neutral: [],
			water: [],
		}

		titanStats = [];

		titansStates = {};

		callsExecuteDungeon = {
			calls: [{
				name: "dungeonGetInfo",
				args: {},
				ident: "dungeonGetInfo"
			}, {
				name: "teamGetAll",
				args: {},
				ident: "teamGetAll"
			}, {
				name: "teamGetFavor",
				args: {},
				ident: "teamGetFavor"
			}, {
				name: "clanGetInfo",
				args: {},
				ident: "clanGetInfo"
			}, {
				name: "titanGetAll",
				args: {},
				ident: "titanGetAll"
			}]
		}

		this.start = function(titanit) {
			maxDungeonActivity = titanit || getInput('countTitanit');
			send(JSON.stringify(callsExecuteDungeon), startDungeon);
		}

		/**
		 * Getting data on the dungeon
		 *
		 * Получаем данные по подземелью
		 */
		function startDungeon(e) {
			res = e.results;
			dungeonGetInfo = res[0].result.response;
			if (!dungeonGetInfo) {
				endDungeon('noDungeon', res);
				return;
			}
			teamGetAll = res[1].result.response;
			teamGetFavor = res[2].result.response;
			dungeonActivity = res[3].result.response.stat.todayDungeonActivity;
			titanGetAll = Object.values(res[4].result.response);

			teams.hero = {
				favor: teamGetFavor.dungeon_hero,
				heroes: teamGetAll.dungeon_hero.filter(id => id < 6000),
				teamNum: 0,
			}
			heroPet = teamGetAll.dungeon_hero.filter(id => id >= 6000).pop();
			if (heroPet) {
				teams.hero.pet = heroPet;
			}

			teams.neutral = {
				favor: {},
				heroes: getTitanTeam(titanGetAll, 'neutral'),
				teamNum: 0,
			};
			teams.water = {
				favor: {},
				heroes: getTitanTeam(titanGetAll, 'water'),
				teamNum: 0,
			};
			teams.fire = {
				favor: {},
				heroes: getTitanTeam(titanGetAll, 'fire'),
				teamNum: 0,
			};
			teams.earth = {
				favor: {},
				heroes: getTitanTeam(titanGetAll, 'earth'),
				teamNum: 0,
			};


			checkFloor(dungeonGetInfo);
		}

		function getTitanTeam(titans, type) {
			switch (type) {
				case 'neutral':
					return titans.sort((a, b) => b.power - a.power).slice(0, 5).map(e => e.id);
				case 'water':
					return titans.filter(e => e.id.toString().slice(2, 3) == '0').map(e => e.id);
				case 'fire':
					return titans.filter(e => e.id.toString().slice(2, 3) == '1').map(e => e.id);
				case 'earth':
					return titans.filter(e => e.id.toString().slice(2, 3) == '2').map(e => e.id);
			}
		}

		function fixTitanTeam(titans) {
			titans.heroes = titans.heroes.filter(e => !titansStates[e]?.isDead);
			return titans;
		}

		/**
		 * Checking the floor
		 *
		 * Проверяем этаж
		 */
		function checkFloor(dungeonInfo) {
			if (!('floor' in dungeonInfo) || dungeonInfo.floor?.state == 2) {
				saveProgress();
				return;
			}
			// console.log(dungeonInfo, dungeonActivity);
			setProgress(`${I18N('DUNGEON')}: ${I18N('TITANIT')} ` + dungeonActivity + '/' + maxDungeonActivity);
			if (dungeonActivity >= maxDungeonActivity) {
				endDungeon('endDungeon');
				return;
			}
			titansStates = dungeonInfo.states.titans;
			titanStats = titanObjToArray(titansStates);
			floorChoices = dungeonInfo.floor.userData;
			floorType = dungeonInfo.floorType;
			primeElement = dungeonInfo.elements.prime;
			if (floorType == "battle") {
				promises = [];
				for (let teamNum in floorChoices) {
					attackerType = floorChoices[teamNum].attackerType;
					promises.push(startBattle(teamNum, attackerType));
				}
				Promise.all(promises)
					.then(processingPromises);
			}
		}

		function processingPromises(results) {
			let selectBattle = results[0];
			if (results.length < 2) {
				// console.log(selectBattle);
				if (!selectBattle.result.win) {
					endDungeon('dungeonEndBattle\n', selectBattle);
					return;
				}
				endBattle(selectBattle);
				return;
			}

			selectBattle = false;
			let bestState = -1000;
			for (const result of results) {
				const recovery = getState(result);
				if (recovery > bestState) {
					bestState = recovery;
					selectBattle = result
				}
			}
			// console.log(selectBattle.teamNum, results);
			if (!selectBattle || bestState <= -1000) {
				endDungeon('dungeonEndBattle\n', results);
				return;
			}

			startBattle(selectBattle.teamNum, selectBattle.attackerType)
				.then(endBattle);
		}

		/**
		 * Let's start the fight
		 *
		 * Начинаем бой
		 */
		function startBattle(teamNum, attackerType) {
			return new Promise(function (resolve, reject) {
				args = fixTitanTeam(teams[attackerType]);
				args.teamNum = teamNum;
				startBattleCall = {
					calls: [{
						name: "dungeonStartBattle",
						args,
						ident: "body"
					}]
				}
				send(JSON.stringify(startBattleCall), resultBattle, {
					resolve,
					teamNum,
					attackerType
				});
			});
		}
		/**
		 * Returns the result of the battle in a promise
		 *
		 * Возращает резульат боя в промис
		 */
		function resultBattle(resultBattles, args) {
			battleData = resultBattles.results[0].result.response;
			battleType = "get_tower";
			if (battleData.type == "dungeon_titan") {
				battleType = "get_titan";
			}
			BattleCalc(battleData, battleType, function (result) {
				result.teamNum = args.teamNum;
				result.attackerType = args.attackerType;
				args.resolve(result);
			});
		}
		/**
		 * Finishing the fight
		 *
		 * Заканчиваем бой
		 */
		function endBattle(battleInfo) {
			if (battleInfo.result.win) {
				endBattleCall = {
					calls: [{
						name: "dungeonEndBattle",
						args: {
							result: battleInfo.result,
							progress: battleInfo.progress,
						},
						ident: "body"
					}]
				}
				send(JSON.stringify(endBattleCall), resultEndBattle);
			} else {
				endDungeon('dungeonEndBattle win: false\n', battleInfo);
			}
		}

		/**
		 * Getting and processing battle results
		 *
		 * Получаем и обрабатываем результаты боя
		 */
		function resultEndBattle(e) {
			battleResult = e.results[0].result.response;
			if ('error' in battleResult) {
				endDungeon('errorBattleResult', battleResult);
				return;
			}
			dungeonGetInfo = battleResult.dungeon ?? battleResult;
			dungeonActivity += battleResult.reward.dungeonActivity ?? 0;
			checkFloor(dungeonGetInfo);
		}

		/**
		 * Returns the coefficient of condition of the
		 * difference in titanium before and after the battle
		 * 
		 * Возвращает коэффициент состояния титанов после боя
		 */
		function getState(result) {
			if (!result.result.win) {
				return -1000;
			}

			let beforeSumFactor = 0;
			const beforeTitans = result.battleData.attackers;
			for (let titanId in beforeTitans) {
				const titan = beforeTitans[titanId];
				const state = titan.state;
				let factor = 1;
				if (state) {
					const hp = state.hp / titan.hp;
					const energy = state.energy / 1e3;
					factor = hp + energy / 20
				}
				beforeSumFactor += factor;
			}

			let afterSumFactor = 0;
			const afterTitans = result.progress[0].attackers.heroes;
			for (let titanId in afterTitans) {
				const titan = afterTitans[titanId];
				const hp = titan.hp / beforeTitans[titanId].hp;
				const energy = titan.energy / 1e3;
				const factor = hp + energy / 20;
				afterSumFactor += factor;
			}
			return afterSumFactor - beforeSumFactor;
		}

		/**
		 * Converts an object with IDs to an array with IDs
		 *
		 * Преобразует объект с идетификаторами в массив с идетификаторами
		 */
		function titanObjToArray(obj) {
			let titans = [];
			for (let id in obj) {
				obj[id].id = id;
				titans.push(obj[id]);
			}
			return titans;
		}

		function saveProgress() {
			let saveProgressCall = {
				calls: [{
					name: "dungeonSaveProgress",
					args: {},
					ident: "body"
				}]
			}
			send(JSON.stringify(saveProgressCall), resultEndBattle);
		}

		function endDungeon(reason, info) {
			console.log(reason, info);
			setProgress(`${I18N('DUNGEON')} ${I18N('COMPLETED')}`, true);
			resolve();
		}
	}

	/**
	 * Passing the tower
	 *
	 * Прохождение башни
	 */
	function testTower() {
		return new Promise((resolve, reject) => {
			tower = new executeTower(resolve, reject);
			tower.start();
		});
	}

	/**
	 * Passing the tower
	 *
	 * Прохождение башни
	 */
	function executeTower(resolve, reject) {
		lastTowerInfo = {};

		scullCoin = 0;

		heroGetAll = [];

		heroesStates = {};

		argsBattle = {
			heroes: [],
			favor: {},
		};

		callsExecuteTower = {
			calls: [{
				name: "towerGetInfo",
				args: {},
				ident: "towerGetInfo"
			}, {
				name: "teamGetAll",
				args: {},
				ident: "teamGetAll"
			}, {
				name: "teamGetFavor",
				args: {},
				ident: "teamGetFavor"
			}, {
				name: "inventoryGet",
				args: {},
				ident: "inventoryGet"
			}, {
				name: "heroGetAll",
				args: {},
				ident: "heroGetAll"
			}]
		}

		buffIds = [
			{id: 0, cost: 0, isBuy: false},   // plug // заглушка
			{id: 1, cost: 1, isBuy: true},    // 3% attack // 3% атака
			{id: 2, cost: 6, isBuy: true},    // 2% attack // 2% атака
			{id: 3, cost: 16, isBuy: true},   // 4% attack // 4% атака
			{id: 4, cost: 40, isBuy: true},   // 8% attack // 8% атака
			{id: 5, cost: 1, isBuy: true},    // 10% armor // 10% броня
			{id: 6, cost: 6, isBuy: true},    // 5% armor // 5% броня
			{id: 7, cost: 16, isBuy: true},   // 10% armor // 10% броня
			{id: 8, cost: 40, isBuy: true},   // 20% armor // 20% броня
			{ id: 9, cost: 1, isBuy: true },    // 10% protection from magic // 10% защита от магии
			{ id: 10, cost: 6, isBuy: true },   // 5% protection from magic // 5% защита от магии
			{ id: 11, cost: 16, isBuy: true },  // 10% protection from magic // 10% защита от магии
			{ id: 12, cost: 40, isBuy: true },  // 20% protection from magic // 20% защита от магии
			{ id: 13, cost: 1, isBuy: false },  // 40% health hero // 40% здоровья герою
			{ id: 14, cost: 6, isBuy: false },  // 40% health hero // 40% здоровья герою
			{ id: 15, cost: 16, isBuy: false }, // 80% health hero // 80% здоровья герою
			{ id: 16, cost: 40, isBuy: false }, // 40% health to all heroes // 40% здоровья всем героям
			{ id: 17, cost: 1, isBuy: false },  // 40% energy to the hero // 40% энергии герою
			{ id: 18, cost: 3, isBuy: false },  // 40% energy to the hero // 40% энергии герою
			{ id: 19, cost: 8, isBuy: false },  // 80% energy to the hero // 80% энергии герою
			{ id: 20, cost: 20, isBuy: false }, // 40% energy to all heroes // 40% энергии всем героям
			{ id: 21, cost: 40, isBuy: false }, // Hero Resurrection // Воскрешение героя
		]

		this.start = function () {
			send(JSON.stringify(callsExecuteTower), startTower);
		}

		/**
		 * Getting data on the Tower
		 *
		 * Получаем данные по башне
		 */
		function startTower(e) {
			res = e.results;
			towerGetInfo = res[0].result.response;
			if (!towerGetInfo) {
				endTower('noTower', res);
				return;
			}
			teamGetAll = res[1].result.response;
			teamGetFavor = res[2].result.response;
			inventoryGet = res[3].result.response;
			heroGetAll = Object.values(res[4].result.response);

			scullCoin = inventoryGet.coin[7] ?? 0;

			argsBattle.favor = teamGetFavor.tower;
			argsBattle.heroes = heroGetAll.sort((a, b) => b.power - a.power).slice(0, 5).map(e => e.id);
			pet = teamGetAll.tower.filter(id => id >= 6000).pop();
			if (pet) {
				argsBattle.pet = pet;
			}

			checkFloor(towerGetInfo);
		}

		function fixHeroesTeam(argsBattle) {
			let fixHeroes = argsBattle.heroes.filter(e => !heroesStates[e]?.isDead);
			if (fixHeroes.length < 5) {
				heroGetAll = heroGetAll.filter(e => !heroesStates[e.id]?.isDead);
				fixHeroes = heroGetAll.sort((a, b) => b.power - a.power).slice(0, 5).map(e => e.id);
				Object.keys(argsBattle.favor).forEach(e => {
					if (!fixHeroes.includes(+e)) {
						delete argsBattle.favor[e];
					}
				})
			}
			argsBattle.heroes = fixHeroes;
			return argsBattle;
		}

		/**
		 * Check the floor
		 *
		 * Проверяем этаж
		 */
		function checkFloor(towerInfo) {
			lastTowerInfo = towerInfo;
			maySkipFloor = +towerInfo.maySkipFloor;
			floorNumber = +towerInfo.floorNumber;
			heroesStates = towerInfo.states.heroes;
			floorInfo = towerInfo.floor;

			/**
			 * Is there at least one chest open on the floor
			 * Открыт ли на этаже хоть один сундук
			 */
			isOpenChest = false;
			if (towerInfo.floorType == "chest") {
				isOpenChest = towerInfo.floor.chests.reduce((n, e) => n + e.opened, 0);
			}

			setProgress(`${I18N('TOWER')}: ${I18N('FLOOR')} ${floorNumber}`);
			if (floorNumber > 49) {
				if (isOpenChest) {
					endTower('alreadyOpenChest 50 floor', floorNumber);
					return;
				}
			}
			/**
			 * If the chest is open and you can skip floors, then move on
			 * Если сундук открыт и можно скипать этажи, то переходим дальше
			 */
			if (towerInfo.mayFullSkip && +towerInfo.teamLevel == 130) {
				if (isOpenChest) {
					nextOpenChest(floorNumber);
				} else {
					nextChestOpen(floorNumber);
				}
				return;
			}

			// console.log(towerInfo, scullCoin);
			switch (towerInfo.floorType) {
				case "battle":
					if (floorNumber <= maySkipFloor) {
						skipFloor();
						return;
					}
					if (floorInfo.state == 2) {
						nextFloor();
						return;
					}
					startBattle().then(endBattle);
					return;
				case "buff":
					checkBuff(towerInfo);
					return;
				case "chest":
					openChest(floorNumber);
					return;
				default:
					console.log('!', towerInfo.floorType, towerInfo);
					break;
			}
		}

		/**
		 * Let's start the fight
		 *
		 * Начинаем бой
		 */
		function startBattle() {
			return new Promise(function (resolve, reject) {
				towerStartBattle = {
					calls: [{
						name: "towerStartBattle",
						args: fixHeroesTeam(argsBattle),
						ident: "body"
					}]
				}
				send(JSON.stringify(towerStartBattle), resultBattle, resolve);
			});
		}
		/**
		 * Returns the result of the battle in a promise
		 *
		 * Возращает резульат боя в промис
		 */
		function resultBattle(resultBattles, resolve) {
			battleData = resultBattles.results[0].result.response;
			battleType = "get_tower";
			BattleCalc(battleData, battleType, function (result) {
				resolve(result);
			});
		}
		/**
		 * Finishing the fight
		 *
		 * Заканчиваем бой
		 */
		function endBattle(battleInfo) {
			if (battleInfo.result.stars >= 3) {
				endBattleCall = {
					calls: [{
						name: "towerEndBattle",
						args: {
							result: battleInfo.result,
							progress: battleInfo.progress,
						},
						ident: "body"
					}]
				}
				send(JSON.stringify(endBattleCall), resultEndBattle);
			} else {
				endTower('towerEndBattle win: false\n', battleInfo);
			}
		}

		/**
		 * Getting and processing battle results
		 *
		 * Получаем и обрабатываем результаты боя
		 */
		function resultEndBattle(e) {
			battleResult = e.results[0].result.response;
			if ('error' in battleResult) {
				endTower('errorBattleResult', battleResult);
				return;
			}
			if ('reward' in battleResult) {
				scullCoin += battleResult.reward?.coin[7] ?? 0;
			}
			nextFloor();
		}

		function nextFloor() {
			nextFloorCall = {
				calls: [{
					name: "towerNextFloor",
					args: {},
					ident: "body"
				}]
			}
			send(JSON.stringify(nextFloorCall), checkDataFloor);
		}

		function openChest(floorNumber) {
			floorNumber = floorNumber || 0;
			openChestCall = {
				calls: [{
					name: "towerOpenChest",
					args: {
						num: 2
					},
					ident: "body"
				}]
			}
			send(JSON.stringify(openChestCall), floorNumber < 50 ? nextFloor : lastChest);
		}

		function lastChest() {
			endTower('openChest 50 floor', floorNumber);
		}

		function skipFloor() {
			skipFloorCall = {
				calls: [{
					name: "towerSkipFloor",
					args: {},
					ident: "body"
				}]
			}
			send(JSON.stringify(skipFloorCall), checkDataFloor);
		}

		function checkBuff(towerInfo) {
			buffArr = towerInfo.floor;
			promises = [];
			for (let buff of buffArr) {
				buffInfo = buffIds[buff.id];
				if (buffInfo.isBuy && buffInfo.cost <= scullCoin) {
					scullCoin -= buffInfo.cost;
					promises.push(buyBuff(buff.id));
				}
			}
			Promise.all(promises).then(nextFloor);
		}

		function buyBuff(buffId) {
			return new Promise(function (resolve, reject) {
				buyBuffCall = {
					calls: [{
						name: "towerBuyBuff",
						args: {
							buffId
						},
						ident: "body"
					}]
				}
				send(JSON.stringify(buyBuffCall), resolve);
			});
		}

		function checkDataFloor(result) {
			towerInfo = result.results[0].result.response;
			if ('reward' in towerInfo && towerInfo.reward?.coin) {
				scullCoin += towerInfo.reward?.coin[7] ?? 0;
			}
			if ('tower' in towerInfo) {
				towerInfo = towerInfo.tower;
			}
			if ('skullReward' in towerInfo) {
				scullCoin += towerInfo.skullReward?.coin[7] ?? 0;
			}
			checkFloor(towerInfo);
		}
		/**
		 * Getting tower rewards
		 *
		 * Получаем награды башни
		 */
		function farmTowerRewards(reason) {
			let { pointRewards, points } = lastTowerInfo;
			let pointsAll = Object.getOwnPropertyNames(pointRewards);
			let farmPoints = pointsAll.filter(e => +e <= +points && !pointRewards[e]);
			if (!farmPoints.length) {
				return;
			}
			let farmTowerRewardsCall = {
				calls: [{
					name: "tower_farmPointRewards",
					args: {
						points: farmPoints
					},
					ident: "tower_farmPointRewards"
				}]
			}

			if (scullCoin > 0 && reason == 'openChest 50 floor') {
				farmTowerRewardsCall.calls.push({
					name: "tower_farmSkullReward",
					args: {},
					ident: "tower_farmSkullReward"
				});
			}

			send(JSON.stringify(farmTowerRewardsCall), () => { });
		}

		function fullSkipTower() {
			/**
			 * Next chest
			 *
			 * Следующий сундук
			 */
			function nextChest(n) {
				return {
					name: "towerNextChest",
					args: {},
					ident: "group_" + n + "_body"
				}
			}
			/**
			 * Open chest
			 *
			 * Открыть сундук
			 */
			function openChest(n) {
				return {
					name: "towerOpenChest",
					args: {
						"num": 2
					},
					ident: "group_" + n + "_body"
				}
			}

			const fullSkipTowerCall = {
				calls: []
			}

			let n = 0;
			for (let i = 0; i < 15; i++) {
				fullSkipTowerCall.calls.push(nextChest(++n));
				fullSkipTowerCall.calls.push(openChest(++n));
			}

			send(JSON.stringify(fullSkipTowerCall), data => {
				data.results[0] = data.results[28];
				checkDataFloor(data);
			});
		}

		function nextChestOpen(floorNumber) {
			const calls = [{
				name: "towerOpenChest",
				args: {
					num: 2
				},
				ident: "towerOpenChest"
			}];

			Send(JSON.stringify({ calls })).then(e => {
				nextOpenChest(floorNumber);
			});
		}

		function nextOpenChest(floorNumber) {
			if (floorNumber > 49) {
				endTower('openChest 50 floor', floorNumber);
				return;
			}
			if (floorNumber == 1) {
				fullSkipTower();
				return;
			}

			let nextOpenChestCall = {
				calls: [{
					name: "towerNextChest",
					args: {},
					ident: "towerNextChest"
				}, {
					name: "towerOpenChest",
					args: {
						num: 2
					},
					ident: "towerOpenChest"
				}]
			}
			send(JSON.stringify(nextOpenChestCall), checkDataFloor);
		}

		function endTower(reason, info) {
			console.log(reason, info);
			if (reason != 'noTower') {
				farmTowerRewards(reason);
			}
			setProgress(`${I18N('TOWER')} ${I18N('COMPLETED')}!`, true);
			resolve();
		}
	}

	/**
	 * Passage of the arena of the titans
	 *
	 * Прохождение арены титанов
	 */
	function testTitanArena() {
		return new Promise((resolve, reject) => {
			titAren = new executeTitanArena(resolve, reject);
			titAren.start();
		});
	}

	/**
	 * Passage of the arena of the titans
	 *
	 * Прохождение арены титанов
	 */
	function executeTitanArena(resolve, reject) {
		let titan_arena = [];
		let finishListBattle = [];
		/**
		 * ID of the current batch
		 *
		 * Идетификатор текущей пачки
		 */
		let currentRival = 0;
		/**
		 * Number of attempts to finish off the pack
		 *
		 * Количество попыток добития пачки
		 */
		let attempts = 0;
		/**
		 * Was there an attempt to finish off the current shooting range
		 *
		 * Была ли попытка добития текущего тира
		 */
		let isCheckCurrentTier = false;
		/**
		 * Current shooting range
		 *
		 * Текущий тир
		 */
		let currTier = 0;
		/**
		 * Number of battles on the current dash
		 *
		 * Количество битв на текущем тире
		 */
		let countRivalsTier = 0;

		let callsStart = {
			calls: [{
				name: "titanArenaGetStatus",
				args: {},
				ident: "titanArenaGetStatus"
			}, {
				name: "teamGetAll",
				args: {},
				ident: "teamGetAll"
			}]
		}

		this.start = function () {
			send(JSON.stringify(callsStart), startTitanArena);
		}

		function startTitanArena(data) {
			let titanArena = data.results[0].result.response;
			if (titanArena.status == 'disabled') {
				endTitanArena('disabled', titanArena);
				return;
			}

			let teamGetAll = data.results[1].result.response;
			titan_arena = teamGetAll.titan_arena;

			checkTier(titanArena)
		}

		function checkTier(titanArena) {
			if (titanArena.status == "peace_time") {
				endTitanArena('Peace_time', titanArena);
				return;
			}
			currTier = titanArena.tier;
			if (currTier) {
				setProgress(`${I18N('TITAN_ARENA')}: ${I18N('LEVEL')} ${currTier}`);
			}

			if (titanArena.status == "completed_tier") {
				titanArenaCompleteTier();
				return;
			}
			/**
			 * Checking for the possibility of a raid
			 * Проверка на возможность рейда
			 */
			if (titanArena.canRaid) {
				titanArenaStartRaid();
				return;
			}
			/**
			 * Check was an attempt to achieve the current shooting range
			 * Проверка была ли попытка добития текущего тира
			 */
			if (!isCheckCurrentTier) {
				checkRivals(titanArena.rivals);
				return;
			}

			endTitanArena('Done or not canRaid', titanArena);
		}
		/**
		 * Submit dash information for verification
		 *
		 * Отправка информации о тире на проверку
		 */
		function checkResultInfo(data) {
			let titanArena = data.results[0].result.response;
			checkTier(titanArena);
		}
		/**
		 * Finish the current tier
		 *
		 * Завершить текущий тир
		 */
		function titanArenaCompleteTier() {
			isCheckCurrentTier = false;
			let calls = [{
				name: "titanArenaCompleteTier",
				args: {},
				ident: "body"
			}];
			send(JSON.stringify({calls}), checkResultInfo);
		}
		/**
		 * Gathering points to be completed
		 *
		 * Собираем точки которые нужно добить
		 */
		function checkRivals(rivals) {
			finishListBattle = [];
			for (let n in rivals) {
				if (rivals[n].attackScore < 250) {
					finishListBattle.push(n);
				}
			}
			console.log('checkRivals', finishListBattle);
			countRivalsTier = finishListBattle.length;
			roundRivals();
		}
		/**
		 * Selecting the next point to finish off
		 *
		 * Выбор следующей точки для добития
		 */
		function roundRivals() {
			let countRivals = finishListBattle.length;
			if (!countRivals) {
				/**
				 * Whole range checked
				 *
				 * Весь тир проверен
				 */
				isCheckCurrentTier = true;
				titanArenaGetStatus();
				return;
			}
			// setProgress('TitanArena: Уровень ' + currTier + ' Бои: ' + (countRivalsTier - countRivals + 1) + '/' + countRivalsTier);
			currentRival = finishListBattle.pop();
			attempts = +currentRival;
			// console.log('roundRivals', currentRival);
			titanArenaStartBattle(currentRival);
		}
		/**
		 * The start of a solo battle
		 *
		 * Начало одиночной битвы
		 */
		function titanArenaStartBattle(rivalId) {
			let calls = [{
				name: "titanArenaStartBattle",
				args: {
					rivalId: rivalId,
					titans: titan_arena
				},
				ident: "body"
			}];
			send(JSON.stringify({calls}), calcResult);
		}
		/**
		 * Calculation of the results of the battle
		 *
		 * Расчет результатов боя
		 */
		function calcResult(data) {
			let battlesInfo = data.results[0].result.response.battle;
			/**
			 * If attempts are equal to the current battle number we make
			 * Если попытки равны номеру текущего боя делаем прерасчет
			 */
			if (attempts == currentRival) {
				preCalcBattle(battlesInfo);
				return;
			}
			/**
			 * If there are still attempts, we calculate a new battle
			 * Если попытки еще есть делаем расчет нового боя
			 */
			if (attempts > 0) {
				attempts--;
				calcBattleResult(battlesInfo)
					.then(resultCalcBattle);
				return;
			}
			/**
			 * Otherwise, go to the next opponent
			 * Иначе переходим к следующему сопернику
			 */
			roundRivals();
		}
		/**
		 * Processing the results of the battle calculation
		 *
		 * Обработка результатов расчета битвы
		 */
		function resultCalcBattle(resultBattle) {
			// console.log('resultCalcBattle', currentRival, attempts, resultBattle.result.win);
			/**
			 * If the current calculation of victory is not a chance or the attempt ended with the finish the battle
			 * Если текущий расчет победа или шансов нет или попытки кончились завершаем бой
			 */
			if (resultBattle.result.win || !attempts) {
				titanArenaEndBattle({
					progress: resultBattle.progress,
					result: resultBattle.result,
					rivalId: resultBattle.battleData.typeId
				});
				return;
			}
			/**
			 * If not victory and there are attempts we start a new battle
			 * Если не победа и есть попытки начинаем новый бой
			 */
			titanArenaStartBattle(resultBattle.battleData.typeId);
		}
		/**
		 * Returns the promise of calculating the results of the battle
		 *
		 * Возращает промис расчета результатов битвы
		 */
		function getBattleInfo(battle, isRandSeed) {
			return new Promise(function (resolve) {
				if (isRandSeed) {
					battle.seed = Math.floor(Date.now() / 1000) + random(0, 1e3);
				}
				// console.log(battle.seed);
				BattleCalc(battle, "get_titanClanPvp", e => resolve(e));
			});
		}
		/**
		 * Recalculate battles
		 *
		 * Прерасчтет битвы
		 */
		function preCalcBattle(battle) {
			let actions = [getBattleInfo(battle, false)];
			const countTestBattle = getInput('countTestBattle');
			for (let i = 0; i < countTestBattle; i++) {
				actions.push(getBattleInfo(battle, true));
			}
			Promise.all(actions)
				.then(resultPreCalcBattle);
		}
		/**
		 * Processing the results of the battle recalculation
		 *
		 * Обработка результатов прерасчета битвы
		 */
		function resultPreCalcBattle(e) {
			let wins = e.map(n => n.result.win);
			let firstBattle = e.shift();
			let countWin = wins.reduce((w, s) => w + s);
			let numReval = countRivalsTier - finishListBattle.length;
			// setProgress('TitanArena: Уровень ' + currTier + ' Бои: ' + numReval + '/' + countRivalsTier + ' - ' + countWin + '/11');
			console.log('resultPreCalcBattle', countWin + '/11' )
			if (countWin > 0) {
				attempts = getInput('countAutoBattle');
			} else {
				attempts = 0;
			}
			resultCalcBattle(firstBattle);
		}

		/**
		 * Complete an arena battle
		 *
		 * Завершить битву на арене
		 */
		function titanArenaEndBattle(args) {
			let calls = [{
				name: "titanArenaEndBattle",
				args,
				ident: "body"
			}];
			send(JSON.stringify({calls}), resultTitanArenaEndBattle);
		}

		function resultTitanArenaEndBattle(e) {
			let attackScore = e.results[0].result.response.attackScore;
			let numReval = countRivalsTier - finishListBattle.length;
			setProgress(`${I18N('TITAN_ARENA')}: ${I18N('LEVEL')} ${currTier} </br>${I18N('BATTLES')}: ${numReval}/${countRivalsTier} - ${attackScore}`);
			/**
			 * TODO: Might need to improve the results.
			 * TODO: Возможно стоит сделать улучшение результатов
			 */
			// console.log('resultTitanArenaEndBattle', e)
			console.log('resultTitanArenaEndBattle', numReval + '/' + countRivalsTier, attempts)
			roundRivals();
		}
		/**
		 * Arena State
		 *
		 * Состояние арены
		 */
		function titanArenaGetStatus() {
			let calls = [{
				name: "titanArenaGetStatus",
				args: {},
				ident: "body"
			}];
			send(JSON.stringify({calls}), checkResultInfo);
		}
		/**
		 * Arena Raid Request
		 *
		 * Запрос рейда арены
		 */
		function titanArenaStartRaid() {
			let calls = [{
				name: "titanArenaStartRaid",
				args: {
					titans: titan_arena
				},
				ident: "body"
			}];
			send(JSON.stringify({calls}), calcResults);
		}

		function calcResults(data) {
			let battlesInfo = data.results[0].result.response;
			let {attackers, rivals} = battlesInfo;

			let promises = [];
			for (let n in rivals) {
				rival = rivals[n];
				promises.push(calcBattleResult({
					attackers: attackers,
					defenders: [rival.team],
					seed: rival.seed,
					typeId: n,
				}));
			}

			Promise.all(promises)
				.then(results => {
					const endResults = {};
					for (let info of results) {
						let id = info.battleData.typeId;
						endResults[id] = {
							progress: info.progress,
							result: info.result,
						}
					}
					titanArenaEndRaid(endResults);
				});
		}

		function calcBattleResult(battleData) {
			return new Promise(function (resolve, reject) {
				BattleCalc(battleData, "get_titanClanPvp", resolve);
			});
		}

		/**
		 * Sending Raid Results
		 *
		 * Отправка результатов рейда
		 */
		function titanArenaEndRaid(results) {
			titanArenaEndRaidCall = {
				calls: [{
					name: "titanArenaEndRaid",
					args: {
						results
					},
					ident: "body"
				}]
			}
			send(JSON.stringify(titanArenaEndRaidCall), checkRaidResults);
		}

		function checkRaidResults(data) {
			results = data.results[0].result.response.results;
			isSucsesRaid = true;
			for (let i in results) {
				isSucsesRaid &&= (results[i].attackScore >= 250);
			}

			if (isSucsesRaid) {
				titanArenaCompleteTier();
			} else {
				titanArenaGetStatus();
			}
		}

		function titanArenaFarmDailyReward() {
			titanArenaFarmDailyRewardCall = {
				calls: [{
					name: "titanArenaFarmDailyReward",
					args: {},
					ident: "body"
				}]
			}
			send(JSON.stringify(titanArenaFarmDailyRewardCall), () => {console.log('Done farm daily reward')});
		}

		function endTitanArena(reason, info) {
			if (!['Peace_time', 'disabled'].includes(reason)) {
				titanArenaFarmDailyReward();
			}
			console.log(reason, info);
			setProgress(`${I18N('TITAN_ARENA')} ${I18N('COMPLETED')}!`, true);
			resolve();
		}
	}
	let hideTimeoutProgress = 0;
	/**
	 * Hide progress
	 *
	 * Скрыть прогресс
	 */
	function hideProgress(timeout) {
		timeout = timeout || 0;
		clearTimeout(hideTimeoutProgress);
		hideTimeoutProgress = setTimeout(function () {
			scriptMenu.setStatus('');
		}, timeout);
	}
	/**
	 * Progress display
	 *
	 * Отображение прогресса
	 */
	function setProgress(text, hide, onclick) {
		scriptMenu.setStatus(text, onclick);
		hide = hide || false;
		if (hide) {
			hideProgress(3000);
		}
	}
	function hackGame() {
		selfGame = null;
		bindId = 1e9;
		/**
		 * List of correspondence of used classes to their names
		 *
		 * Список соответствия используемых классов их названиям
		 */
		ObjectsList = [
			{name:"BattlePresets", prop:"game.battle.controller.thread.BattlePresets"},
			{name:"DataStorage", prop:"game.data.storage.DataStorage"},
			{name:"BattleConfigStorage", prop:"game.data.storage.battle.BattleConfigStorage"},
			{name:"BattleInstantPlay", prop:"game.battle.controller.instant.BattleInstantPlay"},
			{name:"MultiBattleResult", prop:"game.battle.controller.MultiBattleResult"},

			{name:"PlayerMissionData", prop:"game.model.user.mission.PlayerMissionData"},
			{name:"PlayerMissionBattle", prop:"game.model.user.mission.PlayerMissionBattle"},
			{name:"GameModel", prop:"game.model.GameModel"},
			{name:"CommandManager", prop:"game.command.CommandManager"},
			{name:"MissionCommandList", prop:"game.command.rpc.mission.MissionCommandList"},
			{name:"RPCCommandBase", prop:"game.command.rpc.RPCCommandBase"},
			{name:"PlayerTowerData", prop:"game.model.user.tower.PlayerTowerData"},
			{name:"TowerCommandList", prop:"game.command.tower.TowerCommandList"},
			{name:"PlayerHeroTeamResolver", prop:"game.model.user.hero.PlayerHeroTeamResolver"},
			{name:"BattlePausePopup", prop:"game.view.popup.battle.BattlePausePopup"},
			{name:"BattlePopup", prop:"game.view.popup.battle.BattlePopup"},
			{name:"DisplayObjectContainer", prop:"starling.display.DisplayObjectContainer"},
			{name:"GuiClipContainer", prop:"engine.core.clipgui.GuiClipContainer"},
			{name:"BattlePausePopupClip", prop:"game.view.popup.battle.BattlePausePopupClip"},
			{name:"ClipLabel", prop:"game.view.gui.components.ClipLabel"},
			{name:"ClipLabelBase", prop:"game.view.gui.components.ClipLabelBase"},
			{name:"Translate", prop:"com.progrestar.common.lang.Translate"},
			{name:"ClipButtonLabeledCentered", prop:"game.view.gui.components.ClipButtonLabeledCentered"},
			{name:"BattlePausePopupMediator", prop:"game.mediator.gui.popup.battle.BattlePausePopupMediator"},
			{name:"SettingToggleButton", prop:"game.mechanics.settings.popup.view.SettingToggleButton"},
			{name:"PlayerDungeonData", prop:"game.mechanics.dungeon.model.PlayerDungeonData"},
			{name:"NextDayUpdatedManager", prop:"game.model.user.NextDayUpdatedManager"},
			{name:"BattleController", prop:"game.battle.controller.BattleController"},
			{name:"BattleSettingsModel", prop:"game.battle.controller.BattleSettingsModel"},
			{name:"BooleanProperty", prop:"engine.core.utils.property.BooleanProperty"},
			{name:"RuleStorage", prop:"game.data.storage.rule.RuleStorage"},
			{name:"BattleConfig", prop:"battle.BattleConfig"},
			{name:"SpecialShopModel", prop:"game.model.user.shop.SpecialShopModel"},
			{name:"BattleGuiMediator", prop:"game.battle.gui.BattleGuiMediator"},
			{name:"BooleanPropertyWriteable", prop:"engine.core.utils.property.BooleanPropertyWriteable"},
		];
		/**
		 * Contains the game classes needed to write and override game methods
		 *
		 * Содержит классы игры необходимые для написания и подмены методов игры
		 */
		Game = {
			/**
			 * Function 'e'
			 * Функция 'e'
			 */
			bindFunc: function (a, b) {
				if (null == b)
					return null;
				null == b.__id__ && (b.__id__ = bindId++);
				var c;
				null == a.hx__closures__ ? a.hx__closures__ = {} :
					c = a.hx__closures__[b.__id__];
				null == c && (c = b.bind(a), a.hx__closures__[b.__id__] = c);
				return c
			},
		};
		/**
		 * Connects to game objects via the object creation event
		 *
		 * Подключается к объектам игры через событие создания объекта
		 */
		function connectGame() {
			for (let obj of ObjectsList) {
				/**
				 * https: //stackoverflow.com/questions/42611719/how-to-intercept-and-modify-a-specific-property-for-any-object
				 */
				Object.defineProperty(Object.prototype, obj.prop, {
					set: function (value) {
						if (!selfGame) {
							selfGame = this;
						}
						if (!Game[obj.name]) {
							Game[obj.name] = value;
						}
						// console.log('set ' + obj.prop, this, value);
						this[obj.prop + '_'] = value;
					},
					get: function () {
						// console.log('get ' + obj.prop, this);
						return this[obj.prop + '_'];
					}
				});
			}
		}
		/**
		 * Game.BattlePresets
		 * @param {bool} a isReplay
		 * @param {bool} b autoToggleable
		 * @param {bool} c auto On Start
		 * @param {object} d config
		 * @param {bool} f showBothTeams
		 */
		/**
		 * Returns the results of the battle to the callback function
		 * Возвращает в функцию callback результаты боя
		 * @param {*} battleData battle data данные боя
		 * @param {*} battleConfig combat configuration type options:
		 *
		 * тип конфигурации боя варианты:
		 *
		 * "get_invasion", "get_titanPvpManual", "get_titanPvp",
		 * "get_titanClanPvp","get_clanPvp","get_titan","get_boss",
		 * "get_tower","get_pve","get_pvpManual","get_pvp","get_core"
		 *
		 * You can specify the xYc function in the game.assets.storage.BattleAssetStorage class
		 *
		 * Можно уточнить в классе game.assets.storage.BattleAssetStorage функция xYc
		 * @param {*} callback функция в которую вернуться результаты боя
		 */
		this.BattleCalc = function (battleData, battleConfig, callback) {
			// battleConfig = battleConfig || getBattleType(battleData.type)
			if (!Game.BattlePresets) throw Error('Use connectGame');
			battlePresets = new Game.BattlePresets(!1, !1, !0, Game.DataStorage[getFn(Game.DataStorage, 22)][getF(Game.BattleConfigStorage, battleConfig)](), !1);
			battleInstantPlay = new Game.BattleInstantPlay(battleData, battlePresets);
			battleInstantPlay[getProtoFn(Game.BattleInstantPlay, 8)].add((battleInstant) => {
				battleResult = battleInstant[getF(Game.BattleInstantPlay, 'get_result')]();
				battleData = battleInstant[getF(Game.BattleInstantPlay, 'get_rawBattleInfo')]();
				callback({
					battleData,
					progress: battleResult[getF(Game.MultiBattleResult, 'get_progress')](),
					result: battleResult[getF(Game.MultiBattleResult, 'get_result')]()
				})
			});
			battleInstantPlay.start();
		}

		/**
		 * Returns a function with the specified name from the class
		 *
		 * Возвращает из класса функцию с указанным именем
		 * @param {Object} classF Class // класс
		 * @param {String} nameF function name // имя функции
		 * @param {String} pos name and alias order // порядок имени и псевдонима
		 * @returns
		 */
		function getF(classF, nameF, pos) {
			pos = pos || false;
			let prop = Object.entries(classF.prototype.__properties__)
			if (!pos) {
				return prop.filter((e) => e[1] == nameF).pop()[0];
			} else {
				return prop.filter((e) => e[0] == nameF).pop()[1];
			}
		}

		/**
		 * Returns a function with the specified name from the class
		 *
		 * Возвращает из класса функцию с указанным именем
		 * @param {Object} classF Class // класс
		 * @param {String} nameF function name // имя функции
		 * @returns
		 */
		function getFnP(classF, nameF) {
			let prop = Object.entries(classF.__properties__)
			return prop.filter((e) => e[1] == nameF).pop()[0];
		}

		/**
		 * Returns the function name with the specified ordinal from the class
		 *
		 * Возвращает имя функции с указаным порядковым номером из класса
		 * @param {Object} classF Class // класс
		 * @param {Number} nF Order number of function // порядковый номер функции
		 * @returns
		 */
		function getFn(classF, nF) {
			let prop = Object.keys(classF);
			return prop[nF];
		}

		/**
		 * Returns the name of the function with the specified serial number from the prototype of the class
		 *
		 * Возвращает имя функции с указаным порядковым номером из прототипа класса
		 * @param {Object} classF Class // класс
		 * @param {Number} nF Order number of function // порядковый номер функции
		 * @returns
		 */
		function getProtoFn(classF, nF) {
			let prop = Object.keys(classF.prototype);
			return prop[nF];
		}
		/**
		 * Description of replaced functions
		 *
		 * Описание подменяемых функций
		 */
		replaceFunction = {
			company: function() {
				let PMD_12 = getProtoFn(Game.PlayerMissionData, 12);
				let oldSkipMisson = Game.PlayerMissionData.prototype[PMD_12];
				Game.PlayerMissionData.prototype[PMD_12] = function (a, b, c) {
					if (isChecked('passBattle')) {
						this[getProtoFn(Game.PlayerMissionData, 9)] = new Game.PlayerMissionBattle(a, b, c);

						var a = new Game.BattlePresets(!1, !1, !0, Game.DataStorage[getFn(Game.DataStorage, 22)][getProtoFn(Game.BattleConfigStorage, 17)](), !1);
						a = new Game.BattleInstantPlay(c, a);
						a[getProtoFn(Game.BattleInstantPlay, 8)].add(Game.bindFunc(this, this.P$h));
						a.start()
					} else {
						oldSkipMisson.call(this, a, b, c);
					}
				}

				Game.PlayerMissionData.prototype.P$h = function (a) {
					let GM_2 = getFn(Game.GameModel, 2);
					let GM_P2 = getProtoFn(Game.GameModel, 2);
					let CM_20 = getProtoFn(Game.CommandManager, 20);
					let MCL_2 = getProtoFn(Game.MissionCommandList, 2);
					let MBR_15 = getProtoFn(Game.MultiBattleResult, 15);
					let RPCCB_15 = getProtoFn(Game.RPCCommandBase, 15);
					let PMD_32 = getProtoFn(Game.PlayerMissionData, 32);
					Game.GameModel[GM_2]()[GM_P2][CM_20][MCL_2](a[MBR_15]())[RPCCB_15](Game.bindFunc(this, this[PMD_32]))
				}
			},
			tower: function() {
				let PTD_67 = getProtoFn(Game.PlayerTowerData, 67);
				let oldSkipTower = Game.PlayerTowerData.prototype[PTD_67];
				Game.PlayerTowerData.prototype[PTD_67] = function (a) {
					if (isChecked('passBattle')) {
						var p = new Game.BattlePresets(!1, !1, !0, Game.DataStorage[getFn(Game.DataStorage, 22)][getProtoFn(Game.BattleConfigStorage,17)](), !1);
						a = new Game.BattleInstantPlay(a, p);
						a[getProtoFn(Game.BattleInstantPlay,8)].add(Game.bindFunc(this, this.P$h));
						a.start()
					} else {
						oldSkipTower.call(this, a);;
					}
				}

				Game.PlayerTowerData.prototype.P$h = function (a) {
					let GM_2 = getFn(Game.GameModel, 2);
					let GM_P2 = getProtoFn(Game.GameModel, 2);
					let CM_29 = getProtoFn(Game.CommandManager, 29);
					let TCL_5 = getProtoFn(Game.TowerCommandList, 5);
					let MBR_15 = getProtoFn(Game.MultiBattleResult, 15);
					let RPCCB_15 = getProtoFn(Game.RPCCommandBase, 15);
					let PTD_78 = getProtoFn(Game.PlayerTowerData, 78);
					Game.GameModel[GM_2]()[GM_P2][CM_29][TCL_5](a[MBR_15]())[RPCCB_15](Game.bindFunc(this, this[PTD_78]))
				}
			},
			// skipSelectHero: function() {
			// 	if (!HOST) throw Error('Use connectGame');
			// 	Game.PlayerHeroTeamResolver.prototype[getProtoFn(Game.PlayerHeroTeamResolver, 3)] = () => false;
			// },
			passBattle: function() {
				let BPP_4 = getProtoFn(Game.BattlePausePopup, 4);
				let oldPassBattle = Game.BattlePausePopup.prototype[BPP_4];
				Game.BattlePausePopup.prototype[BPP_4] = function (a) {
					if (isChecked('passBattle')) {
						Game.BattlePopup.prototype[getProtoFn(Game.BattlePausePopup, 4)].call(this, a);
						this[getProtoFn(Game.BattlePausePopup, 3)]();
						this[getProtoFn(Game.DisplayObjectContainer, 3)](this.clip[getProtoFn(Game.GuiClipContainer, 2)]());
						this.clip[getProtoFn(Game.BattlePausePopupClip, 1)][getProtoFn(Game.ClipLabelBase, 9)](Game.Translate.translate("UI_POPUP_BATTLE_PAUSE"));

						this.clip[getProtoFn(Game.BattlePausePopupClip, 2)][getProtoFn(Game.ClipButtonLabeledCentered, 2)](Game.Translate.translate("UI_POPUP_BATTLE_RETREAT"), (q = this[getProtoFn(Game.BattlePausePopup, 1)], Game.bindFunc(q, q[getProtoFn(Game.BattlePausePopupMediator, 17)])));
						this.clip[getProtoFn(Game.BattlePausePopupClip, 5)][getProtoFn(Game.ClipButtonLabeledCentered, 2)](
							this[getProtoFn(Game.BattlePausePopup, 1)][getProtoFn(Game.BattlePausePopupMediator, 14)](),
							this[getProtoFn(Game.BattlePausePopup, 1)][getProtoFn(Game.BattlePausePopupMediator, 13)]() ?
							(q = this[getProtoFn(Game.BattlePausePopup, 1)], Game.bindFunc(q, q[getProtoFn(Game.BattlePausePopupMediator, 18)])) :
							(q = this[getProtoFn(Game.BattlePausePopup, 1)], Game.bindFunc(q, q[getProtoFn(Game.BattlePausePopupMediator, 18)]))
						);

						this.clip[getProtoFn(Game.BattlePausePopupClip, 5)][getProtoFn(Game.ClipButtonLabeledCentered, 0)][getProtoFn(Game.ClipLabelBase, 24)]();
						this.clip[getProtoFn(Game.BattlePausePopupClip, 3)][getProtoFn(Game.SettingToggleButton, 3)](this[getProtoFn(Game.BattlePausePopup, 1)][getProtoFn(Game.BattlePausePopupMediator, 9)]());
						this.clip[getProtoFn(Game.BattlePausePopupClip, 4)][getProtoFn(Game.SettingToggleButton, 3)](this[getProtoFn(Game.BattlePausePopup, 1)][getProtoFn(Game.BattlePausePopupMediator, 10)]());
						this.clip[getProtoFn(Game.BattlePausePopupClip, 6)][getProtoFn(Game.SettingToggleButton, 3)](this[getProtoFn(Game.BattlePausePopup, 1)][getProtoFn(Game.BattlePausePopupMediator, 11)]());
						/*
						Какая-то ненужная фигня
						if (!HC.lSb()) {
							this.clip.r6b.g().B(!1);
							a = this.clip.r6b.g().X() + 7;
							var b = this.clip.ba.g();
							b.$(b.X() - a);
							b = this.clip.IS.g();
							b.sa(b.Fa() - a)
						}
						*/
					} else {
						oldPassBattle.call(this, a);
					}
				}

				let retreatButtonLabel = getF(Game.BattlePausePopupMediator, "get_retreatButtonLabel");
				let oldFunc = Game.BattlePausePopupMediator.prototype[retreatButtonLabel];
				Game.BattlePausePopupMediator.prototype[retreatButtonLabel] = function () {
					if (isChecked('passBattle')) {
						return I18N('BTN_PASS');
					} else {
						return oldFunc.call(this);
					}
				}
			},
			endlessCards: function() {
				let PDD_15 = getProtoFn(Game.PlayerDungeonData, 15);
				let oldEndlessCards = Game.PlayerDungeonData.prototype[PDD_15];
				Game.PlayerDungeonData.prototype[PDD_15] = function () {
					if (isChecked('endlessCards')) {
						return true;
					} else {
						return oldEndlessCards.call(this);
					}
				}
			},
			speedBattle: function () {
				const get_timeScale = getF(Game.BattleController, "get_timeScale");
				const oldSpeedBattle = Game.BattleController.prototype[get_timeScale];
				Game.BattleController.prototype[get_timeScale] = function () {
					const speedBattle = Number.parseFloat(getInput('speedBattle'));
					if (speedBattle) {
						const BC_11 = getProtoFn(Game.BattleController, 11);
						const BSM_11 = getProtoFn(Game.BattleSettingsModel, 11);
						const BP_get_value = getF(Game.BooleanProperty, "get_value");
						if (this[BC_11][BSM_11][BP_get_value]()) {
							return 0;
						}
						const BSM_2 = getProtoFn(Game.BattleSettingsModel, 2);
						const BC_44 = getProtoFn(Game.BattleController, 44);
						const BSM_1 = getProtoFn(Game.BattleSettingsModel, 1);
						const BC_13 = getProtoFn(Game.BattleController, 13);
						const BC_3 = getFn(Game.BattleController, 3);
						if (this[BC_11][BSM_2][BP_get_value]()) {
							var a = speedBattle * this[BC_44]();
						} else {
							a = this[BC_11][BSM_1][BP_get_value]();
							//const multiple = a == 1 ? speedBattle : this[BC_13][a];
							a = this[BC_13][a] * Game.BattleController[BC_3][BP_get_value]() * this[BC_44]();
						}
						const BSM_22 = getProtoFn(Game.BattleSettingsModel, 22);
						a > this[BC_11][BSM_22][BP_get_value]() && (a = this[BC_11][BSM_22][BP_get_value]());
						const DS_21 = getFn(Game.DataStorage, 21);
						const get_battleSpeedMultiplier = getF(Game.RuleStorage, "get_battleSpeedMultiplier", true);
						// const RS_167 = getProtoFn(Game.RuleStorage, 167); // get_battleSpeedMultiplier
						var b = Game.DataStorage[DS_21][get_battleSpeedMultiplier]();
						const R_1 = getFn(selfGame.Reflect, 1);
						const BC_1 = getFn(Game.BattleController, 1);
						const get_config = getF(Game.BattlePresets, "get_config");
						// const BC_0 = getProtoFn(Game.BattleConfig, 0); // .ident
						null != b && (a = selfGame.Reflect[R_1](b, this[BC_1][get_config]().ident) ? a * selfGame.Reflect[R_1](b, this[BC_1][get_config]().ident) : a * selfGame.Reflect[R_1](b, "default"));
						return a
					} else {
						return oldSpeedBattle.call(this);
					}
				}
			},
			/**
			 * Remove the rare shop
			 *
			 * Удаление торговца редкими товарами
			 */
			removeWelcomeShop: function () {
				let SSM_3 = getProtoFn(Game.SpecialShopModel, 3);
				const oldWelcomeShop = Game.SpecialShopModel.prototype[SSM_3];
				Game.SpecialShopModel.prototype[SSM_3] = function () {
					if (isChecked('noOfferDonat')) {
						return null;
					} else {
						return oldWelcomeShop.call(this);
					}
				}
			},
			/**
			 * Acceleration button without Valkyries favor
			 *
			 * Кнопка ускорения без Покровительства Валькирий
			 */
			battleFastKey: function () {
				const BGM_39 = getProtoFn(Game.BattleGuiMediator, 39);
				const oldBattleFastKey = Game.BattleGuiMediator.prototype[BGM_39];
				Game.BattleGuiMediator.prototype[BGM_39] = function () {
					if (true) {
						const BGM_8 = getProtoFn(Game.BattleGuiMediator, 8);
						const BGM_9 = getProtoFn(Game.BattleGuiMediator, 9);
						const BPW_0 = getProtoFn(Game.BooleanPropertyWriteable, 0);
						this[BGM_8][BPW_0](true);
						this[BGM_9][BPW_0](true);
					} else {
						return oldBattleFastKey.call(this);
					}
				}
			}
		}
		/**
		 * Starts replacing recorded functions
		 *
		 * Запускает замену записанных функций
		 */
		this.activateHacks = function () {
			if (!selfGame) throw Error('Use connectGame');
			for (let func in replaceFunction) {
				replaceFunction[func]();
			}
		}
		/**
		 * Returns the game object
		 *
		 * Возвращает объект игры
		 */
		this.getSelfGame = function () {
			return selfGame;
		}
		/**
		 * Updates game data
		 *
		 * Обновляет данные игры
		 */
		this.refreshGame = function () {
			(new Game.NextDayUpdatedManager)[getProtoFn(Game.NextDayUpdatedManager, 5)]();
		}

		/**
		 * Change the play screen on windowName
		 *
		 * Сменить экран игры на windowName
		 *
		 * Possible options:
		 *
		 * Возможные варианты:
		 *
		 * MISSION, ARENA, GRAND, CHEST, SKILLS, SOCIAL_GIFT, CLAN, ENCHANT, TOWER, RATING, CHALLENGE, BOSS, CHAT, CLAN_DUNGEON, CLAN_CHEST, TITAN_GIFT, CLAN_RAID, ASGARD, HERO_ASCENSION, ROLE_ASCENSION, ASCENSION_CHEST, TITAN_MISSION, TITAN_ARENA, TITAN_ARTIFACT, TITAN_ARTIFACT_CHEST, TITAN_VALLEY, TITAN_SPIRITS, TITAN_ARTIFACT_MERCHANT, TITAN_ARENA_HALL_OF_FAME, CLAN_PVP, CLAN_PVP_MERCHANT, CLAN_GLOBAL_PVP, CLAN_GLOBAL_PVP_TITAN, ARTIFACT, ZEPPELIN, ARTIFACT_CHEST, ARTIFACT_MERCHANT, EXPEDITIONS, SUBSCRIPTION, NY2018_GIFTS, NY2018_TREE, NY2018_WELCOME, ADVENTURE, ADVENTURESOLO, SANCTUARY, PET_MERCHANT, PET_LIST, PET_SUMMON, BOSS_RATING_EVENT, BRAWL
		 */
		this.goNavigtor = function (windowName) {
			let mechanicStorage = selfGame["game.data.storage.mechanic.MechanicStorage"];
			let window = mechanicStorage[windowName];
			let event = selfGame["game.mediator.gui.popup.PopupStashEventParams"]('');
			let Game = selfGame['Game'];
			let navigator = getF(Game, "get_navigator")
			let navigate = getProtoFn(selfGame["game.screen.navigator.GameNavigator"], 15)
			let instance = getFnP(Game, 'get_instance');
			Game[instance]()[navigator]()[navigate](window, event);
		}
		/**
		 * Move to the sanctuary cheats.goSanctuary()
		 *
		 * Переместиться в святилище cheats.goSanctuary()
		 */
		this.goSanctuary = () => {
			this.goNavigtor("SANCTUARY");
		}
		/**
		 * Go to Guild War
		 *
		 * Перейти к Войне Гильдий
		 */
		this.goClanWar = function() {
			let instance = getFnP(selfGame["game.model.GameModel"], 'get_instance')
			let player = selfGame["game.model.GameModel"][instance]().A;
			let clanWarSelect = selfGame["game.mechanics.cross_clan_war.popup.selectMode.CrossClanWarSelectModeMediator"];
			new clanWarSelect(player).open();
		}

		connectGame();
	}
	/**
	 * Auto collection of gifts
	 *
	 * Автосбор подарков
	 */
	function getAutoGifts() {
		let valName = 'giftSendIds_' + userInfo.id;

		if (!localStorage['clearGift' + userInfo.id]) {
			localStorage[valName] = '';
			localStorage['clearGift' + userInfo.id] = '+';
		}

		if (!localStorage[valName]) {
			localStorage[valName] = '';
		}

		/**
		 * Submit a request to receive gift codes
		 *
		 * Отправка запроса для получения кодов подарков
		 */
		fetch('https://zingery.ru/heroes/getGifts.php', {
				method: 'POST',
				body: JSON.stringify({scriptInfo, userInfo})
		}).then(
			response => response.json()
		).then(
			data => {
				let freebieCheckCalls = {
					calls: []
				}
				data.forEach((giftId, n) => {
					if (localStorage[valName].includes(giftId)) return;
					//localStorage[valName] += ';' + giftId;
					freebieCheckCalls.calls.push({
						name: "freebieCheck",
						args: {
							giftId
						},
						ident: giftId
					});
				});

				if (!freebieCheckCalls.calls.length) {
					return;
				}

				send(JSON.stringify(freebieCheckCalls), e => {
					let countGetGifts = 0;
					const gifts = [];
					for (check of e.results) {
						gifts.push(check.ident);
						if (check.result.response != null) {
							countGetGifts++;
						}
					}
					const saveGifts = localStorage[valName].split(';');
					localStorage[valName] = [...saveGifts, ...gifts].slice(-50).join(';');
					console.log(`${I18N('GIFTS')}: ${countGetGifts}`);
				});
			}
		)
	}
	/**
	 * To fill the kills in the Forge of Souls
	 *
	 * Набить килов в горниле душ
	 */
	async function bossRatingEvent() {
		const topGet = await Send(JSON.stringify({ calls: [{ name: "topGet", args: { type: "bossRatingTop", extraId: 0 }, ident: "body" }] }));
		if (!topGet) {
			setProgress(`${I18N('EVENT')} ${I18N('NOT_AVAILABLE')}`, true);
			return;
		}
		const replayId = topGet.results[0].result.response[0].userData.replayId;
		const result = await Send(JSON.stringify({
			calls: [
				{ name: "battleGetReplay", args: { id: replayId }, ident: "battleGetReplay" },
				{ name: "heroGetAll", args: {}, ident: "heroGetAll" },
				{ name: "pet_getAll", args: {}, ident: "pet_getAll" },
				{ name: "offerGetAll", args: {}, ident: "offerGetAll" }
			]
		}));
		const bossEventInfo = result.results[3].result.response.find(e => e.offerType == "bossEvent");
		if (!bossEventInfo) {
			setProgress(`${I18N('EVENT')} ${I18N('NOT_AVAILABLE')}`, true);
			return;
		}
		const usedHeroes = bossEventInfo.progress.usedHeroes;
		const party = Object.values(result.results[0].result.response.replay.attackers);
		const availableHeroes = Object.values(result.results[1].result.response).map(e => e.id);
		const availablePets = Object.values(result.results[2].result.response).map(e => e.id);
		const calls = [];
		/**
		 * First pack
		 *
		 * Первая пачка
		 */
		const args = {
			heroes: [],
			favor: {}
		}
		for (let hero of party) {
			if (hero.id >= 6000 && availablePets.includes(hero.id)) {
				args.pet = hero.id;
				continue;
			}
			if (!availableHeroes.includes(hero.id) || usedHeroes.includes(hero.id)) {
				continue;
			}
			args.heroes.push(hero.id);
			if (hero.favorPetId) {
				args.favor[hero.id] = hero.favorPetId;
			}
		}
		if (args.heroes.length) {
			calls.push({
				name: "bossRatingEvent_startBattle",
				args,
				ident: "body_0"
			});
		}
		/**
		 * Other packs
		 *
		 * Другие пачки
		 */
		let heroes = [];
		let count = 1;
		while (heroId = availableHeroes.pop()) {
			if (args.heroes.includes(heroId) || usedHeroes.includes(heroId)) {
				continue;
			}
			heroes.push(heroId);
			if (heroes.length == 5) {
				calls.push({
					name: "bossRatingEvent_startBattle",
					args: {
						heroes: [...heroes],
						pet: availablePets[Math.floor(Math.random() * availablePets.length)]
					},
					ident: "body_" + count
				});
				heroes = [];
				count++;
			}
		}

		if (!calls.length) {
			setProgress(`${I18N('NO_HEROES')}`, true);
			return;
		}

		const resultBattles = await Send(JSON.stringify({ calls }));
		console.log(resultBattles);
		rewardBossRatingEvent();
	}
	/**
	 * Collecting Rewards from the Forge of Souls
	 *
	 * Сбор награды из Горнила Душ
	 */
	function rewardBossRatingEvent() {
		let rewardBossRatingCall = '{"calls":[{"name":"offerGetAll","args":{},"ident":"offerGetAll"}]}';
		send(rewardBossRatingCall, function (data) {
			let bossEventInfo = data.results[0].result.response.find(e => e.offerType == "bossEvent");
			if (!bossEventInfo) {
				setProgress(`${I18N('EVENT')} ${I18N('NOT_AVAILABLE')}`, true);
				return;
			}

			let farmedChests = bossEventInfo.progress.farmedChests;
			let score = bossEventInfo.progress.score;
			setProgress(`${I18N('DAMAGE_AMOUNT')}: ${score}`);
			let revard = bossEventInfo.reward;

			let getRewardCall = {
				calls: []
			}

			let count = 0;
			for (let i = 1; i < 10; i++) {
				if (farmedChests.includes(i)) {
					continue;
				}
				if (score < revard[i].score) {
					break;
				}
				getRewardCall.calls.push({
					name: "bossRatingEvent_getReward",
					args: {
						rewardId: i
					},
					ident: "body_" + i
				});
				count++;
			}
			if (!count) {
				setProgress(`${I18N('NOTHING_TO_COLLECT')}`, true);
				return;
			}

			send(JSON.stringify(getRewardCall), e => {
				console.log(e);
				setProgress(`${I18N('COLLECTED')} ${e?.results?.length} ${I18N('REWARD')}`, true);
			});
		});
	}
	/**
	 * Collect Easter eggs and event rewards
	 *
	 * Собрать пасхалки и награды событий
	 */
	function offerFarmAllReward() {
		const offerGetAllCall = '{"calls":[{"name":"offerGetAll","args":{},"ident":"offerGetAll"}]}';
		return Send(offerGetAllCall).then((data) => {
			const offerGetAll = data.results[0].result.response.filter(e => e.type == "reward" && !e?.freeRewardObtained && e.reward);
			if (!offerGetAll.length) {
				setProgress(`${I18N('NOTHING_TO_COLLECT')}`, true);
				return;
			}

			const calls = [];
			for (let reward of offerGetAll) {
				calls.push({
					name: "offerFarmReward",
					args: {
						offerId: reward.id
					},
					ident: "offerFarmReward_" + reward.id
				});
			}

			return Send(JSON.stringify({ calls })).then(e => {
				console.log(e);
				setProgress(`${I18N('COLLECTED')} ${e?.results?.length} ${I18N('REWARD')}`, true);
			});
		});
	}
	/**
	 * Assemble Outland
	 *
	 * Собрать запределье
	 */
	function getOutland() {
		return new Promise(function (resolve, reject) {
			send('{"calls":[{"name":"bossGetAll","args":{},"ident":"bossGetAll"}]}', e => {
				let bosses = e.results[0].result.response;

				let bossRaidOpenChestCall = {
					calls: []
				};

				for (let boss of bosses) {
					if (boss.mayRaid) {
						bossRaidOpenChestCall.calls.push({
							name: "bossRaid",
							args: {
								bossId: boss.id
							},
							ident: "bossRaid_" + boss.id
						});
						bossRaidOpenChestCall.calls.push({
							name: "bossOpenChest",
							args: {
								bossId: boss.id,
								amount: 1,
								starmoney: 0
							},
							ident: "bossOpenChest_" + boss.id
						});
					} else if (boss.chestId == 1) {
						bossRaidOpenChestCall.calls.push({
							name: "bossOpenChest",
							args: {
								bossId: boss.id,
								amount: 1,
								starmoney: 0
							},
							ident: "bossOpenChest_" + boss.id
						});
					}
				}

				if (!bossRaidOpenChestCall.calls.length) {
					setProgress(`${I18N('OUTLAND')} ${I18N('NOTHING_TO_COLLECT')}`, true);
					resolve();
					return;
				}

				send(JSON.stringify(bossRaidOpenChestCall), e => {
					setProgress(`${I18N('OUTLAND')} ${I18N('COLLECTED')}`, true);
					resolve();
				});
			});
		});
	}
	/**
	 * Collect all rewards
	 *
	 * Собрать все награды
	 */
	function questAllFarm() {
		return new Promise(function (resolve, reject) {
			let questGetAllCall = {
				calls: [{
					name: "questGetAll",
					args: {},
					ident: "body"
				}]
			}
			send(JSON.stringify(questGetAllCall), function (data) {
				let questGetAll = data.results[0].result.response;
				const questAllFarmCall = {
					calls: []
				}
				let number = 0;
				for (let quest of questGetAll) {
					if (quest.id < 1e6 && quest.state == 2) {
						questAllFarmCall.calls.push({
							name: "questFarm",
							args: {
								questId: quest.id
							},
							ident: `group_${number}_body`
						});
						number++;
					}
				}

				if (!questAllFarmCall.calls.length) {
					setProgress(`${I18N('COLLECTED')} ${number} ${I18N('REWARD')}`, true);
					resolve();
					return;
				}

				send(JSON.stringify(questAllFarmCall), function (res) {
					console.log(res);
					setProgress(`${I18N('COLLECTED')} ${number} ${I18N('REWARD')}`, true);
					resolve();
				});
			});
		})
	}

	/**
	 * Attack of the minions of Asgard
	 *
	 * Атака прислужников Асгарда
	 */
	function testRaidNodes() {
		return new Promise((resolve, reject) => {
			const tower = new executeRaidNodes(resolve, reject);
			tower.start();
		});
	}

	/**
	 * Attack of the minions of Asgard
	 *
	 * Атака прислужников Асгарда
	 */
	function executeRaidNodes(resolve, reject) {
		let raidData = {
			teams: [],
			favor: {},
			nodes: [],
			attempts: 0,
			countExecuteBattles: 0,
			cancelBattle: 0,
		}

		callsExecuteRaidNodes = {
			calls: [{
				name: "clanRaid_getInfo",
				args: {},
				ident: "clanRaid_getInfo"
			}, {
				name: "teamGetAll",
				args: {},
				ident: "teamGetAll"
			}, {
				name: "teamGetFavor",
				args: {},
				ident: "teamGetFavor"
			}]
		}

		this.start = function () {
			send(JSON.stringify(callsExecuteRaidNodes), startRaidNodes);
		}

		function startRaidNodes(data) {
			res = data.results;
			clanRaidInfo = res[0].result.response;
			teamGetAll = res[1].result.response;
			teamGetFavor = res[2].result.response;

			let index = 0;
			for (let team of teamGetAll.clanRaid_nodes) {
				raidData.teams.push({
					data: {},
					heroes: team.filter(id => id < 6000),
					pet: team.filter(id => id >= 6000).pop(),
					battleIndex: index++
				});
			}
			raidData.favor = teamGetFavor.clanRaid_nodes;

			raidData.nodes = clanRaidInfo.nodes;
			raidData.attempts = clanRaidInfo.attempts;
			isCancalBattle = false;

			checkNodes();
		}

		function getAttackNode() {
			for (let nodeId in raidData.nodes) {
				let node = raidData.nodes[nodeId];
				let points = 0
				for (team of node.teams) {
					points += team.points;
				}
				let now = Date.now() / 1000;
				if (!points && now > node.timestamps.start && now < node.timestamps.end) {
					let countTeam = node.teams.length;
					delete raidData.nodes[nodeId];
					return {
						nodeId,
						countTeam
					};
				}
			}
			return null;
		}

		function checkNodes() {
			setProgress(`${I18N('REMAINING_ATTEMPTS')}: ${raidData.attempts}`);
			let nodeInfo = getAttackNode();
			if (nodeInfo && raidData.attempts) {
				startNodeBattles(nodeInfo);
				return;
			}

			endRaidNodes('EndRaidNodes');
		}

		function startNodeBattles(nodeInfo) {
			let {nodeId, countTeam} = nodeInfo;
			let teams = raidData.teams.slice(0, countTeam);
			let heroes = raidData.teams.map(e => e.heroes).flat();
			let favor = {...raidData.favor};
			for (let heroId in favor) {
				if (!heroes.includes(+heroId)) {
					delete favor[heroId];
				}
			}

			let calls = [{
				name: "clanRaid_startNodeBattles",
				args: {
					nodeId,
					teams,
					favor
				},
				ident: "body"
			}];

			send(JSON.stringify({calls}), resultNodeBattles);
		}

		function resultNodeBattles(e) {
			if (e['error']) {
				endRaidNodes('nodeBattlesError', e['error']);
				return;
			}

			console.log(e);
			let battles = e.results[0].result.response.battles;
			let promises = [];
			let battleIndex = 0;
			for (let battle of battles) {
				battle.battleIndex = battleIndex++;
				promises.push(calcBattleResult(battle));
			}

			Promise.all(promises)
				.then(results => {
					const endResults = {};
					let isAllWin = true;
					for (let r of results) {
						isAllWin &&= r.result.win;
					}
					if (!isAllWin) {
						cancelEndNodeBattle(results[0]);
						return;
					}
					raidData.countExecuteBattles = results.length;
					let timeout = 500;
					for (let r of results) {
						setTimeout(endNodeBattle, timeout, r);
						timeout += 500;
					}
				});
		}
		/**
		 * Returns the battle calculation promise
		 *
		 * Возвращает промис расчета боя
		 */
		function calcBattleResult(battleData) {
			return new Promise(function (resolve, reject) {
				BattleCalc(battleData, "get_clanPvp", resolve);
			});
		}
		/**
		 * Cancels the fight
		 *
		 * Отменяет бой
		 */
		function cancelEndNodeBattle(r) {
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			fixBattle(r.progress[0].attackers.heroes);
			fixBattle(r.progress[0].defenders.heroes);
			endNodeBattle(r);
		}
		/**
		 * Ends the fight
		 *
		 * Завершает бой
		 */
		function endNodeBattle(r) {
			let nodeId = r.battleData.result.nodeId;
			let battleIndex = r.battleData.battleIndex;
			let calls = [{
				name: "clanRaid_endNodeBattle",
				args: {
					nodeId,
					battleIndex,
					result: r.result,
					progress: r.progress
				},
				ident: "body"
			}]

			SendRequest(JSON.stringify({calls}), battleResult);
		}
		/**
		 * Processing the results of the battle
		 *
		 * Обработка результатов боя
		 */
		function battleResult(e) {
			if (e['error']) {
				endRaidNodes('missionEndError', e['error']);
				return;
			}
			r = e.results[0].result.response;
			if (r['error']) {
				if (r.reason == "invalidBattle") {
					raidData.cancelBattle++;
					checkNodes();
				} else {
					endRaidNodes('missionEndError', e['error']);
				}
				return;
			}

			if (!(--raidData.countExecuteBattles)) {
				raidData.attempts--;
				checkNodes();
			}
		}
		/**
		 * Completing a task
		 *
		 * Завершение задачи
		 */
		function endRaidNodes(reason, info) {
			isCancalBattle = true;
			let textCancel = raidData.cancelBattle ? ` ${I18N('BATTLES_CANCELED')}: ${raidData.cancelBattle}` : '';
			setProgress(`${I18N('MINION_RAID')} ${I18N('COMPLETED')}! ${textCancel}`, true);
			console.log(reason, info);
			resolve();
		}
	}
	/**
	 * Mission auto repeat
	 *
	 * Автоповтор миссии
	 * isStopSendMission = false;
	 * isSendsMission = true;
	 **/
	this.sendsMission = async function (param) {
		if (isStopSendMission) {
			isSendsMission = false;
			console.log(I18N('STOPPED'));
			setProgress('');
			await popup.confirm(`${I18N('STOPPED')}<br>${I18N('REPETITIONS')}: ${param.count}`, [{
				msg: 'Ok',
				result: true
			}, ])
			return;
		}

		let missionStartCall = {
			"calls": [{
				"name": "missionStart",
				"args": lastMissionStart,
				"ident": "body"
			}]
		}
		/**
		 * Mission Request
		 *
		 * Запрос на выполнение мисии
		 */
		SendRequest(JSON.stringify(missionStartCall), async e => {
			if (e['error']) {
				isSendsMission = false;
				console.log(e['error']);
				setProgress('');
				let msg = e['error'].name + ' ' + e['error'].description + `<br>${I18N('REPETITIONS')}: ${param.count}`;
				await popup.confirm(msg, [
					{msg: 'Ok', result: true},
				])
				return;
			}
			/**
			 * Mission data calculation
			 *
			 * Расчет данных мисии
			 */
			BattleCalc(e.results[0].result.response, 'get_tower', async r => {

				let missionEndCall = {
					"calls": [{
						"name": "missionEnd",
						"args": {
							"id": param.id,
							"result": r.result,
							"progress": r.progress
						},
						"ident": "body"
					}]
				}
				/**
				 * Mission Completion Request
				 *
				 * Запрос на завершение миссии
				 */
				SendRequest(JSON.stringify(missionEndCall), async (e) => {
					if (e['error']) {
						isSendsMission = false;
						console.log(e['error']);
						setProgress('');
						let msg = e['error'].name + ' ' + e['error'].description + `<br>${I18N('REPETITIONS')}: ${param.count}`;
						await popup.confirm(msg, [
							{msg: 'Ok', result: true},
						])
						return;
					}
					r = e.results[0].result.response;
					if (r['error']) {
						isSendsMission = false;
						console.log(r['error']);
						setProgress('');
						await popup.confirm(`<br>${I18N('REPETITIONS')}: ${param.count}` + ' 3 ' + r['error'], [
							{msg: 'Ok', result: true},
						])
						return;
					}

					param.count++;
					setProgress(`${I18N('MISSIONS_PASSED')}: ${param.count} (${I18N('STOP')})`, false, () => {
						isStopSendMission = true;
					});
					setTimeout(sendsMission, 1, param);
				});
			})
		});
	}
	/**
	 * Recursive opening of matryoshka dolls
	 *
	 * Рекурсивное открытие матрешек
	 */
	function openRussianDoll(id, count, sum) {
		sum = sum || 0;
		sum += count;
		send('{"calls":[{"name":"consumableUseLootBox","args":{"libId":'+id+',"amount":'+count+'},"ident":"body"}]}', e => {
			setProgress(`${I18N('OPEN')} ${count}`, true);
			let result = e.results[0].result.response;
			let newCount = 0;
			for(let n of result) {
				if (n?.consumable && n.consumable[id]) {
					newCount += n.consumable[id]
				}
			}
			if (newCount) {
				openRussianDoll(id, newCount, sum);
			} else {
				popup.confirm(`${I18N('TOTAL_OPEN')} ${sum}`);
			}
		})
	}

	/**
	 * Asgard Boss Attack Replay
	 *
	 * Повтор атаки босса Асгарда
	 */
	function testBossBattle() {
		return new Promise((resolve, reject) => {
			const bossBattle = new executeBossBattle(resolve, reject);
			bossBattle.start(lastBossBattle, lastBossBattleInfo);
		});
	}

	/**
	 * Asgard Boss Attack Replay
	 *
	 * Повтор атаки босса Асгарда
	 */
	function executeBossBattle(resolve, reject) {
		let lastBossBattleArgs = {};
		let reachDamage = 0;
		let countBattle = 0;
		let countMaxBattle = 10;
		let lastDamage = 0;

		this.start = function (battleArg, battleInfo) {
			lastBossBattleArgs = battleArg;
			preCalcBattle(battleInfo);
		}

		function getBattleInfo(battle) {
			return new Promise(function (resolve) {
				battle.seed = Math.floor(Date.now() / 1000) + random(0, 1e3);
				BattleCalc(battle, getBattleType(battle.type), e => {
					let extra = e.progress[0].defenders.heroes[1].extra;
					resolve(extra.damageTaken + extra.damageTakenNextLevel);
				});
			});
		}

		function preCalcBattle(battle) {
			let actions = [];
			const countTestBattle = getInput('countTestBattle');
			for (let i = 0; i < countTestBattle; i++) {
				actions.push(getBattleInfo(battle, true));
			}
			Promise.all(actions)
				.then(resultPreCalcBattle);
		}

		function fixDamage(damage) {
			for (let i = 1e6; i > 1; i /= 10) {
				if (damage > i) {
					let n = i / 10;
					damage = Math.ceil(damage / n) * n;
					break;
				}
			}
			return damage;
		}

		async function resultPreCalcBattle(damages) {
			let maxDamage = 0;
			let minDamage = 1e10;
			let avgDamage = 0;
			for (let damage of damages) {
				avgDamage += damage
				if (damage > maxDamage) {
					maxDamage = damage;
				}
				if (damage < minDamage) {
					minDamage = damage;
				}
			}
			avgDamage /= damages.length;
			console.log(damages.map(e => e.toLocaleString()).join('\n'), avgDamage, maxDamage);

			reachDamage = fixDamage(avgDamage);
			const result = await popup.confirm(
				`${I18N('ROUND_STAT')} ${damages.length} ${I18N('BATTLE')}:` +
				`<br>${I18N('MINIMUM')}: ` + minDamage.toLocaleString() +
				`<br>${I18N('MAXIMUM')}: ` + maxDamage.toLocaleString() +
				`<br>${I18N('AVERAGE')}: ` + avgDamage.toLocaleString()
				/*+ '<br>Поиск урона больше чем ' + reachDamage.toLocaleString()*/
				, [
					{ msg: I18N('BTN_OK'), result: 0},
				/* {msg: 'Погнали', isInput: true, default: reachDamage}, */
			])
			if (result) {
				reachDamage = result;
				isCancalBossBattle = false;
				startBossBattle();
				return;
			}
			endBossBattle(I18N('BTN_CANCEL'));
		}

		function startBossBattle() {
			countBattle++;
			countMaxBattle = getInput('countAutoBattle');
			if (countBattle > countMaxBattle) {
				setProgress('Превышен лимит попыток: ' + countMaxBattle, true);
				endBossBattle('Превышен лимит попыток: ' + countMaxBattle);
				return;
			}
			let calls = [{
				name: "clanRaid_startBossBattle",
				args: lastBossBattleArgs,
				ident: "body"
			}];
			send(JSON.stringify({calls}), calcResultBattle);
		}

		function calcResultBattle(e) {
			BattleCalc(e.results[0].result.response.battle, "get_clanPvp", resultBattle);
		}

		async function resultBattle(e) {
			let extra = e.progress[0].defenders.heroes[1].extra
			resultDamage = extra.damageTaken + extra.damageTakenNextLevel
			console.log(resultDamage);
			scriptMenu.setStatus(countBattle + ') ' + resultDamage.toLocaleString());
			lastDamage = resultDamage;
			if (resultDamage > reachDamage && await popup.confirm(countBattle + ') Урон ' + resultDamage.toLocaleString(), [
				{msg: 'Ок', result: true},
				{msg: 'Не пойдет', result: false},
			]))  {
				endBattle(e, false);
				return;
			}
			cancelEndBattle(e);
		}

		function cancelEndBattle (r) {
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			fixBattle(r.progress[0].attackers.heroes);
			fixBattle(r.progress[0].defenders.heroes);
			endBattle(r, true);
		}

		function endBattle(battleResult, isCancal) {
			let calls = [{
				name: "clanRaid_endBossBattle",
				args: {
					result: battleResult.result,
					progress: battleResult.progress
				},
				ident: "body"
			}];

			send(JSON.stringify({calls}), e => {
				console.log(e);
				if (isCancal) {
					startBossBattle();
					return;
				}
				scriptMenu.setStatus('Босс пробит нанесен урон: ' + lastDamage);
				setTimeout(() => {
					scriptMenu.setStatus('');
				}, 5000);
				endBossBattle('Узпех!');
			});
		}

		/**
		 * Completing a task
		 *
		 * Завершение задачи
		 */
		function endBossBattle(reason, info) {
			isCancalBossBattle = true;
			console.log(reason, info);
			resolve();
		}
	}

	/**
	 * Auto-repeat attack
	 *
	 * Автоповтор атаки
	 */
	function testAutoBattle() {
		return new Promise((resolve, reject) => {
			const bossBattle = new executeAutoBattle(resolve, reject);
			bossBattle.start(lastBattleArg, lastBattleInfo);
		});
	}

	/**
	 * Auto-repeat attack
	 *
	 * Автоповтор атаки
	 */
	function executeAutoBattle(resolve, reject) {
		let battleArg = {};
		let countBattle = 0;
		let findCoeff = 0;

		this.start = function (battleArgs, battleInfo) {
			battleArg = battleArgs;
			preCalcBattle(battleInfo);
		}
		/**
		 * Returns a promise for combat recalculation
		 *
		 * Возвращает промис для прерасчета боя
		 */
		function getBattleInfo(battle) {
			return new Promise(function (resolve) {
				battle.seed = Math.floor(Date.now() / 1000) + random(0, 1e3);
				Calc(battle).then(e => {
					e.coeff = calcCoeff(e, 'defenders');
					resolve(e);
				});
			});
		}
		/**
		 * Battle recalculation
		 *
		 * Прерасчет боя
		 */
		function preCalcBattle(battle) {
			let actions = [];
			const countTestBattle = getInput('countTestBattle');
			for (let i = 0; i < countTestBattle; i++) {
				actions.push(getBattleInfo(battle));
			}
			Promise.all(actions)
				.then(resultPreCalcBattle);
		}
		/**
		 * Processing the results of the battle recalculation
		 *
		 * Обработка результатов прерасчета боя
		 */
		async function resultPreCalcBattle(results) {
			let countWin = results.reduce((s, w) => w.result.win + s, 0);
			setProgress(`${I18N('CHANCE_TO_WIN')} ${Math.floor(countWin / results.length * 100)}% (${results.length})`, true);
			if (countWin > 0) {
				isCancalBattle = false;
				startBattle();
				return;
			}

			let minCoeff = 100;
			let maxCoeff = -100;
			let avgCoeff = 0;
			results.forEach(e => {
				if (e.coeff < minCoeff) minCoeff = e.coeff;
				if (e.coeff > maxCoeff) maxCoeff = e.coeff;
				avgCoeff += e.coeff;
			});
			avgCoeff /= results.length;

			const result = await popup.confirm(
				I18N('VICTORY_IMPOSSIBLE') +
				`<br>${I18N('ROUND_STAT')} ${results.length} ${I18N('BATTLE')}:` +
				`<br>${I18N('MINIMUM')}: ` + minCoeff.toLocaleString() +
				`<br>${I18N('MAXIMUM')}: ` + maxCoeff.toLocaleString() +
				`<br>${I18N('AVERAGE')}: ` + avgCoeff.toLocaleString() +
				`<br>${I18N('FIND_COEFF')} ` + avgCoeff.toLocaleString(), [
				{ msg: I18N('BTN_CANCEL'), result: 0 },
				{ msg: I18N('BTN_GO'), isInput: true, default: Math.round(avgCoeff * 1000) / 1000 },
			])
			if (result) {
				findCoeff = result;
				isCancalBattle = false;
				startBattle();
				return;
			}
			setProgress(I18N('NOT_THIS_TIME'), true);
			endAutoBattle(I18N('NOT_THIS_TIME'));
		}

		/**
		 * Calculation of the combat result coefficient
		 *
		 * Расчет коэфициента результата боя
		 */
		function calcCoeff(result, packType) {
			let beforeSumFactor = 0;
			const beforePack = result.battleData[packType][0];
			for (let heroId in beforePack) {
				const hero = beforePack[heroId];
				const state = hero.state;
				let factor = 1;
				if (state) {
					const hp = state.hp / state.maxHp;
					const energy = state.energy / 1e3;
					factor = hp + energy / 20;
				}
				beforeSumFactor += factor;
			}

			let afterSumFactor = 0;
			const afterPack = result.progress[0][packType].heroes;
			for (let heroId in afterPack) {
				const hero = afterPack[heroId];
				const hp = hero.hp / beforePack[heroId].state.hp;
				const energy = hero.energy / 1e3;
				const factor = hp + energy / 20;
				afterSumFactor += factor;
			}
			const resultCoeff = -(afterSumFactor - beforeSumFactor);
			return Math.round(resultCoeff * 1000) / 1000;
		}
		/**
		 * Start battle
		 *
		 * Начало боя
		 */
		function startBattle() {
			countBattle++;
			const countMaxBattle = getInput('countAutoBattle');
			setProgress(countBattle + '/' + countMaxBattle);
			if (countBattle > countMaxBattle) {
				setProgress(`${I18N('RETRY_LIMIT_EXCEEDED')}: ${countMaxBattle}`, true);
				endAutoBattle(`${I18N('RETRY_LIMIT_EXCEEDED')}: ${countMaxBattle}`)
				return;
			}
			let calls = [{
				name: nameFuncStartBattle,
				args: battleArg,
				ident: "body"
			}];
			send(JSON.stringify({
				calls
			}), calcResultBattle);
		}
		/**
		 * Battle calculation
		 *
		 * Расчет боя
		 */
		function calcResultBattle(e) {
			let battle = e.results[0].result.response.battle
			BattleCalc(battle, getBattleType(battle.type), resultBattle);
		}
		/**
		 * Processing the results of the battle
		 *
		 * Обработка результатов боя
		 */
		function resultBattle(e) {
			const isWin = e.result.win;
			console.log(isWin);
			if (isWin) {
				endBattle(e, false);
				return;
			}
			if (findCoeff) {
				const coeff = calcCoeff(e, 'defenders');
				console.log(coeff);
				setProgress(coeff, true);
				if (coeff > findCoeff) {
					endBattle(e, false);
					return;
				}
			}
			cancelEndBattle(e);
		}
		/**
		 * Cancel fight
		 *
		 * Отмена боя
		 */
		function cancelEndBattle(r) {
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			fixBattle(r.progress[0].attackers.heroes);
			fixBattle(r.progress[0].defenders.heroes);
			endBattle(r, true);
		}
		/**
		 * End of the fight
		 *
		 * Завершение боя */
		function endBattle(battleResult, isCancal) {
			let calls = [{
				name: nameFuncEndBattle,
				args: {
					result: battleResult.result,
					progress: battleResult.progress
				},
				ident: "body"
			}];

			send(JSON.stringify({
				calls
			}), e => {
				console.log(e);
				if (isCancal) {
					startBattle();
					return;
				}
				scriptMenu.setStatus(`${I18N('SUCCESS')}!`);
				setTimeout(() => {
					scriptMenu.setStatus('');
				}, 5000)
				endAutoBattle(`${I18N('SUCCESS')}!`)
			});
		}
		/**
		 * Completing a task
		 *
		 * Завершение задачи
		 */
		function endAutoBattle(reason, info) {
			isCancalBattle = true;
			console.log(reason, info);
			resolve();
		}
	}
	/**
	 * Collect all mail, except letters with energy and charges of the portal
	 *
	 * Собрать всю почту, кроме писем с энергией и зарядами портала
	 */
	function mailGetAll() {
		const getMailInfo = '{"calls":[{"name":"mailGetAll","args":{},"ident":"body"}]}';

		return Send(getMailInfo).then(dataMail => {
			const letters = dataMail.results[0].result.response.letters;
			const letterIds = lettersFilter(letters);
			if (!letterIds.length) {
				setProgress(I18N('NOTHING_TO_COLLECT'), true);
				return;
			}

			const calls = [
				{ name: "mailFarm", args: { letterIds }, ident: "body" }
			];

			return Send(JSON.stringify({ calls })).then(res => {
				const lettersIds = res.results[0].result.response;
				if (lettersIds) {
					const countLetters = Object.keys(lettersIds).length;
					setProgress(`${I18N('RECEIVED')} ${countLetters} ${I18N('LETTERS')}`, true);
				}
			});
		});
	}
	/**
	 * Filters received emails
	 *
	 * Фильтрует получаемые письма
	 */
	function lettersFilter(letters) {
		const lettersIds = [];
		for (let l in letters) {
			letter = letters[l];
			const reward = letter.reward;
			/**
			 * Mail Collection Exceptions
			 *
			 * Исключения на сбор писем
			 */
			const isFarmLetter = !(
				/** Portals // сферы портала */
				(reward?.refillable ? reward.refillable[45] : false) ||
				/** Energy // энергия */
				(reward?.stamina ? reward.stamina : false) ||
				/** accelerating energy gain // ускорение набора энергии */
				(reward?.buff ? true : false) ||
				/** VIP Points // вип очки */
				(reward?.vipPoints ? reward.vipPoints : false) ||
				/** souls of heroes // душы героев */
				(reward?.fragmentHero ? true : false) ||
				/** heroes // герои */
				(reward?.bundleHeroReward ? true : false)
			);
			if (isFarmLetter) {
				lettersIds.push(~~letter.id);
			}
		}
		return lettersIds;
	}
	/**
	 * Displaying information about the areas of the portal and attempts on the VG
	 *
	 * Отображение информации о сферах портала и попытках на ВГ
	 */
	async function justInfo() {
		return new Promise(async (resolve, reject) => {
			const calls = [{
				name: "userGetInfo",
				args: {},
				ident: "userGetInfo"
			},
			{
				name: "clanWarGetInfo",
				args: {},
				ident: "clanWarGetInfo"
			}];
			const result = await Send(JSON.stringify({ calls }));
			const infos = result.results;
			const portalSphere = infos[0].result.response.refillable.find(n => n.id == 45);
			const clanWarMyTries = infos[1].result.response?.myTries ?? 0;
			const sanctuaryButton = buttons['goToSanctuary'].button;
			const clanWarButton = buttons['goToClanWar'].button;
			if (portalSphere.amount) {
				sanctuaryButton.style.color = portalSphere.amount >= 3 ? 'red' : 'brown';
				sanctuaryButton.title = `${I18N('SANCTUARY_TITLE')}\n${portalSphere.amount} ${I18N('PORTALS')}`;
			} else {
				sanctuaryButton.style.color = '';
				sanctuaryButton.title = I18N('SANCTUARY_TITLE');
			}
			if (clanWarMyTries) {
				clanWarButton.style.color = 'red';
				clanWarButton.title = `${I18N('GUILD_WAR_TITLE')}\n${clanWarMyTries}${I18N('ATTEMPTS')}`;
			} else {
				clanWarButton.style.color = '';
				clanWarButton.title = I18N('GUILD_WAR_TITLE');
			}
			setProgress('<img src="https://zingery.ru/heroes/portal.png" style="height: 25px;position: relative;top: 5px;"> ' + `${portalSphere.amount} </br> ${I18N('GUILD_WAR')}: ${clanWarMyTries}`, true);
			resolve();
		});
	}

	function testDailyQuests() {
		return new Promise((resolve, reject) => {
			const bossBattle = new dailyQuests(resolve, reject, questsInfo);
			bossBattle.start();
		});
	}
	/**
	 * Automatic completion of daily quests
	 *
	 * Автоматическое выполнение ежедневных квестов
	 */
	class dailyQuests {
		/**
		 * Send(' {"calls":[{"name":"heroGetAll","args":{},"ident":"body"}]}').then(e => console.log(e))
		 * Send(' {"calls":[{"name":"titanGetAll","args":{},"ident":"body"}]}').then(e => console.log(e))
		 * Send(' {"calls":[{"name":"inventoryGet","args":{},"ident":"body"}]}').then(e => console.log(e))
		 * Send(' {"calls":[{"name":"questGetAll","args":{},"ident":"body"}]}').then(e => console.log(e))
		 */
		dataQuests = {
			10001: {
				/**
				 * TODO: Watch heroes and money
				 * TODO: Смотреть героев и деньги
				 */
				description: 'Улучши умения героев 3 раза', //
				isWeCanDo: () => false,
			},
			10002: {
				description: 'Пройди 10 миссий', // --------------
				isWeCanDo: () => false,
			},
			10003: {
				description: 'Пройди 3 героические миссии', // --------------
				isWeCanDo: () => false,
			},
			10004: {
				description: 'Сразись 3 раза на Арене или Гранд Арене', // --------------
				isWeCanDo: () => false,
			},
			10006: {
				description: 'Используй обмен изумрудов 1 раз',
				doItCall: [{ name: "refillableAlchemyUse", args: { multi: false }, ident: "refillableAlchemyUse" }],
				isWeCanDo: () => false,
			},
			10007: {
				description: 'Открой 1 сундук', // ++++++++++++++++
				doItCall: [{ name: "chestBuy", args: { chest: "town", free: true, pack: false }, ident: "chestBuy" }],
				isWeCanDo: (info) => {
					const chestInfo = info['userGetInfo'].refillable.find(e => e.id == 37);
					return chestInfo.amount > 0;
				},
			},
			10016: {
				description: 'Отправь подарки согильдийцам', // ++++++++++++++++
				doItCall: [{ name: "clanSendDailyGifts", args: {}, ident: "clanSendDailyGifts" }],
				isWeCanDo: () => true,
			},
			10018: {
				/**
				 * TODO: Watch heroes, watch potions (consumable 9, 10, 11, 12)
				 * TODO: Смотреть героев, смотреть зелья (consumable 9, 10, 11, 12)
				 */
				description: 'Используй зелье опыта',
				/**
				 * Spends a bank of experience on Galahard
				 * Тратит банку опыта на Галахарда
				 */
				doItCall: [{ name: "consumableUseHeroXp", args: { heroId: 2, libId: 10, amount: 1 }, ident: "consumableUseHeroXp" }],
				isWeCanDo: () => false,
			},
			10019: {
				description: 'Открой 1 сундук в Башне',
				doItFunc: testTower,
				isWeCanDo: () => false,
			},
			10020: {
				description: 'Открой 3 сундука в Запределье',
				isWeCanDo: () => false,
			},
			10021: {
				description: 'Собери 75 Титанита в Подземелье Гильдии',
				isWeCanDo: () => false,
			},
			10022: {
				description: 'Собери 150 Титанита в Подземелье Гильдии',
				doItFunc: testDungeon,
				isWeCanDo: () => false,
			},
			10023: {
				/**
				 * TODO: Watch heroes, watch sparks (24 consumable, 250 at level 0 and 7000 gold)
				 * TODO: Смотреть героев, смотреть искры (consumable 24, 250 на 0 уровне и золото 7000)
				 */
				description: 'Прокачай Дар Стихий на 1 уровень',
				/**
				 * Upgrade and Reset Gift of the Elements to Galahard
				 * Улучшение и сброс дара стихий Галахарду
				*/
				doItCall: [
					{ name: "heroTitanGiftLevelUp", args: { heroId: 2 }, ident: "heroTitanGiftLevelUp" },
					{ name: "heroTitanGiftDrop", args: { heroId: 2 }, ident: "heroTitanGiftDrop" }
				],
				isWeCanDo: () => false,
			},
			10024: {
				/**
				 * TODO: Watch Heroes
				 * TODO: Смотреть героев
				 */
				description: 'Повысь уровень любого артефакта один раз',
				isWeCanDo: () => false,
			},
			10025: {
				description: 'Начни 1 Экспедицию',
				doItFunc: checkExpedition,
				isWeCanDo: () => false,
			},
			10026: {
				description: 'Начни 4 Экспедиции', // --------------
				doItFunc: checkExpedition,
				isWeCanDo: () => false,
			},
			10027: {
				description: 'Победи в 1 бою Турнира Стихий',
				doItFunc: testTitanArena,
				isWeCanDo: () => false,
			},
			10028: {
				/**
				 * TODO: Смотреть титанов, можно качать арты за золото если золота больше 5 лямов
				 * TODO: Watch titans, you can download arts for gold if there is more than 5kk gold
				 */
				description: 'Повысь уровень любого артефакта титанов',
				isWeCanDo: () => false,
			},
			10029: {
				description: 'Открой сферу артефактов титанов', // ++++++++++++++++
				doItCall: [{ name: "titanArtifactChestOpen", args: { amount: 1, free: true }, ident: "titanArtifactChestOpen" }],
				isWeCanDo: (info) => {
					return info['inventoryGet']?.consumable[55] > 0
				},
			},
			10030: {
				/**
				 * TODO: Watch Heroes
				 * TODO: Смотреть героев
				 */
				description: 'Улучши облик любого героя 1 раз',
				isWeCanDo: () => false,
			},
			10031: {
				description: 'Победи в 6 боях Турнира Стихий', // --------------
				doItFunc: testTitanArena,
				isWeCanDo: () => false,
			},
			10043: {
				description: 'Начни или присоеденись к Приключению', // --------------
				isWeCanDo: () => false,
			},
			10044: {
				description: 'Воспользуйся призывом питомцев 1 раз', // ++++++++++++++++
				doItCall: [{ name: "pet_chestOpen", args: { amount: 1, paid: false }, ident: "pet_chestOpen" }],
				isWeCanDo: (info) => {
					return info['inventoryGet']?.consumable[90] > 0
				},
			},
			10046: {
				/**
				 * TODO: Watch Adventure
				 * TODO: Смотреть приключение
				 */
				description: 'Открой 3 сундука в Приключениях',
				isWeCanDo: () => false,
			},
			10047: {
				/**
				 * TODO: Watch heroes and runes consumable 1, 2, 3, 4
				 * TODO: Смотреть героев и руны consumable 1, 2, 3, 4
				 */
				description: 'Набери 150 очков активности в Гильдии',
				/**
				 * Upgrade the rune Galahard
				 * Прокачать руну Галахарду
				 */
				doItCall: [{ name: "heroEnchantRune", args: { heroId: 2, tier: 0, items: { consumable: { '1': 1 } } }, ident: "heroEnchantRune" }],
				isWeCanDo: () => false,
			},
		};

		constructor(resolve, reject, questInfo) {
			this.resolve = resolve;
			this.reject = reject;
			this.questInfo = questInfo
		}

		async start() {
			/**
			 * TODO may not be needed
			 *
			 * TODO возожно не нужна
			 */
			let countQuest = 0;
			const weCanDo = [];
			const selectedActions = getSaveVal('selectedActions', {});
			for (let quest of this.questInfo['questGetAll']) {
				if (quest.id in this.dataQuests && quest.state == 1) {
					if (!selectedActions[quest.id]) {
						selectedActions[quest.id] = {
							checked: false
						}
					}
					if (!this.dataQuests[quest.id].isWeCanDo(this.questInfo)) {
						continue;
					}
				
					weCanDo.push({
						name: quest.id,
						label: I18N(`QUEST_${quest.id}`),
						checked: selectedActions[quest.id].checked
					});
					countQuest++;
				}
			}

			if (!weCanDo.length) {
				this.end(I18N('NOTHING_TO_DO'));
				return;
			}

			console.log(weCanDo);
			const answer = await popup.confirm(`${I18N('YOU_CAN_COMPLETE') }:`, [
				{ msg: I18N('BTN_DO_IT'), result: true },
				{ msg: I18N('BTN_CANCEL'), result: false },
			], weCanDo);
			if (!answer) {
				this.end('');
				return;
			}
			const taskList = popup.getCheckBoxes();
			taskList.forEach(e => {
				selectedActions[e.name].checked = e.checked;
			});
			setSaveVal('selectedActions', selectedActions);
			const calls = [];
			let countChecked = 0;
			for (const task of taskList) {
				if (task.checked) {
					countChecked++;
					const quest = this.dataQuests[task.name]
					console.log(quest.description);

					if (quest.doItCall) {
						calls.push(...quest.doItCall);
					}
				}
			}

			if (!countChecked) {
				this.end(I18N('NOT_QUEST_COMPLETED'));
				return;
			}

			await Send(JSON.stringify({ calls }));
			this.end(`${I18N('COMPLETED_QUESTS')}: ${countChecked}`);
		}

		errorHandling(error) {
			//console.error(error);
			let errorInfo = error.toString() + '\n';
			try {
				const errorStack = error.stack.split('\n');
				const endStack = errorStack.map(e => e.split('@')[0]).indexOf("testDoYourBest");
				errorInfo += errorStack.slice(0, endStack).join('\n');
			} catch (e) {
				errorInfo += error.stack;
			}
			copyText(errorInfo);
		}

		end(status) {
			setProgress(status, true);
			this.resolve();
		}
	}

	function testDoYourBest() {
		return new Promise((resolve, reject) => {
			const doIt = new doYourBest(resolve, reject);
			doIt.start();
		});
	}
	/**
	 * Do everything button
	 *
	 * Кнопка сделать все
	 */
	class doYourBest {

		funcList = [
			{
				name: 'getOutland',
				label: I18N('ASSEMBLE_OUTLAND'),
				checked: false
			},
			{
				name: 'testTower',
				label: I18N('PASS_THE_TOWER'),
				checked: false
			},
			{
				name: 'checkExpedition',
				label: I18N('CHECK_EXPEDITIONS'),
				checked: false
			},
			{
				name: 'testTitanArena',
				label: I18N('COMPLETE_TOE'),
				checked: false
			},
			{
				name: 'testDungeon',
				label: I18N('COMPLETE_DUNGEON'),
				checked: false
			},
			{
				name: 'mailGetAll',
				label: I18N('COLLECT_MAIL'),
				checked: false
			},
			{
				name: 'collectAllStuff',
				label: I18N('COLLECT_MISC'),
				checked: false
			},
			{
				name: 'questAllFarm',
				label: I18N('COLLECT_QUEST_REWARDS'),
				checked: false
			},
			{
				name: 'synchronization',
				label: I18N('MAKE_A_SYNC'),
				checked: false
			},
		];

		functions = {
			getOutland,
			testTower,
			checkExpedition,
			testTitanArena,
			testDungeon,
			mailGetAll,
			collectAllStuff: async () => {
				await offerFarmAllReward();
				await Send('{"calls":[{"name":"subscriptionFarm","args":{},"ident":"body"},{"name":"zeppelinGiftFarm","args":{},"ident":"zeppelinGiftFarm"},{"name":"grandFarmCoins","args":{},"ident":"grandFarmCoins"}]}');
			},
			questAllFarm,
			synchronization: async () => {
				cheats.refreshGame();
			}
		}

		constructor(resolve, reject, questInfo) {
			this.resolve = resolve;
			this.reject = reject;
			this.questInfo = questInfo
		}

		async start() {
			const selectedDoIt = getSaveVal('selectedDoIt', {});

			this.funcList.forEach(task => {
				if (!selectedDoIt[task.name]) {
					selectedDoIt[task.name] = {
						checked: task.checked
					}
				} else {
					task.checked = selectedDoIt[task.name].checked
				}
			});

			const answer = await popup.confirm(I18N('RUN_FUNCTION'), [
				{ msg: I18N('BTN_CANCEL'), result: false },
				{ msg: I18N('BTN_GO'), result: true },
			], this.funcList);

			if (!answer) {
				this.end('');
				return;
			}

			const taskList = popup.getCheckBoxes();
			taskList.forEach(task => {
				selectedDoIt[task.name].checked = task.checked;
			});
			setSaveVal('selectedDoIt', selectedDoIt);
			for (const task of popup.getCheckBoxes()) {
				if (task.checked) {
					try {
						setProgress(`${task.label} <br>${I18N('PERFORMED')}!`);
						await this.functions[task.name]();
						setProgress(`${task.label} <br>${I18N('DONE')}!`);
					} catch (error) {
						if (await popup.confirm(`${I18N('ERRORS_OCCURRES')}:<br> ${task.label} <br>${I18N('COPY_ERROR')}?`, [
							{ msg: I18N('BTN_NO'), result: false },
							{ msg: I18N('BTN_YES'), result: true },
						])) {
							this.errorHandling(error);
						}
					}
				}
			}
			setTimeout((msg) => {
				this.end(msg);
			}, 2000, I18N('ALL_TASK_COMPLETED'));
			return;
		}

		errorHandling(error) {
			//console.error(error);
			let errorInfo = error.toString() + '\n';
			try {
				const errorStack = error.stack.split('\n');
				const endStack = errorStack.map(e => e.split('@')[0]).indexOf("testDoYourBest");
				errorInfo += errorStack.slice(0, endStack).join('\n');
			} catch (e) {
				errorInfo += error.stack;
			}
			copyText(errorInfo);
		}

		end(status) {
			setProgress(status, true);
			this.resolve();
		}
	}
	/**
	 * Passing the adventure along the specified route
	 *
	 * Прохождение приключения по указанному маршруту
	 */
	function testAdventure(type) {
		return new Promise((resolve, reject) => {
			const bossBattle = new executeAdventure(resolve, reject);
			bossBattle.start(type);
		});
	}
	/**
	 * Passing the adventure along the specified route
	 *
	 * Прохождение приключения по указанному маршруту
	 */
	class executeAdventure {

		type = 'default';

		actions = {
			default: {
				getInfo: "adventure_getInfo",
				startBattle: 'adventure_turnStartBattle',
				endBattle: 'adventure_endBattle',
				collectBuff: 'adventure_turnCollectBuff'
			},
			solo: {
				getInfo: "adventureSolo_getInfo",
				startBattle: 'adventureSolo_turnStartBattle',
				endBattle: 'adventureSolo_endBattle',
				collectBuff: 'adventureSolo_turnCollectBuff'
			}
		}

		terminatеReason = I18N('UNKNOWN');
		callAdventureInfo = {
			name: "adventure_getInfo",
			args: {},
			ident: "adventure_getInfo"
		}
		callTeamGetAll = {
			name: "teamGetAll",
			args: {},
			ident: "teamGetAll"
		}
		callTeamGetFavor = {
			name: "teamGetFavor",
			args: {},
			ident: "teamGetFavor"
		}
		callStartBattle = {
			name: "adventure_turnStartBattle",
			args: {},
			ident: "body"
		}
		callEndBattle = {
			name: "adventure_endBattle",
			args: {
				result: {},
				progress: {},
			},
			ident: "body"
		}
		callCollectBuff = {
			name: "adventure_turnCollectBuff",
			args: {},
			ident: "body"
		}

		constructor(resolve, reject) {
			this.resolve = resolve;
			this.reject = reject;
		}

		async start(type) {
			this.type = type || this.type;
			this.path = await this.getPath();
			if (!this.path) {
				this.end();
				return;
			}
			this.callAdventureInfo.name = this.actions[this.type].getInfo;
			const data = await Send(JSON.stringify({
				calls: [
					this.callAdventureInfo,
					this.callTeamGetAll,
					this.callTeamGetFavor
				]
			}));
			return this.checkAdventureInfo(data.results);
		}

		async getPath() {
			const answer = await popup.confirm(I18N('ENTER_THE_PATH'), [
				{
					msg: I18N('START_ADVENTURE'),
					placeholder: '1,2,3,4,5,6',
					isInput: true,
					default: getSaveVal('adventurePath', '')
				},
				{
					msg: I18N('BTN_CANCEL'),
					result: false
				},
			]);
			if (!answer) {
				this.terminatеReason = I18N('BTN_CANCELED');
				return false;
			}
			let path = answer.split(',');
			if (path.length < 2) {
				this.terminatеReason = I18N('MUST_TWO_POINTS');
				return false;
			}

			for (let p in path) {
				path[p] = +path[p].trim()
				if (Number.isNaN(path[p])) {
					this.terminatеReason = I18N('MUST_ONLY_NUMBERS');
					return false;
				}
			}
			setSaveVal('adventurePath', answer);
			return path;
		}

		checkAdventureInfo(data) {
			this.advInfo = data[0].result.response;
			if (!this.advInfo) {
				this.terminatеReason = I18N('NOT_ON_AN_ADVENTURE') ;
				return this.end();
			}
			const heroesTeam = data[1].result.response.adventure_hero;
			const favor = data[2]?.result.response.adventure_hero;
			const heroes = heroesTeam.slice(0, 5);
			const pet = heroesTeam[5];
			this.args = {
				pet,
				heroes,
				favor,
				path: [],
				broadcast: false
			}
			const advUserInfo = this.advInfo.users[userInfo.id];
			this.turnsLeft = advUserInfo.turnsLeft;
			this.currentNode = advUserInfo.currentNode;
			this.nodes = this.advInfo.nodes;
			return this.loop();
		}

		async loop() {
			const position = this.path.indexOf(+this.currentNode);
			if (!(~position)) {
				this.terminatеReason = I18N('YOU_IN_NOT_ON_THE_WAY');
				return this.end();
			}
			this.path = this.path.slice(position);
			if ((this.path.length - 1) > this.turnsLeft &&
				await popup.confirm(I18N('ATTEMPTS_NOT_ENOUGH'), [
					{ msg: I18N('YES_CONTINUE'), result: false },
					{ msg: I18N('BTN_NO'), result: true },
				])) {
				this.terminatеReason = I18N('NOT_ENOUGH_AP');
				return this.end();
			}
			const toPath = [];
			for (const nodeId of this.path) {
				if (!this.turnsLeft) {
					this.terminatеReason = I18N('ATTEMPTS_ARE_OVER');
					return this.end();
				}
				toPath.push(nodeId);
				console.log(toPath);
				if (toPath.length > 1) {
					setProgress(toPath.join(' > ') + ` ${I18N('MOVES')}: ` + this.turnsLeft);
				}
				if (nodeId == this.currentNode) {
					continue;
				}

				const nodeInfo = this.getNodeInfo(nodeId);
				if (nodeInfo.type == 'TYPE_COMBAT') {
					if (nodeInfo.state == 'empty') {
						this.turnsLeft--;
						continue;
					}

					/**
					 * Disable regular battle cancellation
					 *
					 * Отключаем штатную отменую боя
					 */
					isCancalBattle = false;
					if (await this.battle(toPath)) {
						this.turnsLeft--;
						toPath.splice(0, toPath.indexOf(nodeId));
						nodeInfo.state = 'empty';
						isCancalBattle = true;
						continue;
					}
					isCancalBattle = true;
					return this.end()
				}

				if (nodeInfo.type == 'TYPE_PLAYERBUFF') {
					const buff = this.checkBuff(nodeInfo);
					if (buff == null) {
						continue;
					}

					if (await this.collectBuff(buff, toPath)) {
						this.turnsLeft--;
						toPath.splice(0, toPath.indexOf(nodeId));
						continue;
					}
					this.terminatеReason = I18N('BUFF_GET_ERROR');
					return this.end();
				}
			}
			this.terminatеReason = I18N('SUCCESS');
			return this.end();
		}

		/**
		 * Carrying out a fight
		 *
		 * Проведение боя
		 */
		async battle(path, preCalc = true) {
			const data = await this.startBattle(path);
			try {
				const battle = data.results[0].result.response.battle;
				const result = await Calc(battle);
				if (result.result.win) {
					const info = await this.endBattle(result);
					if (info.results[0].result.response?.error) {
						this.terminatеReason = I18N('BATTLE_END_ERROR');
						return false;
					}
				} else {
					await this.cancelBattle(result);

					if (preCalc && await this.preCalcBattle(battle)) {
						path = path.slice(-2);
						for (let i = 1; i <= getInput('countAutoBattle'); i++) {
							setProgress(`${I18N('AUTOBOT')}: ${i}/${getInput('countAutoBattle')}`);
							const result = await this.battle(path, false);
							if (result) {
								setProgress(I18N('VICTORY'));
								return true;
							}
						}
						this.terminatеReason = I18N('FAILED_TO_WIN_AUTO');
						return false;
					}
					return false;
				}
			} catch (error) {
				console.error(error);
				if (await popup.confirm(I18N('ERROR_OF_THE_BATTLE_COPY'), [
					{ msg: I18N('BTN_NO'), result: false },
					{ msg: I18N('BTN_YES'), result: true },
				])) {
					this.errorHandling(error, data);
				}
				this.terminatеReason = I18N('ERROR_DURING_THE_BATTLE');
				return false;
			}
			return true;
		}

		/**
		 * Recalculate battles
		 *
		 * Прерасчтет битвы
		 */
		async preCalcBattle(battle) {
			const countTestBattle = getInput('countTestBattle');
			for (let i = 0; i < countTestBattle; i++) {
				battle.seed = Math.floor(Date.now() / 1000) + random(0, 1e3);
				const result = await Calc(battle);
				if (result.result.win) {
					console.log(i, countTestBattle);
					return true;
				}
			}
			this.terminatеReason = I18N('NO_CHANCE_WIN') + countTestBattle;
			return false;
		}

		/**
		 * Starts a fight
		 *
		 * Начинает бой
		 */
		startBattle(path) {
			this.args.path = path;
			this.callStartBattle.name = this.actions[this.type].startBattle;
			this.callStartBattle.args = this.args
			const calls = [this.callStartBattle];
			return Send(JSON.stringify({ calls }));
		}

		cancelBattle(battle) {
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					const hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			fixBattle(battle.progress[0].attackers.heroes);
			fixBattle(battle.progress[0].defenders.heroes);
			return this.endBattle(battle);
		}

		/**
		 * Ends the fight
		 *
		 * Заканчивает бой
		 */
		endBattle(battle) {
			this.callEndBattle.name = this.actions[this.type].endBattle;
			this.callEndBattle.args.result = battle.result
			this.callEndBattle.args.progress = battle.progress
			const calls = [this.callEndBattle];
			return Send(JSON.stringify({ calls }));
		}

		/**
		 * Checks if you can get a buff
		 *
		 * Проверяет можно ли получить баф
		 */
		checkBuff(nodeInfo) {
			let id = null;
			let value = 0;
			for (const buffId in nodeInfo.buffs) {
				const buff = nodeInfo.buffs[buffId];
				if (buff.owner == null && buff.value > value) {
					id = buffId;
					value = buff.value;
				}
			}
			nodeInfo.buffs[id].owner = 'Я';
			return id;
		}

		/**
		 * Collects a buff
		 *
		 * Собирает баф
		 */
		async collectBuff(buff, path) {
			this.callCollectBuff.name = this.actions[this.type].collectBuff;
			this.callCollectBuff.args = { buff, path };
			const calls = [this.callCollectBuff];
			return Send(JSON.stringify({ calls }));
		}

		getNodeInfo(nodeId) {
			return this.nodes.find(node => node.id == nodeId);
		}

		errorHandling(error, data) {
			//console.error(error);
			let errorInfo = error.toString() + '\n';
			try {
				const errorStack = error.stack.split('\n');
				const endStack = errorStack.map(e => e.split('@')[0]).indexOf("testAdventure");
				errorInfo += errorStack.slice(0, endStack).join('\n');
			} catch (e) {
				errorInfo += error.stack;
			}
			if (data) {
				errorInfo += '\nData: ' + JSON.stringify(data);
			}
			copyText(errorInfo);
		}

		end() {
			isCancalBattle = true;
			setProgress(this.terminatеReason, true);
			console.log(this.terminatеReason);
			this.resolve();
		}
	}
	/**
	 * Passage of brawls
	 *
	 * Прохождение потасовок
	 */
	function testBrawls() {
		return new Promise((resolve, reject) => {
			const brawls = new executeBrawls(resolve, reject);
			brawls.start(brawlsPack);
		});
	}
	/**
	 * Passage of brawls
	 *
	 * Прохождение потасовок
	 */
	class executeBrawls {
		callBrawlQuestGetInfo = {
			name: "brawl_questGetInfo",
			args: {},
			ident: "brawl_questGetInfo"
		}
		callBrawlFindEnemies = {
			name: "brawl_findEnemies",
			args: {},
			ident: "brawl_findEnemies"
		}
		callBrawlQuestFarm = {
			name: "brawl_questFarm",
			args: {},
			ident: "brawl_questFarm"
		}
		callUserGetInfo = {
			name: "userGetInfo",
			args: {},
			ident: "userGetInfo"
		}

		stats = {
			win: 0,
			loss: 0,
			count: 0,
		}

		stage = {
			'3': 1,
			'7': 2,
			'12': 3,
		}

		constructor(resolve, reject) {
			this.resolve = resolve;
			this.reject = reject;
		}

		async start(heroes) {
			this.heroes = heroes;
			isCancalBattle = false;
			this.brawlInfo = await this.getBrawlInfo();

			if (!this.brawlInfo.attempts) {
				this.end(I18N('DONT_HAVE_LIVES'))
				return;
			}

			while (1) {
				if (!isBrawlsAutoStart) {
					this.end(I18N('BTN_CANCELED'))
					return;
				}

				const maxStage = this.brawlInfo.questInfo.stage;
				const stage = this.stage[maxStage];
				const progress = this.brawlInfo.questInfo.progress;

				setProgress(`${I18N('STAGE')} ${stage}: ${progress}/${maxStage}<br>${I18N('FIGHTS')}: ${this.stats.count}<br>${I18N('WINS')}: ${this.stats.win}<br>${I18N('LOSSES')}: ${this.stats.loss}<br>${I18N('STOP')}`, false, function () {
					isBrawlsAutoStart = false;
				});

				if (this.brawlInfo.questInfo.canFarm) {
					const result = await this.questFarm();
					console.log(result);
				}

				if (this.brawlInfo.questInfo.stage == 12 && this.brawlInfo.questInfo.progress == 12) {
					this.end(I18N('SUCCESS'))
					return;
				}

				const enemie = Object.values(this.brawlInfo.findEnemies).shift();

				const result = await this.battle(enemie.userId);
				this.brawlInfo = {
					questInfo: result[1].result.response,
					findEnemies: result[2].result.response,
				}
			}
		}

		async questFarm() {
			const calls = [this.callBrawlQuestFarm];
			const result = await Send(JSON.stringify({ calls }));
			return result.results[0].result.response;
		}

		async getBrawlInfo() {
			const data = await Send(JSON.stringify({
				calls: [
					this.callUserGetInfo,
					this.callBrawlQuestGetInfo,
					this.callBrawlFindEnemies,
				]
			}));

			let attempts = data.results[0].result.response.refillable.find(n => n.id == 48);
			return {
				attempts: attempts.amount,
				questInfo: data.results[1].result.response,
				findEnemies: data.results[2].result.response,
			}
		}

		/**
		 * Carrying out a fight
		 *
		 * Проведение боя
		 */
		async battle(userId) {
			this.stats.count++;
			const battle = await this.startBattle(userId, this.heroes);
			const result = await Calc(battle);
			console.log(result.result);
			if (result.result.win) {
				this.stats.win++;
				return await this.endBattle(result);
			}
			this.stats.loss++;
			return await this.cancelBattle(result);
		}

		/**
		 * Starts a fight
		 *
		 * Начинает бой
		 */
		async startBattle(userId, heroes) {
			const calls = [{
				name: "brawl_startBattle",
				args: {
					userId,
					heroes,
					favor: {},
				},
				ident: "brawl_startBattle"
			}];
			const result = await Send(JSON.stringify({ calls }));
			return result.results[0].result.response;
		}

		cancelBattle(battle) {
			const fixBattle = function (heroes) {
				for (const ids in heroes) {
					const hero = heroes[ids];
					hero.energy = random(1, 999);
					if (hero.hp > 0) {
						hero.hp = random(1, hero.hp);
					}
				}
			}
			fixBattle(battle.progress[0].attackers.heroes);
			fixBattle(battle.progress[0].defenders.heroes);
			return this.endBattle(battle);
		}

		/**
		 * Ends the fight
		 *
		 * Заканчивает бой
		 */
		async endBattle(battle) {
			battle.progress[0].attackers.input = ['auto', 0, 0, 'auto', 0, 0];
			const calls = [{
				name: "brawl_endBattle",
				args: {
					result: battle.result,
					progress: battle.progress
				},
				ident: "brawl_endBattle"
			},
			this.callBrawlQuestGetInfo,
			this.callBrawlFindEnemies,
			];
			const result = await Send(JSON.stringify({ calls }));
			return result.results;
		}

		end(endReason) {
			isCancalBattle = true;
			isBrawlsAutoStart = false;
			setProgress(endReason, true);
			console.log(endReason);
			this.resolve();
		}
	}
})();

/**
 * TODO:
 * Получение всех уровней при сборе всех наград (квест на титанит и на энку)
 * Добавить проверку правильности пути для приключения
 * Добивание на арене титанов
 * Сбор ежедневных календарных наград
 * Добавить в подземку проверку варианта когда одна пачка из 2х мертва
 */