diff --git a/bin/creep-tasks.js b/bin/creep-tasks.js index ce89b81..4ba65f6 100644 --- a/bin/creep-tasks.js +++ b/bin/creep-tasks.js @@ -1,4 +1,4 @@ -// creep-tasks v1.2.0: github.com/bencbartlett/creep-tasks +// creep-tasks v1.3.0: github.com/bencbartlett/creep-tasks 'use strict'; // Universal reference properties @@ -63,7 +63,7 @@ class Task { }; _.defaults(options, { blind: false, - travelToOptions: {}, + moveOptions: {}, }); this.tick = Game.time; this.options = options; @@ -182,14 +182,14 @@ class Task { return this.parent ? this.parent.isValid() : false; } } - move(range = this.settings.targetRange) { + + moveToTarget(range = this.settings.targetRange) { if (this.options.moveOptions && !this.options.moveOptions.range) { this.options.moveOptions.range = range; } return this.creep.moveTo(this.targetPos, this.options.moveOptions); // return this.creep.travelTo(this.targetPos, this.options.moveOptions); // <- switch if you use Traveler } - /* Moves to the next position on the agenda if specified - call this in some tasks after work() is completed */ moveToNextPos() { if (this.options.nextPos) { @@ -211,10 +211,14 @@ class Task { // Move to somewhere nearby that isn't on a road this.parkCreep(this.creep, this.targetPos, true); } - return this.work(); + let result = this.work(); + if (this.settings.oneShot && result == OK) { + this.finish(); + } + return result; } else { - this.move(); + this.moveToTarget(); } } /* Bundled form of Zerg.park(); adapted from BonzAI codebase*/ @@ -247,6 +251,7 @@ class Task { } // Finalize the task and switch to parent task (or null if there is none) finish() { + this.moveToNextPos(); if (this.creep) { this.creep.task = this.parent; } @@ -257,7 +262,6 @@ class Task { } // Attack task, includes attack and ranged attack if applicable. -// Use meleeAttack and rangedAttack for the exclusive variants. class TaskAttack extends Task { constructor(target, options = {}) { super(TaskAttack.taskName, target, options); @@ -280,7 +284,7 @@ class TaskAttack extends Task { attackReturn = creep.attack(target); } else { - attackReturn = this.move(1); // approach target if you also have attack parts + attackReturn = this.moveToTarget(1); // approach target if you also have attack parts } } if (creep.pos.inRangeTo(target, 3) && creep.getActiveBodyparts(RANGED_ATTACK) > 0) { @@ -376,27 +380,72 @@ class TaskFortify extends Task { } TaskFortify.taskName = 'fortify'; +const MIN_LIFETIME_FOR_BOOST = 0.9; + +function boostCounts(creep) { + return _.countBy(this.body, bodyPart => bodyPart.boost); +} + +const boostParts = { + 'UH': ATTACK, + 'UO': WORK, + 'KH': CARRY, + 'KO': RANGED_ATTACK, + 'LH': WORK, + 'LO': HEAL, + 'ZH': WORK, + 'ZO': MOVE, + 'GH': WORK, + 'GO': TOUGH, + 'UH2O': ATTACK, + 'UHO2': WORK, + 'KH2O': CARRY, + 'KHO2': RANGED_ATTACK, + 'LH2O': WORK, + 'LHO2': HEAL, + 'ZH2O': WORK, + 'ZHO2': MOVE, + 'GH2O': WORK, + 'GHO2': TOUGH, + 'XUH2O': ATTACK, + 'XUHO2': WORK, + 'XKH2O': CARRY, + 'XKHO2': RANGED_ATTACK, + 'XLH2O': WORK, + 'XLHO2': HEAL, + 'XZH2O': WORK, + 'XZHO2': MOVE, + 'XGH2O': WORK, + 'XGHO2': TOUGH, +}; class TaskGetBoosted extends Task { - constructor(target, amount = undefined, options = {}) { + constructor(target, boostType, partCount = undefined, options = {}) { super(TaskGetBoosted.taskName, target, options); // Settings - this.data.amount = amount; + this.data.resourceType = boostType; + this.data.amount = partCount; } isValidTask() { - if (this.data.amount && this.target.mineralType) { - let boostCounts = _.countBy(this.creep.body, bodyPart => bodyPart.boost); - return boostCounts[this.target.mineralType] <= this.data.amount; - } - else { - let boosts = _.compact(_.unique(_.map(this.creep.body, bodyPart => bodyPart.boost))); - return !boosts.includes(this.target.mineralType); + let lifetime = _.any(this.creep.body, part => part.type == CLAIM) ? CREEP_CLAIM_LIFE_TIME : CREEP_LIFE_TIME; + if (this.creep.ticksToLive && this.creep.ticksToLive < MIN_LIFETIME_FOR_BOOST * lifetime) { + return false; // timeout after this amount of lifespan has passed } + let partCount = (this.data.amount || this.creep.getActiveBodyparts(boostParts[this.data.resourceType])); + return (boostCounts(this.creep)[this.data.resourceType] || 0) < partCount; } isValidTarget() { return true; // Warning: this will block creep actions if the lab is left unsupplied of energy or minerals } work() { - return this.target.boostCreep(this.creep); + let partCount = (this.data.amount || this.creep.getActiveBodyparts(boostParts[this.data.resourceType])); + if (this.target.mineralType == this.data.resourceType && + this.target.mineralAmount >= LAB_BOOST_MINERAL * partCount && + this.target.energy >= LAB_BOOST_ENERGY * partCount) { + return this.target.boostCreep(this.creep, this.data.amount); + } + else { + return ERR_NOT_FOUND; + } } } TaskGetBoosted.taskName = 'getBoosted'; @@ -419,13 +468,16 @@ class TaskGetRenewed extends Task { } TaskGetRenewed.taskName = 'getRenewed'; +function hasPos(obj) { + return obj.pos != undefined; +} class TaskGoTo extends Task { constructor(target, options = {}) { - if (target instanceof RoomPosition) { - super(TaskGoTo.taskName, {ref: '', pos: target}, options); + if (hasPos(target)) { + super(TaskGoTo.taskName, {ref: '', pos: target.pos}, options); } else { - super(TaskGoTo.taskName, {ref: '', pos: target.pos}, options); + super(TaskGoTo.taskName, {ref: '', pos: target}, options); } // Settings this.settings.targetRange = 1; @@ -500,15 +552,28 @@ class TaskGoToRoom extends Task { } TaskGoToRoom.taskName = 'goToRoom'; +function isSource(obj) { + return obj.energy != undefined; +} class TaskHarvest extends Task { constructor(target, options = {}) { super(TaskHarvest.taskName, target, options); } isValidTask() { - return this.creep.carry.energy < this.creep.carryCapacity; + return _.sum(this.creep.carry) < this.creep.carryCapacity; } isValidTarget() { - return this.target && this.target.energy > 0; + // if (this.target && (this.target instanceof Source ? this.target.energy > 0 : this.target.mineralAmount > 0)) { + // // Valid only if there's enough space for harvester to work - prevents doing tons of useless pathfinding + // return this.target.pos.availableNeighbors().length > 0 || this.creep.pos.isNearTo(this.target.pos); + // } + // return false; + if (isSource(this.target)) { + return this.target.energy > 0; + } + else { + return this.target.mineralAmount > 0; + } } work() { return this.creep.harvest(this.target); @@ -533,7 +598,7 @@ class TaskHeal extends Task { return this.creep.heal(this.target); } else { - this.move(1); + this.moveToTarget(1); } return this.creep.rangedHeal(this.target); } @@ -609,7 +674,7 @@ class TaskWithdraw extends Task { isValidTarget() { let amount = this.data.amount || 1; let target = this.target; - if (isStoreStructure(target)) { + if (target instanceof Tombstone || isStoreStructure(target)) { return (target.store[this.data.resourceType] || 0) >= amount; } else if (isEnergyStructure(target) && this.data.resourceType == RESOURCE_ENERGY) { @@ -647,7 +712,15 @@ class TaskRepair extends Task { return this.target && this.target.hits < this.target.hitsMax; } work() { - return this.creep.repair(this.target); + let result = this.creep.repair(this.target); + if (this.target.structureType == STRUCTURE_ROAD) { + // prevents workers from idling for a tick before moving to next target + let newHits = this.target.hits + this.creep.getActiveBodyparts(WORK) * REPAIR_POWER; + if (newHits > this.target.hitsMax) { + this.finish(); + } + } + return result; } } TaskRepair.taskName = 'repair'; @@ -820,6 +893,71 @@ class TaskInvalid extends Task { } TaskInvalid.taskName = 'invalid'; +class TaskTransferAll extends Task { + constructor(target, skipEnergy = false, options = {}) { + super(TaskTransferAll.taskName, target, options); + this.data.skipEnergy = skipEnergy; + } + + isValidTask() { + for (let resourceType in this.creep.carry) { + if (this.data.skipEnergy && resourceType == RESOURCE_ENERGY) { + continue; + } + let amountInCarry = this.creep.carry[resourceType] || 0; + if (amountInCarry > 0) { + return true; + } + } + return false; + } + + isValidTarget() { + return _.sum(this.target.store) < this.target.storeCapacity; + } + + work() { + for (let resourceType in this.creep.carry) { + if (this.data.skipEnergy && resourceType == RESOURCE_ENERGY) { + continue; + } + let amountInCarry = this.creep.carry[resourceType] || 0; + if (amountInCarry > 0) { + return this.creep.transfer(this.target, resourceType); + } + } + return -1; + } +} + +TaskTransferAll.taskName = 'transferAll'; + +class TaskWithdrawAll extends Task { + constructor(target, options = {}) { + super(TaskWithdrawAll.taskName, target, options); + } + + isValidTask() { + return (_.sum(this.creep.carry) < this.creep.carryCapacity); + } + + isValidTarget() { + return _.sum(this.target.store) > 0; + } + + work() { + for (let resourceType in this.target.store) { + let amountInStore = this.target.store[resourceType] || 0; + if (amountInStore > 0) { + return this.creep.withdraw(this.target, resourceType); + } + } + return -1; + } +} + +TaskWithdrawAll.taskName = 'withdrawAll'; + // Reinstantiation of a task object from protoTask data function initializeTask(protoTask) { // Retrieve name and target data from the protoTask @@ -847,7 +985,7 @@ function initializeTask(protoTask) { task = new TaskFortify(target); break; case TaskGetBoosted.taskName: - task = new TaskGetBoosted(target); + task = new TaskGetBoosted(target, protoTask.data.resourceType); break; case TaskGetRenewed.taskName: task = new TaskGetRenewed(target); @@ -873,9 +1011,6 @@ function initializeTask(protoTask) { case TaskRangedAttack.taskName: task = new TaskRangedAttack(target); break; - case TaskWithdraw.taskName: - task = new TaskWithdraw(target); - break; case TaskRepair.taskName: task = new TaskRepair(target); break; @@ -888,9 +1023,18 @@ function initializeTask(protoTask) { case TaskTransfer.taskName: task = new TaskTransfer(target); break; + case TaskTransferAll.taskName: + task = new TaskTransferAll(target); + break; case TaskUpgrade.taskName: task = new TaskUpgrade(target); break; + case TaskWithdraw.taskName: + task = new TaskWithdraw(target); + break; + case TaskWithdrawAll.taskName: + task = new TaskWithdrawAll(target); + break; default: console.log(`Invalid task name: ${taskName}! task.creep: ${protoTask._creep.name}. Deleting from memory!`); task = new TaskInvalid(target); @@ -968,8 +1112,9 @@ Object.defineProperty(Creep.prototype, 'task', { } // Register references to creep task.creep = this; - this._task = task; } + // Clear cache + this._task = null; }, }); Creep.prototype.run = function () { @@ -1048,39 +1193,27 @@ RoomPosition.prototype.availableNeighbors = function (ignoreCreeps = false) { return _.filter(this.neighbors, pos => pos.isPassible(ignoreCreeps)); }; -class TaskTransferAll extends Task { - constructor(target, options = {}) { - super(TaskTransferAll.taskName, target, options); - } - - isValidTask() { - for (let resourceType in this.creep.carry) { - let amountInCarry = this.creep.carry[resourceType] || 0; - if (amountInCarry > 0) { - return true; - } +class Tasks { + /* Tasks.chain allows you to transform a list of tasks into a single task, where each subsequent task in the list + * is the previous task's parent. SetNextPos will chain Task.nextPos as well, preventing creeps from idling for a + * tick between tasks. If an empty list is passed, null is returned. */ + static chain(tasks, setNextPos = true) { + if (tasks.length == 0) { + return null; } - return false; - } - - isValidTarget() { - return this.target.storeCapacity - _.sum(this.target.store) >= _.sum(this.creep.carry); - } - - work() { - for (let resourceType in this.creep.carry) { - let amountInCarry = this.creep.carry[resourceType] || 0; - if (amountInCarry > 0) { - return this.creep.transfer(this.target, resourceType); + if (setNextPos) { + for (let i = 0; i < tasks.length - 1; i++) { + tasks[i].options.nextPos = tasks[i + 1].targetPos; } } - return -1; + // Make the accumulator task from the end and iteratively fork it + let task = _.last(tasks); // start with last task + tasks = _.dropRight(tasks); // remove it from the list + for (let i = (tasks.length - 1); i >= 0; i--) { // iterate over the remaining tasks + task = task.fork(tasks[i]); + } + return task; } -} - -TaskTransferAll.taskName = 'transferAll'; - -class Tasks$1 { static attack(target, options = {}) { return new TaskAttack(target, options); } @@ -1099,8 +1232,9 @@ class Tasks$1 { static fortify(target, options = {}) { return new TaskFortify(target, options); } - static getBoosted(target, amount = undefined, options = {}) { - return new TaskGetBoosted(target, amount, options); + + static getBoosted(target, boostType, amount = undefined, options = {}) { + return new TaskGetBoosted(target, boostType, amount, options); } static getRenewed(target, options = {}) { return new TaskGetRenewed(target, options); @@ -1139,8 +1273,8 @@ class Tasks$1 { return new TaskTransfer(target, resourceType, amount, options); } - static transferAll(target, options = {}) { - return new TaskTransferAll(target, options); + static transferAll(target, skipEnergy = false, options = {}) { + return new TaskTransferAll(target, skipEnergy, options); } static upgrade(target, options = {}) { return new TaskUpgrade(target, options); @@ -1148,9 +1282,12 @@ class Tasks$1 { static withdraw(target, resourceType = RESOURCE_ENERGY, amount = undefined, options = {}) { return new TaskWithdraw(target, resourceType, amount, options); } + + static withdrawAll(target, options = {}) { + return new TaskWithdrawAll(target, options); + } } // creep-tasks index; ensures proper compilation order -// If you are using TypeScript and copying the full creep-tasks directory into your codebase, you do not need this file -module.exports = Tasks$1; +module.exports = Tasks; diff --git a/examples/JavaScript/role.patroller.js b/examples/JavaScript/role.patroller.js index 48124df..69d0f31 100644 --- a/examples/JavaScript/role.patroller.js +++ b/examples/JavaScript/role.patroller.js @@ -3,15 +3,12 @@ let Tasks = require('creep-tasks'); let rolePatroller = { // Patroller will patrol in a cycle from Spawn1 to all placed flags. - // This role demonstrates using parents, via Task.fork(), with creep-tasks. + // This role demonstrates using parents, via Task.chain(), with creep-tasks. newTask: function (creep) { - let flagNames = _.keys(Game.flags); - creep.task = Tasks.goTo(Game.spawns['Spawn1']); - for (let name of flagNames) { - let nextTask = Tasks.goTo(Game.flags[name]); - creep.task.fork(nextTask); // Task.fork() suspends the current task and makes it the new task's parent - } + let flags = _.values(Game.flags); + let tasks = _.map(flags, flag => Tasks.goTo(flag)); + creep.task = Tasks.chain(tasks); } }; diff --git a/examples/TypeScript/roles/patroller.ts b/examples/TypeScript/roles/patroller.ts index ac5faa0..ffff074 100644 --- a/examples/TypeScript/roles/patroller.ts +++ b/examples/TypeScript/roles/patroller.ts @@ -3,15 +3,12 @@ import {Tasks} from '../../../src/creep-tasks/Tasks'; // Path to Tasks.ts export class RolePatroller { // Patroller will patrol in a cycle from Spawn1 to all placed flags. - // This role demonstrates using parents, via Task.fork(), with creep-tasks. + // This role demonstrates using parents, via Task.chain(), with creep-tasks. static newTask(creep: Creep) { - let flagNames = _.keys(Game.flags); - creep.task = Tasks.goTo(Game.spawns['Spawn1']); - for (let name of flagNames) { - let nextTask = Tasks.goTo(Game.flags[name]); - creep.task.fork(nextTask); // Task.fork() suspends the current task and makes it the new task's parent - } + let flags = _.values(Game.flags); + let tasks = _.map(flags, flag => Tasks.goTo(flag)); + creep.task = Tasks.chain(tasks); } } diff --git a/package.json b/package.json index 54f4cda..37c73cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "creep-tasks", - "version": "1.2.0", + "version": "1.3.0", "description": "Tasks for your creeps", "author": "Ben Bartlett", "repository": { @@ -31,7 +31,7 @@ "typescript": "^2.7.2" }, "dependencies": { - "@types/screeps": "^2.3.0" + "@types/screeps": "^2.4.0" }, "files": [ "dist/**/*", diff --git a/src/creep-tasks/Task.ts b/src/creep-tasks/Task.ts index 85a58c1..02825e6 100644 --- a/src/creep-tasks/Task.ts +++ b/src/creep-tasks/Task.ts @@ -68,8 +68,8 @@ export abstract class Task implements ITask { oneShot : false, // remove this task once work() returns OK, regardless of validity }; _.defaults(options, { - blind : false, - travelToOptions: {}, + blind : false, + moveOptions: {}, }); this.tick = Game.time; this.options = options; @@ -207,7 +207,7 @@ export abstract class Task implements ITask { } } - move(range = this.settings.targetRange): number { + moveToTarget(range = this.settings.targetRange): number { if (this.options.moveOptions && !this.options.moveOptions.range) { this.options.moveOptions.range = range; } @@ -232,7 +232,7 @@ export abstract class Task implements ITask { } // Execute this task each tick. Returns nothing unless work is done. - run(): number | void { + run(): number | undefined { if (this.creep.pos.inRangeTo(this.targetPos, this.settings.targetRange) && !this.creep.pos.isEdge) { if (this.settings.workOffRoad) { // Move to somewhere nearby that isn't on a road @@ -244,7 +244,7 @@ export abstract class Task implements ITask { } return result; } else { - this.move(); + this.moveToTarget(); } } @@ -283,6 +283,7 @@ export abstract class Task implements ITask { // Finalize the task and switch to parent task (or null if there is none) finish(): void { + this.moveToNextPos(); if (this.creep) { this.creep.task = this.parent; } else { diff --git a/src/creep-tasks/TaskInstances/task_attack.ts b/src/creep-tasks/TaskInstances/task_attack.ts index 2500cdf..2424f83 100644 --- a/src/creep-tasks/TaskInstances/task_attack.ts +++ b/src/creep-tasks/TaskInstances/task_attack.ts @@ -33,7 +33,7 @@ export class TaskAttack extends Task { if (creep.pos.isNearTo(target)) { attackReturn = creep.attack(target); } else { - attackReturn = this.move(1); // approach target if you also have attack parts + attackReturn = this.moveToTarget(1); // approach target if you also have attack parts } } if (creep.pos.inRangeTo(target, 3) && creep.getActiveBodyparts(RANGED_ATTACK) > 0) { diff --git a/src/creep-tasks/TaskInstances/task_getBoosted.ts b/src/creep-tasks/TaskInstances/task_getBoosted.ts index fec68b4..26c2e1b 100644 --- a/src/creep-tasks/TaskInstances/task_getBoosted.ts +++ b/src/creep-tasks/TaskInstances/task_getBoosted.ts @@ -1,25 +1,77 @@ import {Task} from '../Task'; +export const MIN_LIFETIME_FOR_BOOST = 0.9; + export type getBoostedTargetType = StructureLab; +function boostCounts(creep: Creep): { [boostType: string]: number } { + return _.countBy(this.body as BodyPartDefinition[], bodyPart => bodyPart.boost); +} + +const boostParts: { [boostType: string]: BodyPartConstant } = { + + 'UH': ATTACK, + 'UO': WORK, + 'KH': CARRY, + 'KO': RANGED_ATTACK, + 'LH': WORK, + 'LO': HEAL, + 'ZH': WORK, + 'ZO': MOVE, + 'GH': WORK, + 'GO': TOUGH, + + 'UH2O': ATTACK, + 'UHO2': WORK, + 'KH2O': CARRY, + 'KHO2': RANGED_ATTACK, + 'LH2O': WORK, + 'LHO2': HEAL, + 'ZH2O': WORK, + 'ZHO2': MOVE, + 'GH2O': WORK, + 'GHO2': TOUGH, + + 'XUH2O': ATTACK, + 'XUHO2': WORK, + 'XKH2O': CARRY, + 'XKHO2': RANGED_ATTACK, + 'XLH2O': WORK, + 'XLHO2': HEAL, + 'XZH2O': WORK, + 'XZHO2': MOVE, + 'XGH2O': WORK, + 'XGHO2': TOUGH, + +}; + export class TaskGetBoosted extends Task { static taskName = 'getBoosted'; target: getBoostedTargetType; - constructor(target: getBoostedTargetType, amount: number | undefined = undefined, options = {} as TaskOptions) { + data: { + resourceType: _ResourceConstantSansEnergy; + amount: number | undefined; + }; + + constructor(target: getBoostedTargetType, + boostType: _ResourceConstantSansEnergy, + partCount: number | undefined = undefined, + options = {} as TaskOptions) { super(TaskGetBoosted.taskName, target, options); // Settings - this.data.amount = amount; + this.data.resourceType = boostType; + this.data.amount = partCount; + } isValidTask() { - if (this.data.amount && this.target.mineralType) { - let boostCounts = _.countBy(this.creep.body, bodyPart => bodyPart.boost); - return boostCounts[this.target.mineralType] <= this.data.amount; - } else { - let boosts = _.compact(_.unique(_.map(this.creep.body, bodyPart => bodyPart.boost))); - return !boosts.includes(this.target.mineralType); + let lifetime = _.any(this.creep.body, part => part.type == CLAIM) ? CREEP_CLAIM_LIFE_TIME : CREEP_LIFE_TIME; + if (this.creep.ticksToLive && this.creep.ticksToLive < MIN_LIFETIME_FOR_BOOST * lifetime) { + return false; // timeout after this amount of lifespan has passed } + let partCount = (this.data.amount || this.creep.getActiveBodyparts(boostParts[this.data.resourceType])); + return (boostCounts(this.creep)[this.data.resourceType] || 0) < partCount; } isValidTarget() { @@ -27,7 +79,14 @@ export class TaskGetBoosted extends Task { } work() { - return this.target.boostCreep(this.creep); + let partCount = (this.data.amount || this.creep.getActiveBodyparts(boostParts[this.data.resourceType])); + if (this.target.mineralType == this.data.resourceType && + this.target.mineralAmount >= LAB_BOOST_MINERAL * partCount && + this.target.energy >= LAB_BOOST_ENERGY * partCount) { + return this.target.boostCreep(this.creep, this.data.amount); + } else { + return ERR_NOT_FOUND; + } } } diff --git a/src/creep-tasks/TaskInstances/task_goTo.ts b/src/creep-tasks/TaskInstances/task_goTo.ts index 2133865..9d62864 100644 --- a/src/creep-tasks/TaskInstances/task_goTo.ts +++ b/src/creep-tasks/TaskInstances/task_goTo.ts @@ -2,15 +2,19 @@ import {Task} from '../Task'; export type goToTargetType = { pos: RoomPosition } | RoomPosition; +function hasPos(obj: { pos: RoomPosition } | RoomPosition): obj is { pos: RoomPosition } { + return (<{ pos: RoomPosition } >obj).pos != undefined; +} + export class TaskGoTo extends Task { static taskName = 'goTo'; target: null; constructor(target: goToTargetType, options = {} as TaskOptions) { - if (target instanceof RoomPosition) { - super(TaskGoTo.taskName, {ref: '', pos: target}, options); - } else { + if (hasPos(target)) { super(TaskGoTo.taskName, {ref: '', pos: target.pos}, options); + } else { + super(TaskGoTo.taskName, {ref: '', pos: target}, options); } // Settings this.settings.targetRange = 1; diff --git a/src/creep-tasks/TaskInstances/task_harvest.ts b/src/creep-tasks/TaskInstances/task_harvest.ts index b046292..8fcbe99 100644 --- a/src/creep-tasks/TaskInstances/task_harvest.ts +++ b/src/creep-tasks/TaskInstances/task_harvest.ts @@ -1,6 +1,10 @@ import {Task} from '../Task'; -export type harvestTargetType = Source; +export type harvestTargetType = Source | Mineral; + +function isSource(obj: Source | Mineral): obj is Source { + return (obj).energy != undefined; +} export class TaskHarvest extends Task { @@ -12,11 +16,20 @@ export class TaskHarvest extends Task { } isValidTask() { - return this.creep.carry.energy < this.creep.carryCapacity; + return _.sum(this.creep.carry) < this.creep.carryCapacity; } isValidTarget() { - return this.target && this.target.energy > 0; + // if (this.target && (this.target instanceof Source ? this.target.energy > 0 : this.target.mineralAmount > 0)) { + // // Valid only if there's enough space for harvester to work - prevents doing tons of useless pathfinding + // return this.target.pos.availableNeighbors().length > 0 || this.creep.pos.isNearTo(this.target.pos); + // } + // return false; + if (isSource(this.target)) { + return this.target.energy > 0; + } else { + return this.target.mineralAmount > 0; + } } work() { diff --git a/src/creep-tasks/TaskInstances/task_heal.ts b/src/creep-tasks/TaskInstances/task_heal.ts index 58df667..a9adfb5 100644 --- a/src/creep-tasks/TaskInstances/task_heal.ts +++ b/src/creep-tasks/TaskInstances/task_heal.ts @@ -25,7 +25,7 @@ export class TaskHeal extends Task { if (this.creep.pos.isNearTo(this.target)) { return this.creep.heal(this.target); } else { - this.move(1); + this.moveToTarget(1); } return this.creep.rangedHeal(this.target); } diff --git a/src/creep-tasks/TaskInstances/task_repair.ts b/src/creep-tasks/TaskInstances/task_repair.ts index be971bc..b368ca5 100644 --- a/src/creep-tasks/TaskInstances/task_repair.ts +++ b/src/creep-tasks/TaskInstances/task_repair.ts @@ -22,6 +22,14 @@ export class TaskRepair extends Task { } work() { - return this.creep.repair(this.target); + let result = this.creep.repair(this.target); + if (this.target.structureType == STRUCTURE_ROAD) { + // prevents workers from idling for a tick before moving to next target + let newHits = this.target.hits + this.creep.getActiveBodyparts(WORK) * REPAIR_POWER; + if (newHits > this.target.hitsMax) { + this.finish(); + } + } + return result; } } diff --git a/src/creep-tasks/TaskInstances/task_transferAll.ts b/src/creep-tasks/TaskInstances/task_transferAll.ts index 228b2d1..7d8842c 100644 --- a/src/creep-tasks/TaskInstances/task_transferAll.ts +++ b/src/creep-tasks/TaskInstances/task_transferAll.ts @@ -8,12 +8,20 @@ export class TaskTransferAll extends Task { static taskName = 'transferAll'; target: transferAllTargetType; - constructor(target: transferAllTargetType, options = {} as TaskOptions) { + data: { + skipEnergy?: boolean; + }; + + constructor(target: transferAllTargetType, skipEnergy = false, options = {} as TaskOptions) { super(TaskTransferAll.taskName, target, options); + this.data.skipEnergy = skipEnergy; } isValidTask() { for (let resourceType in this.creep.carry) { + if (this.data.skipEnergy && resourceType == RESOURCE_ENERGY) { + continue; + } let amountInCarry = this.creep.carry[resourceType] || 0; if (amountInCarry > 0) { return true; @@ -23,11 +31,14 @@ export class TaskTransferAll extends Task { } isValidTarget() { - return this.target.storeCapacity - _.sum(this.target.store) >= _.sum(this.creep.carry); + return _.sum(this.target.store) < this.target.storeCapacity; } work() { for (let resourceType in this.creep.carry) { + if (this.data.skipEnergy && resourceType == RESOURCE_ENERGY) { + continue; + } let amountInCarry = this.creep.carry[resourceType] || 0; if (amountInCarry > 0) { return this.creep.transfer(this.target, resourceType); diff --git a/src/creep-tasks/TaskInstances/task_withdraw.ts b/src/creep-tasks/TaskInstances/task_withdraw.ts index 44745fb..99dcee6 100644 --- a/src/creep-tasks/TaskInstances/task_withdraw.ts +++ b/src/creep-tasks/TaskInstances/task_withdraw.ts @@ -8,7 +8,8 @@ export type withdrawTargetType = | StoreStructure | StructureLab | StructureNuker - | StructurePowerSpawn; + | StructurePowerSpawn + | Tombstone; export class TaskWithdraw extends Task { @@ -38,7 +39,7 @@ export class TaskWithdraw extends Task { isValidTarget() { let amount = this.data.amount || 1; let target = this.target; - if (isStoreStructure(target)) { + if (target instanceof Tombstone || isStoreStructure(target)) { return (target.store[this.data.resourceType] || 0) >= amount; } else if (isEnergyStructure(target) && this.data.resourceType == RESOURCE_ENERGY) { return target.energy >= amount; diff --git a/src/creep-tasks/TaskInstances/task_withdrawAll.ts b/src/creep-tasks/TaskInstances/task_withdrawAll.ts new file mode 100644 index 0000000..b961b9a --- /dev/null +++ b/src/creep-tasks/TaskInstances/task_withdrawAll.ts @@ -0,0 +1,32 @@ +import {Task} from '../Task'; + + +export type withdrawAllTargetType = StructureStorage | StructureTerminal | StructureContainer | Tombstone; + +export class TaskWithdrawAll extends Task { + + static taskName = 'withdrawAll'; + target: withdrawAllTargetType; + + constructor(target: withdrawAllTargetType, options = {} as TaskOptions) { + super(TaskWithdrawAll.taskName, target, options); + } + + isValidTask() { + return (_.sum(this.creep.carry) < this.creep.carryCapacity); + } + + isValidTarget() { + return _.sum(this.target.store) > 0; + } + + work() { + for (let resourceType in this.target.store) { + let amountInStore = this.target.store[resourceType] || 0; + if (amountInStore > 0) { + return this.creep.withdraw(this.target, resourceType); + } + } + return -1; + } +} diff --git a/src/creep-tasks/Tasks.ts b/src/creep-tasks/Tasks.ts index a990e12..8858a51 100644 --- a/src/creep-tasks/Tasks.ts +++ b/src/creep-tasks/Tasks.ts @@ -20,9 +20,31 @@ import {TaskUpgrade, upgradeTargetType} from './TaskInstances/task_upgrade'; import {TaskWithdraw, withdrawTargetType} from './TaskInstances/task_withdraw'; import {dropTargetType, TaskDrop} from './TaskInstances/task_drop'; import {TaskTransferAll, transferAllTargetType} from './TaskInstances/task_transferAll'; +import {TaskWithdrawAll, withdrawAllTargetType} from './TaskInstances/task_withdrawAll'; export class Tasks { + /* Tasks.chain allows you to transform a list of tasks into a single task, where each subsequent task in the list + * is the previous task's parent. SetNextPos will chain Task.nextPos as well, preventing creeps from idling for a + * tick between tasks. If an empty list is passed, null is returned. */ + static chain(tasks: ITask[], setNextPos = true): ITask | null { + if (tasks.length == 0) { + return null; + } + if (setNextPos) { + for (let i = 0; i < tasks.length - 1; i++) { + tasks[i].options.nextPos = tasks[i + 1].targetPos; + } + } + // Make the accumulator task from the end and iteratively fork it + let task = _.last(tasks); // start with last task + tasks = _.dropRight(tasks); // remove it from the list + for (let i = (tasks.length - 1); i >= 0; i--) { // iterate over the remaining tasks + task = task.fork(tasks[i]); + } + return task; + } + static attack(target: attackTargetType, options = {} as TaskOptions): TaskAttack { return new TaskAttack(target, options); } @@ -51,9 +73,10 @@ export class Tasks { } static getBoosted(target: getBoostedTargetType, + boostType: _ResourceConstantSansEnergy, amount: number | undefined = undefined, options = {} as TaskOptions): TaskGetBoosted { - return new TaskGetBoosted(target, amount, options); + return new TaskGetBoosted(target, boostType, amount, options); } static getRenewed(target: getRenewedTargetType, options = {} as TaskOptions): TaskGetRenewed { @@ -108,8 +131,10 @@ export class Tasks { return new TaskTransfer(target, resourceType, amount, options); } - static transferAll(target: transferAllTargetType, options = {} as TaskOptions): TaskTransferAll { - return new TaskTransferAll(target, options); + static transferAll(target: transferAllTargetType, + skipEnergy = false, + options = {} as TaskOptions): TaskTransferAll { + return new TaskTransferAll(target, skipEnergy, options); } static upgrade(target: upgradeTargetType, options = {} as TaskOptions): TaskUpgrade { @@ -123,4 +148,8 @@ export class Tasks { return new TaskWithdraw(target, resourceType, amount, options); } + static withdrawAll(target: withdrawAllTargetType, options = {} as TaskOptions): TaskWithdrawAll { + return new TaskWithdrawAll(target, options); + } + } diff --git a/src/creep-tasks/declarations.d.ts b/src/creep-tasks/declarations.d.ts index 1a4f183..3e2ee83 100644 --- a/src/creep-tasks/declarations.d.ts +++ b/src/creep-tasks/declarations.d.ts @@ -23,6 +23,7 @@ interface TaskData { resourceType?: string; amount?: number; signature?: string; + skipEnergy?: boolean; } interface protoTask { @@ -59,7 +60,7 @@ interface ITask extends protoTask { isValid(): boolean; - move(): number; + moveToTarget(range?: number): number; run(): number | void; diff --git a/src/creep-tasks/prototypes.ts b/src/creep-tasks/prototypes.ts index 388e107..b12592e 100644 --- a/src/creep-tasks/prototypes.ts +++ b/src/creep-tasks/prototypes.ts @@ -34,8 +34,9 @@ Object.defineProperty(Creep.prototype, 'task', { } // Register references to creep task.creep = this; - this._task = task; } + // Clear cache + this._task = null; }, }); diff --git a/src/creep-tasks/utilities/initializer.ts b/src/creep-tasks/utilities/initializer.ts index 3d6e715..17dce9a 100644 --- a/src/creep-tasks/utilities/initializer.ts +++ b/src/creep-tasks/utilities/initializer.ts @@ -24,6 +24,8 @@ import {TaskUpgrade, upgradeTargetType} from '../TaskInstances/task_upgrade'; import {dropTargetType, TaskDrop} from '../TaskInstances/task_drop'; import {deref, derefRoomPosition} from './helpers'; import {TaskInvalid} from '../TaskInstances/task_invalid'; +import {TaskTransferAll} from '../TaskInstances/task_transferAll'; +import {TaskWithdrawAll, withdrawAllTargetType} from '../TaskInstances/task_withdrawAll'; export function initializeTask(protoTask: protoTask): Task { @@ -52,7 +54,8 @@ export function initializeTask(protoTask: protoTask): Task { task = new TaskFortify(target as fortifyTargetType); break; case TaskGetBoosted.taskName: - task = new TaskGetBoosted(target as getBoostedTargetType); + task = new TaskGetBoosted(target as getBoostedTargetType, + protoTask.data.resourceType as _ResourceConstantSansEnergy); break; case TaskGetRenewed.taskName: task = new TaskGetRenewed(target as getRenewedTargetType); @@ -78,9 +81,6 @@ export function initializeTask(protoTask: protoTask): Task { case TaskRangedAttack.taskName: task = new TaskRangedAttack(target as rangedAttackTargetType); break; - case TaskWithdraw.taskName: - task = new TaskWithdraw(target as withdrawTargetType); - break; case TaskRepair.taskName: task = new TaskRepair(target as repairTargetType); break; @@ -93,9 +93,18 @@ export function initializeTask(protoTask: protoTask): Task { case TaskTransfer.taskName: task = new TaskTransfer(target as transferTargetType); break; + case TaskTransferAll.taskName: + task = new TaskTransferAll(target as transferAllTargetType); + break; case TaskUpgrade.taskName: task = new TaskUpgrade(target as upgradeTargetType); break; + case TaskWithdraw.taskName: + task = new TaskWithdraw(target as withdrawTargetType); + break; + case TaskWithdrawAll.taskName: + task = new TaskWithdrawAll(target as withdrawAllTargetType); + break; default: console.log(`Invalid task name: ${taskName}! task.creep: ${protoTask._creep.name}. Deleting from memory!`); task = new TaskInvalid(target as any); diff --git a/typings/creep-tasks.d.ts b/typings/creep-tasks.d.ts index 6ccf916..5464be8 100644 --- a/typings/creep-tasks.d.ts +++ b/typings/creep-tasks.d.ts @@ -3,6 +3,9 @@ declare module 'creep-tasks' { } declare class Tasks { + + static chain(tasks: ITask[], setNextPos?: boolean): ITask | null + static attack(target: attackTargetType, options?: TaskOptions): ITask; static build(target: buildTargetType, options?: TaskOptions): ITask; @@ -16,8 +19,8 @@ declare class Tasks { static fortify(target: fortifyTargetType, options?: TaskOptions): ITask; - static getBoosted(target: getBoostedTargetType, amount?: number | undefined, - options?: TaskOptions): ITask; + static getBoosted(target: getBoostedTargetType, boostType: _ResourceConstantSansEnergy, + amount?: number | undefined, options?: TaskOptions): ITask; static getRenewed(target: getRenewedTargetType, options?: TaskOptions): ITask; @@ -45,12 +48,14 @@ declare class Tasks { static transfer(target: transferTargetType, resourceType?: ResourceConstant, amount?: number | undefined, options?: TaskOptions): ITask; - static transferAll(target: transferAllTargetType, options?: TaskOptions): ITask; + static transferAll(target: transferAllTargetType, skipEnergy?: boolean, options?: TaskOptions): ITask; static upgrade(target: upgradeTargetType, options?: TaskOptions): ITask; static withdraw(target: withdrawTargetType, resourceType?: ResourceConstant, amount?: number | undefined, options?: TaskOptions): ITask; + + static withdrawAll(target: withdrawAllTargetType, options?: TaskOptions): ITask; } type attackTargetType = Creep | Structure; @@ -84,7 +89,9 @@ type withdrawTargetType = | StoreStructure | StructureLab | StructureNuker - | StructurePowerSpawn; + | StructurePowerSpawn + | Tombstone; +type withdrawAllTargetType = StructureStorage | StructureTerminal | StructureContainer | Tombstone; interface EnergyStructure extends Structure { energy: number; @@ -118,10 +125,10 @@ interface TaskOptions { } interface TaskData { - quiet?: boolean; resourceType?: string; amount?: number; signature?: string; + skipEnergy?: boolean; } interface protoTask { @@ -160,7 +167,7 @@ interface ITask extends protoTask { isValid(): boolean; - move(range?: number): number; + moveToTarget(range?: number): number; run(): number | void;