Add smart resource transfer
This commit is contained in:
		
							
								
								
									
										46
									
								
								src/Action/FindSendResourcesPath.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/Action/FindSendResourcesPath.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import { ActionController, registerAction } from './ActionController';
 | 
				
			||||||
 | 
					import { FailTaskError, taskError, TryLaterError } from '../Errors';
 | 
				
			||||||
 | 
					import { Resources } from '../Core/Resources';
 | 
				
			||||||
 | 
					import { Coordinates } from '../Core/Village';
 | 
				
			||||||
 | 
					import { aroundMinutes, timestamp } from '../utils';
 | 
				
			||||||
 | 
					import { Args } from '../Queue/Args';
 | 
				
			||||||
 | 
					import { Task } from '../Queue/TaskProvider';
 | 
				
			||||||
 | 
					import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage';
 | 
				
			||||||
 | 
					import { VillageState } from '../VillageState';
 | 
				
			||||||
 | 
					import { MerchantsInfo } from '../Core/Market';
 | 
				
			||||||
 | 
					import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles';
 | 
				
			||||||
 | 
					import { ResourceTransferCalculator, ResourceTransferReport } from '../ResourceTransfer';
 | 
				
			||||||
 | 
					import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
 | 
				
			||||||
 | 
					import { path } from '../Helpers/Path';
 | 
				
			||||||
 | 
					import { MARKET_ID } from '../Core/Buildings';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@registerAction
 | 
				
			||||||
 | 
					export class FindSendResourcesPath extends ActionController {
 | 
				
			||||||
 | 
					    async run(args: Args, task: Task): Promise<any> {
 | 
				
			||||||
 | 
					        const reports: Array<ResourceTransferReport> = [];
 | 
				
			||||||
 | 
					        const calculator = new ResourceTransferCalculator(this.villageFactory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const villages = this.villageFactory.getAllVillages();
 | 
				
			||||||
 | 
					        for (let fromVillage of villages) {
 | 
				
			||||||
 | 
					            for (let toVillage of villages) {
 | 
				
			||||||
 | 
					                reports.push(calculator.calc(fromVillage.id, toVillage.id));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reports.sort((r1, r2) => r2.score - r1.score);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bestReport = reports.shift();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!bestReport) {
 | 
				
			||||||
 | 
					            throw new FailTaskError('No best report for transfer resources');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log('Best report', bestReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const storage = new ResourceTransferStorage();
 | 
				
			||||||
 | 
					        storage.storeReport(bestReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const marketPath = path('/build.php', { newdid: bestReport.fromVillageId, gid: MARKET_ID, t: 5 });
 | 
				
			||||||
 | 
					        window.location.assign(marketPath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,123 +1,33 @@
 | 
				
			|||||||
import { ActionController, registerAction } from './ActionController';
 | 
					import { ActionController, registerAction } from './ActionController';
 | 
				
			||||||
import { taskError, TryLaterError } from '../Errors';
 | 
					 | 
				
			||||||
import { Resources } from '../Core/Resources';
 | 
					 | 
				
			||||||
import { Coordinates } from '../Core/Village';
 | 
					 | 
				
			||||||
import { aroundMinutes, timestamp } from '../utils';
 | 
					 | 
				
			||||||
import { Args } from '../Queue/Args';
 | 
					import { Args } from '../Queue/Args';
 | 
				
			||||||
import { Task } from '../Queue/TaskProvider';
 | 
					import { Task } from '../Queue/TaskProvider';
 | 
				
			||||||
import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
 | 
					import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage';
 | 
				
			||||||
import { VillageState } from '../VillageState';
 | 
					import { ResourceTransferCalculator } from '../ResourceTransfer';
 | 
				
			||||||
 | 
					import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
 | 
				
			||||||
 | 
					import { Resources } from '../Core/Resources';
 | 
				
			||||||
 | 
					import { AbortTaskError } from '../Errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@registerAction
 | 
					@registerAction
 | 
				
			||||||
export class SendResourcesAction extends ActionController {
 | 
					export class SendResourcesAction extends ActionController {
 | 
				
			||||||
    async run(args: Args, task: Task): Promise<any> {
 | 
					    async run(args: Args, task: Task): Promise<any> {
 | 
				
			||||||
        this.ensureSameVillage(args, task);
 | 
					        const storage = new ResourceTransferStorage();
 | 
				
			||||||
 | 
					        const savedReport = storage.getReport();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const senderVillageId = args.villageId || taskError('No source village id');
 | 
					        const fromVillage = this.villageFactory.getVillage(savedReport.fromVillageId);
 | 
				
			||||||
        const targetVillageId = args.targetVillageId || taskError('No target village id');
 | 
					        const toVillage = this.villageFactory.getVillage(savedReport.toVillageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const coordinates = Coordinates.fromObject(args.coordinates || taskError('No coordinates'));
 | 
					        const coordinates = toVillage.crd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const senderVillage = this.villageFactory.createState(senderVillageId);
 | 
					        const calculator = new ResourceTransferCalculator(this.villageFactory);
 | 
				
			||||||
        const recipientVillage = this.villageFactory.createState(targetVillageId);
 | 
					        const report = calculator.calc(fromVillage.id, toVillage.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const readyToTransfer = this.getResourcesForTransfer(senderVillage, recipientVillage);
 | 
					        console.log('To transfer report', report);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log('To transfer res', readyToTransfer);
 | 
					        if (Resources.fromObject(report.resources).empty()) {
 | 
				
			||||||
 | 
					            throw new AbortTaskError('No resources to transfer');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Schedule recurrent task
 | 
					        fillSendResourcesForm(report.resources, coordinates);
 | 
				
			||||||
        const timeout = senderVillage.settings.sendResourcesTimeout;
 | 
					 | 
				
			||||||
        this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(timeout));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fillSendResourcesForm(readyToTransfer, coordinates);
 | 
					 | 
				
			||||||
        clickSendButton();
 | 
					        clickSendButton();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private getMerchantsCapacity(timeout: number): number {
 | 
					 | 
				
			||||||
        const merchants = grabMerchantsInfo();
 | 
					 | 
				
			||||||
        const capacity = merchants.available * merchants.carry;
 | 
					 | 
				
			||||||
        if (!capacity) {
 | 
					 | 
				
			||||||
            throw new TryLaterError(aroundMinutes(timeout), 'No merchants');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return capacity;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private getSenderAvailableResources(senderState: VillageState): Resources {
 | 
					 | 
				
			||||||
        const balance = senderState.required.balance;
 | 
					 | 
				
			||||||
        const free = balance.max(Resources.zero());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.table([
 | 
					 | 
				
			||||||
            { name: 'Sender balance', ...balance },
 | 
					 | 
				
			||||||
            { name: 'Sender free', ...free },
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const amount = free.amount();
 | 
					 | 
				
			||||||
        const threshold = senderState.settings.sendResourcesThreshold;
 | 
					 | 
				
			||||||
        const timeout = senderState.settings.sendResourcesTimeout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (amount < threshold) {
 | 
					 | 
				
			||||||
            throw new TryLaterError(
 | 
					 | 
				
			||||||
                aroundMinutes(timeout),
 | 
					 | 
				
			||||||
                `No free resources (amount ${amount} < threshold ${threshold})`
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return free;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private getRecipientRequirements(recipientState: VillageState, timeout: number): Resources {
 | 
					 | 
				
			||||||
        const maxPossibleToStore = recipientState.storage.capacity.sub(recipientState.performance);
 | 
					 | 
				
			||||||
        const currentResources = recipientState.resources;
 | 
					 | 
				
			||||||
        const incomingResources = recipientState.incomingResources;
 | 
					 | 
				
			||||||
        const requirementResources = recipientState.required.resources;
 | 
					 | 
				
			||||||
        const missingResources = requirementResources
 | 
					 | 
				
			||||||
            .min(maxPossibleToStore)
 | 
					 | 
				
			||||||
            .sub(incomingResources)
 | 
					 | 
				
			||||||
            .sub(currentResources)
 | 
					 | 
				
			||||||
            .max(Resources.zero());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.table([
 | 
					 | 
				
			||||||
            { name: 'Recipient max possible', ...maxPossibleToStore },
 | 
					 | 
				
			||||||
            { name: 'Recipient resources', ...currentResources },
 | 
					 | 
				
			||||||
            { name: 'Recipient incoming', ...incomingResources },
 | 
					 | 
				
			||||||
            { name: 'Recipient requirements', ...requirementResources },
 | 
					 | 
				
			||||||
            { name: 'Recipient missing', ...missingResources },
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (missingResources.empty()) {
 | 
					 | 
				
			||||||
            throw new TryLaterError(aroundMinutes(timeout), 'No missing resources');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return missingResources;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private getResourcesForTransfer(senderState: VillageState, recipientState: VillageState): Resources {
 | 
					 | 
				
			||||||
        const multiplier = senderState.settings.sendResourcesMultiplier;
 | 
					 | 
				
			||||||
        const timeout = senderState.settings.sendResourcesTimeout;
 | 
					 | 
				
			||||||
        const senderReadyResources = this.getSenderAvailableResources(senderState).downTo(multiplier);
 | 
					 | 
				
			||||||
        const recipientNeedResources = this.getRecipientRequirements(recipientState, timeout).upTo(multiplier);
 | 
					 | 
				
			||||||
        const contractResources = senderReadyResources.min(recipientNeedResources);
 | 
					 | 
				
			||||||
        const merchantsCapacity = this.getMerchantsCapacity(timeout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let readyToTransfer = contractResources;
 | 
					 | 
				
			||||||
        if (contractResources.amount() > merchantsCapacity) {
 | 
					 | 
				
			||||||
            const merchantScale = merchantsCapacity / contractResources.amount();
 | 
					 | 
				
			||||||
            readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (readyToTransfer.empty()) {
 | 
					 | 
				
			||||||
            throw new TryLaterError(aroundMinutes(timeout), 'Not enough resources to transfer');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log('Merchants capacity', merchantsCapacity);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.table([
 | 
					 | 
				
			||||||
            { name: 'Sender', ...senderReadyResources },
 | 
					 | 
				
			||||||
            { name: 'Recipient', ...recipientNeedResources },
 | 
					 | 
				
			||||||
            { name: 'Prepared', ...contractResources },
 | 
					 | 
				
			||||||
            { name: 'Ready to transfer', ...readyToTransfer },
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return readyToTransfer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,3 +8,8 @@ export class IncomingMerchant {
 | 
				
			|||||||
        this.ts = ts;
 | 
					        this.ts = ts;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MerchantsInfo {
 | 
				
			||||||
 | 
					    available: number;
 | 
				
			||||||
 | 
					    carry: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,6 @@ import { ExecutionStorage } from './Storage/ExecutionStorage';
 | 
				
			|||||||
import { Action } from './Queue/ActionQueue';
 | 
					import { Action } from './Queue/ActionQueue';
 | 
				
			||||||
import { Task } from './Queue/TaskProvider';
 | 
					import { Task } from './Queue/TaskProvider';
 | 
				
			||||||
import { createTaskHandler } from './Task/TaskMap';
 | 
					import { createTaskHandler } from './Task/TaskMap';
 | 
				
			||||||
import { VillageStateFactory } from './VillageState';
 | 
					 | 
				
			||||||
import { VillageFactory } from './VillageFactory';
 | 
					import { VillageFactory } from './VillageFactory';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ExecutionSettings {
 | 
					export interface ExecutionSettings {
 | 
				
			||||||
@@ -79,8 +78,6 @@ export class Executor {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private async doTaskProcessingStep() {
 | 
					    private async doTaskProcessingStep() {
 | 
				
			||||||
        this.runGrabbers();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const currentTs = timestamp();
 | 
					        const currentTs = timestamp();
 | 
				
			||||||
        const { task, action } = this.scheduler.nextTask(currentTs);
 | 
					        const { task, action } = this.scheduler.nextTask(currentTs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,6 +89,8 @@ export class Executor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action);
 | 
					        this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.runGrabbers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            if (task && action) {
 | 
					            if (task && action) {
 | 
				
			||||||
                return await this.processActionCommand(action, task);
 | 
					                return await this.processActionCommand(action, task);
 | 
				
			||||||
@@ -117,7 +116,7 @@ export class Executor {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private async processTaskCommand(task: Task) {
 | 
					    private async processTaskCommand(task: Task) {
 | 
				
			||||||
        const taskHandler = createTaskHandler(task.name, this.scheduler);
 | 
					        const taskHandler = createTaskHandler(task.name, this.scheduler, this.villageFactory);
 | 
				
			||||||
        this.logger.info('Process task', task.name, task, taskHandler);
 | 
					        this.logger.info('Process task', task.name, task, taskHandler);
 | 
				
			||||||
        if (taskHandler) {
 | 
					        if (taskHandler) {
 | 
				
			||||||
            await taskHandler.run(task);
 | 
					            await taskHandler.run(task);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Grabber } from './Grabber';
 | 
					import { Grabber } from './Grabber';
 | 
				
			||||||
import { isMarketSendResourcesPage } from '../Page/PageDetectors';
 | 
					import { isMarketSendResourcesPage } from '../Page/PageDetectors';
 | 
				
			||||||
import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage';
 | 
					import { grabIncomingMerchants, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class MarketPageGrabber extends Grabber {
 | 
					export class MarketPageGrabber extends Grabber {
 | 
				
			||||||
    grab(): void {
 | 
					    grab(): void {
 | 
				
			||||||
@@ -9,5 +9,6 @@ export class MarketPageGrabber extends Grabber {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.storage.storeIncomingMerchants(grabIncomingMerchants());
 | 
					        this.storage.storeIncomingMerchants(grabIncomingMerchants());
 | 
				
			||||||
 | 
					        this.storage.storeMerchantsInfo(grabMerchantsInfo());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { getNumber, uniqId } from '../../utils';
 | 
					import { getNumber, uniqId } from '../../utils';
 | 
				
			||||||
import { Resources } from '../../Core/Resources';
 | 
					import { Resources, ResourcesInterface } from '../../Core/Resources';
 | 
				
			||||||
import { Coordinates } from '../../Core/Village';
 | 
					import { Coordinates } from '../../Core/Village';
 | 
				
			||||||
import { IncomingMerchant } from '../../Core/Market';
 | 
					import { IncomingMerchant, MerchantsInfo } from '../../Core/Market';
 | 
				
			||||||
import { grabResourcesFromList } from './BuildingPage';
 | 
					import { grabResourcesFromList } from './BuildingPage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SendResourcesClickHandler {
 | 
					interface SendResourcesClickHandler {
 | 
				
			||||||
@@ -31,13 +31,13 @@ export function createSendResourcesButton(onClickHandler: SendResourcesClickHand
 | 
				
			|||||||
    jQuery(`#${id}`).on('click', createHandler());
 | 
					    jQuery(`#${id}`).on('click', createHandler());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function grabMerchantsInfo() {
 | 
					export function grabMerchantsInfo(): MerchantsInfo {
 | 
				
			||||||
    const available = getNumber(jQuery('.merchantsAvailable').text());
 | 
					    const available = getNumber(jQuery('.merchantsAvailable').text());
 | 
				
			||||||
    const carry = getNumber(jQuery('.carry b').text());
 | 
					    const carry = getNumber(jQuery('.carry b').text());
 | 
				
			||||||
    return { available, carry };
 | 
					    return { available, carry };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function fillSendResourcesForm(resources: Resources, crd: Coordinates) {
 | 
					export function fillSendResourcesForm(resources: ResourcesInterface, crd: Coordinates) {
 | 
				
			||||||
    const sendSelect = jQuery('#send_select');
 | 
					    const sendSelect = jQuery('#send_select');
 | 
				
			||||||
    sendSelect.find('#r1').val(resources.lumber);
 | 
					    sendSelect.find('#r1').val(resources.lumber);
 | 
				
			||||||
    sendSelect.find('#r2').val(resources.clay);
 | 
					    sendSelect.find('#r2').val(resources.clay);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/ResourceTransfer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/ResourceTransfer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { VillageFactory } from './VillageFactory';
 | 
				
			||||||
 | 
					import { ResourcesInterface } from './Core/Resources';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ResourceTransferReport {
 | 
				
			||||||
 | 
					    fromVillageId: number;
 | 
				
			||||||
 | 
					    toVillageId: number;
 | 
				
			||||||
 | 
					    resources: ResourcesInterface;
 | 
				
			||||||
 | 
					    score: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ResourceTransferCalculator {
 | 
				
			||||||
 | 
					    private factory: VillageFactory;
 | 
				
			||||||
 | 
					    constructor(factory: VillageFactory) {
 | 
				
			||||||
 | 
					        this.factory = factory;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    calc(fromVillageId: number, toVillageId: number): ResourceTransferReport {
 | 
				
			||||||
 | 
					        const senderState = this.factory.createState(fromVillageId);
 | 
				
			||||||
 | 
					        const senderController = this.factory.createController(fromVillageId);
 | 
				
			||||||
 | 
					        const senderStorage = this.factory.createStorage(fromVillageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const recipientController = this.factory.createController(toVillageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const multiplier = senderState.settings.sendResourcesMultiplier;
 | 
				
			||||||
 | 
					        const senderReadyResources = senderController.getAvailableForSendResources().downTo(multiplier);
 | 
				
			||||||
 | 
					        const recipientNeedResources = recipientController.getRequiredResources().upTo(multiplier);
 | 
				
			||||||
 | 
					        const contractResources = senderReadyResources.min(recipientNeedResources);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const merchantsInfo = senderStorage.getMerchantsInfo();
 | 
				
			||||||
 | 
					        const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let readyToTransfer = contractResources;
 | 
				
			||||||
 | 
					        if (contractResources.amount() && contractResources.amount() > merchantsCapacity) {
 | 
				
			||||||
 | 
					            const merchantScale = merchantsCapacity / contractResources.amount();
 | 
				
			||||||
 | 
					            readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log('Merchants capacity', merchantsCapacity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.table([
 | 
				
			||||||
 | 
					            { name: 'Sender', ...senderReadyResources },
 | 
				
			||||||
 | 
					            { name: 'Recipient', ...recipientNeedResources },
 | 
				
			||||||
 | 
					            { name: 'Prepared', ...contractResources },
 | 
				
			||||||
 | 
					            { name: 'Ready to transfer', ...readyToTransfer },
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return { fromVillageId, toVillageId, resources: readyToTransfer, score: readyToTransfer.amount() };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { timestamp } from './utils';
 | 
					import { around, timestamp } from './utils';
 | 
				
			||||||
import { TaskQueue } from './Queue/TaskQueue';
 | 
					import { TaskQueue } from './Queue/TaskQueue';
 | 
				
			||||||
import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
 | 
					import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
 | 
				
			||||||
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
 | 
					import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
 | 
				
			||||||
@@ -22,11 +22,11 @@ export interface NextExecution {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Scheduler {
 | 
					export class Scheduler {
 | 
				
			||||||
    private taskQueue: TaskQueue;
 | 
					    private readonly taskQueue: TaskQueue;
 | 
				
			||||||
    private actionQueue: ActionQueue;
 | 
					    private readonly actionQueue: ActionQueue;
 | 
				
			||||||
    private villageRepository: VillageRepositoryInterface;
 | 
					    private readonly villageRepository: VillageRepositoryInterface;
 | 
				
			||||||
    private villageControllerFactory: VillageFactory;
 | 
					    private readonly villageControllerFactory: VillageFactory;
 | 
				
			||||||
    private logger: Logger;
 | 
					    private readonly logger: Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        taskQueue: TaskQueue,
 | 
					        taskQueue: TaskQueue,
 | 
				
			||||||
@@ -51,14 +51,16 @@ export class Scheduler {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.createUniqTaskTimer(5 * 60, GrabVillageState.name);
 | 
					        this.createUniqTaskTimer(5 * 60, GrabVillageState.name);
 | 
				
			||||||
 | 
					        this.createUniqTaskTimer(10 * 60, SendResourcesTask.name);
 | 
				
			||||||
        this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name);
 | 
					        this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name);
 | 
				
			||||||
        this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name);
 | 
					        this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name);
 | 
				
			||||||
        this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name);
 | 
					        // this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private createUniqTaskTimer(seconds: number, name: string, args: Args = {}) {
 | 
					    private createUniqTaskTimer(seconds: number, name: string, args: Args = {}) {
 | 
				
			||||||
        this.scheduleUniqTask(name, args, timestamp() + seconds - 10);
 | 
					        this.scheduleUniqTask(name, args, timestamp() + seconds - 10);
 | 
				
			||||||
        setInterval(() => this.scheduleUniqTask(name, args, timestamp()), seconds * 1000);
 | 
					        const intervalTime = around(seconds, 0.2) * 1000;
 | 
				
			||||||
 | 
					        setInterval(() => this.scheduleUniqTask(name, args, timestamp()), intervalTime);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getTaskItems(): ImmutableTaskList {
 | 
					    getTaskItems(): ImmutableTaskList {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,20 +13,20 @@ export interface StatisticsStorageInterface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Statistics {
 | 
					export class Statistics {
 | 
				
			||||||
    private state: StatisticsStorageInterface;
 | 
					    private readonly storage: StatisticsStorageInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static readonly keepRecords = KEEP_RECORD_COUNT;
 | 
					    static readonly keepRecords = KEEP_RECORD_COUNT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(storage: StatisticsStorageInterface) {
 | 
					    constructor(storage: StatisticsStorageInterface) {
 | 
				
			||||||
        this.state = storage;
 | 
					        this.storage = storage;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    incrementAction(ts: number): void {
 | 
					    incrementAction(ts: number): void {
 | 
				
			||||||
        const stat = this.state.getActionStatistics();
 | 
					        const stat = this.storage.getActionStatistics();
 | 
				
			||||||
        const key = dateFormat(ts * 1000, KEY_FORMAT);
 | 
					        const key = dateFormat(ts * 1000, KEY_FORMAT);
 | 
				
			||||||
        stat[key] = (stat[key] || 0) + 1;
 | 
					        stat[key] = (stat[key] || 0) + 1;
 | 
				
			||||||
        this.trimStatistics(stat);
 | 
					        this.trimStatistics(stat);
 | 
				
			||||||
        this.state.setActionStatistics(stat);
 | 
					        this.storage.setActionStatistics(stat);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private trimStatistics(stat: ActionStatistics) {
 | 
					    private trimStatistics(stat: ActionStatistics) {
 | 
				
			||||||
@@ -49,6 +49,6 @@ export class Statistics {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getActionStatistics(): ActionStatistics {
 | 
					    getActionStatistics(): ActionStatistics {
 | 
				
			||||||
        return this.state.getActionStatistics();
 | 
					        return this.storage.getActionStatistics();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/Storage/ResourceTransferStorage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Storage/ResourceTransferStorage.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import { DataStorage } from '../DataStorage';
 | 
				
			||||||
 | 
					import { ResourceTransferReport } from '../ResourceTransfer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NAMESPACE = 'resource_transfer.v1';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const REPORT_KEY = 'report';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ResourceTransferStorage {
 | 
				
			||||||
 | 
					    private storage: DataStorage;
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        this.storage = new DataStorage(NAMESPACE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    storeReport(report: ResourceTransferReport): void {
 | 
				
			||||||
 | 
					        this.storage.set(REPORT_KEY, report);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getReport(): ResourceTransferReport {
 | 
				
			||||||
 | 
					        return this.storage.getTyped<ResourceTransferReport>(REPORT_KEY, {
 | 
				
			||||||
 | 
					            factory: () => ({
 | 
				
			||||||
 | 
					                fromVillageId: 0,
 | 
				
			||||||
 | 
					                toVillageId: 0,
 | 
				
			||||||
 | 
					                resources: {
 | 
				
			||||||
 | 
					                    lumber: 0,
 | 
				
			||||||
 | 
					                    clay: 0,
 | 
				
			||||||
 | 
					                    iron: 0,
 | 
				
			||||||
 | 
					                    crop: 0,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                score: 0,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,7 +2,7 @@ import { DataStorage } from '../DataStorage';
 | 
				
			|||||||
import { BuildingQueueInfo } from '../Game';
 | 
					import { BuildingQueueInfo } from '../Game';
 | 
				
			||||||
import { Resources, ResourcesInterface } from '../Core/Resources';
 | 
					import { Resources, ResourcesInterface } from '../Core/Resources';
 | 
				
			||||||
import { ResourceStorage } from '../Core/ResourceStorage';
 | 
					import { ResourceStorage } from '../Core/ResourceStorage';
 | 
				
			||||||
import { IncomingMerchant } from '../Core/Market';
 | 
					import { IncomingMerchant, MerchantsInfo } from '../Core/Market';
 | 
				
			||||||
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
 | 
					import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
 | 
				
			||||||
import { ProductionQueue } from '../Core/ProductionQueue';
 | 
					import { ProductionQueue } from '../Core/ProductionQueue';
 | 
				
			||||||
import { getNumber } from '../utils';
 | 
					import { getNumber } from '../utils';
 | 
				
			||||||
@@ -13,6 +13,7 @@ const CAPACITY_KEY = 'capacity';
 | 
				
			|||||||
const PERFORMANCE_KEY = 'performance';
 | 
					const PERFORMANCE_KEY = 'performance';
 | 
				
			||||||
const BUILDING_QUEUE_INFO_KEY = 'building_queue_info';
 | 
					const BUILDING_QUEUE_INFO_KEY = 'building_queue_info';
 | 
				
			||||||
const INCOMING_MERCHANTS_KEY = 'incoming_merchants';
 | 
					const INCOMING_MERCHANTS_KEY = 'incoming_merchants';
 | 
				
			||||||
 | 
					const MERCHANTS_INFO_KEY = 'merchants_info';
 | 
				
			||||||
const SETTINGS_KEY = 'settings';
 | 
					const SETTINGS_KEY = 'settings';
 | 
				
			||||||
const QUEUE_ENDING_TIME_KEY = 'queue_ending_time';
 | 
					const QUEUE_ENDING_TIME_KEY = 'queue_ending_time';
 | 
				
			||||||
const TASK_LIST_KEY = 'tasks';
 | 
					const TASK_LIST_KEY = 'tasks';
 | 
				
			||||||
@@ -63,6 +64,16 @@ export class VillageStorage {
 | 
				
			|||||||
        return Object.assign(res, plain) as BuildingQueueInfo;
 | 
					        return Object.assign(res, plain) as BuildingQueueInfo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    storeMerchantsInfo(info: MerchantsInfo): void {
 | 
				
			||||||
 | 
					        this.storage.set(MERCHANTS_INFO_KEY, info);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getMerchantsInfo(): MerchantsInfo {
 | 
				
			||||||
 | 
					        return this.storage.getTyped<MerchantsInfo>(MERCHANTS_INFO_KEY, {
 | 
				
			||||||
 | 
					            factory: () => ({ available: 0, carry: 0 }),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    storeIncomingMerchants(merchants: ReadonlyArray<IncomingMerchant>): void {
 | 
					    storeIncomingMerchants(merchants: ReadonlyArray<IncomingMerchant>): void {
 | 
				
			||||||
        this.storage.set(
 | 
					        this.storage.set(
 | 
				
			||||||
            INCOMING_MERCHANTS_KEY,
 | 
					            INCOMING_MERCHANTS_KEY,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,21 +4,23 @@ import { ClickButtonAction } from '../Action/ClickButtonAction';
 | 
				
			|||||||
import { goToMarketSendResourcesPage, goToResourceViewPage } from './ActionBundles';
 | 
					import { goToMarketSendResourcesPage, goToResourceViewPage } from './ActionBundles';
 | 
				
			||||||
import { Task } from '../Queue/TaskProvider';
 | 
					import { Task } from '../Queue/TaskProvider';
 | 
				
			||||||
import { registerTask } from './TaskMap';
 | 
					import { registerTask } from './TaskMap';
 | 
				
			||||||
import { taskError } from '../Errors';
 | 
					import { FindSendResourcesPath } from '../Action/FindSendResourcesPath';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@registerTask()
 | 
					@registerTask()
 | 
				
			||||||
export class SendResourcesTask extends TaskController {
 | 
					export class SendResourcesTask extends TaskController {
 | 
				
			||||||
    defineActions(task: Task): Array<ActionDefinition> {
 | 
					    defineActions(task: Task): Array<ActionDefinition> {
 | 
				
			||||||
        const targetVillageId = task.args.targetVillageId || taskError('Empty target village id');
 | 
					        const actions: Array<ActionDefinition> = [];
 | 
				
			||||||
        const villageId = task.args.villageId || taskError('Empty village id');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return [
 | 
					        const villages = this.factory.getAllVillages();
 | 
				
			||||||
            goToResourceViewPage(targetVillageId),
 | 
					        for (let village of villages) {
 | 
				
			||||||
            goToMarketSendResourcesPage(targetVillageId),
 | 
					            actions.push(goToResourceViewPage(village.id));
 | 
				
			||||||
            goToResourceViewPage(villageId),
 | 
					            actions.push(goToMarketSendResourcesPage(village.id));
 | 
				
			||||||
            goToMarketSendResourcesPage(villageId),
 | 
					        }
 | 
				
			||||||
            [SendResourcesAction.name],
 | 
					
 | 
				
			||||||
            [ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }],
 | 
					        actions.push([FindSendResourcesPath.name]);
 | 
				
			||||||
        ];
 | 
					        actions.push([SendResourcesAction.name]);
 | 
				
			||||||
 | 
					        actions.push([ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return actions;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,17 @@ import { CompleteTaskAction } from '../Action/CompleteTaskAction';
 | 
				
			|||||||
import { Action } from '../Queue/ActionQueue';
 | 
					import { Action } from '../Queue/ActionQueue';
 | 
				
			||||||
import { Args } from '../Queue/Args';
 | 
					import { Args } from '../Queue/Args';
 | 
				
			||||||
import { Task } from '../Queue/TaskProvider';
 | 
					import { Task } from '../Queue/TaskProvider';
 | 
				
			||||||
 | 
					import { VillageFactory } from '../VillageFactory';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ActionDefinition = [string] | [string, Args];
 | 
					export type ActionDefinition = [string] | [string, Args];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TaskController {
 | 
					export class TaskController {
 | 
				
			||||||
    protected scheduler: Scheduler;
 | 
					    protected readonly scheduler: Scheduler;
 | 
				
			||||||
 | 
					    protected readonly factory: VillageFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(scheduler: Scheduler) {
 | 
					    constructor(scheduler: Scheduler, factory: VillageFactory) {
 | 
				
			||||||
        this.scheduler = scheduler;
 | 
					        this.scheduler = scheduler;
 | 
				
			||||||
 | 
					        this.factory = factory;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async run(task: Task) {
 | 
					    async run(task: Task) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Scheduler } from '../Scheduler';
 | 
					import { Scheduler } from '../Scheduler';
 | 
				
			||||||
import { TaskController } from './TaskController';
 | 
					import { TaskController } from './TaskController';
 | 
				
			||||||
import { ProductionQueue } from '../Core/ProductionQueue';
 | 
					import { ProductionQueue } from '../Core/ProductionQueue';
 | 
				
			||||||
 | 
					import { VillageFactory } from '../VillageFactory';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TaskOptions {
 | 
					interface TaskOptions {
 | 
				
			||||||
    queue?: ProductionQueue;
 | 
					    queue?: ProductionQueue;
 | 
				
			||||||
@@ -34,11 +35,15 @@ export function getProductionQueue(name: string): ProductionQueue | undefined {
 | 
				
			|||||||
    return taskDescription.queue;
 | 
					    return taskDescription.queue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createTaskHandler(name: string, scheduler: Scheduler): TaskController | undefined {
 | 
					export function createTaskHandler(
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    scheduler: Scheduler,
 | 
				
			||||||
 | 
					    factory: VillageFactory
 | 
				
			||||||
 | 
					): TaskController | undefined {
 | 
				
			||||||
    const taskDescription = taskMap[name];
 | 
					    const taskDescription = taskMap[name];
 | 
				
			||||||
    if (taskDescription === undefined) {
 | 
					    if (taskDescription === undefined) {
 | 
				
			||||||
        return undefined;
 | 
					        return undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const constructor = (taskDescription.ctor as unknown) as typeof TaskController;
 | 
					    const constructor = (taskDescription.ctor as unknown) as typeof TaskController;
 | 
				
			||||||
    return new constructor(scheduler);
 | 
					    return new constructor(scheduler, factory);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,13 @@ import { VillageTaskCollection } from './VillageTaskCollection';
 | 
				
			|||||||
import { Task, TaskId } from './Queue/TaskProvider';
 | 
					import { Task, TaskId } from './Queue/TaskProvider';
 | 
				
			||||||
import { Args } from './Queue/Args';
 | 
					import { Args } from './Queue/Args';
 | 
				
			||||||
import { VillageState } from './VillageState';
 | 
					import { VillageState } from './VillageState';
 | 
				
			||||||
 | 
					import { Resources } from './Core/Resources';
 | 
				
			||||||
 | 
					import { TryLaterError } from './Errors';
 | 
				
			||||||
 | 
					import { aroundMinutes } from './utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class VillageController {
 | 
					export class VillageController {
 | 
				
			||||||
    private readonly villageId: number;
 | 
					    private readonly villageId: number;
 | 
				
			||||||
    private taskCollection: VillageTaskCollection;
 | 
					    private readonly taskCollection: VillageTaskCollection;
 | 
				
			||||||
    private readonly state: VillageState;
 | 
					    private readonly state: VillageState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) {
 | 
					    constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) {
 | 
				
			||||||
@@ -33,4 +36,46 @@ export class VillageController {
 | 
				
			|||||||
    postponeTask(taskId: TaskId, seconds: number) {
 | 
					    postponeTask(taskId: TaskId, seconds: number) {
 | 
				
			||||||
        this.taskCollection.postponeTask(taskId, seconds);
 | 
					        this.taskCollection.postponeTask(taskId, seconds);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getAvailableForSendResources(): Resources {
 | 
				
			||||||
 | 
					        const balance = this.state.required.balance;
 | 
				
			||||||
 | 
					        const free = balance.max(Resources.zero());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.table([
 | 
				
			||||||
 | 
					            { name: 'Sender balance', ...balance },
 | 
				
			||||||
 | 
					            { name: 'Sender free', ...free },
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const amount = free.amount();
 | 
				
			||||||
 | 
					        const threshold = this.state.settings.sendResourcesThreshold;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (amount < threshold) {
 | 
				
			||||||
 | 
					            return Resources.zero();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return free;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getRequiredResources(): Resources {
 | 
				
			||||||
 | 
					        const performance = this.state.performance;
 | 
				
			||||||
 | 
					        const maxPossibleToStore = this.state.storage.capacity.sub(performance);
 | 
				
			||||||
 | 
					        const currentResources = this.state.resources;
 | 
				
			||||||
 | 
					        const incomingResources = this.state.incomingResources;
 | 
				
			||||||
 | 
					        const requirementResources = this.state.required.resources;
 | 
				
			||||||
 | 
					        const missingResources = requirementResources
 | 
				
			||||||
 | 
					            .min(maxPossibleToStore)
 | 
				
			||||||
 | 
					            .sub(incomingResources)
 | 
				
			||||||
 | 
					            .sub(currentResources)
 | 
				
			||||||
 | 
					            .max(Resources.zero());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.table([
 | 
				
			||||||
 | 
					            { name: 'Recipient max possible', ...maxPossibleToStore },
 | 
				
			||||||
 | 
					            { name: 'Recipient resources', ...currentResources },
 | 
				
			||||||
 | 
					            { name: 'Recipient incoming', ...incomingResources },
 | 
				
			||||||
 | 
					            { name: 'Recipient requirements', ...requirementResources },
 | 
				
			||||||
 | 
					            { name: 'Recipient missing', ...missingResources },
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return missingResources;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { VillageStorage } from './Storage/VillageStorage';
 | 
				
			|||||||
import { VillageRepository } from './VillageRepository';
 | 
					import { VillageRepository } from './VillageRepository';
 | 
				
			||||||
import { VillageTaskCollection } from './VillageTaskCollection';
 | 
					import { VillageTaskCollection } from './VillageTaskCollection';
 | 
				
			||||||
import { VillageState, VillageStateFactory } from './VillageState';
 | 
					import { VillageState, VillageStateFactory } from './VillageState';
 | 
				
			||||||
 | 
					import { Village } from './Core/Village';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class VillageFactory {
 | 
					export class VillageFactory {
 | 
				
			||||||
    private readonly villageRepository: VillageRepository;
 | 
					    private readonly villageRepository: VillageRepository;
 | 
				
			||||||
@@ -11,6 +12,14 @@ export class VillageFactory {
 | 
				
			|||||||
        this.villageRepository = villageRepository;
 | 
					        this.villageRepository = villageRepository;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getAllVillages(): Array<Village> {
 | 
				
			||||||
 | 
					        return this.villageRepository.all();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getVillage(villageId: number): Village {
 | 
				
			||||||
 | 
					        return this.villageRepository.get(villageId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    createStorage(villageId: number): VillageStorage {
 | 
					    createStorage(villageId: number): VillageStorage {
 | 
				
			||||||
        const village = this.villageRepository.get(villageId);
 | 
					        const village = this.villageRepository.get(villageId);
 | 
				
			||||||
        return new VillageStorage(village.id);
 | 
					        return new VillageStorage(village.id);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,11 @@ export function randomInRange(from: number, to: number): number {
 | 
				
			|||||||
    return Math.floor(from + variation);
 | 
					    return Math.floor(from + variation);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function around(value: number, koeff: number): number {
 | 
				
			||||||
 | 
					    const delta = Math.floor(value * koeff);
 | 
				
			||||||
 | 
					    return randomInRange(value - delta, value + delta);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function sleepMicro() {
 | 
					export async function sleepMicro() {
 | 
				
			||||||
    const timeInMs = randomInRange(1500, 2500);
 | 
					    const timeInMs = randomInRange(1500, 2500);
 | 
				
			||||||
    return await sleep(timeInMs);
 | 
					    return await sleep(timeInMs);
 | 
				
			||||||
@@ -17,8 +22,7 @@ export async function sleepMicro() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function aroundMinutes(minutes: number) {
 | 
					export function aroundMinutes(minutes: number) {
 | 
				
			||||||
    const seconds = minutes * 60;
 | 
					    const seconds = minutes * 60;
 | 
				
			||||||
    const delta = Math.floor(seconds * 0.1);
 | 
					    return around(seconds, 0.1);
 | 
				
			||||||
    return randomInRange(seconds - delta, seconds + delta);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function waitForLoad() {
 | 
					export async function waitForLoad() {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user