Add a Task system
This commit is contained in:
parent
216d44ab5c
commit
3922f367a2
12 changed files with 499 additions and 28 deletions
|
|
@ -1,12 +0,0 @@
|
|||
const BodyPartCosts = {
|
||||
[MOVE]: 50,
|
||||
[WORK]: 100,
|
||||
[CARRY]: 50,
|
||||
[ATTACK]: 80,
|
||||
[RANGED_ATTACK]: 150,
|
||||
[HEAL]: 250,
|
||||
[CLAIM]: 600,
|
||||
[TOUGH]: 10,
|
||||
};
|
||||
|
||||
export default BodyPartCosts;
|
||||
96
src/Proto/index.ts
Normal file
96
src/Proto/index.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
declare global {
|
||||
interface RoomMemory {
|
||||
sources: {
|
||||
[id: Id<Source>]: SourceMemory;
|
||||
};
|
||||
spawn: Id<StructureSpawn> | null;
|
||||
_spawnCacheTimeout?: number;
|
||||
}
|
||||
|
||||
interface SourceMemory {
|
||||
container: Id<StructureContainer> | null;
|
||||
}
|
||||
|
||||
interface Source {
|
||||
memory: SourceMemory;
|
||||
get container(): StructureContainer | null;
|
||||
}
|
||||
|
||||
interface Room {
|
||||
get sources(): Source[];
|
||||
get spawn(): StructureSpawn | null;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(Room.prototype, "sources", {
|
||||
get: function (this: Room) {
|
||||
if (this == Room.prototype || this == undefined) return undefined;
|
||||
if (!this.memory.sources) {
|
||||
this.memory.sources = {};
|
||||
const sources = this.find(FIND_SOURCES);
|
||||
for (const source of sources) {
|
||||
this.memory.sources[source.id] = {
|
||||
container: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
return Object.keys(this.memory.sources).map(Game.getObjectById);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(Room.prototype, "spawn", {
|
||||
get: function (this: Room) {
|
||||
if (this == Room.prototype || this == undefined) return undefined;
|
||||
if (!this.memory.spawn) {
|
||||
if (this.memory._spawnCacheTimeout == null
|
||||
|| this.memory._spawnCacheTimeout > Game.time) {
|
||||
const spawns = this.find(FIND_MY_SPAWNS);
|
||||
if (spawns.length > 0) {
|
||||
this.memory.spawn = spawns[0].id;
|
||||
}
|
||||
else {
|
||||
this.memory._spawnCacheTimeout = Game.time + 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.memory.spawn == null
|
||||
? null
|
||||
: Game.getObjectById(this.memory.spawn);
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(Source.prototype, "memory", {
|
||||
get: function (this: Source) {
|
||||
return this.room.memory.sources[this.id];
|
||||
},
|
||||
set: function (this: Source, mem: SourceMemory) {
|
||||
this.room.memory.sources[this.id] = mem;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(Source.prototype, "container", {
|
||||
get: function (this: Source) {
|
||||
if (this.memory.container == null
|
||||
|| Game.getObjectById(this.memory.container) == null) {
|
||||
const containers = this.pos.findInRange(FIND_STRUCTURES, 1, {
|
||||
filter: (s: Structure) => s.structureType === STRUCTURE_CONTAINER,
|
||||
}) as StructureContainer[];
|
||||
if (containers.length > 0) {
|
||||
this.memory.container = containers[0].id;
|
||||
}
|
||||
}
|
||||
return this.memory.container == null
|
||||
? null
|
||||
: Game.getObjectById(this.memory.container);
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
export default {};
|
||||
34
src/Tasks/Build.ts
Normal file
34
src/Tasks/Build.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import profiler from "screeps-profiler";
|
||||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
export const Build
|
||||
= (target: ConstructionSite): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Build,
|
||||
options: {},
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const runBuild = profiler.registerFN((creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (creep.store.getUsedCapacity(RESOURCE_ENERGY) === 0) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as ConstructionSite;
|
||||
if (target == null && task.targetPos.roomName === creep.room.name) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (target == null
|
||||
|| creep.build(target) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}
|
||||
return TaskStatus.DONE;
|
||||
}, "runBuild");
|
||||
49
src/Tasks/Harvest.ts
Normal file
49
src/Tasks/Harvest.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import profiler from "screeps-profiler";
|
||||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
interface HarvestOptions {
|
||||
stopWhenFull: boolean;
|
||||
}
|
||||
|
||||
interface HarvestData {
|
||||
resource: ResourceConstant;
|
||||
};
|
||||
|
||||
const defaultOptions: HarvestOptions = {
|
||||
stopWhenFull: true,
|
||||
};
|
||||
|
||||
export const Harvest
|
||||
= (target: Source | Mineral<MineralConstant>,
|
||||
opts: Partial<HarvestOptions> = {}): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Harvest,
|
||||
options: { ...defaultOptions, ...opts },
|
||||
data: {
|
||||
resource: target instanceof Source
|
||||
? RESOURCE_ENERGY
|
||||
: target.mineralType,
|
||||
},
|
||||
});
|
||||
|
||||
export const runHarvest = profiler.registerFN((creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as Source | Mineral<MineralConstant> | null;
|
||||
const opts = task.options as HarvestOptions;
|
||||
const data = task.data as HarvestData;
|
||||
|
||||
if (opts.stopWhenFull && creep.store.getFreeCapacity(data.resource) == 0) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (target == null
|
||||
|| creep.harvest(target) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
}
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}, "runHarvest");
|
||||
34
src/Tasks/Repair.ts
Normal file
34
src/Tasks/Repair.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import profiler from "screeps-profiler";
|
||||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
export const Repair
|
||||
= (target: Structure): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Repair,
|
||||
options: {},
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const runRepair = profiler.registerFN((creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (creep.store.energy === 0) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as Structure;
|
||||
if (target == null && task.targetPos.roomName === creep.room.name) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (target == null
|
||||
|| creep.repair(target) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}
|
||||
return TaskStatus.DONE;
|
||||
}, "runRepair");
|
||||
97
src/Tasks/Task.ts
Normal file
97
src/Tasks/Task.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import {
|
||||
packId, packPos, unpackId, unpackPos,
|
||||
} from "../../deps/screeps-packrat/src/packrat";
|
||||
|
||||
export enum TaskType {
|
||||
Harvest,
|
||||
Upgrade,
|
||||
Withdraw,
|
||||
Transfer,
|
||||
Build,
|
||||
Repair,
|
||||
}
|
||||
|
||||
export enum TaskStatus {
|
||||
DONE,
|
||||
IN_PROGRESS,
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface CreepMemory {
|
||||
task?: string;
|
||||
}
|
||||
interface Creep {
|
||||
readonly isIdle: boolean;
|
||||
task: TaskData | null;
|
||||
_task?: TaskData | null;
|
||||
}
|
||||
interface RoomObject {
|
||||
readonly assignedCreeps: Creep[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TaskData {
|
||||
type: TaskType;
|
||||
targetPos: RoomPosition;
|
||||
target: RoomObject & _HasId | null;
|
||||
options: object;
|
||||
data: object;
|
||||
}
|
||||
|
||||
const packTaskData = (td: TaskData): string =>
|
||||
String.fromCharCode(td.type + 65)
|
||||
+ packPos(td.targetPos)
|
||||
+ JSON.stringify({ o: td.options, d: td.data, t: td.target?.id });
|
||||
|
||||
const unpackTaskData = (s: string): TaskData => {
|
||||
const { o: options, d: data, t: targetId } = JSON.parse(s.substring(3));
|
||||
const target = Game.getObjectById(targetId as Id<RoomObject & _HasId>);
|
||||
return {
|
||||
type: s.charCodeAt(0) - 65,
|
||||
targetPos: unpackPos(s.substring(1, 3)),
|
||||
target,
|
||||
options,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
Object.defineProperty(Creep.prototype, "isIdle", {
|
||||
get: function (this: Creep) {
|
||||
return this.memory.task == null;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(Creep.prototype, "task", {
|
||||
get: function (this: Creep) {
|
||||
if (this._task != null) {
|
||||
return this._task;
|
||||
}
|
||||
if (this.memory.task == null) {
|
||||
return null;
|
||||
}
|
||||
this._task = unpackTaskData(this.memory.task);
|
||||
return this._task;
|
||||
},
|
||||
set: function (this: Creep, task: TaskData | null) {
|
||||
this._task = task;
|
||||
if (task != null) {
|
||||
this.memory.task = packTaskData(task);
|
||||
}
|
||||
else {
|
||||
delete this.memory.task;
|
||||
}
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(RoomObject.prototype, "assignedCreeps", {
|
||||
get: function (this: RoomObject) {
|
||||
if ("id" in this) {
|
||||
return Object.values(Game.creeps)
|
||||
.filter(x => x.task?.target?.id == this.id);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
41
src/Tasks/Transfer.ts
Normal file
41
src/Tasks/Transfer.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
interface TransferOptions {
|
||||
// The amount of resources to transfer
|
||||
amount: number | null;
|
||||
// The type of resource to transfer
|
||||
resource: ResourceConstant;
|
||||
}
|
||||
|
||||
const defaultOptions: TransferOptions = {
|
||||
amount: null,
|
||||
resource: RESOURCE_ENERGY,
|
||||
};
|
||||
|
||||
export const Transfer
|
||||
= (target: Structure | Creep | PowerCreep,
|
||||
opts: Partial<TransferOptions> = {}): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Transfer,
|
||||
options: { ...defaultOptions, ...opts },
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const runTransfer = (creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as Structure | Creep | PowerCreep;
|
||||
const opts = task.options as TransferOptions;
|
||||
|
||||
if (target == null
|
||||
|| creep.transfer(
|
||||
target, opts.resource, opts.amount ?? undefined) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}
|
||||
return TaskStatus.DONE;
|
||||
};
|
||||
30
src/Tasks/Upgrade.ts
Normal file
30
src/Tasks/Upgrade.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import profiler from "screeps-profiler";
|
||||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
export const Upgrade
|
||||
= (target: StructureController): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Upgrade,
|
||||
options: {},
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const runUpgrade = profiler.registerFN((creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (creep.store.energy === 0) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as StructureController | null;
|
||||
|
||||
if (target == null
|
||||
|| creep.upgradeController(target) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
}
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}, "runUpgrade");
|
||||
55
src/Tasks/Withdraw.ts
Normal file
55
src/Tasks/Withdraw.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { TaskData, TaskStatus, TaskType } from "./Task";
|
||||
|
||||
interface WithdrawOptions {
|
||||
// The maximum number of resources the creep should carry after the withdraw.
|
||||
limit: number | null;
|
||||
// The amount of resources to withdraw
|
||||
amount: number | null;
|
||||
// The type of resource to withdraw
|
||||
resource: ResourceConstant;
|
||||
}
|
||||
|
||||
const defaultOptions: WithdrawOptions = {
|
||||
limit: null,
|
||||
amount: null,
|
||||
resource: RESOURCE_ENERGY,
|
||||
};
|
||||
|
||||
export const Withdraw
|
||||
= (target: Structure | Tombstone | Ruin,
|
||||
opts: Partial<WithdrawOptions> = {}): TaskData => ({
|
||||
target,
|
||||
targetPos: target.pos,
|
||||
type: TaskType.Withdraw,
|
||||
options: { ...defaultOptions, ...opts },
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const runWithdraw = (creep: Creep): TaskStatus => {
|
||||
const task = creep.task;
|
||||
if (task == null) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
const target = task.target as Structure | Tombstone | Ruin;
|
||||
const opts = task.options as WithdrawOptions;
|
||||
|
||||
if (opts.limit != null
|
||||
&& creep.store.getUsedCapacity(opts.resource) >= opts.limit) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
const capacity = creep.store.getFreeCapacity(opts.resource);
|
||||
const amount = Math.min(opts.amount ?? capacity,
|
||||
opts.limit ?? capacity - creep.store.getUsedCapacity(opts.resource));
|
||||
|
||||
if (amount <= 0) {
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
|
||||
if (target == null
|
||||
|| creep.withdraw(target, opts.resource, amount) === ERR_NOT_IN_RANGE) {
|
||||
creep.travelTo(task.targetPos);
|
||||
return TaskStatus.IN_PROGRESS;
|
||||
}
|
||||
return TaskStatus.DONE;
|
||||
};
|
||||
56
src/Tasks/index.ts
Normal file
56
src/Tasks/index.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Harvest, runHarvest } from "./Harvest";
|
||||
import { runUpgrade, Upgrade } from "./Upgrade";
|
||||
|
||||
import { TaskType, TaskStatus, TaskData } from "./Task";
|
||||
import { runWithdraw, Withdraw } from "./Withdraw";
|
||||
import { runTransfer, Transfer } from "./Transfer";
|
||||
import { Build, runBuild } from "./Build";
|
||||
import { Repair, runRepair } from "./Repair";
|
||||
import profiler from "screeps-profiler";
|
||||
export { TaskType, TaskStatus } from "./Task";
|
||||
|
||||
declare global {
|
||||
interface Creep {
|
||||
run: (generator?: (creep: Creep) => TaskData | null) => void;
|
||||
}
|
||||
}
|
||||
|
||||
const runTask = profiler.registerFN((creep: Creep): TaskStatus => {
|
||||
switch (creep.task?.type) {
|
||||
case TaskType.Harvest:
|
||||
return runHarvest(creep);
|
||||
case TaskType.Upgrade:
|
||||
return runUpgrade(creep);
|
||||
case TaskType.Withdraw:
|
||||
return runWithdraw(creep);
|
||||
case TaskType.Transfer:
|
||||
return runTransfer(creep);
|
||||
case TaskType.Build:
|
||||
return runBuild(creep);
|
||||
case TaskType.Repair:
|
||||
return runRepair(creep);
|
||||
default:
|
||||
return TaskStatus.DONE;
|
||||
}
|
||||
}, "runTask");
|
||||
|
||||
Creep.prototype.run = function (generator?: (creep: Creep) => TaskData | null) {
|
||||
const status = runTask(this);
|
||||
if (status === TaskStatus.DONE) {
|
||||
this.task = null;
|
||||
if (generator != null) {
|
||||
this.task = generator(this);
|
||||
runTask(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default profiler.registerObject({
|
||||
Harvest,
|
||||
Upgrade,
|
||||
Withdraw,
|
||||
Transfer,
|
||||
Build,
|
||||
Repair,
|
||||
...TaskStatus,
|
||||
}, "Tasks");
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import BodyPartCosts from "../Constants/BodyPartCosts";
|
||||
import * as Workers from "./index";
|
||||
|
||||
describe("Test Creep Workers", () => {
|
||||
console.log(Workers.Clerk);
|
||||
for (const [moduleName, worker] of Object.entries(Workers)) {
|
||||
test(`${moduleName}: Body parts cost calculation is correct`, () => {
|
||||
for (let cost = 0; cost < 1500; cost++) {
|
||||
expect(
|
||||
worker.bodyDefinition(cost)
|
||||
.map(x => BodyPartCosts[x]).reduce((x, y) => x + y, 0),
|
||||
).toBeLessThanOrEqual(cost);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
7
src/screeps-profiler.d.ts
vendored
Normal file
7
src/screeps-profiler.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
declare module "screeps-profiler" {
|
||||
export const registerFN: <T extends Function>(f: T, n?: string) => T;
|
||||
export const registerObject: <T extends Object>(f: T, n?: string) => T;
|
||||
export const registerClass: <T extends Object>(f: T, n?: string) => T;
|
||||
export const enable: () => void;
|
||||
export const wrap: (f: () => void) => () => void;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue