diff --git a/README.md b/README.md index 7e12f26..38aa6b3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [ ] Автоматический скан карты - [ ] Автоматический скан статистики других игроков - [ ] Автоматическая отправка ресурсов на другие учетные записи -- [ ] Автоматическое обучение войск +- [x] Автоматическое обучение войск - [ ] Автоматическое размещение ставок на аукционе - [ ] Автоматический запуск празднований - [ ] Сканирование собственных деревень для отображения ресурсов и информации в одном месте diff --git a/src/Action/SendOnAdventureAction.ts b/src/Action/SendOnAdventureAction.ts index 9bd2013..8c5e6e9 100644 --- a/src/Action/SendOnAdventureAction.ts +++ b/src/Action/SendOnAdventureAction.ts @@ -4,8 +4,14 @@ import { Task } from '../Storage/TaskQueue'; import { trimPrefix } from '../utils'; import { AbortTaskError } from '../Errors'; -const HARD = 0; -const NORMAL = 3; +const CONFIG = [ + { level: 0, health: 60 }, + { level: 1, health: 50 }, + { level: 2, health: 40 }, + { level: 3, health: 30 }, + { level: 4, health: 30 }, + { level: 5, health: 30 }, +]; interface Adventure { el: HTMLElement; @@ -28,19 +34,20 @@ export class SendOnAdventureAction extends ActionController { console.log('HERO', hero); if (easiest && hero.health) { - if (easiest.level === NORMAL && hero.health >= 30) { - return jQuery(easiest.el) - .find('td.goTo .gotoAdventure') - .trigger('click'); - } - if (easiest.level === HARD && hero.health >= 50) { - return jQuery(easiest.el) + this.checkConfig(easiest, Number(hero.health)); + } + + throw new AbortTaskError(task.id, 'No suitable adventure'); + } + + private checkConfig(adventure: Adventure, health: number) { + for (let conf of CONFIG) { + if (adventure.level === conf.level && health >= conf.health) { + return jQuery(adventure.el) .find('td.goTo .gotoAdventure') .trigger('click'); } } - - throw new AbortTaskError(task.id, 'No suitable adventure'); } private findAdventures(): Array { diff --git a/src/Action/TrainTrooperAction.ts b/src/Action/TrainTrooperAction.ts new file mode 100644 index 0000000..11a6efa --- /dev/null +++ b/src/Action/TrainTrooperAction.ts @@ -0,0 +1,57 @@ +import { ActionController, registerAction } from './ActionController'; +import { Args } from '../Common'; +import { ActionError, TryLaterError } from '../Errors'; +import { Task } from '../Storage/TaskQueue'; +import { getNumber, toNumber } from '../utils'; + +@registerAction +export class TrainTrooperAction extends ActionController { + async run(args: Args, task: Task): Promise { + const troopId = this.getTroopId(args, task); + const trainCount = this.getTrainCount(args, task); + + const block = jQuery(`#nonFavouriteTroops .innerTroopWrapper.troop${troopId}`); + if (block.length !== 1) { + throw new ActionError(task.id, `Troop block not found`); + } + + const countLink = block.find('.cta a'); + if (countLink.length !== 1) { + throw new ActionError(task.id, `Link with max count not found`); + } + + const maxCount = getNumber(countLink.text()); + if (maxCount < trainCount) { + throw new TryLaterError(task.id, 10 * 60, `Max count ${maxCount} less then need ${trainCount}`); + } + + const input = block.find(`input[name="t${troopId}"]`); + if (input.length !== 1) { + throw new ActionError(task.id, `Input element not found`); + } + + const trainButton = jQuery('.startTraining.green').first(); + if (trainButton.length !== 1) { + throw new ActionError(task.id, 'Train button not found'); + } + + input.val(trainCount); + trainButton.trigger('click'); + } + + private getTroopId(args: Args, task: Task): number { + const troopId = toNumber(args.troopId); + if (troopId === undefined) { + throw new ActionError(task.id, `Troop id must be a number, given "${args.troopId}"`); + } + return troopId; + } + + private getTrainCount(args: Args, task: Task): number { + const trainCount = toNumber(args.trainCount); + if (trainCount === undefined) { + throw new ActionError(task.id, `Train count must be a number, given "${args.trainCount}"`); + } + return trainCount; + } +} diff --git a/src/Dashboard/BuildPage.ts b/src/Dashboard/BuildPage.ts new file mode 100644 index 0000000..5891bb9 --- /dev/null +++ b/src/Dashboard/BuildPage.ts @@ -0,0 +1,89 @@ +import { elClassId, split, uniqId } from '../utils'; +import { Command } from '../Common'; +import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; +import { Scheduler } from '../Scheduler'; +import { TrainTroopTask } from '../Task/TrainTroopTask'; + +const QUARTERS_ID = 19; + +export class BuildPage { + private scheduler: Scheduler; + private readonly buildId: number; + constructor(scheduler: Scheduler, buildId: number) { + this.scheduler = scheduler; + this.buildId = buildId; + } + + run() { + const buildTypeId = elClassId(jQuery('#build').attr('class') || '', 'gid'); + this.log('BUILD PAGE DETECTED', 'ID', this.buildId, 'TYPE', buildTypeId); + this.createUpgradeButton(); + if (buildTypeId === QUARTERS_ID) { + this.createTrainTroopButton(); + } + } + + private createUpgradeButton() { + const id = uniqId(); + jQuery('.upgradeButtonsContainer .section1').append( + `
В очередь
` + ); + jQuery(`#${id}`).on('click', evt => { + evt.preventDefault(); + this.onScheduleBuilding(this.buildId); + }); + } + + private onScheduleBuilding(id: number) { + const queueItem = new Command(UpgradeBuildingTask.name, { + id, + }); + this.scheduler.scheduleTask(queueItem); + const n = new Notification(`Building ${id} scheduled`); + setTimeout(() => n && n.close(), 4000); + } + + private createTrainTroopButton() { + const troopBlocks = jQuery('#nonFavouriteTroops .action.troop:not(.empty) .innerTroopWrapper'); + troopBlocks.each((idx, el) => { + const troopId = elClassId(jQuery(el).attr('class') || '', 'troop'); + console.log('TROOP', troopId); + if (troopId) { + const id = uniqId(); + jQuery(el) + .find('.details') + .append(`
Обучить
`); + jQuery(`#${id}`).on('click', evt => { + evt.preventDefault(); + this.onTrainTroopClick(this.buildId, troopId, el); + }); + } + }); + } + + private onTrainTroopClick(buildId: Number, troopId: Number, el: HTMLElement) { + console.log('TRAIN TROOPERS', 'TROOP ID', troopId, 'BUILDING ID', buildId); + const input = jQuery(el).find(`input[name="t${troopId}"]`); + const count = Number(input.val()); + if (!isNaN(count) && count > 0) { + console.log('PREPARE TO TRAIN', count, 'TROOPERS'); + for (let n of split(count)) { + this.scheduler.scheduleTask( + new Command(TrainTroopTask.name, { + buildId, + troopId, + trainCount: n, + }) + ); + } + } + } + + private log(...args) { + console.log('BUILD PAGE:', ...args); + } + + private logError(...args) { + console.error(...args); + } +} diff --git a/src/Dashboard.ts b/src/Dashboard/Dashboard.ts similarity index 52% rename from src/Dashboard.ts rename to src/Dashboard/Dashboard.ts index b7032e9..c3d9ddf 100644 --- a/src/Dashboard.ts +++ b/src/Dashboard/Dashboard.ts @@ -1,9 +1,9 @@ import * as URLParse from 'url-parse'; -import { markPage, uniqId, waitForLoad } from './utils'; -import { Scheduler } from './Scheduler'; -import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; -import { Command } from './Common'; -import { TaskQueueRenderer } from './TaskQueueRenderer'; +import { elClassId, markPage, waitForLoad } from '../utils'; +import { Scheduler } from '../Scheduler'; +import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; +import { TaskQueueRenderer } from '../TaskQueueRenderer'; +import { BuildPage } from './BuildPage'; export class Dashboard { private readonly version: string; @@ -33,15 +33,7 @@ export class Dashboard { } if (p.pathname === '/build.php') { - this.log('BUILD PAGE DETECTED'); - const id = uniqId(); - jQuery('.upgradeButtonsContainer .section1').append( - `
В очередь
` - ); - jQuery(`#${id}`).on('click', evt => { - evt.preventDefault(); - this.onScheduleBuilding(p.query.id || ''); - }); + new BuildPage(this.scheduler, Number(p.query.id)).run(); } } @@ -50,29 +42,24 @@ export class Dashboard { new TaskQueueRenderer().render(this.scheduler.getTaskItems()); } - private onScheduleBuilding(id: string) { - const queueItem = new Command(UpgradeBuildingTask.name, { - id, - }); - this.scheduler.scheduleTask(queueItem); - const n = new Notification(`Building ${id} scheduled`); - setTimeout(() => n && n.close(), 4000); - } - private showSlotIds(prefix: string) { + const tasks = this.scheduler.getTaskItems(); jQuery('.level.colorLayer').each((idx, el) => { - let num = ''; - el.classList.forEach(cls => { - if (cls.startsWith(prefix)) { - num = cls.substr(prefix.length); - } - }); - const t = jQuery(el) + const buildId = elClassId(jQuery(el).attr('class') || '', prefix); + const oldLabel = jQuery(el) .find('.labelLayer') .text(); jQuery(el) .find('.labelLayer') - .text(num + ':' + t); + .text(buildId + ':' + oldLabel); + const inQueue = tasks.find( + t => t.cmd.name === UpgradeBuildingTask.name && Number(t.cmd.args.id) === Number(buildId) + ); + if (inQueue !== undefined) { + jQuery(el).css({ + 'background-image': 'linear-gradient(to top, #f00, #f00 100%)', + }); + } }); } diff --git a/src/ModeDetector.ts b/src/ModeDetector.ts index b4655c6..a50ed9f 100644 --- a/src/ModeDetector.ts +++ b/src/ModeDetector.ts @@ -14,17 +14,11 @@ export class ModeDetector { private isAutoByLocation(): boolean { const p = new URLParse(window.location.href, true); - console.log('PARSED LOCATION', p); - if (p.query['auto-management'] !== undefined) { - console.log('AUTO MANAGEMENT ON'); - return true; - } - - return false; + return p.query['auto-management'] !== undefined; } private isAutoBySession(): boolean { - const k = sessionStorage.getItem(SESSION_KEY); - return k === SESSION_VALUE; + const sessionKey = sessionStorage.getItem(SESSION_KEY); + return sessionKey === SESSION_VALUE; } } diff --git a/src/Task/TrainTroopTask.ts b/src/Task/TrainTroopTask.ts new file mode 100644 index 0000000..50d12e5 --- /dev/null +++ b/src/Task/TrainTroopTask.ts @@ -0,0 +1,18 @@ +import { Args, Command } from '../Common'; +import { Task } from '../Storage/TaskQueue'; +import { TaskController, registerTask } from './TaskController'; +import { GoToPageAction } from '../Action/GoToPageAction'; +import { CompleteTaskAction } from '../Action/CompleteTaskAction'; +import { TrainTrooperAction } from '../Action/TrainTrooperAction'; + +@registerTask +export class TrainTroopTask extends TaskController { + async run(task: Task) { + const args: Args = { ...task.cmd.args, taskId: task.id }; + this.scheduler.scheduleActions([ + new Command(GoToPageAction.name, { ...args, path: '/build.php?id=' + args.buildId }), + new Command(TrainTrooperAction.name, args), + new Command(CompleteTaskAction.name, args), + ]); + } +} diff --git a/src/index.js b/src/index.js index 6530788..1e878c8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { ModeDetector } from './ModeDetector'; import { Scheduler } from './Scheduler'; -import { Dashboard } from './Dashboard'; +import { Dashboard } from './Dashboard/Dashboard'; import TxtVersion from '!!raw-loader!./version.txt'; console.log('TRAVIAN AUTOMATION', TxtVersion); diff --git a/src/utils.ts b/src/utils.ts index 2b4764e..97f7a6f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -34,6 +34,43 @@ export function trimPrefix(text: string, prefix: string): string { return text.startsWith(prefix) ? text.substr(prefix.length) : text; } +export function elClassId(classes: string, prefix: string): number | undefined { + let result: number | undefined = undefined; + classes.split(/\s/).forEach(part => { + if (part.startsWith(prefix)) { + result = Number(part.substr(prefix.length)); + if (isNaN(result)) { + result = undefined; + } + } + }); + return result; +} + +export function* split(n: number) { + let c = n; + while (c > 0) { + const next = 2 + Math.floor(Math.random() * 3); + if (next < c) { + yield next; + c -= next; + } else { + yield c; + c = 0; + } + } +} + +export function toNumber(value: any): number | undefined { + const converted = Number(value); + return isNaN(converted) ? undefined : converted; +} + +export function getNumber(value: any, def: number = 0): number { + const converted = toNumber(value); + return converted === undefined ? def : converted; +} + export function markPage(text: string, version: string) { jQuery('body').append( '