@supalosa/chronodivide-bot 0.3.1 → 0.5.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 (149) hide show
  1. package/.env.template +5 -0
  2. package/README.md +57 -39
  3. package/dist/bot/bot.js +27 -37
  4. package/dist/bot/bot.js.map +1 -1
  5. package/dist/bot/logic/awareness.js +13 -8
  6. package/dist/bot/logic/awareness.js.map +1 -1
  7. package/dist/bot/logic/awarenessImpl.js +132 -0
  8. package/dist/bot/logic/awarenessImpl.js.map +1 -0
  9. package/dist/bot/logic/building/ArtilleryUnit.js +2 -29
  10. package/dist/bot/logic/building/ArtilleryUnit.js.map +1 -0
  11. package/dist/bot/logic/building/antiAirStaticDefence.js +43 -0
  12. package/dist/bot/logic/building/antiAirStaticDefence.js.map +1 -0
  13. package/dist/bot/logic/building/antiGroundStaticDefence.js +8 -5
  14. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  15. package/dist/bot/logic/building/basicAirUnit.js +2 -23
  16. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  17. package/dist/bot/logic/building/basicBuilding.js +3 -2
  18. package/dist/bot/logic/building/basicBuilding.js.map +1 -1
  19. package/dist/bot/logic/building/basicGroundUnit.js +2 -43
  20. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  21. package/dist/bot/logic/building/building.js +55 -11
  22. package/dist/bot/logic/building/building.js.map +1 -0
  23. package/dist/bot/logic/building/buildingRules.js +62 -50
  24. package/dist/bot/logic/building/buildingRules.js.map +1 -1
  25. package/dist/bot/logic/building/common.js +19 -0
  26. package/dist/bot/logic/building/common.js.map +1 -0
  27. package/dist/bot/logic/building/harvester.js +2 -1
  28. package/dist/bot/logic/building/harvester.js.map +1 -1
  29. package/dist/bot/logic/building/queueController.js +73 -41
  30. package/dist/bot/logic/building/queueController.js.map +1 -1
  31. package/dist/bot/logic/common/utils.js +35 -0
  32. package/dist/bot/logic/common/utils.js.map +1 -1
  33. package/dist/bot/logic/composition/alliedCompositions.js +13 -0
  34. package/dist/bot/logic/composition/alliedCompositions.js.map +1 -0
  35. package/dist/bot/logic/composition/common.js +2 -0
  36. package/dist/bot/logic/composition/common.js.map +1 -0
  37. package/dist/bot/logic/composition/sovietCompositions.js +13 -0
  38. package/dist/bot/logic/composition/sovietCompositions.js.map +1 -0
  39. package/dist/bot/logic/mission/actionBatcher.js +92 -0
  40. package/dist/bot/logic/mission/actionBatcher.js.map +1 -0
  41. package/dist/bot/logic/mission/behaviours/combatSquad.js +124 -0
  42. package/dist/bot/logic/mission/behaviours/combatSquad.js.map +1 -0
  43. package/dist/bot/logic/mission/behaviours/common.js +58 -0
  44. package/dist/bot/logic/mission/behaviours/common.js.map +1 -0
  45. package/dist/bot/logic/mission/behaviours/engineerSquad.js +39 -0
  46. package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +1 -0
  47. package/dist/bot/logic/mission/behaviours/expansionSquad.js +46 -0
  48. package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +1 -0
  49. package/dist/bot/logic/mission/behaviours/retreatSquad.js +31 -0
  50. package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +1 -0
  51. package/{src/bot/logic/squad/behaviours/scoutingSquad.ts → dist/bot/logic/mission/behaviours/scoutingSquad.js} +29 -47
  52. package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +1 -0
  53. package/dist/bot/logic/mission/mission.js +91 -19
  54. package/dist/bot/logic/mission/mission.js.map +1 -1
  55. package/dist/bot/logic/mission/missionController.js +262 -21
  56. package/dist/bot/logic/mission/missionController.js.map +1 -1
  57. package/dist/bot/logic/mission/missions/attackMission.js +159 -52
  58. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  59. package/dist/bot/logic/mission/missions/basicMission.js +13 -0
  60. package/dist/bot/logic/mission/missions/basicMission.js.map +1 -0
  61. package/dist/bot/logic/mission/missions/defenceMission.js +43 -28
  62. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  63. package/dist/bot/logic/mission/missions/engineerMission.js +37 -7
  64. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -1
  65. package/dist/bot/logic/mission/missions/expansionMission.js +42 -6
  66. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -1
  67. package/dist/bot/logic/mission/missions/missionBehaviour.js +2 -0
  68. package/dist/bot/logic/mission/missions/missionBehaviour.js.map +1 -0
  69. package/dist/bot/logic/mission/missions/retreatMission.js +31 -5
  70. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  71. package/dist/bot/logic/mission/missions/scoutingMission.js +103 -6
  72. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -1
  73. package/dist/bot/logic/mission/missions/squads/combatSquad.js +116 -0
  74. package/dist/bot/logic/mission/missions/squads/combatSquad.js.map +1 -0
  75. package/dist/bot/logic/mission/missions/squads/common.js +58 -0
  76. package/dist/bot/logic/mission/missions/squads/common.js.map +1 -0
  77. package/dist/bot/logic/mission/missions/squads/squad.js +2 -0
  78. package/dist/bot/logic/mission/missions/squads/squad.js.map +1 -0
  79. package/dist/bot/logic/squad/behaviours/attackSquad.js +63 -56
  80. package/dist/bot/logic/squad/behaviours/combatSquad.js +19 -18
  81. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
  82. package/dist/bot/logic/squad/behaviours/common.js +2 -19
  83. package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
  84. package/dist/bot/logic/squad/behaviours/defenceSquad.js +15 -2
  85. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
  86. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +17 -21
  87. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
  88. package/dist/bot/logic/squad/squad.js +8 -5
  89. package/dist/bot/logic/squad/squad.js.map +1 -1
  90. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
  91. package/dist/bot/logic/squad/squadController.js +3 -2
  92. package/dist/bot/logic/squad/squadController.js.map +1 -1
  93. package/dist/bot/logic/threat/threatCalculator.js +5 -5
  94. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  95. package/dist/exampleBot.js +53 -16
  96. package/dist/exampleBot.js.map +1 -1
  97. package/package.json +5 -4
  98. package/src/bot/bot.ts +38 -53
  99. package/src/bot/logic/awareness.ts +34 -22
  100. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
  101. package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
  102. package/src/bot/logic/building/artilleryUnit.ts +2 -28
  103. package/src/bot/logic/building/basicAirUnit.ts +2 -33
  104. package/src/bot/logic/building/basicBuilding.ts +8 -6
  105. package/src/bot/logic/building/basicGroundUnit.ts +2 -46
  106. package/src/bot/logic/building/buildingRules.ts +73 -57
  107. package/src/bot/logic/building/common.ts +23 -0
  108. package/src/bot/logic/building/harvester.ts +2 -1
  109. package/src/bot/logic/building/queueController.ts +105 -42
  110. package/src/bot/logic/common/utils.ts +47 -0
  111. package/src/bot/logic/composition/alliedCompositions.ts +22 -0
  112. package/src/bot/logic/composition/common.ts +3 -0
  113. package/src/bot/logic/composition/sovietCompositions.ts +21 -0
  114. package/src/bot/logic/mission/actionBatcher.ts +124 -0
  115. package/src/bot/logic/mission/mission.ts +186 -37
  116. package/src/bot/logic/mission/missionController.ts +340 -31
  117. package/src/bot/logic/mission/missionFactories.ts +3 -3
  118. package/src/bot/logic/mission/missions/attackMission.ts +234 -56
  119. package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
  120. package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
  121. package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
  122. package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
  123. package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
  124. package/src/bot/logic/mission/missions/squads/combatSquad.ts +160 -0
  125. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +14 -20
  126. package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
  127. package/src/bot/logic/threat/threat.ts +15 -15
  128. package/src/bot/logic/threat/threatCalculator.ts +10 -10
  129. package/src/exampleBot.ts +59 -19
  130. package/.prettierrc +0 -5
  131. package/TODO.md +0 -18
  132. package/dist/bot/logic/building/artilleryUnit.js.map +0 -1
  133. package/dist/bot/logic/building/massedAntiGroundUnit.js +0 -20
  134. package/dist/bot/logic/building/queues.js +0 -19
  135. package/dist/bot/logic/knowledge.js +0 -1
  136. package/dist/bot/logic/mission/basicMission.js +0 -26
  137. package/dist/bot/logic/mission/expansionMission.js +0 -32
  138. package/dist/bot/logic/squad/behaviours/squadExpansion.js +0 -31
  139. package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
  140. package/rules.ini +0 -23126
  141. package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
  142. package/src/bot/logic/squad/behaviours/combatSquad.ts +0 -127
  143. package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -53
  144. package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -59
  145. package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -44
  146. package/src/bot/logic/squad/squad.ts +0 -159
  147. package/src/bot/logic/squad/squadBehaviour.ts +0 -62
  148. package/src/bot/logic/squad/squadBehaviours.ts +0 -8
  149. package/src/bot/logic/squad/squadController.ts +0 -254
@@ -14,6 +14,7 @@ import {
14
14
  DEFAULT_BUILDING_PRIORITY,
15
15
  getDefaultPlacementLocation,
16
16
  } from "./buildingRules.js";
17
+ import { DebugLogger } from "../common/utils";
17
18
 
18
19
  export const QUEUES = [
19
20
  QueueType.Structures,
@@ -43,9 +44,16 @@ export const queueTypeToName = (queue: QueueType) => {
43
44
  }
44
45
  };
45
46
 
46
- const DEBUG_BUILD_QUEUES = true;
47
+ type QueueState = {
48
+ queue: QueueType;
49
+ /** sorted in ascending order (last item is the topItem) */
50
+ items: TechnoRulesWithPriority[];
51
+ topItem: TechnoRulesWithPriority | undefined;
52
+ };
47
53
 
48
54
  export class QueueController {
55
+ private queueStates: QueueState[] = [];
56
+
49
57
  constructor() {}
50
58
 
51
59
  public onAiUpdate(
@@ -54,23 +62,35 @@ export class QueueController {
54
62
  actionsApi: ActionsApi,
55
63
  playerData: PlayerData,
56
64
  threatCache: GlobalThreat | null,
65
+ unitTypeRequests: Map<string, number>,
57
66
  logger: (message: string) => void,
58
67
  ) {
59
- const decisions = QUEUES.map((queueType) => {
68
+ this.queueStates = QUEUES.map((queueType) => {
60
69
  const options = productionApi.getAvailableObjects(queueType);
70
+ const items = this.getPrioritiesForBuildingOptions(
71
+ game,
72
+ options,
73
+ threatCache,
74
+ playerData,
75
+ unitTypeRequests,
76
+ logger,
77
+ );
78
+ const topItem = items.length > 0 ? items[items.length - 1] : undefined;
61
79
  return {
62
80
  queue: queueType,
63
- decision: this.getBestOptionForBuilding(game, options, threatCache, playerData, logger),
81
+ items,
82
+ // only if the top item has a priority above zero
83
+ topItem: topItem && topItem.priority > 0 ? topItem : undefined,
64
84
  };
65
- }).filter((decision) => decision.decision != null);
66
- let totalWeightAcrossQueues = decisions
67
- .map((decision) => decision.decision?.priority!)
85
+ });
86
+ const totalWeightAcrossQueues = this.queueStates
87
+ .map((decision) => decision.topItem?.priority!)
68
88
  .reduce((pV, cV) => pV + cV, 0);
69
- let totalCostAcrossQueues = decisions
70
- .map((decision) => decision.decision?.unit.cost!)
89
+ const totalCostAcrossQueues = this.queueStates
90
+ .map((decision) => decision.topItem?.unit.cost!)
71
91
  .reduce((pV, cV) => pV + cV, 0);
72
92
 
73
- decisions.forEach((decision) => {
93
+ this.queueStates.forEach((decision) => {
74
94
  this.updateBuildQueue(
75
95
  game,
76
96
  productionApi,
@@ -78,7 +98,7 @@ export class QueueController {
78
98
  playerData,
79
99
  threatCache,
80
100
  decision.queue,
81
- decision.decision,
101
+ decision.topItem,
82
102
  totalWeightAcrossQueues,
83
103
  totalCostAcrossQueues,
84
104
  logger,
@@ -86,13 +106,17 @@ export class QueueController {
86
106
  });
87
107
 
88
108
  // Repair is simple - just repair everything that's damaged.
89
- game.getVisibleUnits(playerData.name, "self", (r) => r.repairable).forEach((unitId) => {
90
- const unit = game.getUnitData(unitId);
91
- if (!unit || !unit.hitPoints || !unit.maxHitPoints || unit.hasWrenchRepair) {
92
- return;
93
- }
94
- actionsApi.toggleRepairWrench(unitId);
95
- });
109
+ if (playerData.credits > 0) {
110
+ game.getVisibleUnits(playerData.name, "self", (r) => r.repairable).forEach((unitId) => {
111
+ const unit = game.getUnitData(unitId);
112
+ if (!unit || !unit.hitPoints || !unit.maxHitPoints || unit.hasWrenchRepair) {
113
+ return;
114
+ }
115
+ if (unit.hitPoints < unit.maxHitPoints) {
116
+ actionsApi.toggleRepairWrench(unitId);
117
+ }
118
+ });
119
+ }
96
120
  }
97
121
 
98
122
  private updateBuildQueue(
@@ -120,31 +144,41 @@ export class QueueController {
120
144
  // Consider placing it.
121
145
  const objectReady: TechnoRules = queueData.items[0].rules;
122
146
  if (queueType == QueueType.Structures || queueType == QueueType.Armory) {
123
- logger(`Complete ${queueTypeToName(queueType)}: ${objectReady.name}`);
124
147
  let location: { rx: number; ry: number } | undefined = this.getBestLocationForStructure(
125
148
  game,
126
149
  playerData,
127
150
  objectReady,
128
151
  );
129
152
  if (location !== undefined) {
153
+ logger(
154
+ `Completed: ${queueTypeToName(queueType)}: ${objectReady.name}, placing at ${location.rx},${
155
+ location.ry
156
+ }`,
157
+ );
130
158
  actionsApi.placeBuilding(objectReady.name, location.rx, location.ry);
159
+ } else {
160
+ logger(`Completed: ${queueTypeToName(queueType)}: ${objectReady.name} but nowhere to place it`);
131
161
  }
132
162
  }
133
163
  } else if (queueData.status == QueueStatus.Active && queueData.items.length > 0 && decision != null) {
134
- // Consider cancelling if something else is significantly higher priority.
135
- const current = queueData.items[0].rules;
136
- const options = productionApi.getAvailableObjects(queueType);
137
- if (decision.unit != current) {
164
+ // Consider cancelling if something else is significantly higher priority than what is currently being produced.
165
+ const currentProduction = queueData.items[0].rules;
166
+ if (decision.unit != currentProduction) {
138
167
  // Changing our mind.
139
- let currentItemPriority = this.getPriorityForBuildingOption(current, game, playerData, threatCache);
168
+ let currentItemPriority = this.getPriorityForBuildingOption(
169
+ currentProduction,
170
+ game,
171
+ playerData,
172
+ threatCache,
173
+ );
140
174
  let newItemPriority = decision.priority;
141
175
  if (newItemPriority > currentItemPriority * 2) {
142
176
  logger(
143
- `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${current.name} because ${
177
+ `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${currentProduction.name} because ${
144
178
  decision.unit.name
145
179
  } has 2x higher priority.`,
146
180
  );
147
- actionsApi.unqueueFromProduction(queueData.type, current.name, current.type, 1);
181
+ actionsApi.unqueueFromProduction(queueData.type, currentProduction.name, currentProduction.type, 1);
148
182
  }
149
183
  } else {
150
184
  // Not changing our mind, but maybe other queues are more important for now.
@@ -173,32 +207,29 @@ export class QueueController {
173
207
  }
174
208
  }
175
209
 
176
- private getBestOptionForBuilding(
210
+ private getPrioritiesForBuildingOptions(
177
211
  game: GameApi,
178
212
  options: TechnoRules[],
179
213
  threatCache: GlobalThreat | null,
180
214
  playerData: PlayerData,
181
- logger: (message: string) => void,
182
- ): TechnoRulesWithPriority | undefined {
215
+ unitTypeRequests: Map<string, number>,
216
+ logger: DebugLogger,
217
+ ): TechnoRulesWithPriority[] {
183
218
  let priorityQueue: TechnoRulesWithPriority[] = [];
184
219
  options.forEach((option) => {
185
- let priority = this.getPriorityForBuildingOption(option, game, playerData, threatCache);
186
- if (priority > 0) {
187
- priorityQueue.push({ unit: option, priority: priority });
220
+ const calculatedPriority = this.getPriorityForBuildingOption(option, game, playerData, threatCache);
221
+ // Get the higher of the dynamic and the mission priority for the unit.
222
+ const actualPriority = Math.max(
223
+ calculatedPriority,
224
+ unitTypeRequests.get(option.name) ?? calculatedPriority,
225
+ );
226
+ if (actualPriority > 0) {
227
+ priorityQueue.push({ unit: option, priority: actualPriority });
188
228
  }
189
229
  });
190
230
 
191
- priorityQueue = priorityQueue.sort((a, b) => {
192
- return a.priority - b.priority;
193
- });
194
- if (priorityQueue.length > 0) {
195
- if (DEBUG_BUILD_QUEUES && game.getCurrentTick() % 100 === 0) {
196
- let queueString = priorityQueue.map((item) => item.unit.name + "(" + item.priority + ")").join(", ");
197
- logger(`Build priority currently: ${queueString}`);
198
- }
199
- }
200
-
201
- return priorityQueue.pop();
231
+ priorityQueue = priorityQueue.sort((a, b) => a.priority - b.priority);
232
+ return priorityQueue;
202
233
  }
203
234
 
204
235
  private getPriorityForBuildingOption(
@@ -231,4 +262,36 @@ export class QueueController {
231
262
  return getDefaultPlacementLocation(game, playerData, playerData.startLocation, objectReady);
232
263
  }
233
264
  }
265
+
266
+ public getGlobalDebugText(gameApi: GameApi, productionApi: ProductionApi) {
267
+ const productionState = QUEUES.reduce((prev, queueType) => {
268
+ if (productionApi.getQueueData(queueType).size === 0) {
269
+ return prev;
270
+ }
271
+ const paused = productionApi.getQueueData(queueType).status === QueueStatus.OnHold;
272
+ return (
273
+ prev +
274
+ " [" +
275
+ queueTypeToName(queueType) +
276
+ (paused ? " PAUSED" : "") +
277
+ ": " +
278
+ productionApi
279
+ .getQueueData(queueType)
280
+ .items.map((item) => item.rules.name + (item.quantity > 1 ? "x" + item.quantity : "")) +
281
+ "]"
282
+ );
283
+ }, "");
284
+
285
+ const queueStates = this.queueStates
286
+ .filter((queueState) => queueState.items.length > 0)
287
+ .map((queueState) => {
288
+ const queueString = queueState.items
289
+ .map((item) => item.unit.name + "(" + Math.round(item.priority * 10) / 10 + ")")
290
+ .join(", ");
291
+ return `${queueTypeToName(queueState.queue)} Prios: ${queueString}\n`;
292
+ })
293
+ .join("");
294
+
295
+ return `Production: ${productionState}\n${queueStates}`;
296
+ }
234
297
  }
@@ -1,5 +1,18 @@
1
+ import { GameObjectData, TechnoRules, UnitData } from "@chronodivide/game-api";
2
+
1
3
  export type DebugLogger = (message: string, sayInGame?: boolean) => void;
2
4
 
5
+ const SOVIET_COUNTRY_NAMES = ["Africans", "Arabs", "Confederation", "Russians"];
6
+
7
+ export const isSoviet = (countryName: string) => SOVIET_COUNTRY_NAMES.includes(countryName);
8
+
9
+ export const isOwnedByNeutral = (unitData: UnitData | undefined) => unitData?.owner === "@@NEUTRAL@@";
10
+
11
+ // Return if the given unit would have .isSelectableCombatant = true.
12
+ // Usable on GameObjectData (which is faster to get than TechnoRules)
13
+ export const isSelectableCombatant = (rules: GameObjectData | undefined) =>
14
+ !!(rules?.rules as any)?.isSelectableCombatant;
15
+
3
16
  // Thanks use-strict!
4
17
  export function formatTimeDuration(timeSeconds: number, skipZeroHours = false) {
5
18
  let h = Math.floor(timeSeconds / 3600);
@@ -16,6 +29,23 @@ export function pad(n: any, format = "0000") {
16
29
  return format.substring(0, format.length - str.length) + str;
17
30
  }
18
31
 
32
+ // So we don't need lodash
33
+ export function minBy<T>(array: T[], predicate: (arg: T) => number | null): T | null {
34
+ if (array.length === 0) {
35
+ return null;
36
+ }
37
+ let minIdx = 0;
38
+ let minVal = predicate(array[0]);
39
+ for (let i = 1; i < array.length; ++i) {
40
+ const newVal = predicate(array[i]);
41
+ if (minVal === null || (newVal !== null && newVal < minVal)) {
42
+ minIdx = i;
43
+ minVal = newVal;
44
+ }
45
+ }
46
+ return array[minIdx];
47
+ }
48
+
19
49
  export function maxBy<T>(array: T[], predicate: (arg: T) => number | null): T | null {
20
50
  if (array.length === 0) {
21
51
  return null;
@@ -63,3 +93,20 @@ export function countBy<T>(array: T[], predicate: (arg: T) => string | undefined
63
93
  {} as Record<string, number>,
64
94
  );
65
95
  }
96
+
97
+ export function groupBy<K extends string, V>(array: V[], predicate: (arg: V) => K): { [key in K]: V[] } {
98
+ return array.reduce(
99
+ (prev, newVal) => {
100
+ const val = predicate(newVal);
101
+ if (val === undefined) {
102
+ return prev;
103
+ }
104
+ if (!prev.hasOwnProperty(val)) {
105
+ prev[val] = [];
106
+ }
107
+ prev[val].push(newVal);
108
+ return prev;
109
+ },
110
+ {} as Record<K, V[]>,
111
+ );
112
+ }
@@ -0,0 +1,22 @@
1
+ import { GameApi, PlayerData } from "@chronodivide/game-api";
2
+ import { MatchAwareness } from "../awareness";
3
+ import { UnitComposition } from "./common";
4
+
5
+ export const getAlliedCompositions = (
6
+ gameApi: GameApi,
7
+ playerData: PlayerData,
8
+ matchAwareness: MatchAwareness,
9
+ ): UnitComposition => {
10
+ const hasWarFactory = gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "GAWEAP").length > 0;
11
+ const hasAirforce =
12
+ gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "GAAIRC" || r.name === "AMRADR").length > 0;
13
+ const hasBattleLab = gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "GATECH").length > 0;
14
+
15
+ const includeInfantry = !hasAirforce && !hasBattleLab;
16
+ return {
17
+ ...(includeInfantry && { E1: 5 }),
18
+ ...(hasWarFactory && { MTNK: 3, FV: 2 }),
19
+ ...(hasAirforce && { JUMPJET: 6 }),
20
+ ...(hasBattleLab && { SREF: 2, MGTK: 3 }),
21
+ };
22
+ };
@@ -0,0 +1,3 @@
1
+ export type UnitComposition = {
2
+ [unitType: string]: number;
3
+ };
@@ -0,0 +1,21 @@
1
+ import { GameApi, PlayerData } from "@chronodivide/game-api";
2
+ import { MatchAwareness } from "../awareness";
3
+ import { UnitComposition } from "./common";
4
+
5
+ export const getSovietComposition = (
6
+ gameApi: GameApi,
7
+ playerData: PlayerData,
8
+ matchAwareness: MatchAwareness,
9
+ ): UnitComposition => {
10
+ const hasWarFactory = gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "NAWEAP").length > 0;
11
+ const hasRadar = gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "NARADR").length > 0;
12
+ const hasBattleLab = gameApi.getVisibleUnits(playerData.name, "self", (r) => r.name === "NATECH").length > 0;
13
+
14
+ const includeInfantry = !hasBattleLab;
15
+ return {
16
+ ...(includeInfantry && { E2: 10 }),
17
+ ...(hasWarFactory && { HTNK: 3, HTK: 2 }),
18
+ ...(hasRadar && { V3: 1 }),
19
+ ...(hasBattleLab && { APOC: 2 }),
20
+ };
21
+ };
@@ -0,0 +1,124 @@
1
+ // Used to group related actions together to minimise actionApi calls. For example, if multiple units
2
+
3
+ import { ActionsApi, OrderType, Vector2 } from "@chronodivide/game-api";
4
+ import { groupBy } from "../common/utils.js";
5
+
6
+ // are ordered to move to the same location, all of them will be ordered to move in a single action.
7
+ export class BatchableAction {
8
+ private constructor(
9
+ private _unitId: number,
10
+ private _orderType: OrderType,
11
+ private _point?: Vector2,
12
+ private _targetId?: number,
13
+ // If you don't want this action to be swallowed by dedupe, provide a unique nonce
14
+ private _nonce: number = 0,
15
+ ) {}
16
+
17
+ static noTarget(unitId: number, orderType: OrderType, nonce: number = 0) {
18
+ return new BatchableAction(unitId, orderType, undefined, undefined, nonce);
19
+ }
20
+
21
+ static toPoint(unitId: number, orderType: OrderType, point: Vector2, nonce: number = 0) {
22
+ return new BatchableAction(unitId, orderType, point, undefined);
23
+ }
24
+
25
+ static toTargetId(unitId: number, orderType: OrderType, targetId: number, nonce: number = 0) {
26
+ return new BatchableAction(unitId, orderType, undefined, targetId, nonce);
27
+ }
28
+
29
+ public get unitId() {
30
+ return this._unitId;
31
+ }
32
+
33
+ public get orderType() {
34
+ return this._orderType;
35
+ }
36
+
37
+ public get point() {
38
+ return this._point;
39
+ }
40
+
41
+ public get targetId() {
42
+ return this._targetId;
43
+ }
44
+
45
+ public isSameAs(other: BatchableAction) {
46
+ if (this._unitId !== other._unitId) {
47
+ return false;
48
+ }
49
+ if (this._orderType !== other._orderType) {
50
+ return false;
51
+ }
52
+ if (this._point !== other._point) {
53
+ return false;
54
+ }
55
+ if (this._targetId !== other._targetId) {
56
+ return false;
57
+ }
58
+ if (this._nonce !== other._nonce) {
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ }
64
+
65
+ export class ActionBatcher {
66
+ private actions: BatchableAction[];
67
+
68
+ constructor() {
69
+ this.actions = [];
70
+ }
71
+
72
+ push(action: BatchableAction) {
73
+ this.actions.push(action);
74
+ }
75
+
76
+ resolve(actionsApi: ActionsApi) {
77
+ const groupedCommands = groupBy(this.actions, (action) => action.orderType.valueOf().toString());
78
+ const vectorToStr = (v: Vector2) => v.x + "," + v.y;
79
+ const strToVector = (str: string) => {
80
+ const [x, y] = str.split(",");
81
+ return new Vector2(parseInt(x), parseInt(y));
82
+ };
83
+
84
+ // Group by command type.
85
+ Object.entries(groupedCommands).forEach(([commandValue, commands]) => {
86
+ // i hate this
87
+ const commandType: OrderType = parseInt(commandValue) as OrderType;
88
+ // Group by command target ID.
89
+ const byTarget = groupBy(
90
+ commands.filter((command) => !!command.targetId),
91
+ (command) => command.targetId?.toString()!,
92
+ );
93
+ Object.entries(byTarget).forEach(([targetId, unitCommands]) => {
94
+ actionsApi.orderUnits(
95
+ unitCommands.map((command) => command.unitId),
96
+ commandType,
97
+ parseInt(targetId),
98
+ );
99
+ });
100
+ // Group by position (the vector is encoded as a string of the form "x,y")
101
+ const byPosition = groupBy(
102
+ commands.filter((command) => !!command.point),
103
+ (command) => vectorToStr(command.point!),
104
+ );
105
+ Object.entries(byPosition).forEach(([point, unitCommands]) => {
106
+ const vector = strToVector(point);
107
+ actionsApi.orderUnits(
108
+ unitCommands.map((command) => command.unitId),
109
+ commandType,
110
+ vector.x,
111
+ vector.y,
112
+ );
113
+ });
114
+ // Actions with no targets
115
+ const noTargets = commands.filter((command) => !command.targetId && !command.point);
116
+ if (noTargets.length > 0) {
117
+ actionsApi.orderUnits(
118
+ noTargets.map((action) => action.unitId),
119
+ commandType,
120
+ );
121
+ }
122
+ });
123
+ }
124
+ }