Add auto training
This commit is contained in:
parent
7f5cdf2cc3
commit
4bf8161b70
@ -6,7 +6,7 @@
|
|||||||
- [ ] Автоматический скан карты
|
- [ ] Автоматический скан карты
|
||||||
- [ ] Автоматический скан статистики других игроков
|
- [ ] Автоматический скан статистики других игроков
|
||||||
- [ ] Автоматическая отправка ресурсов на другие учетные записи
|
- [ ] Автоматическая отправка ресурсов на другие учетные записи
|
||||||
- [ ] Автоматическое обучение войск
|
- [x] Автоматическое обучение войск
|
||||||
- [ ] Автоматическое размещение ставок на аукционе
|
- [ ] Автоматическое размещение ставок на аукционе
|
||||||
- [ ] Автоматический запуск празднований
|
- [ ] Автоматический запуск празднований
|
||||||
- [ ] Сканирование собственных деревень для отображения ресурсов и информации в одном месте
|
- [ ] Сканирование собственных деревень для отображения ресурсов и информации в одном месте
|
||||||
|
@ -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> {
|
||||||
|
57
src/Action/TrainTrooperAction.ts
Normal file
57
src/Action/TrainTrooperAction.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
89
src/Dashboard/BuildPage.ts
Normal file
89
src/Dashboard/BuildPage.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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%)',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/Task/TrainTroopTask.ts
Normal file
18
src/Task/TrainTroopTask.ts
Normal 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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
37
src/utils.ts
37
src/utils.ts
@ -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="' +
|
||||||
|
Loading…
x
Reference in New Issue
Block a user