UNPKG

@screeps/engine

Version:

This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.

531 lines (446 loc) 20.5 kB
var q = require('q'), _ = require('lodash'), utils = require('../utils'), driver = utils.getDriver(), C = driver.constants; module.exports.execute = function(market, gameTime, terminals, bulkObjects) { var bulkUsers = driver.bulkUsersWrite(), bulkTransactions = driver.bulkTransactionsWrite(), bulkUsersMoney = driver.bulkUsersMoney(), bulkUsersResources = driver.bulkUsersResources(), bulkMarketOrders = driver.bulkMarketOrders(); var terminalsByRoom = _.indexBy(terminals, 'room'); function executeTransfer(fromTerminal, toTerminal, resourceType, amount, transferFeeTerminal, additionalFields) { additionalFields = additionalFields || {}; if(!fromTerminal || !toTerminal || !transferFeeTerminal) { return false; } if(fromTerminal.user && (!fromTerminal[resourceType] || fromTerminal[resourceType] < amount)) { return false; } if(toTerminal.user) { var targetResourceTotal = utils.calcResources(toTerminal), freeSpace = Math.max(0, toTerminal.energyCapacity - targetResourceTotal); amount = Math.min(amount, freeSpace); } if(!(amount > 0)) { return; } var range = utils.calcRoomsDistance(fromTerminal.room, toTerminal.room, true); var transferCost = utils.calcTerminalEnergyCost(amount,range); if(transferFeeTerminal === fromTerminal && (resourceType != C.RESOURCE_ENERGY && fromTerminal.energy < transferCost || resourceType == C.RESOURCE_ENERGY && fromTerminal.energy < amount + transferCost) || transferFeeTerminal === toTerminal && toTerminal.energy < transferCost) { return false; } if(toTerminal.user) { toTerminal[resourceType] = toTerminal[resourceType] || 0; toTerminal[resourceType] += amount; bulkObjects.update(toTerminal, {[resourceType]: toTerminal[resourceType]}); } bulkObjects.update(fromTerminal, {[resourceType]: fromTerminal[resourceType] - amount}); bulkObjects.update(transferFeeTerminal, {energy: transferFeeTerminal.energy - transferCost}); bulkTransactions.insert(_.extend({ time: +gameTime, sender: fromTerminal.user ? ""+fromTerminal.user : undefined, recipient: toTerminal.user ? ""+toTerminal.user : undefined, resourceType: resourceType, amount: amount, from: fromTerminal.room, to: toTerminal.room }, additionalFields)); return true; } _.filter(terminals, i => !!i.send).forEach(terminal => { var intent = terminal.send; bulkObjects.update(terminal, {send: null}); if(terminal.cooldownTime > gameTime) { return; } if(!terminalsByRoom[intent.targetRoomName] || !terminalsByRoom[intent.targetRoomName].user) { return; } if(executeTransfer(terminal, terminalsByRoom[intent.targetRoomName], intent.resourceType, intent.amount, terminal, { description: intent.description ? intent.description.replace(/</g, '&lt;') : undefined })) { bulkObjects.update(terminal, {cooldownTime: gameTime + C.TERMINAL_COOLDOWN}); } }); if(market) { var usersById = _.indexBy(market.users, '_id'), ordersById = _.indexBy(market.orders, '_id'), terminalDeals = [], directDeals = []; market.intents.forEach(userIntents => { var user = usersById[userIntents.user]; if (userIntents.intents.createOrder) { userIntents.intents.createOrder.forEach(intent => { if (!intent.price || !intent.totalAmount) { return; } if(!_.contains(C.RESOURCES_ALL, intent.resourceType) && intent.resourceType != C.SUBSCRIPTION_TOKEN) { return; } if (intent.resourceType != C.SUBSCRIPTION_TOKEN && (!terminalsByRoom[intent.roomName] || terminalsByRoom[intent.roomName].user != userIntents.user)) { return; } if(intent.price <= 0 || intent.totalAmount <= 0) { return; } var fee = Math.ceil(intent.price * intent.totalAmount * C.MARKET_FEE); if (user.money < fee) { return; } bulkUsers.inc(user, 'money', -fee); bulkMarketOrders.insert(_.extend({ created: gameTime, user: userIntents.user, active: false, type: intent.type == C.ORDER_SELL ? C.ORDER_SELL : C.ORDER_BUY, amount: 0, remainingAmount: intent.totalAmount }, intent)); intent.price /= 1000; bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: userIntents.user, type: 'market.fee', balance: user.money/1000, change: -fee/1000, market: { order: intent } }); }); } if (userIntents.intents.changeOrderPrice) { userIntents.intents.changeOrderPrice.forEach(intent => { var order = ordersById[intent.orderId]; if (!order || order.user != userIntents.user) { return; } if (!intent.newPrice || intent.newPrice <= 0) { return; } if(intent.newPrice > order.price) { var fee = Math.ceil((intent.newPrice - order.price) * order.remainingAmount * C.MARKET_FEE); if (user.money < fee) { return; } bulkUsers.inc(user, 'money', -fee); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: userIntents.user, type: 'market.fee', balance: user.money/1000, change: -fee/1000, market: { changeOrderPrice: { orderId: intent.orderId, oldPrice: order.price/1000, newPrice: intent.newPrice/1000 } } }); } bulkMarketOrders.update(order._id, {price: intent.newPrice}); }); } if (userIntents.intents.extendOrder) { userIntents.intents.extendOrder.forEach(intent => { var order = ordersById[intent.orderId]; if (!order || order.user != userIntents.user) { return; } if (!intent.addAmount || intent.addAmount <= 0) { return; } var fee = Math.ceil(order.price * intent.addAmount * C.MARKET_FEE); if (user.money < fee) { return; } bulkUsers.inc(user, 'money', -fee); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: userIntents.user, type: 'market.fee', balance: user.money/1000, change: -fee/1000, market: { extendOrder: { orderId: intent.orderId, addAmount: intent.addAmount } } }); bulkMarketOrders.update(order, { remainingAmount: order.remainingAmount + intent.addAmount, totalAmount: order.totalAmount + intent.addAmount }); }); } if (userIntents.intents.cancelOrder) { userIntents.intents.cancelOrder.forEach(intent => { if (ordersById[intent.orderId] && ordersById[intent.orderId].user == userIntents.user) { ordersById[intent.orderId].remainingAmount = 0; ordersById[intent.orderId]._cancelled = true; //console.log('Order cancelled ',JSON.stringify(ordersById[intent.orderId])); } }); } if (userIntents.intents.deal) { userIntents.intents.deal.forEach(intent => { intent.user = userIntents.user; if(!ordersById[intent.orderId]) { return; } if(intent.amount <= 0) { return; } if(ordersById[intent.orderId].resourceType == C.SUBSCRIPTION_TOKEN) { directDeals.push(intent); return; } if(!terminalsByRoom[intent.targetRoomName] || terminalsByRoom[intent.targetRoomName].user != userIntents.user) { return; } terminalDeals.push(intent); }); } }); terminalDeals.sort((a,b) => utils.calcRoomsDistance(a.targetRoomName, ordersById[a.orderId].roomName, true) - utils.calcRoomsDistance(b.targetRoomName, ordersById[b.orderId].roomName, true)) terminalDeals.forEach(deal => { var order = ordersById[deal.orderId], orderTerminal = terminalsByRoom[order.roomName], targetTerminal = terminalsByRoom[deal.targetRoomName], buyer, seller; if(!orderTerminal || !targetTerminal) { return; } if(targetTerminal.cooldownTime > gameTime) { return; } if(order.type == C.ORDER_SELL) { buyer = targetTerminal; seller = orderTerminal; } else { seller = targetTerminal; buyer = orderTerminal; } var amount = Math.min(deal.amount, order.remainingAmount); if(seller.user) { amount = Math.min(amount, seller[order.resourceType] || 0); } if(buyer.user) { var targetResourceTotal = utils.calcResources(buyer), targetFreeSpace = Math.max(0, buyer.energyCapacity - targetResourceTotal); amount = Math.min(amount, targetFreeSpace); } if(!(amount > 0)) { return; } var dealCost = amount * order.price; if(buyer.user) { dealCost = Math.min(dealCost, usersById[buyer.user].money || 0); amount = Math.floor(dealCost/order.price); dealCost = amount * order.price; if(!amount) { return; } } if(executeTransfer(seller, buyer, order.resourceType, amount, targetTerminal, {order: { id: ""+order._id, type: order.type, price: order.price/1000 }})) { if(seller.user) { bulkUsers.inc(usersById[seller.user], 'money', dealCost); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: seller.user, type: 'market.sell', balance: usersById[seller.user].money/1000, change: dealCost/1000, market: { resourceType: order.resourceType, roomName: order.roomName, targetRoomName: deal.targetRoomName, price: order.price/1000, npc: !buyer.user, amount } }); } if(buyer.user) { bulkUsers.inc(usersById[buyer.user], 'money', -dealCost); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: buyer.user, type: 'market.buy', balance: usersById[buyer.user].money/1000, change: -dealCost/1000, market: { resourceType: order.resourceType, roomName: order.roomName, targetRoomName: deal.targetRoomName, price: order.price/1000, npc: !seller.user, amount } }); } bulkMarketOrders.update(order, { amount: order.amount - amount, remainingAmount: order.remainingAmount - amount }); bulkObjects.update(targetTerminal, {cooldownTime: gameTime + C.TERMINAL_COOLDOWN}); } }); directDeals = _.shuffle(directDeals); directDeals.forEach(deal => { var order = ordersById[deal.orderId], buyer, seller, userFieldName = 'subscriptionTokens'; if(order.type == C.ORDER_SELL) { buyer = usersById[deal.user]; seller = usersById[order.user]; } else { seller = usersById[deal.user]; buyer = usersById[order.user]; } if(!seller || !buyer) { return; } var amount = Math.min(deal.amount, order.remainingAmount); if(seller.user) { amount = Math.min(amount, seller[userFieldName] || 0); } if(!amount) { return; } var dealCost = amount * order.price; if(buyer.user && (!buyer.money || buyer.money < dealCost)) { return; } bulkUsers.inc(seller, 'money', dealCost); bulkUsers.inc(seller, userFieldName, -amount); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: ""+seller._id, type: 'market.sell', balance: seller.money/1000, change: dealCost/1000, market: { resourceType: order.resourceType, price: order.price/1000, amount } }); bulkUsersResources.insert({ date: new Date(), resourceType: order.resourceType, user: ""+seller._id, change: -amount, balance: seller[userFieldName], marketOrderId: ""+order._id, market: { orderId: ""+order._id, anotherUser: ""+buyer._id } }); bulkUsers.inc(buyer, 'money', -dealCost); bulkUsers.inc(buyer, userFieldName, amount); bulkUsersMoney.insert({ date: new Date(), tick: gameTime, user: ""+buyer._id, type: 'market.buy', balance: buyer.money/1000, change: -dealCost/1000, market: { resourceType: order.resourceType, price: order.price/1000, amount } }); bulkMarketOrders.update(order, { amount: order.amount - amount, remainingAmount: order.remainingAmount - amount }); bulkUsersResources.insert({ date: new Date(), resourceType: order.resourceType, user: ""+buyer._id, change: amount, balance: buyer[userFieldName], market: { orderId: ""+order._id, anotherUser: ""+seller._id } }); }); market.orders.forEach(order => { if (order._cancelled) { bulkMarketOrders.remove(order._id); return; } if(!order.user) { return; } var terminal = terminalsByRoom[order.roomName]; if (order.type == C.ORDER_SELL) { var availableResourceAmount = order.resourceType == C.SUBSCRIPTION_TOKEN ? (usersById[order.user].subscriptionTokens || 0) : terminal && terminal.user == order.user ? terminal[order.resourceType] || 0 : 0; availableResourceAmount = Math.min(availableResourceAmount, order.remainingAmount); if (order.active) { if (!availableResourceAmount) { bulkMarketOrders.update(order, {active: false, amount: 0}); return; } if (order.amount != availableResourceAmount) { bulkMarketOrders.update(order, {amount: availableResourceAmount}); } } else { if (availableResourceAmount > 0) { bulkMarketOrders.update(order, { active: true, amount: availableResourceAmount }); } } } if (order.type == C.ORDER_BUY) { var user = usersById[order.user], userMoney = user.money || 0; var isOwner = order.resourceType == C.SUBSCRIPTION_TOKEN || (!!terminal && terminal.user == order.user); var newAmount = Math.min(Math.floor(userMoney / order.price), order.remainingAmount); if(terminal && terminal.user) { var targetResourceTotal = utils.calcResources(terminal), targetFreeSpace = Math.max(0, terminal.energyCapacity - targetResourceTotal); newAmount = Math.min(newAmount, targetFreeSpace); } var newActive = isOwner && newAmount > 0; if (order.amount != newAmount || order.active != newActive) { bulkMarketOrders.update(order, {amount: newAmount, active: newActive}); } } }); } return q.all([ bulkUsers.execute(), bulkMarketOrders.execute(), bulkUsersMoney.execute(), bulkTransactions.execute(), bulkUsersResources.execute(), driver.clearMarketIntents() ]); };