Introduce production queues
And add queue state in village state list
This commit is contained in:
		
							
								
								
									
										29
									
								
								src/Core/ProductionQueue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Core/ProductionQueue.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| export enum ProductionQueue { | ||||
|     Building = 'building', | ||||
|     TrainUnit = 'train_unit', | ||||
|     UpgradeUnit = 'upgrade_unit', | ||||
|     Celebration = 'celebration', | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Placed in order of execution priority. Order is important! | ||||
|  */ | ||||
| export const ProductionQueueTypes: ReadonlyArray<ProductionQueue> = [ | ||||
|     ProductionQueue.TrainUnit, | ||||
|     ProductionQueue.Celebration, | ||||
|     ProductionQueue.UpgradeUnit, | ||||
|     ProductionQueue.Building, | ||||
| ]; | ||||
|  | ||||
| export function translateProductionQueue(queue: ProductionQueue): string { | ||||
|     switch (queue) { | ||||
|         case ProductionQueue.Building: | ||||
|             return 'Строительство'; | ||||
|         case ProductionQueue.TrainUnit: | ||||
|             return 'Обучение'; | ||||
|         case ProductionQueue.UpgradeUnit: | ||||
|             return 'Улучшение'; | ||||
|         case ProductionQueue.Celebration: | ||||
|             return 'Празднование'; | ||||
|     } | ||||
| } | ||||
| @@ -48,6 +48,7 @@ | ||||
|             </td> | ||||
|             <td class="right" v-text="storageTime(villageState)"></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr class="performance-line"> | ||||
|             <td class="right">Прирост:</td> | ||||
|             <td class="right"> | ||||
| @@ -64,6 +65,7 @@ | ||||
|             </td> | ||||
|             <td></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr class="required-line"> | ||||
|             <td class="right">След. задача:</td> | ||||
|             <td class="right"> | ||||
| @@ -100,6 +102,7 @@ | ||||
|             </td> | ||||
|             <td class="right" v-text="renderTimeInSeconds(villageState.buildRemainingSeconds)"></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr class="required-line"> | ||||
|             <td class="right">Баланс задачи:</td> | ||||
|             <td class="right"> | ||||
| @@ -116,6 +119,7 @@ | ||||
|             </td> | ||||
|             <td class="right" v-text="renderGatheringTime(villageState.required.time)"></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr class="required-line"> | ||||
|             <td class="right">Баланс очереди:</td> | ||||
|             <td class="right"> | ||||
| @@ -132,6 +136,24 @@ | ||||
|             </td> | ||||
|             <td class="right" v-text="renderGatheringTime(villageState.totalRequired.time)"></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr v-for="queueState of villageState.queues" v-if="queueState.active" class="required-line"> | ||||
|             <td class="right">{{ queueTitle(queueState.queue) }}:</td> | ||||
|             <td class="right"> | ||||
|               <resource :value="queueState.firstTask.balance.lumber"></resource> | ||||
|             </td> | ||||
|             <td class="right"> | ||||
|               <resource :value="queueState.firstTask.balance.clay"></resource> | ||||
|             </td> | ||||
|             <td class="right"> | ||||
|               <resource :value="queueState.firstTask.balance.iron"></resource> | ||||
|             </td> | ||||
|             <td class="right"> | ||||
|               <resource :value="queueState.firstTask.balance.crop"></resource> | ||||
|             </td> | ||||
|             <td class="right" v-text="renderGatheringTime(queueState.firstTask.time)"></td> | ||||
|           </tr> | ||||
|  | ||||
|           <tr class="commitments-line" v-if="!villageState.commitments.empty()"> | ||||
|             <td class="right">Обязательства:</td> | ||||
|             <td class="right"> | ||||
| @@ -206,6 +228,7 @@ import VillageResource from './VillageResource'; | ||||
| import { COLLECTION_POINT_ID, HORSE_STABLE_ID, MARKET_ID, QUARTERS_ID } from '../Core/Buildings'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { Actions } from './Store'; | ||||
| import { translateProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| function secondsToTime(value) { | ||||
|   if (value === 0) { | ||||
| @@ -295,6 +318,9 @@ export default { | ||||
|     openEditor(villageId) { | ||||
|       this.$store.dispatch(Actions.OpenVillageEditor, { villageId }); | ||||
|     }, | ||||
|     queueTitle(queue) { | ||||
|       return translateProductionQueue(queue); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import { Grabber } from './Grabber'; | ||||
| import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; | ||||
| import { VillageStorage } from '../Storage/VillageStorage'; | ||||
| import { parseLocation } from '../utils'; | ||||
| import { parseLocation, timestamp } from '../utils'; | ||||
| import { GrabError } from '../Errors'; | ||||
| import { BuildingQueueInfo } from '../Game'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| export class VillageOverviewPageGrabber extends Grabber { | ||||
|     grab(): void { | ||||
| @@ -13,9 +14,13 @@ export class VillageOverviewPageGrabber extends Grabber { | ||||
|         } | ||||
|  | ||||
|         const villageId = grabActiveVillageId(); | ||||
|         const state = new VillageStorage(villageId); | ||||
|         state.storeResourcesPerformance(grabResourcesPerformance()); | ||||
|         state.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); | ||||
|         const storage = new VillageStorage(villageId); | ||||
|         storage.storeResourcesPerformance(grabResourcesPerformance()); | ||||
|         storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); | ||||
|  | ||||
|         const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); | ||||
|         const buildingEndTime = buildingQueueInfo.seconds ? buildingQueueInfo.seconds + timestamp() : 0; | ||||
|         storage.storeQueueTaskEnding(ProductionQueue.Building, buildingEndTime); | ||||
|     } | ||||
|  | ||||
|     private grabBuildingQueueInfoOrDefault() { | ||||
|   | ||||
| @@ -12,9 +12,10 @@ import { SendResourcesTask } from './Task/SendResourcesTask'; | ||||
| import { Args } from './Queue/Args'; | ||||
| import { ImmutableTaskList, Task, TaskId } from './Queue/TaskProvider'; | ||||
| import { ForgeImprovementTask } from './Task/ForgeImprovementTask'; | ||||
| import { getTaskType, TaskType } from './Task/TaskMap'; | ||||
| import { getProductionQueue } from './Task/TaskMap'; | ||||
| import { MARKET_ID } from './Core/Buildings'; | ||||
| import { VillageRepositoryInterface } from './VillageRepository'; | ||||
| import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | ||||
|  | ||||
| export enum ContractType { | ||||
|     UpgradeBuilding, | ||||
| @@ -205,6 +206,12 @@ export class Scheduler { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     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(); | ||||
|  | ||||
| @@ -229,16 +236,11 @@ interface TaskNamePredicate { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * List on non intersected task type predicates. | ||||
|  * | ||||
|  * Placed in order of execution priority. Order is important! | ||||
|  * List on non intersected task queue predicates. | ||||
|  */ | ||||
| const TASK_TYPE_PREDICATES: Array<TaskNamePredicate> = [ | ||||
|     (taskName: string) => getTaskType(taskName) === TaskType.TrainUnit, | ||||
|     (taskName: string) => getTaskType(taskName) === TaskType.UpgradeUnit, | ||||
|     (taskName: string) => getTaskType(taskName) === TaskType.Building, | ||||
|     (taskName: string) => getTaskType(taskName) === TaskType.Celebration, | ||||
| ]; | ||||
| 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; | ||||
| @@ -273,7 +275,7 @@ function findLastIndex(tasks: ImmutableTaskList, predicate: (t: Task) => boolean | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Calculates insert time for new task based on task type. | ||||
|  * 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; | ||||
|   | ||||
| @@ -4,6 +4,8 @@ import { Resources, ResourcesInterface } from '../Core/Resources'; | ||||
| import { ResourceStorage } from '../Core/ResourceStorage'; | ||||
| import { IncomingMerchant } from '../Core/Market'; | ||||
| import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
| import { getNumber } from '../utils'; | ||||
|  | ||||
| const RESOURCES_KEY = 'res'; | ||||
| const CAPACITY_KEY = 'cap'; | ||||
| @@ -11,6 +13,7 @@ const PERFORMANCE_KEY = 'perf'; | ||||
| const BUILDING_QUEUE_KEY = 'bq'; | ||||
| const INCOMING_MERCHANTS_KEY = 'im'; | ||||
| const SETTINGS_KEY = 'settings'; | ||||
| const QUEUE_ENDING_TIME_KEY = 'qet'; | ||||
|  | ||||
| const ResourceOptions = { | ||||
|     factory: () => new Resources(0, 0, 0, 0), | ||||
| @@ -86,4 +89,18 @@ export class VillageStorage { | ||||
|     storeSettings(settings: VillageSettings) { | ||||
|         this.storage.set(SETTINGS_KEY, settings); | ||||
|     } | ||||
|  | ||||
|     private queueKey(queue: ProductionQueue): string { | ||||
|         return QUEUE_ENDING_TIME_KEY + '.' + queue; | ||||
|     } | ||||
|  | ||||
|     getQueueTaskEnding(queue: ProductionQueue): number { | ||||
|         const key = this.queueKey(queue); | ||||
|         return getNumber(this.storage.get(key)) || 0; | ||||
|     } | ||||
|  | ||||
|     storeQueueTaskEnding(queue: ProductionQueue, ts: number): void { | ||||
|         const key = this.queueKey(queue); | ||||
|         this.storage.set(key, ts); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,11 +3,12 @@ import { GoToPageAction } from '../Action/GoToPageAction'; | ||||
| import { ActionDefinition, TaskController } from './TaskController'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { registerTask, TaskType } from './TaskMap'; | ||||
| import { registerTask } from './TaskMap'; | ||||
| import { taskError } from '../Errors'; | ||||
| import { goToResourceViewPage } from './ActionBundles'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| @registerTask({ type: TaskType.Building }) | ||||
| @registerTask({ queue: ProductionQueue.Building }) | ||||
| export class BuildBuildingTask extends TaskController { | ||||
|     defineActions(task: Task): Array<ActionDefinition> { | ||||
|         const args = task.args; | ||||
|   | ||||
| @@ -3,9 +3,10 @@ import { GoToPageAction } from '../Action/GoToPageAction'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { CelebrationAction } from '../Action/CelebrationAction'; | ||||
| import { registerTask, TaskType } from './TaskMap'; | ||||
| import { registerTask } from './TaskMap'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| @registerTask({ type: TaskType.Celebration }) | ||||
| @registerTask({ queue: ProductionQueue.Celebration }) | ||||
| export class CelebrationTask extends TaskController { | ||||
|     defineActions(task: Task): Array<ActionDefinition> { | ||||
|         const args = task.args; | ||||
|   | ||||
| @@ -3,9 +3,10 @@ import { GoToPageAction } from '../Action/GoToPageAction'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| import { ForgeImprovementAction } from '../Action/ForgeImprovementAction'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { registerTask, TaskType } from './TaskMap'; | ||||
| import { registerTask } from './TaskMap'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| @registerTask({ type: TaskType.UpgradeUnit }) | ||||
| @registerTask({ queue: ProductionQueue.UpgradeUnit }) | ||||
| export class ForgeImprovementTask extends TaskController { | ||||
|     defineActions(task: Task): Array<ActionDefinition> { | ||||
|         const args = task.args; | ||||
|   | ||||
| @@ -1,21 +1,14 @@ | ||||
| import { Scheduler } from '../Scheduler'; | ||||
| import { TaskController } from './TaskController'; | ||||
|  | ||||
| export enum TaskType { | ||||
|     Other = 1, | ||||
|     Building, | ||||
|     TrainUnit, | ||||
|     UpgradeUnit, | ||||
|     Celebration, | ||||
| } | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| interface TaskOptions { | ||||
|     type?: TaskType; | ||||
|     queue?: ProductionQueue; | ||||
| } | ||||
|  | ||||
| interface TaskDescription { | ||||
|     ctor: Function; | ||||
|     type: TaskType; | ||||
|     queue?: ProductionQueue; | ||||
| } | ||||
|  | ||||
| interface TaskMap { | ||||
| @@ -28,17 +21,17 @@ export function registerTask(options: TaskOptions = {}) { | ||||
|     return function(ctor: Function) { | ||||
|         taskMap[ctor.name] = { | ||||
|             ctor, | ||||
|             type: options.type || TaskType.Other, | ||||
|             queue: options.queue, | ||||
|         }; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function getTaskType(name: string): TaskType | undefined { | ||||
| export function getProductionQueue(name: string): ProductionQueue | undefined { | ||||
|     const taskDescription = taskMap[name]; | ||||
|     if (taskDescription === undefined) { | ||||
|         return undefined; | ||||
|     } | ||||
|     return taskDescription.type; | ||||
|     return taskDescription.queue; | ||||
| } | ||||
|  | ||||
| export function createTaskHandler(name: string, scheduler: Scheduler): TaskController | undefined { | ||||
|   | ||||
| @@ -4,9 +4,10 @@ import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | ||||
| import { TrainTrooperAction } from '../Action/TrainTrooperAction'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { registerTask, TaskType } from './TaskMap'; | ||||
| import { registerTask } from './TaskMap'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| @registerTask({ type: TaskType.TrainUnit }) | ||||
| @registerTask({ queue: ProductionQueue.TrainUnit }) | ||||
| export class TrainTroopTask extends TaskController { | ||||
|     defineActions(task: Task): Array<ActionDefinition> { | ||||
|         const args = task.args; | ||||
|   | ||||
| @@ -3,11 +3,12 @@ import { TaskController, ActionDefinition } from './TaskController'; | ||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| import { path } from '../Helpers/Path'; | ||||
| import { registerTask, TaskType } from './TaskMap'; | ||||
| import { registerTask } from './TaskMap'; | ||||
| import { goToResourceViewPage } from './ActionBundles'; | ||||
| import { taskError } from '../Errors'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
|  | ||||
| @registerTask({ type: TaskType.Building }) | ||||
| @registerTask({ queue: ProductionQueue.Building }) | ||||
| export class UpgradeBuildingTask extends TaskController { | ||||
|     defineActions(task: Task): Array<ActionDefinition> { | ||||
|         const args = task.args; | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import { VillageStorage } from './Storage/VillageStorage'; | ||||
| import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings'; | ||||
| import { VillageRepositoryInterface } from './VillageRepository'; | ||||
| import { VillageNotFound } from './Errors'; | ||||
| import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue'; | ||||
| import { Task } from './Queue/TaskProvider'; | ||||
|  | ||||
| interface VillageStorageState { | ||||
|     resources: Resources; | ||||
| @@ -15,7 +17,10 @@ interface VillageStorageState { | ||||
|     timeToFull: GatheringTime; | ||||
| } | ||||
|  | ||||
| interface RequiredResources { | ||||
| /** | ||||
|  * State of one or more tasks, which required some resources. | ||||
|  */ | ||||
| interface ResourceLineState { | ||||
|     /** | ||||
|      * Required resources (always positive) | ||||
|      */ | ||||
| @@ -30,6 +35,14 @@ interface RequiredResources { | ||||
|     time: GatheringTime; | ||||
| } | ||||
|  | ||||
| interface VillageProductionQueueState { | ||||
|     queue: ProductionQueue; | ||||
|     active: boolean; | ||||
|     ts: number; | ||||
|     firstTask: ResourceLineState; | ||||
|     allTasks: ResourceLineState; | ||||
| } | ||||
|  | ||||
| interface VillageOwnState { | ||||
|     /** | ||||
|      * Village id | ||||
| @@ -48,14 +61,15 @@ interface VillageOwnState { | ||||
|     /** | ||||
|      * Required resources for nearest task | ||||
|      */ | ||||
|     required: RequiredResources; | ||||
|     required: ResourceLineState; | ||||
|     /** | ||||
|      * Required resources for all tasks | ||||
|      */ | ||||
|     totalRequired: RequiredResources; | ||||
|     totalRequired: ResourceLineState; | ||||
|     incomingResources: Resources; | ||||
|     buildRemainingSeconds: number; | ||||
|     settings: VillageSettings; | ||||
|     queues: { [queue: string]: VillageProductionQueueState | undefined }; | ||||
| } | ||||
|  | ||||
| interface VillageOwnStateDictionary { | ||||
| @@ -73,7 +87,7 @@ export interface VillageState extends VillageOwnState { | ||||
|     shipment: Array<number>; | ||||
| } | ||||
|  | ||||
| function calcResourceBalance(resources: Resources, current: Resources, performance: Resources): RequiredResources { | ||||
| function calcResourceBalance(resources: Resources, current: Resources, performance: Resources): ResourceLineState { | ||||
|     return { | ||||
|         resources, | ||||
|         balance: current.sub(resources), | ||||
| @@ -106,6 +120,50 @@ function calcIncomingResources(storage: VillageStorage): Resources { | ||||
|     return storage.getIncomingMerchants().reduce((m, i) => m.add(i.resources), Resources.zero()); | ||||
| } | ||||
|  | ||||
| function taskResourceReducer(resources: Resources, task: Task) { | ||||
|     return task.args.resources ? resources.add(Resources.fromObject(task.args.resources)) : resources; | ||||
| } | ||||
|  | ||||
| function createProductionQueueState( | ||||
|     villageId: number, | ||||
|     queue: ProductionQueue, | ||||
|     storage: VillageStorage, | ||||
|     scheduler: Scheduler | ||||
| ): VillageProductionQueueState { | ||||
|     const resources = storage.getResources(); | ||||
|     const performance = storage.getResourcesPerformance(); | ||||
|     const tasks = scheduler.getProductionQueueTasks(villageId, queue); | ||||
|  | ||||
|     const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero()); | ||||
|     const allTaskResources = tasks.reduce(taskResourceReducer, Resources.zero()); | ||||
|  | ||||
|     return { | ||||
|         queue, | ||||
|         active: tasks.length !== 0, | ||||
|         ts: storage.getQueueTaskEnding(queue), | ||||
|         firstTask: calcResourceBalance(firstTaskResources, resources, performance), | ||||
|         allTasks: calcResourceBalance(allTaskResources, resources, performance), | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function createAllProductionQueueStates(villageId: number, storage: VillageStorage, scheduler: Scheduler) { | ||||
|     let result: { [queue: string]: VillageProductionQueueState } = {}; | ||||
|     for (let queue of ProductionQueueTypes) { | ||||
|         result[queue] = createProductionQueueState(villageId, queue, storage, scheduler); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| // function firstTaskRequirements(villageId: number, scheduler: Scheduler): Resources { | ||||
| //     let result = Resources.zero(); | ||||
| //     for (let queue of Object.keys(ProductionQueue)) { | ||||
| //         const tasks = scheduler.getProductionQueueTasks(villageId, queue as ProductionQueue); | ||||
| //         const firstTaskResources = tasks.filter(() => true).reduce(taskResourceReducer, Resources.zero()); | ||||
| //         result = result.add(firstTaskResources); | ||||
| //     } | ||||
| //     return result; | ||||
| // } | ||||
|  | ||||
| function createVillageOwnState(village: Village, scheduler: Scheduler): VillageOwnState { | ||||
|     const storage = new VillageStorage(village.id); | ||||
|     const resources = storage.getResources(); | ||||
| @@ -126,6 +184,7 @@ function createVillageOwnState(village: Village, scheduler: Scheduler): VillageO | ||||
|         buildRemainingSeconds: buildQueueInfo.seconds, | ||||
|         incomingResources: calcIncomingResources(storage), | ||||
|         settings: storage.getSettings(), | ||||
|         queues: createAllProductionQueueStates(village.id, storage, scheduler), | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user