UNPKG

@screeps/engine

Version:

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

532 lines (447 loc) 18.1 kB
'use strict'; 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 = context.scope, core = context.core, ramparts = context.ramparts, bulk = context.bulk, gameTime = context.gameTime; const roomObjects = scope.roomObjects; 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 = context.gameTime, core = context.core, intents = context.intents, roomController = context.roomController; 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 = context.core, intents = context.intents, towers = context.towers, ramparts = context.ramparts; 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 = context.core, intents = context.intents, defenders = context.defenders; 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 = context.intents, towers = context.towers, ramparts = context.ramparts, damagedDefenders = context.damagedDefenders, damagedRoads = context.damagedRoads; 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 = context.core, intents = context.intents, defenders = context.defenders, hostiles = context.hostiles, towers = context.towers; 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 = context.intents, defenders = context.defenders, hostiles = context.hostiles, towers = context.towers, gameTime = context.gameTime; 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 = context.core, intents = context.intents, defenders = context.defenders; 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.core; 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 = context.core, ramparts = context.ramparts, roomObjects = context.roomObjects, bulk = context.bulk, gameTime = context.gameTime; 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 = context.core, bulk = context.bulk; 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 = context.core, bulk = context.bulk; 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 = context.core, bulk = context.bulk; 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 = context.core, bulk = context.bulk; 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); } } }; //# sourceMappingURL=../../../../sourcemaps/processor/intents/invader-core/stronghold/stronghold.js.map