Add crop buildings planning
This commit is contained in:
		| @@ -25,7 +25,7 @@ export class TrainTrooperAction extends ActionController { | ||||
|         const nextToTrainCount = trainCount - readyToTrainCount; | ||||
|  | ||||
|         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) { | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import { ActionController, registerAction } from './ActionController'; | ||||
| import { ActionError, taskError, TryLaterError } from '../Errors'; | ||||
| import { grabResourceDeposits } from '../Page/SlotBlock'; | ||||
| import { grabResourceSlots } from '../Page/SlotBlock'; | ||||
| import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | ||||
| import { ResourceDeposit } from '../Game'; | ||||
| import { ResourceSlot } from '../Game'; | ||||
| import { aroundMinutes, getNumber } from '../utils'; | ||||
| import { Args } from '../Queue/Args'; | ||||
| import { Task } from '../Queue/TaskProvider'; | ||||
| @@ -10,7 +10,7 @@ import { Task } from '../Queue/TaskProvider'; | ||||
| @registerAction | ||||
| export class UpgradeResourceToLevel extends ActionController { | ||||
|     async run(args: Args, task: Task): Promise<any> { | ||||
|         const deposits = grabResourceDeposits(); | ||||
|         const deposits = grabResourceSlots(); | ||||
|         if (deposits.length === 0) { | ||||
|             throw new ActionError('No deposits'); | ||||
|         } | ||||
| @@ -20,7 +20,7 @@ export class UpgradeResourceToLevel extends ActionController { | ||||
|         const requiredLevel = getNumber(args.level); | ||||
|  | ||||
|         const notUpgraded = deposits.filter( | ||||
|             dep => !dep.underConstruction && requiredLevel > dep.level | ||||
|             dep => !dep.isUnderConstruction && requiredLevel > dep.level | ||||
|         ); | ||||
|  | ||||
|         if (notUpgraded.length === 0) { | ||||
| @@ -52,7 +52,7 @@ export class UpgradeResourceToLevel extends ActionController { | ||||
|         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(); | ||||
|         return ( | ||||
|             undefined === | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import { BuildingPageController } from './Page/BuildingPageController'; | ||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||
| import { grabActiveVillageId } from './Page/VillageBlock'; | ||||
| import { | ||||
|     grabResourceDeposits, | ||||
|     grabResourceSlots, | ||||
|     onBuildingSlotCtrlClick, | ||||
|     onResourceSlotCtrlClick, | ||||
|     showBuildingSlotIds, | ||||
| @@ -166,7 +166,7 @@ export class ControlPanel { | ||||
|     } | ||||
|  | ||||
|     private createDepositsQuickActions(villageId: number) { | ||||
|         const deposits = grabResourceDeposits(); | ||||
|         const deposits = grabResourceSlots(); | ||||
|         if (deposits.length === 0) { | ||||
|             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 type: ResourceType; | ||||
|     readonly level: number; | ||||
|     readonly ready: boolean; | ||||
|     readonly underConstruction: boolean; | ||||
|     readonly isReady: 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 { BuildingQueueInfo } from '../Game'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
| import { grabResourceSlots } from '../Page/SlotBlock'; | ||||
|  | ||||
| export class VillageOverviewPageGrabber extends Grabber { | ||||
|     grab(): void { | ||||
| @@ -13,6 +14,7 @@ export class VillageOverviewPageGrabber extends Grabber { | ||||
|         } | ||||
|  | ||||
|         this.storage.storeResourcesPerformance(grabResourcesPerformance()); | ||||
|         this.storage.storeResourceSlots(grabResourceSlots()); | ||||
|  | ||||
|         const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); | ||||
|         const buildingEndTime = buildingQueueInfo.seconds | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| import { elClassId, getNumber } from '../utils'; | ||||
| import { ResourceDeposit } from '../Game'; | ||||
| import { ResourceSlot } from '../Game'; | ||||
| import { numberToResourceType } from '../Core/ResourceType'; | ||||
|  | ||||
| interface Slot { | ||||
| interface SlotElement { | ||||
|     el: HTMLElement; | ||||
|     buildId: number; | ||||
| } | ||||
|  | ||||
| function slotElements(prefix: string): Array<Slot> { | ||||
|     const result: Array<Slot> = []; | ||||
| function slotElements(prefix: string): Array<SlotElement> { | ||||
|     const result: Array<SlotElement> = []; | ||||
|     jQuery('.level.colorLayer').each((idx, el) => { | ||||
|         const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix)); | ||||
|         result.push({ el, buildId }); | ||||
| @@ -74,18 +74,19 @@ export function onBuildingSlotCtrlClick(onClickHandler: (buildId: number) => voi | ||||
|     onSlotCtrlClick('aid', onClickHandler); | ||||
| } | ||||
|  | ||||
| function slotToDepositMapper(slot: Slot): ResourceDeposit { | ||||
|     const el = slot.el; | ||||
|     const classes = jQuery(el).attr('class'); | ||||
| function makeResourceSlot(slot: SlotElement): ResourceSlot { | ||||
|     const $el = jQuery(slot.el); | ||||
|     const classes = $el.attr('class'); | ||||
|     return { | ||||
|         buildId: getNumber(elClassId(classes, 'buildingSlot')), | ||||
|         type: numberToResourceType(getNumber(elClassId(classes, 'gid'))), | ||||
|         level: getNumber(elClassId(classes, 'level')), | ||||
|         ready: !jQuery(el).hasClass('notNow'), | ||||
|         underConstruction: jQuery(el).hasClass('underConstruction'), | ||||
|         isReady: !$el.hasClass('notNow'), | ||||
|         isUnderConstruction: $el.hasClass('underConstruction'), | ||||
|         isMaxLevel: $el.hasClass('maxLevel'), | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function grabResourceDeposits(): Array<ResourceDeposit> { | ||||
|     return slotElements('buildingSlot').map(slotToDepositMapper); | ||||
| export function grabResourceSlots(): Array<ResourceSlot> { | ||||
|     return slotElements('buildingSlot').map(makeResourceSlot); | ||||
| } | ||||
|   | ||||
| @@ -74,7 +74,7 @@ export class Scheduler { | ||||
|     nextTask(ts: number): NextExecution { | ||||
|         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) { | ||||
|             this.clearActions(); | ||||
|             return {}; | ||||
| @@ -99,13 +99,21 @@ export class Scheduler { | ||||
|     private replaceTask(task: Task): Task | undefined { | ||||
|         if (task.name === RunVillageProductionTask.name && 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) { | ||||
|                 this.removeTask(task.id); | ||||
|                 const newTask = new Task(villageTask.id, 0, villageTask.name, { | ||||
|                     ...villageTask.args, | ||||
|                     villageId: controller.getVillageId(), | ||||
|                     villageId: controllerForSelect.getVillageId(), | ||||
|                 }); | ||||
|                 this.taskQueue.add(newTask); | ||||
|                 return newTask; | ||||
|   | ||||
| @@ -6,12 +6,15 @@ import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; | ||||
| import { ProductionQueue } from '../Core/ProductionQueue'; | ||||
| import { getNumber } from '../utils'; | ||||
| import { Task, uniqTaskId } from '../Queue/TaskProvider'; | ||||
| import { ResourceSlot, ResourceSlotDefaults } from '../Game'; | ||||
|  | ||||
| const RESOURCES_KEY = 'resources'; | ||||
| const CAPACITY_KEY = 'capacity'; | ||||
| const PERFORMANCE_KEY = 'performance'; | ||||
| const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; | ||||
| const MERCHANTS_INFO_KEY = 'merchants_info'; | ||||
| const RESOURCE_SLOTS_KEY = 'resource_slots'; | ||||
| const BUILDING_SLOTS_KEY = 'building_slots'; | ||||
| const SETTINGS_KEY = 'settings'; | ||||
| const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; | ||||
| 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 { | ||||
|         return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, { | ||||
|             factory: () => Object.assign({}, VillageSettingsDefaults), | ||||
|   | ||||
| @@ -6,6 +6,9 @@ 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'; | ||||
|  | ||||
| export class VillageController { | ||||
|     private readonly villageId: number; | ||||
| @@ -147,4 +150,47 @@ export class VillageController { | ||||
|         const newSettings = { ...this.state.settings, receiveResourcesMode: next }; | ||||
|         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) { | ||||
|         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)) { | ||||
|             throw new Error(`Task "${name}" is not production task`); | ||||
|         } | ||||
|  | ||||
|         if (args.villageId !== this.villageId) { | ||||
|             throw new Error( | ||||
|                 `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(); | ||||
|         tasks.push(task); | ||||
|         this.storage.storeTaskList(tasks); | ||||
|         return new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args }); | ||||
|     } | ||||
|  | ||||
|     removeTask(taskId: TaskId) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user