Introduce production queues

And add queue state in village state list
This commit is contained in:
Anton Vakhrushev 2020-05-19 09:20:10 +03:00
parent 3b34e06a7d
commit 4c90af31aa
12 changed files with 178 additions and 42 deletions

View 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 'Празднование';
}
}

View File

@ -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>

View File

@ -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() {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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),
};
}