@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
@@ -1,51 +1,233 @@
1
- // Meta-controller for forming and controlling squads.
1
+ // Meta-controller for forming and controlling missions.
2
+ // Missions are groups of zero or more units that aim to accomplish a particular goal.
3
+ import { Vector2 } from "@chronodivide/game-api";
4
+ import { isDisbandMission, isGrabCombatants, isReleaseUnits, isRequestSpecificUnits, isRequestUnits, } from "./mission.js";
2
5
  import { createMissionFactories } from "./missionFactories.js";
6
+ import { ActionBatcher } from "./actionBatcher.js";
7
+ import { countBy, isSelectableCombatant } from "../common/utils.js";
8
+ // `missingUnitTypes` priority decays by this much every update loop.
9
+ const MISSING_UNIT_TYPE_REQUEST_DECAY_MULT_RATE = 0.75;
10
+ const MISSING_UNIT_TYPE_REQUEST_DECAY_FLAT_RATE = 1;
3
11
  export class MissionController {
4
12
  constructor(logger) {
5
13
  this.logger = logger;
6
14
  this.missions = [];
15
+ // A mapping of unit IDs to the missions they are assigned to. This may contain units that are dead, but
16
+ // is periodically cleaned in the update loop.
17
+ this.unitIdToMission = new Map();
18
+ // A mapping of unit types to the highest priority requested for a mission.
19
+ // This decays over time if requests are not 'refreshed' by mission.
20
+ this.requestedUnitTypes = new Map();
21
+ // Tracks missions to be externally disbanded the next time the mission update loop occurs.
7
22
  this.forceDisbandedMissions = [];
8
23
  this.missionFactories = createMissionFactories();
9
24
  }
10
- onAiUpdate(gameApi, playerData, matchAwareness, squadController) {
25
+ updateUnitIds(gameApi) {
26
+ // Check for units in multiple missions, this shouldn't happen.
27
+ this.unitIdToMission = new Map();
28
+ this.missions.forEach((mission) => {
29
+ const toRemove = [];
30
+ mission.getUnitIds().forEach((unitId) => {
31
+ if (this.unitIdToMission.has(unitId)) {
32
+ this.logger(`WARNING: unit ${unitId} is in multiple missions, please debug.`);
33
+ }
34
+ else if (!gameApi.getGameObjectData(unitId)) {
35
+ // say, if a unit was killed
36
+ toRemove.push(unitId);
37
+ }
38
+ else {
39
+ this.unitIdToMission.set(unitId, mission);
40
+ }
41
+ });
42
+ toRemove.forEach((unitId) => mission.removeUnit(unitId));
43
+ });
44
+ }
45
+ onAiUpdate(gameApi, actionsApi, playerData, matchAwareness) {
11
46
  // Remove inactive missions.
12
47
  this.missions = this.missions.filter((missions) => missions.isActive());
48
+ this.updateUnitIds(gameApi);
49
+ // Batch actions to reduce spamming of actions for larger armies.
50
+ const actionBatcher = new ActionBatcher();
13
51
  // Poll missions for requested actions.
14
52
  const missionActions = this.missions.map((mission) => ({
15
53
  mission,
16
- action: mission.onAiUpdate(gameApi, playerData, matchAwareness),
54
+ action: mission.onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, actionBatcher),
17
55
  }));
18
56
  // Handle disbands and merges.
19
- const isDisband = (a) => a.type == "disband";
20
57
  const disbandedMissions = new Map();
21
58
  const disbandedMissionsArray = [];
22
59
  this.forceDisbandedMissions.forEach((name) => disbandedMissions.set(name, null));
23
60
  this.forceDisbandedMissions = [];
24
- missionActions
25
- .filter((a) => isDisband(a.action))
26
- .forEach((a) => {
61
+ missionActions.filter(isDisbandMission).forEach((a) => {
62
+ this.logger(`Mission ${a.mission.getUniqueName()} disbanding as requested.`);
63
+ a.mission.getUnitIds().forEach((unitId) => {
64
+ this.unitIdToMission.delete(unitId);
65
+ actionsApi.setUnitDebugText(unitId, undefined);
66
+ });
27
67
  disbandedMissions.set(a.mission.getUniqueName(), a.action.reason);
28
68
  });
29
- // Remove disbanded and merged squads.
69
+ // Handle unit requests.
70
+ // Release units
71
+ missionActions.filter(isReleaseUnits).forEach((a) => {
72
+ a.action.unitIds.forEach((unitId) => {
73
+ if (this.unitIdToMission.get(unitId)?.getUniqueName() === a.mission.getUniqueName()) {
74
+ this.removeUnitFromMission(a.mission, unitId, actionsApi);
75
+ }
76
+ });
77
+ });
78
+ // Request specific units by ID
79
+ const unitIdToHighestRequest = missionActions.filter(isRequestSpecificUnits).reduce((prev, missionWithAction) => {
80
+ const { unitIds } = missionWithAction.action;
81
+ unitIds.forEach((unitId) => {
82
+ if (prev.hasOwnProperty(unitId)) {
83
+ if (prev[unitId].action.priority > prev[unitId].action.priority) {
84
+ prev[unitId] = missionWithAction;
85
+ }
86
+ }
87
+ else {
88
+ prev[unitId] = missionWithAction;
89
+ }
90
+ });
91
+ return prev;
92
+ }, {});
93
+ // Map of Mission ID to Unit Type to Count.
94
+ const newMissionAssignments = Object.entries(unitIdToHighestRequest)
95
+ .flatMap(([id, request]) => {
96
+ const unitId = Number.parseInt(id);
97
+ const unit = gameApi.getGameObjectData(unitId);
98
+ const { mission: requestingMission } = request;
99
+ const missionName = requestingMission.getUniqueName();
100
+ if (!unit) {
101
+ this.logger(`mission ${missionName} requested non-existent unit ${unitId}`);
102
+ return [];
103
+ }
104
+ if (!this.unitIdToMission.has(unitId)) {
105
+ this.addUnitToMission(requestingMission, unit, actionsApi);
106
+ return [{ unitName: unit?.name, mission: requestingMission.getUniqueName() }];
107
+ }
108
+ return [];
109
+ })
110
+ .reduce((acc, curr) => {
111
+ if (!acc[curr.mission]) {
112
+ acc[curr.mission] = {};
113
+ }
114
+ if (!acc[curr.mission][curr.unitName]) {
115
+ acc[curr.mission][curr.unitName] = 0;
116
+ }
117
+ acc[curr.mission][curr.unitName] = acc[curr.mission][curr.unitName] + 1;
118
+ return acc;
119
+ }, {});
120
+ Object.entries(newMissionAssignments).forEach(([mission, assignments]) => {
121
+ this.logger(`Mission ${mission} received: ${Object.entries(assignments)
122
+ .map(([unitType, count]) => unitType + " x " + count)
123
+ .join(", ")}`);
124
+ });
125
+ // Request units by type - store the highest priority mission for each unit type.
126
+ const unitTypeToHighestRequest = missionActions.filter(isRequestUnits).reduce((prev, missionWithAction) => {
127
+ const { unitNames } = missionWithAction.action;
128
+ unitNames.forEach((unitName) => {
129
+ if (prev.hasOwnProperty(unitName)) {
130
+ if (prev[unitName].action.priority > prev[unitName].action.priority) {
131
+ prev[unitName] = missionWithAction;
132
+ }
133
+ }
134
+ else {
135
+ prev[unitName] = missionWithAction;
136
+ }
137
+ });
138
+ return prev;
139
+ }, {});
140
+ // Request combat-capable units in an area
141
+ const grabRequests = missionActions.filter(isGrabCombatants);
142
+ // Find un-assigned units and distribute them among all the requesting missions.
143
+ const unitIds = gameApi.getVisibleUnits(playerData.name, "self");
144
+ // List of units that are unassigned or not in a locked mission.
145
+ const freeUnits = unitIds
146
+ .map((unitId) => gameApi.getGameObjectData(unitId))
147
+ .filter((unit) => !!unit)
148
+ .map((unit) => ({
149
+ unit,
150
+ mission: this.unitIdToMission.get(unit.id),
151
+ }))
152
+ .filter((unitWithMission) => !unitWithMission.mission || unitWithMission.mission.isUnitsLocked() === false);
153
+ // Sort free units so that unassigned units get chosen before assigned (but unlocked) units.
154
+ freeUnits.sort((u1, u2) => (u1.mission?.getPriority() ?? 0) - (u2.mission?.getPriority() ?? 0));
155
+ const newAssignmentsByType = freeUnits
156
+ .flatMap(({ unit: freeUnit, mission: donatingMission }) => {
157
+ if (unitTypeToHighestRequest.hasOwnProperty(freeUnit.name)) {
158
+ const { mission: requestingMission } = unitTypeToHighestRequest[freeUnit.name];
159
+ if (donatingMission) {
160
+ if (donatingMission === requestingMission ||
161
+ donatingMission.getPriority() > requestingMission.getPriority()) {
162
+ return [];
163
+ }
164
+ this.removeUnitFromMission(donatingMission, freeUnit.id, actionsApi);
165
+ }
166
+ this.logger(`granting unit ${freeUnit.id}#${freeUnit.name} to mission ${requestingMission.getUniqueName()}`);
167
+ this.addUnitToMission(requestingMission, freeUnit, actionsApi);
168
+ delete unitTypeToHighestRequest[freeUnit.name];
169
+ return [
170
+ { unitName: freeUnit.name, missionName: requestingMission.getUniqueName(), method: "type" },
171
+ ];
172
+ }
173
+ else if (grabRequests.length > 0) {
174
+ const grantedMission = grabRequests.find((request) => {
175
+ const canGrabUnit = isSelectableCombatant(freeUnit);
176
+ return (canGrabUnit &&
177
+ request.action.point.distanceTo(new Vector2(freeUnit.tile.rx, freeUnit.tile.ry)) <=
178
+ request.action.radius);
179
+ });
180
+ if (grantedMission) {
181
+ if (donatingMission) {
182
+ if (donatingMission === grantedMission.mission ||
183
+ donatingMission.getPriority() > grantedMission.mission.getPriority()) {
184
+ return [];
185
+ }
186
+ this.removeUnitFromMission(donatingMission, freeUnit.id, actionsApi);
187
+ }
188
+ this.addUnitToMission(grantedMission.mission, freeUnit, actionsApi);
189
+ return [
190
+ {
191
+ unitName: freeUnit.name,
192
+ missionName: grantedMission.mission.getUniqueName(),
193
+ method: "grab",
194
+ },
195
+ ];
196
+ }
197
+ }
198
+ return [];
199
+ })
200
+ .reduce((acc, curr) => {
201
+ if (!acc[curr.missionName]) {
202
+ acc[curr.missionName] = {};
203
+ }
204
+ if (!acc[curr.missionName][curr.unitName]) {
205
+ acc[curr.missionName][curr.unitName] = { grab: 0, type: 0 };
206
+ }
207
+ acc[curr.missionName][curr.unitName][curr.method] =
208
+ acc[curr.missionName][curr.unitName][curr.method] + 1;
209
+ return acc;
210
+ }, {});
211
+ Object.entries(newAssignmentsByType).forEach(([mission, assignments]) => {
212
+ this.logger(`Mission ${mission} received: ${Object.entries(assignments)
213
+ .flatMap(([unitType, methodToCount]) => Object.entries(methodToCount)
214
+ .filter(([, count]) => count > 0)
215
+ .map(([method, count]) => unitType + " x " + count + " (by " + method + ")"))
216
+ .join(", ")}`);
217
+ });
218
+ this.updateRequestedUnitTypes(unitTypeToHighestRequest);
219
+ // Send all actions that can be batched together.
220
+ actionBatcher.resolve(actionsApi);
221
+ // Remove disbanded and merged missions.
30
222
  this.missions
31
223
  .filter((missions) => disbandedMissions.has(missions.getUniqueName()))
32
224
  .forEach((disbandedMission) => {
33
225
  const reason = disbandedMissions.get(disbandedMission.getUniqueName());
34
- this.logger(`mission disbanded: ${disbandedMission.getUniqueName()}, reason: ${reason}, hasSquad: ${!!disbandedMission.getSquad}`);
226
+ this.logger(`mission disbanded: ${disbandedMission.getUniqueName()}, reason: ${reason}`);
35
227
  disbandedMissionsArray.push({ mission: disbandedMission, reason });
36
228
  disbandedMission.endMission(disbandedMissions.get(disbandedMission.getUniqueName()));
37
- disbandedMission.getSquad()?.setMission(null);
38
229
  });
39
230
  this.missions = this.missions.filter((missions) => !disbandedMissions.has(missions.getUniqueName()));
40
- // Register new squads
41
- const isNewSquad = (a) => a.type == "registerSquad";
42
- missionActions
43
- .filter((a) => isNewSquad(a.action))
44
- .forEach((a) => {
45
- const action = a.action;
46
- squadController.registerSquad(action.squad);
47
- this.logger(`registered a squad: ${action.squad.getName()}`);
48
- });
49
231
  // Create dynamic missions.
50
232
  this.missionFactories.forEach((missionFactory) => {
51
233
  missionFactory.maybeCreateMissions(gameApi, playerData, matchAwareness, this, this.logger);
@@ -54,6 +236,43 @@ export class MissionController {
54
236
  });
55
237
  });
56
238
  }
239
+ updateRequestedUnitTypes(missingUnitTypeToHighestRequest) {
240
+ // Decay the priority over time.
241
+ const currentUnitTypes = Array.from(this.requestedUnitTypes.keys());
242
+ for (const unitType of currentUnitTypes) {
243
+ const newPriority = this.requestedUnitTypes.get(unitType) * MISSING_UNIT_TYPE_REQUEST_DECAY_MULT_RATE -
244
+ MISSING_UNIT_TYPE_REQUEST_DECAY_FLAT_RATE;
245
+ if (newPriority > 0.5) {
246
+ this.requestedUnitTypes.set(unitType, newPriority);
247
+ }
248
+ else {
249
+ this.requestedUnitTypes.delete(unitType);
250
+ }
251
+ }
252
+ // Add the new missing units to the priority set, if the request is higher than the existing value.
253
+ Object.entries(missingUnitTypeToHighestRequest).forEach(([unitType, request]) => {
254
+ const currentPriority = this.requestedUnitTypes.get(unitType);
255
+ this.requestedUnitTypes.set(unitType, currentPriority ? Math.max(currentPriority, request.action.priority) : request.action.priority);
256
+ });
257
+ }
258
+ /**
259
+ * Returns the set of units that have been requested for production by the missions.
260
+ *
261
+ * @returns A map of unit type to the highest priority for that unit type.
262
+ */
263
+ getRequestedUnitTypes() {
264
+ return this.requestedUnitTypes;
265
+ }
266
+ addUnitToMission(mission, unit, actionsApi) {
267
+ mission.addUnit(unit.id);
268
+ this.unitIdToMission.set(unit.id, mission);
269
+ actionsApi.setUnitDebugText(unit.id, mission.getUniqueName() + "_" + unit.id);
270
+ }
271
+ removeUnitFromMission(mission, unitId, actionsApi) {
272
+ mission.removeUnit(unitId);
273
+ this.unitIdToMission.delete(unitId);
274
+ actionsApi.setUnitDebugText(unitId, undefined);
275
+ }
57
276
  /**
58
277
  * Attempts to add a mission to the active set.
59
278
  * @param mission
@@ -74,8 +293,30 @@ export class MissionController {
74
293
  disbandMission(missionName) {
75
294
  this.forceDisbandedMissions.push(missionName);
76
295
  }
77
- logDebugOutput() {
78
- this.logger(`Missions (${this.missions.length}): ${this.missions.map((m) => m.getUniqueName()).join(", ")}`);
296
+ // return text to display for global debug
297
+ getGlobalDebugText(gameApi) {
298
+ const unitsInMission = (unitIds) => countBy(unitIds, (unitId) => gameApi.getGameObjectData(unitId)?.name);
299
+ let globalDebugText = "";
300
+ this.missions.forEach((mission) => {
301
+ this.logger(`Mission ${mission.getUniqueName()}: ${Object.entries(unitsInMission(mission.getUnitIds()))
302
+ .map(([unitName, count]) => `${unitName} x ${count}`)
303
+ .join(", ")}`);
304
+ const missionDebugText = mission.getGlobalDebugText();
305
+ if (missionDebugText) {
306
+ globalDebugText += mission.getUniqueName() + ": " + missionDebugText + "\n";
307
+ }
308
+ });
309
+ return globalDebugText;
310
+ }
311
+ updateDebugText(actionsApi) {
312
+ this.missions.forEach((mission) => {
313
+ mission
314
+ .getUnitIds()
315
+ .forEach((unitId) => actionsApi.setUnitDebugText(unitId, `${unitId}: ${mission.getUniqueName()}`));
316
+ });
317
+ }
318
+ getMissions() {
319
+ return this.missions;
79
320
  }
80
321
  }
81
322
  //# sourceMappingURL=missionController.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"missionController.js","sourceRoot":"","sources":["../../../../src/bot/logic/mission/missionController.ts"],"names":[],"mappings":"AAAA,sDAAsD;AAMtD,OAAO,EAAkB,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/E,MAAM,OAAO,iBAAiB;IAM1B,YAAoB,MAAsD;QAAtD,WAAM,GAAN,MAAM,CAAgD;QAJlE,aAAQ,GAAmB,EAAE,CAAC;QAE9B,2BAAsB,GAAa,EAAE,CAAC;QAG1C,IAAI,CAAC,gBAAgB,GAAG,sBAAsB,EAAE,CAAC;IACrD,CAAC;IAEM,UAAU,CACb,OAAgB,EAChB,UAAsB,EACtB,cAA8B,EAC9B,eAAgC;QAEhC,4BAA4B;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAExE,uCAAuC;QACvC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACnD,OAAO;YACP,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,CAAC;SAClE,CAAC,CAAC,CAAC;QAEJ,8BAA8B;QAC9B,MAAM,SAAS,GAAG,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,CAAC;QAC5D,MAAM,iBAAiB,GAAqB,IAAI,GAAG,EAAE,CAAC;QACtD,MAAM,sBAAsB,GAAwC,EAAE,CAAC;QACvE,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QACjF,IAAI,CAAC,sBAAsB,GAAG,EAAE,CAAC;QACjC,cAAc;aACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;aAClC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACX,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAG,CAAC,CAAC,MAA+B,CAAC,MAAM,CAAC,CAAC;QAChG,CAAC,CAAC,CAAC;QAEP,sCAAsC;QACtC,IAAI,CAAC,QAAQ;aACR,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;aACrE,OAAO,CAAC,CAAC,gBAAgB,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC;YACvE,IAAI,CAAC,MAAM,CACP,sBAAsB,gBAAgB,CAAC,aAAa,EAAE,aAAa,MAAM,eAAe,CAAC,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CACxH,CAAC;YACF,sBAAsB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;YACnE,gBAAgB,CAAC,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;YACrF,gBAAgB,CAAC,QAAQ,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QACP,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QAErG,sBAAsB;QACtB,MAAM,UAAU,GAAG,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC;QACnE,cAAc;aACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;aACnC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACX,MAAM,MAAM,GAAG,CAAC,CAAC,MAAoC,CAAC;YACtD,eAAe,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,uBAAuB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEP,2BAA2B;QAC3B,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;YAC7C,cAAc,CAAC,mBAAmB,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3F,sBAAsB,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;gBACnD,cAAc,CAAC,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5G,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAI,OAAyB;QAC1C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE;YAC1E,kCAAkC;YAClC,OAAO,IAAI,CAAC;SACf;QACD,IAAI,CAAC,MAAM,CAAC,kBAAkB,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;OAEG;IACI,cAAc,CAAC,WAAmB;QACrC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;IAEM,cAAc;QACjB,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,QAAQ,CAAC,MAAM,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjH,CAAC;CACJ"}
1
+ {"version":3,"file":"missionController.js","sourceRoot":"","sources":["../../../../src/bot/logic/mission/missionController.ts"],"names":[],"mappings":"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"}
@@ -1,47 +1,128 @@
1
- import { ObjectType, Vector2 } from "@chronodivide/game-api";
2
- import { CombatSquad } from "../../squad/behaviours/combatSquad.js";
3
- import { Mission, disbandMission, noop } from "../mission.js";
4
- import { Squad } from "../../squad/squad.js";
1
+ import { ObjectType, SideType, Vector2 } from "@chronodivide/game-api";
2
+ import { CombatSquad } from "./squads/combatSquad.js";
3
+ import { Mission, disbandMission, noop, requestUnits } from "../mission.js";
5
4
  import { RetreatMission } from "./retreatMission.js";
6
- import { maxBy } from "../../common/utils.js";
5
+ import { countBy, isOwnedByNeutral, maxBy } from "../../common/utils.js";
6
+ import { getSovietComposition } from "../../composition/sovietCompositions.js";
7
+ import { getAlliedCompositions } from "../../composition/alliedCompositions.js";
8
+ import { manageMoveMicro } from "./squads/common.js";
7
9
  export var AttackFailReason;
8
10
  (function (AttackFailReason) {
9
11
  AttackFailReason[AttackFailReason["NoTargets"] = 0] = "NoTargets";
10
12
  AttackFailReason[AttackFailReason["DefenceTooStrong"] = 1] = "DefenceTooStrong";
11
13
  })(AttackFailReason || (AttackFailReason = {}));
12
- const NO_TARGET_IDLE_TIMEOUT_TICKS = 60;
14
+ var AttackMissionState;
15
+ (function (AttackMissionState) {
16
+ AttackMissionState[AttackMissionState["Preparing"] = 0] = "Preparing";
17
+ AttackMissionState[AttackMissionState["Attacking"] = 1] = "Attacking";
18
+ AttackMissionState[AttackMissionState["Retreating"] = 2] = "Retreating";
19
+ })(AttackMissionState || (AttackMissionState = {}));
20
+ const NO_TARGET_RETARGET_TICKS = 450;
21
+ const NO_TARGET_IDLE_TIMEOUT_TICKS = 900;
22
+ function calculateTargetComposition(gameApi, playerData, matchAwareness) {
23
+ if (!playerData.country) {
24
+ throw new Error(`player ${playerData.name} has no country`);
25
+ }
26
+ else if (playerData.country.side === SideType.Nod) {
27
+ return getSovietComposition(gameApi, playerData, matchAwareness);
28
+ }
29
+ else {
30
+ return getAlliedCompositions(gameApi, playerData, matchAwareness);
31
+ }
32
+ }
33
+ const ATTACK_MISSION_PRIORITY_RAMP = 1.01;
34
+ const ATTACK_MISSION_MAX_PRIORITY = 50;
13
35
  /**
14
36
  * A mission that tries to attack a certain area.
15
37
  */
16
38
  export class AttackMission extends Mission {
17
- constructor(uniqueName, priority, rallyArea, attackArea, radius, logger) {
18
- super(uniqueName, priority, logger);
19
- this.rallyArea = rallyArea;
39
+ constructor(uniqueName, priority, rallyArea, attackArea, radius, composition, logger) {
40
+ super(uniqueName, logger);
41
+ this.priority = priority;
20
42
  this.attackArea = attackArea;
21
43
  this.radius = radius;
44
+ this.composition = composition;
22
45
  this.lastTargetSeenAt = 0;
46
+ this.hasPickedNewTarget = false;
47
+ this.state = AttackMissionState.Preparing;
48
+ this.squad = new CombatSquad(rallyArea, attackArea, radius);
49
+ }
50
+ _onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, actionBatcher) {
51
+ switch (this.state) {
52
+ case AttackMissionState.Preparing:
53
+ return this.handlePreparingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
54
+ case AttackMissionState.Attacking:
55
+ return this.handleAttackingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
56
+ case AttackMissionState.Retreating:
57
+ return this.handleRetreatingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
58
+ }
23
59
  }
24
- onAiUpdate(gameApi, playerData, matchAwareness) {
25
- if (this.getSquad() === null) {
26
- return this.setSquad(new Squad(this.getUniqueName(), new CombatSquad(this.rallyArea, this.attackArea, this.radius), this));
60
+ handlePreparingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher) {
61
+ const currentComposition = countBy(this.getUnits(gameApi), (unit) => unit.name);
62
+ const missingUnits = Object.entries(this.composition).filter(([unitType, targetAmount]) => {
63
+ return !currentComposition[unitType] || currentComposition[unitType] < targetAmount;
64
+ });
65
+ if (missingUnits.length > 0) {
66
+ this.priority = Math.min(this.priority * ATTACK_MISSION_PRIORITY_RAMP, ATTACK_MISSION_MAX_PRIORITY);
67
+ return requestUnits(missingUnits.map(([unitName]) => unitName), this.priority);
27
68
  }
28
69
  else {
29
- // Dispatch missions.
30
- if (!matchAwareness.shouldAttack()) {
31
- return disbandMission(AttackFailReason.DefenceTooStrong);
32
- }
33
- const foundTargets = matchAwareness.getHostilesNearPoint2d(this.attackArea, this.radius);
34
- if (foundTargets.length > 0) {
35
- this.lastTargetSeenAt = gameApi.getCurrentTick();
36
- }
37
- else if (gameApi.getCurrentTick() > this.lastTargetSeenAt + NO_TARGET_IDLE_TIMEOUT_TICKS) {
38
- return disbandMission(AttackFailReason.NoTargets);
70
+ this.priority = ATTACK_MISSION_INITIAL_PRIORITY;
71
+ this.state = AttackMissionState.Attacking;
72
+ return noop();
73
+ }
74
+ }
75
+ handleAttackingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher) {
76
+ if (this.getUnitIds().length === 0) {
77
+ // TODO: disband directly (we no longer retreat when losing)
78
+ this.state = AttackMissionState.Retreating;
79
+ return noop();
80
+ }
81
+ const foundTargets = matchAwareness
82
+ .getHostilesNearPoint2d(this.attackArea, this.radius)
83
+ .map((unit) => gameApi.getUnitData(unit.unitId))
84
+ .filter((unit) => !isOwnedByNeutral(unit));
85
+ const update = this.squad.onAiUpdate(gameApi, actionsApi, actionBatcher, playerData, this, matchAwareness, this.logger);
86
+ if (update.type !== "noop") {
87
+ return update;
88
+ }
89
+ if (foundTargets.length > 0) {
90
+ this.lastTargetSeenAt = gameApi.getCurrentTick();
91
+ this.hasPickedNewTarget = false;
92
+ }
93
+ else if (gameApi.getCurrentTick() > this.lastTargetSeenAt + NO_TARGET_IDLE_TIMEOUT_TICKS) {
94
+ return disbandMission(AttackFailReason.NoTargets);
95
+ }
96
+ else if (!this.hasPickedNewTarget &&
97
+ gameApi.getCurrentTick() > this.lastTargetSeenAt + NO_TARGET_RETARGET_TICKS) {
98
+ const newTarget = generateTarget(gameApi, playerData, matchAwareness);
99
+ if (newTarget) {
100
+ this.squad.setAttackArea(newTarget);
101
+ this.hasPickedNewTarget = true;
39
102
  }
40
103
  }
41
104
  return noop();
42
105
  }
106
+ handleRetreatingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher) {
107
+ this.getUnits(gameApi).forEach((unitId) => {
108
+ actionBatcher.push(manageMoveMicro(unitId, matchAwareness.getMainRallyPoint()));
109
+ });
110
+ return disbandMission();
111
+ }
112
+ getGlobalDebugText() {
113
+ return this.squad.getGlobalDebugText() ?? "<none>";
114
+ }
115
+ getState() {
116
+ return this.state;
117
+ }
118
+ // This mission can give up its units while preparing.
119
+ isUnitsLocked() {
120
+ return this.state !== AttackMissionState.Preparing;
121
+ }
122
+ getPriority() {
123
+ return this.priority;
124
+ }
43
125
  }
44
- const ATTACK_COOLDOWN_TICKS = 120;
45
126
  // Calculates the weight for initiating an attack on the position of a unit or building.
46
127
  // This is separate from unit micro; the squad will be ordered to attack in the vicinity of the point.
47
128
  const getTargetWeight = (unitData, tryFocusHarvester) => {
@@ -55,49 +136,75 @@ const getTargetWeight = (unitData, tryFocusHarvester) => {
55
136
  return unitData.maxHitPoints;
56
137
  }
57
138
  };
139
+ function generateTarget(gameApi, playerData, matchAwareness, includeBaseLocations = false) {
140
+ // Randomly decide between harvester and base.
141
+ try {
142
+ const tryFocusHarvester = gameApi.generateRandomInt(0, 1) === 0;
143
+ const enemyUnits = gameApi
144
+ .getVisibleUnits(playerData.name, "enemy")
145
+ .map((unitId) => gameApi.getUnitData(unitId))
146
+ .filter((u) => !!u && gameApi.getPlayerData(u.owner).isCombatant);
147
+ const maxUnit = maxBy(enemyUnits, (u) => getTargetWeight(u, tryFocusHarvester));
148
+ if (maxUnit) {
149
+ return new Vector2(maxUnit.tile.rx, maxUnit.tile.ry);
150
+ }
151
+ if (includeBaseLocations) {
152
+ const mapApi = gameApi.mapApi;
153
+ const enemyPlayers = gameApi
154
+ .getPlayers()
155
+ .map(gameApi.getPlayerData)
156
+ .filter((otherPlayer) => !gameApi.areAlliedPlayers(playerData.name, otherPlayer.name));
157
+ const unexploredEnemyLocations = enemyPlayers.filter((otherPlayer) => {
158
+ const tile = mapApi.getTile(otherPlayer.startLocation.x, otherPlayer.startLocation.y);
159
+ if (!tile) {
160
+ return false;
161
+ }
162
+ return !mapApi.isVisibleTile(tile, playerData.name);
163
+ });
164
+ if (unexploredEnemyLocations.length > 0) {
165
+ const idx = gameApi.generateRandomInt(0, unexploredEnemyLocations.length - 1);
166
+ return unexploredEnemyLocations[idx].startLocation;
167
+ }
168
+ }
169
+ }
170
+ catch (err) {
171
+ // There's a crash here when accessing a building that got destroyed. Will catch and ignore or now.
172
+ return null;
173
+ }
174
+ return null;
175
+ }
176
+ // Number of ticks between attacking visible targets.
177
+ const VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS = 120;
178
+ // Number of ticks between attacking "bases" (enemy starting locations).
179
+ const BASE_ATTACK_COOLDOWN_TICKS = 1800;
180
+ const ATTACK_MISSION_INITIAL_PRIORITY = 1;
58
181
  export class AttackMissionFactory {
59
- constructor(lastAttackAt = -ATTACK_COOLDOWN_TICKS) {
182
+ constructor(lastAttackAt = -VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS) {
60
183
  this.lastAttackAt = lastAttackAt;
61
184
  }
62
185
  getName() {
63
186
  return "AttackMissionFactory";
64
187
  }
65
- generateTarget(gameApi, playerData, matchAwareness) {
66
- // Randomly decide between harvester and base.
67
- try {
68
- const tryFocusHarvester = gameApi.generateRandomInt(0, 1) === 0;
69
- const enemyUnits = gameApi
70
- .getVisibleUnits(playerData.name, "hostile")
71
- .map((unitId) => gameApi.getUnitData(unitId))
72
- .filter((u) => !!u && gameApi.getPlayerData(u.owner).isCombatant);
73
- const maxUnit = maxBy(enemyUnits, (u) => getTargetWeight(u, tryFocusHarvester));
74
- if (maxUnit) {
75
- return new Vector2(maxUnit.tile.rx, maxUnit.tile.ry);
76
- }
77
- }
78
- catch (err) {
79
- // There's a crash here when accessing a building that got destroyed. Will catch and ignore or now.
80
- return null;
81
- }
82
- return null;
83
- }
84
188
  maybeCreateMissions(gameApi, playerData, matchAwareness, missionController, logger) {
85
- if (!matchAwareness.shouldAttack()) {
189
+ if (gameApi.getCurrentTick() < this.lastAttackAt + VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS) {
86
190
  return;
87
191
  }
88
- if (gameApi.getCurrentTick() < this.lastAttackAt + ATTACK_COOLDOWN_TICKS) {
192
+ // can only have one attack 'preparing' at once.
193
+ if (missionController
194
+ .getMissions()
195
+ .some((mission) => mission instanceof AttackMission && mission.getState() === AttackMissionState.Preparing)) {
89
196
  return;
90
197
  }
91
- const attackRadius = 15;
92
- const attackArea = this.generateTarget(gameApi, playerData, matchAwareness);
198
+ const attackRadius = 10;
199
+ const includeEnemyBases = gameApi.getCurrentTick() > this.lastAttackAt + BASE_ATTACK_COOLDOWN_TICKS;
200
+ const attackArea = generateTarget(gameApi, playerData, matchAwareness, includeEnemyBases);
93
201
  if (!attackArea) {
94
- // Nothing to attack.
95
202
  return;
96
203
  }
97
- // TODO: not using a fixed value here. But performance slows to a crawl when this is unique.
98
- const squadName = "globalAttack";
99
- const tryAttack = missionController.addMission(new AttackMission(squadName, 100, matchAwareness.getMainRallyPoint(), attackArea, attackRadius, logger).then((reason, squad) => {
100
- missionController.addMission(new RetreatMission("retreat-from-" + squadName + gameApi.getCurrentTick(), 100, matchAwareness.getMainRallyPoint(), squad?.getUnitIds() ?? [], logger));
204
+ const squadName = "attack_" + gameApi.getCurrentTick();
205
+ const composition = calculateTargetComposition(gameApi, playerData, matchAwareness);
206
+ const tryAttack = missionController.addMission(new AttackMission(squadName, ATTACK_MISSION_INITIAL_PRIORITY, matchAwareness.getMainRallyPoint(), attackArea, attackRadius, composition, logger).then((unitIds, reason) => {
207
+ missionController.addMission(new RetreatMission("retreat-from-" + squadName + gameApi.getCurrentTick(), matchAwareness.getMainRallyPoint(), unitIds, logger));
101
208
  }));
102
209
  if (tryAttack) {
103
210
  this.lastAttackAt = gameApi.getCurrentTick();