@supalosa/chronodivide-bot 0.4.0 → 0.5.2

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 (123) hide show
  1. package/.env.template +5 -0
  2. package/README.md +54 -47
  3. package/dist/bot/bot.js +14 -35
  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/building/ArtilleryUnit.js +2 -29
  8. package/dist/bot/logic/building/antiAirStaticDefence.js +43 -0
  9. package/dist/bot/logic/building/antiAirStaticDefence.js.map +1 -0
  10. package/dist/bot/logic/building/antiGroundStaticDefence.js +10 -20
  11. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  12. package/dist/bot/logic/building/artilleryUnit.js.map +1 -1
  13. package/dist/bot/logic/building/basicAirUnit.js +2 -23
  14. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  15. package/dist/bot/logic/building/basicBuilding.js +3 -2
  16. package/dist/bot/logic/building/basicBuilding.js.map +1 -1
  17. package/dist/bot/logic/building/basicGroundUnit.js +2 -43
  18. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  19. package/dist/bot/logic/building/buildingRules.js +15 -9
  20. package/dist/bot/logic/building/buildingRules.js.map +1 -1
  21. package/dist/bot/logic/building/common.js +19 -0
  22. package/dist/bot/logic/building/common.js.map +1 -0
  23. package/dist/bot/logic/building/harvester.js +2 -1
  24. package/dist/bot/logic/building/harvester.js.map +1 -1
  25. package/dist/bot/logic/building/queueController.js +69 -42
  26. package/dist/bot/logic/building/queueController.js.map +1 -1
  27. package/dist/bot/logic/common/utils.js +21 -0
  28. package/dist/bot/logic/common/utils.js.map +1 -1
  29. package/dist/bot/logic/composition/alliedCompositions.js +13 -0
  30. package/dist/bot/logic/composition/alliedCompositions.js.map +1 -0
  31. package/dist/bot/logic/composition/common.js +2 -0
  32. package/dist/bot/logic/composition/common.js.map +1 -0
  33. package/dist/bot/logic/composition/sovietCompositions.js +13 -0
  34. package/dist/bot/logic/composition/sovietCompositions.js.map +1 -0
  35. package/dist/bot/logic/mission/actionBatcher.js +92 -0
  36. package/dist/bot/logic/mission/actionBatcher.js.map +1 -0
  37. package/dist/bot/logic/mission/behaviours/combatSquad.js +124 -0
  38. package/dist/bot/logic/mission/behaviours/combatSquad.js.map +1 -0
  39. package/dist/bot/logic/mission/behaviours/common.js +56 -0
  40. package/dist/bot/logic/mission/behaviours/common.js.map +1 -0
  41. package/dist/bot/logic/mission/behaviours/engineerSquad.js +39 -0
  42. package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +1 -0
  43. package/dist/bot/logic/mission/behaviours/expansionSquad.js +46 -0
  44. package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +1 -0
  45. package/dist/bot/logic/mission/behaviours/retreatSquad.js +31 -0
  46. package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +1 -0
  47. package/{src/bot/logic/squad/behaviours/scoutingSquad.ts → dist/bot/logic/mission/behaviours/scoutingSquad.js} +27 -51
  48. package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +1 -0
  49. package/dist/bot/logic/mission/mission.js +91 -19
  50. package/dist/bot/logic/mission/mission.js.map +1 -1
  51. package/dist/bot/logic/mission/missionController.js +262 -21
  52. package/dist/bot/logic/mission/missionController.js.map +1 -1
  53. package/dist/bot/logic/mission/missions/attackMission.js +113 -39
  54. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  55. package/dist/bot/logic/mission/missions/basicMission.js +13 -0
  56. package/dist/bot/logic/mission/missions/basicMission.js.map +1 -0
  57. package/dist/bot/logic/mission/missions/defenceMission.js +43 -28
  58. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  59. package/dist/bot/logic/mission/missions/engineerMission.js +37 -7
  60. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -1
  61. package/dist/bot/logic/mission/missions/expansionMission.js +42 -6
  62. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -1
  63. package/dist/bot/logic/mission/missions/missionBehaviour.js +2 -0
  64. package/dist/bot/logic/mission/missions/missionBehaviour.js.map +1 -0
  65. package/dist/bot/logic/mission/missions/retreatMission.js +31 -5
  66. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  67. package/dist/bot/logic/mission/missions/scoutingMission.js +103 -6
  68. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -1
  69. package/dist/bot/logic/mission/missions/squads/combatSquad.js +116 -0
  70. package/dist/bot/logic/mission/missions/squads/combatSquad.js.map +1 -0
  71. package/dist/bot/logic/mission/missions/squads/common.js +58 -0
  72. package/dist/bot/logic/mission/missions/squads/common.js.map +1 -0
  73. package/dist/bot/logic/mission/missions/squads/squad.js +2 -0
  74. package/dist/bot/logic/mission/missions/squads/squad.js.map +1 -0
  75. package/dist/bot/logic/squad/squadController.js +6 -2
  76. package/dist/bot/logic/squad/squadController.js.map +1 -1
  77. package/dist/bot/logic/threat/threatCalculator.js +9 -9
  78. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  79. package/dist/exampleBot.js +50 -18
  80. package/dist/exampleBot.js.map +1 -1
  81. package/package.json +5 -4
  82. package/src/bot/bot.ts +19 -51
  83. package/src/bot/logic/awareness.ts +34 -22
  84. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
  85. package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
  86. package/src/bot/logic/building/artilleryUnit.ts +2 -28
  87. package/src/bot/logic/building/basicAirUnit.ts +2 -33
  88. package/src/bot/logic/building/basicBuilding.ts +8 -6
  89. package/src/bot/logic/building/basicGroundUnit.ts +2 -46
  90. package/src/bot/logic/building/buildingRules.ts +15 -9
  91. package/src/bot/logic/building/common.ts +23 -0
  92. package/src/bot/logic/building/harvester.ts +2 -1
  93. package/src/bot/logic/building/queueController.ts +98 -43
  94. package/src/bot/logic/common/utils.ts +28 -0
  95. package/src/bot/logic/composition/alliedCompositions.ts +22 -0
  96. package/src/bot/logic/composition/common.ts +3 -0
  97. package/src/bot/logic/composition/sovietCompositions.ts +21 -0
  98. package/src/bot/logic/{squad/behaviours → mission}/actionBatcher.ts +66 -7
  99. package/src/bot/logic/mission/mission.ts +186 -37
  100. package/src/bot/logic/mission/missionController.ts +340 -31
  101. package/src/bot/logic/mission/missionFactories.ts +3 -3
  102. package/src/bot/logic/mission/missions/attackMission.ts +181 -44
  103. package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
  104. package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
  105. package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
  106. package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
  107. package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
  108. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/combatSquad.ts +56 -33
  109. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +11 -17
  110. package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
  111. package/src/bot/logic/threat/threatCalculator.ts +10 -10
  112. package/src/exampleBot.ts +56 -24
  113. package/.prettierrc +0 -5
  114. package/TODO.md +0 -15
  115. package/rules.ini +0 -23126
  116. package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
  117. package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -58
  118. package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -64
  119. package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -50
  120. package/src/bot/logic/squad/squad.ts +0 -165
  121. package/src/bot/logic/squad/squadBehaviour.ts +0 -66
  122. package/src/bot/logic/squad/squadBehaviours.ts +0 -8
  123. package/src/bot/logic/squad/squadController.ts +0 -271
@@ -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
  }
@@ -1,36 +1,127 @@
1
- import { GameApi, PlayerData } from "@chronodivide/game-api";
2
- import { Squad } from "../squad/squad.js";
1
+ import { ActionsApi, GameApi, PlayerData, Tile, UnitData, Vector2 } from "@chronodivide/game-api";
3
2
  import { MatchAwareness } from "../awareness.js";
4
3
  import { DebugLogger } from "../common/utils.js";
5
-
6
- // AI starts Missions based on heuristics, which have one or more squads.
7
- // Missions can create squads (but squads will disband themselves).
4
+ import { ActionBatcher } from "./actionBatcher.js";
5
+ import { getDistanceBetweenTileAndPoint } from "../map/map.js";
6
+
7
+ const calculateCenterOfMass: (unitTiles: Tile[]) => {
8
+ centerOfMass: Vector2;
9
+ maxDistance: number;
10
+ } | null = (unitTiles) => {
11
+ if (unitTiles.length === 0) {
12
+ return null;
13
+ }
14
+ // TODO: use median here
15
+ const sums = unitTiles.reduce(
16
+ ({ x, y }, tile) => {
17
+ return {
18
+ x: x + (tile?.rx || 0),
19
+ y: y + (tile?.ry || 0),
20
+ };
21
+ },
22
+ { x: 0, y: 0 },
23
+ );
24
+ const centerOfMass = new Vector2(Math.round(sums.x / unitTiles.length), Math.round(sums.y / unitTiles.length));
25
+
26
+ // max distance of units to the center of mass
27
+ const distances = unitTiles.map((tile) => getDistanceBetweenTileAndPoint(tile, centerOfMass));
28
+ const maxDistance = Math.max(...distances);
29
+ return { centerOfMass, maxDistance };
30
+ };
31
+ // AI starts Missions based on heuristics.
8
32
  export abstract class Mission<FailureReasons = undefined> {
9
- private squad: Squad | null = null;
10
33
  private active = true;
34
+ private unitIds: number[] = [];
35
+ private centerOfMass: Vector2 | null = null;
36
+ private maxDistanceToCenterOfMass: number | null = null;
37
+
38
+ private onFinish: (unitIds: number[], reason: FailureReasons) => void = () => {};
39
+
40
+ constructor(
41
+ private uniqueName: string,
42
+ protected logger: DebugLogger,
43
+ ) {}
44
+
45
+ // TODO call this
46
+ protected updateCenterOfMass(gameApi: GameApi) {
47
+ const movableUnitTiles = this.unitIds
48
+ .map((unitId) => gameApi.getUnitData(unitId))
49
+ .filter((unit) => unit?.canMove)
50
+ .map((unit) => unit?.tile)
51
+ .filter((tile) => !!tile) as Tile[];
52
+ const tileMetrics = calculateCenterOfMass(movableUnitTiles);
53
+ if (tileMetrics) {
54
+ this.centerOfMass = tileMetrics.centerOfMass;
55
+ this.maxDistanceToCenterOfMass = tileMetrics.maxDistance;
56
+ } else {
57
+ this.centerOfMass = null;
58
+ this.maxDistanceToCenterOfMass = null;
59
+ }
60
+ }
11
61
 
12
- private onFinish: (reason: FailureReasons, squad: Squad | null) => void = () => {};
13
-
14
- constructor(private uniqueName: string, private priority: number, protected logger: DebugLogger) {}
62
+ public onAiUpdate(
63
+ gameApi: GameApi,
64
+ actionsApi: ActionsApi,
65
+ playerData: PlayerData,
66
+ matchAwareness: MatchAwareness,
67
+ actionBatcher: ActionBatcher,
68
+ ): MissionAction {
69
+ this.updateCenterOfMass(gameApi);
70
+ return this._onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
71
+ }
15
72
 
16
- abstract onAiUpdate(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): MissionAction;
73
+ // TODO: fix this weird indirection
74
+ abstract _onAiUpdate(
75
+ gameApi: GameApi,
76
+ actionsApi: ActionsApi,
77
+ playerData: PlayerData,
78
+ matchAwareness: MatchAwareness,
79
+ actionBatcher: ActionBatcher,
80
+ ): MissionAction;
17
81
 
18
82
  isActive(): boolean {
19
83
  return this.active;
20
84
  }
21
85
 
22
- protected setSquad(squad: Squad): MissionActionRegisterSquad {
23
- this.squad = squad;
24
- return registerSquad(squad);
86
+ public getUnitIds(): number[] {
87
+ return this.unitIds;
88
+ }
89
+
90
+ public removeUnit(unitIdToRemove: number): void {
91
+ this.unitIds = this.unitIds.filter((unitId) => unitId != unitIdToRemove);
92
+ }
93
+
94
+ public addUnit(unitIdToAdd: number): void {
95
+ this.unitIds.push(unitIdToAdd);
25
96
  }
26
97
 
27
- getSquad(): Squad | null {
28
- return this.squad;
98
+ public getUnits(gameApi: GameApi): UnitData[] {
99
+ return this.unitIds
100
+ .map((unitId) => gameApi.getUnitData(unitId))
101
+ .filter((unit) => unit != null)
102
+ .map((unit) => unit!);
29
103
  }
30
104
 
31
- removeSquad() {
32
- // The squad was removed from this mission.
33
- this.squad = null;
105
+ public getUnitsOfTypes(gameApi: GameApi, ...names: string[]): UnitData[] {
106
+ return this.unitIds
107
+ .map((unitId) => gameApi.getUnitData(unitId))
108
+ .filter((unit) => !!unit && names.includes(unit.name))
109
+ .map((unit) => unit!);
110
+ }
111
+
112
+ public getUnitsMatching(gameApi: GameApi, filter: (r: UnitData) => boolean): UnitData[] {
113
+ return this.unitIds
114
+ .map((unitId) => gameApi.getUnitData(unitId))
115
+ .filter((unit) => !!unit && filter(unit))
116
+ .map((unit) => unit!);
117
+ }
118
+
119
+ public getCenterOfMass() {
120
+ return this.centerOfMass;
121
+ }
122
+
123
+ public getMaxDistanceToCenterOfMass() {
124
+ return this.maxDistanceToCenterOfMass;
34
125
  }
35
126
 
36
127
  getUniqueName(): string {
@@ -39,27 +130,37 @@ export abstract class Mission<FailureReasons = undefined> {
39
130
 
40
131
  // Don't call this from the mission itself
41
132
  endMission(reason: FailureReasons): void {
42
- this.onFinish(reason, this.squad);
43
- this.squad = null;
133
+ this.onFinish(this.unitIds, reason);
44
134
  this.active = false;
45
135
  }
46
136
 
47
137
  /**
48
138
  * Declare a callback that is executed when the mission is disbanded for whatever reason.
49
139
  */
50
- then(onFinish: (reason: FailureReasons, squad: Squad | null) => void): Mission<FailureReasons> {
140
+ then(onFinish: (unitIds: number[], reason: FailureReasons) => void): Mission<FailureReasons> {
51
141
  this.onFinish = onFinish;
52
142
  return this;
53
143
  }
144
+
145
+ abstract getGlobalDebugText(): string | undefined;
146
+
147
+ /**
148
+ * Determines whether units can be stolen from this mission by other missions with higher priority.
149
+ */
150
+ public isUnitsLocked(): boolean {
151
+ return true;
152
+ }
153
+
154
+ abstract getPriority(): number;
54
155
  }
55
156
 
56
- export type MissionActionNoop = {
57
- type: "noop";
157
+ export type MissionWithAction<T extends MissionAction> = {
158
+ mission: Mission<any>;
159
+ action: T;
58
160
  };
59
161
 
60
- export type MissionActionRegisterSquad = {
61
- type: "registerSquad";
62
- squad: Squad;
162
+ export type MissionActionNoop = {
163
+ type: "noop";
63
164
  };
64
165
 
65
166
  export type MissionActionDisband = {
@@ -67,17 +168,65 @@ export type MissionActionDisband = {
67
168
  reason: any | null;
68
169
  };
69
170
 
70
- export const noop = () =>
71
- ({
72
- type: "noop",
73
- } as MissionActionNoop);
171
+ export type MissionActionRequestUnits = {
172
+ type: "request";
173
+ unitNames: string[];
174
+ priority: number;
175
+ };
74
176
 
75
- export const registerSquad = (squad: Squad) =>
76
- ({
77
- type: "registerSquad",
78
- squad,
79
- } as MissionActionRegisterSquad);
177
+ export type MissionActionRequestSpecificUnits = {
178
+ type: "requestSpecific";
179
+ unitIds: number[];
180
+ priority: number;
181
+ };
80
182
 
81
- export const disbandMission = (reason?: any) => ({ type: "disband", reason } as MissionActionDisband);
183
+ export type MissionActionGrabFreeCombatants = {
184
+ type: "requestCombatants";
185
+ point: Vector2;
186
+ radius: number;
187
+ };
82
188
 
83
- export type MissionAction = MissionActionNoop | MissionActionRegisterSquad | MissionActionDisband;
189
+ export type MissionActionReleaseUnits = {
190
+ type: "releaseUnits";
191
+ unitIds: number[];
192
+ };
193
+
194
+ export const noop = () =>
195
+ ({
196
+ type: "noop",
197
+ }) as MissionActionNoop;
198
+
199
+ export const disbandMission = (reason?: any) => ({ type: "disband", reason }) as MissionActionDisband;
200
+ export const isDisbandMission = (a: MissionWithAction<MissionAction>): a is MissionWithAction<MissionActionDisband> =>
201
+ a.action.type === "disband";
202
+
203
+ export const requestUnits = (unitNames: string[], priority: number) =>
204
+ ({ type: "request", unitNames, priority }) as MissionActionRequestUnits;
205
+ export const isRequestUnits = (
206
+ a: MissionWithAction<MissionAction>,
207
+ ): a is MissionWithAction<MissionActionRequestUnits> => a.action.type === "request";
208
+
209
+ export const requestSpecificUnits = (unitIds: number[], priority: number) =>
210
+ ({ type: "requestSpecific", unitIds, priority }) as MissionActionRequestSpecificUnits;
211
+ export const isRequestSpecificUnits = (
212
+ a: MissionWithAction<MissionAction>,
213
+ ): a is MissionWithAction<MissionActionRequestSpecificUnits> => a.action.type === "requestSpecific";
214
+
215
+ export const grabCombatants = (point: Vector2, radius: number) =>
216
+ ({ type: "requestCombatants", point, radius }) as MissionActionGrabFreeCombatants;
217
+ export const isGrabCombatants = (
218
+ a: MissionWithAction<MissionAction>,
219
+ ): a is MissionWithAction<MissionActionGrabFreeCombatants> => a.action.type === "requestCombatants";
220
+
221
+ export const releaseUnits = (unitIds: number[]) => ({ type: "releaseUnits", unitIds }) as MissionActionReleaseUnits;
222
+ export const isReleaseUnits = (
223
+ a: MissionWithAction<MissionAction>,
224
+ ): a is MissionWithAction<MissionActionReleaseUnits> => a.action.type === "releaseUnits";
225
+
226
+ export type MissionAction =
227
+ | MissionActionNoop
228
+ | MissionActionDisband
229
+ | MissionActionRequestUnits
230
+ | MissionActionRequestSpecificUnits
231
+ | MissionActionGrabFreeCombatants
232
+ | MissionActionReleaseUnits;