src/prototype_room_creepbuilder.js
'use strict';
const {
CreepPartData,
sortCreepParts,
} = require('./utils_creep_part');
/**
* from string format of creep parts String (tooAngel config format) to creep parts array (screeps format)
*
* @param {string} stringParts creep parts String
* @return {undefined|string[]} creep parts array
*/
function stringToParts(stringParts) {
if (!stringParts || typeof (stringParts) !== 'string') {
return undefined;
}
const arrayParts = [];
for (const partChar of stringParts) {
let part;
switch (partChar) {
case 'M':
part = MOVE;
break;
case 'C':
part = CARRY;
break;
case 'A':
part = ATTACK;
break;
case 'W':
part = WORK;
break;
case 'R':
part = RANGED_ATTACK;
break;
case 'T':
part = TOUGH;
break;
case 'H':
part = HEAL;
break;
case 'K':
part = CLAIM;
break;
default:
// should never enter
part = MOVE;
console.error('stringToParts illegal partChar : ' + part);
}
arrayParts.push(part);
}
return arrayParts;
}
Room.prototype.creepMem = function(role, targetId, targetRoom, level, base) {
return {
role: role,
routing: {
targetRoom: targetRoom || this.name,
targetId: targetId,
},
level: level,
base: base,
time: Game.time,
};
};
/**
* get priority from config for a creep.
*
* @param {Object} object the creep queue object
* @return {Number} the priority for creep
*/
Room.prototype.getPriority = function(object) {
const priority = config.priorityQueue;
const target = object.routing && object.routing.targetRoom;
const age = Game.time - (object.role.time || Game.time);
const ageTerm = age / CREEP_LIFE_TIME * 20;
if (target === this.name) {
return (priority.sameRoom[object.role] || 4) + ageTerm;
} else if (target) {
return (priority.otherRoom[object.role] || 20) + Game.map.getRoomLinearDistance(this.name, target) + ageTerm;
} else {
return 19;
}
};
Room.prototype.spawnCheckForCreate = function() {
if (this.memory.queue.length === 0) {
return false;
}
this.memory.queue = _.sortBy(this.memory.queue, (c) => this.getPriority(c));
const creep = this.memory.queue[0];
if (this.spawnCreateCreep(creep)) {
this.memory.queue.shift();
return true;
}
if (creep.ttl === 0) {
this.log(`Stop spawning creep - TTL reached, skipping: ${creep.role}`);
this.memory.queue.shift();
return false;
}
creep.ttl = creep.ttl || config.creep.queueTtl;
if (this.findSpawnsNotSpawning().length === 0) {
creep.ttl--;
}
return false;
};
/**
* isCreepValid - Checks for the basic structurer
*
* @param {object} creep - The memory of a creep
* @return {boolean} - If the creep is valid
*/
function isCreepValid(creep) {
if (!creep) {
return false;
}
if (!creep.routing) {
return false;
}
if (!creep.role) {
return false;
}
return true;
}
/**
* compareSingleRoutingValue - Compares a single routing value
*
* @param {object} first - The first creep
* @param {object} second - The second creep
* @param {string} routingValue - The routing value
* @return {boolean} - The value are equal or not set
**/
function compareSingleRoutingValue(first, second, routingValue) {
if (first.routing[routingValue] && second.routing[routingValue] && first.routing[routingValue] !== second.routing[routingValue]) {
return false;
}
return true;
}
/**
* isEqualRouting - Compares the routing values
*
* @param {object} first - The first creep
* @param {object} second - The second creep
* @return {boolean} - If routing is equal
*/
function isEqualRouting(first, second) {
if (!compareSingleRoutingValue(first, second, 'targetRoom')) {
return false;
}
if (!compareSingleRoutingValue(first, second, 'targetId')) {
return false;
}
return true;
}
/**
* isSameCreep - Checks if two creeps are matching in role, targetId and
* targetRoom
*
* @param {object} first the first creep
* @param {object} second the second creep
* @return {boolean} both creeps have the same role, targetId and targetRoom
*/
Room.prototype.isSameCreep = function(first, second) {
if (!isCreepValid(first)) {
return false;
}
if (!isCreepValid(second)) {
return false;
}
if (first.role !== second.role) {
return false;
}
if (!isEqualRouting(first, second)) {
return false;
}
return true;
};
Room.prototype.inQueue = function(creepMemory) {
this.memory.queue = this.memory.queue || [];
for (const item of this.memory.queue) {
if (this.isSameCreep(creepMemory, item)) {
return true;
}
}
return false;
};
Room.prototype.getSpawningCreeps = function() {
const creepsSpawning = [];
for (const spawn of this.findMySpawns()) {
if (spawn.spawning) {
creepsSpawning.push(Game.creeps[spawn.spawning.name]);
}
}
return creepsSpawning;
};
Room.prototype.inRoom = function(creepMemory, amount = 1) {
if (amount === 0) {
return false;
}
const creepsSpawning = this.getSpawningCreeps();
const creeps = this.findMyCreeps().concat(creepsSpawning);
this.memory.roles = this.memory.roles || {};
let j = 0;
for (const creep of creeps) {
const item = Memory.creeps[creep.name];
if (!this.isSameCreep(creepMemory, item)) {
continue;
}
j++;
if (j >= amount) {
this.memory.roles[creepMemory.role] = true;
return true;
}
}
return false;
};
/**
* First function call for ask a creep spawn. Add it in queue after check if spawn is allow.
*
* @param {string} role the role of the creeps to spawn.
* @param {number} [amount] the amount of creeps asked for (1).
* @param {string} [targetId] the id of targeted object by creeps (null).
* @param {string} [targetRoom] the targeted room name (base)
* @param {number} [level] the level of creeps. required by some functions.
* @param {string} [base] the room which will spawn creep
* @param {object} [additionalMemory] add this object to creep memory
* @return {number|boolean} if the spawn is not allow, it will return false.
*/
Room.prototype.checkRoleToSpawn = function(role, amount, targetId, targetRoom, level, base, additionalMemory = {}) {
const creepMemory = this.creepMem(role, targetId, targetRoom, level, base);
Object.assign(creepMemory, additionalMemory);
if (this.inQueue(creepMemory) || this.inRoom(creepMemory, amount)) {
return false;
}
base = base || this.name;
if (targetRoom !== base) {
try {
const baseStatus = Game.map.getRoomStatus(base);
const targetStatus = Game.map.getRoomStatus(targetRoom);
if (typeof targetStatus !== 'undefined' && baseStatus !== 'undefined') {
if (targetStatus.status !== baseStatus.status) {
console.log('prototype_room_creepbuild.checkRoleToSpawn: base and targetRoom have different status');
return false;
}
}
} catch (e) {
console.log(`prototype_room_creepbuild.checkRoleToSpawn exception: ${e}`);
}
}
return this.memory.queue.push(creepMemory);
};
/**
* Room.prototype.getPartsStringData used for parse parts as string and return
* parts as array, cost, if spawn is allow and length.
*
* @param {String} stringParts String of body parts. e.g. 'MMWC'
* @param {Number} energyAvailable energy allow for spawn.
* @return {CreepPartData} The parts data
*/
Room.prototype.getPartsStringData = function(stringParts, energyAvailable) {
const ret = new CreepPartData();
if (!stringParts || typeof (stringParts) !== 'string') {
ret.null = true;
return ret;
}
if (stringParts === '') {
return ret;
}
Memory.layoutsCost = {};
ret.parts = stringToParts(stringParts);
ret.len = ret.parts.length;
_.each(ret.parts,
(p) => {
ret.cost += BODYPART_COST[p];
},
);
ret.fail = ret.cost > energyAvailable;
return ret;
};
/**
* findFittingValueFromScale - Finds the fitting value within a scale
*
* @param {object} setting - The scale
* @param {number} value - The value to match the scale
* @return {object} - The value to return
**/
function findFittingValueFromScale(setting, value) {
let foundKey = 0;
if (!setting) {
return;
}
for (const key of Object.keys(setting)) {
if (value < key && foundKey !== 0) {
break;
}
foundKey = key;
}
return setting[foundKey];
}
/**
* isSettingAnObject - Checks is the setting is an object
*
* @param {object} setting - The setting to check
* @return {boolean} - If it is valid
**/
function isSettingAnObject(setting) {
// Not sure when this happens
if (!setting || setting === null) {
return false;
}
if (_.isArray(setting)) {
return false;
}
if (!_.isObject(setting)) {
return false;
}
return true;
}
/**
* Room.prototype.getSettings use for return creep spawn settings
* adapted to room configuration
*
* @param {Object} creep queue's creep spawn basic data
* @return {object}
*/
Room.prototype.getSettings = function(creep) {
const role = creep.role;
const updateSettings = roles[role].updateSettings && roles[role].updateSettings(this, creep);
const settings = _.merge({}, roles[role].settings, updateSettings);
if (!settings) {
this.log('try to spawn ', role, ' but settings are not done. Abort spawn');
return;
}
const param = settings.param;
return _.mapValues(settings, (setting) => {
if (!param) {
return setting;
}
for (const parameter of param) {
if (!isSettingAnObject(setting)) {
return setting;
}
const valueForI = _.get(this, parameter, 1);
const value = findFittingValueFromScale(setting, valueForI);
if (value === undefined) {
break;
}
setting = value;
}
return setting;
});
};
/**
* Transform a string using an array char amount. e.g. ('WMC', [1,2,3]) ==> 'WMMCCC'
*
* @param {String} input the input parts as string.
* @param {Array} amount the amount of each char needed.
* @return {String} the new parts string
*/
Room.prototype.applyAmount = function(input, amount) {
if (!input) {
return '';
}
if (amount === undefined) {
return input;
}
let output = '';
_.forEach(amount, (element, index) => {
output += _.repeat(input.charAt(index), element);
});
return output;
};
/**
* Sort body parts with the same order used in layout. Parts not in layout are last ones.
*
* @param {string[]} parts the parts array to sort.
* @param {CreepPartData} layout the base layout.
* @return {string[]} sorted array.
*/
Room.prototype.sortParts = function(parts, layout) {
return sortCreepParts(parts, layout);
};
Room.prototype.getPartConfigMaxBodyLength = function(prefixString, suffixString) {
let maxBodyLength = MAX_CREEP_SIZE;
if (prefixString) {
maxBodyLength -= prefixString.length;
}
if (suffixString) {
maxBodyLength -= suffixString.length;
}
return maxBodyLength;
};
/**
* getMaxRepeat
*
* @param {int} energyAvailable
* @param {object} layout
* @param {int} maxBodyLength
* @param {int} maxLayoutAmount
* @return {int}
*/
function getMaxRepeat(energyAvailable, layout, maxBodyLength, maxLayoutAmount) {
let maxRepeat = Math.floor(Math.min(energyAvailable / layout.cost, maxBodyLength / layout.len));
if (layout.len === 0) {
maxRepeat = 0;
}
if (maxLayoutAmount !== undefined) {
maxRepeat = Math.min(maxLayoutAmount, maxRepeat);
}
return maxRepeat;
}
/**
* Room.prototype.getPartsConfig use for generate adapted body
*
* @param {Collection} creep queue's creep spawn basic data
* @return {string[]} The part Config or false
*/
/* eslint-disable complexity */
Room.prototype.getPartConfig = function(creep) {
// TODO this needs to be rethought and rebuild (more simple)
let energyAvailable = this.energyAvailable;
const settings = this.getSettings(creep);
const {
prefixString,
amount,
maxLayoutAmount,
suffixString,
fillTough,
} = settings;
let layoutString = settings.layoutString;
const maxBodyLength = this.getPartConfigMaxBodyLength(prefixString, suffixString);
const prefix = this.getPartsStringData(prefixString, energyAvailable);
if (prefix.fail) {
return false;
}
let parts = prefix.parts || [];
energyAvailable -= prefix.cost || 0;
layoutString = this.applyAmount(layoutString, amount);
const layout = this.getPartsStringData(layoutString, energyAvailable);
if (layout.fail || layout.null) {
return false;
}
const maxRepeat = getMaxRepeat(energyAvailable, layout, maxBodyLength, maxLayoutAmount);
if (maxRepeat > 0) {
parts = parts.concat(_.flatten(_.fill(new Array(maxRepeat), layout.parts)));
}
energyAvailable -= layout.cost * maxRepeat;
const suffix = this.getPartsStringData(suffixString, energyAvailable);
if (!suffix.fail && !suffix.null) {
parts = parts.concat(suffix.parts || []);
energyAvailable -= suffix.cost || 0;
}
if (fillTough && parts.length < MAX_CREEP_SIZE) {
const tough = this.getPartsStringData('T', energyAvailable);
if (!tough.fail && !tough.null) {
const maxTough = Math.floor(Math.min(energyAvailable / tough.cost, MAX_CREEP_SIZE - parts.length, parts.filter((p) => p === MOVE).length));
parts = _.flatten(_.fill(new Array(maxTough), tough.parts)).concat(parts);
}
}
return config.creep.sortParts ? this.sortParts(parts, layout) : parts;
};
/* eslint-enable complexity */
Room.prototype.getCreepConfig = function(creep) {
const role = creep.role;
const unit = roles[role];
if (!unit) {
this.log('Can not find role: ' + role + ' creep_' + role);
return false;
}
const id = _.random(1, 999999);
const name = role + '-' + id;
const body = this.getPartConfig(creep);
if (!body) {
return;
}
const memory = Object.assign({}, creep, {
number: id,
step: 0,
base: creep.base || this.name,
born: Game.time,
killPrevious: unit.killPrevious,
flee: unit.flee,
buildRoad: unit.buildRoad,
routing: creep.routing || {targetRoom: this.name},
});
const opts = {
memory: memory,
};
if (!this.memory.misplacedSpawn && Game.time > 500) {
// On misplaced spawn the top field could be blocked
// On spawning the first universal the `misplacedSpawn` is not necessarily
// set, so checking for `Game.time`
// The room layout expects to spawn in top direction, if this should be extended
// the room layout needs to be improved first.
opts.directions = [TOP];
}
return {
body: body,
name: name,
opts: opts,
};
};
/**
* prepareBoosting
*
* @param {object} room
* @param {object} creep
* @param {object} config
* @return {boolean}
*/
function prepareBoosting(room, creep, config) {
if (!room.terminal || !room.terminal.my) {
return false;
}
const unit = roles[creep.role];
if (!unit.boostActions) {
return false;
}
const boostConfig = {
resources: [],
time: Game.time,
};
const possibleBoostsParts = [...new Set(config.body)];
for (const part of possibleBoostsParts) {
for (const mineral of Object.keys(BOOSTS[part])) {
for (const action of Object.keys(BOOSTS[part][mineral])) {
if (unit.boostActions.includes(action)) {
if (room.terminal.store[mineral] > 30) {
boostConfig.resources.push(mineral);
}
}
}
}
}
if (boostConfig.resources.length === 0) {
return false;
}
room.debugLog('boosts', `boostConfig: ${JSON.stringify(boostConfig)}`);
room.memory.boosts = room.memory.boosts || {};
room.memory.boosts[config.name] = boostConfig;
}
/**
* Room.prototype.spawnCreateCreep use for launch spawn of first creep in queue.
*
* @param {Collection} creep Object with queue's creep data.
* @return {boolean|void}
*/
Room.prototype.spawnCreateCreep = function(creep) {
const spawns = this.findSpawnsNotSpawning();
if (spawns.length === 0) {
return false;
}
const config = this.getCreepConfig(creep);
if (!config) {
return false;
}
for (const spawn of spawns) {
let body = config.body;
if (body.length > 50) {
this.log(`spawnCreateCreep body too long: ${body.length} ${config.name} ${config.opts}`);
body = body.splice(50);
}
const returnCode = spawn.spawnCreep(config.body, config.name, config.opts);
if (returnCode !== OK) {
this.log(`spawnCreateCreep: ${returnCode} ${JSON.stringify(config)}`);
continue;
}
prepareBoosting(this, creep, config);
brain.stats.modifyRoleAmount(creep.role, 1);
return true;
}
return false;
};
Room.prototype.checkAndSpawnSourcer = function() {
const sources = this.findSources();
let source;
for (source of sources) {
const sourcers = this.findSameSourcer(source);
if (sourcers.length === 0) {
// this.log(source.id);
this.checkRoleToSpawn('sourcer', 1, source.id, this.name);
}
}
};