@screeps/engine
Version:
This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.
1,035 lines (923 loc) • 35.6 kB
JavaScript
var _ = require('lodash');
var driver, C, offsetsByDirection;
exports.getDriver = function getDriver() {
driver = typeof process != 'undefined' && process.env.DRIVER_MODULE ?
require(process.env.DRIVER_MODULE) :
require('./core/index');
C = driver.constants;
offsetsByDirection = {
[C.TOP]: [0,-1],
[C.TOP_RIGHT]: [1,-1],
[C.RIGHT]: [1,0],
[C.BOTTOM_RIGHT]: [1,1],
[C.BOTTOM]: [0,1],
[C.BOTTOM_LEFT]: [-1,1],
[C.LEFT]: [-1,0],
[C.TOP_LEFT]: [-1,-1]
};
return driver;
};
exports.fetchXYArguments = function(firstArg, secondArg, globals) {
var x,y, roomName;
if(_.isUndefined(secondArg) || !_.isNumber(secondArg)) {
if(!_.isObject(firstArg)) {
return [undefined,undefined,undefined];
}
if(firstArg instanceof globals.RoomPosition) {
x = firstArg.x;
y = firstArg.y;
roomName = firstArg.roomName;
}
if(firstArg.pos && (firstArg.pos instanceof globals.RoomPosition)) {
x = firstArg.pos.x;
y = firstArg.pos.y;
roomName = firstArg.pos.roomName;
}
}
else {
x = firstArg;
y = secondArg;
}
if(_.isNaN(x)) {
x = undefined;
}
if(_.isNaN(y)) {
y = undefined;
}
return [x,y,roomName];
};
exports.getDirection = function(dx, dy) {
var adx = Math.abs(dx), ady = Math.abs(dy);
if(adx > ady*2) {
if(dx > 0) {
return C.RIGHT;
}
else {
return C.LEFT;
}
}
else if(ady > adx*2) {
if(dy > 0) {
return C.BOTTOM;
}
else {
return C.TOP;
}
}
else {
if(dx > 0 && dy > 0) {
return C.BOTTOM_RIGHT;
}
if(dx > 0 && dy < 0) {
return C.TOP_RIGHT;
}
if(dx < 0 && dy > 0) {
return C.BOTTOM_LEFT;
}
if(dx < 0 && dy < 0) {
return C.TOP_LEFT;
}
}
};
exports.getOffsetsByDirection = function(direction) {
if(!offsetsByDirection[direction]) {
try {
throw new Error();
}
catch(e) {
console.error('Wrong move direction',JSON.stringify(direction), JSON.stringify(offsetsByDirection), e.stack);
}
}
return offsetsByDirection[direction];
};
exports.calcCreepCost = function(body) {
var result = 0;
body.forEach((i) => {
if(_.isObject(i)) {
result += C.BODYPART_COST[i.type];
}
else {
result += C.BODYPART_COST[i];
}
});
return result;
};
exports.checkConstructionSite = function(objects, structureType, x, y) {
var borderTiles;
if(structureType != 'road' && (x == 1 || x == 48 || y == 1 || y == 48)) {
if(x == 1) borderTiles = [[0,y-1],[0,y],[0,y+1]];
if(x == 48) borderTiles = [[49,y-1],[49,y],[49,y+1]];
if(y == 1) borderTiles = [[x-1,0],[x,0],[x+1,0]];
if(y == 48) borderTiles = [[x-1,49],[x,49],[x+1,49]];
}
if(_.isString(objects) || objects instanceof Uint8Array) {
if(borderTiles) {
for(var i in borderTiles) {
if(!exports.checkTerrain(objects, borderTiles[i][0], borderTiles[i][1], C.TERRAIN_MASK_WALL)) {
return false;
}
}
}
if(structureType == 'extractor') {
return true;
}
if(exports.checkTerrain(objects, x, y, C.TERRAIN_MASK_WALL)) {
return false;
}
return true;
}
if(objects && _.isArray(objects[0]) && _.isString(objects[0][0])) {
if(borderTiles) {
for(var i in borderTiles) {
if(!(objects[borderTiles[i][1]][borderTiles[i][0]] & C.TERRAIN_MASK_WALL)) {
return false;
}
}
}
if(structureType == 'extractor') {
return true;
}
if(objects[y][x] & C.TERRAIN_MASK_WALL) {
return false;
}
return true;
}
if(_.any(objects, {x, y, type: structureType})) {
return false;
}
if(_.any(objects, {x, y, type: 'constructionSite'})) {
return false;
}
if(structureType == 'extractor') {
return _.any(objects, {x, y, type: 'mineral'}) && !_.any(objects, {x, y, type: 'extractor'});
}
if(structureType != 'rampart' && structureType != 'road' &&
_.any(objects, (i) => i.x == x && i.y == y && i.type != 'rampart' && i.type != 'road' && C.CONSTRUCTION_COST[i.type])) {
return false;
}
if(x <= 0 || y <= 0 || x >= 49 || y >= 49) {
return false;
}
return true;
};
exports.getDiff = function(oldData, newData) {
function getIndex(data) {
var index = {};
_.forEach(data, (obj) => index[obj._id] = obj);
return index;
}
var result = {},
oldIndex = getIndex(oldData),
newIndex = getIndex(newData);
_.forEach(oldData, (obj) => {
if(newIndex[obj._id]) {
var newObj = newIndex[obj._id];
var objDiff = result[obj._id] = {};
for(var key in obj) {
if(key == '_id') {
continue;
}
if(_.isUndefined(newObj[key])) {
objDiff[key] = null;
}
else if((typeof obj[key]) != (typeof newObj[key]) || obj[key] && !newObj[key]) {
objDiff[key] = newObj[key];
}
else if(_.isObject(obj[key])) {
objDiff[key] = {};
for (var subkey in obj[key]) {
if (!_.isEqual(obj[key][subkey], newObj[key][subkey])) {
objDiff[key][subkey] = newObj[key][subkey];
}
}
for (var subkey in newObj[key]) {
if (_.isUndefined(obj[key][subkey])) {
objDiff[key][subkey] = newObj[key][subkey];
}
}
if (!_.size(objDiff[key])) {
delete result[obj._id][key];
}
}
else if(!_.isEqual(obj[key], newObj[key])) {
objDiff[key] = newObj[key];
}
}
for(var key in newObj) {
if(_.isUndefined(obj[key])) {
objDiff[key] = newObj[key];
}
}
if(!_.size(objDiff)) {
delete result[obj._id];
}
}
else {
result[obj._id] = null;
}
});
_.forEach(newData, (obj) => {
if(!oldIndex[obj._id]) {
result[obj._id] = obj;
}
});
return result;
};
exports.encodeTerrain = function(terrain) {
var result = '';
for(var y=0; y<50; y++) {
for(var x=0; x<50; x++) {
var objects = _.filter(terrain, {x,y}),
code = 0;
if(_.any(objects, {type: 'wall'})) {
code = code | C.TERRAIN_MASK_WALL;
}
if(_.any(objects, {type: 'swamp'})) {
code = code | C.TERRAIN_MASK_SWAMP;
}
result = result + code;
}
}
return result;
};
exports.decodeTerrain = function(items) {
var result = [];
for(var i in items) {
if(items[i].type != 'terrain') {
continue;
}
for (var y = 0; y < 50; y++) {
for (var x = 0; x < 50; x++) {
var code = items[i].terrain.charAt(y * 50 + x);
if (code & C.TERRAIN_MASK_WALL) {
result.push({room: items[i].room, x, y, type: 'wall'});
}
if (code & C.TERRAIN_MASK_SWAMP) {
result.push({room: items[i].room, x, y, type: 'swamp'});
}
}
}
}
return result;
};
exports.decodeTerrainByRoom = function(items) {
var result = {
spatial: {}
};
for(var i in items) {
if(items[i].type != 'terrain') {
continue;
}
result[items[i].room] = result[items[i].room] || [];
result.spatial[items[i].room] = new Array(50);
for (var y = 0; y < 50; y++) {
result.spatial[items[i].room][y] = new Array(50);
for (var x = 0; x < 50; x++) {
var code = items[i].terrain.charAt(y * 50 + x);
/*if (code & C.TERRAIN_MASK_WALL) {
result[items[i].room].push({x, y, type: 'wall'});
}
if (code & C.TERRAIN_MASK_SWAMP) {
result[items[i].room].push({x, y, type: 'swamp'});
}*/
result.spatial[items[i].room][y][x] = code;
}
}
}
return result;
};
exports.checkTerrain = function(terrain, x, y, mask) {
var code = terrain instanceof Uint8Array ? terrain[y*50+x] : Number(terrain.charAt(y*50 + x));
return (code & mask) > 0;
};
exports.checkControllerAvailability = function(type, roomObjects, roomController, offset) {
var rcl = 0;
if(_.isObject(roomController) && roomController.level && (roomController.user || roomController.owner)) {
rcl = roomController.level;
}
if(_.isNumber(roomController)) {
rcl = roomController;
}
offset = offset || 0;
var structuresCnt = _(roomObjects).filter((i) => i.type == type || i.type == 'constructionSite' && i.structureType == type).size();
var availableCnt = C.CONTROLLER_STRUCTURES[type][rcl] + offset;
return structuresCnt < availableCnt;
};
exports.getRoomNameFromXY = function(x,y) {
if(x < 0) {
x = 'W'+(-x-1);
}
else {
x = 'E'+(x);
}
if(y < 0) {
y = 'N'+(-y-1);
}
else {
y = 'S'+(y);
}
return ""+x+y;
};
exports.roomNameToXY = function(name) {
name = name.toUpperCase();
var match = name.match(/^(\w)(\d+)(\w)(\d+)$/);
if(!match) {
return [undefined, undefined];
}
var [,hor,x,ver,y] = match;
if(hor == 'W') {
x = -x-1;
}
else {
x = +x;
}
if(ver == 'N') {
y = -y-1;
}
else {
y = +y;
}
return [x,y];
};
exports.comparatorDistance = function(target) {
if(target.pos) target = target.pos;
return function(a,b) {
if(a.pos) a = a.pos;
if(b.pos) b = b.pos;
var da = Math.max(Math.abs(a.x - target.x), Math.abs(a.y - target.y));
var db = Math.max(Math.abs(b.x - target.x), Math.abs(b.y - target.y));
return da - db;
}
};
exports.storeIntents = function(userId, userIntents, userRuntimeData) {
var intents = {};
var driver = exports.getDriver();
for(var i in userIntents) {
if(i == 'notify') {
intents.notify = [];
if(_.isArray(userIntents.notify)) {
userIntents.notify.forEach((notifyItem) => {
intents.notify.push({
message: (""+notifyItem.message).substring(0,1000),
groupInterval: +notifyItem.groupInterval
})
})
}
continue;
}
if(i == 'room') {
var roomIntentsResult = userIntents.room;
if(roomIntentsResult.createFlag) {
_.forEach(roomIntentsResult.createFlag, (iCreateFlag) => {
intents[iCreateFlag.roomName] =
intents[iCreateFlag.roomName] || {};
var roomIntents = intents[iCreateFlag.roomName].room =
intents[iCreateFlag.roomName].room || {};
roomIntents.createFlag = roomIntents.createFlag || [];
roomIntents.createFlag.push({
x: parseInt(iCreateFlag.x),
y: parseInt(iCreateFlag.y),
name: ""+iCreateFlag.name,
color: +iCreateFlag.color,
secondaryColor: +iCreateFlag.secondaryColor,
roomName: iCreateFlag.roomName
})
});
}
if(roomIntentsResult.createConstructionSite) {
_.forEach(roomIntentsResult.createConstructionSite, (iCreateConstructionSite) => {
intents[iCreateConstructionSite.roomName] =
intents[iCreateConstructionSite.roomName] || {};
var roomIntents = intents[iCreateConstructionSite.roomName].room =
intents[iCreateConstructionSite.roomName].room || {};
roomIntents.createConstructionSite = roomIntents.createConstructionSite || [];
roomIntents.createConstructionSite.push({
x: parseInt(iCreateConstructionSite.x),
y: parseInt(iCreateConstructionSite.y),
structureType: ""+iCreateConstructionSite.structureType,
name: ""+iCreateConstructionSite.name,
roomName: ""+iCreateConstructionSite.roomName
});
});
}
if(roomIntentsResult.removeConstructionSite) {
_.forEach(roomIntentsResult.removeConstructionSite, (iRemoveConstructionSite) => {
intents[iRemoveConstructionSite.roomName] =
intents[iRemoveConstructionSite.roomName] || {};
var roomIntents = intents[iRemoveConstructionSite.roomName].room =
intents[iRemoveConstructionSite.roomName].room || {};
roomIntents.removeConstructionSite = roomIntents.removeConstructionSite || [];
roomIntents.removeConstructionSite.push({
roomName: ""+iRemoveConstructionSite.roomName,
id: ""+iRemoveConstructionSite.id
});
});
}
if(roomIntentsResult.destroyStructure) {
_.forEach(roomIntentsResult.destroyStructure, (iDestroyStructure) => {
intents[iDestroyStructure.roomName] =
intents[iDestroyStructure.roomName] || {};
var roomIntents = intents[iDestroyStructure.roomName].room =
intents[iDestroyStructure.roomName].room || {};
roomIntents.destroyStructure = roomIntents.destroyStructure || [];
roomIntents.destroyStructure.push({
roomName: ""+iDestroyStructure.roomName,
id: ""+iDestroyStructure.id
});
});
}
if(roomIntentsResult.removeFlag) {
_.forEach(roomIntentsResult.removeFlag, (iRemoveFlag) => {
intents[iRemoveFlag.roomName] =
intents[iRemoveFlag.roomName] || {};
var roomIntents = intents[iRemoveFlag.roomName].room =
intents[iRemoveFlag.roomName].room || {};
roomIntents.removeFlag = roomIntents.removeFlag || [];
roomIntents.removeFlag.push({
roomName: ""+iRemoveFlag.roomName,
name: ""+iRemoveFlag.name
});
});
}
continue;
}
if(i == 'market') {
var marketIntentsResult = userIntents.market;
if(marketIntentsResult.createOrder) {
_.forEach(marketIntentsResult.createOrder, (iCreateOrder) => {
intents.market = intents.market || {};
intents.market.createOrder = intents.market.createOrder || [];
intents.market.createOrder.push({
type: ""+iCreateOrder.type,
resourceType: ""+iCreateOrder.resourceType,
price: parseInt(iCreateOrder.price*1000),
totalAmount: parseInt(iCreateOrder.totalAmount),
roomName: iCreateOrder.roomName ? ""+iCreateOrder.roomName : undefined
})
});
}
if(marketIntentsResult.cancelOrder) {
_.forEach(marketIntentsResult.cancelOrder, (iCancelOrder) => {
intents.market = intents.market || {};
intents.market.cancelOrder = intents.market.cancelOrder || [];
intents.market.cancelOrder.push({orderId: ""+iCancelOrder.orderId});
});
}
if(marketIntentsResult.changeOrderPrice) {
_.forEach(marketIntentsResult.changeOrderPrice, (iChangeOrderPrice) => {
intents.market = intents.market || {};
intents.market.changeOrderPrice = intents.market.changeOrderPrice || [];
intents.market.changeOrderPrice.push({
orderId: ""+iChangeOrderPrice.orderId,
newPrice: parseInt(iChangeOrderPrice.newPrice*1000),
});
});
}
if(marketIntentsResult.extendOrder) {
_.forEach(marketIntentsResult.extendOrder, (iExtendOrder) => {
intents.market = intents.market || {};
intents.market.extendOrder = intents.market.extendOrder || [];
intents.market.extendOrder.push({
orderId: ""+iExtendOrder.orderId,
addAmount: parseInt(iExtendOrder.addAmount),
});
});
}
if(marketIntentsResult.deal) {
_.forEach(marketIntentsResult.deal, (iDeal) => {
intents.market = intents.market || {};
intents.market.deal = intents.market.deal || [];
intents.market.deal.push({
orderId: ""+iDeal.orderId,
amount: parseInt(iDeal.amount),
targetRoomName: ""+iDeal.targetRoomName
});
});
}
continue;
}
var object = userRuntimeData.userObjects[i] || userRuntimeData.roomObjects[i]
if(!object) {
continue;
}
var objectIntentsResult = userIntents[i];
intents[object.room] = intents[object.room] || {};
var objectIntents = intents[object.room][i] = {};
// transfer can be invoked by another player
if(objectIntentsResult.transfer) {
objectIntents.transfer = {
id: ""+objectIntentsResult.transfer.id,
amount: parseInt(objectIntentsResult.transfer.amount),
resourceType: ""+objectIntentsResult.transfer.resourceType
};
}
if(objectIntentsResult.move) {
objectIntents.move = {
direction: parseInt(objectIntentsResult.move.direction)
};
}
if(objectIntentsResult.harvest) {
objectIntents.harvest = {
id: ""+objectIntentsResult.harvest.id
};
}
if(objectIntentsResult.attack) {
objectIntents.attack = {
id: ""+objectIntentsResult.attack.id,
x: parseInt(objectIntentsResult.attack.x),
y: parseInt(objectIntentsResult.attack.y)
};
}
if(objectIntentsResult.rangedAttack) {
objectIntents.rangedAttack = {
id: ""+objectIntentsResult.rangedAttack.id
};
}
if(objectIntentsResult.rangedMassAttack) {
objectIntents.rangedMassAttack = {};
}
if(objectIntentsResult.heal) {
objectIntents.heal = {
id: ""+objectIntentsResult.heal.id,
x: parseInt(objectIntentsResult.heal.x),
y: parseInt(objectIntentsResult.heal.y)
};
}
if(objectIntentsResult.rangedHeal) {
objectIntents.rangedHeal = {
id: ""+objectIntentsResult.rangedHeal.id
};
}
if(objectIntentsResult.repair) {
objectIntents.repair = {
id: ""+objectIntentsResult.repair.id,
x: parseInt(objectIntentsResult.repair.x),
y: parseInt(objectIntentsResult.repair.y)
};
}
if(objectIntentsResult.build) {
objectIntents.build = {
id: ""+objectIntentsResult.build.id,
x: parseInt(objectIntentsResult.build.x),
y: parseInt(objectIntentsResult.build.y)
};
}
if(objectIntentsResult.transferEnergy) {
objectIntents.transferEnergy = {
id: ""+objectIntentsResult.transferEnergy.id,
amount: parseInt(objectIntentsResult.transferEnergy.amount)
};
}
if(objectIntentsResult.drop) {
objectIntents.drop = {
amount: parseInt(objectIntentsResult.drop.amount),
resourceType: ""+objectIntentsResult.drop.resourceType
};
}
if(objectIntentsResult.pickup) {
objectIntents.pickup = {
id: ""+objectIntentsResult.pickup.id
};
}
if(objectIntentsResult.createCreep) {
objectIntents.createCreep = {
name: ""+objectIntentsResult.createCreep.name,
body: _.filter(objectIntentsResult.createCreep.body, (i) => _.contains(C.BODYPARTS_ALL, i)),
energyStructures: objectIntentsResult.createCreep.energyStructures
};
}
if(objectIntentsResult.renewCreep) {
objectIntents.renewCreep = {
id: ""+objectIntentsResult.renewCreep.id
};
}
if(objectIntentsResult.recycleCreep) {
objectIntents.recycleCreep = {
id: ""+objectIntentsResult.recycleCreep.id
};
}
if(objectIntentsResult.suicide) {
objectIntents.suicide = {};
}
if(objectIntentsResult.remove) {
objectIntents.remove = {};
}
if(objectIntentsResult.unclaim) {
objectIntents.unclaim = {};
}
if(objectIntentsResult.say) {
objectIntents.say = {
message: objectIntentsResult.say.message.substring(0,10),
isPublic: !!objectIntentsResult.say.isPublic
};
}
if(objectIntentsResult.claimController) {
objectIntents.claimController = {
id: ""+objectIntentsResult.claimController.id
};
}
if(objectIntentsResult.attackController) {
objectIntents.attackController = {
id: ""+objectIntentsResult.attackController.id
};
}
if(objectIntentsResult.unclaimController) {
objectIntents.unclaimController = {
id: ""+objectIntentsResult.unclaimController.id
};
}
if(objectIntentsResult.upgradeController) {
objectIntents.upgradeController = {
id: ""+objectIntentsResult.upgradeController.id
};
}
if(objectIntentsResult.reserveController) {
objectIntents.reserveController = {
id: ""+objectIntentsResult.reserveController.id
};
}
if(objectIntentsResult.notifyWhenAttacked) {
objectIntents.notifyWhenAttacked = {
enabled: !!objectIntentsResult.notifyWhenAttacked.enabled
};
}
if(objectIntentsResult.setPosition) {
objectIntents.setPosition = {
x: parseInt(objectIntentsResult.setPosition.x),
y: parseInt(objectIntentsResult.setPosition.y),
roomName: ""+objectIntentsResult.setPosition.roomName
};
}
if(objectIntentsResult.setColor) {
objectIntents.setColor = {
color: ""+objectIntentsResult.setColor.color,
secondaryColor: ""+objectIntentsResult.setColor.secondaryColor
};
}
if(objectIntentsResult.destroy) {
objectIntents.destroy = {};
}
if(objectIntentsResult.observeRoom) {
objectIntents.observeRoom = {
roomName: ""+objectIntentsResult.observeRoom.roomName
};
}
if(objectIntentsResult.processPower) {
objectIntents.processPower = {};
}
if(objectIntentsResult.dismantle) {
objectIntents.dismantle = {
id: ""+objectIntentsResult.dismantle.id
};
}
if(objectIntentsResult.runReaction) {
objectIntents.runReaction = {
lab1: ""+objectIntentsResult.runReaction.lab1,
lab2: ""+objectIntentsResult.runReaction.lab2
};
}
if(objectIntentsResult.boostCreep) {
objectIntents.boostCreep = {
id: ""+objectIntentsResult.boostCreep.id,
bodyPartsCount: parseInt(objectIntentsResult.boostCreep.bodyPartsCount)
};
}
if(objectIntentsResult.send) {
objectIntents.send = {
targetRoomName: ""+objectIntentsResult.send.targetRoomName,
resourceType: ""+objectIntentsResult.send.resourceType,
amount: parseInt(objectIntentsResult.send.amount),
description: (""+(objectIntentsResult.send.description||"")).substring(0,100)
};
}
if(objectIntentsResult.launchNuke) {
objectIntents.launchNuke = {
roomName: ""+objectIntentsResult.launchNuke.roomName,
x: parseInt(objectIntentsResult.launchNuke.x),
y: parseInt(objectIntentsResult.launchNuke.y)
};
}
if(objectIntentsResult.setPublic) {
objectIntents.setPublic = {
isPublic: !!objectIntentsResult.setPublic.isPublic
};
}
if(objectIntentsResult.withdraw) {
objectIntents.withdraw = {
id: ""+objectIntentsResult.withdraw.id,
amount: parseInt(objectIntentsResult.withdraw.amount),
resourceType: ""+objectIntentsResult.withdraw.resourceType
};
}
if(objectIntentsResult.activateSafeMode) {
objectIntents.activateSafeMode = {};
}
if(objectIntentsResult.generateSafeMode) {
objectIntents.generateSafeMode = {
id: ""+objectIntentsResult.generateSafeMode.id,
};
}
if(objectIntentsResult.signController) {
objectIntents.signController = {
id: ""+objectIntentsResult.signController.id,
sign: (""+objectIntentsResult.signController.sign).substring(0,100)
};
}
for(var iCustomType in driver.config.customIntentTypes) {
if(objectIntentsResult[iCustomType]) {
objectIntents[iCustomType] = {};
for(var prop in driver.config.customIntentTypes[iCustomType]) {
switch(driver.config.customIntentTypes[iCustomType][prop]) {
case 'string': {
objectIntents[iCustomType][prop] = "" + objectIntentsResult[iCustomType][prop];
break;
}
case 'number': {
objectIntents[iCustomType][prop] = +objectIntentsResult[iCustomType][prop];
break;
}
case 'boolean': {
objectIntents[iCustomType][prop] = !!objectIntentsResult[iCustomType][prop];
break;
}
}
}
}
}
}
return intents;
}
exports.sendAttackingNotification = function(target, roomController) {
var driver = exports.getDriver();
var labelText;
if(target.type == 'creep') {
labelText = 'creep '+target.name;
}
else if(target.type == 'spawn') {
labelText = 'spawn '+target.name;
}
else {
labelText = `${target.type} #${target._id}`;
}
var user = target.user ? target.user : roomController ? roomController.user : null;
if(user) {
driver.sendNotification(user, `Your ${labelText} in room ${target.room} is under attack!`);
}
};
exports.checkStructureAgainstController = function(object, roomObjects, roomController) {
// owner-less objects are always active
if(!object.user) {
return true;
}
// eliminate some other easy cases
if(!roomController || roomController.level < 1 || roomController.user !== object.user) {
return false;
}
let allowedRemaining = C.CONTROLLER_STRUCTURES[object.type][roomController.level];
if(allowedRemaining === 0) {
return false;
}
// if only one object ever allowed, this is it
if(C.CONTROLLER_STRUCTURES[object.type][8] === 1) {
return allowedRemaining !== 0;
}
// Scan through the room objects of the same type and count how many are closer.
let foundSelf = false;
let objectDist = Math.max(Math.abs(object.x - roomController.x), Math.abs(object.y - roomController.y));
let objectIds = _.keys(roomObjects);
for (let i = 0; i < objectIds.length; i++) {
let compareObj = roomObjects[objectIds[i]];
if(compareObj.type === object.type && compareObj.user === object.user) {
let compareDist = Math.max(Math.abs(compareObj.x - roomController.x), Math.abs(compareObj.y - roomController.y));
if(compareDist < objectDist) {
allowedRemaining--;
if (allowedRemaining === 0) {
return false;
}
} else if(!foundSelf && compareDist === objectDist) {
// Objects of equal distance that are discovered before we scan over the selected object are considered closer
if(object === compareObj) {
foundSelf = true;
} else {
allowedRemaining--;
if (allowedRemaining === 0) {
return false;
}
}
}
}
}
return true;
};
exports.defineGameObjectProperties = function(obj, dataFn, properties) {
var propertiesInfo = {};
for(var name in properties) {
eval(`
propertiesInfo['${name}'] = {
enumerable: true,
get() {
if(!this['_${name}']) {
this['_${name}'] = properties['${name}'](dataFn(this.id));
}
return this['_${name}'];
}
}`);
}
Object.defineProperties(obj, propertiesInfo);
obj.toJSON = function() {
var result = {};
for(var i in this) {
if(i[0] == '_' || _.contains(['toJSON','toString'],i)) {
continue;
}
result[i] = this[i];
}
return result;
}
};
exports.serializePath = function(path) {
if(!_.isArray(path)) {
throw new Error('path is not an array');
}
var result = '';
if(!path.length) {
return result;
}
if(path[0].x < 0 || path[0].y < 0) {
throw new Error('path coordinates cannot be negative');
}
result += path[0].x > 9 ? path[0].x : '0'+path[0].x;
result += path[0].y > 9 ? path[0].y : '0'+path[0].y;
for(var i=0; i<path.length; i++) {
result += path[i].direction;
}
return result;
};
exports.deserializePath = function(path) {
if(!_.isString(path)) {
throw new Error('`path` is not a string');
}
var result = [];
if(!path.length) {
return result;
}
var x,y, direction, dx, dy;
x = parseInt(path.substring(0, 2));
y = parseInt(path.substring(2, 4));
if(_.isNaN(x) || _.isNaN(y)) {
throw new Error('`path` is not a valid serialized path string');
}
for (var i = 4; i < path.length; i++) {
direction = parseInt(path.charAt(i));
if(!offsetsByDirection[direction]) {
throw new Error('`path` is not a valid serialized path string');
}
dx = offsetsByDirection[direction][0];
dy = offsetsByDirection[direction][1];
if (i > 4) {
x += dx;
y += dy;
}
result.push({
x, y,
dx, dy,
direction
});
}
return result;
};
exports.calcResources = function(object) {
return _.sum(C.RESOURCES_ALL, i => typeof object[i] == 'object' ? object[i].amount : (object[i] || 0));
};
exports.calcBodyEffectiveness = function(body, bodyPartType, methodName, basePower, withoutOldHits) {
var power = 0;
body.forEach(i => {
if(!(i.hits || !withoutOldHits && i._oldHits) || i.type != bodyPartType) {
return;
}
var iPower = basePower;
if(i.boost && C.BOOSTS[bodyPartType][i.boost] && C.BOOSTS[bodyPartType][i.boost][methodName]) {
iPower *= C.BOOSTS[bodyPartType][i.boost][methodName];
}
power += iPower;
});
return power;
};
exports.calcRoomsDistance = function(room1, room2, continuous) {
var [x1,y1] = exports.roomNameToXY(room1);
var [x2,y2] = exports.roomNameToXY(room2);
var dx = Math.abs(x2-x1);
var dy = Math.abs(y2-y1);
if(continuous) {
var worldSize = driver.getWorldSize();
dx = Math.min(worldSize - dx, dx);
dy = Math.min(worldSize - dy, dy);
}
return Math.max(dx, dy);
};
exports.calcTerminalEnergyCost = function(amount, range) {
return Math.ceil(amount * (1 - Math.exp(-range / 30)))
};
exports.calcNeededGcl = function(gclLevel) {
return C.GCL_MULTIPLY * Math.pow(gclLevel-1, C.GCL_POW);
};