Auto build warehouse, granary and crop fields
This commit is contained in:
parent
7653c7b6e7
commit
effc1b1626
@ -4,6 +4,7 @@ import { BuildingPageController } from './Page/BuildingPageController';
|
||||
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
|
||||
import { grabActiveVillageId } from './Page/VillageBlock';
|
||||
import {
|
||||
grabBuildingSlots,
|
||||
grabResourceSlots,
|
||||
onBuildingSlotCtrlClick,
|
||||
onResourceSlotCtrlClick,
|
||||
@ -126,6 +127,7 @@ export class ControlPanel {
|
||||
.map(t => t.args.buildId || 0);
|
||||
|
||||
if (p.pathname === '/dorf1.php') {
|
||||
console.log('RSLOTS', grabResourceSlots());
|
||||
showResourceSlotIds(getBuildingsInQueue());
|
||||
state.quickActions.push(...this.createDepositsQuickActions(villageId));
|
||||
onResourceSlotCtrlClick(buildId => {
|
||||
@ -135,6 +137,7 @@ export class ControlPanel {
|
||||
}
|
||||
|
||||
if (p.pathname === '/dorf2.php') {
|
||||
console.log('BSLOTS', grabBuildingSlots());
|
||||
showBuildingSlotIds(getBuildingsInQueue());
|
||||
onBuildingSlotCtrlClick(buildId => {
|
||||
this.onSlotCtrlClick(villageId, buildId);
|
||||
|
@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<table class="task-list">
|
||||
<tr v-for="task in tasks">
|
||||
<td
|
||||
class="col-name"
|
||||
v-text="(task.canBeBuilt ? '' : '(!) ') + task.name"
|
||||
:title="task.name + ', ' + task.id"
|
||||
></td>
|
||||
<td class="col-name" v-text="taskLabel(task)" :title="task.name + ', ' + task.id"></td>
|
||||
<td class="col-actions">
|
||||
<a href="#" class="action" @click.prevent="upTask(task.id)" title="Поднять задачу">up</a>
|
||||
<a href="#" class="action" @click.prevent="downTask(task.id)" title="Опустить задачу">dn</a>
|
||||
@ -30,6 +26,19 @@ export default {
|
||||
props: ['villageId', 'tasks'],
|
||||
computed: {},
|
||||
methods: {
|
||||
taskLabel(task) {
|
||||
let taskStatus = '';
|
||||
if (!task.isEnoughWarehouseCapacity) {
|
||||
taskStatus += 'w!';
|
||||
}
|
||||
if (!task.isEnoughGranaryCapacity) {
|
||||
taskStatus += 'g!';
|
||||
}
|
||||
if (taskStatus) {
|
||||
taskStatus = '(' + taskStatus + ') ';
|
||||
}
|
||||
return taskStatus + task.name;
|
||||
},
|
||||
upTask(taskId) {
|
||||
this.$store.dispatch(Actions.UpVillageTask, { villageId: this.villageId, taskId });
|
||||
},
|
||||
|
22
src/Game.ts
22
src/Game.ts
@ -7,18 +7,34 @@ export class BuildingQueueInfo {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResourceSlot {
|
||||
export interface Slot {
|
||||
readonly buildId: number;
|
||||
readonly type: ResourceType;
|
||||
readonly level: number;
|
||||
readonly isReady: boolean;
|
||||
readonly isUnderConstruction: boolean;
|
||||
readonly isMaxLevel: boolean;
|
||||
}
|
||||
|
||||
export interface ResourceSlot extends Slot {
|
||||
readonly type: ResourceType;
|
||||
}
|
||||
|
||||
export const ResourceSlotDefaults: ResourceSlot = {
|
||||
buildId: 0,
|
||||
type: ResourceType.Lumber,
|
||||
buildId: 0,
|
||||
level: 0,
|
||||
isReady: false,
|
||||
isUnderConstruction: false,
|
||||
isMaxLevel: false,
|
||||
};
|
||||
|
||||
export interface BuildingSlot extends Slot {
|
||||
readonly buildTypeId: number;
|
||||
}
|
||||
|
||||
export const BuildingSlotDefaults: BuildingSlot = {
|
||||
buildTypeId: 0,
|
||||
buildId: 0,
|
||||
level: 0,
|
||||
isReady: false,
|
||||
isUnderConstruction: false,
|
||||
|
@ -7,6 +7,7 @@ import { BuildingContractGrabber } from './BuildingContractGrabber';
|
||||
import { ForgePageGrabber } from './ForgePageGrabber';
|
||||
import { GuildHallPageGrabber } from './GuildHallPageGrabber';
|
||||
import { VillageFactory } from '../VillageFactory';
|
||||
import { VillageBuildingsPageGrabber } from './VillageBuildingsPageGrabber';
|
||||
|
||||
export class GrabberManager {
|
||||
private factory: VillageFactory;
|
||||
@ -28,6 +29,7 @@ export class GrabberManager {
|
||||
const grabbers: Array<Grabber> = [];
|
||||
grabbers.push(new VillageResourceGrabber(taskCollection, storage));
|
||||
grabbers.push(new VillageOverviewPageGrabber(taskCollection, storage));
|
||||
grabbers.push(new VillageBuildingsPageGrabber(taskCollection, storage));
|
||||
grabbers.push(new HeroPageGrabber(taskCollection, storage));
|
||||
grabbers.push(new MarketPageGrabber(taskCollection, storage));
|
||||
grabbers.push(new BuildingContractGrabber(taskCollection, storage));
|
||||
|
14
src/Grabber/VillageBuildingsPageGrabber.ts
Normal file
14
src/Grabber/VillageBuildingsPageGrabber.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { parseLocation } from '../utils';
|
||||
import { grabBuildingSlots } from '../Page/SlotBlock';
|
||||
|
||||
export class VillageBuildingsPageGrabber extends Grabber {
|
||||
grab(): void {
|
||||
const p = parseLocation();
|
||||
if (p.pathname !== '/dorf2.php') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storage.storeBuildingSlots(grabBuildingSlots());
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { elClassId, getNumber } from '../utils';
|
||||
import { ResourceSlot } from '../Game';
|
||||
import { BuildingSlot, ResourceSlot } from '../Game';
|
||||
import { numberToResourceType } from '../Core/ResourceType';
|
||||
|
||||
interface SlotElement {
|
||||
@ -90,3 +90,22 @@ function makeResourceSlot(slot: SlotElement): ResourceSlot {
|
||||
export function grabResourceSlots(): Array<ResourceSlot> {
|
||||
return slotElements('buildingSlot').map(makeResourceSlot);
|
||||
}
|
||||
|
||||
function makeBuildingSlot(slot: SlotElement): BuildingSlot {
|
||||
const $el = jQuery(slot.el);
|
||||
const classes = $el.attr('class');
|
||||
const $parent = $el.closest('.buildingSlot');
|
||||
const parentClasses = $parent.attr('class');
|
||||
return {
|
||||
buildId: getNumber(elClassId(classes, 'aid')),
|
||||
buildTypeId: getNumber(elClassId(parentClasses, 'g')),
|
||||
level: getNumber(elClassId(classes, 'level')),
|
||||
isReady: !$el.hasClass('notNow'),
|
||||
isUnderConstruction: $el.hasClass('underConstruction'),
|
||||
isMaxLevel: $el.hasClass('maxLevel'),
|
||||
};
|
||||
}
|
||||
|
||||
export function grabBuildingSlots(): Array<BuildingSlot> {
|
||||
return slotElements('aid').map(makeBuildingSlot);
|
||||
}
|
||||
|
@ -20,7 +20,12 @@ export function uniqTaskId(): TaskId {
|
||||
return 'tid.' + ts + '.' + String(idSequence).padStart(4, '0') + '.' + uniqId('');
|
||||
}
|
||||
|
||||
export class Task {
|
||||
export interface TaskCore {
|
||||
readonly name: string;
|
||||
readonly args: Args;
|
||||
}
|
||||
|
||||
export class Task implements TaskCore {
|
||||
readonly id: TaskId;
|
||||
readonly ts: number;
|
||||
readonly name: string;
|
||||
@ -51,7 +56,26 @@ export interface TaskTransformer {
|
||||
}
|
||||
|
||||
export function isInQueue(queue: ProductionQueue): TaskMatcher {
|
||||
return (task: Task) => getProductionQueue(task.name) === queue;
|
||||
return (task: TaskCore) => getProductionQueue(task.name) === queue;
|
||||
}
|
||||
|
||||
export function isBuildingPlanned(
|
||||
name: string,
|
||||
buildId: number | undefined,
|
||||
buildTypeId: number | undefined
|
||||
) {
|
||||
return (task: TaskCore) => {
|
||||
if (name !== task.name) {
|
||||
return false;
|
||||
}
|
||||
if (buildId && task.args.buildId) {
|
||||
return buildId === task.args.buildId;
|
||||
}
|
||||
if (buildTypeId && task.args.buildTypeId) {
|
||||
return buildTypeId === task.args.buildTypeId;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
export function withTime(ts: number): TaskTransformer {
|
||||
|
@ -6,7 +6,7 @@ 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';
|
||||
import { BuildingSlot, BuildingSlotDefaults, ResourceSlot, ResourceSlotDefaults } from '../Game';
|
||||
|
||||
const RESOURCES_KEY = 'resources';
|
||||
const CAPACITY_KEY = 'capacity';
|
||||
@ -96,6 +96,16 @@ export class VillageStorage {
|
||||
this.storage.set(RESOURCE_SLOTS_KEY, slots);
|
||||
}
|
||||
|
||||
getBuildingSlots(): ReadonlyArray<BuildingSlot> {
|
||||
return this.storage.getTypedList<BuildingSlot>(BUILDING_SLOTS_KEY, {
|
||||
factory: () => Object.assign({}, BuildingSlotDefaults),
|
||||
});
|
||||
}
|
||||
|
||||
storeBuildingSlots(slots: ReadonlyArray<BuildingSlot>): void {
|
||||
this.storage.set(BUILDING_SLOTS_KEY, slots);
|
||||
}
|
||||
|
||||
getSettings(): VillageSettings {
|
||||
return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, {
|
||||
factory: () => Object.assign({}, VillageSettingsDefaults),
|
||||
|
@ -13,6 +13,15 @@ export function goToResourceViewPage(villageId: number): ActionDefinition {
|
||||
};
|
||||
}
|
||||
|
||||
export function goToBuildingsViewPage(villageId: number): ActionDefinition {
|
||||
return {
|
||||
name: GoToPageAction.name,
|
||||
args: {
|
||||
path: path('/dorf2.php', { newdid: villageId }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function goToMarketSendResourcesPage(villageId: number): ActionDefinition {
|
||||
return {
|
||||
name: GoToPageAction.name,
|
||||
@ -44,6 +53,7 @@ export function scanAllVillagesBundle(villages: Array<Village>): Array<ActionDef
|
||||
const actions: Array<ActionDefinition> = [];
|
||||
for (let village of villages) {
|
||||
actions.push(goToResourceViewPage(village.id));
|
||||
actions.push(goToBuildingsViewPage(village.id));
|
||||
actions.push(goToMarketSendResourcesPage(village.id));
|
||||
actions.push(goToForgePage(village.id));
|
||||
actions.push(goToGuildHallPage(village.id));
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { VillageTaskCollection } from './VillageTaskCollection';
|
||||
import { TaskId } from './Queue/TaskProvider';
|
||||
import { isBuildingPlanned, TaskId } from './Queue/TaskProvider';
|
||||
import { Args } from './Queue/Args';
|
||||
import { TaskState, VillageState } from './VillageState';
|
||||
import { Resources } from './Core/Resources';
|
||||
@ -9,6 +9,7 @@ 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;
|
||||
@ -152,39 +153,49 @@ export class VillageController {
|
||||
}
|
||||
|
||||
planTasks(): void {
|
||||
const performance = this.state.performance;
|
||||
|
||||
if (performance.crop < 100) {
|
||||
this.planCropBuilding();
|
||||
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);
|
||||
const cropSlots = resourceSlots.filter(s => s.type === ResourceType.Crop && !s.isMaxLevel);
|
||||
if (cropSlots.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check, if crop field is building now
|
||||
const isCropBuilding = cropSlots.filter(s => s.isUnderConstruction);
|
||||
if (isCropBuilding) {
|
||||
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);
|
||||
const cropBuildingTaskInQueue = tasks.find(
|
||||
t => t.args.buildId && cropBuildIds.includes(t.args.buildId)
|
||||
);
|
||||
if (cropBuildingTaskInQueue !== undefined) {
|
||||
return;
|
||||
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
|
||||
const readyCropSlots = cropSlots.filter(s => !s.isMaxLevel);
|
||||
readyCropSlots.sort((s1, s2) => s1.level - s2.level);
|
||||
cropSlots.sort((s1, s2) => s1.level - s2.level);
|
||||
|
||||
const targetCropBuildId = _.first(readyCropSlots)?.buildId;
|
||||
const targetCropBuildId = _.first(cropSlots)?.buildId;
|
||||
if (!targetCropBuildId) {
|
||||
return;
|
||||
}
|
||||
@ -193,4 +204,55 @@ export class VillageController {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ export interface TaskState {
|
||||
id: TaskId;
|
||||
name: string;
|
||||
args: Args;
|
||||
isEnoughWarehouseCapacity: boolean;
|
||||
isEnoughGranaryCapacity: boolean;
|
||||
canBeBuilt: boolean;
|
||||
}
|
||||
|
||||
@ -222,11 +224,29 @@ function getTaskResources(task: Task | TaskState | undefined): Resources {
|
||||
}
|
||||
|
||||
function makeTaskState(task: Task, maxResourcesForTask: Resources): TaskState {
|
||||
const taskResources = getTaskResources(task);
|
||||
const taskWarehouseRequirements = new Resources(
|
||||
taskResources.lumber,
|
||||
taskResources.clay,
|
||||
taskResources.iron,
|
||||
0
|
||||
);
|
||||
const isEnoughWarehouseCapacity = maxResourcesForTask.allGreaterOrEqual(
|
||||
taskWarehouseRequirements
|
||||
);
|
||||
|
||||
const taskGranaryRequirements = new Resources(0, 0, 0, taskResources.crop);
|
||||
const isEnoughGranaryCapacity = maxResourcesForTask.allGreaterOrEqual(taskGranaryRequirements);
|
||||
|
||||
const canBeBuilt = isEnoughWarehouseCapacity && isEnoughGranaryCapacity;
|
||||
|
||||
return {
|
||||
id: task.id,
|
||||
args: task.args,
|
||||
name: task.name,
|
||||
canBeBuilt: maxResourcesForTask.allGreaterOrEqual(getTaskResources(task)),
|
||||
isEnoughWarehouseCapacity,
|
||||
isEnoughGranaryCapacity,
|
||||
canBeBuilt,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,13 +56,7 @@ export class VillageTaskCollection {
|
||||
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}`
|
||||
);
|
||||
}
|
||||
|
||||
return new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args });
|
||||
return new Task(uniqTaskId(), 0, name, { ...args, villageId: this.villageId });
|
||||
}
|
||||
|
||||
removeTask(taskId: TaskId) {
|
||||
|
@ -59,8 +59,9 @@ export function elClassId(classes: string | undefined, prefix: string): number |
|
||||
}
|
||||
let result: number | undefined = undefined;
|
||||
classes.split(/\s/).forEach(part => {
|
||||
if (part.startsWith(prefix)) {
|
||||
result = toNumber(part.substr(prefix.length));
|
||||
const match = part.match(new RegExp(prefix + '(\\d+)'));
|
||||
if (match) {
|
||||
result = toNumber(match[1]);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
@ -1,25 +1,39 @@
|
||||
import { it, describe } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { getNumber } from '../src/utils';
|
||||
import { elClassId, getNumber } from '../src/utils';
|
||||
|
||||
describe('Utils', function() {
|
||||
it('Can parse positive number', function() {
|
||||
const text = '123';
|
||||
expect(getNumber(text)).to.be.equals(123);
|
||||
describe('getNumber', function() {
|
||||
it('Can parse positive number', function() {
|
||||
const text = '123';
|
||||
expect(getNumber(text)).to.be.equals(123);
|
||||
});
|
||||
|
||||
it('Can parse positive number with noise', function() {
|
||||
const text = ' 123 ';
|
||||
expect(getNumber(text)).to.be.equals(123);
|
||||
});
|
||||
|
||||
it('Can parse negative number', function() {
|
||||
const text = '-146';
|
||||
expect(getNumber(text)).to.be.equals(-146);
|
||||
});
|
||||
|
||||
it('Can parse negative number with minus sign', function() {
|
||||
const text = '\u2212132';
|
||||
expect(getNumber(text)).to.be.equals(-132);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can parse positive number with noise', function() {
|
||||
const text = ' 123 ';
|
||||
expect(getNumber(text)).to.be.equals(123);
|
||||
});
|
||||
describe('elClassId', function() {
|
||||
it('Can parse number with prefix', function() {
|
||||
const text = 'foo bar12 baz';
|
||||
expect(elClassId(text, 'bar')).to.be.equals(12);
|
||||
});
|
||||
|
||||
it('Can parse negative number', function() {
|
||||
const text = '-146';
|
||||
expect(getNumber(text)).to.be.equals(-146);
|
||||
});
|
||||
|
||||
it('Can parse negative number with minus sign', function() {
|
||||
const text = '\u2212132';
|
||||
expect(getNumber(text)).to.be.equals(-132);
|
||||
it('Can parse number from parts with same prefix', function() {
|
||||
const text = 'foo12 foobar';
|
||||
expect(elClassId(text, 'foo')).to.be.equals(12);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user