Fix insert build tasks, so order is matter
This commit is contained in:
		| @@ -1,13 +1,14 @@ | |||||||
| 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 { BuildingQueueFullError } from '../Errors'; | import { BuildingQueueFullError, GrabError } from '../Errors'; | ||||||
| import { grabActiveVillageId, grabBuildingQueueInfo } from '../Page/VillageBlock'; | import { grabActiveVillageId, grabBuildingQueueInfo } from '../Page/VillageBlock'; | ||||||
|  | import { BuildingQueueInfo } from '../Game'; | ||||||
|  |  | ||||||
| @registerAction | @registerAction | ||||||
| export class CheckBuildingRemainingTimeAction extends ActionController { | export class CheckBuildingRemainingTimeAction extends ActionController { | ||||||
|     async run(args: Args, task: Task): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         const info = grabBuildingQueueInfo(); |         const info = this.grabBuildingQueueInfoOrDefault(); | ||||||
|         if (info.seconds > 0) { |         if (info.seconds > 0) { | ||||||
|             throw new BuildingQueueFullError( |             throw new BuildingQueueFullError( | ||||||
|                 task.id, |                 task.id, | ||||||
| @@ -17,4 +18,15 @@ export class CheckBuildingRemainingTimeAction extends ActionController { | |||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private grabBuildingQueueInfoOrDefault() { | ||||||
|  |         try { | ||||||
|  |             return grabBuildingQueueInfo(); | ||||||
|  |         } catch (e) { | ||||||
|  |             if (e instanceof GrabError) { | ||||||
|  |                 return new BuildingQueueInfo(0); | ||||||
|  |             } | ||||||
|  |             throw e; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import { clickUpgradeButton } from '../Page/BuildingPage'; | |||||||
| import { grabResourceDeposits } from '../Page/SlotBlock'; | import { grabResourceDeposits } from '../Page/SlotBlock'; | ||||||
| import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | ||||||
| import { ResourceDeposit } from '../Game'; | import { ResourceDeposit } from '../Game'; | ||||||
|  | import { aroundMinutes } from '../utils'; | ||||||
|  |  | ||||||
| @registerAction | @registerAction | ||||||
| export class UpgradeResourceToLevel extends ActionController { | export class UpgradeResourceToLevel extends ActionController { | ||||||
| @@ -35,19 +36,16 @@ export class UpgradeResourceToLevel extends ActionController { | |||||||
|                     task.args.buildId === dep.buildId |                     task.args.buildId === dep.buildId | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|         const available = deposits |         const notUpgraded = deposits.sort((x, y) => x.level - y.level).filter(isDepositTaskNotInQueue); | ||||||
|             .sort((x, y) => x.level - y.level) |  | ||||||
|             .filter(dep => dep.ready) |  | ||||||
|             .filter(isDepositTaskNotInQueue); |  | ||||||
|  |  | ||||||
|         if (available.length === 0) { |         if (notUpgraded.length === 0) { | ||||||
|             throw new TryLaterError(task.id, 10 * 60, 'No available deposits'); |             throw new TryLaterError(task.id, aroundMinutes(10), 'No available deposits'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const targetDep = available[0]; |         for (let dep of notUpgraded) { | ||||||
|  |             this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId: dep.buildId }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId: targetDep.buildId }); |         throw new TryLaterError(task.id, aroundMinutes(10), 'Sleep for next round'); | ||||||
|  |  | ||||||
|         throw new TryLaterError(task.id, 20 * 60, 'Sleep for next round'); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
|  | import { ResourcesInterface } from './Game'; | ||||||
|  |  | ||||||
| export interface Args { | export interface Args { | ||||||
|     villageId?: number; |     villageId?: number; | ||||||
|     buildId?: number; |     buildId?: number; | ||||||
|     categoryId?: number; |     categoryId?: number; | ||||||
|     buildTypeId?: number; |     buildTypeId?: number; | ||||||
|  |     resources?: ResourcesInterface; | ||||||
|     [name: string]: any; |     [name: string]: any; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,14 @@ export function numberToResourceType(typeAsNumber: number): ResourceType { | |||||||
|  |  | ||||||
| export type ResourceList = Array<{ num: number; type: ResourceType; value: number }>; | export type ResourceList = Array<{ num: number; type: ResourceType; value: number }>; | ||||||
|  |  | ||||||
| export class Resources { | export interface ResourcesInterface { | ||||||
|  |     lumber: number; | ||||||
|  |     clay: number; | ||||||
|  |     iron: number; | ||||||
|  |     crop: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class Resources implements ResourcesInterface { | ||||||
|     readonly lumber: number; |     readonly lumber: number; | ||||||
|     readonly clay: number; |     readonly clay: number; | ||||||
|     readonly iron: number; |     readonly iron: number; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { Scheduler } from '../Scheduler'; | |||||||
| import { TrainTroopTask } from '../Task/TrainTroopTask'; | import { TrainTroopTask } from '../Task/TrainTroopTask'; | ||||||
| import { grabActiveVillageId } from './VillageBlock'; | import { grabActiveVillageId } from './VillageBlock'; | ||||||
| import { ConsoleLogger, Logger } from '../Logger'; | import { ConsoleLogger, Logger } from '../Logger'; | ||||||
| import { createBuildButton, createUpgradeButton } from './BuildingPage'; | import { createBuildButton, createUpgradeButton, grabContractResources } from './BuildingPage'; | ||||||
| import { BuildBuildingTask } from '../Task/BuildBuildingTask'; | import { BuildBuildingTask } from '../Task/BuildBuildingTask'; | ||||||
|  |  | ||||||
| const QUARTERS_ID = 19; | const QUARTERS_ID = 19; | ||||||
| @@ -38,14 +38,16 @@ export class BuildPage { | |||||||
|         const buildId = this.buildId; |         const buildId = this.buildId; | ||||||
|         const categoryId = this.categoryId; |         const categoryId = this.categoryId; | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId }); |         const resources = grabContractResources(); | ||||||
|  |         this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId, resources }); | ||||||
|         notify(`Building ${buildId} scheduled`); |         notify(`Building ${buildId} scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onScheduleUpgradeBuilding() { |     private onScheduleUpgradeBuilding() { | ||||||
|         const buildId = this.buildId; |         const buildId = this.buildId; | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId }); |         const resources = grabContractResources(); | ||||||
|  |         this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId, resources }); | ||||||
|         notify(`Upgrading ${buildId} scheduled`); |         notify(`Upgrading ${buildId} scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { GrabError } from '../Errors'; | import { GrabError } from '../Errors'; | ||||||
| import { getNumber, trimPrefix, uniqId } from '../utils'; | import { getNumber, trimPrefix, uniqId } from '../utils'; | ||||||
|  | import { Resources } from '../Game'; | ||||||
|  |  | ||||||
| export function clickBuildButton(typeId: number) { | export function clickBuildButton(typeId: number) { | ||||||
|     const section = jQuery(`#contract_building${typeId}`); |     const section = jQuery(`#contract_building${typeId}`); | ||||||
| @@ -27,6 +28,11 @@ export function createBuildButton(onClickHandler: (buildTypeId: number) => void) | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function hasUpgradeButton(): boolean { | ||||||
|  |     const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); | ||||||
|  |     return btn.length === 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| export function clickUpgradeButton() { | export function clickUpgradeButton() { | ||||||
|     const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); |     const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); | ||||||
|     if (btn.length !== 1) { |     if (btn.length !== 1) { | ||||||
| @@ -45,3 +51,17 @@ export function createUpgradeButton(onClickHandler: () => void) { | |||||||
|         onClickHandler(); |         onClickHandler(); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function grabContractResources(): Resources { | ||||||
|  |     const $els = jQuery('#contract .resource'); | ||||||
|  |     if ($els.length === 0) { | ||||||
|  |         throw new GrabError('No resource contract element'); | ||||||
|  |     } | ||||||
|  |     const grab = n => | ||||||
|  |         getNumber( | ||||||
|  |             jQuery($els.get(n)) | ||||||
|  |                 .find('.value') | ||||||
|  |                 .text() | ||||||
|  |         ); | ||||||
|  |     return new Resources(grab(0), grab(1), grab(2), grab(3)); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ function grabVillageInfo($el): Village { | |||||||
| export function grabResourcesPerformance(): Resources { | export function grabResourcesPerformance(): Resources { | ||||||
|     const $el = jQuery('#production'); |     const $el = jQuery('#production'); | ||||||
|     if ($el.length !== 1) { |     if ($el.length !== 1) { | ||||||
|         throw new GrabError(); |         throw new GrabError('No production element'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const $nums = $el.find('td.num'); |     const $nums = $el.find('td.num'); | ||||||
| @@ -63,7 +63,7 @@ export function grabResourcesPerformance(): Resources { | |||||||
| export function grabBuildingQueueInfo(): BuildingQueueInfo { | export function grabBuildingQueueInfo(): BuildingQueueInfo { | ||||||
|     const timer = jQuery('.buildDuration .timer'); |     const timer = jQuery('.buildDuration .timer'); | ||||||
|     if (timer.length !== 1) { |     if (timer.length !== 1) { | ||||||
|         throw new GrabError(); |         throw new GrabError('No building queue timer element'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const remainingSeconds = getNumber(timer.attr('value')); |     const remainingSeconds = getNumber(timer.attr('value')); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { timestamp } from './utils'; | import { timestamp } from './utils'; | ||||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
| import { TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | ||||||
| import { Args, Command } from './Common'; | import { Args, Command } from './Common'; | ||||||
| import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; | import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; | ||||||
| import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; | import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; | ||||||
| @@ -50,7 +50,17 @@ export class Scheduler { | |||||||
|  |  | ||||||
|     scheduleTask(name: string, args: Args): void { |     scheduleTask(name: string, args: Args): void { | ||||||
|         this.logger.log('PUSH TASK', name, args); |         this.logger.log('PUSH TASK', name, args); | ||||||
|         this.taskQueue.push(name, args, timestamp()); |         const villageId = args.villageId; | ||||||
|  |         if (isBuildingTask(name)) { | ||||||
|  |             this.taskQueue.insertAfterLast( | ||||||
|  |                 t => isBuildingTask(t.name) && sameVillage(villageId, t.args), | ||||||
|  |                 name, | ||||||
|  |                 args, | ||||||
|  |                 timestamp() | ||||||
|  |             ); | ||||||
|  |         } else { | ||||||
|  |             this.taskQueue.push(name, args, timestamp()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     completeTask(id: TaskId) { |     completeTask(id: TaskId) { | ||||||
| @@ -69,11 +79,7 @@ export class Scheduler { | |||||||
|  |  | ||||||
|     postponeBuildingsInVillage(villageId: number, seconds: number) { |     postponeBuildingsInVillage(villageId: number, seconds: number) { | ||||||
|         this.taskQueue.modify( |         this.taskQueue.modify( | ||||||
|             t => t.name === BuildBuildingTask.name && t.args.villageId === villageId, |             t => isBuildingTask(t.name) && sameVillage(villageId, t.args), | ||||||
|             t => t.withTime(timestamp() + seconds) |  | ||||||
|         ); |  | ||||||
|         this.taskQueue.modify( |  | ||||||
|             t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId, |  | ||||||
|             t => t.withTime(timestamp() + seconds) |             t => t.withTime(timestamp() + seconds) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -86,3 +92,11 @@ export class Scheduler { | |||||||
|         this.actionQueue.clear(); |         this.actionQueue.clear(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isBuildingTask(taskName: string) { | ||||||
|  |     return taskName === BuildBuildingTask.name || taskName === UpgradeBuildingTask.name; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function sameVillage(villageId: number | undefined, args: Args) { | ||||||
|  |     return villageId !== undefined && args.villageId === villageId; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ import { StateGrabber } from './StateGrabber'; | |||||||
| import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; | import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; | ||||||
| import { VillageState } from './VillageState'; | import { VillageState } from './VillageState'; | ||||||
| import { parseLocation } from '../utils'; | import { parseLocation } from '../utils'; | ||||||
|  | import { GrabError } from '../Errors'; | ||||||
|  | import { BuildingQueueInfo } from '../Game'; | ||||||
|  |  | ||||||
| export class VillageOverviewPageGrabber extends StateGrabber { | export class VillageOverviewPageGrabber extends StateGrabber { | ||||||
|     grab(): void { |     grab(): void { | ||||||
| @@ -13,6 +15,17 @@ export class VillageOverviewPageGrabber extends StateGrabber { | |||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         const state = new VillageState(villageId); |         const state = new VillageState(villageId); | ||||||
|         state.storeResourcesPerformance(grabResourcesPerformance()); |         state.storeResourcesPerformance(grabResourcesPerformance()); | ||||||
|         state.storeBuildingQueueInfo(grabBuildingQueueInfo()); |         state.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private grabBuildingQueueInfoOrDefault() { | ||||||
|  |         try { | ||||||
|  |             return grabBuildingQueueInfo(); | ||||||
|  |         } catch (e) { | ||||||
|  |             if (e instanceof GrabError) { | ||||||
|  |                 return new BuildingQueueInfo(0); | ||||||
|  |             } | ||||||
|  |             throw e; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -50,6 +50,15 @@ export class TaskQueue { | |||||||
|         return task; |         return task; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     insertAfterLast(predicate: (t: Task) => boolean, name: string, args: Args, ts: number): Task { | ||||||
|  |         const id = uniqTaskId(); | ||||||
|  |         const task = new Task(id, ts, name, args); | ||||||
|  |         this.logger.log('INSERT AFTER TASK', id, ts, name, args); | ||||||
|  |         const items = insertTaskAfter(task, this.getItems(), predicate); | ||||||
|  |         this.flushItems(items); | ||||||
|  |         return task; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get(ts: number): Task | undefined { |     get(ts: number): Task | undefined { | ||||||
|         const readyItems = this.getItems().filter(t => t.ts <= ts); |         const readyItems = this.getItems().filter(t => t.ts <= ts); | ||||||
|         if (readyItems.length === 0) { |         if (readyItems.length === 0) { | ||||||
| @@ -133,3 +142,27 @@ export class TaskQueue { | |||||||
|         this.storage.set(QUEUE_NAME, normalized); |         this.storage.set(QUEUE_NAME, normalized); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function insertTaskAfter(task: Task, tasks: TaskList, predicate: (t: Task) => boolean): TaskList { | ||||||
|  |     const queuedTaskIndex = findLastIndex(tasks, predicate); | ||||||
|  |     if (queuedTaskIndex === undefined) { | ||||||
|  |         tasks.push(task); | ||||||
|  |         return tasks; | ||||||
|  |     } | ||||||
|  |     const queuedTask = tasks[queuedTaskIndex]; | ||||||
|  |     let insertedTask = task.withTime(Math.max(task.ts, queuedTask.ts + 1)); | ||||||
|  |     tasks.splice(queuedTaskIndex, 0, insertedTask); | ||||||
|  |     return tasks; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function findLastIndex(tasks: TaskList, predicate: (t: Task) => boolean): number | undefined { | ||||||
|  |     const count = tasks.length; | ||||||
|  |     const indexInReversed = tasks | ||||||
|  |         .slice() | ||||||
|  |         .reverse() | ||||||
|  |         .findIndex(predicate); | ||||||
|  |     if (indexInReversed < 0) { | ||||||
|  |         return undefined; | ||||||
|  |     } | ||||||
|  |     return count - 1 - indexInReversed; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -22,6 +22,12 @@ export async function sleepLong() { | |||||||
|     return await sleep(ms); |     return await sleep(ms); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function aroundMinutes(minutes: number) { | ||||||
|  |     const seconds = minutes * 60; | ||||||
|  |     const delta = Math.floor(seconds * 0.9); | ||||||
|  |     return seconds - delta + Math.floor(Math.random() * 2 * delta); | ||||||
|  | } | ||||||
|  |  | ||||||
| export async function waitForLoad() { | export async function waitForLoad() { | ||||||
|     return new Promise(resolve => jQuery(resolve)); |     return new Promise(resolve => jQuery(resolve)); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user