-
Notifications
You must be signed in to change notification settings - Fork 41
Framework Overview
Depending on whether you want to write code using this framework or just understand my own code so you can easily take down my rooms, it will help to know about the framework.
- Main-loop architecture
- Spawn Order
- Persistent data (memory)
- Phase functions
- Overview of concrete classes
The archictecture of the framework is best understood by looking at main.ts and the code within module.exports.loop
. The following figure shows how phases (init, roleCall, actions, finalize) are executed within the loop.
Each phase is executed completely for each operation before moving on to the next phase. This allows you to assume, for example, that every initOperation()
and initMission()
function has been executed before any roleCall()
function is executed.
For operations that use the same SpawnGroup, operation.priority
determines which operation will have its creeps spawned first.
Within operations, spawn order for each missions is determined by the order in which they are added to the operation. Take the following initOperation()
function:
initOperation() {
// creeps for upgrading controller, these will spawn first as they appear sooner in the function
let boostUpgraders = this.flag.room.controller.level < 8;
this.addMission(new UpgradeMission(this, boostUpgraders));
// these will only begin to spawn after the creeps in UpgradeMission are fully spawned
this.addMission(new PaverMission(this));
}
Operation and Mission objects are instantiated from scratch each game tick. Whenever possible, member variables are assigned by accessing the Game object and its convience properties (i.e., Game.structures
) . For everything else, persistent data can be accessed for each Operation through the operation.memory
and mission.memory
properties, which are hosted on the flag that is being used to bootstrap the operation.
The execution of phase functions follows the pattern demonstrated in the Operation.init()
function below:
init() {
try { this.initOperation(); }
catch (e) {
console.log("error caught in initOperation phase, operation:", this.name);
console.log(e.stack);
}
for (let missionName in this.missions) {
try { this.missions[missionName].initMission(); }
catch (e) {
console.log("error caught in initMission phase, operation:", this.name, "mission:", missionName);
console.log(e.stack);
}
}
}
abstract initOperation();
Missions are added to operation.missions
within the initOperation()
function (see code block above for example). Then, each mission.initMission()
function gets executed in the order it was added. These are surrounded by try/catch blocks, so that when code fails it will fail gracefully. You needn't worry about errors in one mission causing other operations/missions to cease functioning.
Previously, the majority of creep behavior was defined in functions that were added to the Creep
prototype. This practice creates a bit of overhead with typescript workflow, as it is necessary to define each type modification in the custom.d.ts
file. To make things a little smoother I introduced the Agent class and moved all the prototype functions there. It acts as a mission-aware wrapper for your creeps, and I've found it nicer to work with. As such, mission.headCount()
now returns an array of Agent objects rather than Creep objects.
For better encapsulation, missions are generally unaware of other missions belonging to the same operation. The downside is that some data is relevant to more than one mission, and it would be handy to have a way to share this data and provide a means of coordination where it is useful. To fill this gap, I've created a Guru
base class and a few classes that extend it.
For example, DefenseGuru
(which keeps track of invaders) is instantiated in ControllerOperation
's init phase and passed through the constructor to any Mission that can find that data useful. For example, DefenseMission
uses it to send defender creeps at player invaders and MasonMission
will eventually use it to give wall-builders unique behavior during attacks. Similarly, I've written a RaidGuru
class that is used by several mission types.
This repository includes all the missions/operations that make up the bonzaiferroni AI. Players looking to write a completely original AI can simply take the framework and write their own concrete classes that extend Operation and Mission.
The following is a non-comprehensive list of the Operations/missions you can find in this repository:
- QuadOperation: Manages all missions relative to an owned room, including upgrading, tower defense, spawn refilling, and more
- FlexOperation: All the functions of QuadOperation but uses a flexible layout that is compatible with a wider variety of rooms
- MiningOperation: Remote harvesting in non-SK rooms
- KeeperOperation: Remote harvesting in SK rooms
- ConquestOperation: Spawn creeps to be used to settle a new owned-room.
- Defense
- BodyguardMission: Protect creeps working in non-owned rooms from invaders
- EnhancedBodyguardMission: Protect creeps from boosted invaders in SK-rooms and cores
- Infrastructure
- PaverMission: Keep roads repaired
- TerminalNetworkMission: Trade resources with other rooms and with ally rooms
- LinkNetworkMission: Send resources around an owned-room
- BuildMission: Build structures in an owned-room
- RemoteBuildMission: Build structures in a non-owned-room
- RefillMission: Refill spawns and extensions with energy
- IgorMission: Manage labs and special resource use
- Resource gathering
- MiningMission: Conducts energy mining activities relative to a single energy source
- LinkMiningMission: Uses a link to fire energy mined from a source to a storage in an owned-room.
- EmergencyMiningMission: Builds miners small enough to resume energy in a room that has suffered some critical failure
- GeologyMission: Like MiningMission, but manages a Mineral source
- Progress
- UpgradeMission: Upgrade controllers