@supalosa/chronodivide-bot 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +5 -0
- package/README.md +54 -47
- package/dist/bot/bot.js +14 -30
- 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/awarenessImpl.js +132 -0
- package/dist/bot/logic/awarenessImpl.js.map +1 -0
- package/dist/bot/logic/building/ArtilleryUnit.js +2 -29
- package/dist/bot/logic/building/ArtilleryUnit.js.map +1 -0
- 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 +7 -4
- package/dist/bot/logic/building/antiGroundStaticDefence.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/building.js +55 -11
- package/dist/bot/logic/building/building.js.map +1 -0
- package/dist/bot/logic/building/buildingRules.js +10 -5
- 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 +58 -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/behaviours/attackSquad.js +63 -56
- package/dist/bot/logic/squad/behaviours/combatSquad.js +21 -25
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/common.js +11 -26
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +15 -2
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +2 -4
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/expansionSquad.js +2 -4
- package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -4
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +18 -25
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
- package/dist/bot/logic/squad/squad.js +10 -10
- package/dist/bot/logic/squad/squad.js.map +1 -1
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
- package/dist/bot/logic/squad/squadController.js +5 -17
- package/dist/bot/logic/squad/squadController.js.map +1 -1
- package/dist/bot/logic/threat/threatCalculator.js +5 -5
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
- package/dist/exampleBot.js +45 -18
- package/dist/exampleBot.js.map +1 -1
- package/package.json +5 -4
- package/src/bot/bot.ts +19 -45
- 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/threat.ts +15 -15
- package/src/bot/logic/threat/threatCalculator.ts +10 -10
- package/src/exampleBot.ts +50 -24
- package/.prettierrc +0 -5
- package/TODO.md +0 -15
- package/dist/bot/logic/building/artilleryUnit.js.map +0 -1
- package/dist/bot/logic/building/massedAntiGroundUnit.js +0 -20
- package/dist/bot/logic/building/queues.js +0 -19
- package/dist/bot/logic/knowledge.js +0 -1
- package/dist/bot/logic/mission/basicMission.js +0 -26
- package/dist/bot/logic/mission/expansionMission.js +0 -32
- package/dist/bot/logic/squad/behaviours/actionBatcher.js +0 -36
- package/dist/bot/logic/squad/behaviours/actionBatcher.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/squadExpansion.js +0 -31
- package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
- 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,63 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Squad } from "../squad.js";
|
|
3
|
-
import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
|
|
4
|
-
import { MatchAwareness } from "../../awareness.js";
|
|
5
|
-
import { DebugLogger } from "../../common/utils.js";
|
|
1
|
+
import { OrderType } from "@chronodivide/game-api";
|
|
6
2
|
import { getDistanceBetweenTileAndPoint } from "../../map/map.js";
|
|
7
|
-
import {
|
|
8
|
-
import { ActionBatcher } from "./actionBatcher.js";
|
|
9
|
-
|
|
3
|
+
import { disbandMission, noop, requestUnits } from "../mission.js";
|
|
10
4
|
const SCOUT_MOVE_COOLDOWN_TICKS = 30;
|
|
11
|
-
|
|
12
5
|
// Max units to spend on a particular scout target.
|
|
13
6
|
const MAX_ATTEMPTS_PER_TARGET = 5;
|
|
14
|
-
|
|
15
7
|
// Maximum ticks to spend trying to scout a target *without making progress towards it*.
|
|
16
8
|
// Every time a unit gets closer to the target, the timer refreshes.
|
|
17
9
|
const MAX_TICKS_PER_TARGET = 600;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private hadUnit: boolean = false;
|
|
30
|
-
|
|
31
|
-
public onAiUpdate(
|
|
32
|
-
gameApi: GameApi,
|
|
33
|
-
actionsApi: ActionsApi,
|
|
34
|
-
actionBatcher: ActionBatcher,
|
|
35
|
-
playerData: PlayerData,
|
|
36
|
-
squad: Squad,
|
|
37
|
-
matchAwareness: MatchAwareness,
|
|
38
|
-
logger: DebugLogger,
|
|
39
|
-
): SquadAction {
|
|
10
|
+
export class ScoutingSquad {
|
|
11
|
+
constructor(priority) {
|
|
12
|
+
this.priority = priority;
|
|
13
|
+
this.scoutTarget = null;
|
|
14
|
+
this.attemptsOnCurrentTarget = 0;
|
|
15
|
+
this.scoutTargetRefreshedAt = 0;
|
|
16
|
+
this.lastMoveCommandTick = 0;
|
|
17
|
+
this.scoutTargetIsPermanent = false;
|
|
18
|
+
this.hadUnit = false;
|
|
19
|
+
}
|
|
20
|
+
onAiUpdate(gameApi, actionsApi, actionBatcher, playerData, mission, matchAwareness, logger) {
|
|
40
21
|
const scoutNames = ["ADOG", "DOG", "E1", "E2", "FV", "HTK"];
|
|
41
|
-
const scouts =
|
|
42
|
-
|
|
22
|
+
const scouts = mission.getUnitsOfTypes(gameApi, ...scoutNames);
|
|
43
23
|
if ((matchAwareness.getSectorCache().getOverallVisibility() || 0) > 0.9) {
|
|
44
|
-
return
|
|
24
|
+
return disbandMission();
|
|
45
25
|
}
|
|
46
|
-
|
|
47
26
|
if (scouts.length === 0) {
|
|
48
27
|
// Count the number of times the scout dies trying to uncover the current scoutTarget.
|
|
49
28
|
if (this.scoutTarget && this.hadUnit) {
|
|
50
29
|
this.attemptsOnCurrentTarget++;
|
|
51
30
|
this.hadUnit = false;
|
|
52
31
|
}
|
|
53
|
-
return requestUnits(scoutNames,
|
|
54
|
-
}
|
|
32
|
+
return requestUnits(scoutNames, this.priority);
|
|
33
|
+
}
|
|
34
|
+
else if (this.scoutTarget) {
|
|
55
35
|
this.hadUnit = true;
|
|
56
36
|
if (!this.scoutTargetIsPermanent) {
|
|
57
37
|
if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
|
|
58
|
-
logger(
|
|
59
|
-
`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
|
|
60
|
-
);
|
|
38
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`);
|
|
61
39
|
this.setScoutTarget(null, 0);
|
|
62
40
|
return noop();
|
|
63
41
|
}
|
|
@@ -79,12 +57,10 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
79
57
|
}
|
|
80
58
|
});
|
|
81
59
|
// Check that a scout is actually moving closer to the target.
|
|
82
|
-
const distances = scouts.map((unit) => getDistanceBetweenTileAndPoint(unit.tile, this.scoutTarget
|
|
60
|
+
const distances = scouts.map((unit) => getDistanceBetweenTileAndPoint(unit.tile, this.scoutTarget));
|
|
83
61
|
const newMinDistance = Math.min(...distances);
|
|
84
62
|
if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
|
|
85
|
-
logger(
|
|
86
|
-
`Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`,
|
|
87
|
-
);
|
|
63
|
+
logger(`Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`);
|
|
88
64
|
this.scoutTargetRefreshedAt = gameApi.getCurrentTick();
|
|
89
65
|
this.scoutMinDistance = newMinDistance;
|
|
90
66
|
}
|
|
@@ -93,26 +69,26 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
93
69
|
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} successfully scouted, moving to next`);
|
|
94
70
|
this.setScoutTarget(null, gameApi.getCurrentTick());
|
|
95
71
|
}
|
|
96
|
-
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
97
74
|
const nextScoutTarget = matchAwareness.getScoutingManager().getNewScoutTarget();
|
|
98
75
|
if (!nextScoutTarget) {
|
|
99
76
|
logger(`No more scouting targets available, disbanding.`);
|
|
100
|
-
return
|
|
77
|
+
return disbandMission();
|
|
101
78
|
}
|
|
102
79
|
this.setScoutTarget(nextScoutTarget, gameApi.getCurrentTick());
|
|
103
80
|
}
|
|
104
81
|
return noop();
|
|
105
82
|
}
|
|
106
|
-
|
|
107
|
-
setScoutTarget(target: PrioritisedScoutTarget | null, currentTick: number) {
|
|
83
|
+
setScoutTarget(target, currentTick) {
|
|
108
84
|
this.attemptsOnCurrentTarget = 0;
|
|
109
85
|
this.scoutTargetRefreshedAt = currentTick;
|
|
110
86
|
this.scoutTarget = target?.asVector2() ?? null;
|
|
111
87
|
this.scoutMinDistance = undefined;
|
|
112
88
|
this.scoutTargetIsPermanent = target?.isPermanent ?? false;
|
|
113
89
|
}
|
|
114
|
-
|
|
115
|
-
public getGlobalDebugText(): string | undefined {
|
|
90
|
+
getGlobalDebugText() {
|
|
116
91
|
return undefined;
|
|
117
92
|
}
|
|
118
93
|
}
|
|
94
|
+
//# sourceMappingURL=scoutingSquad.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoutingSquad.js","sourceRoot":"","sources":["../../../../../src/bot/logic/mission/behaviours/scoutingSquad.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Vector2 } from "@chronodivide/game-api";
|
|
2
|
+
import { getDistanceBetweenTileAndPoint } from "../map/map.js";
|
|
3
|
+
const calculateCenterOfMass = (unitTiles) => {
|
|
4
|
+
if (unitTiles.length === 0) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
// TODO: use median here
|
|
8
|
+
const sums = unitTiles.reduce(({ x, y }, tile) => {
|
|
9
|
+
return {
|
|
10
|
+
x: x + (tile?.rx || 0),
|
|
11
|
+
y: y + (tile?.ry || 0),
|
|
12
|
+
};
|
|
13
|
+
}, { x: 0, y: 0 });
|
|
14
|
+
const centerOfMass = new Vector2(Math.round(sums.x / unitTiles.length), Math.round(sums.y / unitTiles.length));
|
|
15
|
+
// max distance of units to the center of mass
|
|
16
|
+
const distances = unitTiles.map((tile) => getDistanceBetweenTileAndPoint(tile, centerOfMass));
|
|
17
|
+
const maxDistance = Math.max(...distances);
|
|
18
|
+
return { centerOfMass, maxDistance };
|
|
19
|
+
};
|
|
20
|
+
// AI starts Missions based on heuristics.
|
|
3
21
|
export class Mission {
|
|
4
|
-
constructor(uniqueName,
|
|
22
|
+
constructor(uniqueName, logger) {
|
|
5
23
|
this.uniqueName = uniqueName;
|
|
6
|
-
this.priority = priority;
|
|
7
24
|
this.logger = logger;
|
|
8
|
-
this.squad = null;
|
|
9
25
|
this.active = true;
|
|
26
|
+
this.unitIds = [];
|
|
27
|
+
this.centerOfMass = null;
|
|
28
|
+
this.maxDistanceToCenterOfMass = null;
|
|
10
29
|
this.onFinish = () => { };
|
|
11
30
|
}
|
|
31
|
+
// TODO call this
|
|
32
|
+
updateCenterOfMass(gameApi) {
|
|
33
|
+
const movableUnitTiles = this.unitIds
|
|
34
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
35
|
+
.filter((unit) => unit?.canMove)
|
|
36
|
+
.map((unit) => unit?.tile)
|
|
37
|
+
.filter((tile) => !!tile);
|
|
38
|
+
const tileMetrics = calculateCenterOfMass(movableUnitTiles);
|
|
39
|
+
if (tileMetrics) {
|
|
40
|
+
this.centerOfMass = tileMetrics.centerOfMass;
|
|
41
|
+
this.maxDistanceToCenterOfMass = tileMetrics.maxDistance;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.centerOfMass = null;
|
|
45
|
+
this.maxDistanceToCenterOfMass = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, actionBatcher) {
|
|
49
|
+
this.updateCenterOfMass(gameApi);
|
|
50
|
+
return this._onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, actionBatcher);
|
|
51
|
+
}
|
|
12
52
|
isActive() {
|
|
13
53
|
return this.active;
|
|
14
54
|
}
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
55
|
+
getUnitIds() {
|
|
56
|
+
return this.unitIds;
|
|
57
|
+
}
|
|
58
|
+
removeUnit(unitIdToRemove) {
|
|
59
|
+
this.unitIds = this.unitIds.filter((unitId) => unitId != unitIdToRemove);
|
|
60
|
+
}
|
|
61
|
+
addUnit(unitIdToAdd) {
|
|
62
|
+
this.unitIds.push(unitIdToAdd);
|
|
63
|
+
}
|
|
64
|
+
getUnits(gameApi) {
|
|
65
|
+
return this.unitIds
|
|
66
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
67
|
+
.filter((unit) => unit != null)
|
|
68
|
+
.map((unit) => unit);
|
|
18
69
|
}
|
|
19
|
-
|
|
20
|
-
return this.
|
|
70
|
+
getUnitsOfTypes(gameApi, ...names) {
|
|
71
|
+
return this.unitIds
|
|
72
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
73
|
+
.filter((unit) => !!unit && names.includes(unit.name))
|
|
74
|
+
.map((unit) => unit);
|
|
21
75
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
76
|
+
getUnitsMatching(gameApi, filter) {
|
|
77
|
+
return this.unitIds
|
|
78
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
79
|
+
.filter((unit) => !!unit && filter(unit))
|
|
80
|
+
.map((unit) => unit);
|
|
81
|
+
}
|
|
82
|
+
getCenterOfMass() {
|
|
83
|
+
return this.centerOfMass;
|
|
84
|
+
}
|
|
85
|
+
getMaxDistanceToCenterOfMass() {
|
|
86
|
+
return this.maxDistanceToCenterOfMass;
|
|
25
87
|
}
|
|
26
88
|
getUniqueName() {
|
|
27
89
|
return this.uniqueName;
|
|
28
90
|
}
|
|
29
91
|
// Don't call this from the mission itself
|
|
30
92
|
endMission(reason) {
|
|
31
|
-
this.onFinish(
|
|
32
|
-
this.squad = null;
|
|
93
|
+
this.onFinish(this.unitIds, reason);
|
|
33
94
|
this.active = false;
|
|
34
95
|
}
|
|
35
96
|
/**
|
|
@@ -39,13 +100,24 @@ export class Mission {
|
|
|
39
100
|
this.onFinish = onFinish;
|
|
40
101
|
return this;
|
|
41
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Determines whether units can be stolen from this mission by other missions with higher priority.
|
|
105
|
+
*/
|
|
106
|
+
isUnitsLocked() {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
42
109
|
}
|
|
43
110
|
export const noop = () => ({
|
|
44
111
|
type: "noop",
|
|
45
112
|
});
|
|
46
|
-
export const registerSquad = (squad) => ({
|
|
47
|
-
type: "registerSquad",
|
|
48
|
-
squad,
|
|
49
|
-
});
|
|
50
113
|
export const disbandMission = (reason) => ({ type: "disband", reason });
|
|
114
|
+
export const isDisbandMission = (a) => a.action.type === "disband";
|
|
115
|
+
export const requestUnits = (unitNames, priority) => ({ type: "request", unitNames, priority });
|
|
116
|
+
export const isRequestUnits = (a) => a.action.type === "request";
|
|
117
|
+
export const requestSpecificUnits = (unitIds, priority) => ({ type: "requestSpecific", unitIds, priority });
|
|
118
|
+
export const isRequestSpecificUnits = (a) => a.action.type === "requestSpecific";
|
|
119
|
+
export const grabCombatants = (point, radius) => ({ type: "requestCombatants", point, radius });
|
|
120
|
+
export const isGrabCombatants = (a) => a.action.type === "requestCombatants";
|
|
121
|
+
export const releaseUnits = (unitIds) => ({ type: "releaseUnits", unitIds });
|
|
122
|
+
export const isReleaseUnits = (a) => a.action.type === "releaseUnits";
|
|
51
123
|
//# sourceMappingURL=mission.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mission.js","sourceRoot":"","sources":["../../../../src/bot/logic/mission/mission.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mission.js","sourceRoot":"","sources":["../../../../src/bot/logic/mission/mission.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,51 +1,233 @@
|
|
|
1
|
-
// Meta-controller for forming and controlling
|
|
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
|
-
|
|
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
|
-
.
|
|
26
|
-
.forEach((
|
|
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
|
-
//
|
|
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}
|
|
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
|
-
|
|
78
|
-
|
|
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
|