Rewrite hero resource action

try to detect hero village before balancing
This commit is contained in:
Anton Vakhrushev 2020-04-11 13:46:10 +03:00
parent 8e8b358b91
commit 2d9c3f94e7
12 changed files with 279 additions and 76 deletions

View File

@ -1,71 +1,30 @@
import { ActionController, registerAction } from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { getNumber, trimPrefix } from '../utils'; import { grabResources } from '../Page/ResourcesBlock';
import { AbortTaskError, ActionError } from '../Errors'; import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage';
import { HeroAllResources } from '../Game';
interface Resource {
type: number;
value: number;
}
const ALL = 0;
@registerAction @registerAction
export class BalanceHeroResourcesAction extends ActionController { export class BalanceHeroResourcesAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const res = this.getResources(); const resources = grabResources().asList();
const currentType = this.getCurrentHeroResource(task); const currentType = grabCurrentHeroResource();
console.log('RESOURCES', res);
console.log('RESOURCES', resources);
console.log('CURRENT TYPE', currentType); console.log('CURRENT TYPE', currentType);
const sorted = res.sort((x, y) => x.value - y.value);
const sorted = resources.sort((x, y) => x.value - y.value);
const min = sorted[0]; const min = sorted[0];
const max = sorted[sorted.length - 1]; const max = sorted[sorted.length - 1];
const delta = max.value - min.value; const delta = max.value - min.value;
const eps = max.value / 10; const eps = max.value / 10;
console.log('MIN', min, 'MAX', max, 'DELTA', delta, 'EPS', eps); console.log('MIN', min, 'MAX', max, 'DELTA', delta, 'EPS', eps);
const resType = delta > eps ? min.type : ALL;
const resType = delta > eps ? min.type : HeroAllResources;
if (resType !== currentType) { if (resType !== currentType) {
this.changeToHeroResource(task, resType); changeHeroResource(resType);
} }
} }
private getResources(): Array<Resource> {
const res = this.state.get('resources');
const resList: Array<Resource> = [];
for (let r in res) {
const type = getNumber(r);
const value = getNumber(res[r]);
resList.push({ type, value });
}
return resList;
}
private getCurrentHeroResource(task: Task): number {
for (let type of [0, 1, 2, 3, 4]) {
const input = jQuery(`#resourceHero${type}`);
if (input.length !== 1) {
throw new ActionError(task.id, `Hero resource ${type} not found`);
}
if (input.prop('checked')) {
return type;
}
}
return 0;
}
private changeToHeroResource(task: Task, type: number) {
const input = jQuery(`#resourceHero${type}`);
if (input.length !== 1) {
throw new ActionError(task.id, `Hero resource ${type} not found`);
}
const btn = jQuery('#saveHeroAttributes');
if (btn.length !== 1) {
throw new ActionError(task.id, `Hero resource button not found`);
}
input.trigger('click');
btn.trigger('click');
}
} }

View File

@ -2,7 +2,7 @@ import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { BuildingQueueFullError } from '../Errors'; import { BuildingQueueFullError } from '../Errors';
import { grabActiveVillageId } from '../Page/EveryPage'; import { grabActiveVillageId } from '../Page/VillageBlock';
@registerAction @registerAction
export class CheckBuildingRemainingTimeAction extends ActionController { export class CheckBuildingRemainingTimeAction extends ActionController {

View File

@ -0,0 +1,32 @@
import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue';
import { grabVillageList } from '../Page/VillageBlock';
import { grabHeroVillage } from '../Page/HeroPage';
import { path } from '../utils';
@registerAction
export class GoToHeroVillageAction extends ActionController {
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();
console.log('VILLAGES', villages);
console.log('HERO VILLAGE', heroVillage);
for (let village of villages) {
if (village.name === heroVillage) {
return village.id;
}
}
return undefined;
}
}

View File

@ -3,13 +3,11 @@ import { markPage, waitForLoad } from '../utils';
import { Scheduler } from '../Scheduler'; import { Scheduler } from '../Scheduler';
import { TaskQueueRenderer } from '../TaskQueueRenderer'; import { TaskQueueRenderer } from '../TaskQueueRenderer';
import { BuildPage } from '../Page/BuildPage'; import { BuildPage } from '../Page/BuildPage';
import {
grabActiveVillageId,
onResourceSlotCtrlClick,
showBuildingSlotIds,
showFieldsSlotIds,
} from '../Page/EveryPage';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { grabResources } from '../Page/ResourcesBlock';
import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock';
import { onResourceSlotCtrlClick, showBuildingSlotIds, showFieldsSlotIds } from '../Page/SlotBlock';
export class Dashboard { export class Dashboard {
private readonly version: string; private readonly version: string;
@ -30,6 +28,12 @@ export class Dashboard {
this.renderTaskQueue(); this.renderTaskQueue();
setInterval(() => this.renderTaskQueue(), 5000); setInterval(() => this.renderTaskQueue(), 5000);
const res = grabResources();
this.log('RES', res);
const villages = grabVillageList();
this.log('VILL', villages);
const villageId = grabActiveVillageId(); const villageId = grabActiveVillageId();
const tasks = this.scheduler.getTaskItems(); const tasks = this.scheduler.getTaskItems();

View File

@ -1,5 +1,12 @@
import { TaskId } from './Storage/TaskQueue'; import { TaskId } from './Storage/TaskQueue';
export class GrabError extends Error {
constructor(msg: string = '') {
super(msg);
Object.setPrototypeOf(this, GrabError.prototype);
}
}
export class ActionError extends Error { export class ActionError extends Error {
readonly taskId: TaskId; readonly taskId: TaskId;
constructor(taskId: TaskId, msg: string = '') { constructor(taskId: TaskId, msg: string = '') {

82
src/Game.ts Normal file
View File

@ -0,0 +1,82 @@
export enum ResourceType {
Lumber = 1,
Clay = 2,
Iron = 3,
Crop = 4,
}
export const ResourceMapping: ReadonlyArray<{ num: number; type: ResourceType }> = [
{ num: 1, type: ResourceType.Lumber },
{ num: 2, type: ResourceType.Clay },
{ num: 3, type: ResourceType.Iron },
{ num: 4, type: ResourceType.Crop },
];
export type ResourceList = Array<{ num: number; type: ResourceType; value: number }>;
export class Resources {
readonly lumber: number;
readonly clay: number;
readonly iron: number;
readonly crop: number;
readonly warehouse: number;
readonly granary: number;
constructor(lumber: number, clay: number, iron: number, crop: number, warehouse: number, granary: number) {
this.lumber = lumber;
this.clay = clay;
this.iron = iron;
this.crop = crop;
this.warehouse = warehouse;
this.granary = granary;
}
getByType(type: ResourceType): number {
switch (type) {
case ResourceType.Lumber:
return this.lumber;
case ResourceType.Clay:
return this.clay;
case ResourceType.Iron:
return this.iron;
case ResourceType.Crop:
return this.crop;
}
}
asList(): ResourceList {
const result: ResourceList = [];
for (let mp of ResourceMapping) {
result.push({ num: mp.num, type: mp.type, value: this.getByType(mp.type) });
}
return result;
}
}
export class Coordinates {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export class Village {
readonly id: number;
readonly name: string;
readonly active: boolean;
readonly crd: Coordinates;
constructor(id: number, name: string, active: boolean, crd: Coordinates) {
this.id = id;
this.name = name;
this.active = active;
this.crd = crd;
}
}
export type VillageList = Array<Village>;
export type HeroAllResourcesType = 'all';
export const HeroAllResources: HeroAllResourcesType = 'all';
export type HeroResourceType = ResourceType | HeroAllResourcesType;

View File

@ -2,7 +2,7 @@ import { elClassId, split, uniqId } from '../utils';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { Scheduler } from '../Scheduler'; import { Scheduler } from '../Scheduler';
import { TrainTroopTask } from '../Task/TrainTroopTask'; import { TrainTroopTask } from '../Task/TrainTroopTask';
import { grabActiveVillageId } from './EveryPage'; import { grabActiveVillageId } from './VillageBlock';
const QUARTERS_ID = 19; const QUARTERS_ID = 19;

52
src/Page/HeroPage.ts Normal file
View File

@ -0,0 +1,52 @@
import { GrabError } from '../Errors';
import { HeroAllResources, HeroResourceType, ResourceMapping, ResourceType } from '../Game';
export function grabCurrentHeroResource(): HeroResourceType {
for (let mp of ResourceMapping) {
if (checkHeroResourceType(mp.num)) {
return mp.type;
}
}
return HeroAllResources;
}
function checkHeroResourceType(typeAsNumber: number): boolean {
const input = jQuery(`#resourceHero${typeAsNumber}`);
if (input.length !== 1) {
throw new GrabError(`Hero resource ${typeAsNumber} not found`);
}
return !!input.prop('checked');
}
export function changeHeroResource(type: HeroResourceType) {
const typeAsNumber = heroResourceTypeToNumber(type);
const input = jQuery(`#resourceHero${typeAsNumber}`);
if (input.length !== 1) {
throw new GrabError(`Hero resource ${typeAsNumber} not found`);
}
const btn = jQuery('#saveHeroAttributes');
if (btn.length !== 1) {
throw new GrabError(`Hero resource button not found`);
}
input.trigger('click');
btn.trigger('click');
}
function heroResourceTypeToNumber(type: HeroResourceType): number {
if (type === HeroAllResources) {
return 0;
}
return type as ResourceType;
}
export function grabHeroVillage(): string | undefined {
const status = jQuery('.heroStatusMessage').text();
const hrefText = jQuery('.heroStatusMessage a').text();
if (status.includes('в родной деревне')) {
return hrefText || undefined;
} else {
return undefined;
}
}

View File

@ -0,0 +1,39 @@
import { Resources, ResourceType } from '../Game';
import { GrabError } from '../Errors';
import { getNumber } from '../utils';
export function grabResources(): Resources {
const lumber = grabResource(ResourceType.Lumber);
const clay = grabResource(ResourceType.Clay);
const iron = grabResource(ResourceType.Iron);
const crop = grabResource(ResourceType.Crop);
const warehouse = grabCapacity('warehouse');
const granary = grabCapacity('granary');
return new Resources(lumber, clay, iron, crop, warehouse, granary);
}
function findStockBarElement() {
const stockBarElement = jQuery('#stockBar');
if (stockBarElement.length !== 1) {
throw new GrabError('Stock Bar not found');
}
return stockBarElement;
}
function grabResource(type: number): number {
const resElement = findStockBarElement().find(`#l${type}`);
if (resElement.length !== 1) {
throw new GrabError(`Resource #${type} not found`);
}
return getNumber(resElement.text().replace(/[^0-9]/g, ''));
}
function grabCapacity(type: string): number {
const capacityElement = findStockBarElement().find(`.${type} .capacity .value`);
if (capacityElement.length !== 1) {
throw new GrabError(`Capacity #${type} not found`);
}
return getNumber(capacityElement.text().replace(/[^0-9]/g, ''));
}

View File

@ -1,13 +1,5 @@
import * as URLParse from 'url-parse';
import { elClassId, getNumber } from '../utils'; import { elClassId, getNumber } from '../utils';
export function grabActiveVillageId(): number {
const href = jQuery('#sidebarBoxVillagelist a.active').attr('href');
const p = new URLParse(href || '', true);
console.log('VILLAGE REF', href, p);
return getNumber(p.query.newdid);
}
interface Slot { interface Slot {
el: HTMLElement; el: HTMLElement;
buildId: number; buildId: number;

40
src/Page/VillageBlock.ts Normal file
View File

@ -0,0 +1,40 @@
import { Coordinates, Village, VillageList } from '../Game';
import { GrabError } from '../Errors';
import * as URLParse from 'url-parse';
import { getNumber } from '../utils';
export function grabVillageList(): VillageList {
const villageList: VillageList = [];
const $elements = getVillageListItems();
$elements.each((idx, el) => {
villageList.push(grabVillageInfo(jQuery(el)));
});
return villageList;
}
export function grabActiveVillageId(): number {
const villageList = grabVillageList();
for (let village of villageList) {
if (village.active) {
return village.id;
}
}
return 0;
}
function getVillageListItems() {
const $elements = jQuery('#sidebarBoxVillagelist ul li a');
if ($elements.length === 0) {
throw new GrabError('Village list items not found');
}
return $elements;
}
function grabVillageInfo($el): Village {
const href = $el.attr('href');
const parsedHref = new URLParse(href || '', true);
const id = getNumber(parsedHref.query.newdid);
const name = $el.find('.name').text();
const active = $el.hasClass('active');
return new Village(id, name, active, new Coordinates(0, 0));
}

View File

@ -3,24 +3,20 @@ import { Task } from '../Storage/TaskQueue';
import { TaskController, registerTask } from './TaskController'; import { TaskController, registerTask } from './TaskController';
import { GoToPageAction } from '../Action/GoToPageAction'; import { GoToPageAction } from '../Action/GoToPageAction';
import { CompleteTaskAction } from '../Action/CompleteTaskAction'; import { CompleteTaskAction } from '../Action/CompleteTaskAction';
import { GrabVillageResourcesAction } from '../Action/GrabVillageResourcesAction';
import { BalanceHeroResourcesAction } from '../Action/BalanceHeroResourcesAction'; import { BalanceHeroResourcesAction } from '../Action/BalanceHeroResourcesAction';
import { path } from '../utils'; import { path } from '../utils';
import { GoToHeroVillageAction } from '../Action/GoToHeroVillageAction';
@registerTask @registerTask
export class BalanceHeroResourcesTask extends TaskController { export class BalanceHeroResourcesTask extends TaskController {
async run(task: Task) { async run(task: Task) {
const args: Args = { ...task.args, taskId: task.id }; const args: Args = { ...task.args, taskId: task.id };
this.scheduler.scheduleActions([ this.scheduler.scheduleActions([
new Command(GoToPageAction.name, {
...args,
path: path('/dorf1.php'),
}),
new Command(GrabVillageResourcesAction.name, args),
new Command(GoToPageAction.name, { new Command(GoToPageAction.name, {
...args, ...args,
path: path('/hero.php'), path: path('/hero.php'),
}), }),
new Command(GoToHeroVillageAction.name, args),
new Command(BalanceHeroResourcesAction.name, args), new Command(BalanceHeroResourcesAction.name, args),
new Command(CompleteTaskAction.name, args), new Command(CompleteTaskAction.name, args),
]); ]);