Add auto training

This commit is contained in:
Anton Vakhrushev 2020-04-04 12:13:28 +03:00
parent 7f5cdf2cc3
commit 4bf8161b70
9 changed files with 242 additions and 53 deletions

View File

@ -6,7 +6,7 @@
- [ ] Автоматический скан карты - [ ] Автоматический скан карты
- [ ] Автоматический скан статистики других игроков - [ ] Автоматический скан статистики других игроков
- [ ] Автоматическая отправка ресурсов на другие учетные записи - [ ] Автоматическая отправка ресурсов на другие учетные записи
- [ ] Автоматическое обучение войск - [x] Автоматическое обучение войск
- [ ] Автоматическое размещение ставок на аукционе - [ ] Автоматическое размещение ставок на аукционе
- [ ] Автоматический запуск празднований - [ ] Автоматический запуск празднований
- [ ] Сканирование собственных деревень для отображения ресурсов и информации в одном месте - [ ] Сканирование собственных деревень для отображения ресурсов и информации в одном месте

View File

@ -4,8 +4,14 @@ import { Task } from '../Storage/TaskQueue';
import { trimPrefix } from '../utils'; import { trimPrefix } from '../utils';
import { AbortTaskError } from '../Errors'; import { AbortTaskError } from '../Errors';
const HARD = 0; const CONFIG = [
const NORMAL = 3; { 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 { interface Adventure {
el: HTMLElement; el: HTMLElement;
@ -28,19 +34,20 @@ export class SendOnAdventureAction extends ActionController {
console.log('HERO', hero); console.log('HERO', hero);
if (easiest && hero.health) { if (easiest && hero.health) {
if (easiest.level === NORMAL && hero.health >= 30) { this.checkConfig(easiest, Number(hero.health));
return jQuery(easiest.el) }
.find('td.goTo .gotoAdventure')
.trigger('click'); throw new AbortTaskError(task.id, 'No suitable adventure');
} }
if (easiest.level === HARD && hero.health >= 50) {
return jQuery(easiest.el) 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') .find('td.goTo .gotoAdventure')
.trigger('click'); .trigger('click');
} }
} }
throw new AbortTaskError(task.id, 'No suitable adventure');
} }
private findAdventures(): Array<Adventure> { private findAdventures(): Array<Adventure> {

View File

@ -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<any> {
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;
}
}

View File

@ -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(
`<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`
);
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(`<div style="padding: 8px 0"><a id="${id}" href="#">Обучить</a></div>`);
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);
}
}

View File

@ -1,9 +1,9 @@
import * as URLParse from 'url-parse'; import * as URLParse from 'url-parse';
import { markPage, uniqId, waitForLoad } from './utils'; import { elClassId, markPage, waitForLoad } from '../utils';
import { Scheduler } from './Scheduler'; import { Scheduler } from '../Scheduler';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { Command } from './Common'; import { TaskQueueRenderer } from '../TaskQueueRenderer';
import { TaskQueueRenderer } from './TaskQueueRenderer'; import { BuildPage } from './BuildPage';
export class Dashboard { export class Dashboard {
private readonly version: string; private readonly version: string;
@ -33,15 +33,7 @@ export class Dashboard {
} }
if (p.pathname === '/build.php') { if (p.pathname === '/build.php') {
this.log('BUILD PAGE DETECTED'); new BuildPage(this.scheduler, Number(p.query.id)).run();
const id = uniqId();
jQuery('.upgradeButtonsContainer .section1').append(
`<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`
);
jQuery(`#${id}`).on('click', evt => {
evt.preventDefault();
this.onScheduleBuilding(p.query.id || '');
});
} }
} }
@ -50,29 +42,24 @@ export class Dashboard {
new TaskQueueRenderer().render(this.scheduler.getTaskItems()); 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) { private showSlotIds(prefix: string) {
const tasks = this.scheduler.getTaskItems();
jQuery('.level.colorLayer').each((idx, el) => { jQuery('.level.colorLayer').each((idx, el) => {
let num = ''; const buildId = elClassId(jQuery(el).attr('class') || '', prefix);
el.classList.forEach(cls => { const oldLabel = jQuery(el)
if (cls.startsWith(prefix)) {
num = cls.substr(prefix.length);
}
});
const t = jQuery(el)
.find('.labelLayer') .find('.labelLayer')
.text(); .text();
jQuery(el) jQuery(el)
.find('.labelLayer') .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%)',
});
}
}); });
} }

View File

@ -14,17 +14,11 @@ export class ModeDetector {
private isAutoByLocation(): boolean { private isAutoByLocation(): boolean {
const p = new URLParse(window.location.href, true); const p = new URLParse(window.location.href, true);
console.log('PARSED LOCATION', p); return p.query['auto-management'] !== undefined;
if (p.query['auto-management'] !== undefined) {
console.log('AUTO MANAGEMENT ON');
return true;
}
return false;
} }
private isAutoBySession(): boolean { private isAutoBySession(): boolean {
const k = sessionStorage.getItem(SESSION_KEY); const sessionKey = sessionStorage.getItem(SESSION_KEY);
return k === SESSION_VALUE; return sessionKey === SESSION_VALUE;
} }
} }

View File

@ -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),
]);
}
}

View File

@ -1,6 +1,6 @@
import { ModeDetector } from './ModeDetector'; import { ModeDetector } from './ModeDetector';
import { Scheduler } from './Scheduler'; import { Scheduler } from './Scheduler';
import { Dashboard } from './Dashboard'; import { Dashboard } from './Dashboard/Dashboard';
import TxtVersion from '!!raw-loader!./version.txt'; import TxtVersion from '!!raw-loader!./version.txt';
console.log('TRAVIAN AUTOMATION', TxtVersion); console.log('TRAVIAN AUTOMATION', TxtVersion);

View File

@ -34,6 +34,43 @@ export function trimPrefix(text: string, prefix: string): string {
return text.startsWith(prefix) ? text.substr(prefix.length) : text; 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) { export function markPage(text: string, version: string) {
jQuery('body').append( jQuery('body').append(
'<div style="' + '<div style="' +