@supalosa/chronodivide-bot 0.4.0 → 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 (155) hide show
  1. package/.env.template +5 -0
  2. package/README.md +54 -47
  3. package/dist/bot/bot.js +14 -30
  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 +7 -4
  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 +10 -5
  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 +69 -42
  30. package/dist/bot/logic/building/queueController.js.map +1 -1
  31. package/dist/bot/logic/common/utils.js +21 -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} +27 -51
  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 +113 -39
  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 +21 -25
  81. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
  82. package/dist/bot/logic/squad/behaviours/common.js +11 -26
  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/engineerSquad.js +2 -4
  86. package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -1
  87. package/dist/bot/logic/squad/behaviours/expansionSquad.js +2 -4
  88. package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -1
  89. package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -4
  90. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
  91. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +18 -25
  92. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
  93. package/dist/bot/logic/squad/squad.js +10 -10
  94. package/dist/bot/logic/squad/squad.js.map +1 -1
  95. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
  96. package/dist/bot/logic/squad/squadController.js +5 -17
  97. package/dist/bot/logic/squad/squadController.js.map +1 -1
  98. package/dist/bot/logic/threat/threatCalculator.js +5 -5
  99. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  100. package/dist/exampleBot.js +45 -18
  101. package/dist/exampleBot.js.map +1 -1
  102. package/package.json +5 -4
  103. package/src/bot/bot.ts +19 -45
  104. package/src/bot/logic/awareness.ts +34 -22
  105. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
  106. package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
  107. package/src/bot/logic/building/artilleryUnit.ts +2 -28
  108. package/src/bot/logic/building/basicAirUnit.ts +2 -33
  109. package/src/bot/logic/building/basicBuilding.ts +8 -6
  110. package/src/bot/logic/building/basicGroundUnit.ts +2 -46
  111. package/src/bot/logic/building/buildingRules.ts +15 -9
  112. package/src/bot/logic/building/common.ts +23 -0
  113. package/src/bot/logic/building/harvester.ts +2 -1
  114. package/src/bot/logic/building/queueController.ts +98 -43
  115. package/src/bot/logic/common/utils.ts +28 -0
  116. package/src/bot/logic/composition/alliedCompositions.ts +22 -0
  117. package/src/bot/logic/composition/common.ts +3 -0
  118. package/src/bot/logic/composition/sovietCompositions.ts +21 -0
  119. package/src/bot/logic/{squad/behaviours → mission}/actionBatcher.ts +66 -7
  120. package/src/bot/logic/mission/mission.ts +186 -37
  121. package/src/bot/logic/mission/missionController.ts +340 -31
  122. package/src/bot/logic/mission/missionFactories.ts +3 -3
  123. package/src/bot/logic/mission/missions/attackMission.ts +181 -44
  124. package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
  125. package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
  126. package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
  127. package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
  128. package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
  129. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/combatSquad.ts +56 -33
  130. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +11 -17
  131. package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
  132. package/src/bot/logic/threat/threat.ts +15 -15
  133. package/src/bot/logic/threat/threatCalculator.ts +10 -10
  134. package/src/exampleBot.ts +50 -24
  135. package/.prettierrc +0 -5
  136. package/TODO.md +0 -15
  137. package/dist/bot/logic/building/artilleryUnit.js.map +0 -1
  138. package/dist/bot/logic/building/massedAntiGroundUnit.js +0 -20
  139. package/dist/bot/logic/building/queues.js +0 -19
  140. package/dist/bot/logic/knowledge.js +0 -1
  141. package/dist/bot/logic/mission/basicMission.js +0 -26
  142. package/dist/bot/logic/mission/expansionMission.js +0 -32
  143. package/dist/bot/logic/squad/behaviours/actionBatcher.js +0 -36
  144. package/dist/bot/logic/squad/behaviours/actionBatcher.js.map +0 -1
  145. package/dist/bot/logic/squad/behaviours/squadExpansion.js +0 -31
  146. package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
  147. package/rules.ini +0 -23126
  148. package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
  149. package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -58
  150. package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -64
  151. package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -50
  152. package/src/bot/logic/squad/squad.ts +0 -165
  153. package/src/bot/logic/squad/squadBehaviour.ts +0 -66
  154. package/src/bot/logic/squad/squadBehaviours.ts +0 -8
  155. package/src/bot/logic/squad/squadController.ts +0 -271
@@ -20,6 +20,7 @@ import { PowerPlant } from "./powerPlant.js";
20
20
  import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
21
21
  import { Harvester } from "./harvester.js";
22
22
  import { uniqBy } from "../common/utils.js";
23
+ import { AntiAirStaticDefence } from "./antiAirStaticDefence.js";
23
24
 
24
25
  export interface AiBuildingRules {
25
26
  getPriority(
@@ -183,7 +184,7 @@ export function getDefaultPlacementLocation(
183
184
  // Priority 0 = don't build.
184
185
  export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
185
186
 
186
- export const DEFAULT_BUILDING_PRIORITY = 1;
187
+ export const DEFAULT_BUILDING_PRIORITY = 0;
187
188
 
188
189
  export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
189
190
  // Allied
@@ -192,7 +193,6 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
192
193
  ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
193
194
  ["GAPILE", new BasicBuilding(12, 1)], // Barracks
194
195
  ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
195
- ["ENGINEER", new BasicBuilding(10, 1, 1000)], // Engineer
196
196
  ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
197
197
  ["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
198
198
  ["AMRADR", new BasicBuilding(10, 1, 500)], // Airforce Command (USA)
@@ -200,11 +200,13 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
200
200
  ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
201
201
  ["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
202
202
 
203
- ["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
204
- ["ATESLA", new AntiGroundStaticDefence(5, 1, 10)], // Prism Cannon
203
+ ["GAPILL", new AntiGroundStaticDefence(2, 1, 5)], // Pillbox
204
+ ["ATESLA", new AntiGroundStaticDefence(2, 1, 10)], // Prism Cannon
205
+ ["NASAM", new AntiAirStaticDefence(2, 1, 5)], // Prism Cannon
205
206
  ["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
206
207
 
207
- ["E1", new BasicGroundUnit(2, 3, 0.25, 0)], // GI
208
+ ["E1", new BasicGroundUnit(2, 2, 0.2, 0)], // GI
209
+ ["ENGINEER", new BasicGroundUnit(1, 0, 0)], // Engineer
208
210
  ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
209
211
  ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
210
212
  ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
@@ -220,7 +222,6 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
220
222
  ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
221
223
  ["NAHAND", new BasicBuilding(12, 1)], // Barracks
222
224
  ["HARV", new Harvester(15, 4, 2)], // War Miner
223
- ["SENGINEER", new BasicBuilding(10, 1, 1000)], // Soviet Engineer
224
225
  ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
225
226
  ["NARADR", new BasicBuilding(10, 1, 500)], // Radar
226
227
  ["NANRCT", new PowerPlant()], // Nuclear Reactor
@@ -228,11 +229,16 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
228
229
 
229
230
  ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
230
231
 
231
- ["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
232
- ["TESLA", new AntiGroundStaticDefence(5, 1, 10)], // Tesla Coil
232
+ ["NALASR", new AntiGroundStaticDefence(2, 1, 5)], // Sentry Gun
233
+ ["NAFLAK", new AntiAirStaticDefence(2, 1, 5)], // Flak Cannon
234
+ ["TESLA", new AntiGroundStaticDefence(2, 1, 10)], // Tesla Coil
233
235
  ["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
234
236
 
235
- ["E2", new BasicGroundUnit(2, 3, 0.25, 0)], // Conscript
237
+ ["E2", new BasicGroundUnit(2, 2, 0.2, 0)], // Conscript
238
+ ["SENGINEER", new BasicGroundUnit(1, 0, 0)], // Soviet Engineer
239
+ ["FLAKT", new BasicGroundUnit(2, 2, 0.1, 0.3)], // Flak Trooper
240
+ ["YURI", new BasicGroundUnit(1, 1, 1, 0)], // Yuri
241
+ ["DOG", new BasicGroundUnit(1, 1, 0, 0)], // Soviet Attack Dog
236
242
  ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
237
243
  ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
238
244
  ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
@@ -0,0 +1,23 @@
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
+ let startLocation = playerData.startLocation;
8
+ let players = game.getPlayers();
9
+ let enemyFacingLocationCandidates: Vector2[] = [];
10
+ for (let i = 0; i < players.length; ++i) {
11
+ let playerName = players[i];
12
+ if (playerName == playerData.name) {
13
+ continue;
14
+ }
15
+ let enemyPlayer = game.getPlayerData(playerName);
16
+ enemyFacingLocationCandidates.push(
17
+ getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5),
18
+ );
19
+ }
20
+ let selectedLocation =
21
+ enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
22
+ return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 0);
23
+ };
@@ -3,6 +3,7 @@ import { GlobalThreat } from "../threat/threat.js";
3
3
  import { BasicGroundUnit } from "./basicGroundUnit.js";
4
4
 
5
5
  const IDEAL_HARVESTERS_PER_REFINERY = 2;
6
+ const MAX_HARVESTERS_PER_REFINERY = 4;
6
7
 
7
8
  export class Harvester extends BasicGroundUnit {
8
9
  constructor(
@@ -23,7 +24,7 @@ export class Harvester extends BasicGroundUnit {
23
24
  const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
24
25
  const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
25
26
 
26
- const boost = harvesters < this.minNeeded ? 3 : 1;
27
+ const boost = harvesters < this.minNeeded ? 3 : harvesters > refineries * MAX_HARVESTERS_PER_REFINERY ? 0 : 1;
27
28
 
28
29
  return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
29
30
  }
@@ -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,15 +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
- if (unit.hitPoints < unit.maxHitPoints) {
95
- actionsApi.toggleRepairWrench(unitId);
96
- }
97
- });
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
+ }
98
120
  }
99
121
 
100
122
  private updateBuildQueue(
@@ -139,20 +161,24 @@ export class QueueController {
139
161
  }
140
162
  }
141
163
  } else if (queueData.status == QueueStatus.Active && queueData.items.length > 0 && decision != null) {
142
- // Consider cancelling if something else is significantly higher priority.
143
- const current = queueData.items[0].rules;
144
- const options = productionApi.getAvailableObjects(queueType);
145
- 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) {
146
167
  // Changing our mind.
147
- let currentItemPriority = this.getPriorityForBuildingOption(current, game, playerData, threatCache);
168
+ let currentItemPriority = this.getPriorityForBuildingOption(
169
+ currentProduction,
170
+ game,
171
+ playerData,
172
+ threatCache,
173
+ );
148
174
  let newItemPriority = decision.priority;
149
175
  if (newItemPriority > currentItemPriority * 2) {
150
176
  logger(
151
- `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${current.name} because ${
177
+ `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${currentProduction.name} because ${
152
178
  decision.unit.name
153
179
  } has 2x higher priority.`,
154
180
  );
155
- actionsApi.unqueueFromProduction(queueData.type, current.name, current.type, 1);
181
+ actionsApi.unqueueFromProduction(queueData.type, currentProduction.name, currentProduction.type, 1);
156
182
  }
157
183
  } else {
158
184
  // Not changing our mind, but maybe other queues are more important for now.
@@ -181,32 +207,29 @@ export class QueueController {
181
207
  }
182
208
  }
183
209
 
184
- private getBestOptionForBuilding(
210
+ private getPrioritiesForBuildingOptions(
185
211
  game: GameApi,
186
212
  options: TechnoRules[],
187
213
  threatCache: GlobalThreat | null,
188
214
  playerData: PlayerData,
189
- logger: (message: string) => void,
190
- ): TechnoRulesWithPriority | undefined {
215
+ unitTypeRequests: Map<string, number>,
216
+ logger: DebugLogger,
217
+ ): TechnoRulesWithPriority[] {
191
218
  let priorityQueue: TechnoRulesWithPriority[] = [];
192
219
  options.forEach((option) => {
193
- let priority = this.getPriorityForBuildingOption(option, game, playerData, threatCache);
194
- if (priority > 0) {
195
- 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 });
196
228
  }
197
229
  });
198
230
 
199
- priorityQueue = priorityQueue.sort((a, b) => {
200
- return a.priority - b.priority;
201
- });
202
- if (priorityQueue.length > 0) {
203
- if (DEBUG_BUILD_QUEUES && game.getCurrentTick() % 100 === 0) {
204
- let queueString = priorityQueue.map((item) => item.unit.name + "(" + item.priority + ")").join(", ");
205
- logger(`Build priority currently: ${queueString}`);
206
- }
207
- }
208
-
209
- return priorityQueue.pop();
231
+ priorityQueue = priorityQueue.sort((a, b) => a.priority - b.priority);
232
+ return priorityQueue;
210
233
  }
211
234
 
212
235
  private getPriorityForBuildingOption(
@@ -239,4 +262,36 @@ export class QueueController {
239
262
  return getDefaultPlacementLocation(game, playerData, playerData.startLocation, objectReady);
240
263
  }
241
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
+ }
242
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);
@@ -17,6 +30,21 @@ export function pad(n: any, format = "0000") {
17
30
  }
18
31
 
19
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
+ }
20
48
 
21
49
  export function maxBy<T>(array: T[], predicate: (arg: T) => number | null): T | null {
22
50
  if (array.length === 0) {
@@ -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
+ };
@@ -1,15 +1,66 @@
1
1
  // Used to group related actions together to minimise actionApi calls. For example, if multiple units
2
2
 
3
3
  import { ActionsApi, OrderType, Vector2 } from "@chronodivide/game-api";
4
- import { groupBy } from "../../common/utils.js";
4
+ import { groupBy } from "../common/utils.js";
5
5
 
6
6
  // are ordered to move to the same location, all of them will be ordered to move in a single action.
7
- export type BatchableAction = {
8
- unitId: number;
9
- orderType: OrderType;
10
- point?: Vector2;
11
- targetId?: number;
12
- };
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
+ }
13
64
 
14
65
  export class ActionBatcher {
15
66
  private actions: BatchableAction[];
@@ -60,6 +111,14 @@ export class ActionBatcher {
60
111
  vector.y,
61
112
  );
62
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
+ }
63
122
  });
64
123
  }
65
124
  }