@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.
- package/.env.template +5 -0
- package/README.md +54 -47
- package/dist/bot/bot.js +14 -35
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/logic/awareness.js +13 -8
- package/dist/bot/logic/awareness.js.map +1 -1
- package/dist/bot/logic/building/ArtilleryUnit.js +2 -29
- package/dist/bot/logic/building/antiAirStaticDefence.js +43 -0
- package/dist/bot/logic/building/antiAirStaticDefence.js.map +1 -0
- package/dist/bot/logic/building/antiGroundStaticDefence.js +10 -20
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -1
- package/dist/bot/logic/building/basicAirUnit.js +2 -23
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
- package/dist/bot/logic/building/basicBuilding.js +3 -2
- package/dist/bot/logic/building/basicBuilding.js.map +1 -1
- package/dist/bot/logic/building/basicGroundUnit.js +2 -43
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
- package/dist/bot/logic/building/buildingRules.js +15 -9
- package/dist/bot/logic/building/buildingRules.js.map +1 -1
- package/dist/bot/logic/building/common.js +19 -0
- package/dist/bot/logic/building/common.js.map +1 -0
- package/dist/bot/logic/building/harvester.js +2 -1
- package/dist/bot/logic/building/harvester.js.map +1 -1
- package/dist/bot/logic/building/queueController.js +69 -42
- package/dist/bot/logic/building/queueController.js.map +1 -1
- package/dist/bot/logic/common/utils.js +21 -0
- package/dist/bot/logic/common/utils.js.map +1 -1
- package/dist/bot/logic/composition/alliedCompositions.js +13 -0
- package/dist/bot/logic/composition/alliedCompositions.js.map +1 -0
- package/dist/bot/logic/composition/common.js +2 -0
- package/dist/bot/logic/composition/common.js.map +1 -0
- package/dist/bot/logic/composition/sovietCompositions.js +13 -0
- package/dist/bot/logic/composition/sovietCompositions.js.map +1 -0
- package/dist/bot/logic/mission/actionBatcher.js +92 -0
- package/dist/bot/logic/mission/actionBatcher.js.map +1 -0
- package/dist/bot/logic/mission/behaviours/combatSquad.js +124 -0
- package/dist/bot/logic/mission/behaviours/combatSquad.js.map +1 -0
- package/dist/bot/logic/mission/behaviours/common.js +56 -0
- package/dist/bot/logic/mission/behaviours/common.js.map +1 -0
- package/dist/bot/logic/mission/behaviours/engineerSquad.js +39 -0
- package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +1 -0
- package/dist/bot/logic/mission/behaviours/expansionSquad.js +46 -0
- package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +1 -0
- package/dist/bot/logic/mission/behaviours/retreatSquad.js +31 -0
- package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +1 -0
- package/{src/bot/logic/squad/behaviours/scoutingSquad.ts → dist/bot/logic/mission/behaviours/scoutingSquad.js} +27 -51
- package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +1 -0
- package/dist/bot/logic/mission/mission.js +91 -19
- package/dist/bot/logic/mission/mission.js.map +1 -1
- package/dist/bot/logic/mission/missionController.js +262 -21
- package/dist/bot/logic/mission/missionController.js.map +1 -1
- package/dist/bot/logic/mission/missions/attackMission.js +113 -39
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/basicMission.js +13 -0
- package/dist/bot/logic/mission/missions/basicMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +43 -28
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/engineerMission.js +37 -7
- package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/expansionMission.js +42 -6
- package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/missionBehaviour.js +2 -0
- package/dist/bot/logic/mission/missions/missionBehaviour.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js +31 -5
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/scoutingMission.js +103 -6
- package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/squads/combatSquad.js +116 -0
- package/dist/bot/logic/mission/missions/squads/combatSquad.js.map +1 -0
- package/dist/bot/logic/mission/missions/squads/common.js +58 -0
- package/dist/bot/logic/mission/missions/squads/common.js.map +1 -0
- package/dist/bot/logic/mission/missions/squads/squad.js +2 -0
- package/dist/bot/logic/mission/missions/squads/squad.js.map +1 -0
- package/dist/bot/logic/squad/squadController.js +6 -2
- package/dist/bot/logic/squad/squadController.js.map +1 -1
- package/dist/bot/logic/threat/threatCalculator.js +9 -9
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
- package/dist/exampleBot.js +50 -18
- package/dist/exampleBot.js.map +1 -1
- package/package.json +5 -4
- package/src/bot/bot.ts +19 -51
- package/src/bot/logic/awareness.ts +34 -22
- package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
- package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
- package/src/bot/logic/building/artilleryUnit.ts +2 -28
- package/src/bot/logic/building/basicAirUnit.ts +2 -33
- package/src/bot/logic/building/basicBuilding.ts +8 -6
- package/src/bot/logic/building/basicGroundUnit.ts +2 -46
- package/src/bot/logic/building/buildingRules.ts +15 -9
- package/src/bot/logic/building/common.ts +23 -0
- package/src/bot/logic/building/harvester.ts +2 -1
- package/src/bot/logic/building/queueController.ts +98 -43
- package/src/bot/logic/common/utils.ts +28 -0
- package/src/bot/logic/composition/alliedCompositions.ts +22 -0
- package/src/bot/logic/composition/common.ts +3 -0
- package/src/bot/logic/composition/sovietCompositions.ts +21 -0
- package/src/bot/logic/{squad/behaviours → mission}/actionBatcher.ts +66 -7
- package/src/bot/logic/mission/mission.ts +186 -37
- package/src/bot/logic/mission/missionController.ts +340 -31
- package/src/bot/logic/mission/missionFactories.ts +3 -3
- package/src/bot/logic/mission/missions/attackMission.ts +181 -44
- package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
- package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
- package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
- package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
- package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
- package/src/bot/logic/{squad/behaviours → mission/missions/squads}/combatSquad.ts +56 -33
- package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +11 -17
- package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
- package/src/bot/logic/threat/threatCalculator.ts +10 -10
- package/src/exampleBot.ts +56 -24
- package/.prettierrc +0 -5
- package/TODO.md +0 -15
- package/rules.ini +0 -23126
- package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -58
- package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -64
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -50
- package/src/bot/logic/squad/squad.ts +0 -165
- package/src/bot/logic/squad/squadBehaviour.ts +0 -66
- package/src/bot/logic/squad/squadBehaviours.ts +0 -8
- package/src/bot/logic/squad/squadController.ts +0 -271
|
@@ -1,70 +1,195 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CombatSquad } from "
|
|
3
|
-
import { Mission, MissionAction, disbandMission, noop } from "../mission.js";
|
|
4
|
-
import { Squad } from "../../squad/squad.js";
|
|
1
|
+
import { ActionsApi, GameApi, ObjectType, PlayerData, SideType, UnitData, Vector2 } from "@chronodivide/game-api";
|
|
2
|
+
import { CombatSquad } from "./squads/combatSquad.js";
|
|
3
|
+
import { Mission, MissionAction, disbandMission, noop, requestUnits } from "../mission.js";
|
|
5
4
|
import { MissionFactory } from "../missionFactories.js";
|
|
6
5
|
import { MatchAwareness } from "../../awareness.js";
|
|
7
6
|
import { MissionController } from "../missionController.js";
|
|
8
7
|
import { RetreatMission } from "./retreatMission.js";
|
|
9
|
-
import { DebugLogger, maxBy } from "../../common/utils.js";
|
|
8
|
+
import { DebugLogger, countBy, isOwnedByNeutral, maxBy } from "../../common/utils.js";
|
|
9
|
+
import { ActionBatcher } from "../actionBatcher.js";
|
|
10
|
+
import { getSovietComposition } from "../../composition/sovietCompositions.js";
|
|
11
|
+
import { getAlliedCompositions } from "../../composition/alliedCompositions.js";
|
|
12
|
+
import { UnitComposition } from "../../composition/common.js";
|
|
13
|
+
import { manageMoveMicro } from "./squads/common.js";
|
|
10
14
|
|
|
11
15
|
export enum AttackFailReason {
|
|
12
16
|
NoTargets = 0,
|
|
13
17
|
DefenceTooStrong = 1,
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
enum AttackMissionState {
|
|
21
|
+
Preparing = 0,
|
|
22
|
+
Attacking = 1,
|
|
23
|
+
Retreating = 2,
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
const NO_TARGET_RETARGET_TICKS = 450;
|
|
17
27
|
const NO_TARGET_IDLE_TIMEOUT_TICKS = 900;
|
|
18
28
|
|
|
29
|
+
function calculateTargetComposition(
|
|
30
|
+
gameApi: GameApi,
|
|
31
|
+
playerData: PlayerData,
|
|
32
|
+
matchAwareness: MatchAwareness,
|
|
33
|
+
): UnitComposition {
|
|
34
|
+
if (!playerData.country) {
|
|
35
|
+
throw new Error(`player ${playerData.name} has no country`);
|
|
36
|
+
} else if (playerData.country.side === SideType.Nod) {
|
|
37
|
+
return getSovietComposition(gameApi, playerData, matchAwareness);
|
|
38
|
+
} else {
|
|
39
|
+
return getAlliedCompositions(gameApi, playerData, matchAwareness);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ATTACK_MISSION_PRIORITY_RAMP = 1.01;
|
|
44
|
+
const ATTACK_MISSION_MAX_PRIORITY = 50;
|
|
45
|
+
|
|
19
46
|
/**
|
|
20
47
|
* A mission that tries to attack a certain area.
|
|
21
48
|
*/
|
|
22
49
|
export class AttackMission extends Mission<AttackFailReason> {
|
|
50
|
+
private squad: CombatSquad;
|
|
51
|
+
|
|
23
52
|
private lastTargetSeenAt = 0;
|
|
24
|
-
private behaviour: CombatSquad | undefined;
|
|
25
53
|
private hasPickedNewTarget: boolean = false;
|
|
26
54
|
|
|
55
|
+
private state: AttackMissionState = AttackMissionState.Preparing;
|
|
56
|
+
|
|
27
57
|
constructor(
|
|
28
58
|
uniqueName: string,
|
|
29
|
-
priority: number,
|
|
30
|
-
|
|
59
|
+
private priority: number,
|
|
60
|
+
rallyArea: Vector2,
|
|
31
61
|
private attackArea: Vector2,
|
|
32
62
|
private radius: number,
|
|
63
|
+
private composition: UnitComposition,
|
|
33
64
|
logger: DebugLogger,
|
|
34
65
|
) {
|
|
35
|
-
super(uniqueName,
|
|
66
|
+
super(uniqueName, logger);
|
|
67
|
+
this.squad = new CombatSquad(rallyArea, attackArea, radius);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_onAiUpdate(
|
|
71
|
+
gameApi: GameApi,
|
|
72
|
+
actionsApi: ActionsApi,
|
|
73
|
+
playerData: PlayerData,
|
|
74
|
+
matchAwareness: MatchAwareness,
|
|
75
|
+
actionBatcher: ActionBatcher,
|
|
76
|
+
): MissionAction {
|
|
77
|
+
switch (this.state) {
|
|
78
|
+
case AttackMissionState.Preparing:
|
|
79
|
+
return this.handlePreparingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
|
|
80
|
+
case AttackMissionState.Attacking:
|
|
81
|
+
return this.handleAttackingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
|
|
82
|
+
case AttackMissionState.Retreating:
|
|
83
|
+
return this.handleRetreatingState(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
|
|
84
|
+
}
|
|
36
85
|
}
|
|
37
86
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
87
|
+
private handlePreparingState(
|
|
88
|
+
gameApi: GameApi,
|
|
89
|
+
actionsApi: ActionsApi,
|
|
90
|
+
playerData: PlayerData,
|
|
91
|
+
matchAwareness: MatchAwareness,
|
|
92
|
+
actionBatcher: ActionBatcher,
|
|
93
|
+
) {
|
|
94
|
+
const currentComposition: UnitComposition = countBy(this.getUnits(gameApi), (unit) => unit.name);
|
|
95
|
+
|
|
96
|
+
const missingUnits = Object.entries(this.composition).filter(([unitType, targetAmount]) => {
|
|
97
|
+
return !currentComposition[unitType] || currentComposition[unitType] < targetAmount;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (missingUnits.length > 0) {
|
|
101
|
+
this.priority = Math.min(this.priority * ATTACK_MISSION_PRIORITY_RAMP, ATTACK_MISSION_MAX_PRIORITY);
|
|
102
|
+
return requestUnits(
|
|
103
|
+
missingUnits.map(([unitName]) => unitName),
|
|
104
|
+
this.priority,
|
|
105
|
+
);
|
|
42
106
|
} else {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
107
|
+
this.priority = ATTACK_MISSION_INITIAL_PRIORITY;
|
|
108
|
+
this.state = AttackMissionState.Attacking;
|
|
109
|
+
return noop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
47
112
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
113
|
+
private handleAttackingState(
|
|
114
|
+
gameApi: GameApi,
|
|
115
|
+
actionsApi: ActionsApi,
|
|
116
|
+
playerData: PlayerData,
|
|
117
|
+
matchAwareness: MatchAwareness,
|
|
118
|
+
actionBatcher: ActionBatcher,
|
|
119
|
+
) {
|
|
120
|
+
if (this.getUnitIds().length === 0) {
|
|
121
|
+
// TODO: disband directly (we no longer retreat when losing)
|
|
122
|
+
this.state = AttackMissionState.Retreating;
|
|
123
|
+
return noop();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const foundTargets = matchAwareness
|
|
127
|
+
.getHostilesNearPoint2d(this.attackArea, this.radius)
|
|
128
|
+
.map((unit) => gameApi.getUnitData(unit.unitId))
|
|
129
|
+
.filter((unit) => !isOwnedByNeutral(unit)) as UnitData[];
|
|
130
|
+
|
|
131
|
+
const update = this.squad.onAiUpdate(
|
|
132
|
+
gameApi,
|
|
133
|
+
actionsApi,
|
|
134
|
+
actionBatcher,
|
|
135
|
+
playerData,
|
|
136
|
+
this,
|
|
137
|
+
matchAwareness,
|
|
138
|
+
this.logger,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (update.type !== "noop") {
|
|
142
|
+
return update;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (foundTargets.length > 0) {
|
|
146
|
+
this.lastTargetSeenAt = gameApi.getCurrentTick();
|
|
147
|
+
this.hasPickedNewTarget = false;
|
|
148
|
+
} else if (gameApi.getCurrentTick() > this.lastTargetSeenAt + NO_TARGET_IDLE_TIMEOUT_TICKS) {
|
|
149
|
+
return disbandMission(AttackFailReason.NoTargets);
|
|
150
|
+
} else if (
|
|
151
|
+
!this.hasPickedNewTarget &&
|
|
152
|
+
gameApi.getCurrentTick() > this.lastTargetSeenAt + NO_TARGET_RETARGET_TICKS
|
|
153
|
+
) {
|
|
154
|
+
const newTarget = generateTarget(gameApi, playerData, matchAwareness);
|
|
155
|
+
if (newTarget) {
|
|
156
|
+
this.squad.setAttackArea(newTarget);
|
|
157
|
+
this.hasPickedNewTarget = true;
|
|
64
158
|
}
|
|
65
159
|
}
|
|
160
|
+
|
|
66
161
|
return noop();
|
|
67
162
|
}
|
|
163
|
+
|
|
164
|
+
private handleRetreatingState(
|
|
165
|
+
gameApi: GameApi,
|
|
166
|
+
actionsApi: ActionsApi,
|
|
167
|
+
playerData: PlayerData,
|
|
168
|
+
matchAwareness: MatchAwareness,
|
|
169
|
+
actionBatcher: ActionBatcher,
|
|
170
|
+
) {
|
|
171
|
+
this.getUnits(gameApi).forEach((unitId) => {
|
|
172
|
+
actionBatcher.push(manageMoveMicro(unitId, matchAwareness.getMainRallyPoint()));
|
|
173
|
+
});
|
|
174
|
+
return disbandMission();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public getGlobalDebugText(): string | undefined {
|
|
178
|
+
return this.squad.getGlobalDebugText() ?? "<none>";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public getState() {
|
|
182
|
+
return this.state;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// This mission can give up its units while preparing.
|
|
186
|
+
public isUnitsLocked(): boolean {
|
|
187
|
+
return this.state !== AttackMissionState.Preparing;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public getPriority() {
|
|
191
|
+
return this.priority;
|
|
192
|
+
}
|
|
68
193
|
}
|
|
69
194
|
|
|
70
195
|
// Calculates the weight for initiating an attack on the position of a unit or building.
|
|
@@ -89,7 +214,7 @@ function generateTarget(
|
|
|
89
214
|
try {
|
|
90
215
|
const tryFocusHarvester = gameApi.generateRandomInt(0, 1) === 0;
|
|
91
216
|
const enemyUnits = gameApi
|
|
92
|
-
.getVisibleUnits(playerData.name, "
|
|
217
|
+
.getVisibleUnits(playerData.name, "enemy")
|
|
93
218
|
.map((unitId) => gameApi.getUnitData(unitId))
|
|
94
219
|
.filter((u) => !!u && gameApi.getPlayerData(u.owner).isCombatant) as UnitData[];
|
|
95
220
|
|
|
@@ -129,6 +254,8 @@ const VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS = 120;
|
|
|
129
254
|
// Number of ticks between attacking "bases" (enemy starting locations).
|
|
130
255
|
const BASE_ATTACK_COOLDOWN_TICKS = 1800;
|
|
131
256
|
|
|
257
|
+
const ATTACK_MISSION_INITIAL_PRIORITY = 1;
|
|
258
|
+
|
|
132
259
|
export class AttackMissionFactory implements MissionFactory {
|
|
133
260
|
constructor(private lastAttackAt: number = -VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS) {}
|
|
134
261
|
|
|
@@ -143,14 +270,23 @@ export class AttackMissionFactory implements MissionFactory {
|
|
|
143
270
|
missionController: MissionController,
|
|
144
271
|
logger: DebugLogger,
|
|
145
272
|
): void {
|
|
146
|
-
if (
|
|
273
|
+
if (gameApi.getCurrentTick() < this.lastAttackAt + VISIBLE_TARGET_ATTACK_COOLDOWN_TICKS) {
|
|
147
274
|
return;
|
|
148
275
|
}
|
|
149
|
-
|
|
276
|
+
|
|
277
|
+
// can only have one attack 'preparing' at once.
|
|
278
|
+
if (
|
|
279
|
+
missionController
|
|
280
|
+
.getMissions()
|
|
281
|
+
.some(
|
|
282
|
+
(mission): mission is AttackMission =>
|
|
283
|
+
mission instanceof AttackMission && mission.getState() === AttackMissionState.Preparing,
|
|
284
|
+
)
|
|
285
|
+
) {
|
|
150
286
|
return;
|
|
151
287
|
}
|
|
152
288
|
|
|
153
|
-
const attackRadius =
|
|
289
|
+
const attackRadius = 10;
|
|
154
290
|
|
|
155
291
|
const includeEnemyBases = gameApi.getCurrentTick() > this.lastAttackAt + BASE_ATTACK_COOLDOWN_TICKS;
|
|
156
292
|
|
|
@@ -160,24 +296,25 @@ export class AttackMissionFactory implements MissionFactory {
|
|
|
160
296
|
return;
|
|
161
297
|
}
|
|
162
298
|
|
|
163
|
-
|
|
164
|
-
|
|
299
|
+
const squadName = "attack_" + gameApi.getCurrentTick();
|
|
300
|
+
|
|
301
|
+
const composition: UnitComposition = calculateTargetComposition(gameApi, playerData, matchAwareness);
|
|
165
302
|
|
|
166
303
|
const tryAttack = missionController.addMission(
|
|
167
304
|
new AttackMission(
|
|
168
305
|
squadName,
|
|
169
|
-
|
|
306
|
+
ATTACK_MISSION_INITIAL_PRIORITY,
|
|
170
307
|
matchAwareness.getMainRallyPoint(),
|
|
171
308
|
attackArea,
|
|
172
309
|
attackRadius,
|
|
310
|
+
composition,
|
|
173
311
|
logger,
|
|
174
|
-
).then((
|
|
312
|
+
).then((unitIds, reason) => {
|
|
175
313
|
missionController.addMission(
|
|
176
314
|
new RetreatMission(
|
|
177
315
|
"retreat-from-" + squadName + gameApi.getCurrentTick(),
|
|
178
|
-
100,
|
|
179
316
|
matchAwareness.getMainRallyPoint(),
|
|
180
|
-
|
|
317
|
+
unitIds,
|
|
181
318
|
logger,
|
|
182
319
|
),
|
|
183
320
|
);
|
|
@@ -192,7 +329,7 @@ export class AttackMissionFactory implements MissionFactory {
|
|
|
192
329
|
gameApi: GameApi,
|
|
193
330
|
playerData: PlayerData,
|
|
194
331
|
matchAwareness: MatchAwareness,
|
|
195
|
-
failedMission: Mission
|
|
332
|
+
failedMission: Mission<any>,
|
|
196
333
|
failureReason: any,
|
|
197
334
|
missionController: MissionController,
|
|
198
335
|
): void {}
|
|
@@ -1,55 +1,88 @@
|
|
|
1
|
-
import { GameApi, PlayerData, Vector2 } from "@chronodivide/game-api";
|
|
1
|
+
import { ActionsApi, GameApi, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
|
|
2
2
|
import { MatchAwareness } from "../../awareness.js";
|
|
3
3
|
import { MissionController } from "../missionController.js";
|
|
4
|
-
import { Mission, MissionAction,
|
|
4
|
+
import { Mission, MissionAction, grabCombatants, noop, releaseUnits, requestUnits } from "../mission.js";
|
|
5
5
|
import { MissionFactory } from "../missionFactories.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { DebugLogger } from "../../common/utils.js";
|
|
6
|
+
import { CombatSquad } from "./squads/combatSquad.js";
|
|
7
|
+
import { DebugLogger, isOwnedByNeutral } from "../../common/utils.js";
|
|
8
|
+
import { ActionBatcher } from "../actionBatcher.js";
|
|
10
9
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
}
|
|
10
|
+
export const MAX_PRIORITY = 100;
|
|
11
|
+
export const PRIORITY_INCREASE_PER_TICK_RATIO = 1.025;
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* A mission that tries to defend a certain area.
|
|
17
15
|
*/
|
|
18
|
-
export class DefenceMission extends Mission<
|
|
19
|
-
private
|
|
16
|
+
export class DefenceMission extends Mission<CombatSquad> {
|
|
17
|
+
private squad: CombatSquad;
|
|
20
18
|
|
|
21
19
|
constructor(
|
|
22
20
|
uniqueName: string,
|
|
23
|
-
priority: number,
|
|
21
|
+
private priority: number,
|
|
22
|
+
rallyArea: Vector2,
|
|
24
23
|
private defenceArea: Vector2,
|
|
25
24
|
private radius: number,
|
|
26
25
|
logger: DebugLogger,
|
|
27
26
|
) {
|
|
28
|
-
super(uniqueName,
|
|
27
|
+
super(uniqueName, logger);
|
|
28
|
+
this.squad = new CombatSquad(rallyArea, defenceArea, radius);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
_onAiUpdate(
|
|
32
|
+
gameApi: GameApi,
|
|
33
|
+
actionsApi: ActionsApi,
|
|
34
|
+
playerData: PlayerData,
|
|
35
|
+
matchAwareness: MatchAwareness,
|
|
36
|
+
actionBatcher: ActionBatcher,
|
|
37
|
+
): MissionAction {
|
|
38
|
+
// Dispatch missions.
|
|
39
|
+
const foundTargets = matchAwareness
|
|
40
|
+
.getHostilesNearPoint2d(this.defenceArea, this.radius)
|
|
41
|
+
.map((unit) => gameApi.getUnitData(unit.unitId))
|
|
42
|
+
.filter((unit) => !isOwnedByNeutral(unit)) as UnitData[];
|
|
43
|
+
|
|
44
|
+
const update = this.squad.onAiUpdate(
|
|
45
|
+
gameApi,
|
|
46
|
+
actionsApi,
|
|
47
|
+
actionBatcher,
|
|
48
|
+
playerData,
|
|
49
|
+
this,
|
|
50
|
+
matchAwareness,
|
|
51
|
+
this.logger,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (update.type !== "noop") {
|
|
55
|
+
return update;
|
|
56
|
+
}
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
if (foundTargets.length === 0) {
|
|
59
|
+
this.priority = 0;
|
|
60
|
+
if (this.getUnitIds().length > 0) {
|
|
61
|
+
this.logger(`(Defence Mission ${this.getUniqueName()}): No targets found, releasing units.`);
|
|
62
|
+
return releaseUnits(this.getUnitIds());
|
|
42
63
|
} else {
|
|
43
|
-
|
|
44
|
-
this.logger(
|
|
45
|
-
`(Defence Mission ${this.getUniqueName()}): Focused on target ${targetUnit?.name} (${
|
|
46
|
-
foundTargets.length
|
|
47
|
-
} found in area ${this.radius})`,
|
|
48
|
-
);
|
|
49
|
-
this.combatSquad?.setAttackArea(new Vector2(foundTargets[0].x, foundTargets[0].y));
|
|
64
|
+
return noop();
|
|
50
65
|
}
|
|
66
|
+
} else {
|
|
67
|
+
const targetUnit = foundTargets[0];
|
|
68
|
+
this.logger(
|
|
69
|
+
`(Defence Mission ${this.getUniqueName()}): Focused on target ${targetUnit?.name} (${
|
|
70
|
+
foundTargets.length
|
|
71
|
+
} found in area ${this.radius})`,
|
|
72
|
+
);
|
|
73
|
+
this.squad.setAttackArea(new Vector2(foundTargets[0].tile.rx, foundTargets[0].tile.ry));
|
|
74
|
+
this.priority = MAX_PRIORITY; // Math.min(MAX_PRIORITY, this.priority * PRIORITY_INCREASE_PER_TICK_RATIO);
|
|
75
|
+
return grabCombatants(playerData.startLocation, this.priority);
|
|
51
76
|
}
|
|
52
|
-
return
|
|
77
|
+
//return requestUnits(["E1", "E2", "FV", "HTK", "MTNK", "HTNK"], this.priority);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public getGlobalDebugText(): string | undefined {
|
|
81
|
+
return this.squad.getGlobalDebugText() ?? "<none>";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public getPriority() {
|
|
85
|
+
return this.priority;
|
|
53
86
|
}
|
|
54
87
|
}
|
|
55
88
|
|
|
@@ -83,7 +116,10 @@ export class DefenceMissionFactory implements MissionFactory {
|
|
|
83
116
|
|
|
84
117
|
const defendableRadius =
|
|
85
118
|
DEFENCE_STARTING_RADIUS + DEFENCE_RADIUS_INCREASE_PER_GAME_TICK * gameApi.getCurrentTick();
|
|
86
|
-
const enemiesNearSpawn = matchAwareness
|
|
119
|
+
const enemiesNearSpawn = matchAwareness
|
|
120
|
+
.getHostilesNearPoint2d(playerData.startLocation, defendableRadius)
|
|
121
|
+
.map((unit) => gameApi.getUnitData(unit.unitId))
|
|
122
|
+
.filter((unit) => !isOwnedByNeutral(unit)) as UnitData[];
|
|
87
123
|
|
|
88
124
|
if (enemiesNearSpawn.length > 0) {
|
|
89
125
|
logger(
|
|
@@ -94,21 +130,12 @@ export class DefenceMissionFactory implements MissionFactory {
|
|
|
94
130
|
missionController.addMission(
|
|
95
131
|
new DefenceMission(
|
|
96
132
|
"globalDefence",
|
|
97
|
-
|
|
133
|
+
10,
|
|
134
|
+
matchAwareness.getMainRallyPoint(),
|
|
98
135
|
playerData.startLocation,
|
|
99
136
|
defendableRadius * 1.2,
|
|
100
137
|
logger,
|
|
101
|
-
)
|
|
102
|
-
missionController.addMission(
|
|
103
|
-
new RetreatMission(
|
|
104
|
-
"retreat-from-globalDefence" + gameApi.getCurrentTick(),
|
|
105
|
-
100,
|
|
106
|
-
matchAwareness.getMainRallyPoint(),
|
|
107
|
-
squad?.getUnitIds() ?? [],
|
|
108
|
-
logger,
|
|
109
|
-
),
|
|
110
|
-
);
|
|
111
|
-
}),
|
|
138
|
+
),
|
|
112
139
|
);
|
|
113
140
|
}
|
|
114
141
|
}
|
|
@@ -117,7 +144,7 @@ export class DefenceMissionFactory implements MissionFactory {
|
|
|
117
144
|
gameApi: GameApi,
|
|
118
145
|
playerData: PlayerData,
|
|
119
146
|
matchAwareness: MatchAwareness,
|
|
120
|
-
failedMission: Mission
|
|
147
|
+
failedMission: Mission<any>,
|
|
121
148
|
failureReason: undefined,
|
|
122
149
|
missionController: MissionController,
|
|
123
150
|
): void {}
|
|
@@ -1,21 +1,70 @@
|
|
|
1
|
-
import { GameApi, PlayerData } from "@chronodivide/game-api";
|
|
2
|
-
import {
|
|
3
|
-
import { Mission } from "../mission.js";
|
|
4
|
-
import { ExpansionSquad } from "../../squad/behaviours/expansionSquad.js";
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData } from "@chronodivide/game-api";
|
|
2
|
+
import { Mission, MissionAction, disbandMission, noop, requestUnits } from "../mission.js";
|
|
5
3
|
import { MissionFactory } from "../missionFactories.js";
|
|
6
|
-
import { OneTimeMission } from "./oneTimeMission.js";
|
|
7
4
|
import { MatchAwareness } from "../../awareness.js";
|
|
8
5
|
import { MissionController } from "../missionController.js";
|
|
9
6
|
import { DebugLogger } from "../../common/utils.js";
|
|
10
|
-
import {
|
|
7
|
+
import { ActionBatcher } from "../actionBatcher.js";
|
|
8
|
+
|
|
9
|
+
const CAPTURE_COOLDOWN_TICKS = 30;
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* A mission that tries to send an engineer into a building (e.g. to capture tech building or repair bridge)
|
|
14
13
|
*/
|
|
15
|
-
export class EngineerMission extends
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export class EngineerMission extends Mission {
|
|
15
|
+
private hasAttemptedCaptureWith: {
|
|
16
|
+
unitId: number;
|
|
17
|
+
gameTick: number;
|
|
18
|
+
} | null = null;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
uniqueName: string,
|
|
22
|
+
private priority: number,
|
|
23
|
+
private captureTargetId: number,
|
|
24
|
+
logger: DebugLogger,
|
|
25
|
+
) {
|
|
26
|
+
super(uniqueName, logger);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public _onAiUpdate(
|
|
30
|
+
gameApi: GameApi,
|
|
31
|
+
actionsApi: ActionsApi,
|
|
32
|
+
playerData: PlayerData,
|
|
33
|
+
matchAwareness: MatchAwareness,
|
|
34
|
+
actionBatcher: ActionBatcher,
|
|
35
|
+
): MissionAction {
|
|
36
|
+
const engineerTypes = ["ENGINEER", "SENGINEER"];
|
|
37
|
+
const engineers = this.getUnitsOfTypes(gameApi, ...engineerTypes);
|
|
38
|
+
if (engineers.length === 0) {
|
|
39
|
+
// Perhaps we deployed already (or the unit was destroyed), end the mission.
|
|
40
|
+
if (this.hasAttemptedCaptureWith !== null) {
|
|
41
|
+
return disbandMission();
|
|
42
|
+
}
|
|
43
|
+
return requestUnits(engineerTypes, this.priority);
|
|
44
|
+
} else if (
|
|
45
|
+
!this.hasAttemptedCaptureWith ||
|
|
46
|
+
gameApi.getCurrentTick() > this.hasAttemptedCaptureWith.gameTick + CAPTURE_COOLDOWN_TICKS
|
|
47
|
+
) {
|
|
48
|
+
actionsApi.orderUnits(
|
|
49
|
+
engineers.map((engineer) => engineer.id),
|
|
50
|
+
OrderType.Capture,
|
|
51
|
+
this.captureTargetId,
|
|
52
|
+
);
|
|
53
|
+
// Add a cooldown to deploy attempts.
|
|
54
|
+
this.hasAttemptedCaptureWith = {
|
|
55
|
+
unitId: engineers[0].id,
|
|
56
|
+
gameTick: gameApi.getCurrentTick(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return noop();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public getGlobalDebugText(): string | undefined {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getPriority() {
|
|
67
|
+
return this.priority;
|
|
19
68
|
}
|
|
20
69
|
}
|
|
21
70
|
|
|
@@ -36,13 +85,17 @@ export class EngineerMissionFactory implements MissionFactory {
|
|
|
36
85
|
playerData: PlayerData,
|
|
37
86
|
matchAwareness: MatchAwareness,
|
|
38
87
|
missionController: MissionController,
|
|
39
|
-
logger: DebugLogger
|
|
88
|
+
logger: DebugLogger,
|
|
40
89
|
): void {
|
|
41
90
|
if (!(gameApi.getCurrentTick() > this.lastCheckAt + TECH_CHECK_INTERVAL_TICKS)) {
|
|
42
91
|
return;
|
|
43
92
|
}
|
|
44
93
|
this.lastCheckAt = gameApi.getCurrentTick();
|
|
45
|
-
const eligibleTechBuildings = gameApi.getVisibleUnits(
|
|
94
|
+
const eligibleTechBuildings = gameApi.getVisibleUnits(
|
|
95
|
+
playerData.name,
|
|
96
|
+
"hostile",
|
|
97
|
+
(r) => r.capturable && r.produceCashAmount > 0,
|
|
98
|
+
);
|
|
46
99
|
|
|
47
100
|
eligibleTechBuildings.forEach((techBuildingId) => {
|
|
48
101
|
missionController.addMission(new EngineerMission("capture-" + techBuildingId, 100, techBuildingId, logger));
|
|
@@ -53,9 +106,8 @@ export class EngineerMissionFactory implements MissionFactory {
|
|
|
53
106
|
gameApi: GameApi,
|
|
54
107
|
playerData: PlayerData,
|
|
55
108
|
matchAwareness: MatchAwareness,
|
|
56
|
-
failedMission: Mission
|
|
109
|
+
failedMission: Mission<any>,
|
|
57
110
|
failureReason: undefined,
|
|
58
111
|
missionController: MissionController,
|
|
59
|
-
): void {
|
|
60
|
-
}
|
|
112
|
+
): void {}
|
|
61
113
|
}
|