diff --git a/HWHExtension-1.0.user.js b/HWHExtension-1.0.user.js new file mode 100644 index 0000000..b9bafb2 --- /dev/null +++ b/HWHExtension-1.0.user.js @@ -0,0 +1,1230 @@ +// ==UserScript== +// @name HWHExtension +// @name:en HWHExtension +// @name:ru HWHExtension +// @namespace HWHExtension +// @version 1.0 +// @description Extension for HeroWarsHelper script +// @description:en Extension for HeroWarsHelper script +// @description:ru Расширение для скрипта HeroWarsHelper +// @author ZingerY +// @license Copyright ZingerY +// @homepage https://zingery.ru/scripts/HWHExtension.user.js +// @downloadURL https://zingery.ru/scripts/HWHExtension.user.js +// @updateURL https://zingery.ru/scripts/HWHExtension.user.js +// @icon https://zingery.ru/scripts/VaultBoyIco16.ico +// @icon64 https://zingery.ru/scripts/VaultBoyIco64.png +// @match https://www.hero-wars.com/* +// @match https://apps-1701433570146040.apps.fbsbx.com/* +// @run-at document-start +// ==/UserScript== + +(function () { + +if (!this.HWHClasses) { + console.log('%cObject for extension not found', 'color: red'); + return; +} + +console.log('%cStart Extension ' + GM_info.script.name + ', v' + GM_info.script.version + ' by ' + GM_info.script.author, 'color: red'); +const { addExtentionName } = HWHFuncs; +addExtentionName(GM_info.script.name, GM_info.script.version, GM_info.script.author); + +const { + getInput, + setProgress, + hideProgress, + I18N, + send, + getTimer, + countdownTimer, + getUserInfo, + getSaveVal, + setSaveVal, + popup, + setIsCancalBattle, + random, +} = HWHFuncs; + +function executeDungeon(resolve, reject) { + let countPredictionCard = 0; + let dungeonActivity = 0; + let startDungeonActivity = 0; + let maxDungeonActivity = 150; + let limitDungeonActivity = 30180; + let countShowStats = 1; + //let fastMode = isChecked('fastMode'); + let end = false; + + let countTeam = []; + let timeDungeon = { + all: new Date().getTime(), + findAttack: 0, + attackNeutral: 0, + attackEarthOrFire: 0, + }; + + let titansStates = {}; + let bestBattle = {}; + + let teams = { + neutral: [], + water: [], + earth: [], + fire: [], + hero: [], + }; + + //тест + let talentMsg = ''; + let talentMsgReward = ''; + + let callsExecuteDungeon = { + calls: [ + { + name: 'dungeonGetInfo', + args: {}, + ident: 'dungeonGetInfo', + }, + { + name: 'teamGetAll', + args: {}, + ident: 'teamGetAll', + }, + { + name: 'teamGetFavor', + args: {}, + ident: 'teamGetFavor', + }, + { + name: 'clanGetInfo', + args: {}, + ident: 'clanGetInfo', + }, + { + name: 'inventoryGet', + args: {}, + ident: 'inventoryGet', + }, + ], + }; + + this.start = async function (titanit) { + //maxDungeonActivity = titanit > limitDungeonActivity ? limitDungeonActivity : titanit; + maxDungeonActivity = titanit || getInput('countTitanit'); + send(JSON.stringify(callsExecuteDungeon), startDungeon); + }; + + /** Получаем данные по подземелью */ + function startDungeon(e) { + stopDung = false; // стоп подземка + let res = e.results; + let dungeonGetInfo = res[0].result.response; + if (!dungeonGetInfo) { + endDungeon('noDungeon', res); + return; + } + console.log('Начинаем копать на фулл: ', new Date()); + let teamGetAll = res[1].result.response; + let teamGetFavor = res[2].result.response; + dungeonActivity = res[3].result.response.stat.todayDungeonActivity; + startDungeonActivity = res[3].result.response.stat.todayDungeonActivity; + countPredictionCard = res[4].result.response.consumable[81]; + titansStates = dungeonGetInfo.states.titans; + + teams.hero = { + favor: teamGetFavor.dungeon_hero, + heroes: teamGetAll.dungeon_hero.filter((id) => id < 6000), + teamNum: 0, + }; + let heroPet = teamGetAll.dungeon_hero.filter((id) => id >= 6000).pop(); + if (heroPet) { + teams.hero.pet = heroPet; + } + teams.neutral = getTitanTeam('neutral'); + teams.water = { + favor: {}, + heroes: getTitanTeam('water'), + teamNum: 0, + }; + teams.earth = { + favor: {}, + heroes: getTitanTeam('earth'), + teamNum: 0, + }; + teams.fire = { + favor: {}, + heroes: getTitanTeam('fire'), + teamNum: 0, + }; + + checkFloor(dungeonGetInfo); + } + + function getTitanTeam(type) { + switch (type) { + case 'neutral': + return [4023, 4022, 4012, 4021, 4011, 4010, 4020]; + case 'water': + return [4000, 4001, 4002, 4003].filter((e) => !titansStates[e]?.isDead); + case 'earth': + return [4020, 4022, 4021, 4023].filter((e) => !titansStates[e]?.isDead); + case 'fire': + return [4010, 4011, 4012, 4013].filter((e) => !titansStates[e]?.isDead); + } + } + + /** Создать копию объекта */ + function clone(a) { + return JSON.parse(JSON.stringify(a)); + } + + /** Находит стихию на этаже */ + function findElement(floor, element) { + for (let i in floor) { + if (floor[i].attackerType === element) { + return i; + } + } + return undefined; + } + + /** Проверяем этаж */ + async function checkFloor(dungeonInfo) { + if (!('floor' in dungeonInfo) || dungeonInfo.floor?.state == 2) { + saveProgress(); + return; + } + checkTalent(dungeonInfo); + // console.log(dungeonInfo, dungeonActivity); + maxDungeonActivity = getInput('countTitanit'); + setProgress(`${I18N('DUNGEON')}: ${I18N('TITANIT')} ${dungeonActivity}/${maxDungeonActivity} ${talentMsg}`); + //setProgress('Dungeon: Титанит ' + dungeonActivity + '/' + maxDungeonActivity); + if (dungeonActivity >= maxDungeonActivity) { + endDungeon('Стоп подземка,', 'набрано титанита: ' + dungeonActivity + '/' + maxDungeonActivity); + return; + } + let activity = dungeonActivity - startDungeonActivity; + titansStates = dungeonInfo.states.titans; + if (stopDung) { + endDungeon('Стоп подземка,', 'набрано титанита: ' + dungeonActivity + '/' + maxDungeonActivity); + return; + } + /*if (activity / 1000 > countShowStats) { + countShowStats++; + showStats(); + }*/ + bestBattle = {}; + let floorChoices = dungeonInfo.floor.userData; + if (floorChoices.length > 1) { + for (let element in teams) { + let teamNum = findElement(floorChoices, element); + if (!!teamNum) { + if (element == 'earth') { + teamNum = await chooseEarthOrFire(floorChoices); + if (teamNum < 0) { + endDungeon('Невозможно победить без потери Титана!', dungeonInfo); + return; + } + } + chooseElement(floorChoices[teamNum].attackerType, teamNum); + return; + } + } + } else { + chooseElement(floorChoices[0].attackerType, 0); + } + } + //тест черепахи + async function checkTalent(dungeonInfo) { + const talent = dungeonInfo.talent; + if (!talent) { + return; + } + const dungeonFloor = +dungeonInfo.floorNumber; + const talentFloor = +talent.floorRandValue; + let doorsAmount = 3 - talent.conditions.doorsAmount; + + if (dungeonFloor === talentFloor && (!doorsAmount || !talent.conditions?.farmedDoors[dungeonFloor])) { + const reward = await Send({ + calls: [ + { name: 'heroTalent_getReward', args: { talentType: 'tmntDungeonTalent', reroll: false }, ident: 'group_0_body' }, + { name: 'heroTalent_farmReward', args: { talentType: 'tmntDungeonTalent' }, ident: 'group_1_body' }, + ], + }).then((e) => e.results[0].result.response); + const type = Object.keys(reward).pop(); + const itemId = Object.keys(reward[type]).pop(); + const count = reward[type][itemId]; + const itemName = cheats.translate(`LIB_${type.toUpperCase()}_NAME_${itemId}`); + talentMsgReward += `
${count} ${itemName}`; + doorsAmount++; + } + talentMsg = `
TMNT Talent: ${doorsAmount}/3 ${talentMsgReward}
`; + } + + /** Выбираем огнем или землей атаковать */ + async function chooseEarthOrFire(floorChoices) { + bestBattle.recovery = -11; + let selectedTeamNum = -1; + for (let attempt = 0; selectedTeamNum < 0 && attempt < 4; attempt++) { + for (let teamNum in floorChoices) { + let attackerType = floorChoices[teamNum].attackerType; + selectedTeamNum = await attemptAttackEarthOrFire(teamNum, attackerType, attempt); + } + } + console.log('Выбор команды огня или земли: ', selectedTeamNum < 0 ? 'не сделан' : floorChoices[selectedTeamNum].attackerType); + return selectedTeamNum; + } + + /** Попытка атаки землей и огнем */ + async function attemptAttackEarthOrFire(teamNum, attackerType, attempt) { + let start = new Date(); + let team = clone(teams[attackerType]); + let startIndex = team.heroes.length + attempt - 4; + if (startIndex >= 0) { + team.heroes = team.heroes.slice(startIndex); + let recovery = await getBestRecovery(teamNum, attackerType, team, 25); + if (recovery > bestBattle.recovery) { + bestBattle.recovery = recovery; + bestBattle.selectedTeamNum = teamNum; + bestBattle.team = team; + } + } + let workTime = new Date().getTime() - start.getTime(); + timeDungeon.attackEarthOrFire += workTime; + if (bestBattle.recovery < -10) { + return -1; + } + return bestBattle.selectedTeamNum; + } + + /** Выбираем стихию для атаки */ + async function chooseElement(attackerType, teamNum) { + let result; + switch (attackerType) { + case 'hero': + case 'water': + result = await startBattle(teamNum, attackerType, teams[attackerType]); + break; + case 'earth': + case 'fire': + result = await attackEarthOrFire(teamNum, attackerType); + break; + case 'neutral': + result = await attackNeutral(teamNum, attackerType); + } + if (!!result && attackerType != 'hero') { + let recovery = (!!!bestBattle.recovery ? 10 * getRecovery(result) : bestBattle.recovery) * 100; + let titans = result.progress[0].attackers.heroes; + console.log('Проведен бой: ' + attackerType + ', recovery = ' + (recovery > 0 ? '+' : '') + Math.round(recovery) + '% \r\n', titans); + } + endBattle(result); + } + + /** Атакуем Землей или Огнем */ + async function attackEarthOrFire(teamNum, attackerType) { + if (!!!bestBattle.recovery) { + bestBattle.recovery = -11; + let selectedTeamNum = -1; + for (let attempt = 0; selectedTeamNum < 0 && attempt < 4; attempt++) { + selectedTeamNum = await attemptAttackEarthOrFire(teamNum, attackerType, attempt); + } + if (selectedTeamNum < 0) { + endDungeon('Невозможно победить без потери Титана!', attackerType); + return; + } + } + return findAttack(teamNum, attackerType, bestBattle.team); + } + + /** Находим подходящий результат для атаки */ + async function findAttack(teamNum, attackerType, team) { + let start = new Date(); + let recovery = -1000; + let iterations = 0; + let result; + let correction = 0.01; + for (let needRecovery = bestBattle.recovery; recovery < needRecovery; needRecovery -= correction, iterations++) { + result = await startBattle(teamNum, attackerType, team); + recovery = getRecovery(result); + } + bestBattle.recovery = recovery; + let workTime = new Date().getTime() - start.getTime(); + timeDungeon.findAttack += workTime; + return result; + } + + /** Атакуем Нейтральной командой */ + async function attackNeutral(teamNum, attackerType) { + let start = new Date(); + let factors = calcFactor(); + bestBattle.recovery = -0.2; + await findBestBattleNeutral(teamNum, attackerType, factors, true); + if (bestBattle.recovery < 0 || (bestBattle.recovery < 0.2 && factors[0].value < 0.5)) { + let recovery = 100 * bestBattle.recovery; + console.log( + 'Не удалось найти удачный бой в быстром режиме: ' + + attackerType + + ', recovery = ' + + (recovery > 0 ? '+' : '') + + Math.round(recovery) + + '% \r\n', + bestBattle.attackers + ); + await findBestBattleNeutral(teamNum, attackerType, factors, false); + } + let workTime = new Date().getTime() - start.getTime(); + timeDungeon.attackNeutral += workTime; + if (!!bestBattle.attackers) { + let team = getTeam(bestBattle.attackers); + return findAttack(teamNum, attackerType, team); + } + endDungeon('Не удалось найти удачный бой!', attackerType); + return undefined; + } + + /** Находит лучшую нейтральную команду */ + async function findBestBattleNeutral(teamNum, attackerType, factors, mode) { + let countFactors = factors.length < 4 ? factors.length : 4; + let aradgi = !titansStates['4013']?.isDead; + let edem = !titansStates['4023']?.isDead; + let dark = [4032, 4033].filter((e) => !titansStates[e]?.isDead); + let light = [4042].filter((e) => !titansStates[e]?.isDead); + let actions = []; + if (mode) { + for (let i = 0; i < countFactors; i++) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(factors[i].id))); + } + if (countFactors > 1) { + let firstId = factors[0].id; + let secondId = factors[1].id; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4001, secondId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4002, secondId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4003, secondId))); + } + if (aradgi) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(4013))); + if (countFactors > 0) { + let firstId = factors[0].id; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4000, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4001, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4002, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4003, 4013))); + } + if (edem) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(4023, 4000, 4013))); + } + } + } else { + if (mode) { + for (let i = 0; i < factors.length; i++) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(factors[i].id))); + } + } else { + countFactors = factors.length < 2 ? factors.length : 2; + } + for (let i = 0; i < countFactors; i++) { + let mainId = factors[i].id; + if (aradgi && (mode || i > 0)) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4000, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4001, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4002, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4003, 4013))); + } + for (let i = 0; i < dark.length; i++) { + let darkId = dark[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4001, darkId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4002, darkId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4003, darkId))); + } + for (let i = 0; i < light.length; i++) { + let lightId = light[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4001, lightId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4002, lightId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4003, lightId))); + } + let isFull = mode || i > 0; + for (let j = isFull ? i + 1 : 2; j < factors.length; j++) { + let extraId = factors[j].id; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4000, extraId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4001, extraId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(mainId, 4002, extraId))); + } + } + if (aradgi) { + if (mode) { + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(4013))); + } + for (let i = 0; i < dark.length; i++) { + let darkId = dark[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(darkId, 4001, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(darkId, 4002, 4013))); + } + for (let i = 0; i < light.length; i++) { + let lightId = light[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(lightId, 4001, 4013))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(lightId, 4002, 4013))); + } + } + for (let i = 0; i < dark.length; i++) { + let firstId = dark[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId))); + for (let j = i + 1; j < dark.length; j++) { + let secondId = dark[j]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4001, secondId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4002, secondId))); + } + } + for (let i = 0; i < light.length; i++) { + let firstId = light[i]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId))); + for (let j = i + 1; j < light.length; j++) { + let secondId = light[j]; + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4001, secondId))); + actions.push(startBattle(teamNum, attackerType, getNeutralTeam(firstId, 4002, secondId))); + } + } + } + for (let result of await Promise.all(actions)) { + let recovery = getRecovery(result); + if (recovery > bestBattle.recovery) { + bestBattle.recovery = recovery; + bestBattle.attackers = result.progress[0].attackers.heroes; + } + } + } + + /** Получаем нейтральную команду */ + function getNeutralTeam(id, swapId, addId) { + let neutralTeam = clone(teams.water); + let neutral = neutralTeam.heroes; + if (neutral.length == 4) { + if (!!swapId) { + for (let i in neutral) { + if (neutral[i] == swapId) { + neutral[i] = addId; + } + } + } + } else if (!!addId) { + neutral.push(addId); + } + neutral.push(id); + return neutralTeam; + } + + /** Получить команду титанов */ + function getTeam(titans) { + return { + favor: {}, + heroes: Object.keys(titans).map((id) => parseInt(id)), + teamNum: 0, + }; + } + + /** Вычисляем фактор боеготовности титанов */ + function calcFactor() { + let neutral = teams.neutral; + let factors = []; + for (let i in neutral) { + let titanId = neutral[i]; + let titan = titansStates[titanId]; + let factor = !!titan ? titan.hp / titan.maxHp + titan.energy / 10000.0 : 1; + if (factor > 0) { + factors.push({ id: titanId, value: factor }); + } + } + factors.sort(function (a, b) { + return a.value - b.value; + }); + return factors; + } + + /** Возвращает наилучший результат из нескольких боев */ + async function getBestRecovery(teamNum, attackerType, team, countBattle) { + let bestRecovery = -1000; + let actions = []; + for (let i = 0; i < countBattle; i++) { + actions.push(startBattle(teamNum, attackerType, team)); + } + for (let result of await Promise.all(actions)) { + let recovery = getRecovery(result); + if (recovery > bestRecovery) { + bestRecovery = recovery; + } + } + return bestRecovery; + } + + /** Возвращает разницу в здоровье атакующей команды после и до битвы и проверяет здоровье титанов на необходимый минимум*/ + function getRecovery(result) { + if (result.result.stars < 3) { + return -100; + } + let beforeSumFactor = 0; + let afterSumFactor = 0; + let beforeTitans = result.battleData.attackers; + let afterTitans = result.progress[0].attackers.heroes; + for (let i in afterTitans) { + let titan = afterTitans[i]; + let percentHP = titan.hp / beforeTitans[i].hp; + let energy = titan.energy; + let factor = checkTitan(i, energy, percentHP) ? getFactor(i, energy, percentHP) : -100; + afterSumFactor += factor; + } + for (let i in beforeTitans) { + let titan = beforeTitans[i]; + let state = titan.state; + beforeSumFactor += !!state ? getFactor(i, state.energy, state.hp / titan.hp) : 1; + } + return afterSumFactor - beforeSumFactor; + } + + /** Возвращает состояние титана*/ + function getFactor(id, energy, percentHP) { + let elemantId = id.slice(2, 3); + let isEarthOrFire = elemantId == '1' || elemantId == '2'; + let energyBonus = id == '4020' && energy == 1000 ? 0.1 : energy / 20000.0; + let factor = percentHP + energyBonus; + return isEarthOrFire ? factor : factor / 10; + } + + /** Проверяет состояние титана*/ + function checkTitan(id, energy, percentHP) { + switch (id) { + case '4020': + return percentHP > 0.25 || (energy == 1000 && percentHP > 0.05); + break; + case '4010': + return percentHP + energy / 2000.0 > 0.63; + break; + case '4000': + return percentHP > 0.62 || (energy < 1000 && ((percentHP > 0.45 && energy >= 400) || (percentHP > 0.3 && energy >= 670))); + } + return true; + } + + /** Начинаем бой */ + function startBattle(teamNum, attackerType, args) { + return new Promise(function (resolve, reject) { + args.teamNum = teamNum; + let startBattleCall = { + calls: [ + { + name: 'dungeonStartBattle', + args, + ident: 'body', + }, + ], + }; + send(JSON.stringify(startBattleCall), resultBattle, { + resolve, + teamNum, + attackerType, + }); + }); + } + + /** Возращает результат боя в промис */ + /*function resultBattle(resultBattles, args) { + if (!!resultBattles && !!resultBattles.results) { + let battleData = resultBattles.results[0].result.response; + let 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); + }); + } else { + endDungeon('Потеряна связь с сервером игры!', 'break'); + } + }*/ + 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); + }); + } + + /** Заканчиваем бой */ + + //// + async function endBattle(battleInfo) { + if (!!battleInfo) { + const args = { + result: battleInfo.result, + progress: battleInfo.progress, + }; + if (battleInfo.result.stars < 3) { + endDungeon('Герой или Титан мог погибнуть в бою!', battleInfo); + return; + } + if (countPredictionCard > 0) { + args.isRaid = true; + countPredictionCard--; + } else { + const timer = getTimer(battleInfo.battleTime); + console.log(timer); + await countdownTimer(timer, `${I18N('DUNGEON')}: ${I18N('TITANIT')} ${dungeonActivity}/${maxDungeonActivity} ${talentMsg}`); + } + const calls = [ + { + name: 'dungeonEndBattle', + args, + ident: 'body', + }, + ]; + lastDungeonBattleData = null; + send(JSON.stringify({ calls }), resultEndBattle); + } else { + endDungeon('dungeonEndBattle win: false\n', battleInfo); + } + } + /** Получаем и обрабатываем результаты боя */ + function resultEndBattle(e) { + if (!!e && !!e.results) { + let battleResult = e.results[0].result.response; + if ('error' in battleResult) { + endDungeon('errorBattleResult', battleResult); + return; + } + let dungeonGetInfo = battleResult.dungeon ?? battleResult; + dungeonActivity += battleResult.reward.dungeonActivity ?? 0; + checkFloor(dungeonGetInfo); + } else { + endDungeon('Потеряна связь с сервером игры!', 'break'); + } + } + + /** Добавить команду титанов в общий список команд */ + function addTeam(team) { + for (let i in countTeam) { + if (equalsTeam(countTeam[i].team, team)) { + countTeam[i].count++; + return; + } + } + countTeam.push({ team: team, count: 1 }); + } + + /** Сравнить команды на равенство */ + function equalsTeam(team1, team2) { + if (team1.length == team2.length) { + for (let i in team1) { + if (team1[i] != team2[i]) { + return false; + } + } + return true; + } + return false; + } + + function saveProgress() { + let saveProgressCall = { + calls: [ + { + name: 'dungeonSaveProgress', + args: {}, + ident: 'body', + }, + ], + }; + send(JSON.stringify(saveProgressCall), resultEndBattle); + } + + /** Выводит статистику прохождения подземелья */ + function showStats() { + let activity = dungeonActivity - startDungeonActivity; + let workTime = clone(timeDungeon); + workTime.all = new Date().getTime() - workTime.all; + for (let i in workTime) { + workTime[i] = Math.round(workTime[i] / 1000); + } + countTeam.sort(function (a, b) { + return b.count - a.count; + }); + console.log(titansStates); + console.log('Собрано титанита: ', activity); + console.log('Скорость сбора: ' + Math.round((3600 * activity) / workTime.all) + ' титанита/час'); + console.log('Время раскопок: '); + for (let i in workTime) { + let timeNow = workTime[i]; + console.log( + i + ': ', + Math.round(timeNow / 3600) + ' ч. ' + Math.round((timeNow % 3600) / 60) + ' мин. ' + (timeNow % 60) + ' сек.' + ); + } + console.log('Частота использования команд: '); + for (let i in countTeam) { + let teams = countTeam[i]; + console.log(teams.team + ': ', teams.count); + } + } + + /** Заканчиваем копать подземелье */ + function endDungeon(reason, info) { + if (!end) { + end = true; + console.log(reason, info); + showStats(); + if (info == 'break') { + setProgress( + 'Dungeon stoped: Титанит ' + dungeonActivity + '/' + maxDungeonActivity + '\r\nПотеряна связь с сервером игры!', + false, + hideProgress + ); + } else { + setProgress('Dungeon completed: Титанит ' + dungeonActivity + '/' + maxDungeonActivity, false, hideProgress); + } + setTimeout(cheats.refreshGame, 1000); + resolve(); + } + } +} + +this.HWHClasses.executeDungeon = executeDungeon; + +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.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 oldVal = getSaveVal('adventurePath', ''); + const keyPath = `adventurePath:${this.mapIdent}`; + const answer = await popup.confirm(I18N('ENTER_THE_PATH'), [ + { + msg: I18N('START_ADVENTURE'), + placeholder: '1,2,3,4,5,6', + isInput: true, + default: getSaveVal(keyPath, oldVal), + }, + { + msg: I18N('BTN_CANCEL'), + result: false, + isCancel: true, + }, + ]); + 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; + } + } + + if (!this.checkPath(path)) { + return false; + } + setSaveVal(keyPath, answer); + return path; + } + + checkPath(path) { + for (let i = 0; i < path.length - 1; i++) { + const currentPoint = path[i]; + const nextPoint = path[i + 1]; + + const isValidPath = this.paths.some( + (p) => (p.from_id === currentPoint && p.to_id === nextPoint) || (p.from_id === nextPoint && p.to_id === currentPoint) + ); + + if (!isValidPath) { + this.terminatеReason = I18N('INCORRECT_WAY', { + from: currentPoint, + to: nextPoint, + }); + return false; + } + } + + return true; + } + + async 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 userInfo = getUserInfo(); + const advUserInfo = this.advInfo.users[userInfo.id]; + this.turnsLeft = advUserInfo.turnsLeft; + this.currentNode = advUserInfo.currentNode; + this.nodes = this.advInfo.nodes; + this.paths = this.advInfo.paths; + this.mapIdent = this.advInfo.mapIdent; + + this.path = await this.getPath(); + if (!this.path) { + return this.end(); + } + + 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 + * + * Отключаем штатную отменую боя + */ + setIsCancalBattle(false); + if (await this.battle(toPath)) { + this.turnsLeft--; + toPath.splice(0, toPath.indexOf(nodeId)); + nodeInfo.state = 'empty'; + setIsCancalBattle(true); + continue; + } + setIsCancalBattle(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; + let result = await Calc(battle); + + if (!result.result.win) { + const { WinFixBattle } = HWHClasses; + const cloneBattle = structuredClone(battle); + const bFix = new WinFixBattle(cloneBattle); + const endTime = Date.now() + 1e4; // 10 sec + const fixResult = await bFix.start(endTime, Infinity); + console.log(fixResult); + if (fixResult.value) { + result = fixResult; + } + } + + 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() { + setIsCancalBattle(true); + setProgress(this.terminatеReason, true); + console.log(this.terminatеReason); + this.resolve(); + } +} + +this.HWHClasses.executeAdventure = executeAdventure; + +})();