diff --git a/src/creepSetups/setups.ts b/src/creepSetups/setups.ts index 1c57dcbee..6e34dd94d 100644 --- a/src/creepSetups/setups.ts +++ b/src/creepSetups/setups.ts @@ -21,6 +21,7 @@ export const Roles = { melee : 'zergling', ranged : 'hydralisk', healer : 'transfuser', + bunkerGuard : 'bunkerGuard', dismantler: 'lurker', }; @@ -297,6 +298,35 @@ export const CombatSetups = { }, + /** + * Pure melee raw power creeps that should never leave the bunker. These are the final guards for a room + */ + bunkerGuard: { + + early: new CreepSetup(Roles.bunkerGuard, { + pattern : [ATTACK, MOVE], + sizeLimit: Infinity, + }), + + default: new CreepSetup(Roles.bunkerGuard, { + pattern : [ATTACK, ATTACK, MOVE], + sizeLimit: Infinity, + }), + + halfMove: new CreepSetup(Roles.bunkerGuard, { + pattern : [ATTACK, ATTACK, ATTACK, ATTACK, MOVE], + sizeLimit: Infinity, + }), + + boosted_T3: new CreepSetup(Roles.bunkerGuard, { + // 22 ATTACK, 3 MOVE times 2 + pattern : [ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, + ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, ATTACK, MOVE, MOVE, MOVE], + sizeLimit: Infinity, + }), + + }, + /** * Dismantlers (lurkers) are creeps with work parts for dismantle sieges */ diff --git a/src/directives/defense/invasionDefense.ts b/src/directives/defense/invasionDefense.ts index 1d6cab663..5b27611e7 100644 --- a/src/directives/defense/invasionDefense.ts +++ b/src/directives/defense/invasionDefense.ts @@ -1,8 +1,10 @@ -import {ColonyStage} from '../../Colony'; import {CombatIntel} from '../../intel/CombatIntel'; +import {BunkerDefenseOverlord} from '../../overlords/defense/bunkerDefense'; import {MeleeDefenseOverlord} from '../../overlords/defense/meleeDefense'; import {RangedDefenseOverlord} from '../../overlords/defense/rangedDefense'; import {profile} from '../../profiler/decorator'; + +import {ColonyStage} from '../../Colony'; import {Directive} from '../Directive'; import {NotifierPriority} from '../Notifier'; @@ -36,8 +38,9 @@ export class DirectiveInvasionDefense extends Directive { if (!this.room) { return; } - const expectedDamage = CombatIntel.maxDamageByCreeps(this.room.dangerousHostiles); - const useBoosts = (expectedDamage > ATTACK_POWER * 75) + const expectedDamage = CombatIntel.maxDamageByCreeps(this.room.dangerousPlayerHostiles); + const expectedHealing = CombatIntel.maxHealingByCreeps(this.room.dangerousPlayerHostiles); + const useBoosts = (expectedDamage > ATTACK_POWER * 50) || (expectedHealing > RANGED_ATTACK_POWER * 100) && !!this.colony.terminal && !!this.colony.evolutionChamber; const percentWalls = _.filter(this.room.barriers, s => s.structureType == STRUCTURE_WALL).length / @@ -50,6 +53,11 @@ export class DirectiveInvasionDefense extends Directive { } else { this.overlords.meleeDefense = new MeleeDefenseOverlord(this, useBoosts); } + // If serious bunker busting attempt, spawn lurkers + // TODO understand dismantlers damage output + if (meleeHostiles.length > 0 && (expectedDamage > ATTACK_POWER * 70)) { + this.overlords.bunkerDefense = new BunkerDefenseOverlord(this, false); + } } diff --git a/src/movement/Movement.ts b/src/movement/Movement.ts index d297c7eb6..233162766 100644 --- a/src/movement/Movement.ts +++ b/src/movement/Movement.ts @@ -33,6 +33,7 @@ const STATE_CURRENT_Y = 8; export const MovePriorities = { [Roles.manager] : 1, [Roles.queen] : 2, + [Roles.bunkerGuard]: 3, [Roles.melee] : 3, [Roles.ranged] : 4, [Roles.guardMelee]: 5, @@ -92,6 +93,7 @@ export interface CombatMoveOptions { avoidPenalty?: number; approachBonus?: number; preferRamparts?: boolean; + requireRamparts?: boolean; displayCostMatrix?: boolean; displayAvoid?: boolean; } @@ -998,6 +1000,7 @@ export class Movement { avoidPenalty : 10, approachBonus : 5, preferRamparts: true, + requireRamparts: false, }); const debug = false; @@ -1006,6 +1009,7 @@ export class Movement { const matrix = Pathing.getDefaultMatrix(creep.room).clone(); Pathing.blockMyCreeps(matrix, creep.room); Pathing.blockHostileCreeps(matrix, creep.room); + if (options.requireRamparts) { Pathing.blockNonRamparts(matrix, creep.room); } Movement.combatMoveCallbackModifier(creep.room, matrix, approach, avoid, options); if (options.displayCostMatrix) { Visualizer.displayCostMatrix(matrix, roomName); @@ -1059,7 +1063,7 @@ export class Movement { } // Try to maneuver under ramparts if possible - if (options.preferRamparts && !creep.inRampart && approach.length > 0) { + if ((options.preferRamparts || options.requireRamparts) && !creep.inRampart && approach.length > 0) { const openRamparts = _.filter(creep.room.walkableRamparts, rampart => _.any(approach, g => rampart.pos.inRangeToXY(g.pos.x, g.pos.y, g.range)) diff --git a/src/movement/Pathing.ts b/src/movement/Pathing.ts index 4c207d594..7811632ea 100644 --- a/src/movement/Pathing.ts +++ b/src/movement/Pathing.ts @@ -613,6 +613,20 @@ export class Pathing { }); } + /** + * Sets walkable rampart positions to 1, everything else is blocked + */ + static blockNonRamparts(matrix: CostMatrix, room: Room) { + for (let y = 0; y < 50; ++y) { + for (let x = 0; x < 50; ++x) { + matrix.set(x, y, 0xff); + } + } + _.forEach(room.walkableRamparts, rampart => { + matrix.set(rampart.pos.x, rampart.pos.y, 1); + }); + } + /** * Explicitly blocks off walls for a room */ diff --git a/src/overlords/CombatOverlord.ts b/src/overlords/CombatOverlord.ts index d080962f8..88d446b28 100644 --- a/src/overlords/CombatOverlord.ts +++ b/src/overlords/CombatOverlord.ts @@ -19,11 +19,11 @@ export abstract class CombatOverlord extends Overlord { spawnGroup: SpawnGroup; requiredRCL: number; // default required RCL - constructor(directive: Directive, name: string, priority: number, requiredRCL: number) { + constructor(directive: Directive, name: string, priority: number, requiredRCL: number, maxPathDistance?: number) { super(directive, name, priority); this.directive = directive; this.requiredRCL = requiredRCL; - this.spawnGroup = new SpawnGroup(this, {requiredRCL: this.requiredRCL}); + this.spawnGroup = new SpawnGroup(this, {requiredRCL: this.requiredRCL, maxPathDistance: maxPathDistance}); } // Standard sequence of actions for running combat creeps diff --git a/src/overlords/SwarmOverlord.ts b/src/overlords/SwarmOverlord.ts index 9d581a1ea..223aba651 100644 --- a/src/overlords/SwarmOverlord.ts +++ b/src/overlords/SwarmOverlord.ts @@ -32,7 +32,7 @@ export abstract class SwarmOverlord extends CombatOverlord { creepQuantities[setup.role] += existingCreepsOfRole.length; if (!neededQuantities[setup.role]) neededQuantities[setup.role] = 0; neededQuantities[setup.role] += amount; - // Spawn the neede quantity of creeps + // Spawn the needed quantity of creeps const spawnQuantity = amount - existingCreepsOfRole.length; for (let i = 0; i < spawnQuantity; i++) { this.requestCreep(setup, {priority: priority}); diff --git a/src/overlords/defense/bunkerDefense.ts b/src/overlords/defense/bunkerDefense.ts new file mode 100644 index 000000000..eeb036def --- /dev/null +++ b/src/overlords/defense/bunkerDefense.ts @@ -0,0 +1,62 @@ +import {log} from '../../console/log'; +import {CombatSetups, Roles} from '../../creepSetups/setups'; +import {DirectiveInvasionDefense} from '../../directives/defense/invasionDefense'; +import {OverlordPriority} from '../../priorities/priorities_overlords'; +import {profile} from '../../profiler/decorator'; +import {boostResources} from '../../resources/map_resources'; +import {CombatZerg} from '../../zerg/CombatZerg'; +import {CombatOverlord} from '../CombatOverlord'; + +/** + * Spawns bunker-only defenders to defend against incoming sieges + */ +@profile +export class BunkerDefenseOverlord extends CombatOverlord { + + lurkers: CombatZerg[]; + room: Room; + + static settings = { + retreatHitsPercent : 0.85, + reengageHitsPercent: 0.95, + }; + + constructor(directive: DirectiveInvasionDefense, boosted = false, priority = OverlordPriority.defense.meleeDefense) { + // Only spawn inside room + super(directive, 'bunkerDefense', priority, 1, 30); + this.lurkers = this.combatZerg(Roles.bunkerGuard, { + boostWishlist: boosted ? [boostResources.attack[3], boostResources.move[3]] + : undefined + }); + } + + private handleDefender(lurker: CombatZerg): void { + log.debug(`Running BunkerDefender in room ${this.room.print}`); + if (!lurker.inRampart) { + const nearRampart = _.find(lurker.room.walkableRamparts, rampart => rampart.pos.getRangeTo(lurker) < 5); + if (nearRampart) { + lurker.goTo(nearRampart); + } + } + if (lurker.room.hostiles.length > 0) { + lurker.autoBunkerCombat(lurker.room.name); + } else { + // go out of way in bunker + } + } + + init() { + this.reassignIdleCreeps(Roles.bunkerGuard); + if (this.canBoostSetup(CombatSetups.bunkerGuard.boosted_T3)) { + const setup = CombatSetups.bunkerGuard.boosted_T3; + this.wishlist(1, setup); + } else { + const setup = CombatSetups.bunkerGuard.halfMove; + this.wishlist(1, setup); + } + } + + run() { + this.autoRun(this.lurkers, lurkers => this.handleDefender(lurkers)); + } +} diff --git a/src/overlords/defense/rangedDefense.ts b/src/overlords/defense/rangedDefense.ts index c3f2f2e5f..1ce728d34 100644 --- a/src/overlords/defense/rangedDefense.ts +++ b/src/overlords/defense/rangedDefense.ts @@ -27,8 +27,8 @@ export class RangedDefenseOverlord extends CombatOverlord { priority = OverlordPriority.defense.rangedDefense) { super(directive, 'rangedDefense', priority, 1); this.hydralisks = this.combatZerg(Roles.ranged, { - boostWishlist: boosted ? [boostResources.ranged_attack[3], boostResources.heal[3], boostResources.move[3]] - : undefined + boostWishlist: boosted ? [boostResources.tough[3], boostResources.ranged_attack[3], + boostResources.heal[3], boostResources.move[3]] : undefined }); } diff --git a/src/zerg/CombatZerg.ts b/src/zerg/CombatZerg.ts index 567219dfc..514ad707d 100644 --- a/src/zerg/CombatZerg.ts +++ b/src/zerg/CombatZerg.ts @@ -1,6 +1,7 @@ import {CombatIntel} from '../intel/CombatIntel'; import {Movement, NO_ACTION} from '../movement/Movement'; import {profile} from '../profiler/decorator'; +import {insideBunkerBounds} from '../roomPlanner/layouts/bunker'; import {CombatTargeting} from '../targeting/CombatTargeting'; import {GoalFinder} from '../targeting/GoalFinder'; import {randomHex} from '../utilities/utils'; @@ -271,6 +272,31 @@ export class CombatZerg extends Zerg { } + autoBunkerCombat(roomName: string, verbose = false) { + if (this.getActiveBodyparts(ATTACK) > 0) { + this.autoMelee(); // Melee should be performed first + } + if (this.getActiveBodyparts(RANGED_ATTACK) > 0) { + this.autoRanged(); + } + + // Travel to the target room + if (!this.safelyInRoom(roomName)) { + this.debug(`Going to room!`); + return this.goToRoom(roomName, {ensurePath: true}); + } + + // TODO check if right colony, also yes colony check is in there to stop red squigglies + const siegingCreeps = this.room.hostiles.filter(creep => + _.any(creep.pos.neighbors, pos => this.colony && insideBunkerBounds(pos, this.colony))); + + const target = CombatTargeting.findTarget(this, siegingCreeps); + + if (target) { + return Movement.combatMove(this, [{pos: target.pos, range: 1}], [], {preferRamparts: true, requireRamparts: true}); + } + } + needsToRecover(recoverThreshold = CombatIntel.minimumDamageTakenMultiplier(this.creep) < 1 ? 0.85 : 0.75, reengageThreshold = 1.0): boolean { let recovering: boolean;