From 129f107881524180d412c408063e1c137b6d69cb Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Fri, 1 May 2020 16:13:12 +0300 Subject: [PATCH] Rebuild village state calculation --- src/ControlPanel.ts | 127 +++++++------------------ src/DashboardView/Header.vue | 4 +- src/DashboardView/TaskList.vue | 5 +- src/DashboardView/VillageStateList.vue | 122 +++++++++++++++--------- src/Queue/Args.ts | 1 + src/Scheduler.ts | 12 +++ src/VillageState.ts | 100 +++++++++++++++++++ 7 files changed, 226 insertions(+), 145 deletions(-) create mode 100644 src/VillageState.ts diff --git a/src/ControlPanel.ts b/src/ControlPanel.ts index 928c6fc..6835ae9 100644 --- a/src/ControlPanel.ts +++ b/src/ControlPanel.ts @@ -1,4 +1,4 @@ -import { parseLocation, timestamp, uniqId, waitForLoad } from './utils'; +import { notify, parseLocation, timestamp, uniqId, waitForLoad } from './utils'; import { Scheduler } from './Scheduler'; import { BuildingPageController } from './Page/BuildingPageController'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; @@ -13,21 +13,35 @@ import Vue from 'vue'; import DashboardApp from './DashboardView/Dashboard.vue'; import { ResourcesToLevel } from './Task/ResourcesToLevel'; import { ConsoleLogger, Logger } from './Logger'; -import { VillageStorage } from './Storage/VillageStorage'; -import { Resources } from './Core/Resources'; -import { Coordinates, Village } from './Core/Village'; -import { calcGatheringTimings } from './Core/GatheringTimings'; import { DataStorage } from './DataStorage'; import { getBuildingPageAttributes, isBuildingPage } from './Page/PageDetectors'; import { debounce } from 'debounce'; import { ExecutionStorage } from './Storage/ExecutionStorage'; -import { ResourceStorage } from './Core/ResourceStorage'; +import { createVillageStates, VillageState } from './VillageState'; +import { Task } from './Queue/TaskProvider'; +import { Action } from './Queue/ActionQueue'; interface QuickAction { label: string; cb: () => void; } +interface GameState { + name: string; + version: string; + activeVillageState: VillageState | undefined; + villageStates: ReadonlyArray; + taskList: ReadonlyArray; + actionList: ReadonlyArray; + quickActions: Array; + pauseSeconds: number; + + refresh(): void; + removeTask(taskId: string): void; + refreshVillages(): void; + pause(): void; +} + export class ControlPanel { private readonly version: string; private readonly scheduler: Scheduler; @@ -48,18 +62,17 @@ export class ControlPanel { const villageId = grabActiveVillageId(); const scheduler = this.scheduler; - const quickActions: QuickAction[] = []; const executionState = new ExecutionStorage(); - const state: any = { - name: 'Dashboard', + const state: GameState = { + name: 'Control', version: this.version, - activeVillage: {}, - villages: [], + activeVillageState: undefined, + villageStates: [], taskList: [], actionList: [], - quickActions: quickActions, + quickActions: [], pauseSeconds: 0, refresh() { @@ -76,12 +89,10 @@ export class ControlPanel { }, refreshVillages() { - this.villages = grabVillageList().map(village => { - return new VillageController(village, new VillageStorage(village.id), scheduler); - }); - for (let village of this.villages) { - if (village.active) { - this.activeVillage = village; + this.villageStates = createVillageStates(grabVillageList(), scheduler); + for (let state of this.villageStates) { + if (state.village.active) { + this.activeVillageState = state; } } }, @@ -111,7 +122,7 @@ export class ControlPanel { if (p.pathname === '/dorf1.php') { showResourceSlotIds(buildingsInQueue); onResourceSlotCtrlClick(buildId => this.onResourceSlotCtrlClick(villageId, buildId)); - quickActions.push(...this.createDepositsQuickActions(villageId)); + state.quickActions.push(...this.createDepositsQuickActions(villageId)); } if (p.pathname === '/dorf2.php') { @@ -126,12 +137,12 @@ export class ControlPanel { this.createControlPanel(state); } - private createControlPanel(state: any) { + private createControlPanel(gameState: GameState) { const appId = `app-${uniqId()}`; jQuery('body').prepend(`
`); new Vue({ el: `#${appId}`, - data: state, + data: gameState, render: h => h(DashboardApp), }); } @@ -157,78 +168,6 @@ export class ControlPanel { private onResourceSlotCtrlClick(villageId: number, buildId: number) { this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId }); - const n = new Notification(`Building ${buildId} scheduled`); - setTimeout(() => n && n.close(), 4000); - } -} - -class VillageController { - public readonly id: number; - public readonly name: string; - public readonly crd: Coordinates; - public readonly active: boolean; - public readonly lumber: number; - public readonly clay: number; - public readonly iron: number; - public readonly crop: number; - public readonly resources: Resources; - public readonly performance: Resources; - public readonly requiredResources: Resources; - public readonly requiredBalance: Resources; - public readonly totalRequiredResources: Resources; - public readonly totalRequiredBalance: Resources; - public readonly incomingResources: Resources; - public readonly storage: ResourceStorage; - public readonly warehouse: number; - public readonly granary: number; - public readonly buildRemainingSeconds: number; - - constructor(village: Village, state: VillageStorage, scheduler: Scheduler) { - const resources = state.getResources(); - const storage = state.getResourceStorage(); - const performance = state.getResourcesPerformance(); - const buildQueueInfo = state.getBuildingQueueInfo(); - const requiredResources = scheduler.getVillageRequiredResources(village.id); - const totalRequiredResources = scheduler.getTotalVillageRequiredResources(village.id); - this.id = village.id; - this.name = village.name; - this.crd = village.crd; - this.active = village.active; - this.lumber = resources.lumber; - this.clay = resources.clay; - this.iron = resources.iron; - this.crop = resources.crop; - this.resources = resources; - this.performance = performance; - this.requiredResources = requiredResources; - this.requiredBalance = resources.sub(requiredResources); - this.totalRequiredResources = totalRequiredResources; - this.totalRequiredBalance = resources.sub(totalRequiredResources); - this.storage = storage; - this.warehouse = storage.warehouse; - this.granary = storage.granary; - this.buildRemainingSeconds = buildQueueInfo.seconds; - this.incomingResources = this.calcIncomingResources(state); - } - - timeToRequired() { - return this.timeToResources(this.requiredResources); - } - - timeToTotalRequired() { - return this.timeToResources(this.totalRequiredResources); - } - - private timeToResources(resources: Resources): number { - const timings = calcGatheringTimings(this.resources, resources, this.performance); - if (timings.never) { - return -1; - } - - return timings.hours * 3600; - } - - private calcIncomingResources(state: VillageStorage): Resources { - return state.getIncomingMerchants().reduce((m, i) => m.add(i.resources), new Resources(0, 0, 0, 0)); + notify(`Building ${buildId} scheduled`); } } diff --git a/src/DashboardView/Header.vue b/src/DashboardView/Header.vue index c5b0597..4dde606 100644 --- a/src/DashboardView/Header.vue +++ b/src/DashboardView/Header.vue @@ -19,8 +19,8 @@ export default { }, computed: { villageName() { - let village = this.shared.activeVillage; - return village ? village.name : 'Unknown'; + let state = this.shared.activeVillageState; + return state ? state.village.name : 'Unknown'; }, }, methods: { diff --git a/src/DashboardView/TaskList.vue b/src/DashboardView/TaskList.vue index ebba85f..4cd7140 100644 --- a/src/DashboardView/TaskList.vue +++ b/src/DashboardView/TaskList.vue @@ -25,13 +25,12 @@ @@ -244,6 +273,7 @@ export default { .performance-line td, .required-line td, +.commitments-line td, .incoming-line td { padding: 0 4px 4px; font-size: 90%; diff --git a/src/Queue/Args.ts b/src/Queue/Args.ts index 7d922a2..613a8e8 100644 --- a/src/Queue/Args.ts +++ b/src/Queue/Args.ts @@ -6,6 +6,7 @@ export interface Args { taskId?: TaskId; targetTaskId?: TaskId; villageId?: number; + targetVillageId?: number; buildId?: number; categoryId?: number; sheetId?: number; diff --git a/src/Scheduler.ts b/src/Scheduler.ts index f454fa2..1809046 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -133,6 +133,18 @@ export class Scheduler { return tasks.reduce((acc, t) => acc.add(t.args.resources!), new Resources(0, 0, 0, 0)); } + getResourceCommitments(villageId: number): Array { + const tasks = this.taskQueue + .seeItems() + .filter( + t => + t.name === SendResourcesTask.name && + t.args.villageId === villageId && + t.args.targetVillageId !== undefined + ); + return tasks.map(t => t.args.targetVillageId!); + } + private reorderVillageTasks(villageId: number) { const tasks = this.taskQueue.seeItems(); const trainPred = (t: Task) => isTrainTroopTask(t.name) && sameVillage(villageId, t.args); diff --git a/src/VillageState.ts b/src/VillageState.ts new file mode 100644 index 0000000..6f1766a --- /dev/null +++ b/src/VillageState.ts @@ -0,0 +1,100 @@ +import { Village } from './Core/Village'; +import { Scheduler } from './Scheduler'; +import { Resources } from './Core/Resources'; +import { VillageStorage } from './Storage/VillageStorage'; +import { calcGatheringTimings } from './Core/GatheringTimings'; + +interface RequiredResources { + resources: Resources; + balance: Resources; + time: number; +} + +interface VillageOwnState { + id: number; + village: Village; + resources: Resources; + performance: Resources; + required: RequiredResources; + totalRequired: RequiredResources; + incomingResources: Resources; + storage: Resources; + buildRemainingSeconds: number; +} + +interface VillageOwnStateDictionary { + [id: number]: VillageOwnState; +} + +export interface VillageState extends VillageOwnState { + commitments: Resources; +} + +function calcResourceBalance(resources: Resources, current: Resources, performance: Resources): RequiredResources { + return { + resources: resources, + balance: current.sub(resources), + time: timeToResources(current, resources, performance), + }; +} + +function timeToResources(current: Resources, desired: Resources, performance: Resources): number { + const timings = calcGatheringTimings(current, desired, performance); + if (timings.never) { + return -1; + } + + return timings.hours * 3600; +} + +function calcIncomingResources(storage: VillageStorage): Resources { + return storage.getIncomingMerchants().reduce((m, i) => m.add(i.resources), Resources.zero()); +} + +function createVillageOwnState(village: Village, scheduler: Scheduler): VillageOwnState { + const storage = new VillageStorage(village.id); + const resources = storage.getResources(); + const resourceStorage = storage.getResourceStorage(); + const performance = storage.getResourcesPerformance(); + const buildQueueInfo = storage.getBuildingQueueInfo(); + const requiredResources = scheduler.getVillageRequiredResources(village.id); + const totalRequiredResources = scheduler.getTotalVillageRequiredResources(village.id); + + return { + id: village.id, + village, + resources, + performance, + required: calcResourceBalance(requiredResources, resources, performance), + totalRequired: calcResourceBalance(totalRequiredResources, resources, performance), + storage: Resources.fromStorage(resourceStorage), + buildRemainingSeconds: buildQueueInfo.seconds, + incomingResources: calcIncomingResources(storage), + }; +} + +function createVillageOwnStates(villages: Array, scheduler: Scheduler): VillageOwnStateDictionary { + const result: VillageOwnStateDictionary = {}; + for (let village of villages) { + result[village.id] = createVillageOwnState(village, scheduler); + } + return result; +} + +function createVillageState( + state: VillageOwnState, + ownStates: VillageOwnStateDictionary, + scheduler: Scheduler +): VillageState { + const villageIds = scheduler.getResourceCommitments(state.id); + const commitments = villageIds.reduce( + (res, villageId) => res.add(ownStates[villageId].required.balance), + Resources.zero() + ); + return { ...state, commitments }; +} + +export function createVillageStates(villages: Array, scheduler: Scheduler): Array { + const ownStates = createVillageOwnStates(villages, scheduler); + return villages.map(village => createVillageState(ownStates[village.id], ownStates, scheduler)); +}