259 lines
8.2 KiB
TypeScript
259 lines
8.2 KiB
TypeScript
import { VillageTaskCollection } from './VillageTaskCollection';
|
|
import { isBuildingPlanned, TaskId } from './Queue/TaskProvider';
|
|
import { Args } from './Queue/Args';
|
|
import { TaskState, VillageState } from './VillageState';
|
|
import { Resources } from './Core/Resources';
|
|
import { MerchantsInfo } from './Core/Market';
|
|
import { VillageStorage } from './Storage/VillageStorage';
|
|
import { ReceiveResourcesMode } from './Core/Village';
|
|
import { ResourceType } from './Core/ResourceType';
|
|
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
|
|
import * as _ from 'underscore';
|
|
import { GARNER_ID, WAREHOUSE_ID } from './Core/Buildings';
|
|
|
|
export class VillageController {
|
|
private readonly villageId: number;
|
|
private readonly storage: VillageStorage;
|
|
private readonly taskCollection: VillageTaskCollection;
|
|
private readonly state: VillageState;
|
|
|
|
constructor(
|
|
villageId: number,
|
|
storage: VillageStorage,
|
|
taskCollection: VillageTaskCollection,
|
|
state: VillageState
|
|
) {
|
|
this.villageId = villageId;
|
|
this.storage = storage;
|
|
this.taskCollection = taskCollection;
|
|
this.state = state;
|
|
}
|
|
|
|
getVillageId() {
|
|
return this.villageId;
|
|
}
|
|
|
|
getState(): VillageState {
|
|
return this.state;
|
|
}
|
|
|
|
getReadyProductionTask(): TaskState | undefined {
|
|
return this.state.firstReadyTask;
|
|
}
|
|
|
|
addTask(name: string, args: Args) {
|
|
this.taskCollection.addTask(name, args);
|
|
}
|
|
|
|
removeTask(taskId: TaskId) {
|
|
this.taskCollection.removeTask(taskId);
|
|
}
|
|
|
|
upTask(taskId: TaskId) {
|
|
this.taskCollection.upTask(taskId);
|
|
}
|
|
|
|
downTask(taskId: TaskId) {
|
|
this.taskCollection.downTask(taskId);
|
|
}
|
|
|
|
postponeTask(taskId: TaskId, seconds: number) {
|
|
this.taskCollection.postponeTask(taskId, seconds);
|
|
}
|
|
|
|
getMerchantsInfo(): MerchantsInfo {
|
|
return this.storage.getMerchantsInfo();
|
|
}
|
|
|
|
getSendResourcesMultiplier(): number {
|
|
return this.state.settings.sendResourcesMultiplier;
|
|
}
|
|
|
|
getOverflowResources(): Resources {
|
|
const limit = this.state.storage.optimumFullness;
|
|
const currentResources = this.state.resources;
|
|
|
|
return currentResources.sub(limit).max(Resources.zero());
|
|
}
|
|
|
|
getFreeResources(): Resources {
|
|
const mode = this.state.settings.receiveResourcesMode;
|
|
const requirementResources = this.state.required.resources;
|
|
const optimumToStoreResources = this.state.storage.optimumFullness;
|
|
|
|
switch (mode) {
|
|
case ReceiveResourcesMode.Required:
|
|
return this.calcFreeResources(requirementResources);
|
|
case ReceiveResourcesMode.Optimum:
|
|
return this.calcFreeResources(optimumToStoreResources);
|
|
}
|
|
}
|
|
|
|
private calcFreeResources(targetResources: Resources): Resources {
|
|
const currentResources = this.state.resources;
|
|
const free = currentResources.sub(targetResources).max(Resources.zero());
|
|
|
|
const amount = free.amount();
|
|
const threshold = this.state.settings.sendResourcesThreshold;
|
|
|
|
if (amount < threshold) {
|
|
return Resources.zero();
|
|
}
|
|
|
|
return free;
|
|
}
|
|
|
|
getRequiredResources(): Resources {
|
|
const mode = this.state.settings.receiveResourcesMode;
|
|
const optimumToStoreResources = this.state.storage.optimumFullness;
|
|
const requirementResources = this.state.required.resources;
|
|
|
|
switch (mode) {
|
|
case ReceiveResourcesMode.Required:
|
|
return this.calcRequiredResources(requirementResources);
|
|
case ReceiveResourcesMode.Optimum:
|
|
return this.calcRequiredResources(optimumToStoreResources);
|
|
}
|
|
}
|
|
|
|
private calcRequiredResources(targetResources: Resources): Resources {
|
|
const optimumToStoreResources = this.state.storage.optimumFullness;
|
|
const currentResources = this.state.resources;
|
|
const incomingResources = this.state.incomingResources;
|
|
|
|
return targetResources
|
|
.min(optimumToStoreResources)
|
|
.sub(currentResources)
|
|
.sub(incomingResources)
|
|
.max(Resources.zero());
|
|
}
|
|
|
|
getAvailableToReceiveResources(): Resources {
|
|
const optimumToStoreResources = this.state.storage.optimumFullness;
|
|
const currentResources = this.state.resources;
|
|
|
|
return optimumToStoreResources.sub(currentResources).max(Resources.zero());
|
|
}
|
|
|
|
toggleReceiveResourcesMode(): void {
|
|
const current = this.state.settings.receiveResourcesMode;
|
|
|
|
let next;
|
|
switch (current) {
|
|
case ReceiveResourcesMode.Required:
|
|
next = ReceiveResourcesMode.Optimum;
|
|
break;
|
|
case ReceiveResourcesMode.Optimum:
|
|
next = ReceiveResourcesMode.Required;
|
|
break;
|
|
}
|
|
|
|
const newSettings = { ...this.state.settings, receiveResourcesMode: next };
|
|
this.storage.storeSettings(newSettings);
|
|
}
|
|
|
|
planTasks(): void {
|
|
if (this.state.tasks.length >= 100) {
|
|
return;
|
|
}
|
|
this.planCropBuilding();
|
|
this.planWarehouseBuilding();
|
|
this.planGranaryBuilding();
|
|
}
|
|
|
|
private planCropBuilding() {
|
|
const performance = this.state.performance;
|
|
if (performance.crop >= 100) {
|
|
return;
|
|
}
|
|
|
|
const resourceSlots = this.storage.getResourceSlots();
|
|
const tasks = this.taskCollection.getTasks();
|
|
|
|
const cropSlots = resourceSlots.filter(s => s.type === ResourceType.Crop && !s.isMaxLevel);
|
|
if (cropSlots.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Check, if crop field is building now
|
|
const underContraction = cropSlots.find(s => s.isUnderConstruction);
|
|
if (underContraction !== undefined) {
|
|
return;
|
|
}
|
|
|
|
// Check, if we already have crop task in queue
|
|
const cropBuildIds = cropSlots.map(s => s.buildId);
|
|
for (let buildId of cropBuildIds) {
|
|
const upgradeTask = tasks.find(
|
|
isBuildingPlanned(UpgradeBuildingTask.name, buildId, undefined)
|
|
);
|
|
if (upgradeTask !== undefined) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Find ready for building slots and sort them by level
|
|
cropSlots.sort((s1, s2) => s1.level - s2.level);
|
|
|
|
const targetCropBuildId = _.first(cropSlots)?.buildId;
|
|
if (!targetCropBuildId) {
|
|
return;
|
|
}
|
|
|
|
this.taskCollection.addTaskAsFirst(UpgradeBuildingTask.name, {
|
|
buildId: targetCropBuildId,
|
|
});
|
|
}
|
|
|
|
private planWarehouseBuilding(): void {
|
|
this.planStorageBuilding(WAREHOUSE_ID, t => !t.isEnoughWarehouseCapacity);
|
|
}
|
|
|
|
private planGranaryBuilding(): void {
|
|
this.planStorageBuilding(GARNER_ID, t => !t.isEnoughGranaryCapacity);
|
|
}
|
|
|
|
private planStorageBuilding(
|
|
buildTypeId: number,
|
|
checkNeedEnlargeFunc: (task: TaskState) => boolean
|
|
): void {
|
|
const buildingSlots = this.storage.getBuildingSlots();
|
|
|
|
const storageSlots = buildingSlots.filter(
|
|
s => s.buildTypeId === buildTypeId && !s.isMaxLevel
|
|
);
|
|
if (storageSlots.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Check, if storage is building now
|
|
const underConstruction = storageSlots.find(s => s.isUnderConstruction);
|
|
if (underConstruction !== undefined) {
|
|
return;
|
|
}
|
|
|
|
const tasks = this.state.tasks;
|
|
|
|
// Check, if we have storage is in building queue
|
|
const storageBuildIds = storageSlots.map(s => s.buildId);
|
|
for (let buildId of storageBuildIds) {
|
|
const upgradeTask = tasks.find(
|
|
isBuildingPlanned(UpgradeBuildingTask.name, buildId, buildTypeId)
|
|
);
|
|
if (upgradeTask !== undefined) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const needStorageEnlargeTasks = tasks.filter(checkNeedEnlargeFunc);
|
|
if (needStorageEnlargeTasks.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const firstSlot = _.first(storageSlots);
|
|
if (firstSlot) {
|
|
this.addTask(UpgradeBuildingTask.name, { buildId: firstSlot.buildId, buildTypeId });
|
|
}
|
|
}
|
|
}
|