Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
test-bots/

### Node ###
# Logs
logs
Expand Down
7 changes: 5 additions & 2 deletions agent/create-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ function createAgent(blueprint = {}) {
race: Race.RANDOM,
...blueprint.settings,
},
interface: blueprint.interface || { raw: true },
interface: blueprint.interface || { raw: true, rawCropToPlayableArea: true },
canAfford(unitTypeId, earmarkName) {
const { data } = this._world;
const { minerals, vespene } = this;

const earmarks = data.getEarmarkTotals(earmarkName);
const unitType = data.getUnitTypeData(unitTypeId);

// console.log("current earmarks", earmarks);
// console.log("mineral cost:", unitType.mineralCost);

const result = (
(minerals - earmarks.minerals >= unitType.mineralCost) &&
Expand Down Expand Up @@ -185,7 +188,7 @@ function createAgent(blueprint = {}) {
* @TODO: the first time we see an enemy unit, we should set the value of this on the agent and then
* memoize it
*/
this.enemy = {
this.opponent = {
race: enemyPlayer.raceRequested !== Race.RANDOM ? enemyPlayer.raceRequested : Race.NORACE,
};
}
Expand Down
13 changes: 13 additions & 0 deletions constants/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,17 @@ const AbilityDataTarget = {

const { valuesById: AbilityDataTargetId } = api.lookupType('AbilityData').lookupEnum('Target');

/**
* @enum {SC2APIProtocol.CloakState}
*/
const CloakState = {
CLOAKED: 1,
CLOAKEDDETECTED: 2,
NOTCLOAKED: 3,
};

const { valuesById: CloakStateId } = api.lookupEnum('CloakState');

module.exports = {
AbilityDataTarget,
AbilityDataTargetId,
Expand All @@ -198,6 +209,8 @@ module.exports = {
BuildOrder,
BuildOrderId,
BuildResult,
CloakState,
CloakStateId,
Difficulty,
DifficultyId,
DisplayType,
Expand Down
12 changes: 11 additions & 1 deletion engine/create-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const argv = require('yargs')
.number('StartPort')
.number('GamePort')
.string('LadderServer')
.string('OpponentId')
.argv;
const pascalCase = require('pascal-case');
// const chalk = require('chalk');
Expand Down Expand Up @@ -63,6 +64,7 @@ function createEngine(options = {}) {

/** @type {Engine} */
const engine = {
getWorld() { return world; },
_totalLoopDelay: 0,
_gameLeft: false,
launcher,
Expand Down Expand Up @@ -163,7 +165,7 @@ function createEngine(options = {}) {
};

if (isManaged) {
let sPort = /** @type {number} */ argv.StartPort + 1;
let sPort = argv.StartPort + 1;

participant = {
...participant,
Expand Down Expand Up @@ -209,6 +211,11 @@ function createEngine(options = {}) {
async firstRun() {
const { data, resources, agent } = world;

if (isManaged) {
agent.opponent = agent.opponent || {};
agent.opponent.id = argv.OpponentId;
}

/** @type {SC2APIProtocol.ResponseData} */
const gameData = await _client.data({
abilityId: true,
Expand Down Expand Up @@ -362,6 +369,9 @@ function createEngine(options = {}) {
// debug system runs last because it updates the in-client debug display
return debugSystem(world);
},
shutdown() {
world.resources.get().actions._client.close();
},
_lastRequest: null,
};

Expand Down
53 changes: 50 additions & 3 deletions engine/create-unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

const UnitType = require("../constants/unit-type");
const Ability = require("../constants/ability");
const { Alliance, WeaponTargetType, Attribute } = require("../constants/enums");
const { TownhallRace } = require("../constants/race-map");
const { Alliance, WeaponTargetType, Attribute, CloakState, Race } = require("../constants/enums");
const {
techLabTypes,
reactorTypes,
Expand All @@ -26,15 +27,43 @@ function createUnit(unitData, { data, resources }) {

const { alliance } = unitData;

/** @type {Unit} */
const blueprint = {
tag: unitData.tag,
lastSeen: frame.getGameLoop(),
noQueue: unitData.orders.length === 0,
labels: new Map(),
_availableAbilities: [],
async burrow() {
async inject(t) {
if (this.canInject()) {
let target;
if (t) {
target = t;
} else {
const [closestIdleHatch] = units.getClosest(
this.pos,
units.getById(TownhallRace[Race.ZERG]),
3
).filter(u => u.isIdle());

if (closestIdleHatch) {
target = closestIdleHatch;
} else {
return;
}
}

return actions.do(Ability.EFFECT_INJECTLARVA, this.tag, { target });
}
},
async blink(target, opts = {}) {
if (this.canBlink()) {
return actions.do(Ability.EFFECT_BLINK, this.tag, { target, ...opts });
}
},
async burrow(opts = {}) {
if (this.is(UnitType.WIDOWMINE)) {
return actions.do(Ability.BURROWDOWN, this.tag);
return actions.do(Ability.BURROWDOWN, this.tag, opts);
}
},
async toggle(options = {}) {
Expand Down Expand Up @@ -68,6 +97,9 @@ function createUnit(unitData, { data, resources }) {
getLabel(name) {
return this.labels.get(name);
},
getLife() {
return this.health / this.healthMax * 100;
},
abilityAvailable(id) {
return this._availableAbilities.includes(id);
},
Expand All @@ -80,12 +112,18 @@ function createUnit(unitData, { data, resources }) {
is(type) {
return this.unitType === type;
},
isCloaked() {
return this.cloak !== CloakState.NOTCLOAKED;
},
isConstructing() {
return this.orders.some(o => constructionAbilities.includes(o.abilityId));
},
isCombatUnit() {
return combatTypes.includes(this.unitType);
},
isEnemy() {
return this.alliance === Alliance.ENEMY;
},
isFinished() {
return this.buildProgress >= 1;
},
Expand Down Expand Up @@ -130,6 +168,9 @@ function createUnit(unitData, { data, resources }) {
// if this unit wasn't updated this frame, this will be false
return this.lastSeen === frame.getGameLoop();
},
isIdle() {
return this.noQueue;
},
isStructure() {
return this.data().attributes.includes(Attribute.STRUCTURE);
},
Expand All @@ -141,6 +182,12 @@ function createUnit(unitData, { data, resources }) {
const addon = units.getByTag(this.addOnTag);
return techLabTypes.includes(addon.unitType);
},
canInject() {
return this.abilityAvailable(Ability.EFFECT_INJECTLARVA);
},
canBlink() {
return this.abilityAvailable(Ability.EFFECT_BLINK) || this.abilityAvailable(Ability.EFFECT_BLINK_STALKER);
},
canMove() {
return this._availableAbilities.includes(Ability.MOVE);
},
Expand Down
4 changes: 2 additions & 2 deletions engine/create-world.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const MapManager = require('../resources/map');
const UnitManager = require('../resources/units');

/** @returns {World} */
function createWorld() {
function createWorld(client) {
const world = {
agent: null,
data: null,
Expand All @@ -26,7 +26,7 @@ function createWorld() {
debug: Debugger(world),
units: UnitManager(world),
events: EventChannel(world),
actions: ActionManager(world),
actions: ActionManager(world, client),
});

return world;
Expand Down
16 changes: 15 additions & 1 deletion engine/data-storage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

const debugEarmark = require('debug')('sc2:debug:earmark');
const { Race, Attribute } = require('../constants/enums');
const { AbilitiesByUnit } = require('../constants');

/**
Expand Down Expand Up @@ -29,7 +30,20 @@ function createDataManager() {
.map(unitAbility => parseInt(unitAbility[0], 10));
},
getUnitTypeData(unitTypeId) {
return this.get('units')[unitTypeId];
/** @type {SC2APIProtocol.UnitTypeData} */
const unitData = this.get('units')[unitTypeId];

/**
* Fixes unit cost for zerg structures (removes the 'drone cost' inflation)
*/
if (unitData.race === Race.ZERG && unitData.attributes.includes(Attribute.STRUCTURE)) {
return {
...unitData,
mineralCost: unitData.mineralCost - 50,
};
} else {
return unitData;
}
},
getUpgradeData(upgradeId) {
return this.get('upgrades')[upgradeId];
Expand Down
37 changes: 22 additions & 15 deletions engine/launcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,13 @@ const path = require('path');
const findP = require('find-process');

let EXECUTE_INFO_PATH;
if (os.platform() === 'darwin') {
EXECUTE_INFO_PATH = path.join('Library', 'Application Support', 'Blizzard', 'StarCraft II', 'ExecuteInfo.txt');
} else {
EXECUTE_INFO_PATH = path.join('Documents', 'StarCraft II', 'ExecuteInfo.txt');
}

const HOME_DIR = os.homedir();

const executeInfoText = fs.readFileSync(path.join(HOME_DIR, EXECUTE_INFO_PATH)).toString();
const executablePath = executeInfoText.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/m)[2];

const parsedPath = executablePath.split(path.sep);
const execName = parsedPath[parsedPath.length - 1];

const basePath = parsedPath.slice(0, parsedPath.findIndex(s => s === 'StarCraft II') + 1).join(path.sep);
let executablePath;
let execName;
let basePath;

/** @type {Launcher} */
async function launcher(options = {}) {
setupFilePaths();
const opts = {
listen: '127.0.0.1',
port: 5000,
Expand Down Expand Up @@ -119,4 +108,22 @@ async function findMap(mapName) {
throw new Error(`Map "${mapName}" not found`);
}

function setupFilePaths() {
if (os.platform() === 'darwin') {
EXECUTE_INFO_PATH = path.join('Library', 'Application Support', 'Blizzard', 'StarCraft II', 'ExecuteInfo.txt');
} else {
EXECUTE_INFO_PATH = path.join('Documents', 'StarCraft II', 'ExecuteInfo.txt');
}

const HOME_DIR = os.homedir();

const executeInfoText = fs.readFileSync(path.join(HOME_DIR, EXECUTE_INFO_PATH)).toString();
executablePath = executeInfoText.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/m)[2];

const parsedPath = executablePath.split(path.sep);
execName = parsedPath[parsedPath.length - 1];

basePath = parsedPath.slice(0, parsedPath.findIndex(s => s === 'StarCraft II') + 1).join(path.sep);
}

module.exports = { launcher, findMap };
21 changes: 17 additions & 4 deletions interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ interface SystemWrapper<T> {
_system: System;
}

type Enemy = {
race: SC2APIProtocol.Race;
type Opponent = {
id?: string;
race?: SC2APIProtocol.Race;
}

interface PlayerData extends SC2APIProtocol.PlayerCommon, SC2APIProtocol.PlayerRaw { }
Expand All @@ -122,7 +123,7 @@ interface Agent extends PlayerData {
canAffordUpgrade: (upgradeId: number) => boolean;
hasTechFor: (unitTypeId: number) => boolean;
race?: SC2APIProtocol.Race;
enemy?: Enemy;
opponent?: Opponent;
settings: SC2APIProtocol.PlayerSetup;
systems: SystemWrapper<System>[];
use: (sys: (SystemWrapper<System> | SystemWrapper<System>[])) => void;
Expand All @@ -140,25 +141,36 @@ interface Unit extends SC2APIProtocol.Unit {
availableAbilities: () => Array<number>;
data: () => SC2APIProtocol.UnitTypeData;
is: (unitType: UnitTypeId) => boolean;
isCloaked: () => boolean;
isConstructing: () => boolean;
isCombatUnit: () => boolean;
isEnemy: () => boolean;
isFinished: () => boolean;
isWorker: () => boolean;
isTownhall: () => boolean;
isGasMine: () => boolean;
isMineralField: () => boolean;
isStructure: () => boolean;
isIdle: () => boolean;
isCurrent: () => boolean;
isHolding: () => boolean;
isGathering: (type?: 'minerals' | 'vespene') => boolean;
isReturning: () => boolean;
hasReactor: () => boolean;
hasTechLab: () => boolean;
hasNoLabels: () => boolean;
canInject: () => boolean;
canBlink: () => boolean;
canMove: () => boolean;
canShootUp: () => boolean;
update: (unit: SC2APIProtocol.Unit) => void;
inject: (target?: Unit) => Promise<SC2APIProtocol.ResponseAction>;
blink: (target: Point2D, opts: AbilityOptions) => Promise<SC2APIProtocol.ResponseAction>;
toggle: (options: AbilityOptions) => Promise<SC2APIProtocol.ResponseAction>;
burrow: (options: AbilityOptions) => Promise<SC2APIProtocol.ResponseAction>;
addLabel: (name: string, value: any) => Map<string, any>;
hasLabel: (name: string) => boolean;
getLife: () => number;
getLabel: (name: string) => any;
removeLabel: (name: string) => boolean;
}
Expand All @@ -173,7 +185,7 @@ interface UnitResource {
getRangedCombatUnits(): Unit[];
getAll: (filter?: (number | UnitFilter)) => Unit[];
getAlive: (filter?: (number | UnitFilter)) => Unit[];
getById: (unitTypeId: number, filter?: UnitFilter) => Unit[];
getById: (unitTypeId: (number | number[]), filter?: UnitFilter) => Unit[];
getByTag(unitTags: string): Unit;
getByTag(unitTags: string[]): Unit[];
getClosest(pos: Point2D, units: Unit[], n?: number): Unit[];
Expand Down Expand Up @@ -584,5 +596,6 @@ interface Engine {
dispatch: () => Promise<any>;
systems: SystemWrapper<EngineObject>[];
firstRun: () => Promise<GameResult>
getWorld: () => World
onGameEnd: (results: SC2APIProtocol.PlayerResult[]) => GameResult;
}
Loading