@supalosa/chronodivide-bot 0.2.1 → 0.2.2-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/bot/bot.js +7 -3
- package/dist/bot/bot.js.map +1 -0
- package/dist/bot/logic/awareness.js +12 -2
- package/dist/bot/logic/awareness.js.map +1 -0
- package/dist/bot/logic/building/ArtilleryUnit.js +29 -8
- package/dist/bot/logic/building/antiGroundStaticDefence.js +3 -2
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +2 -1
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
- package/dist/bot/logic/building/basicBuilding.js +2 -1
- package/dist/bot/logic/building/basicBuilding.js.map +1 -0
- package/dist/bot/logic/building/basicGroundUnit.js +2 -1
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
- package/dist/bot/logic/building/buildingRules.js +168 -0
- package/dist/bot/logic/building/buildingRules.js.map +1 -0
- package/dist/bot/logic/building/harvester.js +1 -0
- package/dist/bot/logic/building/harvester.js.map +1 -0
- package/dist/bot/logic/building/powerPlant.js +2 -1
- package/dist/bot/logic/building/powerPlant.js.map +1 -0
- package/dist/bot/logic/building/queueController.js +2 -1
- package/dist/bot/logic/building/queueController.js.map +1 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js +2 -1
- package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -0
- package/dist/bot/logic/common/scout.js +100 -0
- package/dist/bot/logic/common/scout.js.map +1 -0
- package/dist/bot/logic/common/utils.js +14 -0
- package/dist/bot/logic/common/utils.js.map +1 -0
- package/dist/bot/logic/map/map.js +9 -25
- package/dist/bot/logic/map/map.js.map +1 -0
- package/dist/bot/logic/map/sector.js +33 -1
- package/dist/bot/logic/map/sector.js.map +1 -0
- package/dist/bot/logic/mission/mission.js +3 -1
- package/dist/bot/logic/mission/mission.js.map +1 -0
- package/dist/bot/logic/mission/missionController.js +3 -2
- package/dist/bot/logic/mission/missionController.js.map +1 -0
- package/dist/bot/logic/mission/missionFactories.js +3 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -0
- package/dist/bot/logic/mission/missions/attackMission.js +8 -7
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +11 -6
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
- package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/expansionMission.js +5 -4
- package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/oneTimeMission.js +3 -2
- package/dist/bot/logic/mission/missions/oneTimeMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js +3 -2
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/scoutingMission.js +8 -9
- package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/combatSquad.js +19 -17
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/common.js +20 -2
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/expansionSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +66 -18
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -0
- package/dist/bot/logic/squad/squad.js +4 -5
- package/dist/bot/logic/squad/squad.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js.map +1 -0
- package/dist/bot/logic/squad/squadController.js +58 -18
- package/dist/bot/logic/squad/squadController.js.map +1 -0
- package/dist/bot/logic/threat/threat.js +1 -0
- package/dist/bot/logic/threat/threat.js.map +1 -0
- package/dist/bot/logic/threat/threatCalculator.js +1 -0
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -0
- package/dist/exampleBot.js +7 -6
- package/dist/exampleBot.js.map +1 -0
- package/package.json +15 -7
- package/src/bot/bot.ts +10 -7
- package/src/bot/logic/awareness.ts +21 -4
- package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
- package/src/bot/logic/building/artilleryUnit.ts +68 -0
- package/src/bot/logic/building/basicAirUnit.ts +68 -68
- package/src/bot/logic/building/basicBuilding.ts +47 -47
- package/src/bot/logic/building/basicGroundUnit.ts +1 -1
- package/src/bot/logic/building/buildingRules.ts +233 -0
- package/src/bot/logic/building/powerPlant.ts +32 -32
- package/src/bot/logic/building/queueController.ts +1 -1
- package/src/bot/logic/building/resourceCollectionBuilding.ts +56 -56
- package/src/bot/logic/common/scout.ts +127 -1
- package/src/bot/logic/common/utils.ts +17 -0
- package/src/bot/logic/map/map.ts +70 -84
- package/src/bot/logic/map/sector.ts +46 -4
- package/src/bot/logic/mission/mission.ts +2 -2
- package/src/bot/logic/mission/missionController.ts +2 -3
- package/src/bot/logic/mission/missionFactories.ts +5 -0
- package/src/bot/logic/mission/missions/attackMission.ts +25 -20
- package/src/bot/logic/mission/missions/defenceMission.ts +34 -14
- package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
- package/src/bot/logic/mission/missions/expansionMission.ts +6 -4
- package/src/bot/logic/mission/missions/oneTimeMission.ts +3 -2
- package/src/bot/logic/mission/missions/retreatMission.ts +3 -2
- package/src/bot/logic/mission/missions/scoutingMission.ts +9 -6
- package/src/bot/logic/squad/behaviours/combatSquad.ts +21 -17
- package/src/bot/logic/squad/behaviours/common.ts +33 -2
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +79 -26
- package/src/bot/logic/squad/squad.ts +4 -4
- package/src/bot/logic/squad/squadBehaviour.ts +3 -1
- package/src/bot/logic/squad/squadController.ts +89 -44
- package/src/exampleBot.ts +6 -6
- package/tsconfig.json +73 -73
- package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
- package/src/bot/logic/building/building.ts +0 -127
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BuildingPlacementData,
|
|
3
|
+
GameApi,
|
|
4
|
+
ObjectType,
|
|
5
|
+
PlayerData,
|
|
6
|
+
Point2D,
|
|
7
|
+
Size,
|
|
8
|
+
TechnoRules,
|
|
9
|
+
Tile,
|
|
10
|
+
} from "@chronodivide/game-api";
|
|
11
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
12
|
+
import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
|
|
13
|
+
import { ArtilleryUnit } from "./artilleryUnit.js";
|
|
14
|
+
import { BasicAirUnit } from "./basicAirUnit.js";
|
|
15
|
+
import { BasicBuilding } from "./basicBuilding.js";
|
|
16
|
+
import { BasicGroundUnit } from "./basicGroundUnit.js";
|
|
17
|
+
import { PowerPlant } from "./powerPlant.js";
|
|
18
|
+
import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
|
|
19
|
+
import { Harvester } from "./harvester.js";
|
|
20
|
+
import uniqBy from "lodash.uniqby";
|
|
21
|
+
|
|
22
|
+
export interface AiBuildingRules {
|
|
23
|
+
getPriority(
|
|
24
|
+
game: GameApi,
|
|
25
|
+
playerData: PlayerData,
|
|
26
|
+
technoRules: TechnoRules,
|
|
27
|
+
threatCache: GlobalThreat | null,
|
|
28
|
+
): number;
|
|
29
|
+
|
|
30
|
+
getPlacementLocation(
|
|
31
|
+
game: GameApi,
|
|
32
|
+
playerData: PlayerData,
|
|
33
|
+
technoRules: TechnoRules,
|
|
34
|
+
): { rx: number; ry: number } | undefined;
|
|
35
|
+
|
|
36
|
+
getMaxCount(
|
|
37
|
+
game: GameApi,
|
|
38
|
+
playerData: PlayerData,
|
|
39
|
+
technoRules: TechnoRules,
|
|
40
|
+
threatCache: GlobalThreat | null,
|
|
41
|
+
): number | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function numBuildingsOwnedOfType(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
|
|
45
|
+
return game.getVisibleUnits(playerData.name, "self", (r) => r == technoRules).length;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, name: string): number {
|
|
49
|
+
return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Computes a rect 'centered' around a structure of a certain size with additional radius.
|
|
54
|
+
*
|
|
55
|
+
* This is essentially the placeable area around a given structure.
|
|
56
|
+
*
|
|
57
|
+
* @param point Top-left location of the inner rect.
|
|
58
|
+
* @param t Size of the inner rect.
|
|
59
|
+
* @param adjacent Size of the outer rect.
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
function computeAdjacentRect(point: Point2D, t: Size, adjacent: number) {
|
|
63
|
+
return {
|
|
64
|
+
x: point.x - adjacent,
|
|
65
|
+
y: point.y - adjacent,
|
|
66
|
+
width: t.width + 2 * adjacent,
|
|
67
|
+
height: t.height + 2 * adjacent,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getAdjacencyTiles(
|
|
72
|
+
game: GameApi,
|
|
73
|
+
playerData: PlayerData,
|
|
74
|
+
technoRules: TechnoRules,
|
|
75
|
+
minimumSpace: number,
|
|
76
|
+
) {
|
|
77
|
+
const placementRules = game.getBuildingPlacementData(technoRules.name);
|
|
78
|
+
const { width: newBuildingWidth, height: newBuildingHeight } = placementRules.foundation;
|
|
79
|
+
const tiles = [];
|
|
80
|
+
const buildings = game.getVisibleUnits(playerData.name, "self", (r: TechnoRules) => r.type === ObjectType.Building);
|
|
81
|
+
const removedTiles = new Set<string>();
|
|
82
|
+
for (let buildingId of buildings) {
|
|
83
|
+
const building = game.getUnitData(buildingId);
|
|
84
|
+
if (building?.rules?.baseNormal) {
|
|
85
|
+
const { foundation, tile } = building;
|
|
86
|
+
const buildingBase = {
|
|
87
|
+
x: tile.rx,
|
|
88
|
+
y: tile.ry,
|
|
89
|
+
};
|
|
90
|
+
const buildingSize = {
|
|
91
|
+
width: foundation?.width,
|
|
92
|
+
height: foundation?.height,
|
|
93
|
+
};
|
|
94
|
+
const range = computeAdjacentRect(buildingBase, buildingSize, technoRules.adjacent);
|
|
95
|
+
const baseTile = game.mapApi.getTile(range.x, range.y);
|
|
96
|
+
if (!baseTile) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const adjacentTiles = game.mapApi.getTilesInRect(baseTile, {
|
|
100
|
+
width: range.width,
|
|
101
|
+
height: range.height,
|
|
102
|
+
});
|
|
103
|
+
tiles.push(...adjacentTiles);
|
|
104
|
+
|
|
105
|
+
// Prevent placing the new building on tiles that would cause it to overlap with this building.
|
|
106
|
+
const modifiedBase = {
|
|
107
|
+
x: buildingBase.x - (newBuildingWidth - 1),
|
|
108
|
+
y: buildingBase.y - (newBuildingHeight - 1),
|
|
109
|
+
};
|
|
110
|
+
const modifiedSize = {
|
|
111
|
+
width: buildingSize.width + (newBuildingWidth - 1),
|
|
112
|
+
height: buildingSize.height + (newBuildingHeight - 1),
|
|
113
|
+
};
|
|
114
|
+
const blockedRect = computeAdjacentRect(modifiedBase, modifiedSize, minimumSpace);
|
|
115
|
+
const buildingTiles = adjacentTiles.filter((tile) => {
|
|
116
|
+
return (
|
|
117
|
+
tile.rx >= blockedRect.x &&
|
|
118
|
+
tile.rx < blockedRect.x + blockedRect.width &&
|
|
119
|
+
tile.ry >= blockedRect.y &&
|
|
120
|
+
tile.ry < blockedRect.y + blockedRect.height
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
buildingTiles.forEach((buildingTile) => removedTiles.add(buildingTile.id));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Remove duplicate tiles.
|
|
127
|
+
const withDuplicatesRemoved = uniqBy(tiles, (tile) => tile.id);
|
|
128
|
+
// Remove tiles containing buildings and potentially area around them removed as well.
|
|
129
|
+
return withDuplicatesRemoved.filter((tile) => !removedTiles.has(tile.id));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getTileDistances(startPoint: Point2D, tiles: Tile[]) {
|
|
133
|
+
return tiles
|
|
134
|
+
.map((tile) => ({
|
|
135
|
+
tile,
|
|
136
|
+
distance: distance(tile.rx, tile.ry, startPoint.x, startPoint.y),
|
|
137
|
+
}))
|
|
138
|
+
.sort((a, b) => {
|
|
139
|
+
return a.distance - b.distance;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function distance(x1: number, y1: number, x2: number, y2: number) {
|
|
144
|
+
var dx = x1 - x2;
|
|
145
|
+
var dy = y1 - y2;
|
|
146
|
+
let tmp = dx * dx + dy * dy;
|
|
147
|
+
if (0 === tmp) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
return Math.sqrt(tmp);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function getDefaultPlacementLocation(
|
|
154
|
+
game: GameApi,
|
|
155
|
+
playerData: PlayerData,
|
|
156
|
+
startPoint: Point2D,
|
|
157
|
+
technoRules: TechnoRules,
|
|
158
|
+
minSpace: number = 1,
|
|
159
|
+
): { rx: number; ry: number } | undefined {
|
|
160
|
+
// Random location, preferably near start location.
|
|
161
|
+
const size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
|
|
162
|
+
if (!size) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const tiles = getAdjacencyTiles(game, playerData, technoRules, minSpace);
|
|
166
|
+
const tileDistances = getTileDistances(startPoint, tiles);
|
|
167
|
+
|
|
168
|
+
for (let tileDistance of tileDistances) {
|
|
169
|
+
if (tileDistance.tile && game.canPlaceBuilding(playerData.name, technoRules.name, tileDistance.tile)) {
|
|
170
|
+
return tileDistance.tile;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Priority 0 = don't build.
|
|
177
|
+
export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
|
|
178
|
+
|
|
179
|
+
export const DEFAULT_BUILDING_PRIORITY = 1;
|
|
180
|
+
|
|
181
|
+
export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
|
|
182
|
+
// Allied
|
|
183
|
+
["GAPOWR", new PowerPlant()],
|
|
184
|
+
["GAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
|
|
185
|
+
["GAWEAP", new BasicBuilding(15, 1)], // War Factory
|
|
186
|
+
["GAPILE", new BasicBuilding(12, 1)], // Barracks
|
|
187
|
+
["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
|
|
188
|
+
["ENGINEER", new BasicBuilding(10, 1, 1000)], // Engineer
|
|
189
|
+
["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
|
|
190
|
+
["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
|
|
191
|
+
|
|
192
|
+
["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
|
|
193
|
+
["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
|
|
194
|
+
|
|
195
|
+
["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
|
|
196
|
+
["ATESLA", new AntiGroundStaticDefence(5, 1, 10)], // Prism Cannon
|
|
197
|
+
["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
|
|
198
|
+
|
|
199
|
+
["E1", new BasicGroundUnit(2, 3, 0.25, 0)], // GI
|
|
200
|
+
["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
|
|
201
|
+
["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
|
|
202
|
+
["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
|
|
203
|
+
["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
|
|
204
|
+
["ORCA", new BasicAirUnit(7, 1, 2, 0)], // Rocketeer
|
|
205
|
+
["SREF", new ArtilleryUnit(10, 5, 3, 3)], // Prism Tank
|
|
206
|
+
["CLEG", new BasicGroundUnit(0, 0)], // Chrono Legionnaire (Disabled - we don't handle the warped out phase properly and it tends to bug both bots out)
|
|
207
|
+
["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
|
|
208
|
+
|
|
209
|
+
// Soviet
|
|
210
|
+
["NAPOWR", new PowerPlant()],
|
|
211
|
+
["NAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
|
|
212
|
+
["NAWEAP", new BasicBuilding(15, 1)], // War Factory
|
|
213
|
+
["NAHAND", new BasicBuilding(12, 1)], // Barracks
|
|
214
|
+
["HARV", new Harvester(15, 4, 2)], // War Miner
|
|
215
|
+
["SENGINEER", new BasicBuilding(10, 1, 1000)], // Soviet Engineer
|
|
216
|
+
["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
|
|
217
|
+
["NARADR", new BasicBuilding(10, 1, 500)], // Radar
|
|
218
|
+
["NANRCT", new PowerPlant()], // Nuclear Reactor
|
|
219
|
+
["NAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
|
|
220
|
+
|
|
221
|
+
["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
|
|
222
|
+
|
|
223
|
+
["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
|
|
224
|
+
["TESLA", new AntiGroundStaticDefence(5, 1, 10)], // Tesla Coil
|
|
225
|
+
["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
|
|
226
|
+
|
|
227
|
+
["E2", new BasicGroundUnit(2, 3, 0.25, 0)], // Conscript
|
|
228
|
+
["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
|
|
229
|
+
["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
|
|
230
|
+
["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
|
|
231
|
+
["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
|
|
232
|
+
["V3", new ArtilleryUnit(9, 10, 0, 3)], // V3 Rocket Launcher
|
|
233
|
+
]);
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { AiBuildingRules, getDefaultPlacementLocation } from "./
|
|
3
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
-
|
|
5
|
-
export class PowerPlant implements AiBuildingRules {
|
|
6
|
-
getPlacementLocation(
|
|
7
|
-
game: GameApi,
|
|
8
|
-
playerData: PlayerData,
|
|
9
|
-
technoRules: TechnoRules
|
|
10
|
-
): { rx: number; ry: number } | undefined {
|
|
11
|
-
return getDefaultPlacementLocation(game, playerData, playerData.startLocation, technoRules);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
getPriority(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
|
|
15
|
-
if (playerData.power.total < playerData.power.drain) {
|
|
16
|
-
return 100;
|
|
17
|
-
} else if (playerData.power.total < playerData.power.drain + technoRules.power / 2) {
|
|
18
|
-
return 20;
|
|
19
|
-
} else {
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getMaxCount(
|
|
25
|
-
game: GameApi,
|
|
26
|
-
playerData: PlayerData,
|
|
27
|
-
technoRules: TechnoRules,
|
|
28
|
-
threatCache: GlobalThreat | null
|
|
29
|
-
): number | null {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { AiBuildingRules, getDefaultPlacementLocation } from "./buildingRules.js";
|
|
3
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
+
|
|
5
|
+
export class PowerPlant implements AiBuildingRules {
|
|
6
|
+
getPlacementLocation(
|
|
7
|
+
game: GameApi,
|
|
8
|
+
playerData: PlayerData,
|
|
9
|
+
technoRules: TechnoRules
|
|
10
|
+
): { rx: number; ry: number } | undefined {
|
|
11
|
+
return getDefaultPlacementLocation(game, playerData, playerData.startLocation, technoRules);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getPriority(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
|
|
15
|
+
if (playerData.power.total < playerData.power.drain) {
|
|
16
|
+
return 100;
|
|
17
|
+
} else if (playerData.power.total < playerData.power.drain + technoRules.power / 2) {
|
|
18
|
+
return 20;
|
|
19
|
+
} else {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getMaxCount(
|
|
25
|
+
game: GameApi,
|
|
26
|
+
playerData: PlayerData,
|
|
27
|
+
technoRules: TechnoRules,
|
|
28
|
+
threatCache: GlobalThreat | null
|
|
29
|
+
): number | null {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { GameApi, PlayerData, Point2D, TechnoRules, Tile } from "@chronodivide/game-api";
|
|
2
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import { BasicBuilding } from "./basicBuilding.js";
|
|
4
|
-
import {
|
|
5
|
-
AiBuildingRules,
|
|
6
|
-
getDefaultPlacementLocation,
|
|
7
|
-
numBuildingsOwnedOfName,
|
|
8
|
-
numBuildingsOwnedOfType,
|
|
9
|
-
} from "./
|
|
10
|
-
|
|
11
|
-
export class ResourceCollectionBuilding extends BasicBuilding {
|
|
12
|
-
constructor(basePriority: number, maxNeeded: number, onlyBuildWhenFloatingCreditsAmount?: number) {
|
|
13
|
-
super(basePriority, maxNeeded, onlyBuildWhenFloatingCreditsAmount);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
getPlacementLocation(
|
|
17
|
-
game: GameApi,
|
|
18
|
-
playerData: PlayerData,
|
|
19
|
-
technoRules: TechnoRules
|
|
20
|
-
): { rx: number; ry: number } | undefined {
|
|
21
|
-
// Prefer spawning close to ore.
|
|
22
|
-
let selectedLocation = playerData.startLocation;
|
|
23
|
-
|
|
24
|
-
var closeOre: Tile | undefined;
|
|
25
|
-
var closeOreDist: number | undefined;
|
|
26
|
-
let allTileResourceData = game.mapApi.getAllTilesResourceData();
|
|
27
|
-
for (let i = 0; i < allTileResourceData.length; ++i) {
|
|
28
|
-
let tileResourceData = allTileResourceData[i];
|
|
29
|
-
if (tileResourceData.spawnsOre) {
|
|
30
|
-
let dist = Math.sqrt(
|
|
31
|
-
(selectedLocation.x - tileResourceData.tile.rx) ** 2 +
|
|
32
|
-
(selectedLocation.y - tileResourceData.tile.ry) ** 2
|
|
33
|
-
);
|
|
34
|
-
if (closeOreDist == undefined || dist < closeOreDist) {
|
|
35
|
-
closeOreDist = dist;
|
|
36
|
-
closeOre = tileResourceData.tile;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (closeOre) {
|
|
41
|
-
selectedLocation = { x: closeOre.rx, y: closeOre.ry };
|
|
42
|
-
}
|
|
43
|
-
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Don't build/start selling these if we don't have any harvesters
|
|
47
|
-
getMaxCount(
|
|
48
|
-
game: GameApi,
|
|
49
|
-
playerData: PlayerData,
|
|
50
|
-
technoRules: TechnoRules,
|
|
51
|
-
threatCache: GlobalThreat | null
|
|
52
|
-
): number | null {
|
|
53
|
-
const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
|
|
54
|
-
return Math.max(1, harvesters * 2);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
import { GameApi, PlayerData, Point2D, TechnoRules, Tile } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
+
import { BasicBuilding } from "./basicBuilding.js";
|
|
4
|
+
import {
|
|
5
|
+
AiBuildingRules,
|
|
6
|
+
getDefaultPlacementLocation,
|
|
7
|
+
numBuildingsOwnedOfName,
|
|
8
|
+
numBuildingsOwnedOfType,
|
|
9
|
+
} from "./buildingRules.js";
|
|
10
|
+
|
|
11
|
+
export class ResourceCollectionBuilding extends BasicBuilding {
|
|
12
|
+
constructor(basePriority: number, maxNeeded: number, onlyBuildWhenFloatingCreditsAmount?: number) {
|
|
13
|
+
super(basePriority, maxNeeded, onlyBuildWhenFloatingCreditsAmount);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getPlacementLocation(
|
|
17
|
+
game: GameApi,
|
|
18
|
+
playerData: PlayerData,
|
|
19
|
+
technoRules: TechnoRules
|
|
20
|
+
): { rx: number; ry: number } | undefined {
|
|
21
|
+
// Prefer spawning close to ore.
|
|
22
|
+
let selectedLocation = playerData.startLocation;
|
|
23
|
+
|
|
24
|
+
var closeOre: Tile | undefined;
|
|
25
|
+
var closeOreDist: number | undefined;
|
|
26
|
+
let allTileResourceData = game.mapApi.getAllTilesResourceData();
|
|
27
|
+
for (let i = 0; i < allTileResourceData.length; ++i) {
|
|
28
|
+
let tileResourceData = allTileResourceData[i];
|
|
29
|
+
if (tileResourceData.spawnsOre) {
|
|
30
|
+
let dist = Math.sqrt(
|
|
31
|
+
(selectedLocation.x - tileResourceData.tile.rx) ** 2 +
|
|
32
|
+
(selectedLocation.y - tileResourceData.tile.ry) ** 2
|
|
33
|
+
);
|
|
34
|
+
if (closeOreDist == undefined || dist < closeOreDist) {
|
|
35
|
+
closeOreDist = dist;
|
|
36
|
+
closeOre = tileResourceData.tile;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (closeOre) {
|
|
41
|
+
selectedLocation = { x: closeOre.rx, y: closeOre.ry };
|
|
42
|
+
}
|
|
43
|
+
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Don't build/start selling these if we don't have any harvesters
|
|
47
|
+
getMaxCount(
|
|
48
|
+
game: GameApi,
|
|
49
|
+
playerData: PlayerData,
|
|
50
|
+
technoRules: TechnoRules,
|
|
51
|
+
threatCache: GlobalThreat | null
|
|
52
|
+
): number | null {
|
|
53
|
+
const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
|
|
54
|
+
return Math.max(1, harvesters * 2);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { GameApi, PlayerData } from "@chronodivide/game-api";
|
|
1
|
+
import { GameApi, PlayerData, Point2D } from "@chronodivide/game-api";
|
|
2
|
+
import { Sector, SectorCache } from "../map/sector";
|
|
3
|
+
import { DebugLogger } from "./utils";
|
|
4
|
+
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
|
2
5
|
|
|
3
6
|
export const getUnseenStartingLocations = (gameApi: GameApi, playerData: PlayerData) => {
|
|
4
7
|
const unseenStartingLocations = gameApi.mapApi.getStartingLocations().filter((startingLocation) => {
|
|
@@ -10,3 +13,126 @@ export const getUnseenStartingLocations = (gameApi: GameApi, playerData: PlayerD
|
|
|
10
13
|
});
|
|
11
14
|
return unseenStartingLocations;
|
|
12
15
|
};
|
|
16
|
+
|
|
17
|
+
class PrioritisedScoutTarget {
|
|
18
|
+
private _targetPoint2D?: Point2D;
|
|
19
|
+
private _targetSector?: Sector;
|
|
20
|
+
private _priority: number;
|
|
21
|
+
|
|
22
|
+
constructor(priority: number, target: Point2D | Sector) {
|
|
23
|
+
if (target.hasOwnProperty("x") && target.hasOwnProperty("y")) {
|
|
24
|
+
this._targetPoint2D = target as Point2D;
|
|
25
|
+
} else if (target.hasOwnProperty("sectorStartPoint")) {
|
|
26
|
+
this._targetSector = target as Sector;
|
|
27
|
+
} else {
|
|
28
|
+
throw new TypeError(`invalid object passed as target: ${target}`);
|
|
29
|
+
}
|
|
30
|
+
this._priority = priority;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get priority() {
|
|
34
|
+
return this._priority;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
asPoint2D() {
|
|
38
|
+
return this._targetPoint2D ?? this._targetSector?.sectorStartPoint ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get targetSector() {
|
|
42
|
+
return this._targetSector;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ENEMY_SPAWN_POINT_PRIORITY = 100;
|
|
47
|
+
|
|
48
|
+
// Amount of sectors around the starting sector to try to scout.
|
|
49
|
+
const NEARBY_SECTOR_RADIUS = 2;
|
|
50
|
+
const NEARBY_SECTOR_BASE_PRIORITY = 1000;
|
|
51
|
+
|
|
52
|
+
export class ScoutingManager {
|
|
53
|
+
private scoutingQueue: PriorityQueue<PrioritisedScoutTarget>;
|
|
54
|
+
|
|
55
|
+
constructor(private logger: DebugLogger) {
|
|
56
|
+
// Order by descending priority.
|
|
57
|
+
this.scoutingQueue = new PriorityQueue((a: PrioritisedScoutTarget, b: PrioritisedScoutTarget) => b.priority - a.priority);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onGameStart(gameApi: GameApi, playerData: PlayerData, sectorCache: SectorCache) {
|
|
61
|
+
// Queue hostile starting locations with high priority.
|
|
62
|
+
gameApi.mapApi
|
|
63
|
+
.getStartingLocations()
|
|
64
|
+
.filter((startingLocation) => {
|
|
65
|
+
if (startingLocation == playerData.startLocation) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
let tile = gameApi.mapApi.getTile(startingLocation.x, startingLocation.y);
|
|
69
|
+
return tile ? !gameApi.mapApi.isVisibleTile(tile, playerData.name) : false;
|
|
70
|
+
})
|
|
71
|
+
.map((tile) => new PrioritisedScoutTarget(ENEMY_SPAWN_POINT_PRIORITY, tile))
|
|
72
|
+
.forEach((target) => {
|
|
73
|
+
this.logger(`Adding ${target.asPoint2D()?.x},${target.asPoint2D()?.y} to initial scouting queue`);
|
|
74
|
+
this.scoutingQueue.enqueue(target);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Queue nearby sectors.
|
|
78
|
+
const { x: startX, y: startY } = playerData.startLocation;
|
|
79
|
+
const { x: sectorsX, y: sectorsY } = sectorCache.getSectorBounds();
|
|
80
|
+
const startingSector = sectorCache.getSectorCoordinatesForWorldPosition(startX, startY);
|
|
81
|
+
|
|
82
|
+
if (!startingSector) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (
|
|
87
|
+
let x: number = Math.max(0, startingSector.sectorX - NEARBY_SECTOR_RADIUS);
|
|
88
|
+
x <= Math.min(sectorsX, startingSector.sectorX + NEARBY_SECTOR_RADIUS);
|
|
89
|
+
++x
|
|
90
|
+
) {
|
|
91
|
+
for (
|
|
92
|
+
let y: number = Math.max(0, startingSector.sectorY - NEARBY_SECTOR_RADIUS);
|
|
93
|
+
y <= Math.min(sectorsY, startingSector.sectorY + NEARBY_SECTOR_RADIUS);
|
|
94
|
+
++y
|
|
95
|
+
) {
|
|
96
|
+
if (x === startingSector?.sectorX && y === startingSector?.sectorY) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Make it scout closer sectors first.
|
|
100
|
+
const distanceFactor = Math.pow(x - startingSector.sectorX, 2) + Math.pow(y - startingSector.sectorY, 2);
|
|
101
|
+
const sector = sectorCache.getSector(x, y);
|
|
102
|
+
if (sector) {
|
|
103
|
+
const maybeTarget = new PrioritisedScoutTarget(NEARBY_SECTOR_BASE_PRIORITY - distanceFactor, sector);
|
|
104
|
+
const maybePoint = maybeTarget.asPoint2D();
|
|
105
|
+
if (maybePoint && gameApi.mapApi.getTile(maybePoint.x, maybePoint.y)) {
|
|
106
|
+
this.scoutingQueue.enqueue(maybeTarget);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onAiUpdate(gameApi: GameApi, playerData: PlayerData) {
|
|
114
|
+
const currentHead = this.scoutingQueue.front();
|
|
115
|
+
if (!currentHead) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const head = currentHead.asPoint2D();
|
|
119
|
+
if (!head) {
|
|
120
|
+
this.scoutingQueue.dequeue();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const { x, y } = head;
|
|
124
|
+
const tile = gameApi.mapApi.getTile(x, y);
|
|
125
|
+
if (tile && gameApi.mapApi.isVisibleTile(tile, playerData.name)) {
|
|
126
|
+
this.logger(`head point is visible, dequeueing`);
|
|
127
|
+
this.scoutingQueue.dequeue();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getNewScoutTarget() {
|
|
132
|
+
return this.scoutingQueue.dequeue();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasScoutTargets() {
|
|
136
|
+
return !this.scoutingQueue.isEmpty();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type DebugLogger = (message: string, sayInGame?: boolean) => void;
|
|
2
|
+
|
|
3
|
+
// Thanks use-strict!
|
|
4
|
+
export function formatTimeDuration(timeSeconds: number, skipZeroHours = false) {
|
|
5
|
+
let h = Math.floor(timeSeconds / 3600);
|
|
6
|
+
timeSeconds -= h * 3600;
|
|
7
|
+
let m = Math.floor(timeSeconds / 60);
|
|
8
|
+
timeSeconds -= m * 60;
|
|
9
|
+
let s = Math.floor(timeSeconds);
|
|
10
|
+
|
|
11
|
+
return [...(h || !skipZeroHours ? [h] : []), pad(m, "00"), pad(s, "00")].join(":");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function pad(n: any, format = "0000") {
|
|
15
|
+
let str = "" + n;
|
|
16
|
+
return format.substring(0, format.length - str.length) + str;
|
|
17
|
+
}
|