Move action and task handlers in their own space

This commit is contained in:
2020-07-18 18:06:55 +03:00
parent b6cda983c7
commit a74a26896c
50 changed files with 387 additions and 370 deletions

View File

@ -0,0 +1,40 @@
import { BaseAction } from './BaseAction';
import { changeHeroResource, grabCurrentHeroResource } from '../../Page/HeroPage';
import { grabActiveVillageId } from '../../Page/VillageBlock';
import { HeroStorage } from '../../Storage/HeroStorage';
import { calcHeroResource } from '../../Core/HeroBalance';
import { HeroAllResources } from '../../Core/Hero';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { registerAction } from '../ActionMap';
@registerAction
export class BalanceHeroResourcesAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const thisVillageId = grabActiveVillageId();
const heroVillageId = new HeroStorage().getVillageId();
if (heroVillageId === undefined || heroVillageId !== thisVillageId) {
changeHeroResource(HeroAllResources);
return;
}
const thisVillageState = this.villageFactory.createState(thisVillageId);
const requirements = [
thisVillageState.required.balance,
thisVillageState.commitments,
thisVillageState.resources.sub(thisVillageState.storage.capacity),
];
console.log('Requirements');
console.table(requirements);
const heroType = calcHeroResource(requirements);
const currentType = grabCurrentHeroResource();
if (heroType !== currentType) {
changeHeroResource(heroType);
}
}
}

View File

@ -0,0 +1,26 @@
import { Scheduler } from '../../Scheduler';
import { taskError, TryLaterError } from '../../Errors';
import { grabActiveVillageId } from '../../Page/VillageBlock';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { VillageFactory } from '../../Village/VillageFactory';
import { aroundMinutes } from '../../Helpers/Time';
export class BaseAction {
protected readonly scheduler: Scheduler;
protected readonly villageFactory: VillageFactory;
constructor(scheduler: Scheduler, villageFactory: VillageFactory) {
this.scheduler = scheduler;
this.villageFactory = villageFactory;
}
async run(args: Args, task: Task) {}
ensureSameVillage(args: Args) {
let villageId = args.villageId || taskError('Undefined village id');
const activeVillageId = grabActiveVillageId();
if (villageId !== activeVillageId) {
throw new TryLaterError(aroundMinutes(1), 'Not same village');
}
}
}

View File

@ -0,0 +1,23 @@
import { BaseAction } from './BaseAction';
import { GrabError, taskError, TryLaterError } from '../../Errors';
import { clickBuildButton } from '../../Page/BuildingPage/BuildingPage';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class BuildBuildingAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
try {
this.ensureSameVillage(args);
const buildTypeId = args.buildTypeId || taskError('Undefined build type id');
clickBuildButton(buildTypeId);
} catch (e) {
if (e instanceof GrabError) {
throw new TryLaterError(aroundMinutes(5), 'No upgrade button, try later');
}
throw e;
}
}
}

View File

@ -0,0 +1,22 @@
import { BaseAction } from './BaseAction';
import { GrabError, TryLaterError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { clickCelebrationButton } from '../../Page/BuildingPage/GuildHallPage';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class CelebrationAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
try {
this.ensureSameVillage(args);
clickCelebrationButton(args.celebrationIndex);
} catch (e) {
if (e instanceof GrabError) {
throw new TryLaterError(aroundMinutes(60), e.message);
}
throw e;
}
}
}

View File

@ -0,0 +1,28 @@
import { BaseAction } from './BaseAction';
import { GrabError, TryLaterError } from '../../Errors';
import { grabBuildingQueueInfo } from '../../Page/VillageBlock';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { BuildingQueueInfo } from '../../Core/BuildingQueueInfo';
import { registerAction } from '../ActionMap';
@registerAction
export class CheckBuildingRemainingTimeAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const info = this.grabBuildingQueueInfoOrDefault();
if (info.seconds > 0) {
throw new TryLaterError(info.seconds + 1, 'Building queue is full');
}
}
private grabBuildingQueueInfoOrDefault() {
try {
return grabBuildingQueueInfo();
} catch (e) {
if (e instanceof GrabError) {
return new BuildingQueueInfo(0);
}
throw e;
}
}
}

View File

@ -0,0 +1,17 @@
import { BaseAction } from './BaseAction';
import { taskError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { registerAction } from '../ActionMap';
@registerAction
export class ClickButtonAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const selector = args.selector || taskError('No selector');
const el = jQuery(selector);
if (el.length === 1) {
console.log('CLICK BUTTON', el);
el.trigger('click');
}
}
}

View File

@ -0,0 +1,11 @@
import { BaseAction } from './BaseAction';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { registerAction } from '../ActionMap';
@registerAction
export class CompleteTaskAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
this.scheduler.completeTask(task.id);
}
}

View File

@ -0,0 +1,57 @@
import { BaseAction } from './BaseAction';
import { FailTaskError, taskError, TryLaterError } from '../../Errors';
import { Resources } from '../../Core/Resources';
import { Coordinates } from '../../Core/Village';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { clickSendButton, fillSendResourcesForm } from '../../Page/BuildingPage/MarketPage';
import { VillageState } from '../../Village/VillageState';
import { MerchantsInfo } from '../../Core/Market';
import { goToMarketSendResourcesPage, goToResourceViewPage } from '../ActionBundles';
import {
compareReports,
ResourceTransferCalculator,
ResourceTransferReport,
} from '../../Village/ResourceTransfer';
import { ResourceTransferStorage } from '../../Storage/ResourceTransferStorage';
import { path } from '../../Helpers/Path';
import { MARKET_ID } from '../../Core/Buildings';
import { aroundMinutes, timestamp } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class FindSendResourcesPathAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const reports: Array<ResourceTransferReport> = [];
const calculator = new ResourceTransferCalculator(this.villageFactory);
const villages = this.villageFactory.getAllVillages();
for (let fromVillage of villages) {
for (let toVillage of villages) {
if (fromVillage.id !== toVillage.id) {
reports.push(calculator.calc(fromVillage.id, toVillage.id));
}
}
}
reports.sort(compareReports);
const bestReport = reports.shift();
if (!bestReport) {
throw new FailTaskError('No best report for transfer resources');
}
console.log('Best report', bestReport);
const storage = new ResourceTransferStorage();
storage.storeReport(bestReport);
const marketPath = path('/build.php', {
newdid: bestReport.fromVillageId,
gid: MARKET_ID,
t: 5,
});
window.location.assign(marketPath);
}
}

View File

@ -0,0 +1,23 @@
import { BaseAction } from './BaseAction';
import { GrabError, taskError, TryLaterError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { clickResearchButton } from '../../Page/BuildingPage/ForgePage';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class ForgeImprovementAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
try {
this.ensureSameVillage(args);
const unitId = args.unitId || taskError('No unitId in args');
clickResearchButton(unitId);
} catch (e) {
if (e instanceof GrabError) {
throw new TryLaterError(aroundMinutes(15), e.message);
}
throw e;
}
}
}

View File

@ -0,0 +1,31 @@
import { BaseAction } from './BaseAction';
import { grabVillageList } from '../../Page/VillageBlock';
import { grabHeroVillage } from '../../Page/HeroPage';
import { HeroStorage } from '../../Storage/HeroStorage';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { path } from '../../Helpers/Path';
import { registerAction } from '../ActionMap';
@registerAction
export class GoToHeroVillageAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const heroVillageId = this.getHeroVillageId();
if (heroVillageId) {
window.location.assign(path('/hero.php', { newdid: heroVillageId }));
}
}
private getHeroVillageId(): number | undefined {
const villages = grabVillageList();
const heroVillage = grabHeroVillage();
for (let village of villages) {
if (village.name === heroVillage) {
return village.id;
}
}
return new HeroStorage().getVillageId();
}
}

View File

@ -0,0 +1,13 @@
import { BaseAction } from './BaseAction';
import { taskError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { registerAction } from '../ActionMap';
@registerAction
export class GoToPageAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const path = args.path || taskError('Empty path');
window.location.assign(path);
}
}

View File

@ -0,0 +1,71 @@
import { BaseAction } from './BaseAction';
import { AbortTaskError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { trimPrefix } from '../../Helpers/Convert';
import { registerAction } from '../ActionMap';
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;
level: number;
}
@registerAction
export class SendOnAdventureAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const adventures = this.findAdventures();
console.log('ADVENTURES', adventures);
adventures.sort((x, y) => x.level - y.level);
const easiest = adventures.shift();
const hero = { health: 0 };
console.log('EASIEST', easiest);
console.log('HERO', hero);
if (easiest && hero.health) {
this.checkConfig(easiest, Number(hero.health));
}
throw new AbortTaskError('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');
}
}
}
private findAdventures(): Array<Adventure> {
const adventures: Array<Adventure> = [];
jQuery('tr[id^=adventure]').each((index, el) => {
const imgClass =
jQuery(el)
.find('.difficulty img')
.attr('class')
?.toString() || '';
const level = Number(trimPrefix(imgClass, 'adventureDifficulty'));
if (!isNaN(level)) {
adventures.push({ el, level: level });
}
});
return adventures;
}
}

View File

@ -0,0 +1,34 @@
import { BaseAction } from './BaseAction';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { clickSendButton, fillSendResourcesForm } from '../../Page/BuildingPage/MarketPage';
import { ResourceTransferCalculator } from '../../Village/ResourceTransfer';
import { ResourceTransferStorage } from '../../Storage/ResourceTransferStorage';
import { Resources } from '../../Core/Resources';
import { AbortTaskError } from '../../Errors';
import { registerAction } from '../ActionMap';
@registerAction
export class SendResourcesAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const storage = new ResourceTransferStorage();
const savedReport = storage.getReport();
const fromVillage = this.villageFactory.getVillage(savedReport.fromVillageId);
const toVillage = this.villageFactory.getVillage(savedReport.toVillageId);
const coordinates = toVillage.crd;
const calculator = new ResourceTransferCalculator(this.villageFactory);
const report = calculator.calc(fromVillage.id, toVillage.id);
console.log('To transfer report', report);
if (Resources.fromObject(report.resources).empty()) {
throw new AbortTaskError('No resources to transfer');
}
fillSendResourcesForm(report.resources, coordinates);
clickSendButton();
}
}

View File

@ -0,0 +1,44 @@
import { BaseAction } from './BaseAction';
import { taskError, TryLaterError } from '../../Errors';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import {
clickTrainButton,
fillTrainCount,
getAvailableCount,
} from '../../Page/BuildingPage/TrooperPage';
import { TrainTroopTask } from '../Task/TrainTroopTask';
import { Resources } from '../../Core/Resources';
import { randomInRange } from '../../Helpers/Random';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class TrainTrooperAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const troopId = args.troopId || taskError('No troop id');
const trainCount = args.trainCount || taskError('No troop train count');
const troopResources = args.troopResources || taskError('No troop resources');
const availableCount = getAvailableCount(troopId);
const desiredCount = randomInRange(3, 12);
const readyToTrainCount = Math.min(availableCount, trainCount, desiredCount);
const nextToTrainCount = trainCount - readyToTrainCount;
if (readyToTrainCount <= 0) {
throw new TryLaterError(aroundMinutes(15), 'No isReady to train troops');
}
if (nextToTrainCount > 0) {
this.scheduler.scheduleTask(TrainTroopTask.name, {
...task.args,
trainCount: nextToTrainCount,
resources: Resources.fromObject(troopResources).scale(nextToTrainCount),
});
}
fillTrainCount(troopId, readyToTrainCount);
clickTrainButton();
}
}

View File

@ -0,0 +1,22 @@
import { BaseAction } from './BaseAction';
import { GrabError, TryLaterError } from '../../Errors';
import { clickUpgradeButton } from '../../Page/BuildingPage/BuildingPage';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class UpgradeBuildingAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
try {
this.ensureSameVillage(args);
clickUpgradeButton();
} catch (e) {
if (e instanceof GrabError) {
throw new TryLaterError(aroundMinutes(5), 'No upgrade button, try later');
}
throw e;
}
}
}

View File

@ -0,0 +1,69 @@
import { BaseAction } from './BaseAction';
import { ActionError, taskError, TryLaterError } from '../../Errors';
import { grabResourceSlots } from '../../Page/SlotBlock';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { Args } from '../../Queue/Args';
import { Task } from '../../Queue/TaskProvider';
import { ResourceSlot } from '../../Core/Slot';
import { getNumber } from '../../Helpers/Convert';
import { aroundMinutes } from '../../Helpers/Time';
import { registerAction } from '../ActionMap';
@registerAction
export class UpgradeResourceToLevelAction extends BaseAction {
async run(args: Args, task: Task): Promise<any> {
const deposits = grabResourceSlots();
if (deposits.length === 0) {
throw new ActionError('No deposits');
}
const villageId = args.villageId || taskError('No village id');
const requiredLevel = getNumber(args.level);
const notUpgraded = deposits.filter(
dep => !dep.isUnderConstruction && requiredLevel > dep.level
);
if (notUpgraded.length === 0) {
this.scheduler.removeTask(task.id);
return;
}
notUpgraded.sort((x, y) => x.level - y.level);
// Next two buildings: no delay between start building and scheduling next
const firstNotUpgraded = notUpgraded.shift();
const secondNotUpgraded = notUpgraded.shift();
if (firstNotUpgraded && this.isTaskNotInQueue(villageId, firstNotUpgraded)) {
this.scheduler.scheduleTask(UpgradeBuildingTask.name, {
villageId,
buildId: firstNotUpgraded.buildId,
});
}
if (secondNotUpgraded && this.isTaskNotInQueue(villageId, secondNotUpgraded)) {
this.scheduler.scheduleTask(UpgradeBuildingTask.name, {
villageId,
buildId: secondNotUpgraded.buildId,
});
}
throw new TryLaterError(aroundMinutes(10), 'Sleep for next round');
}
private isTaskNotInQueue(villageId: number, dep: ResourceSlot): boolean {
const tasks = this.scheduler.getTaskItems();
return (
undefined ===
tasks.find(
task =>
task.name === UpgradeBuildingTask.name &&
task.args.villageId === villageId &&
task.args.buildId === dep.buildId
)
);
}
}