From 4c90af31aa1cef7725231fc7024c0d8c079c1fda Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Tue, 19 May 2020 09:20:10 +0300 Subject: [PATCH] Introduce production queues And add queue state in village state list --- src/Core/ProductionQueue.ts | 29 ++++++++++ src/DashboardView/VillageStateList.vue | 26 +++++++++ src/Grabber/VillageOverviewPageGrabber.ts | 13 +++-- src/Scheduler.ts | 24 ++++---- src/Storage/VillageStorage.ts | 17 ++++++ src/Task/BuildBuildingTask.ts | 5 +- src/Task/CelebrationTask.ts | 5 +- src/Task/ForgeImprovementTask.ts | 5 +- src/Task/TaskMap.ts | 19 ++----- src/Task/TrainTroopTask.ts | 5 +- src/Task/UpgradeBuildingTask.ts | 5 +- src/VillageState.ts | 67 +++++++++++++++++++++-- 12 files changed, 178 insertions(+), 42 deletions(-) create mode 100644 src/Core/ProductionQueue.ts diff --git a/src/Core/ProductionQueue.ts b/src/Core/ProductionQueue.ts new file mode 100644 index 0000000..f003199 --- /dev/null +++ b/src/Core/ProductionQueue.ts @@ -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.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 'Празднование'; + } +} diff --git a/src/DashboardView/VillageStateList.vue b/src/DashboardView/VillageStateList.vue index 4f7645e..1b2638f 100644 --- a/src/DashboardView/VillageStateList.vue +++ b/src/DashboardView/VillageStateList.vue @@ -48,6 +48,7 @@ + Прирост: @@ -64,6 +65,7 @@ + След. задача: @@ -100,6 +102,7 @@ + Баланс задачи: @@ -116,6 +119,7 @@ + Баланс очереди: @@ -132,6 +136,24 @@ + + + {{ queueTitle(queueState.queue) }}: + + + + + + + + + + + + + + + Обязательства: @@ -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); + }, }, }; diff --git a/src/Grabber/VillageOverviewPageGrabber.ts b/src/Grabber/VillageOverviewPageGrabber.ts index 7d5cde7..50b370a 100644 --- a/src/Grabber/VillageOverviewPageGrabber.ts +++ b/src/Grabber/VillageOverviewPageGrabber.ts @@ -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() { diff --git a/src/Scheduler.ts b/src/Scheduler.ts index 1acffa2..97dafda 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -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 { + 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 = [ - (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 = 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; diff --git a/src/Storage/VillageStorage.ts b/src/Storage/VillageStorage.ts index 9eeb05a..13f494a 100644 --- a/src/Storage/VillageStorage.ts +++ b/src/Storage/VillageStorage.ts @@ -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); + } } diff --git a/src/Task/BuildBuildingTask.ts b/src/Task/BuildBuildingTask.ts index 3d24a59..c76a0e9 100644 --- a/src/Task/BuildBuildingTask.ts +++ b/src/Task/BuildBuildingTask.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 { const args = task.args; diff --git a/src/Task/CelebrationTask.ts b/src/Task/CelebrationTask.ts index 2db2a63..e2cc28a 100644 --- a/src/Task/CelebrationTask.ts +++ b/src/Task/CelebrationTask.ts @@ -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 { const args = task.args; diff --git a/src/Task/ForgeImprovementTask.ts b/src/Task/ForgeImprovementTask.ts index ecb5945..3f85b3a 100644 --- a/src/Task/ForgeImprovementTask.ts +++ b/src/Task/ForgeImprovementTask.ts @@ -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 { const args = task.args; diff --git a/src/Task/TaskMap.ts b/src/Task/TaskMap.ts index 302640c..d7d10b5 100644 --- a/src/Task/TaskMap.ts +++ b/src/Task/TaskMap.ts @@ -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 { diff --git a/src/Task/TrainTroopTask.ts b/src/Task/TrainTroopTask.ts index f10a200..77b7010 100644 --- a/src/Task/TrainTroopTask.ts +++ b/src/Task/TrainTroopTask.ts @@ -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 { const args = task.args; diff --git a/src/Task/UpgradeBuildingTask.ts b/src/Task/UpgradeBuildingTask.ts index 0cc0c97..8299119 100644 --- a/src/Task/UpgradeBuildingTask.ts +++ b/src/Task/UpgradeBuildingTask.ts @@ -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 { const args = task.args; diff --git a/src/VillageState.ts b/src/VillageState.ts index bcb84d5..26afd70 100644 --- a/src/VillageState.ts +++ b/src/VillageState.ts @@ -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; } -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), }; }