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