First attempt to resource sending

This commit is contained in:
Anton Vakhrushev 2020-04-24 10:45:05 +03:00
parent 542bc353b0
commit fb4ac6424c
25 changed files with 437 additions and 74 deletions

12
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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);

View 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();
}
}

View File

@ -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;
}

View File

@ -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.refreshVillages(), 5000);
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;
}
}

View 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)
);
}

View File

@ -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)
);
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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
View File

@ -0,0 +1,3 @@
export abstract class Grabber {
abstract grab(): void;
}

View File

@ -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());
}

View File

@ -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') {

View File

@ -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());
}
}

View File

@ -1,3 +0,0 @@
export abstract class StateGrabber {
abstract grab(): void;
}

View File

@ -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') {

View 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());
}
}

View File

@ -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');
}

View File

@ -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)}`);
}
}

View File

@ -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);

View 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),
]);
}
}

View File

@ -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);

View 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);
});
});

View File

@ -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/**/*"