First attempt to resource sending
This commit is contained in:
parent
542bc353b0
commit
fb4ac6424c
12
package-lock.json
generated
12
package-lock.json
generated
@ -336,6 +336,12 @@
|
||||
"integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/debounce": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz",
|
||||
"integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jquery": {
|
||||
"version": "3.3.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.34.tgz",
|
||||
@ -2082,6 +2088,12 @@
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
|
||||
"dev": true
|
||||
},
|
||||
"debounce": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
|
||||
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/debounce": "^1.2.0",
|
||||
"@types/jquery": "^3.3.34",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^13.9.4",
|
||||
@ -30,6 +31,7 @@
|
||||
"chai": "^4.2.0",
|
||||
"css-loader": "^3.5.2",
|
||||
"dateformat": "^3.0.3",
|
||||
"debounce": "^1.2.0",
|
||||
"jquery": "^3.4.1",
|
||||
"mocha": "^7.1.1",
|
||||
"mocha-junit-reporter": "^1.23.3",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ActionController, registerAction } from './ActionController';
|
||||
import { Args } from '../Command';
|
||||
import { Task } from '../Queue/TaskQueue';
|
||||
import { grabResources, grabResourceStorage } from '../Page/ResourcesBlock';
|
||||
import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock';
|
||||
import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { HeroState } from '../State/HeroState';
|
||||
@ -19,10 +19,10 @@ export class BalanceHeroResourcesAction extends ActionController {
|
||||
return;
|
||||
}
|
||||
|
||||
const resources = grabResources();
|
||||
const resources = grabVillageResources();
|
||||
const requiredResources = this.scheduler.getVillageRequiredResources(heroVillageId);
|
||||
const totalRequiredResources = this.scheduler.getTotalVillageRequiredResources(heroVillageId);
|
||||
const storage = grabResourceStorage();
|
||||
const storage = grabVillageResourceStorage();
|
||||
const currentType = grabCurrentHeroResource();
|
||||
|
||||
const heroType = calcHeroResource(resources, requiredResources, totalRequiredResources, storage);
|
||||
|
88
src/Action/SendResourcesAction.ts
Normal file
88
src/Action/SendResourcesAction.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { ActionController, registerAction } from './ActionController';
|
||||
import { Args } from '../Command';
|
||||
import { AbortTaskError, ActionError } from '../Errors';
|
||||
import { Task } from '../Queue/TaskQueue';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { Coordinates } from '../Core/Village';
|
||||
import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage';
|
||||
import { grabVillageResources } from '../Page/ResourcesBlock';
|
||||
import { grabVillageList } from '../Page/VillageBlock';
|
||||
import { SendResourcesTask } from '../Task/SendResourcesTask';
|
||||
import { timestamp } from '../utils';
|
||||
import { VillageState } from '../State/VillageState';
|
||||
|
||||
function err(msg): never {
|
||||
throw new ActionError(msg);
|
||||
}
|
||||
|
||||
@registerAction
|
||||
export class SendResourcesAction extends ActionController {
|
||||
async run(args: Args, task: Task): Promise<any> {
|
||||
const resources = Resources.fromObject(args.resources || err('No resources'));
|
||||
const coordinates = Coordinates.fromObject(args.coordinates || err('No coordinates'));
|
||||
|
||||
const merchants = grabMerchantsInfo();
|
||||
const villageList = grabVillageList();
|
||||
|
||||
const village = villageList.find(v => v.crd.eq(coordinates));
|
||||
|
||||
if (!village) {
|
||||
throw new AbortTaskError('No village');
|
||||
}
|
||||
|
||||
const capacity = merchants.available * merchants.carry;
|
||||
|
||||
if (!capacity) {
|
||||
throw new AbortTaskError('No merchants');
|
||||
}
|
||||
|
||||
console.log('Send', resources, 'to', coordinates);
|
||||
console.log('Merchants', merchants, capacity);
|
||||
|
||||
const villageResources = grabVillageResources();
|
||||
|
||||
const targetVillageState = new VillageState(village.id);
|
||||
const targetVillageResources = targetVillageState.getResources();
|
||||
const targetVillageRequirements = this.scheduler
|
||||
.getVillageRequiredResources(village.id)
|
||||
.sub(targetVillageResources)
|
||||
.max(Resources.zero());
|
||||
|
||||
if (targetVillageRequirements.eq(Resources.zero())) {
|
||||
throw new AbortTaskError('No requirements');
|
||||
}
|
||||
|
||||
let sendResources = targetVillageRequirements.min(villageResources).min(resources);
|
||||
|
||||
const reqSum = sendResources.lumber + sendResources.clay + sendResources.iron + sendResources.crop;
|
||||
|
||||
let coeff = reqSum > capacity ? capacity / reqSum : 1;
|
||||
|
||||
const normSendResources = sendResources.scale(coeff);
|
||||
|
||||
const remainingResources = resources.sub(normSendResources);
|
||||
|
||||
console.log('planned res', resources);
|
||||
console.log('current village res', villageResources);
|
||||
console.log('target village req', targetVillageRequirements);
|
||||
console.log('send res', sendResources);
|
||||
console.log('coeff', coeff);
|
||||
console.log('norm send res', normSendResources);
|
||||
console.log('remaining res', remainingResources);
|
||||
|
||||
if (remainingResources.gt(Resources.zero())) {
|
||||
console.log('schedule next', remainingResources);
|
||||
this.scheduler.scheduleTask(
|
||||
SendResourcesTask.name,
|
||||
{
|
||||
...args,
|
||||
resources: remainingResources,
|
||||
},
|
||||
timestamp() + 10 * 60
|
||||
);
|
||||
}
|
||||
|
||||
fillSendResourcesForm(normSendResources, coordinates);
|
||||
clickSendButton();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { TaskId } from './Queue/TaskQueue';
|
||||
import { ResourcesInterface } from './Core/Resources';
|
||||
import { CoordinatesInterface } from './Core/Village';
|
||||
|
||||
export interface Args {
|
||||
taskId?: TaskId;
|
||||
@ -7,11 +8,13 @@ export interface Args {
|
||||
villageId?: number;
|
||||
buildId?: number;
|
||||
categoryId?: number;
|
||||
sheetId?: number;
|
||||
tabId?: number;
|
||||
buildTypeId?: number;
|
||||
troopId?: number;
|
||||
trainCount?: number;
|
||||
resources?: ResourcesInterface;
|
||||
coordinates?: CoordinatesInterface;
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ import { ConsoleLogger, Logger } from './Logger';
|
||||
import { VillageState } from './State/VillageState';
|
||||
import { Resources } from './Core/Resources';
|
||||
import { Village } from './Core/Village';
|
||||
import { calcGatheringTimings } from './Core/GatheringTimings';
|
||||
import { DataStorage } from './DataStorage';
|
||||
import { debounce } from 'debounce';
|
||||
|
||||
interface QuickAction {
|
||||
label: string;
|
||||
@ -76,10 +79,19 @@ export class ControlPanel {
|
||||
};
|
||||
|
||||
state.refreshTasks();
|
||||
setInterval(() => state.refreshTasks(), 2000);
|
||||
|
||||
state.refreshVillages();
|
||||
|
||||
setInterval(() => {
|
||||
state.refreshTasks();
|
||||
state.refreshVillages();
|
||||
}, 3000);
|
||||
|
||||
DataStorage.onChange(() => {
|
||||
debounce(() => {
|
||||
setInterval(() => state.refreshTasks(), 2000);
|
||||
setInterval(() => state.refreshVillages(), 5000);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
const tasks = this.scheduler.getTaskItems();
|
||||
const buildingsInQueue = tasks
|
||||
@ -101,7 +113,8 @@ export class ControlPanel {
|
||||
buildId: getNumber(p.query.id),
|
||||
buildTypeId: getNumber(elClassId(jQuery('#build').attr('class'), 'gid')),
|
||||
categoryId: getNumber(p.query.category, 1),
|
||||
tabId: getNumber(p.query.s, 0),
|
||||
sheetId: getNumber(p.query.s, 0),
|
||||
tabId: getNumber(p.query.t, 0),
|
||||
});
|
||||
buildPage.run();
|
||||
}
|
||||
@ -223,29 +236,11 @@ class VillageController {
|
||||
}
|
||||
|
||||
private timeToResources(resources: Resources): number {
|
||||
const time_to_lumber = this.timeToRes(this.resources.lumber, resources.lumber, this.performance.lumber);
|
||||
|
||||
const time_to_clay = this.timeToRes(this.resources.clay, resources.clay, this.performance.clay);
|
||||
const time_to_iron = this.timeToRes(this.resources.iron, resources.iron, this.performance.iron);
|
||||
const time_to_crop = this.timeToRes(this.resources.crop, resources.crop, this.performance.crop);
|
||||
|
||||
const min = Math.max(time_to_lumber, time_to_clay, time_to_iron, time_to_crop);
|
||||
|
||||
if (min === -1) {
|
||||
const timings = calcGatheringTimings(this.resources, resources, this.performance);
|
||||
if (timings.never) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Math.max(time_to_lumber, time_to_clay, time_to_iron, time_to_crop);
|
||||
}
|
||||
|
||||
private timeToRes(current: number, desired: number, speed: number) {
|
||||
if (current >= desired) {
|
||||
return 0;
|
||||
}
|
||||
if (current < desired && speed <= 0) {
|
||||
return -1;
|
||||
}
|
||||
const diff = desired - current;
|
||||
return (diff / speed) * 3600;
|
||||
return timings.hours * 3600;
|
||||
}
|
||||
}
|
||||
|
56
src/Core/GatheringTimings.ts
Normal file
56
src/Core/GatheringTimings.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { Resources } from './Resources';
|
||||
|
||||
type GatheringNever = 'never';
|
||||
type GatheringTime = number | GatheringNever;
|
||||
|
||||
export class GatheringTimings {
|
||||
readonly lumber: GatheringTime;
|
||||
readonly clay: GatheringTime;
|
||||
readonly iron: GatheringTime;
|
||||
readonly crop: GatheringTime;
|
||||
|
||||
constructor(lumber: GatheringTime, clay: GatheringTime, iron: GatheringTime, crop: GatheringTime) {
|
||||
this.lumber = lumber;
|
||||
this.clay = clay;
|
||||
this.iron = iron;
|
||||
this.crop = crop;
|
||||
}
|
||||
|
||||
get common(): GatheringTime {
|
||||
const xs = [this.lumber, this.clay, this.iron, this.crop];
|
||||
return xs.reduce((m, t) => (m === 'never' || t === 'never' ? 'never' : Math.max(m, t)), 0);
|
||||
}
|
||||
|
||||
get hours(): number {
|
||||
const common = this.common;
|
||||
if (common === 'never') {
|
||||
throw Error('Never');
|
||||
}
|
||||
return common;
|
||||
}
|
||||
|
||||
get never(): boolean {
|
||||
return this.common === 'never';
|
||||
}
|
||||
}
|
||||
|
||||
function calcGatheringTime(val: number, desired: number, speed: number): GatheringTime {
|
||||
const diff = desired - val;
|
||||
if (diff > 0 && speed <= 0) {
|
||||
return 'never';
|
||||
}
|
||||
if (diff <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return diff / speed;
|
||||
}
|
||||
|
||||
export function calcGatheringTimings(resources: Resources, desired: Resources, speed: Resources): GatheringTimings {
|
||||
return new GatheringTimings(
|
||||
calcGatheringTime(resources.lumber, desired.lumber, speed.lumber),
|
||||
calcGatheringTime(resources.clay, desired.clay, speed.clay),
|
||||
calcGatheringTime(resources.iron, desired.iron, speed.iron),
|
||||
calcGatheringTime(resources.crop, desired.crop, speed.crop)
|
||||
);
|
||||
}
|
@ -29,6 +29,10 @@ export class Resources implements ResourcesInterface {
|
||||
return new Resources(storage.warehouse, storage.warehouse, storage.warehouse, storage.granary);
|
||||
}
|
||||
|
||||
static zero(): Resources {
|
||||
return new Resources(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
getByType(type: ResourceType): number {
|
||||
switch (type) {
|
||||
case ResourceType.Lumber:
|
||||
@ -72,19 +76,46 @@ export class Resources implements ResourcesInterface {
|
||||
);
|
||||
}
|
||||
|
||||
lt(other: Resources): boolean {
|
||||
eq(other: ResourcesInterface): boolean {
|
||||
return (
|
||||
this.lumber === other.lumber &&
|
||||
this.clay === other.clay &&
|
||||
this.iron === other.iron &&
|
||||
this.crop === other.crop
|
||||
);
|
||||
}
|
||||
|
||||
lt(other: ResourcesInterface): boolean {
|
||||
return this.lumber < other.lumber && this.clay < other.clay && this.iron < other.iron && this.crop < other.crop;
|
||||
}
|
||||
|
||||
gt(other: Resources): boolean {
|
||||
gt(other: ResourcesInterface): boolean {
|
||||
return this.lumber > other.lumber && this.clay > other.clay && this.iron > other.iron && this.crop > other.crop;
|
||||
}
|
||||
|
||||
lte(other: Resources): boolean {
|
||||
lte(other: ResourcesInterface): boolean {
|
||||
return !this.gt(other);
|
||||
}
|
||||
|
||||
gte(other: Resources): boolean {
|
||||
gte(other: ResourcesInterface): boolean {
|
||||
return !this.lt(other);
|
||||
}
|
||||
|
||||
min(other: ResourcesInterface): Resources {
|
||||
return new Resources(
|
||||
Math.min(this.lumber, other.lumber),
|
||||
Math.min(this.clay, other.clay),
|
||||
Math.min(this.iron, other.iron),
|
||||
Math.min(this.crop, other.crop)
|
||||
);
|
||||
}
|
||||
|
||||
max(other: ResourcesInterface): Resources {
|
||||
return new Resources(
|
||||
Math.max(this.lumber, other.lumber),
|
||||
Math.max(this.clay, other.clay),
|
||||
Math.max(this.iron, other.iron),
|
||||
Math.max(this.crop, other.crop)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,24 @@
|
||||
export class Coordinates {
|
||||
export interface CoordinatesInterface {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export class Coordinates implements CoordinatesInterface {
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
|
||||
static fromObject(crd: CoordinatesInterface) {
|
||||
return new Coordinates(crd.x, crd.y);
|
||||
}
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
eq(other: CoordinatesInterface): boolean {
|
||||
return this.x === other.x && this.y === other.y;
|
||||
}
|
||||
}
|
||||
|
||||
export class Village {
|
||||
|
@ -2,6 +2,8 @@ import { ConsoleLogger, Logger, NullLogger } from './Logger';
|
||||
|
||||
const NAMESPACE = 'travian:v1';
|
||||
|
||||
const storage = localStorage;
|
||||
|
||||
function join(...parts: Array<string>) {
|
||||
return parts.map(p => p.replace(/[:]+$/g, '').replace(/^[:]+/g, '')).join(':');
|
||||
}
|
||||
@ -16,10 +18,18 @@ export class DataStorage {
|
||||
this.logger = new NullLogger();
|
||||
}
|
||||
|
||||
static onChange(handler: (key: string) => void) {
|
||||
window.addEventListener('storage', ({ key, storageArea }) => {
|
||||
if (storageArea === storage) {
|
||||
handler(key || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string): any {
|
||||
const fullKey = join(NAMESPACE, this.name, key);
|
||||
try {
|
||||
const serialized = localStorage.getItem(fullKey);
|
||||
const serialized = storage.getItem(fullKey);
|
||||
this.logger.log('GET', fullKey, serialized);
|
||||
return JSON.parse(serialized || '"null"');
|
||||
} catch (e) {
|
||||
@ -32,13 +42,13 @@ export class DataStorage {
|
||||
|
||||
has(key: string): boolean {
|
||||
const fullKey = join(NAMESPACE, this.name, key);
|
||||
return localStorage.getItem(fullKey) !== null;
|
||||
return storage.getItem(fullKey) !== null;
|
||||
}
|
||||
|
||||
set(key: string, value: any) {
|
||||
const fullKey = join(NAMESPACE, this.name, key);
|
||||
let serialized = JSON.stringify(value);
|
||||
this.logger.log('SET', fullKey, serialized);
|
||||
localStorage.setItem(fullKey, serialized);
|
||||
storage.setItem(fullKey, serialized);
|
||||
}
|
||||
}
|
||||
|
@ -6,19 +6,19 @@ import { TaskQueueRenderer } from './TaskQueueRenderer';
|
||||
import { createAction } from './Action/ActionController';
|
||||
import { createTask } from './Task/TaskController';
|
||||
import { ConsoleLogger, Logger } from './Logger';
|
||||
import { StateGrabberManager } from './Grabber/StateGrabberManager';
|
||||
import { GrabberManager } from './Grabber/GrabberManager';
|
||||
import { Scheduler } from './Scheduler';
|
||||
|
||||
export class Executor {
|
||||
private readonly version: string;
|
||||
private readonly scheduler: Scheduler;
|
||||
private grabbers: StateGrabberManager;
|
||||
private grabbers: GrabberManager;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(version: string, scheduler: Scheduler) {
|
||||
this.version = version;
|
||||
this.scheduler = scheduler;
|
||||
this.grabbers = new StateGrabberManager();
|
||||
this.grabbers = new GrabberManager();
|
||||
this.logger = new ConsoleLogger(this.constructor.name);
|
||||
}
|
||||
|
||||
|
3
src/Grabber/Grabber.ts
Normal file
3
src/Grabber/Grabber.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export abstract class Grabber {
|
||||
abstract grab(): void;
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { StateGrabber } from './StateGrabber';
|
||||
import { ResourceGrabber } from './ResourceGrabber';
|
||||
import { Grabber } from './Grabber';
|
||||
import { VillageResourceGrabber } from './VillageResourceGrabber';
|
||||
import { VillageOverviewPageGrabber } from './VillageOverviewPageGrabber';
|
||||
import { HeroPageGrabber } from './HeroPageGrabber';
|
||||
|
||||
export class StateGrabberManager {
|
||||
private readonly grabbers: Array<StateGrabber> = [];
|
||||
export class GrabberManager {
|
||||
private readonly grabbers: Array<Grabber> = [];
|
||||
|
||||
constructor() {
|
||||
this.grabbers = [];
|
||||
this.grabbers.push(new ResourceGrabber());
|
||||
this.grabbers.push(new VillageResourceGrabber());
|
||||
this.grabbers.push(new VillageOverviewPageGrabber());
|
||||
this.grabbers.push(new HeroPageGrabber());
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { StateGrabber } from './StateGrabber';
|
||||
import { Grabber } from './Grabber';
|
||||
import {
|
||||
grabActiveVillageId,
|
||||
grabBuildingQueueInfo,
|
||||
@ -12,7 +12,7 @@ import { BuildingQueueInfo } from '../Game';
|
||||
import { HeroState } from '../State/HeroState';
|
||||
import { grabHeroAttributes, grabHeroVillage } from '../Page/HeroPage';
|
||||
|
||||
export class HeroPageGrabber extends StateGrabber {
|
||||
export class HeroPageGrabber extends Grabber {
|
||||
grab(): void {
|
||||
const p = parseLocation();
|
||||
if (p.pathname !== '/hero.php') {
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { StateGrabber } from './StateGrabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { grabResources, grabResourceStorage } from '../Page/ResourcesBlock';
|
||||
import { VillageState } from '../State/VillageState';
|
||||
|
||||
export class ResourceGrabber extends StateGrabber {
|
||||
grab(): void {
|
||||
const villageId = grabActiveVillageId();
|
||||
const state = new VillageState(villageId);
|
||||
state.storeResources(grabResources());
|
||||
state.storeResourceStorage(grabResourceStorage());
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export abstract class StateGrabber {
|
||||
abstract grab(): void;
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import { StateGrabber } from './StateGrabber';
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock';
|
||||
import { VillageState } from '../State/VillageState';
|
||||
import { parseLocation } from '../utils';
|
||||
import { GrabError } from '../Errors';
|
||||
import { BuildingQueueInfo } from '../Game';
|
||||
|
||||
export class VillageOverviewPageGrabber extends StateGrabber {
|
||||
export class VillageOverviewPageGrabber extends Grabber {
|
||||
grab(): void {
|
||||
const p = parseLocation();
|
||||
if (p.pathname !== '/dorf1.php') {
|
||||
|
13
src/Grabber/VillageResourceGrabber.ts
Normal file
13
src/Grabber/VillageResourceGrabber.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock';
|
||||
import { VillageState } from '../State/VillageState';
|
||||
|
||||
export class VillageResourceGrabber extends Grabber {
|
||||
grab(): void {
|
||||
const villageId = grabActiveVillageId();
|
||||
const state = new VillageState(villageId);
|
||||
state.storeResources(grabVillageResources());
|
||||
state.storeResourceStorage(grabVillageResourceStorage());
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { GrabError } from '../Errors';
|
||||
import { elClassId, getNumber, trimPrefix, uniqId } from '../utils';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { Coordinates } from '../Core/Village';
|
||||
|
||||
export function clickBuildButton(typeId: number) {
|
||||
const section = jQuery(`#contract_building${typeId}`);
|
||||
@ -102,3 +103,40 @@ export function createTrainTroopButtons(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function createSendResourcesButton(onClickHandler: (resources: Resources, crd: Coordinates) => void) {
|
||||
const id = uniqId();
|
||||
jQuery('#button').before(`<div style="padding: 8px"><a id="${id}" href="#">Отправить</a></div>`);
|
||||
jQuery(`#${id}`).on('click', evt => {
|
||||
evt.preventDefault();
|
||||
const sendSelect = jQuery('#send_select');
|
||||
const resources = new Resources(
|
||||
getNumber(sendSelect.find('#r1').val()),
|
||||
getNumber(sendSelect.find('#r2').val()),
|
||||
getNumber(sendSelect.find('#r3').val()),
|
||||
getNumber(sendSelect.find('#r4').val())
|
||||
);
|
||||
const crd = new Coordinates(getNumber(jQuery('#xCoordInput').val()), getNumber(jQuery('#yCoordInput').val()));
|
||||
onClickHandler(resources, crd);
|
||||
});
|
||||
}
|
||||
|
||||
export function grabMerchantsInfo() {
|
||||
const available = getNumber(jQuery('.merchantsAvailable').text());
|
||||
const carry = getNumber(jQuery('.carry b').text());
|
||||
return { available, carry };
|
||||
}
|
||||
|
||||
export function fillSendResourcesForm(resources: Resources, crd: Coordinates) {
|
||||
const sendSelect = jQuery('#send_select');
|
||||
sendSelect.find('#r1').val(resources.lumber);
|
||||
sendSelect.find('#r2').val(resources.clay);
|
||||
sendSelect.find('#r3').val(resources.iron);
|
||||
sendSelect.find('#r4').val(resources.crop);
|
||||
jQuery('#xCoordInput').val(crd.x);
|
||||
jQuery('#yCoordInput').val(crd.y);
|
||||
}
|
||||
|
||||
export function clickSendButton() {
|
||||
jQuery('#enabledButton').trigger('click');
|
||||
}
|
||||
|
@ -4,10 +4,18 @@ import { Scheduler } from '../Scheduler';
|
||||
import { TrainTroopTask } from '../Task/TrainTroopTask';
|
||||
import { grabActiveVillageId } from './VillageBlock';
|
||||
import { ConsoleLogger } from '../Logger';
|
||||
import { createBuildButton, createTrainTroopButtons, createUpgradeButton } from './BuildingPage';
|
||||
import {
|
||||
createBuildButton,
|
||||
createSendResourcesButton,
|
||||
createTrainTroopButtons,
|
||||
createUpgradeButton,
|
||||
} from './BuildingPage';
|
||||
import { BuildBuildingTask } from '../Task/BuildBuildingTask';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { Coordinates } from '../Core/Village';
|
||||
import { SendResourcesTask } from '../Task/SendResourcesTask';
|
||||
|
||||
const MARKET_ID = 17;
|
||||
const QUARTERS_ID = 19;
|
||||
const HORSE_STABLE_ID = 20;
|
||||
const EMBASSY_ID = 25;
|
||||
@ -16,6 +24,7 @@ export interface BuildingPageAttributes {
|
||||
buildId: number;
|
||||
buildTypeId: number;
|
||||
categoryId: number;
|
||||
sheetId: number;
|
||||
tabId: number;
|
||||
}
|
||||
|
||||
@ -31,8 +40,8 @@ export class BuildingPageController {
|
||||
}
|
||||
|
||||
run() {
|
||||
const { buildTypeId } = this.attributes;
|
||||
this.logger.log('BUILD PAGE DETECTED', 'ID', this.attributes.buildId, 'TYPE', buildTypeId);
|
||||
const { buildTypeId, sheetId, tabId } = this.attributes;
|
||||
this.logger.log('BUILD PAGE DETECTED', 'ID', this.attributes.buildId, this.attributes);
|
||||
|
||||
if (buildTypeId) {
|
||||
createUpgradeButton(res => this.onScheduleUpgradeBuilding(res));
|
||||
@ -48,9 +57,13 @@ export class BuildingPageController {
|
||||
createTrainTroopButtons((troopId, res, count) => this.onScheduleTrainTroopers(troopId, res, count));
|
||||
}
|
||||
|
||||
if (buildTypeId === EMBASSY_ID && this.attributes.tabId === 1) {
|
||||
if (buildTypeId === EMBASSY_ID && sheetId === 1) {
|
||||
createTrainTroopButtons((troopId, res, count) => this.onScheduleTrainTroopers(troopId, res, count));
|
||||
}
|
||||
|
||||
if (buildTypeId === MARKET_ID && tabId === 5) {
|
||||
createSendResourcesButton((res, crd) => this.onSendResources(res, crd));
|
||||
}
|
||||
}
|
||||
|
||||
private onScheduleBuildBuilding(buildTypeId: number, resources: Resources) {
|
||||
@ -69,13 +82,12 @@ export class BuildingPageController {
|
||||
}
|
||||
|
||||
private onScheduleTrainTroopers(troopId: number, resources: Resources, count: number) {
|
||||
const tabId = this.attributes.tabId;
|
||||
for (let chunk of split(count)) {
|
||||
const args = {
|
||||
villageId: grabActiveVillageId(),
|
||||
buildId: this.attributes.buildId,
|
||||
buildTypeId: this.attributes.buildTypeId,
|
||||
tabId,
|
||||
sheetId: this.attributes.sheetId,
|
||||
troopId,
|
||||
resources: resources.scale(chunk),
|
||||
trainCount: chunk,
|
||||
@ -85,4 +97,17 @@ export class BuildingPageController {
|
||||
}
|
||||
notify(`Training ${count} troopers scheduled`);
|
||||
}
|
||||
|
||||
private onSendResources(resources: Resources, coordinates: Coordinates) {
|
||||
const villageId = grabActiveVillageId();
|
||||
this.scheduler.scheduleTask(SendResourcesTask.name, {
|
||||
villageId: villageId,
|
||||
buildTypeId: this.attributes.buildTypeId,
|
||||
buildId: this.attributes.buildId,
|
||||
tabId: this.attributes.tabId,
|
||||
resources,
|
||||
coordinates,
|
||||
});
|
||||
notify(`Send resources ${JSON.stringify(resources)} from ${villageId} to ${JSON.stringify(coordinates)}`);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Resources } from '../Core/Resources';
|
||||
import { ResourceType } from '../Core/ResourceType';
|
||||
import { ResourceStorage } from '../Core/ResourceStorage';
|
||||
|
||||
export function grabResources(): Resources {
|
||||
export function grabVillageResources(): Resources {
|
||||
const lumber = grabResource(ResourceType.Lumber);
|
||||
const clay = grabResource(ResourceType.Clay);
|
||||
const iron = grabResource(ResourceType.Iron);
|
||||
@ -13,7 +13,7 @@ export function grabResources(): Resources {
|
||||
return new Resources(lumber, clay, iron, crop);
|
||||
}
|
||||
|
||||
export function grabResourceStorage() {
|
||||
export function grabVillageResourceStorage() {
|
||||
const warehouse = grabCapacity('warehouse');
|
||||
const granary = grabCapacity('granary');
|
||||
return new ResourceStorage(warehouse, granary);
|
||||
|
31
src/Task/SendResourcesTask.ts
Normal file
31
src/Task/SendResourcesTask.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Args, Command } from '../Command';
|
||||
import { Task } from '../Queue/TaskQueue';
|
||||
import { TaskController, registerTask } from './TaskController';
|
||||
import { GoToPageAction } from '../Action/GoToPageAction';
|
||||
import { CompleteTaskAction } from '../Action/CompleteTaskAction';
|
||||
import { path } from '../utils';
|
||||
import { SendResourcesAction } from '../Action/SendResourcesAction';
|
||||
import { ClickButtonAction } from '../Action/ClickButtonAction';
|
||||
|
||||
@registerTask
|
||||
export class SendResourcesTask extends TaskController {
|
||||
async run(task: Task) {
|
||||
const args: Args = { ...task.args, taskId: task.id };
|
||||
|
||||
const pathArgs = {
|
||||
newdid: args.villageId,
|
||||
gid: args.buildTypeId || undefined,
|
||||
id: args.buildId || undefined,
|
||||
t: args.tabId,
|
||||
};
|
||||
|
||||
const pagePath = path('/build.php', pathArgs);
|
||||
|
||||
this.scheduler.scheduleActions([
|
||||
new Command(GoToPageAction.name, { ...args, path: pagePath }),
|
||||
new Command(SendResourcesAction.name, args),
|
||||
new Command(ClickButtonAction.name, { ...args, selector: '#enabledButton.green.sendRessources' }),
|
||||
new Command(CompleteTaskAction.name, args),
|
||||
]);
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ export class TrainTroopTask extends TaskController {
|
||||
newdid: args.villageId,
|
||||
gid: args.buildTypeId || undefined,
|
||||
id: args.buildId || undefined,
|
||||
s: args.tabId,
|
||||
s: args.sheetId,
|
||||
};
|
||||
|
||||
const pagePath = path('/build.php', pathArgs);
|
||||
|
59
tests/Core/GatheringTimingsTest.ts
Normal file
59
tests/Core/GatheringTimingsTest.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { it, describe } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { Resources } from '../../src/Core/Resources';
|
||||
import { ResourceType } from '../../src/Core/ResourceType';
|
||||
import { ResourceStorage } from '../../src/Core/ResourceStorage';
|
||||
import { calcGatheringTimings, GatheringTimings } from '../../src/Core/GatheringTimings';
|
||||
|
||||
describe('Gathering timings', function() {
|
||||
it('Can calc common from numbers', function() {
|
||||
const timings = new GatheringTimings(10, 20, 15, 5);
|
||||
expect(20).to.equals(timings.common);
|
||||
});
|
||||
|
||||
it('Can calc common with never', function() {
|
||||
const timings = new GatheringTimings(10, 20, 'never', 5);
|
||||
expect('never').to.equals(timings.common);
|
||||
expect(true).to.equals(timings.never);
|
||||
});
|
||||
|
||||
it('Can calc common with all never', function() {
|
||||
const timings = new GatheringTimings('never', 'never', 'never', 'never');
|
||||
expect('never').to.equals(timings.common);
|
||||
expect(true).to.equals(timings.never);
|
||||
});
|
||||
|
||||
it('Can calc timings', function() {
|
||||
const resources = new Resources(10, 10, 10, 10);
|
||||
const desired = new Resources(60, 60, 60, 60);
|
||||
const speed = new Resources(5, 5, 5, 5);
|
||||
const timings = calcGatheringTimings(resources, desired, speed);
|
||||
expect(10).to.equals(timings.common);
|
||||
});
|
||||
|
||||
it('Can calc timings with different speed', function() {
|
||||
const resources = new Resources(10, 10, 10, 10);
|
||||
const desired = new Resources(60, 60, 60, 60);
|
||||
const speed = new Resources(5, 10, 25, 5);
|
||||
const timings = calcGatheringTimings(resources, desired, speed);
|
||||
expect(10).to.equals(timings.lumber);
|
||||
expect(5).to.equals(timings.clay);
|
||||
expect(2).to.equals(timings.iron);
|
||||
expect(10).to.equals(timings.crop);
|
||||
expect(10).to.equals(timings.common);
|
||||
});
|
||||
|
||||
it('Can calc timings with negative speed', function() {
|
||||
const resources = new Resources(10, 10, 10, 10);
|
||||
const desired = new Resources(60, 60, 60, 60);
|
||||
const speed = new Resources(5, 10, 25, -5);
|
||||
const timings = calcGatheringTimings(resources, desired, speed);
|
||||
expect(10).to.equals(timings.lumber);
|
||||
expect(5).to.equals(timings.clay);
|
||||
expect(2).to.equals(timings.iron);
|
||||
expect('never').to.equals(timings.crop);
|
||||
expect('never').to.equals(timings.common);
|
||||
expect(true).to.equals(timings.never);
|
||||
});
|
||||
});
|
@ -8,7 +8,7 @@
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"target": "es2018",
|
||||
"types": ["node", "url-parse", "jquery", "mocha", "chai"]
|
||||
"types": ["node", "url-parse", "jquery", "mocha", "chai", "debounce"]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
|
Loading…
Reference in New Issue
Block a user