@screeps/engine
Version:
This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.
583 lines (492 loc) • 22.9 kB
JavaScript
'use strict';
var q = require('q'),
_ = require('lodash'),
utils = require('../../utils'),
driver = utils.getDriver(),
C = driver.constants;
module.exports = function ({ orders, userIntents, usersById, gameTime, roomObjectsByType, bulkObjects,
bulkUsers, bulkTransactions, bulkUsersMoney, bulkUsersResources,
bulkMarketOrders, bulkMarketIntershardOrders }) {
var terminals = roomObjectsByType.terminal;
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.store || !fromTerminal.store[resourceType] || fromTerminal.store[resourceType] < amount)) {
return false;
}
if (toTerminal.user) {
var targetResourceTotal = utils.calcResources(toTerminal),
freeSpace = Math.max(0, toTerminal.storeCapacity - 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);
var effect = _.find(transferFeeTerminal.effects, { power: C.PWR_OPERATE_TERMINAL });
if (effect && effect.endTime > gameTime) {
transferCost = Math.ceil(transferCost * C.POWER_INFO[C.PWR_OPERATE_TERMINAL].effect[effect.level - 1]);
}
if (transferFeeTerminal === fromTerminal && (resourceType != C.RESOURCE_ENERGY && fromTerminal.store.energy < transferCost || resourceType == C.RESOURCE_ENERGY && fromTerminal.store.energy < amount + transferCost) || transferFeeTerminal === toTerminal && toTerminal.store.energy < transferCost) {
return false;
}
if (toTerminal.user) {
toTerminal.store = toTerminal.store || {};
toTerminal.store[resourceType] = (toTerminal.store[resourceType] || 0) + amount;
bulkObjects.update(toTerminal, { store: { [resourceType]: toTerminal.store[resourceType] } });
}
bulkObjects.update(fromTerminal, { store: { [resourceType]: fromTerminal.store[resourceType] - amount } });
bulkObjects.update(transferFeeTerminal, { store: { energy: transferFeeTerminal.store.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;
}
var cooldown = C.TERMINAL_COOLDOWN;
var effect = _.find(terminal.effects, { power: C.PWR_OPERATE_TERMINAL });
if (effect && effect.endTime > gameTime) {
cooldown = Math.round(cooldown * C.POWER_INFO[C.PWR_OPERATE_TERMINAL].effect[effect.level - 1]);
}
if (executeTransfer(terminal, terminalsByRoom[intent.targetRoomName], intent.resourceType, intent.amount, terminal, {
description: intent.description ? intent.description.replace(/</g, '<') : undefined
})) {
bulkObjects.update(terminal, { cooldownTime: gameTime + cooldown });
}
});
var ordersById = _.indexBy(orders, '_id'),
terminalDeals = [],
directDeals = [];
const nowTimestamp = new Date().getTime();
if (userIntents) {
userIntents.forEach(iUserIntents => {
var user = usersById[iUserIntents.user];
if (iUserIntents.intents.createOrder) {
iUserIntents.intents.createOrder.forEach(intent => {
if (!intent.price || !intent.totalAmount) {
return;
}
if (!_.contains(C.RESOURCES_ALL, intent.resourceType) && !_.contains(C.INTERSHARD_RESOURCES, intent.resourceType)) {
return;
}
if (!_.contains(C.INTERSHARD_RESOURCES, intent.resourceType) && (!terminalsByRoom[intent.roomName] || terminalsByRoom[intent.roomName].user != iUserIntents.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);
const order = _.extend({
createdTimestamp: nowTimestamp,
user: iUserIntents.user,
active: false,
type: intent.type == C.ORDER_SELL ? C.ORDER_SELL : C.ORDER_BUY,
amount: 0,
remainingAmount: intent.totalAmount
}, intent);
let bulk = bulkMarketIntershardOrders;
if (!_.contains(C.INTERSHARD_RESOURCES, intent.resourceType)) {
bulk = bulkMarketOrders;
order.created = gameTime;
}
bulk.insert(order);
intent.price /= 1000;
bulkUsersMoney.insert({
date: new Date(),
tick: gameTime,
user: iUserIntents.user,
type: 'market.fee',
balance: user.money / 1000,
change: -fee / 1000,
market: {
order: intent
}
});
});
}
if (iUserIntents.intents.changeOrderPrice) {
iUserIntents.intents.changeOrderPrice.forEach(intent => {
const order = ordersById[intent.orderId];
if (!order || order.user != iUserIntents.user) {
return;
}
if (!intent.newPrice || intent.newPrice <= 0) {
return;
}
if (intent.newPrice != order.price) {
order._skip = true;
}
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: iUserIntents.user,
type: 'market.fee',
balance: user.money / 1000,
change: -fee / 1000,
market: {
changeOrderPrice: {
orderId: intent.orderId,
oldPrice: order.price / 1000,
newPrice: intent.newPrice / 1000
}
}
});
}
const bulk = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) ? bulkMarketIntershardOrders : bulkMarketOrders;
bulk.inc(order, 'price', intent.newPrice - order.price);
});
}
if (iUserIntents.intents.extendOrder) {
iUserIntents.intents.extendOrder.forEach(intent => {
const order = ordersById[intent.orderId];
if (!order || order.user != iUserIntents.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: iUserIntents.user,
type: 'market.fee',
balance: user.money / 1000,
change: -fee / 1000,
market: {
extendOrder: {
orderId: intent.orderId,
addAmount: intent.addAmount
}
}
});
const bulk = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) ? bulkMarketIntershardOrders : bulkMarketOrders;
bulk.inc(order, 'remainingAmount', intent.addAmount);
bulk.inc(order, 'totalAmount', intent.addAmount);
});
}
if (iUserIntents.intents.cancelOrder) {
iUserIntents.intents.cancelOrder.forEach(intent => {
if (ordersById[intent.orderId] && ordersById[intent.orderId].user == iUserIntents.user) {
ordersById[intent.orderId].remainingAmount = 0;
ordersById[intent.orderId]._cancelled = true;
}
});
}
if (iUserIntents.intents.deal) {
iUserIntents.intents.deal.forEach(intent => {
intent.user = iUserIntents.user;
if (!ordersById[intent.orderId] || ordersById[intent.orderId]._skip) {
return;
}
if (intent.amount <= 0) {
return;
}
if (_.contains(C.INTERSHARD_RESOURCES, ordersById[intent.orderId].resourceType)) {
directDeals.push(intent);
return;
}
if (!terminalsByRoom[intent.targetRoomName] || terminalsByRoom[intent.targetRoomName].user != iUserIntents.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;
}
orderTerminal.store = orderTerminal.store || {};
targetTerminal.store = targetTerminal.store || {};
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.store[order.resourceType] || 0);
}
if (buyer.user) {
var targetResourceTotal = utils.calcResources(buyer),
targetFreeSpace = Math.max(0, buyer.storeCapacity - 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,
owner: order.user,
dealer: deal.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,
owner: order.user,
dealer: deal.user,
amount
}
});
}
bulkMarketOrders.update(order, {
amount: order.amount - amount,
remainingAmount: order.remainingAmount - amount
});
let cooldown = C.TERMINAL_COOLDOWN;
const effect = _.find(targetTerminal.effects, { power: C.PWR_OPERATE_TERMINAL });
if (effect && effect.endTime > gameTime) {
cooldown = Math.round(cooldown * C.POWER_INFO[C.PWR_OPERATE_TERMINAL].effect[effect.level - 1]);
}
bulkObjects.update(targetTerminal, { cooldownTime: gameTime + cooldown });
}
});
directDeals = _.shuffle(directDeals);
directDeals.forEach(deal => {
var order = ordersById[deal.orderId],
buyer,
seller;
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;
}
seller.resources = seller.resources || {};
buyer.resources = buyer.resources || {};
var amount = Math.min(deal.amount, order.amount, order.remainingAmount, seller.resources[order.resourceType] || 0);
if (!amount || amount < 0) {
return;
}
var dealCost = amount * order.price;
if (buyer.user && (!buyer.money || buyer.money < dealCost)) {
return;
}
bulkUsers.inc(seller, 'money', dealCost);
bulkUsers.inc(seller, 'resources.' + order.resourceType, -amount);
seller.resources[order.resourceType] -= 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.resources[order.resourceType],
marketOrderId: "" + order._id,
market: {
orderId: "" + order._id,
anotherUser: "" + buyer._id
}
});
bulkUsers.inc(buyer, 'money', -dealCost);
bulkUsers.inc(buyer, 'resources.' + order.resourceType, amount);
buyer.money -= dealCost;
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
}
});
const bulk = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) ? bulkMarketIntershardOrders : bulkMarketOrders;
bulk.inc(order, 'amount', -amount);
bulk.inc(order, 'remainingAmount', -amount);
bulkUsersResources.insert({
date: new Date(),
resourceType: order.resourceType,
user: "" + buyer._id,
change: amount,
balance: buyer.resources[order.resourceType],
market: {
orderId: "" + order._id,
anotherUser: "" + seller._id
}
});
});
if (orders) {
orders.forEach(order => {
const bulk = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) ? bulkMarketIntershardOrders : bulkMarketOrders;
if (order._cancelled) {
bulk.remove(order._id);
return;
}
if (order.user && nowTimestamp - order.createdTimestamp > C.MARKET_ORDER_LIFE_TIME) {
const remainingFee = order.remainingAmount * order.price * C.MARKET_FEE;
if (remainingFee > 0) {
const user = usersById[order.user];
bulkUsers.inc(user, 'money', remainingFee);
bulkUsersMoney.insert({
date: new Date(),
tick: gameTime,
user: user._id.toString(),
type: 'market.fee',
balance: user.money / 1000,
change: remainingFee / 1000,
market: {
order: {
orderId: order._id.toString(),
type: order.type,
resourceType: order.resourceType,
price: order.price / 1000,
remainingAmount: order.remainingAmount,
roomName: order.roomName
}
}
});
}
bulk.remove(order._id);
return;
}
if (!order.user) {
return;
}
var terminal = terminalsByRoom[order.roomName];
if (order.type == C.ORDER_SELL) {
var availableResourceAmount = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) ? (usersById[order.user].resources || {})[order.resourceType] || 0 : terminal && terminal.user == order.user ? terminal.store[order.resourceType] || 0 : 0;
availableResourceAmount = Math.min(availableResourceAmount, order.remainingAmount);
if (order.active) {
if (!availableResourceAmount || availableResourceAmount < 0) {
bulk.update(order, { active: false, amount: 0 });
return;
}
if (order.amount != availableResourceAmount) {
bulk.update(order, { amount: availableResourceAmount });
}
} else {
if (availableResourceAmount > 0) {
bulk.update(order, {
active: true,
amount: availableResourceAmount
});
}
}
}
if (order.type == C.ORDER_BUY) {
var user = usersById[order.user],
userMoney = user.money || 0;
var isOwner = _.contains(C.INTERSHARD_RESOURCES, order.resourceType) || !!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.storeCapacity - targetResourceTotal);
newAmount = Math.min(newAmount, targetFreeSpace);
}
var newActive = isOwner && newAmount > 0;
if (order.amount != newAmount || order.active != newActive) {
bulk.update(order, { amount: newAmount, active: newActive });
}
}
});
}
return () => q.all([bulkUsers.execute(), bulkMarketOrders.execute(), bulkMarketIntershardOrders.execute(), bulkUsersMoney.execute(), bulkTransactions.execute(), bulkUsersResources.execute(), driver.clearMarketIntents()]);
};
//# sourceMappingURL=../../sourcemaps/processor/global-intents/market.js.map