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 { 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, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
 | 
			
		||||
import { VillageState } from '../VillageState';
 | 
			
		||||
import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage';
 | 
			
		||||
import { ResourceTransferCalculator } from '../ResourceTransfer';
 | 
			
		||||
import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
 | 
			
		||||
import { Resources } from '../Core/Resources';
 | 
			
		||||
import { AbortTaskError } from '../Errors';
 | 
			
		||||
 | 
			
		||||
@registerAction
 | 
			
		||||
export class SendResourcesAction extends ActionController {
 | 
			
		||||
    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 targetVillageId = args.targetVillageId || taskError('No target village id');
 | 
			
		||||
        const fromVillage = this.villageFactory.getVillage(savedReport.fromVillageId);
 | 
			
		||||
        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 recipientVillage = this.villageFactory.createState(targetVillageId);
 | 
			
		||||
        const calculator = new ResourceTransferCalculator(this.villageFactory);
 | 
			
		||||
        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
 | 
			
		||||
        const timeout = senderVillage.settings.sendResourcesTimeout;
 | 
			
		||||
        this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(timeout));
 | 
			
		||||
 | 
			
		||||
        fillSendResourcesForm(readyToTransfer, coordinates);
 | 
			
		||||
        fillSendResourcesForm(report.resources, coordinates);
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user