@screeps/engine
Version:
This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.
493 lines (422 loc) • 17 kB
JavaScript
const _ = require('lodash'),
utils = require('../../../../utils'),
driver = utils.getDriver(),
C = driver.constants,
strongholds = driver.strongholds,
defence = require('./defence'),
creeps = require('./creeps');
const towerRefillChance = [0,0.01,0.1,0.3,1,1];
const range = function(a, b) {
if(
_.isUndefined(a) || _.isUndefined(a.x) || _.isUndefined(a.y) || _.isUndefined(a.room) ||
_.isUndefined(b) || _.isUndefined(b.x) || _.isUndefined(b.y) || _.isUndefined(b.room) ||
a.room != b.room) {
return Infinity;
}
return Math.max(Math.abs(a.x-b.x), Math.abs(a.y-b.y));
};
const deployStronghold = function deployStronghold(context) {
const { scope, core, ramparts, bulk, gameTime } = context;
const { roomObjects } = scope;
if(core.deployTime && (core.deployTime <= (1+gameTime))) {
const duration = Math.round(C.STRONGHOLD_DECAY_TICKS * (0.9 + Math.random() * 0.2));
const decayTime = gameTime + duration;
core.effects.push({
effect: C.EFFECT_COLLAPSE_TIMER,
power: C.EFFECT_COLLAPSE_TIMER,
endTime: gameTime + duration,
duration
});
bulk.update(core, {
deployTime: null,
decayTime,
hits: C.INVADER_CORE_HITS,
hitsMax: C.INVADER_CORE_HITS,
effects: core.effects
});
_.forEach(ramparts, rampart => {bulk.remove(rampart._id); delete roomObjects[rampart._id]});
const template = strongholds.templates[core.templateName];
const objectOptions = {};
objectOptions[C.STRUCTURE_RAMPART] = {
hits: C.STRONGHOLD_RAMPART_HITS[template.rewardLevel],
hitsMax: C.RAMPART_HITS_MAX[8],
hitsTarget: C.STRONGHOLD_RAMPART_HITS[core.level],
nextDecayTime: decayTime
};
objectOptions[C.STRUCTURE_TOWER] = {
hits: C.TOWER_HITS,
hitsMax: C.TOWER_HITS,
store:{ energy: C.TOWER_CAPACITY },
storeCapacityResource: { energy: C.TOWER_CAPACITY },
actionLog: {attack: null, heal: null, repair: null}
};
objectOptions[C.STRUCTURE_CONTAINER] = {
notifyWhenAttacked: false,
hits: C.CONTAINER_HITS,
hitsMax: C.CONTAINER_HITS,
nextDecayTime: decayTime,
store: {},
storeCapacity: 0
};
objectOptions[C.STRUCTURE_ROAD] = {
notifyWhenAttacked: false,
hits: C.ROAD_HITS,
hitsMax: C.ROAD_HITS,
nextDecayTime: decayTime
};
let createdStructureCounter = 1;
_.forEach(template.structures, i => {
const x = 0+core.x+i.dx, y = 0+core.y+i.dy;
_.forEach(roomObjects, o => {
if(o.strongholdId || o.x != x || o.y != y) {
return;
}
if(o.type == 'creep' || o.type == 'powerCreep') {
require('../../creeps/_die')(o, undefined, true, scope);
}
if(o.type == 'constructionSite') {
delete roomObjects[o._id];
bulk.remove(o._id);
if(o.progress > 1) {
require('../../_create-energy')(o.x, o.y, o.room, Math.floor(o.progress/2), 'energy', scope);
}
}
if(C.CONSTRUCTION_COST[o.type]) {
require('../../structures/_destroy')(o, scope);
}
});
if(i.type == C.STRUCTURE_INVADER_CORE) {
return;
}
const s = Object.assign({}, i, {
x,
y,
room: core.room,
strongholdId: core.strongholdId,
decayTime,
effects: [{
effect: C.EFFECT_COLLAPSE_TIMER,
power: C.EFFECT_COLLAPSE_TIMER,
endTime: gameTime + duration,
duration
}]
}, objectOptions[i.type]||{});
delete s.dx;
delete s.dy;
if(i.type == C.STRUCTURE_TOWER || i.type == C.STRUCTURE_RAMPART) {
s.user = core.user;
}
if(i.type == C.STRUCTURE_CONTAINER) {
s.store = utils.calcReward(strongholds.containerRewards, strongholds.containerAmounts[template.rewardLevel], 3);
}
bulk.insert(s);
roomObjects['deployedStructure'+createdStructureCounter] = s;
createdStructureCounter++;
});
}
};
const handleController = function reserveController (context) {
const { gameTime, core, intents, roomController } = context;
if(roomController) {
if(roomController.user === core.user) {
if(roomController.downgradeTime - gameTime < C.INVADER_CORE_CONTROLLER_DOWNGRADE - 25) {
intents.set(core._id, 'upgradeController', {id: roomController._id});
}
} else if(!roomController.reservation || roomController.reservation.user === core.user) {
intents.set(core._id, 'reserveController', {id: roomController._id});
} else {
intents.set(core._id, 'attackController', {id: roomController._id});
}
}
};
const refillTowers = function refillTowers(context) {
const {core, intents, towers, ramparts} = context;
if(towerRefillChance[core.level] < Math.random()) {
return false;
}
const underchargedTowers = _.filter(towers, t => (t.store.energy <= 2*C.TOWER_ENERGY_COST) && _.some(ramparts, {x: t.x, y: t.y}));
if(_.some(underchargedTowers)) {
const towerToCharge = _.min(underchargedTowers, 'store.energy');
if(towerToCharge) {
intents.set(core._id, 'transfer', {id: towerToCharge._id, amount: towerToCharge.storeCapacityResource.energy - towerToCharge.store.energy, resourceType: C.RESOURCE_ENERGY});
return true;
}
}
return false;
};
const refillCreeps = function refillCreeps(context) {
const {core, intents, defenders} = context;
const underchargedCreeps = _.filter(defenders, c => (c.storeCapacity > 0) && (2*c.store.energy <= c.storeCapacity));
if(_.some(underchargedCreeps)) {
const creep = _.min(underchargedCreeps, 'store.energy');
if(creep) {
intents.set(core._id, 'transfer', {id: creep._id, amount: creep.storeCapacity - creep.store.energy, resourceType: C.RESOURCE_ENERGY});
return true;
}
}
return false;
};
const towersMaintenance = function towersMaintenance(context) {
const {intents, towers, ramparts, damagedDefenders, damagedRoads} = context;
if(!towers.length || (!damagedDefenders.length && !damagedRoads.length)) {
return;
}
const protectedCreeps = _.filter(damagedDefenders, d => _.some(ramparts, {x: d.x, y: d.y}));
if(_.some(protectedCreeps)) {
const creep = _.first(protectedCreeps);
const tower = _.first(towers);
intents.set(tower._id, 'heal', {id: creep._id});
_.pull(towers, tower);
return;
}
const protectedRoads = _.filter(damagedRoads, r => _.some(ramparts, {x: r.x, y: r.y}));
if(_.some(protectedRoads)) {
const road = _.first(damagedRoads);
const tower = _.first(towers);
intents.set(tower._id, 'repair', {id: road._id});
_.pull(towers, tower);
}
};
const focusClosest = function focusClosest(context) {
const {core, intents, defenders, hostiles, towers} = context;
if(!_.some(hostiles)) {
return false;
}
const target = _.min(hostiles, c => utils.dist(c, core));
if(!target) {
return false;
}
for(let t of towers) {
intents.set(t._id, 'attack', {id: target._id});
}
const meleesNear = _.filter(defenders, d => (range(d, target) == 1) && _.some(d.body, {type: C.ATTACK}));
for(let melee of meleesNear) {
intents.set(melee._id, 'attack', {id: target._id, x: target.x, y: target.y});
}
const rangersInRange = _.filter(defenders, d => (range(d, target) <= 3) && _.some(d.body, {type: C.RANGED_ATTACK}));
for(let r of rangersInRange) {
if(range(r,target) == 1) {
intents.set(r._id, 'rangedMassAttack', {});
} else {
intents.set(r._id, 'rangedAttack', {id: target._id});
}
}
return true;
};
const focusMax = function focusMax(context) {
const {intents, defenders, hostiles, towers, gameTime} = context;
if(!_.some(hostiles)) {
return false;
}
const activeTowers = _.filter(towers, t => t.store.energy >= C.TOWER_ENERGY_COST);
const target = _.max(hostiles, creep => {
let damage = _.sum(activeTowers, tower => {
let r = utils.dist(creep, tower);
let amount = C.TOWER_POWER_ATTACK;
if(r > C.TOWER_OPTIMAL_RANGE) {
if(r > C.TOWER_FALLOFF_RANGE) {
r = C.TOWER_FALLOFF_RANGE;
}
amount -= amount * C.TOWER_FALLOFF * (r - C.TOWER_OPTIMAL_RANGE) / (C.TOWER_FALLOFF_RANGE - C.TOWER_OPTIMAL_RANGE);
}
[C.PWR_OPERATE_TOWER, C.PWR_DISRUPT_TOWER].forEach(power => {
const effect = _.find(tower.effects, {power});
if(effect && effect.endTime > gameTime) {
amount *= C.POWER_INFO[power].effect[effect.level-1];
}
});
return Math.floor(amount);
});
damage += _.sum(defenders, defender => {
let d = 0;
if((range(defender, creep) <= 3) && _.some(defender.body, {type: C.RANGED_ATTACK})) {
d += utils.calcBodyEffectiveness(defender.body, C.RANGED_ATTACK, 'rangedAttack', C.RANGED_ATTACK_POWER);
}
if((range(defender, creep) <= 1) && _.some(defender.body, {type: C.ATTACK})) {
d += utils.calcBodyEffectiveness(defender.body, C.ATTACK, 'attack', C.ATTACK_POWER);
}
return d;
});
return damage;
});
const meleesNear = _.filter(defenders, d => (range(d, target) == 1) && _.some(d.body, {type: C.ATTACK}));
for(let melee of meleesNear) {
intents.set(melee._id, 'attack', {id: target._id, x: target.x, y: target.y});
}
const rangersInRange = _.filter(defenders, d => (range(d, target) <= 3) && _.some(d.body, {type: C.RANGED_ATTACK}));
for(let r of rangersInRange) {
if(range(r,target) == 1) {
intents.set(r._id, 'rangedMassAttack', {});
} else {
intents.set(r._id, 'rangedAttack', {id: target._id});
}
}
for(let t of activeTowers) {
intents.set(t._id, 'attack', {id: target._id});
}
return true;
};
const maintainCreep = function maintainCreep(name, setup, context, behavior) {
const {core, intents, defenders} = context;
const creep = _.find(defenders, {name});
if(creep && behavior) {
behavior(creep, context);
return;
}
if(!core.spawning && !core._spawning) {
intents.set(core._id, 'createCreep', {
name,
body: setup.body,
boosts: setup.boosts
});
core._spawning = true;
}
};
const maintainPopulation = function(context) {
const {core} = context;
if(!core.population) {
return;
}
for(let i in core.population) {
maintainCreep(`defender${i}`,
creeps.bodies[core.population[i].body],
context,
creeps.behaviors[core.population[i].behavior]);
}
};
const antinuke = function antinuke(context) {
const { core, ramparts, roomObjects, bulk, gameTime } = context;
if(!!(gameTime % 10)) {
return;
}
const nukes = _.filter(roomObjects, {type: 'nuke'});
const baseLevel = C.STRONGHOLD_RAMPART_HITS[core.level];
for(let rampart of ramparts) {
if(_.some(roomObjects, {type: C.STRUCTURE_CONTAINER, x: rampart.x, y: rampart.y})) {
continue;
}
let hitsTarget = baseLevel;
_.forEach(nukes, n => {
const range = utils.dist(rampart, n);
if(range == 0) {
hitsTarget += C.NUKE_DAMAGE[0];
return;
}
if(range <= 2) {
hitsTarget += C.NUKE_DAMAGE[2];
}
});
if(rampart.hitsTarget != hitsTarget) {
bulk.update(rampart, {hitsTarget});
}
}
};
const assignDefenders = function assignDefenders(context) {
let rangerSpots = [], meleeSpots = [];
_.forEach(context.hostiles, h => {
meleeSpots.push(..._.filter(context.ramparts, r => utils.dist(h, r) <= 1));
rangerSpots.push(..._.filter(context.ramparts, r => utils.dist(h, r) <= 3));
});
meleeSpots = _.unique(meleeSpots);
rangerSpots = _.unique(_.without(rangerSpots, ...meleeSpots));
const rangers = [], melees = [];
_.forEach(context.defenders, d => {
if(_.some(d.body, {type: C.ATTACK})) { melees.push(d._id.toString()); }
if(_.some(d.body, {type: C.RANGED_ATTACK})) { rangers.push(d._id.toString()); }
});
let spots = {};
if(_.some(meleeSpots) && _.some(melees)) {
spots = defence.distribute(meleeSpots, melees);
}
if(_.some(rangerSpots) && _.some(rangers)) {
Object.assign(spots, defence.distribute(rangerSpots, rangers));
}
return spots;
};
module.exports = {
behaviors: {
'deploy': function(context) {
handleController(context);
deployStronghold(context);
},
'default': function(context){
handleController(context);
refillTowers(context);
focusClosest(context);
},
'bunker1': function(context) {
handleController(context);
refillTowers(context) || refillCreeps(context);
focusClosest(context);
},
'bunker2': function(context) {
handleController(context);
refillTowers(context) || refillCreeps(context);
const { core, bulk } = context;
if(!core.population) {
bulk.update(core, {
population: [{body: 'weakDefender', behavior: 'simple-melee'}]
});
}
maintainPopulation(context);
focusClosest(context);
},
'bunker3': function(context) {
handleController(context);
refillTowers(context);
const { core, bulk } = context;
if(!core.population) {
bulk.update(core, {
population: [
{body: 'fullDefender', behavior: 'simple-melee'},
{body: 'fullDefender', behavior: 'simple-melee'},
]
});
}
maintainPopulation(context);
focusClosest(context);
},
'bunker4': function(context) {
handleController(context);
refillTowers(context) || refillCreeps(context);
const { core, bulk } = context;
if(!core.population) {
const populationDeck = [
{body: 'fortifier', behavior: 'fortifier'},
...new Array(4).fill({body: 'boostedDefender', behavior: 'coordinated'}),
...new Array(3).fill({body: 'boostedRanger', behavior: 'coordinated'})
];
bulk.update(core, {
population: _(populationDeck).shuffle().take(4).value()
});
}
context.spots = assignDefenders(context);
maintainPopulation(context);
towersMaintenance(context);
focusMax(context);
},
'bunker5': function(context) {
handleController(context);
refillTowers(context) || refillCreeps(context);
const { core, bulk } = context;
if(!core.population) {
const populationDeck = _.shuffle([
{body: 'fortifier', behavior: 'fortifier'},
...new Array(7).fill({body: 'fullBoostedMelee', behavior: 'coordinated'}),
...new Array(9).fill({body: 'fullBoostedRanger', behavior: 'coordinated'})
]);
bulk.update(core, {
population: [
{body: 'fortifier', behavior: 'fortifier'},
..._(populationDeck).shuffle().take(8).value()
]
});
}
antinuke(context);
context.spots = assignDefenders(context);
maintainPopulation(context);
towersMaintenance(context);
focusMax(context);
},
}
};