Rebuild village production queue system
This commit is contained in:
		| @@ -5,6 +5,6 @@ import { Task } from '../Queue/TaskProvider'; | |||||||
| @registerAction | @registerAction | ||||||
| export class CompleteTaskAction extends ActionController { | export class CompleteTaskAction extends ActionController { | ||||||
|     async run(args: Args, task: Task): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         this.scheduler.removeTask(task.id); |         this.scheduler.completeTask(task.id); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ import { StatisticsStorage } from './Storage/StatisticsStorage'; | |||||||
| import { VillageRepository } from './VillageRepository'; | import { VillageRepository } from './VillageRepository'; | ||||||
| import { VillageStateRepository } from './VillageState'; | import { VillageStateRepository } from './VillageState'; | ||||||
| import { LogStorage } from './Storage/LogStorage'; | import { LogStorage } from './Storage/LogStorage'; | ||||||
|  | import { VillageControllerFactory } from './VillageControllerFactory'; | ||||||
|  | import { GrabberManager } from './Grabber/GrabberManager'; | ||||||
|  |  | ||||||
| export class Container { | export class Container { | ||||||
|     private readonly version: string; |     private readonly version: string; | ||||||
| @@ -40,16 +42,35 @@ export class Container { | |||||||
|         return this._statistics; |         return this._statistics; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private _villageControllerFactory: VillageControllerFactory | undefined; | ||||||
|  |  | ||||||
|  |     get villageControllerFactory(): VillageControllerFactory { | ||||||
|  |         this._villageControllerFactory = | ||||||
|  |             this._villageControllerFactory || | ||||||
|  |             (() => { | ||||||
|  |                 return new VillageControllerFactory(this.villageRepository); | ||||||
|  |             })(); | ||||||
|  |         return this._villageControllerFactory; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private _scheduler: Scheduler | undefined; |     private _scheduler: Scheduler | undefined; | ||||||
|  |  | ||||||
|     get scheduler(): Scheduler { |     get scheduler(): Scheduler { | ||||||
|         this._scheduler = |         this._scheduler = | ||||||
|             this._scheduler || |             this._scheduler || | ||||||
|             (() => { |             (() => { | ||||||
|                 const taskProvider = DataStorageTaskProvider.create(); |                 const taskQueue = new TaskQueue( | ||||||
|                 const taskQueue = new TaskQueue(taskProvider, new ConsoleLogger(TaskQueue.name)); |                     DataStorageTaskProvider.create('tasks:v1'), | ||||||
|  |                     new ConsoleLogger(TaskQueue.name) | ||||||
|  |                 ); | ||||||
|                 const actionQueue = new ActionQueue(); |                 const actionQueue = new ActionQueue(); | ||||||
|                 return new Scheduler(taskQueue, actionQueue, this.villageRepository, new ConsoleLogger(Scheduler.name)); |                 return new Scheduler( | ||||||
|  |                     taskQueue, | ||||||
|  |                     actionQueue, | ||||||
|  |                     this.villageRepository, | ||||||
|  |                     this.villageControllerFactory, | ||||||
|  |                     new ConsoleLogger(Scheduler.name) | ||||||
|  |                 ); | ||||||
|             })(); |             })(); | ||||||
|         return this._scheduler; |         return this._scheduler; | ||||||
|     } |     } | ||||||
| @@ -60,11 +81,22 @@ export class Container { | |||||||
|         this._villageStateRepository = |         this._villageStateRepository = | ||||||
|             this._villageStateRepository || |             this._villageStateRepository || | ||||||
|             (() => { |             (() => { | ||||||
|                 return new VillageStateRepository(this.villageRepository, this.scheduler); |                 return new VillageStateRepository(this.villageRepository, this.villageControllerFactory); | ||||||
|             })(); |             })(); | ||||||
|         return this._villageStateRepository; |         return this._villageStateRepository; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private _grabberManager: GrabberManager | undefined; | ||||||
|  |  | ||||||
|  |     get grabberManager(): GrabberManager { | ||||||
|  |         this._grabberManager = | ||||||
|  |             this._grabberManager || | ||||||
|  |             (() => { | ||||||
|  |                 return new GrabberManager(this.villageControllerFactory); | ||||||
|  |             })(); | ||||||
|  |         return this._grabberManager; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private _executor: Executor | undefined; |     private _executor: Executor | undefined; | ||||||
|  |  | ||||||
|     get executor(): Executor { |     get executor(): Executor { | ||||||
| @@ -75,7 +107,15 @@ export class Container { | |||||||
|                     new ConsoleLogger(Executor.name), |                     new ConsoleLogger(Executor.name), | ||||||
|                     new StorageLogger(new LogStorage(), LogLevel.warning), |                     new StorageLogger(new LogStorage(), LogLevel.warning), | ||||||
|                 ]); |                 ]); | ||||||
|                 return new Executor(this.version, this.scheduler, this.villageStateRepository, this.statistics, logger); |                 return new Executor( | ||||||
|  |                     this.version, | ||||||
|  |                     this.scheduler, | ||||||
|  |                     this.villageStateRepository, | ||||||
|  |                     this.villageControllerFactory, | ||||||
|  |                     this.grabberManager, | ||||||
|  |                     this.statistics, | ||||||
|  |                     logger | ||||||
|  |                 ); | ||||||
|             })(); |             })(); | ||||||
|         return this._executor; |         return this._executor; | ||||||
|     } |     } | ||||||
| @@ -86,7 +126,12 @@ export class Container { | |||||||
|         this._controlPanel = |         this._controlPanel = | ||||||
|             this._controlPanel || |             this._controlPanel || | ||||||
|             (() => { |             (() => { | ||||||
|                 return new ControlPanel(this.version, this.scheduler, this.villageStateRepository); |                 return new ControlPanel( | ||||||
|  |                     this.version, | ||||||
|  |                     this.scheduler, | ||||||
|  |                     this.villageStateRepository, | ||||||
|  |                     this.villageControllerFactory | ||||||
|  |                 ); | ||||||
|             })(); |             })(); | ||||||
|         return this._controlPanel; |         return this._controlPanel; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import { VillageState, VillageStateRepository } from './VillageState'; | |||||||
| import { Task } from './Queue/TaskProvider'; | import { Task } from './Queue/TaskProvider'; | ||||||
| import { Action } from './Queue/ActionQueue'; | import { Action } from './Queue/ActionQueue'; | ||||||
| import { createStore } from './DashboardView/Store'; | import { createStore } from './DashboardView/Store'; | ||||||
|  | import { VillageControllerFactory } from './VillageControllerFactory'; | ||||||
|  |  | ||||||
| Vue.use(Vuex); | Vue.use(Vuex); | ||||||
|  |  | ||||||
| @@ -53,11 +54,18 @@ export class ControlPanel { | |||||||
|     private readonly scheduler: Scheduler; |     private readonly scheduler: Scheduler; | ||||||
|     private readonly villageStateRepository: VillageStateRepository; |     private readonly villageStateRepository: VillageStateRepository; | ||||||
|     private readonly logger: Logger; |     private readonly logger: Logger; | ||||||
|  |     private villageControllerFactory: VillageControllerFactory; | ||||||
|  |  | ||||||
|     constructor(version: string, scheduler: Scheduler, villageStateRepository: VillageStateRepository) { |     constructor( | ||||||
|  |         version: string, | ||||||
|  |         scheduler: Scheduler, | ||||||
|  |         villageStateRepository: VillageStateRepository, | ||||||
|  |         villageControllerFactory: VillageControllerFactory | ||||||
|  |     ) { | ||||||
|         this.version = version; |         this.version = version; | ||||||
|         this.scheduler = scheduler; |         this.scheduler = scheduler; | ||||||
|         this.villageStateRepository = villageStateRepository; |         this.villageStateRepository = villageStateRepository; | ||||||
|  |         this.villageControllerFactory = villageControllerFactory; | ||||||
|         this.logger = new ConsoleLogger(this.constructor.name); |         this.logger = new ConsoleLogger(this.constructor.name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -128,9 +136,10 @@ export class ControlPanel { | |||||||
|         DataStorage.onChange(() => state.refresh()); |         DataStorage.onChange(() => state.refresh()); | ||||||
|  |  | ||||||
|         const getBuildingsInQueue = () => |         const getBuildingsInQueue = () => | ||||||
|             this.scheduler |             this.villageControllerFactory | ||||||
|                 .getTaskItems() |                 .create(villageId) | ||||||
|                 .filter(t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId) |                 .getTasks() | ||||||
|  |                 .filter(t => t.name === UpgradeBuildingTask.name) | ||||||
|                 .map(t => t.args.buildId || 0); |                 .map(t => t.args.buildId || 0); | ||||||
|  |  | ||||||
|         if (p.pathname === '/dorf1.php') { |         if (p.pathname === '/dorf1.php') { | ||||||
| @@ -151,7 +160,11 @@ export class ControlPanel { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (isBuildingPage()) { |         if (isBuildingPage()) { | ||||||
|             const buildPage = new BuildingPageController(this.scheduler, getBuildingPageAttributes()); |             const buildPage = new BuildingPageController( | ||||||
|  |                 this.scheduler, | ||||||
|  |                 getBuildingPageAttributes(), | ||||||
|  |                 this.villageControllerFactory.create(villageId) | ||||||
|  |             ); | ||||||
|             buildPage.run(); |             buildPage.run(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/Core/Contract.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Core/Contract.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | export enum ContractType { | ||||||
|  |     UpgradeBuilding, | ||||||
|  |     ImproveTrooper, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface ContractAttributes { | ||||||
|  |     type: ContractType; | ||||||
|  |     buildId?: number; | ||||||
|  |     unitId?: number; | ||||||
|  | } | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import { getProductionQueue } from '../Task/TaskMap'; | ||||||
|  |  | ||||||
| export enum ProductionQueue { | export enum ProductionQueue { | ||||||
|     Building = 'building', |     Building = 'building', | ||||||
|     TrainUnit = 'train_unit', |     TrainUnit = 'train_unit', | ||||||
| @@ -27,3 +29,18 @@ export function translateProductionQueue(queue: ProductionQueue): string { | |||||||
|             return 'Празднование'; |             return 'Празднование'; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface TaskNamePredicate { | ||||||
|  |     (name: string): boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * List on non intersected task queue predicates. | ||||||
|  |  */ | ||||||
|  | export const TASK_TYPE_PREDICATES: Array<TaskNamePredicate> = ProductionQueueTypes.map(queue => { | ||||||
|  |     return (taskName: string) => getProductionQueue(taskName) === queue; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export function isProductionTask(taskName: string): boolean { | ||||||
|  |     return TASK_TYPE_PREDICATES.reduce((memo, predicate) => memo || predicate(taskName), false); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import { Action } from './Queue/ActionQueue'; | |||||||
| import { Task } from './Queue/TaskProvider'; | import { Task } from './Queue/TaskProvider'; | ||||||
| import { createTaskHandler } from './Task/TaskMap'; | import { createTaskHandler } from './Task/TaskMap'; | ||||||
| import { VillageStateRepository } from './VillageState'; | import { VillageStateRepository } from './VillageState'; | ||||||
|  | import { VillageControllerFactory } from './VillageControllerFactory'; | ||||||
|  |  | ||||||
| export interface ExecutionSettings { | export interface ExecutionSettings { | ||||||
|     pauseTs: number; |     pauseTs: number; | ||||||
| @@ -20,7 +21,8 @@ export class Executor { | |||||||
|     private readonly version: string; |     private readonly version: string; | ||||||
|     private readonly scheduler: Scheduler; |     private readonly scheduler: Scheduler; | ||||||
|     private readonly villageStateRepository: VillageStateRepository; |     private readonly villageStateRepository: VillageStateRepository; | ||||||
|     private grabbers: GrabberManager; |     private villageControllerFactory: VillageControllerFactory; | ||||||
|  |     private grabberManager: GrabberManager; | ||||||
|     private statistics: Statistics; |     private statistics: Statistics; | ||||||
|     private executionState: ExecutionStorage; |     private executionState: ExecutionStorage; | ||||||
|     private logger: Logger; |     private logger: Logger; | ||||||
| @@ -29,13 +31,16 @@ export class Executor { | |||||||
|         version: string, |         version: string, | ||||||
|         scheduler: Scheduler, |         scheduler: Scheduler, | ||||||
|         villageStateRepository: VillageStateRepository, |         villageStateRepository: VillageStateRepository, | ||||||
|  |         villageControllerFactory: VillageControllerFactory, | ||||||
|  |         grabberManager: GrabberManager, | ||||||
|         statistics: Statistics, |         statistics: Statistics, | ||||||
|         logger: Logger |         logger: Logger | ||||||
|     ) { |     ) { | ||||||
|         this.version = version; |         this.version = version; | ||||||
|         this.scheduler = scheduler; |         this.scheduler = scheduler; | ||||||
|         this.villageStateRepository = villageStateRepository; |         this.villageStateRepository = villageStateRepository; | ||||||
|         this.grabbers = new GrabberManager(scheduler); |         this.villageControllerFactory = villageControllerFactory; | ||||||
|  |         this.grabberManager = grabberManager; | ||||||
|         this.statistics = statistics; |         this.statistics = statistics; | ||||||
|         this.executionState = new ExecutionStorage(); |         this.executionState = new ExecutionStorage(); | ||||||
|         this.logger = logger; |         this.logger = logger; | ||||||
| @@ -49,6 +54,7 @@ export class Executor { | |||||||
|  |  | ||||||
|         const sleep = createExecutionLoopSleeper(); |         const sleep = createExecutionLoopSleeper(); | ||||||
|  |  | ||||||
|  |         // noinspection InfiniteLoopJS | ||||||
|         while (true) { |         while (true) { | ||||||
|             await sleep(); |             await sleep(); | ||||||
|             if (!this.isPaused()) { |             if (!this.isPaused()) { | ||||||
| @@ -76,25 +82,22 @@ export class Executor { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async doTaskProcessingStep() { |     private async doTaskProcessingStep() { | ||||||
|  |         this.runGrabbers(); | ||||||
|  |  | ||||||
|         const currentTs = timestamp(); |         const currentTs = timestamp(); | ||||||
|         const task = this.scheduler.nextTask(currentTs); |         const { task, action } = this.scheduler.nextTask(currentTs); | ||||||
|  |  | ||||||
|         // текущего таска нет, очищаем очередь действий по таску |         // текущего таска нет, очищаем очередь действий по таску | ||||||
|         if (!task) { |         if (!task) { | ||||||
|             this.logger.info('NO ACTIVE TASK'); |             this.logger.info('NO ACTIVE TASK'); | ||||||
|             this.scheduler.clearActions(); |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const actionCommand = this.scheduler.nextAction(); |         this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action); | ||||||
|  |  | ||||||
|         this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', actionCommand); |  | ||||||
|  |  | ||||||
|         this.runGrabbers(); |  | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             if (actionCommand) { |             if (task && action) { | ||||||
|                 return await this.processActionCommand(actionCommand, task); |                 return await this.processActionCommand(action, task); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (task) { |             if (task) { | ||||||
| @@ -105,27 +108,24 @@ export class Executor { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async processActionCommand(cmd: Action, task: Task) { |     private async processActionCommand(action: Action, task: Task) { | ||||||
|         const actionHandler = createActionHandler(cmd.name, this.scheduler, this.villageStateRepository); |         const actionHandler = createActionHandler(action.name, this.scheduler, this.villageStateRepository); | ||||||
|         this.logger.info('PROCESS ACTION', cmd.name, actionHandler); |         this.logger.info('Process action', action.name, actionHandler); | ||||||
|         if (cmd.args.taskId !== task.id) { |  | ||||||
|             throw new ActionError(`Action task id ${cmd.args.taskId} not equal current task id ${task.id}`); |  | ||||||
|         } |  | ||||||
|         if (actionHandler) { |         if (actionHandler) { | ||||||
|             this.statistics.incrementAction(timestamp()); |             this.statistics.incrementAction(timestamp()); | ||||||
|             await actionHandler.run(cmd.args, task); |             await actionHandler.run(action.args, task); | ||||||
|         } else { |         } else { | ||||||
|             this.logger.warn('ACTION NOT FOUND', cmd.name); |             this.logger.error('Action not found', action.name); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async processTaskCommand(task: Task) { |     private async processTaskCommand(task: Task) { | ||||||
|         const taskHandler = createTaskHandler(task.name, this.scheduler); |         const taskHandler = createTaskHandler(task.name, this.scheduler); | ||||||
|         this.logger.info('PROCESS TASK', task.name, task, taskHandler); |         this.logger.info('Process task', task.name, task, taskHandler); | ||||||
|         if (taskHandler) { |         if (taskHandler) { | ||||||
|             await taskHandler.run(task); |             await taskHandler.run(task); | ||||||
|         } else { |         } else { | ||||||
|             this.logger.warn('TASK NOT FOUND', task.name); |             this.logger.error('Task handler not created', task.name); | ||||||
|             this.scheduler.removeTask(task.id); |             this.scheduler.removeTask(task.id); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -174,7 +174,7 @@ export class Executor { | |||||||
|     private runGrabbers() { |     private runGrabbers() { | ||||||
|         try { |         try { | ||||||
|             this.logger.info('Rug grabbers'); |             this.logger.info('Rug grabbers'); | ||||||
|             this.grabbers.grab(); |             this.grabberManager.grab(); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             this.logger.warn('Grabbers fails with', e.message); |             this.logger.warn('Grabbers fails with', e.message); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; |  | ||||||
| import { getBuildingPageAttributes, isBuildingPage } from '../Page/PageDetectors'; | import { getBuildingPageAttributes, isBuildingPage } from '../Page/PageDetectors'; | ||||||
| import { grabContractResources, hasContractResources } from '../Page/BuildingPage/BuildingPage'; | import { grabContractResources, hasContractResources } from '../Page/BuildingPage/BuildingPage'; | ||||||
| import { ContractType } from '../Scheduler'; | import { ContractType } from '../Core/Contract'; | ||||||
|  |  | ||||||
| export class BuildingContractGrabber extends Grabber { | export class BuildingContractGrabber extends Grabber { | ||||||
|     grab(): void { |     grab(): void { | ||||||
| @@ -19,12 +18,10 @@ export class BuildingContractGrabber extends Grabber { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = grabActiveVillageId(); |  | ||||||
|         const contract = grabContractResources(); |         const contract = grabContractResources(); | ||||||
|  |  | ||||||
|         this.scheduler.updateResources(contract, { |         this.controller.updateResources(contract, { | ||||||
|             type: ContractType.UpgradeBuilding, |             type: ContractType.UpgradeBuilding, | ||||||
|             villageId, |  | ||||||
|             buildId: building.buildId, |             buildId: building.buildId, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; |  | ||||||
| import { getBuildingPageAttributes, isForgePage } from '../Page/PageDetectors'; | import { getBuildingPageAttributes, isForgePage } from '../Page/PageDetectors'; | ||||||
| import { ContractType } from '../Scheduler'; | import { ContractType } from '../Core/Contract'; | ||||||
| import { grabImprovementContracts, grabRemainingSeconds } from '../Page/BuildingPage/ForgePage'; | import { grabImprovementContracts, grabRemainingSeconds } from '../Page/BuildingPage/ForgePage'; | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; |  | ||||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | import { ProductionQueue } from '../Core/ProductionQueue'; | ||||||
| import { timestamp } from '../utils'; | import { timestamp } from '../utils'; | ||||||
|  |  | ||||||
| @@ -13,29 +11,26 @@ export class ForgePageGrabber extends Grabber { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = grabActiveVillageId(); |         this.grabContracts(); | ||||||
|  |         this.grabTimer(); | ||||||
|         this.grabContracts(villageId); |  | ||||||
|         this.grabTimer(villageId); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private grabContracts(villageId: number): void { |     private grabContracts(): void { | ||||||
|         const { buildId } = getBuildingPageAttributes(); |         const { buildId } = getBuildingPageAttributes(); | ||||||
|         const contracts = grabImprovementContracts(); |         const contracts = grabImprovementContracts(); | ||||||
|  |  | ||||||
|         for (let { resources, unitId } of contracts) { |         for (let { resources, unitId } of contracts) { | ||||||
|             this.scheduler.updateResources(resources, { |             this.controller.updateResources(resources, { | ||||||
|                 type: ContractType.ImproveTrooper, |                 type: ContractType.ImproveTrooper, | ||||||
|                 villageId, |  | ||||||
|                 buildId, |                 buildId, | ||||||
|                 unitId, |                 unitId, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private grabTimer(villageId: number): void { |     private grabTimer(): void { | ||||||
|         const state = new VillageStorage(villageId); |         const storage = this.controller.getStorage(); | ||||||
|         const seconds = grabRemainingSeconds(); |         const seconds = grabRemainingSeconds(); | ||||||
|         state.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0); |         storage.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import { Scheduler } from '../Scheduler'; | import { VillageController } from '../VillageController'; | ||||||
|  |  | ||||||
| export abstract class Grabber { | export abstract class Grabber { | ||||||
|     protected scheduler: Scheduler; |     protected controller: VillageController; | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler) { |     constructor(controller: VillageController) { | ||||||
|         this.scheduler = scheduler; |         this.controller = controller; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     abstract grab(): void; |     abstract grab(): void; | ||||||
|   | |||||||
| @@ -3,28 +3,35 @@ import { VillageResourceGrabber } from './VillageResourceGrabber'; | |||||||
| import { VillageOverviewPageGrabber } from './VillageOverviewPageGrabber'; | import { VillageOverviewPageGrabber } from './VillageOverviewPageGrabber'; | ||||||
| import { HeroPageGrabber } from './HeroPageGrabber'; | import { HeroPageGrabber } from './HeroPageGrabber'; | ||||||
| import { MarketPageGrabber } from './MarketPageGrabber'; | import { MarketPageGrabber } from './MarketPageGrabber'; | ||||||
| import { Scheduler } from '../Scheduler'; |  | ||||||
| import { BuildingContractGrabber } from './BuildingContractGrabber'; | import { BuildingContractGrabber } from './BuildingContractGrabber'; | ||||||
| import { ForgePageGrabber } from './ForgePageGrabber'; | import { ForgePageGrabber } from './ForgePageGrabber'; | ||||||
| import { GuildHallPageGrabber } from './GuildHallPageGrabber'; | import { GuildHallPageGrabber } from './GuildHallPageGrabber'; | ||||||
|  | import { VillageControllerFactory } from '../VillageControllerFactory'; | ||||||
|  |  | ||||||
| export class GrabberManager { | export class GrabberManager { | ||||||
|     private readonly grabbers: Array<Grabber> = []; |     private factory: VillageControllerFactory; | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler) { |     constructor(factory: VillageControllerFactory) { | ||||||
|         this.grabbers = []; |         this.factory = factory; | ||||||
|         this.grabbers.push(new VillageResourceGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new VillageOverviewPageGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new HeroPageGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new MarketPageGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new BuildingContractGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new ForgePageGrabber(scheduler)); |  | ||||||
|         this.grabbers.push(new GuildHallPageGrabber(scheduler)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     grab() { |     grab() { | ||||||
|         for (let grabber of this.grabbers) { |         const grabbers = this.createGrabbers(); | ||||||
|  |         for (let grabber of grabbers) { | ||||||
|             grabber.grab(); |             grabber.grab(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private createGrabbers(): Array<Grabber> { | ||||||
|  |         const controller = this.factory.getActive(); | ||||||
|  |         const grabbers: Array<Grabber> = []; | ||||||
|  |         grabbers.push(new VillageResourceGrabber(controller)); | ||||||
|  |         grabbers.push(new VillageOverviewPageGrabber(controller)); | ||||||
|  |         grabbers.push(new HeroPageGrabber(controller)); | ||||||
|  |         grabbers.push(new MarketPageGrabber(controller)); | ||||||
|  |         grabbers.push(new BuildingContractGrabber(controller)); | ||||||
|  |         grabbers.push(new ForgePageGrabber(controller)); | ||||||
|  |         grabbers.push(new GuildHallPageGrabber(controller)); | ||||||
|  |         return grabbers; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; |  | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; |  | ||||||
| import { isGuildHallPage } from '../Page/PageDetectors'; | import { isGuildHallPage } from '../Page/PageDetectors'; | ||||||
| import { grabRemainingSeconds } from '../Page/BuildingPage/GuildHallPage'; | import { grabRemainingSeconds } from '../Page/BuildingPage/GuildHallPage'; | ||||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | import { ProductionQueue } from '../Core/ProductionQueue'; | ||||||
| @@ -12,9 +10,8 @@ export class GuildHallPageGrabber extends Grabber { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = grabActiveVillageId(); |  | ||||||
|         const state = new VillageStorage(villageId); |  | ||||||
|         const seconds = grabRemainingSeconds(); |         const seconds = grabRemainingSeconds(); | ||||||
|         state.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0); |         const storage = this.controller.getStorage(); | ||||||
|  |         storage.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; |  | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; |  | ||||||
| import { isMarketSendResourcesPage } from '../Page/PageDetectors'; | import { isMarketSendResourcesPage } from '../Page/PageDetectors'; | ||||||
| import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage'; | import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage'; | ||||||
|  |  | ||||||
| @@ -10,8 +8,7 @@ export class MarketPageGrabber extends Grabber { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = grabActiveVillageId(); |         const storage = this.controller.getStorage(); | ||||||
|         const state = new VillageStorage(villageId); |         storage.storeIncomingMerchants(grabIncomingMerchants()); | ||||||
|         state.storeIncomingMerchants(grabIncomingMerchants()); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; | import { grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; |  | ||||||
| import { parseLocation, timestamp } from '../utils'; | import { parseLocation, timestamp } from '../utils'; | ||||||
| import { GrabError } from '../Errors'; | import { GrabError } from '../Errors'; | ||||||
| import { BuildingQueueInfo } from '../Game'; | import { BuildingQueueInfo } from '../Game'; | ||||||
| @@ -13,8 +12,7 @@ export class VillageOverviewPageGrabber extends Grabber { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = grabActiveVillageId(); |         const storage = this.controller.getStorage(); | ||||||
|         const storage = new VillageStorage(villageId); |  | ||||||
|         storage.storeResourcesPerformance(grabResourcesPerformance()); |         storage.storeResourcesPerformance(grabResourcesPerformance()); | ||||||
|         storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); |         storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; |  | ||||||
| import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock'; | import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock'; | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; |  | ||||||
|  |  | ||||||
| export class VillageResourceGrabber extends Grabber { | export class VillageResourceGrabber extends Grabber { | ||||||
|     grab(): void { |     grab(): void { | ||||||
|         const villageId = grabActiveVillageId(); |         const storage = this.controller.getStorage(); | ||||||
|         const state = new VillageStorage(villageId); |         storage.storeResources(grabVillageResources()); | ||||||
|         state.storeResources(grabVillageResources()); |         storage.storeResourceStorage(grabVillageResourceStorage()); | ||||||
|         state.storeResourceStorage(grabVillageResourceStorage()); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { notify, split } from '../utils'; | import { notify } 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'; | ||||||
| @@ -17,15 +17,18 @@ import { createResearchButtons } from './BuildingPage/ForgePage'; | |||||||
| import { ForgeImprovementTask } from '../Task/ForgeImprovementTask'; | import { ForgeImprovementTask } from '../Task/ForgeImprovementTask'; | ||||||
| import { createCelebrationButtons } from './BuildingPage/GuildHallPage'; | import { createCelebrationButtons } from './BuildingPage/GuildHallPage'; | ||||||
| import { CelebrationTask } from '../Task/CelebrationTask'; | import { CelebrationTask } from '../Task/CelebrationTask'; | ||||||
|  | import { VillageController } from '../VillageController'; | ||||||
|  |  | ||||||
| export class BuildingPageController { | export class BuildingPageController { | ||||||
|     private scheduler: Scheduler; |     private scheduler: Scheduler; | ||||||
|     private readonly attributes: BuildingPageAttributes; |     private readonly attributes: BuildingPageAttributes; | ||||||
|  |     private villageController: VillageController; | ||||||
|     private readonly logger: Logger; |     private readonly logger: Logger; | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler, attributes: BuildingPageAttributes) { |     constructor(scheduler: Scheduler, attributes: BuildingPageAttributes, villageController: VillageController) { | ||||||
|         this.scheduler = scheduler; |         this.scheduler = scheduler; | ||||||
|         this.attributes = attributes; |         this.attributes = attributes; | ||||||
|  |         this.villageController = villageController; | ||||||
|         this.logger = new ConsoleLogger(this.constructor.name); |         this.logger = new ConsoleLogger(this.constructor.name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -56,7 +59,7 @@ export class BuildingPageController { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (isMarketSendResourcesPage()) { |         if (isMarketSendResourcesPage()) { | ||||||
|             createSendResourcesButton((res, crd) => this.onSendResources(res, crd)); |             createSendResourcesButton((res, crd) => this.onSendResources(crd)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (isForgePage()) { |         if (isForgePage()) { | ||||||
| @@ -64,7 +67,7 @@ export class BuildingPageController { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (isGuildHallPage()) { |         if (isGuildHallPage()) { | ||||||
|             createCelebrationButtons((res, idx) => this.onCelebration(res, idx)); |             createCelebrationButtons(res => this.onCelebration(res)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -72,14 +75,20 @@ export class BuildingPageController { | |||||||
|         const buildId = this.attributes.buildId; |         const buildId = this.attributes.buildId; | ||||||
|         const categoryId = this.attributes.categoryId; |         const categoryId = this.attributes.categoryId; | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId, resources }); |         this.villageController.addTask(BuildBuildingTask.name, { | ||||||
|  |             villageId, | ||||||
|  |             buildId, | ||||||
|  |             categoryId, | ||||||
|  |             buildTypeId, | ||||||
|  |             resources, | ||||||
|  |         }); | ||||||
|         notify(`Building ${buildId} scheduled`); |         notify(`Building ${buildId} scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onScheduleUpgradeBuilding(resources: Resources) { |     private onScheduleUpgradeBuilding(resources: Resources) { | ||||||
|         const buildId = this.attributes.buildId; |         const buildId = this.attributes.buildId; | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId, resources }); |         this.villageController.addTask(UpgradeBuildingTask.name, { villageId, buildId, resources }); | ||||||
|         notify(`Upgrading ${buildId} scheduled`); |         notify(`Upgrading ${buildId} scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -94,11 +103,11 @@ export class BuildingPageController { | |||||||
|             troopResources: resources, |             troopResources: resources, | ||||||
|             resources: resources.scale(trainCount), |             resources: resources.scale(trainCount), | ||||||
|         }; |         }; | ||||||
|         this.scheduler.scheduleTask(TrainTroopTask.name, args); |         this.villageController.addTask(TrainTroopTask.name, args); | ||||||
|         notify(`Training ${trainCount} troopers scheduled`); |         notify(`Training ${trainCount} troopers scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onSendResources(resources: Resources, coordinates: Coordinates) { |     private onSendResources(coordinates: Coordinates) { | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         const targetVillage = grabVillageList().find(v => v.crd.eq(coordinates)); |         const targetVillage = grabVillageList().find(v => v.crd.eq(coordinates)); | ||||||
|         this.scheduler.scheduleTask(SendResourcesTask.name, { |         this.scheduler.scheduleTask(SendResourcesTask.name, { | ||||||
| @@ -114,7 +123,7 @@ export class BuildingPageController { | |||||||
|  |  | ||||||
|     private onResearch(resources: Resources, unitId: number) { |     private onResearch(resources: Resources, unitId: number) { | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(ForgeImprovementTask.name, { |         this.villageController.addTask(ForgeImprovementTask.name, { | ||||||
|             villageId, |             villageId, | ||||||
|             buildTypeId: this.attributes.buildTypeId, |             buildTypeId: this.attributes.buildTypeId, | ||||||
|             buildId: this.attributes.buildId, |             buildId: this.attributes.buildId, | ||||||
| @@ -124,9 +133,9 @@ export class BuildingPageController { | |||||||
|         notify(`Researching ${unitId} scheduled`); |         notify(`Researching ${unitId} scheduled`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onCelebration(resources: Resources, idx: number) { |     private onCelebration(resources: Resources) { | ||||||
|         const villageId = grabActiveVillageId(); |         const villageId = grabActiveVillageId(); | ||||||
|         this.scheduler.scheduleTask(CelebrationTask.name, { |         this.villageController.addTask(CelebrationTask.name, { | ||||||
|             villageId, |             villageId, | ||||||
|             buildTypeId: this.attributes.buildTypeId, |             buildTypeId: this.attributes.buildTypeId, | ||||||
|             buildId: this.attributes.buildId, |             buildId: this.attributes.buildId, | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import { DataStorage } from '../DataStorage'; | import { DataStorage } from '../DataStorage'; | ||||||
| import { Task, TaskList, TaskProvider, uniqTaskId } from './TaskProvider'; | import { Task, TaskList, TaskProvider, uniqTaskId } from './TaskProvider'; | ||||||
|  |  | ||||||
| const NAMESPACE = 'tasks:v1'; |  | ||||||
| const QUEUE_NAME = 'queue'; | const QUEUE_NAME = 'queue'; | ||||||
|  |  | ||||||
| export class DataStorageTaskProvider implements TaskProvider { | export class DataStorageTaskProvider implements TaskProvider { | ||||||
| @@ -11,8 +10,8 @@ export class DataStorageTaskProvider implements TaskProvider { | |||||||
|         this.storage = storage; |         this.storage = storage; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static create() { |     static create(namespace: string) { | ||||||
|         return new DataStorageTaskProvider(new DataStorage(NAMESPACE)); |         return new DataStorageTaskProvider(new DataStorage(namespace)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getTasks(): TaskList { |     getTasks(): TaskList { | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| import { Args } from './Args'; | import { Args } from './Args'; | ||||||
| import { uniqId } from '../utils'; | import { uniqId } from '../utils'; | ||||||
|  | import { ResourcesInterface } from '../Core/Resources'; | ||||||
|  |  | ||||||
| export type TaskId = string; | export type TaskId = string; | ||||||
|  |  | ||||||
| let idSequence = 1; | let idSequence = 1; | ||||||
| let lastTimestamp: number | null = null; | let lastTimestamp: number | undefined = undefined; | ||||||
|  |  | ||||||
| export function uniqTaskId(): TaskId { | export function uniqTaskId(): TaskId { | ||||||
|     const ts = Math.floor(Date.now() / 1000); |     const ts = Math.floor(Date.now() / 1000); | ||||||
| @@ -38,3 +39,15 @@ export interface TaskProvider { | |||||||
|     getTasks(): TaskList; |     getTasks(): TaskList; | ||||||
|     setTasks(tasks: TaskList): void; |     setTasks(tasks: TaskList): void; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface TaskTransformer { | ||||||
|  |     (task: Task): Task; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function withTime(ts: number): TaskTransformer { | ||||||
|  |     return (task: Task) => new Task(task.id, ts, task.name, task.args); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function withResources(resources: ResourcesInterface): TaskTransformer { | ||||||
|  |     return (task: Task) => new Task(task.id, task.ts, task.name, { ...task.args, resources }); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,14 +11,10 @@ export class TaskQueue { | |||||||
|         this.logger = logger; |         this.logger = logger; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     push(name: string, args: Args, ts: number): Task { |     add(task: Task) { | ||||||
|         const id = uniqTaskId(); |  | ||||||
|         const task = new Task(id, ts, name, args); |  | ||||||
|         this.logger.info('PUSH TASK', id, ts, name, args); |  | ||||||
|         let items = this.getItems(); |         let items = this.getItems(); | ||||||
|         items.push(task); |         items.push(task); | ||||||
|         this.flushItems(items); |         this.flushItems(items); | ||||||
|         return task; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get(ts: number): Task | undefined { |     get(ts: number): Task | undefined { | ||||||
| @@ -29,6 +25,11 @@ export class TaskQueue { | |||||||
|         return readyItems[0]; |         return readyItems[0]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     findById(taskId: TaskId): Task | undefined { | ||||||
|  |         const [matched, _] = this.split(t => t.id === taskId); | ||||||
|  |         return matched.shift(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     has(predicate: (t: Task) => boolean): boolean { |     has(predicate: (t: Task) => boolean): boolean { | ||||||
|         const [matched, _] = this.split(predicate); |         const [matched, _] = this.split(predicate); | ||||||
|         return matched.length > 0; |         return matched.length > 0; | ||||||
| @@ -53,11 +54,6 @@ export class TaskQueue { | |||||||
|         return this.getItems(); |         return this.getItems(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private shiftTask(id: TaskId): [Task | undefined, TaskList] { |  | ||||||
|         const [a, b] = this.split(t => t.id === id); |  | ||||||
|         return [a.shift(), b]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private split(predicate: (t: Task) => boolean): [TaskList, TaskList] { |     private split(predicate: (t: Task) => boolean): [TaskList, TaskList] { | ||||||
|         const matched: TaskList = []; |         const matched: TaskList = []; | ||||||
|         const other: TaskList = []; |         const other: TaskList = []; | ||||||
|   | |||||||
							
								
								
									
										263
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							
							
						
						
									
										263
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							| @@ -1,5 +1,4 @@ | |||||||
| import { timestamp } from './utils'; | import { timestamp } from './utils'; | ||||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; |  | ||||||
| import { TaskQueue } from './Queue/TaskQueue'; | import { TaskQueue } from './Queue/TaskQueue'; | ||||||
| import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; | import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; | ||||||
| import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; | import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; | ||||||
| @@ -7,49 +6,49 @@ import { Logger } from './Logger'; | |||||||
| import { GrabVillageState } from './Task/GrabVillageState'; | import { GrabVillageState } from './Task/GrabVillageState'; | ||||||
| import { Action, ActionQueue, ImmutableActionList } from './Queue/ActionQueue'; | import { Action, ActionQueue, ImmutableActionList } from './Queue/ActionQueue'; | ||||||
| import { UpdateResourceContracts } from './Task/UpdateResourceContracts'; | import { UpdateResourceContracts } from './Task/UpdateResourceContracts'; | ||||||
| import { Resources, ResourcesInterface } from './Core/Resources'; |  | ||||||
| import { SendResourcesTask } from './Task/SendResourcesTask'; | import { SendResourcesTask } from './Task/SendResourcesTask'; | ||||||
| import { Args } from './Queue/Args'; | import { Args } from './Queue/Args'; | ||||||
| import { ImmutableTaskList, Task, TaskId } from './Queue/TaskProvider'; | import { ImmutableTaskList, Task, TaskId, uniqTaskId, withTime } from './Queue/TaskProvider'; | ||||||
| import { ForgeImprovementTask } from './Task/ForgeImprovementTask'; |  | ||||||
| import { getProductionQueue } from './Task/TaskMap'; |  | ||||||
| import { MARKET_ID } from './Core/Buildings'; | import { MARKET_ID } from './Core/Buildings'; | ||||||
| import { VillageRepositoryInterface } from './VillageRepository'; | import { VillageRepositoryInterface } from './VillageRepository'; | ||||||
| import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | import { isProductionTask } from './Core/ProductionQueue'; | ||||||
|  | import { VillageControllerFactory } from './VillageControllerFactory'; | ||||||
|  | import { RunVillageProductionTask } from './Task/RunVillageProductionTask'; | ||||||
|  |  | ||||||
| export enum ContractType { | export interface NextExecution { | ||||||
|     UpgradeBuilding, |     task?: Task; | ||||||
|     ImproveTrooper, |     action?: Action; | ||||||
| } |  | ||||||
|  |  | ||||||
| interface ContractAttributes { |  | ||||||
|     type: ContractType; |  | ||||||
|     villageId?: number; |  | ||||||
|     buildId?: number; |  | ||||||
|     unitId?: number; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export class Scheduler { | export class Scheduler { | ||||||
|     private taskQueue: TaskQueue; |     private taskQueue: TaskQueue; | ||||||
|     private actionQueue: ActionQueue; |     private actionQueue: ActionQueue; | ||||||
|     private villageRepository: VillageRepositoryInterface; |     private villageRepository: VillageRepositoryInterface; | ||||||
|  |     private villageControllerFactory: VillageControllerFactory; | ||||||
|     private logger: Logger; |     private logger: Logger; | ||||||
|  |  | ||||||
|     constructor( |     constructor( | ||||||
|         taskQueue: TaskQueue, |         taskQueue: TaskQueue, | ||||||
|         actionQueue: ActionQueue, |         actionQueue: ActionQueue, | ||||||
|         villageRepository: VillageRepositoryInterface, |         villageRepository: VillageRepositoryInterface, | ||||||
|  |         villageControllerFactory: VillageControllerFactory, | ||||||
|         logger: Logger |         logger: Logger | ||||||
|     ) { |     ) { | ||||||
|         this.taskQueue = taskQueue; |         this.taskQueue = taskQueue; | ||||||
|         this.actionQueue = actionQueue; |         this.actionQueue = actionQueue; | ||||||
|         this.villageRepository = villageRepository; |         this.villageRepository = villageRepository; | ||||||
|  |         this.villageControllerFactory = villageControllerFactory; | ||||||
|         this.logger = logger; |         this.logger = logger; | ||||||
|  |  | ||||||
|         // this.taskQueue.push(GrabVillageState.name, {}, timestamp()); |         // this.taskQueue.push(GrabVillageState.name, {}, timestamp()); | ||||||
|         // this.taskQueue.push(UpdateResourceContracts.name, {}, timestamp()); |         // this.taskQueue.push(UpdateResourceContracts.name, {}, timestamp()); | ||||||
|         // this.taskQueue.push(BalanceHeroResourcesTask.name, {}, timestamp()); |         // this.taskQueue.push(BalanceHeroResourcesTask.name, {}, timestamp()); | ||||||
|  |  | ||||||
|  |         const villages = this.villageRepository.all(); | ||||||
|  |         for (let village of villages) { | ||||||
|  |             this.createUniqTaskTimer(5 * 60, RunVillageProductionTask.name, { villageId: village.id }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         this.createUniqTaskTimer(5 * 60, GrabVillageState.name); |         this.createUniqTaskTimer(5 * 60, GrabVillageState.name); | ||||||
|         this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name); |         this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name); | ||||||
|         this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name); |         this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name); | ||||||
| @@ -69,25 +68,67 @@ export class Scheduler { | |||||||
|         return this.actionQueue.seeItems(); |         return this.actionQueue.seeItems(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     nextTask(ts: number) { |     nextTask(ts: number): NextExecution { | ||||||
|         return this.taskQueue.get(ts); |         const task = this.taskQueue.get(ts); | ||||||
|  |  | ||||||
|  |         // Task not found - next task not ready or queue is empty | ||||||
|  |         if (!task) { | ||||||
|  |             this.clearActions(); | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const action = this.actionQueue.pop(); | ||||||
|  |  | ||||||
|  |         // Action not found - task is new | ||||||
|  |         if (!action) { | ||||||
|  |             return { task: this.replaceTask(task) }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Task in action not equals current task it - rerun task | ||||||
|  |         if (action.args.taskId !== task.id) { | ||||||
|  |             this.clearActions(); | ||||||
|  |             return { task: this.replaceTask(task) }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return { task, action }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     nextAction() { |     private replaceTask(task: Task): Task | undefined { | ||||||
|         return this.actionQueue.pop(); |         if (task.name === RunVillageProductionTask.name && task.args.villageId) { | ||||||
|  |             const villageId = task.args.villageId; | ||||||
|  |             const controller = this.villageControllerFactory.create(villageId); | ||||||
|  |             const villageTask = controller.getReadyProductionTask(); | ||||||
|  |             if (villageTask) { | ||||||
|  |                 this.removeTask(task.id); | ||||||
|  |                 const newTask = new Task(villageTask.id, 0, villageTask.name, { | ||||||
|  |                     ...villageTask.args, | ||||||
|  |                     villageId: controller.villageId, | ||||||
|  |                 }); | ||||||
|  |                 this.taskQueue.add(newTask); | ||||||
|  |                 return newTask; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return task; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     scheduleTask(name: string, args: Args, ts?: number | undefined): void { |     scheduleTask(name: string, args: Args, ts?: number | undefined): void { | ||||||
|         this.logger.info('PUSH TASK', name, args, ts); |         if (isProductionTask(name) && args.villageId) { | ||||||
|         let insertedTs = calculateInsertTime(this.taskQueue.seeItems(), name, args, ts); |             const controller = this.villageControllerFactory.create(args.villageId); | ||||||
|         this.taskQueue.push(name, args, insertedTs); |             controller.addTask(name, args); | ||||||
|         if (args.villageId) { |         } else { | ||||||
|             this.reorderVillageTasks(args.villageId); |             this.logger.info('Schedule task', name, args, ts); | ||||||
|  |             this.taskQueue.add(new Task(uniqTaskId(), ts || timestamp(), name, args)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     scheduleUniqTask(name: string, args: Args, ts?: number | undefined): void { |     scheduleUniqTask(name: string, args: Args, ts?: number | undefined): void { | ||||||
|         let alreadyHasTask = this.taskQueue.has(t => t.name === name); |         let alreadyHasTask; | ||||||
|  |         if (args.villageId) { | ||||||
|  |             alreadyHasTask = this.taskQueue.has(t => t.name === name && t.args.villageId === args.villageId); | ||||||
|  |         } else { | ||||||
|  |             alreadyHasTask = this.taskQueue.has(t => t.name === name); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!alreadyHasTask) { |         if (!alreadyHasTask) { | ||||||
|             this.scheduleTask(name, args, ts); |             this.scheduleTask(name, args, ts); | ||||||
|         } |         } | ||||||
| @@ -98,55 +139,30 @@ export class Scheduler { | |||||||
|         this.actionQueue.clear(); |         this.actionQueue.clear(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     completeTask(taskId: TaskId) { | ||||||
|  |         const task = this.taskQueue.findById(taskId); | ||||||
|  |         const villageId = task ? task.args.villageId : undefined; | ||||||
|  |         if (villageId) { | ||||||
|  |             const controller = this.villageControllerFactory.create(villageId); | ||||||
|  |             controller.removeTask(taskId); | ||||||
|  |         } | ||||||
|  |         this.removeTask(taskId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     postponeTask(taskId: TaskId, seconds: number) { |     postponeTask(taskId: TaskId, seconds: number) { | ||||||
|         const task = this.taskQueue.seeItems().find(t => t.id === taskId); |         const task = this.taskQueue.seeItems().find(t => t.id === taskId); | ||||||
|         if (!task) { |         if (!task) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const villageId = task.args.villageId; |         if (isProductionTask(task.name) && task.args.villageId) { | ||||||
|         const modifyTime = (t: Task) => withTime(t, timestamp() + seconds); |             const controller = this.villageControllerFactory.create(task.args.villageId); | ||||||
|  |             controller.postponeTask(taskId, seconds); | ||||||
|         let predicateUsed = false; |             this.removeTask(taskId); | ||||||
|  |         } else { | ||||||
|         for (let taskTypePred of TASK_TYPE_PREDICATES) { |             const modifyTime = withTime(timestamp() + seconds); | ||||||
|             if (taskTypePred(task.name) && villageId) { |  | ||||||
|                 this.taskQueue.modify(t => sameVillage(villageId, t.args) && taskTypePred(t.name), modifyTime); |  | ||||||
|                 predicateUsed = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!predicateUsed) { |  | ||||||
|             this.taskQueue.modify(t => t.id === taskId, modifyTime); |             this.taskQueue.modify(t => t.id === taskId, modifyTime); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (villageId) { |  | ||||||
|             this.reorderVillageTasks(villageId); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     updateResources(resources: Resources, attr: ContractAttributes): void { |  | ||||||
|         if (attr.type === ContractType.UpgradeBuilding && attr.villageId && attr.buildId) { |  | ||||||
|             const count = this.taskQueue.modify( |  | ||||||
|                 t => |  | ||||||
|                     t.name === UpgradeBuildingTask.name && |  | ||||||
|                     t.args.villageId === attr.villageId && |  | ||||||
|                     t.args.buildId === attr.buildId, |  | ||||||
|                 t => withResources(t, resources) |  | ||||||
|             ); |  | ||||||
|             this.logger.info('Update', count, 'upgrade contracts', attr, resources); |  | ||||||
|         } |  | ||||||
|         if (attr.type === ContractType.ImproveTrooper && attr.villageId && attr.buildId && attr.unitId) { |  | ||||||
|             const count = this.taskQueue.modify( |  | ||||||
|                 t => |  | ||||||
|                     t.name === ForgeImprovementTask.name && |  | ||||||
|                     t.args.villageId === attr.villageId && |  | ||||||
|                     t.args.buildId === attr.buildId && |  | ||||||
|                     t.args.unitId === attr.unitId, |  | ||||||
|                 t => withResources(t, resources) |  | ||||||
|             ); |  | ||||||
|             this.logger.info('Update', count, 'improve contracts', attr, resources); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     scheduleActions(actions: Array<Action>): void { |     scheduleActions(actions: Array<Action>): void { | ||||||
| @@ -157,31 +173,6 @@ export class Scheduler { | |||||||
|         this.actionQueue.clear(); |         this.actionQueue.clear(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getVillageRequiredResources(villageId: number): Resources { |  | ||||||
|         const tasks = this.taskQueue |  | ||||||
|             .seeItems() |  | ||||||
|             .filter(t => sameVillage(villageId, t.args) && t.args.resources && t.name !== SendResourcesTask.name); |  | ||||||
|         const first = tasks.shift(); |  | ||||||
|         if (first && first.args.resources) { |  | ||||||
|             return Resources.fromObject(first.args.resources); |  | ||||||
|         } |  | ||||||
|         return Resources.zero(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     getTotalVillageRequiredResources(villageId: number): Resources { |  | ||||||
|         const tasks = this.taskQueue |  | ||||||
|             .seeItems() |  | ||||||
|             .filter(t => sameVillage(villageId, t.args) && t.args.resources && t.name !== SendResourcesTask.name); |  | ||||||
|         return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     getResourceShipmentVillageIds(villageId: number): Array<number> { |  | ||||||
|         const tasks = this.taskQueue |  | ||||||
|             .seeItems() |  | ||||||
|             .filter(t => t.name === SendResourcesTask.name && t.args.villageId === villageId && t.args.targetVillageId); |  | ||||||
|         return tasks.map(t => t.args.targetVillageId!); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     dropResourceTransferTasks(fromVillageId: number, toVillageId: number): void { |     dropResourceTransferTasks(fromVillageId: number, toVillageId: number): void { | ||||||
|         this.taskQueue.remove( |         this.taskQueue.remove( | ||||||
|             t => |             t => | ||||||
| @@ -205,92 +196,4 @@ export class Scheduler { | |||||||
|             tabId: 5, |             tabId: 5, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getProductionQueueTasks(villageId: number, queue: ProductionQueue): ReadonlyArray<Task> { |  | ||||||
|         return this.taskQueue |  | ||||||
|             .seeItems() |  | ||||||
|             .filter(t => t.args.villageId === villageId && getProductionQueue(t.name) === queue); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private reorderVillageTasks(villageId: number) { |  | ||||||
|         const tasks = this.taskQueue.seeItems(); |  | ||||||
|  |  | ||||||
|         for (let i = 0; i < TASK_TYPE_PREDICATES.length; ++i) { |  | ||||||
|             const taskTypePred = TASK_TYPE_PREDICATES[i]; |  | ||||||
|             const lowTaskTypePredicates = TASK_TYPE_PREDICATES.slice(i + 1); |  | ||||||
|             const lastTaskTs = lastTaskTime(tasks, t => taskTypePred(t.name) && sameVillage(villageId, t.args)); |  | ||||||
|             if (lastTaskTs) { |  | ||||||
|                 for (let pred of lowTaskTypePredicates) { |  | ||||||
|                     this.taskQueue.modify( |  | ||||||
|                         t => pred(t.name) && sameVillage(villageId, t.args), |  | ||||||
|                         t => withTime(t, lastTaskTs + 1) |  | ||||||
|                     ); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface TaskNamePredicate { |  | ||||||
|     (name: string): boolean; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * List on non intersected task queue predicates. |  | ||||||
|  */ |  | ||||||
| const TASK_TYPE_PREDICATES: Array<TaskNamePredicate> = ProductionQueueTypes.map(queue => { |  | ||||||
|     return (taskName: string) => getProductionQueue(taskName) === queue; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function sameVillage(villageId: number | undefined, args: Args) { |  | ||||||
|     return villageId !== undefined && args.villageId === villageId; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function withTime(task: Task, ts: number): Task { |  | ||||||
|     return new Task(task.id, ts, task.name, task.args); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function withResources(task: Task, resources: ResourcesInterface): Task { |  | ||||||
|     return new Task(task.id, task.ts, task.name, { ...task.args, resources }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function lastTaskTime(tasks: ImmutableTaskList, predicate: (t: Task) => boolean): number | undefined { |  | ||||||
|     const queuedTaskIndex = findLastIndex(tasks, predicate); |  | ||||||
|     if (queuedTaskIndex === undefined) { |  | ||||||
|         return undefined; |  | ||||||
|     } |  | ||||||
|     return tasks[queuedTaskIndex].ts; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function findLastIndex(tasks: ImmutableTaskList, 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; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Calculates insert time for new task based on task queue. |  | ||||||
|  */ |  | ||||||
| function calculateInsertTime(tasks: ImmutableTaskList, name: string, args: Args, ts: number | undefined): number { |  | ||||||
|     const villageId = args.villageId; |  | ||||||
|     let insertedTs = ts; |  | ||||||
|  |  | ||||||
|     if (villageId && !insertedTs) { |  | ||||||
|         for (let taskTypePred of TASK_TYPE_PREDICATES) { |  | ||||||
|             const sameVillageAndTypePred = (t: Task) => |  | ||||||
|                 taskTypePred(name) && taskTypePred(t.name) && sameVillage(villageId, t.args); |  | ||||||
|             insertedTs = lastTaskTime(tasks, sameVillageAndTypePred); |  | ||||||
|             if (insertedTs) { |  | ||||||
|                 insertedTs += 1; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return insertedTs || timestamp(); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,14 +6,16 @@ import { IncomingMerchant } from '../Core/Market'; | |||||||
| import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; | import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; | ||||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | import { ProductionQueue } from '../Core/ProductionQueue'; | ||||||
| import { getNumber } from '../utils'; | import { getNumber } from '../utils'; | ||||||
|  | import { Task, TaskList, uniqTaskId } from '../Queue/TaskProvider'; | ||||||
|  |  | ||||||
| const RESOURCES_KEY = 'res'; | const RESOURCES_KEY = 'resources'; | ||||||
| const CAPACITY_KEY = 'cap'; | const CAPACITY_KEY = 'capacity'; | ||||||
| const PERFORMANCE_KEY = 'perf'; | const PERFORMANCE_KEY = 'performance'; | ||||||
| const BUILDING_QUEUE_KEY = 'bq'; | const BUILDING_QUEUE_INFO_KEY = 'building_queue_info'; | ||||||
| const INCOMING_MERCHANTS_KEY = 'im'; | const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; | ||||||
| const SETTINGS_KEY = 'settings'; | const SETTINGS_KEY = 'settings'; | ||||||
| const QUEUE_ENDING_TIME_KEY = 'qet'; | const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; | ||||||
|  | const TASK_LIST_KEY = 'tasks'; | ||||||
|  |  | ||||||
| const ResourceOptions = { | const ResourceOptions = { | ||||||
|     factory: () => new Resources(0, 0, 0, 0), |     factory: () => new Resources(0, 0, 0, 0), | ||||||
| @@ -52,11 +54,11 @@ export class VillageStorage { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     storeBuildingQueueInfo(info: BuildingQueueInfo): void { |     storeBuildingQueueInfo(info: BuildingQueueInfo): void { | ||||||
|         this.storage.set(BUILDING_QUEUE_KEY, info); |         this.storage.set(BUILDING_QUEUE_INFO_KEY, info); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getBuildingQueueInfo(): BuildingQueueInfo { |     getBuildingQueueInfo(): BuildingQueueInfo { | ||||||
|         let plain = this.storage.get(BUILDING_QUEUE_KEY); |         let plain = this.storage.get(BUILDING_QUEUE_INFO_KEY); | ||||||
|         let res = new BuildingQueueInfo(0); |         let res = new BuildingQueueInfo(0); | ||||||
|         return Object.assign(res, plain) as BuildingQueueInfo; |         return Object.assign(res, plain) as BuildingQueueInfo; | ||||||
|     } |     } | ||||||
| @@ -103,4 +105,48 @@ export class VillageStorage { | |||||||
|         const key = this.queueKey(queue); |         const key = this.queueKey(queue); | ||||||
|         this.storage.set(key, ts); |         this.storage.set(key, ts); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     getTasks(): Array<Task> { | ||||||
|  |         return this.storage.getTypedList<Task>(TASK_LIST_KEY, { | ||||||
|  |             factory: () => new Task(uniqTaskId(), 0, '', {}), | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     addTask(task: Task): void { | ||||||
|  |         const tasks = this.getTasks(); | ||||||
|  |         tasks.push(task); | ||||||
|  |         this.storeTaskList(tasks); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     modifyTasks(predicate: (t: Task) => boolean, modifier: (t: Task) => Task): number { | ||||||
|  |         const [matched, other] = this.split(predicate); | ||||||
|  |         const modified = matched.map(modifier); | ||||||
|  |         const modifiedCount = modified.length; | ||||||
|  |         this.storeTaskList(modified.concat(other)); | ||||||
|  |         return modifiedCount; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     removeTasks(predicate: (t: Task) => boolean): number { | ||||||
|  |         const [_, other] = this.split(predicate); | ||||||
|  |         const result = other.length; | ||||||
|  |         this.storeTaskList(other); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private split(predicate: (t: Task) => boolean): [TaskList, TaskList] { | ||||||
|  |         const matched: TaskList = []; | ||||||
|  |         const other: TaskList = []; | ||||||
|  |         this.getTasks().forEach(t => { | ||||||
|  |             if (predicate(t)) { | ||||||
|  |                 matched.push(t); | ||||||
|  |             } else { | ||||||
|  |                 other.push(t); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         return [matched, other]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private storeTaskList(tasks: Array<Task>): void { | ||||||
|  |         this.storage.set(TASK_LIST_KEY, tasks); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								src/Task/RunVillageProductionTask.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Task/RunVillageProductionTask.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import { TaskController, ActionDefinition } from './TaskController'; | ||||||
|  | import { scanAllVillagesBundle } from './ActionBundles'; | ||||||
|  | import { Task } from '../Queue/TaskProvider'; | ||||||
|  | import { registerTask } from './TaskMap'; | ||||||
|  |  | ||||||
|  | @registerTask() | ||||||
|  | export class RunVillageProductionTask extends TaskController { | ||||||
|  |     defineActions(task: Task): Array<ActionDefinition> { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { TaskController, ActionDefinition } from './TaskController'; | import { ActionDefinition, TaskController } from './TaskController'; | ||||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
| import { UpgradeBuildingTask } from './UpgradeBuildingTask'; | import { UpgradeBuildingTask } from './UpgradeBuildingTask'; | ||||||
| import { ImmutableTaskList, Task } from '../Queue/TaskProvider'; | import { ImmutableTaskList, Task } from '../Queue/TaskProvider'; | ||||||
| @@ -11,10 +11,9 @@ export class UpdateResourceContracts extends TaskController { | |||||||
|     defineActions(task: Task): Array<ActionDefinition> { |     defineActions(task: Task): Array<ActionDefinition> { | ||||||
|         const tasks = this.scheduler.getTaskItems(); |         const tasks = this.scheduler.getTaskItems(); | ||||||
|  |  | ||||||
|         const paths = [...this.walkUpgradeTasks(tasks), ...this.walkImprovementTask(tasks)]; |         const paths = uniqPaths([...this.walkUpgradeTasks(tasks), ...this.walkImprovementTask(tasks)]); | ||||||
|         const uniq = uniqPaths(paths); |  | ||||||
|  |  | ||||||
|         return uniq.map(p => [GoToPageAction.name, { path: path(p.name, p.query) }]); |         return paths.map(p => [GoToPageAction.name, { path: path(p.name, p.query) }]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private walkUpgradeTasks(tasks: ImmutableTaskList): PathList { |     private walkUpgradeTasks(tasks: ImmutableTaskList): PathList { | ||||||
|   | |||||||
							
								
								
									
										93
									
								
								src/VillageController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/VillageController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | import { Task, TaskId, uniqTaskId, withResources, withTime } from './Queue/TaskProvider'; | ||||||
|  | import { VillageStorage } from './Storage/VillageStorage'; | ||||||
|  | import { Args } from './Queue/Args'; | ||||||
|  | import { isProductionTask, ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | ||||||
|  | import { Resources } from './Core/Resources'; | ||||||
|  | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
|  | import { ForgeImprovementTask } from './Task/ForgeImprovementTask'; | ||||||
|  | import { ContractType, ContractAttributes } from './Core/Contract'; | ||||||
|  | import { timestamp } from './utils'; | ||||||
|  | import { getProductionQueue } from './Task/TaskMap'; | ||||||
|  |  | ||||||
|  | export class VillageController { | ||||||
|  |     private readonly _villageId: number; | ||||||
|  |     private readonly _storage: VillageStorage; | ||||||
|  |  | ||||||
|  |     constructor(villageId: number, storage: VillageStorage) { | ||||||
|  |         this._villageId = villageId; | ||||||
|  |         this._storage = storage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get villageId() { | ||||||
|  |         return this._villageId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getStorage(): VillageStorage { | ||||||
|  |         return this._storage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     addTask(name: string, args: Args) { | ||||||
|  |         if (!isProductionTask(name)) { | ||||||
|  |             throw new Error(`Task "${name}" is not production task`); | ||||||
|  |         } | ||||||
|  |         if (args.villageId !== this._villageId) { | ||||||
|  |             throw new Error(`Task village id (${args.villageId}) not equal controller village id (${this._villageId}`); | ||||||
|  |         } | ||||||
|  |         const task = new Task(uniqTaskId(), 0, name, { villageId: this._villageId, ...args }); | ||||||
|  |         this._storage.addTask(task); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getTasks(): Array<Task> { | ||||||
|  |         return this._storage.getTasks(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     removeTask(taskId: TaskId) { | ||||||
|  |         this._storage.removeTasks(t => t.id === taskId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getTasksInProductionQueue(queue: ProductionQueue): Array<Task> { | ||||||
|  |         return this._storage.getTasks().filter(task => getProductionQueue(task.name) === queue); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getReadyProductionTask(): Task | undefined { | ||||||
|  |         let sortedTasks: Array<Task> = []; | ||||||
|  |         for (let queue of ProductionQueueTypes) { | ||||||
|  |             const tasks = this.getTasksInProductionQueue(queue); | ||||||
|  |             sortedTasks = sortedTasks.concat(tasks); | ||||||
|  |         } | ||||||
|  |         return sortedTasks.shift(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     postponeTask(taskId: TaskId, seconds: number) { | ||||||
|  |         const modifyTime = withTime(timestamp() + seconds); | ||||||
|  |         this._storage.modifyTasks(task => task.id === taskId, modifyTime); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     updateResources(resources: Resources, attr: ContractAttributes): void { | ||||||
|  |         if (attr.type === ContractType.UpgradeBuilding && attr.buildId) { | ||||||
|  |             const predicate = (t: Task) => t.name === UpgradeBuildingTask.name && t.args.buildId === attr.buildId; | ||||||
|  |             this._storage.modifyTasks(predicate, withResources(resources)); | ||||||
|  |         } | ||||||
|  |         if (attr.type === ContractType.ImproveTrooper && attr.buildId && attr.unitId) { | ||||||
|  |             const predicate = (t: Task) => | ||||||
|  |                 t.name === ForgeImprovementTask.name && | ||||||
|  |                 t.args.buildId === attr.buildId && | ||||||
|  |                 t.args.unitId === attr.unitId; | ||||||
|  |             this._storage.modifyTasks(predicate, withResources(resources)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getVillageRequiredResources(): Resources { | ||||||
|  |         const tasks = this._storage.getTasks().filter(t => t.args.resources); | ||||||
|  |         const first = tasks.shift(); | ||||||
|  |         if (first && first.args.resources) { | ||||||
|  |             return Resources.fromObject(first.args.resources); | ||||||
|  |         } | ||||||
|  |         return Resources.zero(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getTotalVillageRequiredResources(): Resources { | ||||||
|  |         const tasks = this._storage.getTasks().filter(t => t.args.resources); | ||||||
|  |         return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/VillageControllerFactory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/VillageControllerFactory.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | import { VillageController } from './VillageController'; | ||||||
|  | import { VillageStorage } from './Storage/VillageStorage'; | ||||||
|  | import { VillageRepository } from './VillageRepository'; | ||||||
|  |  | ||||||
|  | export class VillageControllerFactory { | ||||||
|  |     private villageRepository: VillageRepository; | ||||||
|  |  | ||||||
|  |     constructor(villageRepository: VillageRepository) { | ||||||
|  |         this.villageRepository = villageRepository; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     create(villageId: number): VillageController { | ||||||
|  |         const village = this.villageRepository.get(villageId); | ||||||
|  |         return new VillageController(village.id, new VillageStorage(village.id)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getActive(): VillageController { | ||||||
|  |         const village = this.villageRepository.getActive(); | ||||||
|  |         return this.create(village.id); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,12 +1,30 @@ | |||||||
| import { Village } from './Core/Village'; | import { Village } from './Core/Village'; | ||||||
| import { grabVillageList } from './Page/VillageBlock'; | import { grabVillageList } from './Page/VillageBlock'; | ||||||
|  | import { VillageNotFound } from './Errors'; | ||||||
|  |  | ||||||
| export interface VillageRepositoryInterface { | export interface VillageRepositoryInterface { | ||||||
|     all(): Array<Village>; |     all(): Array<Village>; | ||||||
|  |     getActive(): Village; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class VillageRepository implements VillageRepositoryInterface { | export class VillageRepository implements VillageRepositoryInterface { | ||||||
|     all(): Array<Village> { |     all(): Array<Village> { | ||||||
|         return grabVillageList(); |         return grabVillageList(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get(villageId: number): Village { | ||||||
|  |         const village = this.all().find(vlg => vlg.id === villageId); | ||||||
|  |         if (!village) { | ||||||
|  |             throw new VillageNotFound('Active village not found'); | ||||||
|  |         } | ||||||
|  |         return village; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getActive(): Village { | ||||||
|  |         const village = this.all().find(vlg => vlg.active); | ||||||
|  |         if (!village) { | ||||||
|  |             throw new VillageNotFound('Active village not found'); | ||||||
|  |         } | ||||||
|  |         return village; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import { Village, VillageSettings } from './Core/Village'; | import { Village, VillageSettings } from './Core/Village'; | ||||||
| import { Scheduler } from './Scheduler'; |  | ||||||
| import { Resources } from './Core/Resources'; | import { Resources } from './Core/Resources'; | ||||||
| import { VillageStorage } from './Storage/VillageStorage'; | import { VillageStorage } from './Storage/VillageStorage'; | ||||||
| import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings'; | import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings'; | ||||||
| @@ -8,6 +7,8 @@ import { VillageNotFound } from './Errors'; | |||||||
| import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | ||||||
| import { Task } from './Queue/TaskProvider'; | import { Task } from './Queue/TaskProvider'; | ||||||
| import { timestamp } from './utils'; | import { timestamp } from './utils'; | ||||||
|  | import { VillageControllerFactory } from './VillageControllerFactory'; | ||||||
|  | import { VillageController } from './VillageController'; | ||||||
|  |  | ||||||
| interface VillageStorageState { | interface VillageStorageState { | ||||||
|     resources: Resources; |     resources: Resources; | ||||||
| @@ -132,14 +133,13 @@ function taskResourceReducer(resources: Resources, task: Task) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function createProductionQueueState( | function createProductionQueueState( | ||||||
|     villageId: number, |  | ||||||
|     queue: ProductionQueue, |     queue: ProductionQueue, | ||||||
|     storage: VillageStorage, |     controller: VillageController | ||||||
|     scheduler: Scheduler |  | ||||||
| ): VillageProductionQueueState { | ): VillageProductionQueueState { | ||||||
|  |     const storage = controller.getStorage(); | ||||||
|     const resources = storage.getResources(); |     const resources = storage.getResources(); | ||||||
|     const performance = storage.getResourcesPerformance(); |     const performance = storage.getResourcesPerformance(); | ||||||
|     const tasks = scheduler.getProductionQueueTasks(villageId, queue); |     const tasks = controller.getTasksInProductionQueue(queue); | ||||||
|  |  | ||||||
|     const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero()); |     const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero()); | ||||||
|     const allTaskResources = tasks.reduce(taskResourceReducer, Resources.zero()); |     const allTaskResources = tasks.reduce(taskResourceReducer, Resources.zero()); | ||||||
| @@ -156,33 +156,33 @@ function createProductionQueueState( | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createAllProductionQueueStates(villageId: number, storage: VillageStorage, scheduler: Scheduler) { | function createAllProductionQueueStates(controller: VillageController) { | ||||||
|     let result: { [queue: string]: VillageProductionQueueState } = {}; |     let result: { [queue: string]: VillageProductionQueueState } = {}; | ||||||
|     for (let queue of ProductionQueueTypes) { |     for (let queue of ProductionQueueTypes) { | ||||||
|         result[queue] = createProductionQueueState(villageId, queue, storage, scheduler); |         result[queue] = createProductionQueueState(queue, controller); | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| function calcFrontierResources(villageId: number, scheduler: Scheduler): Resources { | function calcFrontierResources(controller: VillageController): Resources { | ||||||
|     let result = Resources.zero(); |     let result = Resources.zero(); | ||||||
|     for (let queue of ProductionQueueTypes) { |     for (let queue of ProductionQueueTypes) { | ||||||
|         const tasks = scheduler.getProductionQueueTasks(villageId, queue); |         const tasks = controller.getTasksInProductionQueue(queue); | ||||||
|         const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero()); |         const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero()); | ||||||
|         result = result.add(firstTaskResources); |         result = result.add(firstTaskResources); | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createVillageOwnState(village: Village, scheduler: Scheduler): VillageOwnState { | function createVillageOwnState(village: Village, controller: VillageController): VillageOwnState { | ||||||
|     const storage = new VillageStorage(village.id); |     const storage = controller.getStorage(); | ||||||
|     const resources = storage.getResources(); |     const resources = storage.getResources(); | ||||||
|     const resourceStorage = storage.getResourceStorage(); |     const resourceStorage = storage.getResourceStorage(); | ||||||
|     const performance = storage.getResourcesPerformance(); |     const performance = storage.getResourcesPerformance(); | ||||||
|     const buildQueueInfo = storage.getBuildingQueueInfo(); |     const buildQueueInfo = storage.getBuildingQueueInfo(); | ||||||
|     const requiredResources = scheduler.getVillageRequiredResources(village.id); |     const requiredResources = controller.getVillageRequiredResources(); | ||||||
|     const frontierResources = calcFrontierResources(village.id, scheduler); |     const frontierResources = calcFrontierResources(controller); | ||||||
|     const totalRequiredResources = scheduler.getTotalVillageRequiredResources(village.id); |     const totalRequiredResources = controller.getTotalVillageRequiredResources(); | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         id: village.id, |         id: village.id, | ||||||
| @@ -196,24 +196,24 @@ function createVillageOwnState(village: Village, scheduler: Scheduler): VillageO | |||||||
|         buildRemainingSeconds: buildQueueInfo.seconds, |         buildRemainingSeconds: buildQueueInfo.seconds, | ||||||
|         incomingResources: calcIncomingResources(storage), |         incomingResources: calcIncomingResources(storage), | ||||||
|         settings: storage.getSettings(), |         settings: storage.getSettings(), | ||||||
|         queues: createAllProductionQueueStates(village.id, storage, scheduler), |         queues: createAllProductionQueueStates(controller), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createVillageOwnStates(villages: Array<Village>, scheduler: Scheduler): VillageOwnStateDictionary { | function createVillageOwnStates( | ||||||
|  |     villages: Array<Village>, | ||||||
|  |     villageControllerFactory: VillageControllerFactory | ||||||
|  | ): VillageOwnStateDictionary { | ||||||
|     const result: VillageOwnStateDictionary = {}; |     const result: VillageOwnStateDictionary = {}; | ||||||
|     for (let village of villages) { |     for (let village of villages) { | ||||||
|         result[village.id] = createVillageOwnState(village, scheduler); |         const villageController = villageControllerFactory.create(village.id); | ||||||
|  |         result[village.id] = createVillageOwnState(village, villageController); | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createVillageState( | function createVillageState(state: VillageOwnState, ownStates: VillageOwnStateDictionary): VillageState { | ||||||
|     state: VillageOwnState, |     const villageIds = Object.keys(ownStates).map(k => +k); | ||||||
|     ownStates: VillageOwnStateDictionary, |  | ||||||
|     scheduler: Scheduler |  | ||||||
| ): VillageState { |  | ||||||
|     const villageIds = scheduler.getResourceShipmentVillageIds(state.id); |  | ||||||
|     const commitments = villageIds.reduce((memo, shipmentVillageId) => { |     const commitments = villageIds.reduce((memo, shipmentVillageId) => { | ||||||
|         const shipmentVillageState = ownStates[shipmentVillageId]; |         const shipmentVillageState = ownStates[shipmentVillageId]; | ||||||
|         const shipmentVillageRequired = shipmentVillageState.required; |         const shipmentVillageRequired = shipmentVillageState.required; | ||||||
| @@ -224,26 +224,29 @@ function createVillageState( | |||||||
|     return { ...state, commitments, shipment: villageIds }; |     return { ...state, commitments, shipment: villageIds }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getVillageStates(villages: Array<Village>, scheduler: Scheduler): Array<VillageState> { | function getVillageStates( | ||||||
|     const ownStates = createVillageOwnStates(villages, scheduler); |     villages: Array<Village>, | ||||||
|     return villages.map(village => createVillageState(ownStates[village.id], ownStates, scheduler)); |     villageControllerFactory: VillageControllerFactory | ||||||
|  | ): Array<VillageState> { | ||||||
|  |     const ownStates = createVillageOwnStates(villages, villageControllerFactory); | ||||||
|  |     return villages.map(village => createVillageState(ownStates[village.id], ownStates)); | ||||||
| } | } | ||||||
|  |  | ||||||
| export class VillageStateRepository { | export class VillageStateRepository { | ||||||
|     private villageRepository: VillageRepositoryInterface; |     private villageRepository: VillageRepositoryInterface; | ||||||
|     private scheduler: Scheduler; |     private villageControllerFactory: VillageControllerFactory; | ||||||
|  |  | ||||||
|     constructor(villageRepository: VillageRepositoryInterface, scheduler: Scheduler) { |     constructor(villageRepository: VillageRepositoryInterface, villageControllerFactory: VillageControllerFactory) { | ||||||
|         this.villageRepository = villageRepository; |         this.villageRepository = villageRepository; | ||||||
|         this.scheduler = scheduler; |         this.villageControllerFactory = villageControllerFactory; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getAllVillageStates(): Array<VillageState> { |     getAllVillageStates(): Array<VillageState> { | ||||||
|         return getVillageStates(this.villageRepository.all(), this.scheduler); |         return getVillageStates(this.villageRepository.all(), this.villageControllerFactory); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getVillageState(villageId: number): VillageState { |     getVillageState(villageId: number): VillageState { | ||||||
|         const states = getVillageStates(this.villageRepository.all(), this.scheduler); |         const states = getVillageStates(this.villageRepository.all(), this.villageControllerFactory); | ||||||
|         const needle = states.find(s => s.id === villageId); |         const needle = states.find(s => s.id === villageId); | ||||||
|         if (!needle) { |         if (!needle) { | ||||||
|             throw new VillageNotFound(`Village ${villageId} not found`); |             throw new VillageNotFound(`Village ${villageId} not found`); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user