UNPKG

@reldens/items-system

Version:
319 lines (296 loc) 12.3 kB
/** * * Reldens - Items System - ExchangePlatform * */ const RequirementsCollection = require('./requirements-collection'); const RewardsCollection = require('./rewards-collection'); const RequirementsProcessor = require('./requirements-processor'); const RewardsProcessor = require('./rewards-processor'); const ItemsError = require('../items-error'); const ItemsEvents = require('../items-events'); const ItemsConst = require('../constants'); const { EventsManagerSingleton, ErrorManager, Logger, sc } = require('@reldens/utils'); class ExchangePlatform { constructor(props) { this.events = sc.get(props, 'eventsManager', EventsManagerSingleton); this.requirementsProcessor = new RequirementsProcessor(); this.rewardsProcessor = new RewardsProcessor(); this.exchangeInitializerId = sc.get(props, 'exchangeInitializerId', false); this.initializeProperties(); } initializeExchangeBetween(props) { let inventoryA = sc.get(props, 'inventoryA', null); let inventoryB = sc.get(props, 'inventoryB', null); if(null === inventoryA || null === inventoryB){ ErrorManager.error('Missing inventories from properties.', props); } this.inventories = {A: inventoryA, B: inventoryB}; this.lockInventories(); this.confirmations = {A: false, B: false}; this.exchangeBetween = {A: {}, B: {}}; this.exchangeRequirements = { A: sc.get(props, 'exchangeRequirementsA', new RequirementsCollection()), B: sc.get(props, 'exchangeRequirementsB', new RequirementsCollection()) }; this.exchangeRewards = { A: sc.get(props, 'exchangeRewardsA', new RewardsCollection()), B: sc.get(props, 'exchangeRewardsB', new RewardsCollection()) }; this.dropExchange = { A: sc.get(props, 'dropExchangeA', false), B: sc.get(props, 'dropExchangeB', false) }; this.avoidExchangeDecrease = { A: sc.get(props, 'avoidExchangeDecreaseA', false), B: sc.get(props, 'avoidExchangeDecreaseB', false) }; this.setError(); this.events.emit(ItemsEvents.EXCHANGE.INITIALIZED, {exchangePlatform: this, props, inventoryA, inventoryB}); } setError(message = '', code = '', data = {}, withError = false) { this.lastError = new ItemsError(message, code, data, withError); } cancelExchange() { this.unlockInventories(); this.initializeProperties(); this.events.emit(ItemsEvents.EXCHANGE.CANCELED, {exchangePlatform: this}); } initializeProperties() { this.inventories = {A: null, B: null}; this.confirmations = {A: false, B: false}; this.exchangeBetween = {A: {}, B: {}}; this.exchangeRequirements = {A: [], B: []}; this.exchangeRewards = {A: [], B: []}; this.setError(); } async pushForExchange(itemUid, qty, inventoryKey) { if(this.confirmations['A'] || this.confirmations['B']){ // @TODO - BETA - Make lock exchange configurable so confirmation will be auto-removed on exchange changes. Logger.info('Push for exchange "'+itemUid+'" was blocked.' +' Exchange for "'+inventoryKey+'" and owner "'+this.inventories[inventoryKey].owner_id+'"' +' was already confirmed.'); return false; } if(!this.canBePushed(itemUid, qty, inventoryKey)){ this.events.emit(ItemsEvents.EXCHANGE.INVALID_PUSH, {exchangePlatform: this, itemUid, qty, inventoryKey}); return false; } this.exchangeBetween[inventoryKey][itemUid] = qty; if( !this.validateRequirements('A') || !this.validateRewards('A') || !this.validateRequirements('B') || !this.validateRewards('B') ){ delete this.exchangeBetween[inventoryKey][itemUid]; return false; } this.events.emit(ItemsEvents.EXCHANGE.ITEM_PUSHED, {exchangePlatform: this, itemUid, qty, inventoryKey}); return true; } async removeFromExchange(itemUid, inventoryKey) { if(this.confirmations['A'] || this.confirmations['B']){ Logger.info('Remove from exchange "'+itemUid+'" was blocked.' +' Exchange for "'+inventoryKey+'" and owner "'+this.inventories[inventoryKey].owner_id +'"' +' was already confirmed.'); return false; } this.events.emit(ItemsEvents.EXCHANGE.ITEM_REMOVE, {exchangePlatform: this, itemUid, inventoryKey}); if(!sc.hasOwn(this.exchangeBetween[inventoryKey], itemUid)){ return false; } delete this.exchangeBetween[inventoryKey][itemUid]; return true; } async confirmExchange(inventoryKey) { this.events.emit(ItemsEvents.EXCHANGE.CONFIRM, {exchangePlatform: this, inventoryKey}); this.confirmations[inventoryKey] = true; } async disconfirmExchange(inventoryKey) { this.events.emit(ItemsEvents.EXCHANGE.DISCONFIRM, {exchangePlatform: this, inventoryKey}); this.confirmations[inventoryKey] = false; } async finalizeExchange() { this.events.emit(ItemsEvents.EXCHANGE.BEFORE_FINALIZE, {exchangePlatform: this}); if(!this.confirmations['A'] || !this.confirmations['B']){ this.setError( 'Missing confirmation.', ItemsConst.ERROR_CODES.EXCHANGE.MISSING_CONFIRMATION, {confirmations: this.confirmations} ); return false; } if(!this.validateRequirements('A') || !this.validateRequirements('B')){ return false; } if(!this.validateRewards('A') || !this.validateRewards('B')){ return false; } this.unlockInventories(); let resultA = await this.executeExchangeFromTo('A', 'B'); if(false === resultA){ return false; } let resultB = await this.executeExchangeFromTo('B', 'A'); if(false === resultB){ return false; } this.events.emit(ItemsEvents.EXCHANGE.FINALIZED, {exchangePlatform: this}); this.setError(); return true; } validateRequirements(inventoryKey) { return this.requirementsProcessor.validateRequirements(inventoryKey, this); } validateRewards(inventoryKey) { return this.rewardsProcessor.validateRewards(inventoryKey, this); } lockInventories() { this.setLocks(true); } unlockInventories() { this.setLocks(false); } setLocks(status) { this.setInventoryLock('A', status); this.setInventoryLock('B', status); } setInventoryLock(inventoryKey, status) { let inventory = sc.get(this.inventories, inventoryKey, false); if(null === inventory){ return false; } if(false === inventory){ Logger.error('Inventory not found "'+inventoryKey+'".', this.inventories); return false; } inventory.locked = status; return true; } canBePushed(itemUid, qty, inventoryKey) { if(!sc.hasOwn(this.inventories[inventoryKey].items, itemUid)){ Logger.critical('Item Key does not exists on the inventory', itemUid, this.inventories[inventoryKey].items); return false; } // item qty is available or -1 item qty is infinite: let pushedItem = this.inventories[inventoryKey].items[itemUid]; let isValidQuantity = qty <= pushedItem.qty || -1 === pushedItem.qty; if(!isValidQuantity){ this.setError( 'Invalid item pushed quantity ('+qty+'), available: '+pushedItem.qty, ItemsConst.ERROR_CODES.EXCHANGE.INVALID_PUSHED_QUANTITY, {itemUid, qty, pushedItemQty: pushedItem.qty} ); } return isValidQuantity; } async executeExchangeFromTo(from, to) { if(from === to){ this.setError( 'Inventories "FROM" and "TO" are the same, exchange cancelled.', ItemsConst.ERROR_CODES.EXCHANGE.INVALID_EXCHANGE ); this.cancelExchange(); return this.inventories; } let inventoryFrom = this.inventories[from]; let inventoryTo = this.inventories[to]; let itemsFromAUids = Object.keys(this.exchangeBetween[from]); for(let itemUid of itemsFromAUids){ let itemQtyFrom = this.exchangeBetween[from][itemUid]; if(0 === itemQtyFrom){ this.setError( 'Invalid item quantity 0.', ItemsConst.ERROR_CODES.EXCHANGE.INVALID_QUANTITY, {itemUid} ); return false; } // first create the new item because the decrease quantity could remove the instance we need to clone: let newItemFor = false === this.dropExchange[to] ? inventoryTo.createItemInstance(inventoryFrom.items[itemUid].key, itemQtyFrom) : false; let rewardsResult = await this.rewardsProcessor.processRewards( from, to, itemUid, inventoryFrom, inventoryTo, this ); if(false === rewardsResult){ return false; } // @NOTE: since we already validate the transaction requirements before execute it, here we only need to // remove the requirements that were not pushed for exchange. let requirementsResult = await this.requirementsProcessor.processRequirements( from, to, itemUid, itemQtyFrom, inventoryFrom, inventoryTo, this ); if(false === requirementsResult){ return false; } let inventoryFromDecreaseItemQtyResult = true if(false === this.avoidExchangeDecrease[from]){ inventoryFrom.frozenItems[itemUid] = Object.assign({}, inventoryFrom.items[itemUid]); inventoryFromDecreaseItemQtyResult = await inventoryFrom.decreaseItemQty(itemUid, itemQtyFrom); } // @NOTE: drop exchange is for when only care about giving rewards for items, for example selling an item, // if the seller is an NPC it won't need to add the sold item from the player to its own inventory. if(false === inventoryFromDecreaseItemQtyResult){ this.setError( 'Exchange inventory decrease error.', ItemsConst.ERROR_CODES.EXCHANGE.DECREASE_QUANTITY, {itemUid}, inventoryFrom.lastError ); return false; } if(false === this.dropExchange[to]){ let itemInstances = !sc.isArray(newItemFor) ? [newItemFor] : newItemFor; let addResult = await inventoryTo.addItems(itemInstances); if(false === addResult){ this.setError( 'Exchange add inventory result error.', ItemsConst.ERROR_CODES.EXCHANGE.ITEM_ADD, {itemUid}, inventoryTo.lastError ); return false; } } } this.exchangeBetween[from] = {}; return this.inventories; } oppositeKey(inventoryKey) { return 'A' === inventoryKey ? 'B' : 'A'; } } module.exports = ExchangePlatform;