@supalosa/chronodivide-bot 0.5.4 → 0.6.6-beta.1

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 (124) hide show
  1. package/.env.template +4 -4
  2. package/.github/workflows/npm-publish.yml +24 -0
  3. package/README.md +108 -103
  4. package/dist/bot/bot.js +105 -105
  5. package/dist/bot/logic/awareness.js +136 -136
  6. package/dist/bot/logic/building/antiAirStaticDefence.js +42 -42
  7. package/dist/bot/logic/building/antiGroundStaticDefence.js +34 -34
  8. package/dist/bot/logic/building/{ArtilleryUnit.js → artilleryUnit.js} +18 -18
  9. package/dist/bot/logic/building/basicAirUnit.js +19 -19
  10. package/dist/bot/logic/building/basicBuilding.js +26 -26
  11. package/dist/bot/logic/building/basicGroundUnit.js +19 -19
  12. package/dist/bot/logic/building/buildingRules.js +175 -175
  13. package/dist/bot/logic/building/common.js +19 -19
  14. package/dist/bot/logic/building/harvester.js +16 -16
  15. package/dist/bot/logic/building/powerPlant.js +20 -20
  16. package/dist/bot/logic/building/queueController.js +183 -183
  17. package/dist/bot/logic/building/resourceCollectionBuilding.js +36 -36
  18. package/dist/bot/logic/common/scout.js +126 -126
  19. package/dist/bot/logic/common/utils.js +95 -95
  20. package/dist/bot/logic/composition/alliedCompositions.js +12 -12
  21. package/dist/bot/logic/composition/common.js +1 -1
  22. package/dist/bot/logic/composition/sovietCompositions.js +12 -12
  23. package/dist/bot/logic/map/map.js +44 -44
  24. package/dist/bot/logic/map/sector.js +137 -137
  25. package/dist/bot/logic/mission/actionBatcher.js +91 -91
  26. package/dist/bot/logic/mission/mission.js +122 -122
  27. package/dist/bot/logic/mission/missionController.js +321 -321
  28. package/dist/bot/logic/mission/missionFactories.js +12 -12
  29. package/dist/bot/logic/mission/missions/attackMission.js +214 -214
  30. package/dist/bot/logic/mission/missions/defenceMission.js +82 -82
  31. package/dist/bot/logic/mission/missions/engineerMission.js +63 -63
  32. package/dist/bot/logic/mission/missions/expansionMission.js +60 -60
  33. package/dist/bot/logic/mission/missions/retreatMission.js +33 -33
  34. package/dist/bot/logic/mission/missions/scoutingMission.js +133 -133
  35. package/dist/bot/logic/mission/missions/squads/combatSquad.js +115 -115
  36. package/dist/bot/logic/mission/missions/squads/common.js +57 -57
  37. package/dist/bot/logic/mission/missions/squads/squad.js +1 -1
  38. package/dist/bot/logic/threat/threat.js +22 -22
  39. package/dist/bot/logic/threat/threatCalculator.js +73 -73
  40. package/dist/exampleBot.js +100 -100
  41. package/package.json +32 -29
  42. package/src/bot/bot.ts +161 -161
  43. package/src/bot/logic/awareness.ts +245 -245
  44. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -64
  45. package/src/bot/logic/building/antiGroundStaticDefence.ts +55 -55
  46. package/src/bot/logic/building/artilleryUnit.ts +39 -39
  47. package/src/bot/logic/building/basicAirUnit.ts +39 -39
  48. package/src/bot/logic/building/basicBuilding.ts +49 -49
  49. package/src/bot/logic/building/basicGroundUnit.ts +39 -39
  50. package/src/bot/logic/building/buildingRules.ts +250 -250
  51. package/src/bot/logic/building/common.ts +21 -21
  52. package/src/bot/logic/building/harvester.ts +31 -31
  53. package/src/bot/logic/building/powerPlant.ts +32 -32
  54. package/src/bot/logic/building/queueController.ts +297 -297
  55. package/src/bot/logic/building/resourceCollectionBuilding.ts +52 -52
  56. package/src/bot/logic/common/scout.ts +183 -183
  57. package/src/bot/logic/common/utils.ts +120 -120
  58. package/src/bot/logic/composition/alliedCompositions.ts +22 -22
  59. package/src/bot/logic/composition/common.ts +3 -3
  60. package/src/bot/logic/composition/sovietCompositions.ts +21 -21
  61. package/src/bot/logic/map/map.ts +66 -66
  62. package/src/bot/logic/map/sector.ts +174 -174
  63. package/src/bot/logic/mission/actionBatcher.ts +124 -124
  64. package/src/bot/logic/mission/mission.ts +232 -232
  65. package/src/bot/logic/mission/missionController.ts +413 -413
  66. package/src/bot/logic/mission/missionFactories.ts +51 -51
  67. package/src/bot/logic/mission/missions/attackMission.ts +336 -336
  68. package/src/bot/logic/mission/missions/defenceMission.ts +151 -151
  69. package/src/bot/logic/mission/missions/engineerMission.ts +113 -113
  70. package/src/bot/logic/mission/missions/expansionMission.ts +104 -104
  71. package/src/bot/logic/mission/missions/retreatMission.ts +54 -54
  72. package/src/bot/logic/mission/missions/scoutingMission.ts +186 -186
  73. package/src/bot/logic/mission/missions/squads/combatSquad.ts +160 -160
  74. package/src/bot/logic/mission/missions/squads/common.ts +63 -63
  75. package/src/bot/logic/mission/missions/squads/squad.ts +19 -19
  76. package/src/bot/logic/threat/threat.ts +15 -15
  77. package/src/bot/logic/threat/threatCalculator.ts +100 -100
  78. package/src/exampleBot.ts +111 -111
  79. package/tsconfig.json +73 -73
  80. package/dist/bot/logic/awarenessImpl.js +0 -132
  81. package/dist/bot/logic/awarenessImpl.js.map +0 -1
  82. package/dist/bot/logic/building/building.js +0 -126
  83. package/dist/bot/logic/building/building.js.map +0 -1
  84. package/dist/bot/logic/mission/behaviours/combatSquad.js +0 -124
  85. package/dist/bot/logic/mission/behaviours/combatSquad.js.map +0 -1
  86. package/dist/bot/logic/mission/behaviours/common.js +0 -58
  87. package/dist/bot/logic/mission/behaviours/common.js.map +0 -1
  88. package/dist/bot/logic/mission/behaviours/engineerSquad.js +0 -39
  89. package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +0 -1
  90. package/dist/bot/logic/mission/behaviours/expansionSquad.js +0 -46
  91. package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +0 -1
  92. package/dist/bot/logic/mission/behaviours/retreatSquad.js +0 -31
  93. package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +0 -1
  94. package/dist/bot/logic/mission/behaviours/scoutingSquad.js +0 -94
  95. package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +0 -1
  96. package/dist/bot/logic/mission/missions/basicMission.js +0 -13
  97. package/dist/bot/logic/mission/missions/basicMission.js.map +0 -1
  98. package/dist/bot/logic/mission/missions/missionBehaviour.js +0 -2
  99. package/dist/bot/logic/mission/missions/missionBehaviour.js.map +0 -1
  100. package/dist/bot/logic/mission/missions/oneTimeMission.js +0 -27
  101. package/dist/bot/logic/mission/missions/oneTimeMission.js.map +0 -1
  102. package/dist/bot/logic/squad/behaviours/attackSquad.js +0 -89
  103. package/dist/bot/logic/squad/behaviours/combatSquad.js +0 -102
  104. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +0 -1
  105. package/dist/bot/logic/squad/behaviours/common.js +0 -40
  106. package/dist/bot/logic/squad/behaviours/common.js.map +0 -1
  107. package/dist/bot/logic/squad/behaviours/defenceSquad.js +0 -61
  108. package/dist/bot/logic/squad/behaviours/engineerSquad.js +0 -36
  109. package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +0 -1
  110. package/dist/bot/logic/squad/behaviours/expansionSquad.js +0 -43
  111. package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +0 -1
  112. package/dist/bot/logic/squad/behaviours/retreatSquad.js +0 -28
  113. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +0 -1
  114. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +0 -86
  115. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +0 -1
  116. package/dist/bot/logic/squad/squad.js +0 -126
  117. package/dist/bot/logic/squad/squad.js.map +0 -1
  118. package/dist/bot/logic/squad/squadBehaviour.js +0 -6
  119. package/dist/bot/logic/squad/squadBehaviour.js.map +0 -1
  120. package/dist/bot/logic/squad/squadBehaviours.js +0 -7
  121. package/dist/bot/logic/squad/squadBehaviours.js.map +0 -1
  122. package/dist/bot/logic/squad/squadController.js +0 -199
  123. package/dist/bot/logic/squad/squadController.js.map +0 -1
  124. /package/dist/bot/logic/building/{ArtilleryUnit.js.map → artilleryUnit.js.map} +0 -0
@@ -1,250 +1,250 @@
1
- import {
2
- BuildingPlacementData,
3
- GameApi,
4
- GameMath,
5
- LandType,
6
- ObjectType,
7
- PlayerData,
8
- Rectangle,
9
- Size,
10
- TechnoRules,
11
- Tile,
12
- Vector2,
13
- } from "@chronodivide/game-api";
14
- import { GlobalThreat } from "../threat/threat.js";
15
- import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
16
- import { ArtilleryUnit } from "./artilleryUnit.js";
17
- import { BasicAirUnit } from "./basicAirUnit.js";
18
- import { BasicBuilding } from "./basicBuilding.js";
19
- import { BasicGroundUnit } from "./basicGroundUnit.js";
20
- import { PowerPlant } from "./powerPlant.js";
21
- import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
22
- import { Harvester } from "./harvester.js";
23
- import { uniqBy } from "../common/utils.js";
24
- import { AntiAirStaticDefence } from "./antiAirStaticDefence.js";
25
-
26
- export interface AiBuildingRules {
27
- getPriority(
28
- game: GameApi,
29
- playerData: PlayerData,
30
- technoRules: TechnoRules,
31
- threatCache: GlobalThreat | null,
32
- ): number;
33
-
34
- getPlacementLocation(
35
- game: GameApi,
36
- playerData: PlayerData,
37
- technoRules: TechnoRules,
38
- ): { rx: number; ry: number } | undefined;
39
-
40
- getMaxCount(
41
- game: GameApi,
42
- playerData: PlayerData,
43
- technoRules: TechnoRules,
44
- threatCache: GlobalThreat | null,
45
- ): number | null;
46
- }
47
-
48
- export function numBuildingsOwnedOfType(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
49
- return game.getVisibleUnits(playerData.name, "self", (r) => r == technoRules).length;
50
- }
51
-
52
- export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, name: string): number {
53
- return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
54
- }
55
-
56
- /**
57
- * Computes a rect 'centered' around a structure of a certain size with an additional radius (`adjacent`).
58
- * The radius is optionally expanded by the size of the new building.
59
- *
60
- * This is essentially the candidate placement around a given structure.
61
- *
62
- * @param point Top-left location of the inner rect.
63
- * @param t Size of the inner rect.
64
- * @param adjacent Amount to expand the building's inner rect by (so buildings must be adjacent by this many tiles)
65
- * @param newBuildingSize? Size of the new building
66
- * @returns
67
- */
68
- function computeAdjacentRect(point: Vector2, t: Size, adjacent: number, newBuildingSize?: Size): Rectangle {
69
- return {
70
- x: point.x - adjacent - (newBuildingSize?.width || 0),
71
- y: point.y - adjacent - (newBuildingSize?.height || 0),
72
- width: t.width + 2 * adjacent + (newBuildingSize?.width || 0),
73
- height: t.height + 2 * adjacent + (newBuildingSize?.height || 0),
74
- };
75
- }
76
-
77
- function getAdjacentTiles(game: GameApi, range: Rectangle, onWater: boolean) {
78
- // use the bulk API to get all tiles from the baseTile to the (baseTile + range)
79
- const adjacentTiles = game.mapApi
80
- .getTilesInRect(range)
81
- .filter((tile) => !onWater || tile.landType === LandType.Water);
82
- return adjacentTiles;
83
- }
84
-
85
- export function getAdjacencyTiles(
86
- game: GameApi,
87
- playerData: PlayerData,
88
- technoRules: TechnoRules,
89
- onWater: boolean,
90
- minimumSpace: number,
91
- ): Tile[] {
92
- const placementRules = game.getBuildingPlacementData(technoRules.name);
93
- const { width: newBuildingWidth, height: newBuildingHeight } = placementRules.foundation;
94
- const tiles = [];
95
- const buildings = game.getVisibleUnits(playerData.name, "self", (r: TechnoRules) => r.type === ObjectType.Building);
96
- const removedTiles = new Set<string>();
97
- for (let buildingId of buildings) {
98
- const building = game.getUnitData(buildingId);
99
- if (!building?.rules?.baseNormal) {
100
- // This building is not considered for adjacency checks.
101
- continue;
102
- }
103
- const { foundation, tile } = building;
104
- const buildingBase = new Vector2(tile.rx, tile.ry);
105
- const buildingSize = {
106
- width: foundation?.width,
107
- height: foundation?.height,
108
- };
109
- const range = computeAdjacentRect(buildingBase, buildingSize, technoRules.adjacent, placementRules.foundation);
110
- const adjacentTiles = getAdjacentTiles(game, range, onWater);
111
- if (adjacentTiles.length === 0) {
112
- continue;
113
- }
114
- tiles.push(...adjacentTiles);
115
-
116
- // Prevent placing the new building on tiles that would cause it to overlap with this building.
117
- const modifiedBase = new Vector2(
118
- buildingBase.x - (newBuildingWidth - 1),
119
- buildingBase.y - (newBuildingHeight - 1),
120
- );
121
- const modifiedSize = {
122
- width: buildingSize.width + (newBuildingWidth - 1),
123
- height: buildingSize.height + (newBuildingHeight - 1),
124
- };
125
- const blockedRect = computeAdjacentRect(modifiedBase, modifiedSize, minimumSpace);
126
- const buildingTiles = adjacentTiles.filter((tile) => {
127
- return (
128
- tile.rx >= blockedRect.x &&
129
- tile.rx < blockedRect.x + blockedRect.width &&
130
- tile.ry >= blockedRect.y &&
131
- tile.ry < blockedRect.y + blockedRect.height
132
- );
133
- });
134
- buildingTiles.forEach((buildingTile) => removedTiles.add(buildingTile.id));
135
- }
136
- // Remove duplicate tiles.
137
- const withDuplicatesRemoved = uniqBy(tiles, (tile) => tile.id);
138
- // Remove tiles containing buildings and potentially area around them removed as well.
139
- return withDuplicatesRemoved.filter((tile) => !removedTiles.has(tile.id));
140
- }
141
-
142
- function getTileDistances(startPoint: Vector2, tiles: Tile[]) {
143
- return tiles
144
- .map((tile) => ({
145
- tile,
146
- distance: distance(tile.rx, tile.ry, startPoint.x, startPoint.y),
147
- }))
148
- .sort((a, b) => {
149
- return a.distance - b.distance;
150
- });
151
- }
152
-
153
- function distance(x1: number, y1: number, x2: number, y2: number) {
154
- var dx = x1 - x2;
155
- var dy = y1 - y2;
156
- let tmp = dx * dx + dy * dy;
157
- if (0 === tmp) {
158
- return 0;
159
- }
160
- return GameMath.sqrt(tmp);
161
- }
162
-
163
- export function getDefaultPlacementLocation(
164
- game: GameApi,
165
- playerData: PlayerData,
166
- idealPoint: Vector2,
167
- technoRules: TechnoRules,
168
- onWater: boolean = false,
169
- minSpace: number = 1,
170
- ): { rx: number; ry: number } | undefined {
171
- // Closest possible location near `startPoint`.
172
- const size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
173
- if (!size) {
174
- return undefined;
175
- }
176
- const tiles = getAdjacencyTiles(game, playerData, technoRules, onWater, minSpace);
177
- const tileDistances = getTileDistances(idealPoint, tiles);
178
-
179
- for (let tileDistance of tileDistances) {
180
- if (tileDistance.tile && game.canPlaceBuilding(playerData.name, technoRules.name, tileDistance.tile)) {
181
- return tileDistance.tile;
182
- }
183
- }
184
- return undefined;
185
- }
186
-
187
- // Priority 0 = don't build.
188
- export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
189
-
190
- export const DEFAULT_BUILDING_PRIORITY = 0;
191
-
192
- export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
193
- // Allied
194
- ["GAPOWR", new PowerPlant()],
195
- ["GAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
196
- ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
197
- ["GAPILE", new BasicBuilding(12, 1)], // Barracks
198
- ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
199
- ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
200
- ["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
201
- ["AMRADR", new BasicBuilding(10, 1, 500)], // Airforce Command (USA)
202
-
203
- ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
204
- ["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
205
-
206
- ["GAPILL", new AntiGroundStaticDefence(2, 1, 5, 5)], // Pillbox
207
- ["ATESLA", new AntiGroundStaticDefence(2, 1, 10, 3)], // Prism Cannon
208
- ["NASAM", new AntiAirStaticDefence(2, 1, 5)], // Patriot Missile
209
- ["GAWALL", new AntiGroundStaticDefence(0, 0, 0, 0)], // Walls
210
-
211
- ["E1", new BasicGroundUnit(2, 2, 0.2, 0)], // GI
212
- ["ENGINEER", new BasicGroundUnit(1, 0, 0)], // Engineer
213
- ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
214
- ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
215
- ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
216
- ["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
217
- ["ORCA", new BasicAirUnit(7, 1, 2, 0)], // Rocketeer
218
- ["SREF", new ArtilleryUnit(10, 5, 3, 3)], // Prism Tank
219
- ["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)
220
- ["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
221
-
222
- // Soviet
223
- ["NAPOWR", new PowerPlant()],
224
- ["NAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
225
- ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
226
- ["NAHAND", new BasicBuilding(12, 1)], // Barracks
227
- ["HARV", new Harvester(15, 4, 2)], // War Miner
228
- ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
229
- ["NARADR", new BasicBuilding(10, 1, 500)], // Radar
230
- ["NANRCT", new PowerPlant()], // Nuclear Reactor
231
- ["NAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
232
-
233
- ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
234
-
235
- ["NALASR", new AntiGroundStaticDefence(2, 1, 5, 5)], // Sentry Gun
236
- ["NAFLAK", new AntiAirStaticDefence(2, 1, 5)], // Flak Cannon
237
- ["TESLA", new AntiGroundStaticDefence(2, 1, 10, 3)], // Tesla Coil
238
- ["NAWALL", new AntiGroundStaticDefence(0, 0, 0, 0)], // Walls
239
-
240
- ["E2", new BasicGroundUnit(2, 2, 0.2, 0)], // Conscript
241
- ["SENGINEER", new BasicGroundUnit(1, 0, 0)], // Soviet Engineer
242
- ["FLAKT", new BasicGroundUnit(2, 2, 0.1, 0.3)], // Flak Trooper
243
- ["YURI", new BasicGroundUnit(1, 1, 1, 0)], // Yuri
244
- ["DOG", new BasicGroundUnit(1, 1, 0, 0)], // Soviet Attack Dog
245
- ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
246
- ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
247
- ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
248
- ["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
249
- ["V3", new ArtilleryUnit(9, 10, 0, 3)], // V3 Rocket Launcher
250
- ]);
1
+ import {
2
+ BuildingPlacementData,
3
+ GameApi,
4
+ GameMath,
5
+ LandType,
6
+ ObjectType,
7
+ PlayerData,
8
+ Rectangle,
9
+ Size,
10
+ TechnoRules,
11
+ Tile,
12
+ Vector2,
13
+ } from "@chronodivide/game-api";
14
+ import { GlobalThreat } from "../threat/threat.js";
15
+ import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
16
+ import { ArtilleryUnit } from "./artilleryUnit.js";
17
+ import { BasicAirUnit } from "./basicAirUnit.js";
18
+ import { BasicBuilding } from "./basicBuilding.js";
19
+ import { BasicGroundUnit } from "./basicGroundUnit.js";
20
+ import { PowerPlant } from "./powerPlant.js";
21
+ import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
22
+ import { Harvester } from "./harvester.js";
23
+ import { uniqBy } from "../common/utils.js";
24
+ import { AntiAirStaticDefence } from "./antiAirStaticDefence.js";
25
+
26
+ export interface AiBuildingRules {
27
+ getPriority(
28
+ game: GameApi,
29
+ playerData: PlayerData,
30
+ technoRules: TechnoRules,
31
+ threatCache: GlobalThreat | null,
32
+ ): number;
33
+
34
+ getPlacementLocation(
35
+ game: GameApi,
36
+ playerData: PlayerData,
37
+ technoRules: TechnoRules,
38
+ ): { rx: number; ry: number } | undefined;
39
+
40
+ getMaxCount(
41
+ game: GameApi,
42
+ playerData: PlayerData,
43
+ technoRules: TechnoRules,
44
+ threatCache: GlobalThreat | null,
45
+ ): number | null;
46
+ }
47
+
48
+ export function numBuildingsOwnedOfType(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
49
+ return game.getVisibleUnits(playerData.name, "self", (r) => r == technoRules).length;
50
+ }
51
+
52
+ export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, name: string): number {
53
+ return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
54
+ }
55
+
56
+ /**
57
+ * Computes a rect 'centered' around a structure of a certain size with an additional radius (`adjacent`).
58
+ * The radius is optionally expanded by the size of the new building.
59
+ *
60
+ * This is essentially the candidate placement around a given structure.
61
+ *
62
+ * @param point Top-left location of the inner rect.
63
+ * @param t Size of the inner rect.
64
+ * @param adjacent Amount to expand the building's inner rect by (so buildings must be adjacent by this many tiles)
65
+ * @param newBuildingSize? Size of the new building
66
+ * @returns
67
+ */
68
+ function computeAdjacentRect(point: Vector2, t: Size, adjacent: number, newBuildingSize?: Size): Rectangle {
69
+ return {
70
+ x: point.x - adjacent - (newBuildingSize?.width || 0),
71
+ y: point.y - adjacent - (newBuildingSize?.height || 0),
72
+ width: t.width + 2 * adjacent + (newBuildingSize?.width || 0),
73
+ height: t.height + 2 * adjacent + (newBuildingSize?.height || 0),
74
+ };
75
+ }
76
+
77
+ function getAdjacentTiles(game: GameApi, range: Rectangle, onWater: boolean) {
78
+ // use the bulk API to get all tiles from the baseTile to the (baseTile + range)
79
+ const adjacentTiles = game.mapApi
80
+ .getTilesInRect(range)
81
+ .filter((tile) => !onWater || tile.landType === LandType.Water);
82
+ return adjacentTiles;
83
+ }
84
+
85
+ export function getAdjacencyTiles(
86
+ game: GameApi,
87
+ playerData: PlayerData,
88
+ technoRules: TechnoRules,
89
+ onWater: boolean,
90
+ minimumSpace: number,
91
+ ): Tile[] {
92
+ const placementRules = game.getBuildingPlacementData(technoRules.name);
93
+ const { width: newBuildingWidth, height: newBuildingHeight } = placementRules.foundation;
94
+ const tiles = [];
95
+ const buildings = game.getVisibleUnits(playerData.name, "self", (r: TechnoRules) => r.type === ObjectType.Building);
96
+ const removedTiles = new Set<string>();
97
+ for (let buildingId of buildings) {
98
+ const building = game.getUnitData(buildingId);
99
+ if (!building?.rules?.baseNormal) {
100
+ // This building is not considered for adjacency checks.
101
+ continue;
102
+ }
103
+ const { foundation, tile } = building;
104
+ const buildingBase = new Vector2(tile.rx, tile.ry);
105
+ const buildingSize = {
106
+ width: foundation?.width,
107
+ height: foundation?.height,
108
+ };
109
+ const range = computeAdjacentRect(buildingBase, buildingSize, technoRules.adjacent, placementRules.foundation);
110
+ const adjacentTiles = getAdjacentTiles(game, range, onWater);
111
+ if (adjacentTiles.length === 0) {
112
+ continue;
113
+ }
114
+ tiles.push(...adjacentTiles);
115
+
116
+ // Prevent placing the new building on tiles that would cause it to overlap with this building.
117
+ const modifiedBase = new Vector2(
118
+ buildingBase.x - (newBuildingWidth - 1),
119
+ buildingBase.y - (newBuildingHeight - 1),
120
+ );
121
+ const modifiedSize = {
122
+ width: buildingSize.width + (newBuildingWidth - 1),
123
+ height: buildingSize.height + (newBuildingHeight - 1),
124
+ };
125
+ const blockedRect = computeAdjacentRect(modifiedBase, modifiedSize, minimumSpace);
126
+ const buildingTiles = adjacentTiles.filter((tile) => {
127
+ return (
128
+ tile.rx >= blockedRect.x &&
129
+ tile.rx < blockedRect.x + blockedRect.width &&
130
+ tile.ry >= blockedRect.y &&
131
+ tile.ry < blockedRect.y + blockedRect.height
132
+ );
133
+ });
134
+ buildingTiles.forEach((buildingTile) => removedTiles.add(buildingTile.id));
135
+ }
136
+ // Remove duplicate tiles.
137
+ const withDuplicatesRemoved = uniqBy(tiles, (tile) => tile.id);
138
+ // Remove tiles containing buildings and potentially area around them removed as well.
139
+ return withDuplicatesRemoved.filter((tile) => !removedTiles.has(tile.id));
140
+ }
141
+
142
+ function getTileDistances(startPoint: Vector2, tiles: Tile[]) {
143
+ return tiles
144
+ .map((tile) => ({
145
+ tile,
146
+ distance: distance(tile.rx, tile.ry, startPoint.x, startPoint.y),
147
+ }))
148
+ .sort((a, b) => {
149
+ return a.distance - b.distance;
150
+ });
151
+ }
152
+
153
+ function distance(x1: number, y1: number, x2: number, y2: number) {
154
+ var dx = x1 - x2;
155
+ var dy = y1 - y2;
156
+ let tmp = dx * dx + dy * dy;
157
+ if (0 === tmp) {
158
+ return 0;
159
+ }
160
+ return GameMath.sqrt(tmp);
161
+ }
162
+
163
+ export function getDefaultPlacementLocation(
164
+ game: GameApi,
165
+ playerData: PlayerData,
166
+ idealPoint: Vector2,
167
+ technoRules: TechnoRules,
168
+ onWater: boolean = false,
169
+ minSpace: number = 1,
170
+ ): { rx: number; ry: number } | undefined {
171
+ // Closest possible location near `startPoint`.
172
+ const size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
173
+ if (!size) {
174
+ return undefined;
175
+ }
176
+ const tiles = getAdjacencyTiles(game, playerData, technoRules, onWater, minSpace);
177
+ const tileDistances = getTileDistances(idealPoint, tiles);
178
+
179
+ for (let tileDistance of tileDistances) {
180
+ if (tileDistance.tile && game.canPlaceBuilding(playerData.name, technoRules.name, tileDistance.tile)) {
181
+ return tileDistance.tile;
182
+ }
183
+ }
184
+ return undefined;
185
+ }
186
+
187
+ // Priority 0 = don't build.
188
+ export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
189
+
190
+ export const DEFAULT_BUILDING_PRIORITY = 0;
191
+
192
+ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
193
+ // Allied
194
+ ["GAPOWR", new PowerPlant()],
195
+ ["GAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
196
+ ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
197
+ ["GAPILE", new BasicBuilding(12, 1)], // Barracks
198
+ ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
199
+ ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
200
+ ["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
201
+ ["AMRADR", new BasicBuilding(10, 1, 500)], // Airforce Command (USA)
202
+
203
+ ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
204
+ ["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
205
+
206
+ ["GAPILL", new AntiGroundStaticDefence(2, 1, 5, 5)], // Pillbox
207
+ ["ATESLA", new AntiGroundStaticDefence(2, 1, 10, 3)], // Prism Cannon
208
+ ["NASAM", new AntiAirStaticDefence(2, 1, 5)], // Patriot Missile
209
+ ["GAWALL", new AntiGroundStaticDefence(0, 0, 0, 0)], // Walls
210
+
211
+ ["E1", new BasicGroundUnit(2, 2, 0.2, 0)], // GI
212
+ ["ENGINEER", new BasicGroundUnit(1, 0, 0)], // Engineer
213
+ ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
214
+ ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
215
+ ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
216
+ ["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
217
+ ["ORCA", new BasicAirUnit(7, 1, 2, 0)], // Rocketeer
218
+ ["SREF", new ArtilleryUnit(10, 5, 3, 3)], // Prism Tank
219
+ ["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)
220
+ ["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
221
+
222
+ // Soviet
223
+ ["NAPOWR", new PowerPlant()],
224
+ ["NAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
225
+ ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
226
+ ["NAHAND", new BasicBuilding(12, 1)], // Barracks
227
+ ["HARV", new Harvester(15, 4, 2)], // War Miner
228
+ ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
229
+ ["NARADR", new BasicBuilding(10, 1, 500)], // Radar
230
+ ["NANRCT", new PowerPlant()], // Nuclear Reactor
231
+ ["NAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
232
+
233
+ ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
234
+
235
+ ["NALASR", new AntiGroundStaticDefence(2, 1, 5, 5)], // Sentry Gun
236
+ ["NAFLAK", new AntiAirStaticDefence(2, 1, 5)], // Flak Cannon
237
+ ["TESLA", new AntiGroundStaticDefence(2, 1, 10, 3)], // Tesla Coil
238
+ ["NAWALL", new AntiGroundStaticDefence(0, 0, 0, 0)], // Walls
239
+
240
+ ["E2", new BasicGroundUnit(2, 2, 0.2, 0)], // Conscript
241
+ ["SENGINEER", new BasicGroundUnit(1, 0, 0)], // Soviet Engineer
242
+ ["FLAKT", new BasicGroundUnit(2, 2, 0.1, 0.3)], // Flak Trooper
243
+ ["YURI", new BasicGroundUnit(1, 1, 1, 0)], // Yuri
244
+ ["DOG", new BasicGroundUnit(1, 1, 0, 0)], // Soviet Attack Dog
245
+ ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
246
+ ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
247
+ ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
248
+ ["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
249
+ ["V3", new ArtilleryUnit(9, 10, 0, 3)], // V3 Rocket Launcher
250
+ ]);
@@ -1,21 +1,21 @@
1
- import { GameApi, PlayerData, TechnoRules, Vector2 } from "@chronodivide/game-api";
2
- import { getPointTowardsOtherPoint } from "../map/map.js";
3
- import { getDefaultPlacementLocation } from "./buildingRules.js";
4
-
5
- export const getStaticDefencePlacement = (game: GameApi, playerData: PlayerData, technoRules: TechnoRules) => {
6
- // Prefer front towards enemy.
7
- const { startLocation, name: currentName } = playerData;
8
- const allNames = game.getPlayers();
9
- // Create a list of positions that point roughly towards hostile player start locatoins.
10
- const candidates = allNames
11
- .filter((otherName) => otherName !== currentName && !game.areAlliedPlayers(otherName, currentName))
12
- .map((otherName) => {
13
- const enemyPlayer = game.getPlayerData(otherName);
14
- return getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5);
15
- });
16
- if (candidates.length === 0) {
17
- return undefined;
18
- }
19
- const selectedLocation = candidates[Math.floor(game.generateRandom() * candidates.length)];
20
- return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 2);
21
- };
1
+ import { GameApi, PlayerData, TechnoRules, Vector2 } from "@chronodivide/game-api";
2
+ import { getPointTowardsOtherPoint } from "../map/map.js";
3
+ import { getDefaultPlacementLocation } from "./buildingRules.js";
4
+
5
+ export const getStaticDefencePlacement = (game: GameApi, playerData: PlayerData, technoRules: TechnoRules) => {
6
+ // Prefer front towards enemy.
7
+ const { startLocation, name: currentName } = playerData;
8
+ const allNames = game.getPlayers();
9
+ // Create a list of positions that point roughly towards hostile player start locatoins.
10
+ const candidates = allNames
11
+ .filter((otherName) => otherName !== currentName && !game.areAlliedPlayers(otherName, currentName))
12
+ .map((otherName) => {
13
+ const enemyPlayer = game.getPlayerData(otherName);
14
+ return getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5);
15
+ });
16
+ if (candidates.length === 0) {
17
+ return undefined;
18
+ }
19
+ const selectedLocation = candidates[Math.floor(game.generateRandom() * candidates.length)];
20
+ return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 2);
21
+ };
@@ -1,31 +1,31 @@
1
- import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../threat/threat.js";
3
- import { BasicGroundUnit } from "./basicGroundUnit.js";
4
-
5
- const IDEAL_HARVESTERS_PER_REFINERY = 2;
6
- const MAX_HARVESTERS_PER_REFINERY = 4;
7
-
8
- export class Harvester extends BasicGroundUnit {
9
- constructor(
10
- basePriority: number,
11
- baseAmount: number,
12
- private minNeeded: number,
13
- ) {
14
- super(basePriority, baseAmount, 0, 0);
15
- }
16
-
17
- // Priority goes up when we have fewer than this many refineries.
18
- getPriority(
19
- game: GameApi,
20
- playerData: PlayerData,
21
- technoRules: TechnoRules,
22
- threatCache: GlobalThreat | null,
23
- ): number {
24
- const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
25
- const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
26
-
27
- const boost = harvesters < this.minNeeded ? 3 : harvesters > refineries * MAX_HARVESTERS_PER_REFINERY ? 0 : 1;
28
-
29
- return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
30
- }
31
- }
1
+ import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
2
+ import { GlobalThreat } from "../threat/threat.js";
3
+ import { BasicGroundUnit } from "./basicGroundUnit.js";
4
+
5
+ const IDEAL_HARVESTERS_PER_REFINERY = 2;
6
+ const MAX_HARVESTERS_PER_REFINERY = 4;
7
+
8
+ export class Harvester extends BasicGroundUnit {
9
+ constructor(
10
+ basePriority: number,
11
+ baseAmount: number,
12
+ private minNeeded: number,
13
+ ) {
14
+ super(basePriority, baseAmount, 0, 0);
15
+ }
16
+
17
+ // Priority goes up when we have fewer than this many refineries.
18
+ getPriority(
19
+ game: GameApi,
20
+ playerData: PlayerData,
21
+ technoRules: TechnoRules,
22
+ threatCache: GlobalThreat | null,
23
+ ): number {
24
+ const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
25
+ const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
26
+
27
+ const boost = harvesters < this.minNeeded ? 3 : harvesters > refineries * MAX_HARVESTERS_PER_REFINERY ? 0 : 1;
28
+
29
+ return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
30
+ }
31
+ }