Add crop buildings planning
This commit is contained in:
		| @@ -25,7 +25,7 @@ export class TrainTrooperAction extends ActionController { | |||||||
|         const nextToTrainCount = trainCount - readyToTrainCount; |         const nextToTrainCount = trainCount - readyToTrainCount; | ||||||
|  |  | ||||||
|         if (readyToTrainCount <= 0) { |         if (readyToTrainCount <= 0) { | ||||||
|             throw new TryLaterError(aroundMinutes(15), 'No ready to train troops'); |             throw new TryLaterError(aroundMinutes(15), 'No isReady to train troops'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (nextToTrainCount > 0) { |         if (nextToTrainCount > 0) { | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import { ActionController, registerAction } from './ActionController'; | import { ActionController, registerAction } from './ActionController'; | ||||||
| import { ActionError, taskError, TryLaterError } from '../Errors'; | import { ActionError, taskError, TryLaterError } from '../Errors'; | ||||||
| import { grabResourceDeposits } from '../Page/SlotBlock'; | import { grabResourceSlots } from '../Page/SlotBlock'; | ||||||
| import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | ||||||
| import { ResourceDeposit } from '../Game'; | import { ResourceSlot } from '../Game'; | ||||||
| import { aroundMinutes, getNumber } from '../utils'; | import { aroundMinutes, getNumber } from '../utils'; | ||||||
| import { Args } from '../Queue/Args'; | import { Args } from '../Queue/Args'; | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
| @@ -10,7 +10,7 @@ import { Task } from '../Queue/TaskProvider'; | |||||||
| @registerAction | @registerAction | ||||||
| export class UpgradeResourceToLevel extends ActionController { | export class UpgradeResourceToLevel extends ActionController { | ||||||
|     async run(args: Args, task: Task): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         const deposits = grabResourceDeposits(); |         const deposits = grabResourceSlots(); | ||||||
|         if (deposits.length === 0) { |         if (deposits.length === 0) { | ||||||
|             throw new ActionError('No deposits'); |             throw new ActionError('No deposits'); | ||||||
|         } |         } | ||||||
| @@ -20,7 +20,7 @@ export class UpgradeResourceToLevel extends ActionController { | |||||||
|         const requiredLevel = getNumber(args.level); |         const requiredLevel = getNumber(args.level); | ||||||
|  |  | ||||||
|         const notUpgraded = deposits.filter( |         const notUpgraded = deposits.filter( | ||||||
|             dep => !dep.underConstruction && requiredLevel > dep.level |             dep => !dep.isUnderConstruction && requiredLevel > dep.level | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         if (notUpgraded.length === 0) { |         if (notUpgraded.length === 0) { | ||||||
| @@ -52,7 +52,7 @@ export class UpgradeResourceToLevel extends ActionController { | |||||||
|         throw new TryLaterError(aroundMinutes(10), 'Sleep for next round'); |         throw new TryLaterError(aroundMinutes(10), 'Sleep for next round'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private isTaskNotInQueue(villageId: number, dep: ResourceDeposit): boolean { |     private isTaskNotInQueue(villageId: number, dep: ResourceSlot): boolean { | ||||||
|         const tasks = this.scheduler.getTaskItems(); |         const tasks = this.scheduler.getTaskItems(); | ||||||
|         return ( |         return ( | ||||||
|             undefined === |             undefined === | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { BuildingPageController } from './Page/BuildingPageController'; | |||||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
| import { grabActiveVillageId } from './Page/VillageBlock'; | import { grabActiveVillageId } from './Page/VillageBlock'; | ||||||
| import { | import { | ||||||
|     grabResourceDeposits, |     grabResourceSlots, | ||||||
|     onBuildingSlotCtrlClick, |     onBuildingSlotCtrlClick, | ||||||
|     onResourceSlotCtrlClick, |     onResourceSlotCtrlClick, | ||||||
|     showBuildingSlotIds, |     showBuildingSlotIds, | ||||||
| @@ -166,7 +166,7 @@ export class ControlPanel { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private createDepositsQuickActions(villageId: number) { |     private createDepositsQuickActions(villageId: number) { | ||||||
|         const deposits = grabResourceDeposits(); |         const deposits = grabResourceSlots(); | ||||||
|         if (deposits.length === 0) { |         if (deposits.length === 0) { | ||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								src/Game.ts
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/Game.ts
									
									
									
									
									
								
							| @@ -7,10 +7,20 @@ export class BuildingQueueInfo { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface ResourceDeposit { | export interface ResourceSlot { | ||||||
|     readonly buildId: number; |     readonly buildId: number; | ||||||
|     readonly type: ResourceType; |     readonly type: ResourceType; | ||||||
|     readonly level: number; |     readonly level: number; | ||||||
|     readonly ready: boolean; |     readonly isReady: boolean; | ||||||
|     readonly underConstruction: boolean; |     readonly isUnderConstruction: boolean; | ||||||
|  |     readonly isMaxLevel: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export const ResourceSlotDefaults: ResourceSlot = { | ||||||
|  |     buildId: 0, | ||||||
|  |     type: ResourceType.Lumber, | ||||||
|  |     level: 0, | ||||||
|  |     isReady: false, | ||||||
|  |     isUnderConstruction: false, | ||||||
|  |     isMaxLevel: false, | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import { parseLocation, timestamp } from '../utils'; | |||||||
| import { GrabError } from '../Errors'; | import { GrabError } from '../Errors'; | ||||||
| import { BuildingQueueInfo } from '../Game'; | import { BuildingQueueInfo } from '../Game'; | ||||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | import { ProductionQueue } from '../Core/ProductionQueue'; | ||||||
|  | import { grabResourceSlots } from '../Page/SlotBlock'; | ||||||
|  |  | ||||||
| export class VillageOverviewPageGrabber extends Grabber { | export class VillageOverviewPageGrabber extends Grabber { | ||||||
|     grab(): void { |     grab(): void { | ||||||
| @@ -13,6 +14,7 @@ export class VillageOverviewPageGrabber extends Grabber { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.storage.storeResourcesPerformance(grabResourcesPerformance()); |         this.storage.storeResourcesPerformance(grabResourcesPerformance()); | ||||||
|  |         this.storage.storeResourceSlots(grabResourceSlots()); | ||||||
|  |  | ||||||
|         const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); |         const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); | ||||||
|         const buildingEndTime = buildingQueueInfo.seconds |         const buildingEndTime = buildingQueueInfo.seconds | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
| import { elClassId, getNumber } from '../utils'; | import { elClassId, getNumber } from '../utils'; | ||||||
| import { ResourceDeposit } from '../Game'; | import { ResourceSlot } from '../Game'; | ||||||
| import { numberToResourceType } from '../Core/ResourceType'; | import { numberToResourceType } from '../Core/ResourceType'; | ||||||
|  |  | ||||||
| interface Slot { | interface SlotElement { | ||||||
|     el: HTMLElement; |     el: HTMLElement; | ||||||
|     buildId: number; |     buildId: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| function slotElements(prefix: string): Array<Slot> { | function slotElements(prefix: string): Array<SlotElement> { | ||||||
|     const result: Array<Slot> = []; |     const result: Array<SlotElement> = []; | ||||||
|     jQuery('.level.colorLayer').each((idx, el) => { |     jQuery('.level.colorLayer').each((idx, el) => { | ||||||
|         const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix)); |         const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix)); | ||||||
|         result.push({ el, buildId }); |         result.push({ el, buildId }); | ||||||
| @@ -74,18 +74,19 @@ export function onBuildingSlotCtrlClick(onClickHandler: (buildId: number) => voi | |||||||
|     onSlotCtrlClick('aid', onClickHandler); |     onSlotCtrlClick('aid', onClickHandler); | ||||||
| } | } | ||||||
|  |  | ||||||
| function slotToDepositMapper(slot: Slot): ResourceDeposit { | function makeResourceSlot(slot: SlotElement): ResourceSlot { | ||||||
|     const el = slot.el; |     const $el = jQuery(slot.el); | ||||||
|     const classes = jQuery(el).attr('class'); |     const classes = $el.attr('class'); | ||||||
|     return { |     return { | ||||||
|         buildId: getNumber(elClassId(classes, 'buildingSlot')), |         buildId: getNumber(elClassId(classes, 'buildingSlot')), | ||||||
|         type: numberToResourceType(getNumber(elClassId(classes, 'gid'))), |         type: numberToResourceType(getNumber(elClassId(classes, 'gid'))), | ||||||
|         level: getNumber(elClassId(classes, 'level')), |         level: getNumber(elClassId(classes, 'level')), | ||||||
|         ready: !jQuery(el).hasClass('notNow'), |         isReady: !$el.hasClass('notNow'), | ||||||
|         underConstruction: jQuery(el).hasClass('underConstruction'), |         isUnderConstruction: $el.hasClass('underConstruction'), | ||||||
|  |         isMaxLevel: $el.hasClass('maxLevel'), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function grabResourceDeposits(): Array<ResourceDeposit> { | export function grabResourceSlots(): Array<ResourceSlot> { | ||||||
|     return slotElements('buildingSlot').map(slotToDepositMapper); |     return slotElements('buildingSlot').map(makeResourceSlot); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ export class Scheduler { | |||||||
|     nextTask(ts: number): NextExecution { |     nextTask(ts: number): NextExecution { | ||||||
|         const task = this.taskQueue.get(ts); |         const task = this.taskQueue.get(ts); | ||||||
|  |  | ||||||
|         // Task not found - next task not ready or queue is empty |         // Task not found - next task not isReady or queue is empty | ||||||
|         if (!task) { |         if (!task) { | ||||||
|             this.clearActions(); |             this.clearActions(); | ||||||
|             return {}; |             return {}; | ||||||
| @@ -99,13 +99,21 @@ export class Scheduler { | |||||||
|     private replaceTask(task: Task): Task | undefined { |     private replaceTask(task: Task): Task | undefined { | ||||||
|         if (task.name === RunVillageProductionTask.name && task.args.villageId) { |         if (task.name === RunVillageProductionTask.name && task.args.villageId) { | ||||||
|             const villageId = task.args.villageId; |             const villageId = task.args.villageId; | ||||||
|             const controller = this.villageControllerFactory.createController(villageId); |  | ||||||
|             const villageTask = controller.getReadyProductionTask(); |             // First stage - plan new tasks if needed. | ||||||
|  |             const controllerForPlan = this.villageControllerFactory.createController(villageId); | ||||||
|  |             controllerForPlan.planTasks(); | ||||||
|  |  | ||||||
|  |             // Second stage - select isReady for production task. | ||||||
|  |             // We recreate controller, because need new village state. | ||||||
|  |             const controllerForSelect = this.villageControllerFactory.createController(villageId); | ||||||
|  |             const villageTask = controllerForSelect.getReadyProductionTask(); | ||||||
|  |  | ||||||
|             if (villageTask) { |             if (villageTask) { | ||||||
|                 this.removeTask(task.id); |                 this.removeTask(task.id); | ||||||
|                 const newTask = new Task(villageTask.id, 0, villageTask.name, { |                 const newTask = new Task(villageTask.id, 0, villageTask.name, { | ||||||
|                     ...villageTask.args, |                     ...villageTask.args, | ||||||
|                     villageId: controller.getVillageId(), |                     villageId: controllerForSelect.getVillageId(), | ||||||
|                 }); |                 }); | ||||||
|                 this.taskQueue.add(newTask); |                 this.taskQueue.add(newTask); | ||||||
|                 return newTask; |                 return newTask; | ||||||
|   | |||||||
| @@ -6,12 +6,15 @@ import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; | |||||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | import { ProductionQueue } from '../Core/ProductionQueue'; | ||||||
| import { getNumber } from '../utils'; | import { getNumber } from '../utils'; | ||||||
| import { Task, uniqTaskId } from '../Queue/TaskProvider'; | import { Task, uniqTaskId } from '../Queue/TaskProvider'; | ||||||
|  | import { ResourceSlot, ResourceSlotDefaults } from '../Game'; | ||||||
|  |  | ||||||
| const RESOURCES_KEY = 'resources'; | const RESOURCES_KEY = 'resources'; | ||||||
| const CAPACITY_KEY = 'capacity'; | const CAPACITY_KEY = 'capacity'; | ||||||
| const PERFORMANCE_KEY = 'performance'; | const PERFORMANCE_KEY = 'performance'; | ||||||
| const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; | const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; | ||||||
| const MERCHANTS_INFO_KEY = 'merchants_info'; | const MERCHANTS_INFO_KEY = 'merchants_info'; | ||||||
|  | const RESOURCE_SLOTS_KEY = 'resource_slots'; | ||||||
|  | const BUILDING_SLOTS_KEY = 'building_slots'; | ||||||
| const SETTINGS_KEY = 'settings'; | const SETTINGS_KEY = 'settings'; | ||||||
| const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; | const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; | ||||||
| const TASK_LIST_KEY = 'tasks'; | const TASK_LIST_KEY = 'tasks'; | ||||||
| @@ -83,6 +86,16 @@ export class VillageStorage { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     getResourceSlots(): ReadonlyArray<ResourceSlot> { | ||||||
|  |         return this.storage.getTypedList<ResourceSlot>(RESOURCE_SLOTS_KEY, { | ||||||
|  |             factory: () => Object.assign({}, ResourceSlotDefaults), | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     storeResourceSlots(slots: ReadonlyArray<ResourceSlot>): void { | ||||||
|  |         this.storage.set(RESOURCE_SLOTS_KEY, slots); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     getSettings(): VillageSettings { |     getSettings(): VillageSettings { | ||||||
|         return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, { |         return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, { | ||||||
|             factory: () => Object.assign({}, VillageSettingsDefaults), |             factory: () => Object.assign({}, VillageSettingsDefaults), | ||||||
|   | |||||||
| @@ -6,6 +6,9 @@ import { Resources } from './Core/Resources'; | |||||||
| import { MerchantsInfo } from './Core/Market'; | import { MerchantsInfo } from './Core/Market'; | ||||||
| import { VillageStorage } from './Storage/VillageStorage'; | import { VillageStorage } from './Storage/VillageStorage'; | ||||||
| import { ReceiveResourcesMode } from './Core/Village'; | import { ReceiveResourcesMode } from './Core/Village'; | ||||||
|  | import { ResourceType } from './Core/ResourceType'; | ||||||
|  | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
|  | import * as _ from 'underscore'; | ||||||
|  |  | ||||||
| export class VillageController { | export class VillageController { | ||||||
|     private readonly villageId: number; |     private readonly villageId: number; | ||||||
| @@ -147,4 +150,47 @@ export class VillageController { | |||||||
|         const newSettings = { ...this.state.settings, receiveResourcesMode: next }; |         const newSettings = { ...this.state.settings, receiveResourcesMode: next }; | ||||||
|         this.storage.storeSettings(newSettings); |         this.storage.storeSettings(newSettings); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     planTasks(): void { | ||||||
|  |         const performance = this.state.performance; | ||||||
|  |  | ||||||
|  |         if (performance.crop < 100) { | ||||||
|  |             this.planCropBuilding(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private planCropBuilding() { | ||||||
|  |         const resourceSlots = this.storage.getResourceSlots(); | ||||||
|  |         const tasks = this.taskCollection.getTasks(); | ||||||
|  |  | ||||||
|  |         const cropSlots = resourceSlots.filter(s => s.type === ResourceType.Crop); | ||||||
|  |  | ||||||
|  |         // Check, if crop field is building now | ||||||
|  |         const isCropBuilding = cropSlots.filter(s => s.isUnderConstruction); | ||||||
|  |         if (isCropBuilding) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Check, if we already have crop task in queue | ||||||
|  |         const cropBuildIds = cropSlots.map(s => s.buildId); | ||||||
|  |         const cropBuildingTaskInQueue = tasks.find( | ||||||
|  |             t => t.args.buildId && cropBuildIds.includes(t.args.buildId) | ||||||
|  |         ); | ||||||
|  |         if (cropBuildingTaskInQueue !== undefined) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Find ready for building slots and sort them by level | ||||||
|  |         const readyCropSlots = cropSlots.filter(s => !s.isMaxLevel); | ||||||
|  |         readyCropSlots.sort((s1, s2) => s1.level - s2.level); | ||||||
|  |  | ||||||
|  |         const targetCropBuildId = _.first(readyCropSlots)?.buildId; | ||||||
|  |         if (!targetCropBuildId) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.taskCollection.addTaskAsFirst(UpgradeBuildingTask.name, { | ||||||
|  |             buildId: targetCropBuildId, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,19 +40,29 @@ export class VillageTaskCollection { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     addTask(name: string, args: Args) { |     addTask(name: string, args: Args) { | ||||||
|  |         const tasks = this.getTasks(); | ||||||
|  |         tasks.push(this.createVillageTask(name, args)); | ||||||
|  |         this.storage.storeTaskList(tasks); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     addTaskAsFirst(name: string, args: Args) { | ||||||
|  |         const tasks = this.getTasks(); | ||||||
|  |         tasks.unshift(this.createVillageTask(name, args)); | ||||||
|  |         this.storage.storeTaskList(tasks); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private createVillageTask(name: string, args: Args): Task { | ||||||
|         if (!isProductionTask(name)) { |         if (!isProductionTask(name)) { | ||||||
|             throw new Error(`Task "${name}" is not production task`); |             throw new Error(`Task "${name}" is not production task`); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (args.villageId !== this.villageId) { |         if (args.villageId !== this.villageId) { | ||||||
|             throw new Error( |             throw new Error( | ||||||
|                 `Task village id (${args.villageId}) not equal controller village id (${this.villageId}` |                 `Task village id (${args.villageId}) not equal controller village id (${this.villageId}` | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|         const task = new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args }); |  | ||||||
|  |  | ||||||
|         const tasks = this.getTasks(); |         return new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args }); | ||||||
|         tasks.push(task); |  | ||||||
|         this.storage.storeTaskList(tasks); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     removeTask(taskId: TaskId) { |     removeTask(taskId: TaskId) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user