diff --git a/src/Action/BalanceHeroResourcesAction.ts b/src/Action/BalanceHeroResourcesAction.ts index 9c7de40..de3f208 100644 --- a/src/Action/BalanceHeroResourcesAction.ts +++ b/src/Action/BalanceHeroResourcesAction.ts @@ -1,71 +1,30 @@ import { ActionController, registerAction } from './ActionController'; import { Args } from '../Common'; import { Task } from '../Storage/TaskQueue'; -import { getNumber, trimPrefix } from '../utils'; -import { AbortTaskError, ActionError } from '../Errors'; - -interface Resource { - type: number; - value: number; -} - -const ALL = 0; +import { grabResources } from '../Page/ResourcesBlock'; +import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage'; +import { HeroAllResources } from '../Game'; @registerAction export class BalanceHeroResourcesAction extends ActionController { async run(args: Args, task: Task): Promise { - const res = this.getResources(); - const currentType = this.getCurrentHeroResource(task); - console.log('RESOURCES', res); + const resources = grabResources().asList(); + const currentType = grabCurrentHeroResource(); + + console.log('RESOURCES', resources); 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 max = sorted[sorted.length - 1]; const delta = max.value - min.value; const eps = max.value / 10; + 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) { - this.changeToHeroResource(task, resType); + changeHeroResource(resType); } } - - private getResources(): Array { - const res = this.state.get('resources'); - const resList: Array = []; - 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'); - } } diff --git a/src/Action/CheckBuildingRemainingTimeAction.ts b/src/Action/CheckBuildingRemainingTimeAction.ts index b31c83a..52e6736 100644 --- a/src/Action/CheckBuildingRemainingTimeAction.ts +++ b/src/Action/CheckBuildingRemainingTimeAction.ts @@ -2,7 +2,7 @@ import { ActionController, registerAction } from './ActionController'; import { Args } from '../Common'; import { Task } from '../Storage/TaskQueue'; import { BuildingQueueFullError } from '../Errors'; -import { grabActiveVillageId } from '../Page/EveryPage'; +import { grabActiveVillageId } from '../Page/VillageBlock'; @registerAction export class CheckBuildingRemainingTimeAction extends ActionController { diff --git a/src/Action/GoToHeroVillageAction.ts b/src/Action/GoToHeroVillageAction.ts new file mode 100644 index 0000000..6eacd77 --- /dev/null +++ b/src/Action/GoToHeroVillageAction.ts @@ -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 { + 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; + } +} diff --git a/src/Dashboard/Dashboard.ts b/src/Dashboard/Dashboard.ts index 338e257..777cb86 100644 --- a/src/Dashboard/Dashboard.ts +++ b/src/Dashboard/Dashboard.ts @@ -3,13 +3,11 @@ import { markPage, waitForLoad } from '../utils'; import { Scheduler } from '../Scheduler'; import { TaskQueueRenderer } from '../TaskQueueRenderer'; import { BuildPage } from '../Page/BuildPage'; -import { - grabActiveVillageId, - onResourceSlotCtrlClick, - showBuildingSlotIds, - showFieldsSlotIds, -} from '../Page/EveryPage'; + 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 { private readonly version: string; @@ -30,6 +28,12 @@ export class Dashboard { this.renderTaskQueue(); setInterval(() => this.renderTaskQueue(), 5000); + const res = grabResources(); + this.log('RES', res); + + const villages = grabVillageList(); + this.log('VILL', villages); + const villageId = grabActiveVillageId(); const tasks = this.scheduler.getTaskItems(); diff --git a/src/Errors.ts b/src/Errors.ts index 71ff81b..2090d48 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -1,5 +1,12 @@ 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 { readonly taskId: TaskId; constructor(taskId: TaskId, msg: string = '') { diff --git a/src/Game.ts b/src/Game.ts new file mode 100644 index 0000000..39ca4fe --- /dev/null +++ b/src/Game.ts @@ -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; + +export type HeroAllResourcesType = 'all'; +export const HeroAllResources: HeroAllResourcesType = 'all'; + +export type HeroResourceType = ResourceType | HeroAllResourcesType; diff --git a/src/Page/BuildPage.ts b/src/Page/BuildPage.ts index 728b5e1..3220f88 100644 --- a/src/Page/BuildPage.ts +++ b/src/Page/BuildPage.ts @@ -2,7 +2,7 @@ import { elClassId, split, uniqId } from '../utils'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { Scheduler } from '../Scheduler'; import { TrainTroopTask } from '../Task/TrainTroopTask'; -import { grabActiveVillageId } from './EveryPage'; +import { grabActiveVillageId } from './VillageBlock'; const QUARTERS_ID = 19; diff --git a/src/Page/HeroPage.ts b/src/Page/HeroPage.ts new file mode 100644 index 0000000..5be6583 --- /dev/null +++ b/src/Page/HeroPage.ts @@ -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; + } +} diff --git a/src/Page/ResourcesBlock.ts b/src/Page/ResourcesBlock.ts new file mode 100644 index 0000000..14b0b29 --- /dev/null +++ b/src/Page/ResourcesBlock.ts @@ -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, '')); +} diff --git a/src/Page/EveryPage.ts b/src/Page/SlotBlock.ts similarity index 85% rename from src/Page/EveryPage.ts rename to src/Page/SlotBlock.ts index 2a4370e..8a07d48 100644 --- a/src/Page/EveryPage.ts +++ b/src/Page/SlotBlock.ts @@ -1,13 +1,5 @@ -import * as URLParse from 'url-parse'; 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 { el: HTMLElement; buildId: number; diff --git a/src/Page/VillageBlock.ts b/src/Page/VillageBlock.ts new file mode 100644 index 0000000..9893ef7 --- /dev/null +++ b/src/Page/VillageBlock.ts @@ -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)); +} diff --git a/src/Task/BalanceHeroResourcesTask.ts b/src/Task/BalanceHeroResourcesTask.ts index b4cac86..6ccff4e 100644 --- a/src/Task/BalanceHeroResourcesTask.ts +++ b/src/Task/BalanceHeroResourcesTask.ts @@ -3,24 +3,20 @@ import { Task } from '../Storage/TaskQueue'; import { TaskController, registerTask } from './TaskController'; import { GoToPageAction } from '../Action/GoToPageAction'; import { CompleteTaskAction } from '../Action/CompleteTaskAction'; -import { GrabVillageResourcesAction } from '../Action/GrabVillageResourcesAction'; import { BalanceHeroResourcesAction } from '../Action/BalanceHeroResourcesAction'; import { path } from '../utils'; +import { GoToHeroVillageAction } from '../Action/GoToHeroVillageAction'; @registerTask export class BalanceHeroResourcesTask extends TaskController { async run(task: Task) { const args: Args = { ...task.args, taskId: task.id }; this.scheduler.scheduleActions([ - new Command(GoToPageAction.name, { - ...args, - path: path('/dorf1.php'), - }), - new Command(GrabVillageResourcesAction.name, args), new Command(GoToPageAction.name, { ...args, path: path('/hero.php'), }), + new Command(GoToHeroVillageAction.name, args), new Command(BalanceHeroResourcesAction.name, args), new Command(CompleteTaskAction.name, args), ]);