// ==UserScript== // @name HeroWarsHelper // @name:en HeroWarsHelper // @name:ru HeroWarsHelper // @namespace HeroWarsHelper // @version 2.099 // @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 // @downloadURL none // ==/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', SECRET_WEALTH_CHECKBOX: 'Automatic purchase in the store "Secret Wealth" when entering the game', /* 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', SECRET_WEALTH: 'Secret Wealth', SECRET_WEALTH_TITLE: 'Buy something in the store "Secret Wealth"', /* Misc */ BOTTOM_URLS: 'tg', 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: '

Using this feature may result in a ban.

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: '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 adventure using commas or dashes', 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
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', SECRET_WEALTH_ALREADY: 'Secret Wealth: Item for Pet Potions already purchased', SECRET_WEALTH_NOT_ENOUGH: 'Secret Wealth: Not Enough Pet Potion, You Have {available}, Need {need}', SECRET_WEALTH_UPGRADE_NEW_PET: 'Secret Wealth: After purchasing the Pet Potion, it will not be enough to upgrade a new pet', SECRET_WEALTH_PURCHASED: 'Secret wealth: Purchased {count} {name}', SECRET_WEALTH_CANCELED: 'Secret Wealth: Purchase Canceled', SECRET_WEALTH_BUY: 'You have {available} Pet Potion.
Do you want to buy {countBuy} {name} for {price} Pet Potion?', }, 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: 'Автоматическое получение правильных ответов на вопросы викторины', SECRET_WEALTH_CHECKBOX: 'Автоматическая покупка в магазине "Тайное Богатство" при заходе в игру', /* Поля ввода */ 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: 'Быстрый переход к Войне гильдий', SECRET_WEALTH: 'Тайное богатство', SECRET_WEALTH_TITLE: 'Купить что-то в магазине "Тайное богатство"', /* Разное */ BOTTOM_URLS: 'tg vk', 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: '

Использование этой функции может привести к бану.

Продолжить?', 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: 'Призошли ошибка в процессе прохождения боя
Скопировать ошибку в буфер обмена?', 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: 'У Вас нет жизней', SECRET_WEALTH_ALREADY: 'Тайное богатство: товар за Зелья питомцев уже куплен', SECRET_WEALTH_NOT_ENOUGH: 'Тайное богатство: Не достаточно Зелье Питомца, у Вас {available}, нужно {need}', SECRET_WEALTH_UPGRADE_NEW_PET: 'Тайное богатство: После покупки Зелье Питомца будет не достаточно для прокачки нового питомца', SECRET_WEALTH_PURCHASED: 'Тайное богатство: Куплено {count} {name}', SECRET_WEALTH_CANCELED: 'Тайное богатство: покупка отменена', SECRET_WEALTH_BUY: 'У вас {available} Зелье Питомца.
Вы хотите купить {countBuy} {name} за {price} Зелье Питомца?', } } 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, replace) { if (constant && constant in i18nLangData[selectLang]) { const result = i18nLangData[selectLang][constant]; if (replace) { return result.sprintf(replace); } return result; } return `% ${constant} %`; }; String.prototype.sprintf = String.prototype.sprintf || function () { "use strict"; var str = this.toString(); if (arguments.length) { var t = typeof arguments[0]; var key; var args = ("string" === t || "number" === t) ? Array.prototype.slice.call(arguments) : arguments[0]; for (key in args) { str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]); } } return str; }; /** * 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 }, secretWealth: { label: I18N('SECRET_WEALTH'), cbox: null, title: I18N('SECRET_WEALTH_CHECKBOX'), 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) { if (!(checkBox in checkboxes)) { return false; } 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, }, secretWealth: { name: I18N('SECRET_WEALTH'), title: I18N('SECRET_WEALTH_TITLE'), func: buyWithPetExperience, }, } /** * 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 = ''; /** * Data for the last battle in the dungeon * (Fix endless cards) * * Данные для последнего боя в подземке * (Исправление бесконечных карт) */ let lastDungeonBattleData = null; /** * 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); await lib.load(cheats.libGame); addControls(); addControlButtons(); addBottomUrls(); if (isChecked('sendExpedition')) { checkExpedition(); } checkSendGifts(); getAutoGifts(); cheats.activateHacks(); justInfo(); if (isChecked('dailyQuests')) { testDailyQuests(); } if (isChecked('secretWealth')) { buyWithPetExperienceAuto(); } } /** * 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 (isChecked('endlessCards') && call.name == 'dungeonEndBattle') { if (call.args.isRaid) { delete call.args.isRaid; } /** * Fix endless cards * Исправление бесконечных карт */ const lastBattle = lastDungeonBattleData; const attackers = call.args.progress[0].attackers; if (attackers.input.length === 0 && !call.args.isRaid) { //if (lastBattle.type == 'dungeon_titan') { lastBattle.progress = [{ attackers: { input: ["auto", 0, 0, "auto", 0, 0] } }]; const result = await Calc(lastBattle); call.args.progress = result.progress; call.args.result = result.result; // } else { // attackers.input = ["auto", 0, 0, "auto", 0, 0]; // } } 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) { original.setRequestHeader.call(this, 'X-Auth-Signature', signature); } } 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 1 * Скрываем предложения доната 1 */ 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 2 * Скрываем предложения доната 2 */ if (getSaveVal('noOfferDonat') && (call.ident == callsIdent['offerGetAll'] || call.ident == callsIdent['specialOffer_getAll'])) { let offers = call.result.response; if (offers) { call.result.response = offers.filter(e => !['addBilling', 'bundleCarousel'].includes(e.type)); isChange = true; } } /** * Hiding donation offers 3 * Скрываем предложения доната 3 */ if (call.result?.bundleUpdate) { delete call.result.bundleUpdate; 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['adventureSolo_turnStartBattle'] || 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; } } /** * Sum the result of opening Pet Eggs * Суммирование результата открытия яиц питомцев */ if (isChecked('countControl') && call.ident == callsIdent['pet_chestOpen']) { const rewards = call.result.response.rewards; rewards.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]; } } } }); call.result.response.rewards = [allReward]; isChange = true; } /** * 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); } } /** * Dungeon recalculation (fix endless cards) * Прерасчет подземки (исправление бесконечных карт) */ if (isChecked('endlessCards') && call.ident == callsIdent['dungeonStartBattle']) { lastDungeonBattleData = call.result.response; } /** * 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": return "get_clanPvp"; case "dungeon_titan": case "titan_tower": return "get_titan"; case "tower": return "get_tower"; case "clan_dungeon": case "pve": return "get_pve"; case "pvp_manual": return "get_pvpManual"; case "grand": case "arena": 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<>>32-n,u=Array(r.length>>2),h=0;h>5]|=(255&r.charCodeAt(h/8))<>5]|=128<>>9<<4)]=len;var l=1732584193,i=-271733879,g=-1732584194,v=271733878;for(h=0;h>5]>>>h%32&255);var S="0123456789abcdef",j="";for(h=0;h>>4&15)+S.charAt(15&u);return j} /** * 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); } 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); } } /** * 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; } }); /** * Game Library * * Игровая библиотека */ class Library { defaultLibUrl = 'https://heroesru-a.akamaihd.net/vk/v1008/lib/lib.json'; constructor() { if (!Library.instance) { Library.instance = this; } return Library.instance; } async load(data) { if (data) { this.data = data; return; } try { this.data = await fetch(this.defaultLibUrl).then(e => e.json()) } catch (error) { console.error('Не удалось загрузить библиотеку') } } getData(id) { return this.data[id]; } } this.lib = new Library(); /** * 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() } } /** * 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; if (attackerType == 'neutral') { const titans = titanGetAll.filter(e => !titansStates[e.id]?.isDead) teams[attackerType].heroes = titans.sort((a, b) => b.power - a.power).slice(0, 5).map(e => e.id); } const titans = fixTitanTeam(teams[attackerType]); if (!titans.heroes.length) { continue; } promises.push(startBattle(teamNum, attackerType)); } if (!promises.length) { endDungeon('endDungeon', 'All Dead'); return; } 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; if (attackerType == 'neutral') { const titans = titanGetAll.filter(e => !titansStates[e.id]?.isDead) args.heroes = titans.sort((a, b) => b.power - a.power).slice(0, 5).map(e => e.id); } 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"; } battleData.progress = [{ attackers: { input: ["auto", 0, 0, "auto", 0, 0] } }]; 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.warn(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}
${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(); } } function hackGame() { self = this; selfGame = null; bindId = 1e9; this.libGame = null; /** * 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(!!battleData.progress, !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 = new 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(); } /** * Go to BrawlShop * * Переместиться в BrawlShop */ this.goBrawlShop = () => { const instance = getFnP(selfGame["game.model.GameModel"], 'get_instance') const P_36 = getProtoFn(selfGame["game.model.user.Player"], 36); const PSD_0 = getProtoFn(selfGame["game.model.user.shop.PlayerShopData"], 0); const IM_0 = getProtoFn(selfGame["haxe.ds.IntMap"], 0); const PSDE_4 = getProtoFn(selfGame["game.model.user.shop.PlayerShopDataEntry"], 4); const player = selfGame["game.model.GameModel"][instance]().A; const shop = player[P_36][PSD_0][IM_0][1038][PSDE_4]; const shopPopup = new selfGame["game.mechanics.brawl.mediator.BrawlShopPopupMediator"](player, shop) shopPopup.open(new selfGame["game.mediator.gui.popup.PopupStashEventParams"]) } /** * Game library availability tracker * * Отслеживание доступности игровой библиотеки */ function checkLibLoad() { timeout = setTimeout(() => { if (Game.GameModel) { changeLib(); } else { checkLibLoad(); } }, 100) } /** * Game library data spoofing * * Подмена данных игровой библиотеки */ function changeLib() { console.log('lib connect'); const originalStartFunc = Game.GameModel.prototype.start; Game.GameModel.prototype.start = function (a, b, c) { self.libGame = b.raw; try { b.raw.shop[26].requirements = null; } catch (e) { console.warn(e); } originalStartFunc.call(this, a, b, c); } } /** * Returns the value of a language constant * * Возвращает значение языковой константы * @param {*} langConst language constant // языковая константа * @returns */ this.translate = function (langConst) { return Game.Translate.translate(langConst); } connectGame(); checkLibLoad(); } /** * 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(); }); }); }) } /** * 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')}
${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 + `
${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 + `
${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(`
${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}`); } }) } /** * 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(' ' + `${portalSphere.amount}
${I18N('GUILD_WAR')}: ${clanWarMyTries}`, true); resolve(); }); } async function buyWithPetExperience() { const itemLib = lib.getData('inventoryItem'); const result = await Send('{"calls":[{"name":"inventoryGet","args":{},"ident":"inventoryGet"},{"name":"shopGet","args":{"shopId":"26"},"ident":"shopGet"}]}').then(e => e.results.map(n => n.result.response)); const inventory = result[0]; const shop = result[1]; const slot = shop.slots[2]; const currentCount = inventory.consumable[85]; const price = slot.cost.consumable[85]; const typeBuyItem = Object.keys(slot.reward).pop(); const itemIdBuyItem = Object.keys(slot.reward[typeBuyItem]).pop(); const countBuyItem = slot.reward[typeBuyItem][itemIdBuyItem]; const itemName = cheats.translate(`LIB_${typeBuyItem.toUpperCase()}_NAME_${itemIdBuyItem}`); if (slot.bought) { await popup.confirm(I18N('SECRET_WEALTH_ALREADY'), [ { msg: 'Ok', result: true }, ]); return; } const purchaseMsg = I18N('SECRET_WEALTH_BUY', { available: currentCount, countBuy: countBuyItem, name: itemName, price }) const answer = await popup.confirm(purchaseMsg, [ { msg: I18N('BTN_NO'), result: false }, { msg: I18N('BTN_YES'), result: true }, ]); if (!answer) { setProgress(I18N('SECRET_WEALTH_CANCELED'), true); return; } if (currentCount < price) { const msg = I18N('SECRET_WEALTH_NOT_ENOUGH', { available: currentCount, need: price }); await popup.confirm(msg, [ { msg: 'Ok', result: true }, ]); return; } const calls = [{ name: "shopBuy", args: { shopId: 26, slot: 2, cost: slot.cost, reward: slot.reward }, ident: "body" }]; const bought = await Send(JSON.stringify({ calls })).then(e => e.results[0].result.response); const type = Object.keys(bought).pop(); const itemId = Object.keys(bought[type]).pop(); const count = bought[type][itemId]; const resultMsg = I18N('SECRET_WEALTH_PURCHASED', { count, name: itemName }); await popup.confirm(resultMsg, [ { msg: 'Ok', result: true }, ]); } async function buyWithPetExperienceAuto() { const itemLib = lib.getData('inventoryItem'); const minCount = 450551; const result = await Send('{"calls":[{"name":"inventoryGet","args":{},"ident":"inventoryGet"},{"name":"shopGet","args":{"shopId":"26"},"ident":"shopGet"}]}').then(e => e.results.map(n => n.result.response)); const inventory = result[0]; const shop = result[1]; const slot = shop.slots[2]; const currentCount = inventory.consumable[85]; const price = slot.cost.consumable[85]; if (slot.bought) { console.log(I18N('SECRET_WEALTH_ALREADY')); setProgress(I18N('SECRET_WEALTH_ALREADY'), true); return; } if (currentCount < price) { const msg = I18N('SECRET_WEALTH_NOT_ENOUGH', { available: currentCount, need: price }); console.log(msg); setProgress(msg, true); return; } if ((currentCount - price) < minCount) { console.log(I18N('SECRET_WEALTH_UPGRADE_NEW_PET')); setProgress(I18N('SECRET_WEALTH_UPGRADE_NEW_PET'), true); return; } const calls = [{ name: "shopBuy", args: { shopId: 26, slot: 2, cost: slot.cost, reward: slot.reward }, ident: "body" }]; const bought = await Send(JSON.stringify({ calls })).then(e => e.results[0].result.response); const type = Object.keys(bought).pop(); const itemId = Object.keys(bought[type]).pop(); const count = bought[type][itemId]; const itemName = itemLib[type][itemId].label; const resultMsg = I18N('SECRET_WEALTH_PURCHASED', { count, name: itemName }); console.log(resultMsg, bought); setProgress(resultMsg, true); } /** * 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(); } } /** * 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')}:` + `
${I18N('MINIMUM')}: ` + minDamage.toLocaleString() + `
${I18N('MAXIMUM')}: ` + maxDamage.toLocaleString() + `
${I18N('AVERAGE')}: ` + avgDamage.toLocaleString() /*+ '
Поиск урона больше чем ' + 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') + `
${I18N('ROUND_STAT')} ${results.length} ${I18N('BATTLE')}:` + `
${I18N('MINIMUM')}: ` + minCoeff.toLocaleString() + `
${I18N('MAXIMUM')}: ` + maxCoeff.toLocaleString() + `
${I18N('AVERAGE')}: ` + avgCoeff.toLocaleString() + `
${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(); } } 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}
${I18N('PERFORMED')}!`); await this.functions[task.name](); setProgress(`${task.label}
${I18N('DONE')}!`); } catch (error) { if (await popup.confirm(`${I18N('ERRORS_OCCURRES')}:
${task.label}
${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) { 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; if (this.currentNode == 1 && this.path[0] != 1) { this.path.unshift(1); } 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}
${I18N('FIGHTS')}: ${this.stats.count}
${I18N('WINS')}: ${this.stats.win}
${I18N('LOSSES')}: ${this.stats.loss}
${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х мертва * Кнопку Турнир стихий красить в красный цвет если не дошел до 7 этапа * добавить отображение урона на авто перед боем для босса Асгарда * Добавить ввод количества для рейдов в кампании * Удалить progress боя при прерасчете реплеев */