diff --git a/src/Colony.ts b/src/Colony.ts index c23be5588..451937220 100644 --- a/src/Colony.ts +++ b/src/Colony.ts @@ -546,6 +546,13 @@ export class Colony { return allAssets; } + private runPowerSpawn() { + if (this.powerSpawn && this.assets.energy > 300000 && this.powerSpawn.energy > 50 + && this.powerSpawn.power > 0) { + this.powerSpawn.processPower(); + } + } + /** * Initializes the state of the colony each tick */ @@ -567,6 +574,7 @@ export class Colony { this.linkNetwork.run(); // Run the link network this.roadLogistics.run(); // Run the road network this.roomPlanner.run(); // Run the room planner + this.runPowerSpawn(); // Run power spawn - short term this.stats(); // Log stats per tick } diff --git a/src/declarations/prototypes.d.ts b/src/declarations/prototypes.d.ts index 1d0e9f187..093f452a9 100644 --- a/src/declarations/prototypes.d.ts +++ b/src/declarations/prototypes.d.ts @@ -7,6 +7,17 @@ interface Creep { inRampart: boolean; } +interface PowerCreep { + hitsPredicted?: number; + intel?: { [property: string]: number }; + memory: CreepMemory; + fatigue: number; + body: BodyPartDefinition[]; + boosts: _ResourceConstantSansEnergy[]; + boostCounts: { [boostType: string]: number }; + inRampart: boolean; +} + interface ConstructionSite { isWalkable: boolean; } diff --git a/src/directives/initializer.ts b/src/directives/initializer.ts index 081a081bd..a63af3941 100644 --- a/src/directives/initializer.ts +++ b/src/directives/initializer.ts @@ -25,6 +25,7 @@ import {DirectiveTargetSiege} from './targeting/siegeTarget'; import {DirectiveTerminalEmergencyState} from './terminalState/terminalState_emergency'; import {DirectiveTerminalEvacuateState} from './terminalState/terminalState_evacuate'; import {DirectiveTerminalRebuildState} from './terminalState/terminalState_rebuild'; +import {DirectiveBaseOperator} from "./powerCreeps/baseOperator"; /** * This is the initializer for directives, which maps flags by their color code to the corresponding directive @@ -128,6 +129,13 @@ export function DirectiveWrapper(flag: Flag): Directive | undefined { return new DirectiveRPBunker(flag); } break; + // Power directives ==================================================================================== + case COLOR_CYAN: + switch (flag.secondaryColor) { + case COLOR_PURPLE: + return new DirectiveBaseOperator(flag); + } + break; } } diff --git a/src/directives/powerCreeps/baseOperator.ts b/src/directives/powerCreeps/baseOperator.ts new file mode 100644 index 000000000..84e3659bd --- /dev/null +++ b/src/directives/powerCreeps/baseOperator.ts @@ -0,0 +1,288 @@ +import {CombatPlanner, SiegeAnalysis} from "../../strategy/CombatPlanner"; +import {profile} from "../../profiler/decorator"; +import {Directive} from "../Directive"; +import {log} from "../../console/log"; +import {Visualizer} from "../../visuals/Visualizer"; +import {Power} from "./powers/genericPower"; +import {GenerateOps} from "./powers/generateOps"; +import {DirectiveNukeResponse} from "../situational/nukeResponse"; +import {OperateExtension} from "./powers/operateExtension"; + + +interface DirectiveBaseOperatorMemory extends FlagMemory { + powerPriorities: PowerConstant[]; +} + +export enum types { + opgen, + baseoperator, + basedefender +} + +/** + * Simple directive to run a power creep where the flag name is the power creep name + */ +@profile +export class DirectiveBaseOperator extends Directive { + + static directiveName = 'BaseOperator'; + static color = COLOR_CYAN; + static secondaryColor = COLOR_PURPLE; + + memory: DirectiveBaseOperatorMemory; + + // Power Creep Hack + //powerCreep: PowerCreep; + powerCreepName: string; + + defaultPowerPriorities: PowerConstant[] = [ + PWR_GENERATE_OPS, + PWR_REGEN_SOURCE, + PWR_OPERATE_TOWER, + PWR_OPERATE_LAB, + PWR_OPERATE_SPAWN, + PWR_OPERATE_EXTENSION, + PWR_REGEN_MINERAL]; + + // overlords: { + // scout?: StationaryScoutOverlord; + // destroy?: SwarmDestroyerOverlord | PairDestroyerOverlord; + // guard?: OutpostDefenseOverlord; + // controllerAttack?: ControllerAttackerOverlord; + // }; + + constructor(flag: Flag) { + super(flag); + const powerCreep = Game.powerCreeps[flag.name]; + if (!powerCreep) { + log.error(`Power Creep not found for ${this.print}, deleting directive`); + this.remove(); + } + this.memory.powerPriorities = this.memory.powerPriorities || this.defaultPowerPriorities; + } + + spawnMoarOverlords() { + } + + init(): void { + + } + + + // Wrapped powerCreep methods =========================================================================================== + + renew(powerCreep: PowerCreep, powerSource: StructurePowerBank | StructurePowerSpawn) { + if (powerCreep.pos.inRangeToPos(powerSource.pos, 1)) { + return powerCreep.renew(powerSource); + } else { + return powerCreep.moveTo(powerSource, {ignoreRoads: true, range: 1, swampCost: 1, reusePath: 0, visualizePathStyle: {lineStyle: "dashed", fill: 'yellow'}}); + } + } + + enablePower(powerCreep: PowerCreep, controller: StructureController) { + log.alert(`Trying to enable power for ${controller} with `); + if (powerCreep.pos.inRangeToPos(controller.pos, 1)) { + return powerCreep.enableRoom(controller); + } else { + //let path = powerCreep.pos.findPathTo(controller, {ignoreRoads: true, range: 1, swampCost: 1}); + //log.alert(`Trying to enable power for ${controller} with ${JSON.stringify(path)}`); + //return powerCreep.moveByPath(path); + return powerCreep.moveTo(controller.pos, {ignoreRoads: true, range: 1, swampCost: 1, reusePath: 0, visualizePathStyle: {lineStyle: "solid"}}); + } + } + + usePower(powerCreep: PowerCreep, power: PowerConstant) { + console.log(`The power constant is ${power}`) + switch(power) { + case PWR_GENERATE_OPS: return new GenerateOps(powerCreep); + case PWR_OPERATE_EXTENSION: return new OperateExtension(powerCreep); +// case PWR_OPERATE_SPAWN: return this.operateSpawn(); + } + + } + // + // /** + // * Generate 1/2/4/6/8 ops resource units. Cooldown 50 ticks. Required creep level: 0/2/7/14/22. + // */ + // generateOps() { + // if (powerCreep.powers[PWR_GENERATE_OPS].cooldown !> 0) { + // return powerCreep.usePower(PWR_GENERATE_OPS); + // } + // return ERR_TIRED; + // } + // + // operateSpawn(spawn?: StructureSpawn) { + // // if (powerCreep.powers[PWR_oper]) + // // if (!spawn) { + // // spawn = _.first(this.room!.spawns.filter(spawn => spawn.effects.length == 0)); + // // if (!spawn) { + // // return ERR; + // // } + // // } + // if (this.pos.inRangeToPos(spawn.pos, 1)) { + // return powerCreep.usePower(PWR_OPERATE_SPAWN, spawn); + // } else { + // return powerCreep.moveTo(spawn); + // } + // } + // + // operateTower(tower: StructureTower) { + // if (this.pos.inRangeToPos(tower.pos, POWER_INFO[PWR_OPERATE_TOWER].range)) { + // return powerCreep.usePower(PWR_OPERATE_TOWER, tower); + // } else { + // return powerCreep.moveTo(tower); + // } + // } + // + // operateStorage(storage: StructureStorage) { + // if (this.pos.inRangeToPos(storage.pos, POWER_INFO[PWR_OPERATE_STORAGE].range)) { + // return powerCreep.usePower(PWR_OPERATE_STORAGE, storage); + // } else { + // return powerCreep.moveTo(storage); + // } + // } + // + // operateExtensions(container: StructureStorage | StructureTerminal | StructureContainer) { + // if (this.pos.inRangeToPos(container.pos, POWER_INFO[PWR_OPERATE_EXTENSION].range)) { + // return powerCreep.usePower(PWR_OPERATE_EXTENSION, container); + // } else { + // return powerCreep.moveTo(container); + // } + // } + // + // operateObserver(observer: StructureObserver) { + // if (this.pos.inRangeToPos(observer.pos, POWER_INFO[PWR_OPERATE_OBSERVER].range)) { + // return powerCreep.usePower(PWR_OPERATE_OBSERVER, observer); + // } else { + // return powerCreep.moveTo(observer); + // } + // } + // + // operateTerminal(terminal: StructureTerminal) { + // if (this.pos.inRangeToPos(terminal.pos, POWER_INFO[PWR_OPERATE_TERMINAL].range)) { + // return powerCreep.usePower(PWR_OPERATE_TERMINAL, terminal); + // } else { + // return powerCreep.moveTo(terminal); + // } + // } + // + // operatePower(power: StructurePowerSpawn) { + // if (this.pos.inRangeToPos(power.pos, POWER_INFO[PWR_OPERATE_POWER].range)) { + // return powerCreep.usePower(PWR_OPERATE_POWER, power); + // } else { + // return powerCreep.moveTo(power); + // } + // } + // + // operateController(controller: StructureController) { + // if (this.pos.inRangeToPos(controller.pos, POWER_INFO[PWR_OPERATE_CONTROLLER].range)) { + // return powerCreep.usePower(PWR_OPERATE_CONTROLLER, controller); + // } else { + // return powerCreep.moveTo(controller); + // } + // } + // + // // operateFactory(factory: StructureFactory) { + // // if (this.pos.inRangeToPos(factory.pos, POWER_INFO[PWR_OPERATE_FACTORY].range)) { + // // return powerCreep.usePower(PWR_OPERATE_FACTORY, factory); + // // } else { + // // return this.moveTo(factory); + // // } + // // } + // + // shield() { + // if (powerCreep.powers[PWR_SHIELD].cooldown !> 0) { + // return powerCreep.usePower(PWR_SHIELD); + // } + // return ERR_TIRED; + // } + // + // regenSource(source : Source) { + // if (this.pos.inRangeToPos(source.pos, POWER_INFO[PWR_REGEN_SOURCE].range)) { + // return powerCreep.usePower(PWR_REGEN_SOURCE, source); + // } else { + // return powerCreep.moveTo(source); + // } + // } + // + // regenMineral(mineral: Mineral) { + // if (this.pos.inRangeToPos(mineral.pos, POWER_INFO[PWR_REGEN_MINERAL].range)) { + // return powerCreep.usePower(PWR_REGEN_MINERAL, mineral); + // } else { + // return powerCreep.moveTo(mineral); + // } + // } + // + // fortify(rampart: StructureRampart) { + // if (this.pos.inRangeToPos(rampart.pos, POWER_INFO[PWR_FORTIFY].range)) { + // return powerCreep.usePower(PWR_FORTIFY, rampart); + // } else { + // return powerCreep.moveTo(rampart); + // } + // } + // + // operateLab(lab: StructureLab) { + // if (this.pos.inRangeToPos(lab.pos, POWER_INFO[PWR_OPERATE_LAB].range)) { + // return powerCreep.usePower(PWR_OPERATE_LAB, lab); + // } else { + // return powerCreep.moveTo(lab); + // } + // } + + + runPowers(powerCreep: PowerCreep) { + const priorities = this.memory.powerPriorities; + console.log(`Powerid of priority list of ${priorities}`); + for (let powerId in priorities) { + console.log(`Powerid of ${powerId} and list of ${priorities}`); + let powerToUse = this.usePower(powerCreep, priorities[powerId]); + if (powerToUse && powerToUse.operatePower()) { + break; + } + } + } + + + run(): void { + const powerCreep = Game.powerCreeps[this.powerCreepName]; + + // For the power creeps that just sit on power spawn + const isStationary = powerCreep.name.toLowerCase().indexOf(types.basedefender.toString()); + + console.log(`Running power creep ${JSON.stringify(powerCreep)} with ttl ${powerCreep.ticksToLive} with ${this.room!.powerSpawn}`); + if (!this.room) { + return; + } else if (!powerCreep.ticksToLive && this.room && this.room.powerSpawn) { + // Spawn creep + let res = powerCreep.spawn(this.room.powerSpawn); + log.alert(`Running ${powerCreep} with spawn of ${res}`); + } else if (this.room.controller && !this.room.controller.isPowerEnabled && !isStationary) { + // Enable power + let res = this.enablePower(powerCreep, this.room.controller); + log.alert(`Running ${powerCreep} with enable power of ${res}`); + } else if (powerCreep && powerCreep.ticksToLive && powerCreep.ticksToLive < 900 && this.room.powerSpawn) { + let res = this.renew(powerCreep, this.room.powerSpawn); + log.alert(`Running ${powerCreep} with renew of ${res}`); + } else { + let res = this.runPowers(powerCreep); + log.alert(`Running ${powerCreep} with power of ${res}`); + } + + if (this.room.hostiles.length > 2 || (powerCreep.pos && DirectiveNukeResponse.isPresent(powerCreep.pos, 'room'))) { + const towersToBoost = this.colony.towers.filter(tower => !tower.effects || tower.effects.length == 0); + if (towersToBoost.length > 0) { + powerCreep.usePower(PWR_OPERATE_TOWER, towersToBoost[0]) + } + if ((!powerCreep.carry.ops || powerCreep.carry.ops < 20) && this.room.storage && this.room.storage.store.ops && this.room.storage.store.ops > 100) { + powerCreep.withdraw(this.room.storage, RESOURCE_OPS, 100); + } + } + + + } + + visuals(): void { + Visualizer.marker(this.pos, {color: 'red'}); + } + +} \ No newline at end of file diff --git a/src/directives/powerCreeps/powers/generateOps.ts b/src/directives/powerCreeps/powers/generateOps.ts new file mode 100644 index 000000000..2f02070bd --- /dev/null +++ b/src/directives/powerCreeps/powers/generateOps.ts @@ -0,0 +1,31 @@ +import {profile} from "../../../profiler/decorator"; +import {Power} from "./genericPower"; +import {log} from "../../../console/log"; + +export const powerId = PWR_GENERATE_OPS; + +/** + * An abstract class for encapsulating power creep power usage. + */ +@profile +export class GenerateOps extends Power { + + constructor(powerCreep: PowerCreep, target?: RoomObject) { + super(powerCreep, target); + } + + operatePower() { + if (this.powerCreep.carry.ops && this.powerCreep.carry.ops > (this.powerCreep.carryCapacity * 0.9)) { + const terminal = this.powerCreep.room!.terminal; + if (!terminal) { + log.error(`Ops power creep with no storage`); + } else { + this.powerCreep.moveTo(terminal); + this.powerCreep.transfer(terminal, RESOURCE_OPS, this.powerCreep.carry.ops); + } + } else { + return this.powerCreep.usePower(powerId); + } + return ERR_TIRED; + } +} \ No newline at end of file diff --git a/src/directives/powerCreeps/powers/genericPower.ts b/src/directives/powerCreeps/powers/genericPower.ts new file mode 100644 index 000000000..97a07042e --- /dev/null +++ b/src/directives/powerCreeps/powers/genericPower.ts @@ -0,0 +1,72 @@ +import {profile} from "../../../profiler/decorator"; +import {powerId} from "./generateOps"; +import {log} from "../../../console/log"; + +/** + * An abstract class for encapsulating power creep power usage. + */ +@profile +export abstract class Power { + static powerId: PowerConstant; + + _target: { // Data for the target the task is directed to: + ref: string; // Target id or name + _pos: ProtoPos; // Target position's coordinates in case vision is lost + }; + + + _powerCreep: { + name: string; + }; + + constructor(powerCreep: PowerCreep, target?: RoomObject) { + log.notify(`Creating power task for ${powerCreep}`); + this._powerCreep = { + name: powerCreep.name, + }; + if (target) { + this._target = { + ref : target.ref, + _pos: target.pos, + } + } + + } + + /** + * Dereferences the Task's target + */ + get target(): RoomObject | null { + return deref(this._target.ref); + } + + canRunPower() { + const power = this.powerCreep.powers[powerId]; + return power && power.level > 0 && power.cooldown == 0; + } + + /** + * Return the wrapped creep which is executing this task + */ + get powerCreep(): PowerCreep { // Get task's own creep by its name + // Returns zerg wrapper instead of creep to use monkey-patched functions + return Game.powerCreeps[this._powerCreep.name]; + } + + /** + * Set the creep which is executing this task + */ + set powerCreep(pc: PowerCreep) { + this._powerCreep.name = pc.name; + } + + run() { + if (this.canRunPower()) { + this.operatePower() + } + } + + operatePower() { + + } +} \ No newline at end of file diff --git a/src/directives/powerCreeps/powers/operateExtension.ts b/src/directives/powerCreeps/powers/operateExtension.ts new file mode 100644 index 000000000..dac9c6021 --- /dev/null +++ b/src/directives/powerCreeps/powers/operateExtension.ts @@ -0,0 +1,30 @@ +import {profile} from "../../../profiler/decorator"; +import {Power} from "./genericPower"; +import {log} from "../../../console/log"; + +export const powerId = PWR_OPERATE_EXTENSION; + +/** + * An abstract class for encapsulating power creep power usage. + */ +@profile +export class OperateExtension extends Power { + + constructor(powerCreep: PowerCreep, target?: RoomObject) { + super(powerCreep, target); + } + + operatePower() { + if (this.powerCreep.carry.ops && this.powerCreep.carry.ops > 2 && this.powerCreep.room + && this.powerCreep.room.energyAvailable < this.powerCreep.room.energyCapacityAvailable * 0.5) { + const terminal = this.powerCreep.room!.storage; + if (!terminal) { + log.error(`Ops power creep with no storage`); + } else { + this.powerCreep.moveTo(terminal); + return this.powerCreep.usePower(powerId, terminal); + } + } + return ERR_TIRED; + } +} \ No newline at end of file diff --git a/src/hiveClusters/commandCenter.ts b/src/hiveClusters/commandCenter.ts index 51e0c07ef..fc799f281 100644 --- a/src/hiveClusters/commandCenter.ts +++ b/src/hiveClusters/commandCenter.ts @@ -135,8 +135,12 @@ export class CommandCenter extends HiveCluster { } } // Refill power spawn - if (this.powerSpawn && this.powerSpawn.energy < this.powerSpawn.energyCapacity) { - this.transportRequests.requestInput(this.powerSpawn, Priority.NormalLow); + if (this.powerSpawn) { + if (this.powerSpawn.energy < this.powerSpawn.energyCapacity) { + this.transportRequests.requestInput(this.powerSpawn, Priority.NormalLow); + } else if (this.powerSpawn.power < this.powerSpawn.powerCapacity) { + this.transportRequests.requestInput(this.powerSpawn, Priority.Low, {resourceType: RESOURCE_POWER}); + } } // Refill nuker with low priority if (this.nuker) { diff --git a/src/zerg/PowerZerg.ts b/src/zerg/PowerZerg.ts new file mode 100644 index 000000000..36e5bff54 --- /dev/null +++ b/src/zerg/PowerZerg.ts @@ -0,0 +1,51 @@ +import {CombatIntel} from '../intel/CombatIntel'; +import {Movement, NO_ACTION} from '../movement/Movement'; +import {profile} from '../profiler/decorator'; +import {CombatTargeting} from '../targeting/CombatTargeting'; +import {GoalFinder} from '../targeting/GoalFinder'; +import {randomHex} from '../utilities/utils'; +import {Zerg} from './Zerg'; + +interface CombatZergMemory extends CreepMemory { + recovering: boolean; + lastInDanger: number; + partner?: string; + swarm?: string; +} + +export const DEFAULT_PARTNER_TICK_DIFFERENCE = 650; +export const DEFAULT_SWARM_TICK_DIFFERENCE = 500; + +/** + * CombatZerg is an extension of the Zerg class which contains additional combat-related methods + */ +@profile +export class PowerZerg extends Zerg { + + memory: CombatZergMemory; + isPowerZerg: boolean; + + constructor(creep: Creep, notifyWhenAttacked = true) { + super(creep, notifyWhenAttacked); + this.isPowerZerg = true; + _.defaults(this.memory, { + recovering : false, + lastInDanger: 0, + targets : {} + }); + } + + static fatigue() { + return 0; + } + + static body() { + return [MOVE]; + } + + static attack(target: Creep | Structure): 0 | -1 | -4 | -7 | -9 | -12 | -11 { + return ERR_TIRED; + } + + +} diff --git a/src/zerg/ZergShell.ts b/src/zerg/ZergShell.ts new file mode 100644 index 000000000..44b11c033 --- /dev/null +++ b/src/zerg/ZergShell.ts @@ -0,0 +1,592 @@ +import {Colony} from '../Colony'; +import {log} from '../console/log'; +import {isCreep, isZerg} from '../declarations/typeGuards'; +import {CombatIntel} from '../intel/CombatIntel'; +import {Movement, MoveOptions} from '../movement/Movement'; +import {Overlord} from '../overlords/Overlord'; +import {profile} from '../profiler/decorator'; +import {initializeTask} from '../tasks/initializer'; +import {Task} from '../tasks/Task'; +import {NEW_OVERMIND_INTERVAL} from '../~settings'; +import {PowerZerg} from "./PowerZerg"; + +export function getOverlord(creep: Zerg | Creep): Overlord | null { + if (creep.memory[_MEM.OVERLORD]) { + return Overmind.overlords[creep.memory[_MEM.OVERLORD]!] || null; + } else { + return null; + } +} + +export function setOverlord(creep: Zerg | Creep, newOverlord: Overlord | null) { + // Remove cache references to old assignments + const roleName = creep.memory.role; + const ref = creep.memory[_MEM.OVERLORD]; + const oldOverlord: Overlord | null = ref ? Overmind.overlords[ref] : null; + if (ref && Overmind.cache.overlords[ref] && Overmind.cache.overlords[ref][roleName]) { + _.remove(Overmind.cache.overlords[ref][roleName], name => name == creep.name); + } + if (newOverlord) { + // Change to the new overlord's colony + creep.memory[_MEM.COLONY] = newOverlord.colony.name; + // Change assignments in memory + creep.memory[_MEM.OVERLORD] = newOverlord.ref; + // Update the cache references + if (!Overmind.cache.overlords[newOverlord.ref]) { + Overmind.cache.overlords[newOverlord.ref] = {}; + } + if (!Overmind.cache.overlords[newOverlord.ref][roleName]) { + Overmind.cache.overlords[newOverlord.ref][roleName] = []; + } + Overmind.cache.overlords[newOverlord.ref][roleName].push(creep.name); + } else { + creep.memory[_MEM.OVERLORD] = null; + } + if (oldOverlord) oldOverlord.recalculateCreeps(); + if (newOverlord) newOverlord.recalculateCreeps(); +} + +export function normalizeZerg(creep: Zerg | Creep): Zerg | Creep { + return Overmind.zerg[creep.name] || creep; +} + +export function toCreep(creep: Zerg | Creep): Creep { + return isZerg(creep) ? creep.creep : creep; +} + +// Last pipeline is more complex because it depends on the energy a creep has; sidelining this for now +const actionPipelines: string[][] = [ + ['harvest', 'attack', 'build', 'repair', 'dismantle', 'attackController', 'rangedHeal', 'heal'], + ['rangedAttack', 'rangedMassAttack', 'build', 'repair', 'rangedHeal'], + // ['upgradeController', 'build', 'repair', 'withdraw', 'transfer', 'drop'], +]; + +interface ParkingOptions { + range: number; + exactRange: boolean; + offroad: boolean; +} + +interface FleeOptions { + dropEnergy?: boolean; + invalidateTask?: boolean; +} + +const RANGES = { + BUILD : 3, + REPAIR : 3, + TRANSFER: 1, + WITHDRAW: 1, + HARVEST : 1, + DROP : 0, +}; + +/** + * The Zerg class is a wrapper for owned creeps and contains all wrapped creep methods and many additional methods for + * direct control of a creep. + */ +@profile +export class Zerg { + + creep: Creep | PowerZerg; // The creep that this wrapper class will control + body: BodyPartDefinition[]; // These properties are all wrapped from this.creep.* to this.* + carry: StoreDefinition; // | + carryCapacity: number; // | + fatigue: number; // | + hits: number; // | + hitsMax: number; // | + id: string; // | + memory: CreepMemory; // | See the ICreepMemory interface for structure + name: string; // | + pos: RoomPosition; // | + nextPos: RoomPosition; // | The next position the creep will be in after registering a move intent + ref: string; // | + roleName: string; // | + room: Room; // | + saying: string; // | + spawning: boolean; // | + ticksToLive: number | undefined; // | + lifetime: number; + actionLog: { [actionName: string]: boolean }; // Tracks the actions that a creep has completed this tick + blockMovement: boolean; // Whether the zerg is allowed to move or not + private _task: Task | null; // Cached Task object that is instantiated once per tick and on change + + constructor(creep: Creep, notifyWhenAttacked = true) { + // Copy over creep references + this.creep = creep; + this.body = creep.body; + this.carry = creep.carry; + this.carryCapacity = creep.carryCapacity; + this.fatigue = creep.fatigue; + this.hits = creep.hits; + this.hitsMax = creep.hitsMax; + this.id = creep.id; + this.memory = creep.memory; + this.name = creep.name; + this.pos = creep.pos; + this.nextPos = creep.pos; + this.ref = creep.ref; + this.roleName = creep.memory.role; + this.room = creep.room; + this.saying = creep.saying; + this.spawning = creep.spawning; + this.ticksToLive = creep.ticksToLive; + // Extra properties + this.lifetime = this.getBodyparts(CLAIM) > 0 ? CREEP_CLAIM_LIFE_TIME : CREEP_LIFE_TIME; + this.actionLog = {}; + this.blockMovement = false; + // Register global references + Overmind.zerg[this.name] = this; + global[this.name] = this; + // Handle attack notification when at lifetime - 1 + if (!notifyWhenAttacked && (this.ticksToLive || 0) >= this.lifetime - (NEW_OVERMIND_INTERVAL + 1)) { + // creep.notifyWhenAttacked only uses the 0.2CPU intent cost if it changes the intent value + this.notifyWhenAttacked(notifyWhenAttacked); + } + } + + /** + * Refresh all changeable properties of the creep or delete from Overmind and global when dead + */ + refresh(): void { + const creep = Game.creeps[this.name]; + if (creep) { + this.creep = creep; + this.pos = creep.pos; + this.nextPos = creep.pos; + this.body = creep.body; + this.carry = creep.carry; + this.carryCapacity = creep.carryCapacity; + this.fatigue = creep.fatigue; + this.hits = creep.hits; + this.memory = creep.memory; + this.roleName = creep.memory.role; + this.room = creep.room; + this.saying = creep.saying; + this.spawning = creep.spawning; + this.ticksToLive = creep.ticksToLive; + this.actionLog = {}; + this.blockMovement = false; + this._task = null; // todo + } else { + log.debug(`Deleting from global`); + delete Overmind.zerg[this.name]; + delete global[this.name]; + } + } + + debug(...args: any[]) { + if (this.memory.debug) { + log.debug(this.print, args); + } + } + + get ticksUntilSpawned(): number | undefined { + if (this.spawning) { + const spawner = this.pos.lookForStructure(STRUCTURE_SPAWN) as StructureSpawn; + if (spawner && spawner.spawning) { + return spawner.spawning.remainingTime; + } else { + // Shouldn't ever get here + console.log(`Error determining ticks to spawn for ${this.name} @ ${this.pos.print}!`); + } + } + } + + get print(): string { + return '[' + this.name + ']'; + } + + cancelOrder(methodName: string): OK | ERR_NOT_FOUND { + const result = this.creep.cancelOrder(methodName); + if (result == OK) this.actionLog[methodName] = false; + return result; + } + + drop(resourceType: ResourceConstant, amount?: number) { + const result = this.creep.drop(resourceType, amount); + if (!this.actionLog.drop) this.actionLog.drop = (result == OK); + return result; + } + + goDrop(pos: RoomPosition, resourceType: ResourceConstant, amount?: number) { + if (this.pos.inRangeToPos(pos, RANGES.DROP)) { + return this.drop(resourceType, amount); + } else { + return this.goTo(pos); + } + } + + generateSafeMode(target: StructureController) { + return this.creep.generateSafeMode(target); + } + + harvest(source: Source | Mineral) { + const result = this.creep.harvest(source); + if (!this.actionLog.harvest) this.actionLog.harvest = (result == OK); + return result; + } + + goHarvest(source: Source | Mineral) { + if (this.pos.inRangeToPos(source.pos, RANGES.HARVEST)) { + return this.harvest(source); + } else { + return this.goTo(source); + } + } + + move(direction: DirectionConstant, force = false) { + if (!this.blockMovement && !force) { + const result = this.creep.move(direction); + if (result == OK) { + if (!this.actionLog.move) this.actionLog.move = true; + this.nextPos = this.pos.getPositionAtDirection(direction); + } + return result; + } else { + return ERR_BUSY; + } + } + + notifyWhenAttacked(enabled: boolean) { + return this.creep.notifyWhenAttacked(enabled); + } + + pickup(resource: Resource) { + const result = this.creep.pickup(resource); + if (!this.actionLog.pickup) this.actionLog.pickup = (result == OK); + return result; + } + + /* Say a message; maximum message length is 10 characters */ + say(message: string, pub?: boolean) { + return this.creep.say(message, pub); + } + + signController(target: StructureController, text: string) { + const result = this.creep.signController(target, text); + if (!this.actionLog.signController) this.actionLog.signController = (result == OK); + return result; + } + + suicide() { + return this.creep.suicide(); + } + + transfer(target: Creep | Zerg | Structure, resourceType: ResourceConstant = RESOURCE_ENERGY, amount?: number) { + let result: ScreepsReturnCode; + if (target instanceof Zerg) { + result = this.creep.transfer(target.creep, resourceType, amount); + } else { + result = this.creep.transfer(target, resourceType, amount); + } + if (!this.actionLog.transfer) this.actionLog.transfer = (result == OK); + return result; + } + + goTransfer(target: Creep | Zerg | Structure, resourceType: ResourceConstant = RESOURCE_ENERGY, amount?: number) { + if (this.pos.inRangeToPos(target.pos, RANGES.TRANSFER)) { + return this.transfer(target, resourceType, amount); + } else { + return this.goTo(target); + } + } + + withdraw(target: Structure | Tombstone, resourceType: ResourceConstant = RESOURCE_ENERGY, amount?: number) { + const result = this.creep.withdraw(target, resourceType, amount); + if (!this.actionLog.withdraw) this.actionLog.withdraw = (result == OK); + return result; + } + + goWithdraw(target: Structure | Tombstone, resourceType: ResourceConstant = RESOURCE_ENERGY, amount?: number) { + if (this.pos.inRangeToPos(target.pos, RANGES.WITHDRAW)) { + return this.withdraw(target, resourceType, amount); + } else { + return this.goTo(target); + } + } + + // Simultaneous creep actions -------------------------------------------------------------------------------------- + + /** + * Determine whether the given action will conflict with an action the creep has already taken. + * See http://docs.screeps.com/simultaneous-actions.html for more details. + */ + canExecute(actionName: string): boolean { + // Only one action can be executed from within a single pipeline + let conflictingActions: string[] = [actionName]; + for (const pipeline of actionPipelines) { + if (pipeline.includes(actionName)) conflictingActions = conflictingActions.concat(pipeline); + } + for (const action of conflictingActions) { + if (this.actionLog[action]) { + return false; + } + } + return true; + } + + // Body configuration and related data ----------------------------------------------------------------------------- + + getActiveBodyparts(type: BodyPartConstant): number { + return this.creep.getActiveBodyparts(type); + } + + /* The same as creep.getActiveBodyparts, but just counts bodyparts regardless of condition. */ + getBodyparts(partType: BodyPartConstant): number { + return _.filter(this.body, (part: BodyPartDefinition) => part.type == partType).length; + } + + // Custom creep methods ============================================================================================ + + // Carry methods + + get hasMineralsInCarry(): boolean { + for (const resourceType in this.carry) { + if (resourceType != RESOURCE_ENERGY && (this.carry[resourceType] || 0) > 0) { + return true; + } + } + return false; + } + + // Boosting logic -------------------------------------------------------------------------------------------------- + + get boosts(): _ResourceConstantSansEnergy[] { + return this.creep.boosts; + } + + get boostCounts(): { [boostType: string]: number } { + return this.creep.boostCounts; + } + + get needsBoosts(): boolean { + if (this.overlord) { + return this.overlord.shouldBoost(this); + } + return false; + } + + // Overlord logic -------------------------------------------------------------------------------------------------- + + get overlord(): Overlord | null { + return getOverlord(this); + } + + set overlord(newOverlord: Overlord | null) { + setOverlord(this, newOverlord); + } + + /* Reassigns the creep to work under a new overlord and as a new role. */ + reassign(newOverlord: Overlord | null, newRole: string, invalidateTask = true) { + this.overlord = newOverlord; + this.roleName = newRole; + this.memory.role = newRole; + if (invalidateTask) { + this.task = null; + } + } + + // Task logic ------------------------------------------------------------------------------------------------------ + + /** + * Wrapper for _task + */ + get task(): Task | null { + if (!this._task) { + this._task = this.memory.task ? initializeTask(this.memory.task) : null; + } + return this._task; + } + + /** + * Assign the creep a task with the setter, replacing creep.assign(Task) + */ + set task(task: Task | null) { + // Unregister target from old task if applicable + const oldProtoTask = this.memory.task; + if (oldProtoTask) { + const oldRef = oldProtoTask._target.ref; + if (Overmind.cache.targets[oldRef]) { + _.remove(Overmind.cache.targets[oldRef], name => name == this.name); + } + } + // Set the new task + this.memory.task = task ? task.proto : null; + if (task) { + if (task.target) { + // Register task target in cache if it is actively targeting something (excludes goTo and similar) + if (!Overmind.cache.targets[task.target.ref]) { + Overmind.cache.targets[task.target.ref] = []; + } + Overmind.cache.targets[task.target.ref].push(this.name); + } + // Register references to creep + task.creep = this; + } + // Clear cache + this._task = null; + } + + /** + * Does the creep have a valid task at the moment? + */ + get hasValidTask(): boolean { + return !!this.task && this.task.isValid(); + } + + /** + * Creeps are idle if they don't have a task. + */ + get isIdle(): boolean { + return !this.task || !this.task.isValid(); + } + + /** + * Execute the task you currently have. + */ + run(): number | undefined { + if (this.task) { + return this.task.run(); + } + } + + // Colony association ---------------------------------------------------------------------------------------------- + + /** + * Colony that the creep belongs to. + */ + get colony(): Colony { + return Overmind.colonies[this.memory[_MEM.COLONY]]; + } + + set colony(newColony: Colony) { + this.memory[_MEM.COLONY] = newColony.name; + } + + /** + * If the creep is in a colony room or outpost + */ + get inColonyRoom(): boolean { + return Overmind.colonyMap[this.room.name] == this.memory[_MEM.COLONY]; + } + + // Movement and location ------------------------------------------------------------------------------------------- + + goTo(destination: RoomPosition | HasPos, options: MoveOptions = {}) { + return Movement.goTo(this, destination, options); + } + + goToRoom(roomName: string, options: MoveOptions = {}) { + return Movement.goToRoom(this, roomName, options); + } + + inSameRoomAs(target: HasPos): boolean { + return this.pos.roomName == target.pos.roomName; + } + + safelyInRoom(roomName: string): boolean { + return this.room.name == roomName && !this.pos.isEdge; + } + + get inRampart(): boolean { + return this.creep.inRampart; + } + + get isMoving(): boolean { + const moveData = this.memory._go as MoveData | undefined; + return !!moveData && !!moveData.path && moveData.path.length > 1; + } + + /** + * Kite around hostiles in the room + */ + kite(avoidGoals: (RoomPosition | HasPos)[] = this.room.hostiles, options: MoveOptions = {}): number | undefined { + _.defaults(options, { + fleeRange: 5 + }); + return Movement.kite(this, avoidGoals, options); + } + + private defaultFleeGoals() { + let fleeGoals: (RoomPosition | HasPos)[] = []; + fleeGoals = fleeGoals.concat(this.room.hostiles) + .concat(_.filter(this.room.keeperLairs, lair => (lair.ticksToSpawn || Infinity) < 10)); + return fleeGoals; + } + + /** + * Flee from hostiles in the room, while not repathing every tick + */ + flee(avoidGoals: (RoomPosition | HasPos)[] = this.room.fleeDefaults, + fleeOptions: FleeOptions = {}, + moveOptions: MoveOptions = {}): boolean { + if (avoidGoals.length == 0) { + return false; + } else if (this.room.controller && this.room.controller.my && this.room.controller.safeMode) { + return false; + } else { + const fleeing = Movement.flee(this, avoidGoals, fleeOptions.dropEnergy, moveOptions) != undefined; + if (fleeing) { + // Drop energy if needed + if (fleeOptions.dropEnergy && this.carry.energy > 0) { + const nearbyContainers = this.pos.findInRange(this.room.storageUnits, 1); + if (nearbyContainers.length > 0) { + this.transfer(_.first(nearbyContainers), RESOURCE_ENERGY); + } else { + this.drop(RESOURCE_ENERGY); + } + } + // Invalidate task + if (fleeOptions.invalidateTask) { + this.task = null; + } + } + return fleeing; + } + } + + /** + * Park the creep off-roads + */ + park(pos: RoomPosition = this.pos, maintainDistance = false): number { + return Movement.park(this, pos, maintainDistance); + } + + /** + * Moves a creep off of the current tile to the first available neighbor + */ + moveOffCurrentPos(): number | undefined { + return Movement.moveOffCurrentPos(this); + } + + /** + * Moves onto an exit tile + */ + moveOnExit(): ScreepsReturnCode | undefined { + return Movement.moveOnExit(this); + } + + /** + * Moves off of an exit tile + */ + moveOffExit(avoidSwamp = true): ScreepsReturnCode { + return Movement.moveOffExit(this, avoidSwamp); + } + + moveOffExitToward(pos: RoomPosition, detour = true): number | undefined { + return Movement.moveOffExitToward(this, pos, detour); + } + + // Miscellaneous fun stuff ----------------------------------------------------------------------------------------- + + sayLoop(messageList: string[], pub?: boolean) { + return this.say(messageList[Game.time % messageList.length], pub); + } + + sayRandom(phrases: string[], pub?: boolean) { + return this.say(phrases[Math.floor(Math.random() * phrases.length)], pub); + } + +} +