@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.
Files changed (116) hide show
  1. package/README.md +3 -3
  2. package/dist/bot/bot.js +7 -3
  3. package/dist/bot/bot.js.map +1 -0
  4. package/dist/bot/logic/awareness.js +12 -2
  5. package/dist/bot/logic/awareness.js.map +1 -0
  6. package/dist/bot/logic/building/ArtilleryUnit.js +29 -8
  7. package/dist/bot/logic/building/antiGroundStaticDefence.js +3 -2
  8. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
  9. package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
  10. package/dist/bot/logic/building/basicAirUnit.js +2 -1
  11. package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
  12. package/dist/bot/logic/building/basicBuilding.js +2 -1
  13. package/dist/bot/logic/building/basicBuilding.js.map +1 -0
  14. package/dist/bot/logic/building/basicGroundUnit.js +2 -1
  15. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
  16. package/dist/bot/logic/building/buildingRules.js +168 -0
  17. package/dist/bot/logic/building/buildingRules.js.map +1 -0
  18. package/dist/bot/logic/building/harvester.js +1 -0
  19. package/dist/bot/logic/building/harvester.js.map +1 -0
  20. package/dist/bot/logic/building/powerPlant.js +2 -1
  21. package/dist/bot/logic/building/powerPlant.js.map +1 -0
  22. package/dist/bot/logic/building/queueController.js +2 -1
  23. package/dist/bot/logic/building/queueController.js.map +1 -0
  24. package/dist/bot/logic/building/resourceCollectionBuilding.js +2 -1
  25. package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -0
  26. package/dist/bot/logic/common/scout.js +100 -0
  27. package/dist/bot/logic/common/scout.js.map +1 -0
  28. package/dist/bot/logic/common/utils.js +14 -0
  29. package/dist/bot/logic/common/utils.js.map +1 -0
  30. package/dist/bot/logic/map/map.js +9 -25
  31. package/dist/bot/logic/map/map.js.map +1 -0
  32. package/dist/bot/logic/map/sector.js +33 -1
  33. package/dist/bot/logic/map/sector.js.map +1 -0
  34. package/dist/bot/logic/mission/mission.js +3 -1
  35. package/dist/bot/logic/mission/mission.js.map +1 -0
  36. package/dist/bot/logic/mission/missionController.js +3 -2
  37. package/dist/bot/logic/mission/missionController.js.map +1 -0
  38. package/dist/bot/logic/mission/missionFactories.js +3 -0
  39. package/dist/bot/logic/mission/missionFactories.js.map +1 -0
  40. package/dist/bot/logic/mission/missions/attackMission.js +8 -7
  41. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
  42. package/dist/bot/logic/mission/missions/defenceMission.js +11 -6
  43. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
  44. package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
  45. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
  46. package/dist/bot/logic/mission/missions/expansionMission.js +5 -4
  47. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -0
  48. package/dist/bot/logic/mission/missions/oneTimeMission.js +3 -2
  49. package/dist/bot/logic/mission/missions/oneTimeMission.js.map +1 -0
  50. package/dist/bot/logic/mission/missions/retreatMission.js +3 -2
  51. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -0
  52. package/dist/bot/logic/mission/missions/scoutingMission.js +8 -9
  53. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -0
  54. package/dist/bot/logic/squad/behaviours/combatSquad.js +19 -17
  55. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
  56. package/dist/bot/logic/squad/behaviours/common.js +20 -2
  57. package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
  58. package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
  59. package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
  60. package/dist/bot/logic/squad/behaviours/expansionSquad.js +1 -0
  61. package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -0
  62. package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -0
  63. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -0
  64. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +66 -18
  65. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -0
  66. package/dist/bot/logic/squad/squad.js +4 -5
  67. package/dist/bot/logic/squad/squad.js.map +1 -0
  68. package/dist/bot/logic/squad/squadBehaviour.js +1 -0
  69. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -0
  70. package/dist/bot/logic/squad/squadBehaviours.js +1 -0
  71. package/dist/bot/logic/squad/squadBehaviours.js.map +1 -0
  72. package/dist/bot/logic/squad/squadController.js +58 -18
  73. package/dist/bot/logic/squad/squadController.js.map +1 -0
  74. package/dist/bot/logic/threat/threat.js +1 -0
  75. package/dist/bot/logic/threat/threat.js.map +1 -0
  76. package/dist/bot/logic/threat/threatCalculator.js +1 -0
  77. package/dist/bot/logic/threat/threatCalculator.js.map +1 -0
  78. package/dist/exampleBot.js +7 -6
  79. package/dist/exampleBot.js.map +1 -0
  80. package/package.json +15 -7
  81. package/src/bot/bot.ts +10 -7
  82. package/src/bot/logic/awareness.ts +21 -4
  83. package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
  84. package/src/bot/logic/building/artilleryUnit.ts +68 -0
  85. package/src/bot/logic/building/basicAirUnit.ts +68 -68
  86. package/src/bot/logic/building/basicBuilding.ts +47 -47
  87. package/src/bot/logic/building/basicGroundUnit.ts +1 -1
  88. package/src/bot/logic/building/buildingRules.ts +233 -0
  89. package/src/bot/logic/building/powerPlant.ts +32 -32
  90. package/src/bot/logic/building/queueController.ts +1 -1
  91. package/src/bot/logic/building/resourceCollectionBuilding.ts +56 -56
  92. package/src/bot/logic/common/scout.ts +127 -1
  93. package/src/bot/logic/common/utils.ts +17 -0
  94. package/src/bot/logic/map/map.ts +70 -84
  95. package/src/bot/logic/map/sector.ts +46 -4
  96. package/src/bot/logic/mission/mission.ts +2 -2
  97. package/src/bot/logic/mission/missionController.ts +2 -3
  98. package/src/bot/logic/mission/missionFactories.ts +5 -0
  99. package/src/bot/logic/mission/missions/attackMission.ts +25 -20
  100. package/src/bot/logic/mission/missions/defenceMission.ts +34 -14
  101. package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
  102. package/src/bot/logic/mission/missions/expansionMission.ts +6 -4
  103. package/src/bot/logic/mission/missions/oneTimeMission.ts +3 -2
  104. package/src/bot/logic/mission/missions/retreatMission.ts +3 -2
  105. package/src/bot/logic/mission/missions/scoutingMission.ts +9 -6
  106. package/src/bot/logic/squad/behaviours/combatSquad.ts +21 -17
  107. package/src/bot/logic/squad/behaviours/common.ts +33 -2
  108. package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
  109. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +79 -26
  110. package/src/bot/logic/squad/squad.ts +4 -4
  111. package/src/bot/logic/squad/squadBehaviour.ts +3 -1
  112. package/src/bot/logic/squad/squadController.ts +89 -44
  113. package/src/exampleBot.ts +6 -6
  114. package/tsconfig.json +73 -73
  115. package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
  116. 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 "./building.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
+ 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
+ }
@@ -13,7 +13,7 @@ import {
13
13
  BUILDING_NAME_TO_RULES,
14
14
  DEFAULT_BUILDING_PRIORITY,
15
15
  getDefaultPlacementLocation,
16
- } from "./building.js";
16
+ } from "./buildingRules.js";
17
17
 
18
18
  export const QUEUES = [
19
19
  QueueType.Structures,
@@ -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 "./building.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
+ 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
+ }