@supalosa/chronodivide-bot 0.2.1 → 0.2.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/.prettierrc +5 -5
- package/README.md +3 -3
- package/dist/bot/bot.js +5 -1
- package/dist/bot/bot.js.map +1 -0
- package/dist/bot/logic/awareness.js +12 -2
- package/dist/bot/logic/awareness.js.map +1 -0
- package/dist/bot/logic/awarenessImpl.js +132 -0
- package/dist/bot/logic/awarenessImpl.js.map +1 -0
- package/dist/bot/logic/building/ArtilleryUnit.js +1 -0
- package/dist/bot/logic/building/ArtilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/antiGroundStaticDefence.js +1 -0
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +1 -0
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
- package/dist/bot/logic/building/basicBuilding.js +1 -0
- package/dist/bot/logic/building/basicBuilding.js.map +1 -0
- package/dist/bot/logic/building/basicGroundUnit.js +1 -0
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
- package/dist/bot/logic/building/building.js +55 -11
- package/dist/bot/logic/building/building.js.map +1 -0
- package/dist/bot/logic/building/harvester.js +1 -0
- package/dist/bot/logic/building/harvester.js.map +1 -0
- package/dist/bot/logic/building/powerPlant.js +1 -0
- package/dist/bot/logic/building/powerPlant.js.map +1 -0
- package/dist/bot/logic/building/queueController.js +1 -0
- package/dist/bot/logic/building/queueController.js.map +1 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js +1 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -0
- package/dist/bot/logic/common/scout.js +100 -0
- package/dist/bot/logic/common/scout.js.map +1 -0
- package/dist/bot/logic/common/utils.js +2 -0
- package/dist/bot/logic/common/utils.js.map +1 -0
- package/dist/bot/logic/map/map.js +9 -25
- package/dist/bot/logic/map/map.js.map +1 -0
- package/dist/bot/logic/map/sector.js +33 -1
- package/dist/bot/logic/map/sector.js.map +1 -0
- package/dist/bot/logic/mission/mission.js +3 -1
- package/dist/bot/logic/mission/mission.js.map +1 -0
- package/dist/bot/logic/mission/missionController.js +3 -2
- package/dist/bot/logic/mission/missionController.js.map +1 -0
- package/dist/bot/logic/mission/missionFactories.js +1 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -0
- package/dist/bot/logic/mission/missions/attackMission.js +6 -5
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +11 -6
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/expansionMission.js +5 -4
- package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/oneTimeMission.js +3 -2
- package/dist/bot/logic/mission/missions/oneTimeMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js +3 -2
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/scoutingMission.js +8 -9
- package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/attackSquad.js +63 -56
- package/dist/bot/logic/squad/behaviours/combatSquad.js +5 -2
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/common.js +1 -0
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +15 -2
- package/dist/bot/logic/squad/behaviours/expansionSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +66 -18
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -0
- package/dist/bot/logic/squad/squad.js +3 -3
- package/dist/bot/logic/squad/squad.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js.map +1 -0
- package/dist/bot/logic/squad/squadController.js +56 -16
- package/dist/bot/logic/squad/squadController.js.map +1 -0
- package/dist/bot/logic/threat/threat.js +1 -0
- package/dist/bot/logic/threat/threat.js.map +1 -0
- package/dist/bot/logic/threat/threatCalculator.js +1 -0
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -0
- package/dist/exampleBot.js +1 -0
- package/dist/exampleBot.js.map +1 -0
- package/package.json +9 -6
- package/src/bot/bot.ts +6 -2
- package/src/bot/logic/awareness.ts +21 -4
- package/src/bot/logic/building/ArtilleryUnit.ts +43 -43
- package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
- package/src/bot/logic/building/basicAirUnit.ts +68 -68
- package/src/bot/logic/building/basicBuilding.ts +47 -47
- package/src/bot/logic/building/building.ts +70 -12
- package/src/bot/logic/building/powerPlant.ts +32 -32
- package/src/bot/logic/building/resourceCollectionBuilding.ts +56 -56
- package/src/bot/logic/common/scout.ts +127 -1
- package/src/bot/logic/common/utils.ts +1 -0
- package/src/bot/logic/map/map.ts +70 -84
- package/src/bot/logic/map/sector.ts +46 -4
- package/src/bot/logic/mission/mission.ts +2 -2
- package/src/bot/logic/mission/missionController.ts +2 -3
- package/src/bot/logic/mission/missionFactories.ts +3 -0
- package/src/bot/logic/mission/missions/attackMission.ts +6 -2
- package/src/bot/logic/mission/missions/defenceMission.ts +34 -14
- package/src/bot/logic/mission/missions/expansionMission.ts +6 -4
- package/src/bot/logic/mission/missions/oneTimeMission.ts +3 -2
- package/src/bot/logic/mission/missions/retreatMission.ts +3 -2
- package/src/bot/logic/mission/missions/scoutingMission.ts +9 -6
- package/src/bot/logic/squad/behaviours/combatSquad.ts +5 -1
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +81 -23
- package/src/bot/logic/squad/squad.ts +3 -2
- package/src/bot/logic/squad/squadBehaviour.ts +3 -1
- package/src/bot/logic/squad/squadController.ts +88 -41
- package/src/bot/logic/threat/threat.ts +15 -15
- package/src/bot/logic/threat/threatCalculator.ts +99 -99
- package/tsconfig.json +73 -73
- 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/squadExpansion.js +0 -31
- package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
|
@@ -6,6 +6,7 @@ import { MissionFactory } from "../missionFactories.js";
|
|
|
6
6
|
import { Squad } from "../../squad/squad.js";
|
|
7
7
|
import { CombatSquad } from "../../squad/behaviours/combatSquad.js";
|
|
8
8
|
import { RetreatMission } from "./retreatMission.js";
|
|
9
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
9
10
|
|
|
10
11
|
export enum DefenceFailReason {
|
|
11
12
|
NoTargets,
|
|
@@ -22,8 +23,9 @@ export class DefenceMission extends Mission<DefenceFailReason> {
|
|
|
22
23
|
priority: number,
|
|
23
24
|
private defenceArea: Point2D,
|
|
24
25
|
private radius: number,
|
|
26
|
+
logger: DebugLogger,
|
|
25
27
|
) {
|
|
26
|
-
super(uniqueName, priority);
|
|
28
|
+
super(uniqueName, priority, logger);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
onAiUpdate(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): MissionAction {
|
|
@@ -35,8 +37,15 @@ export class DefenceMission extends Mission<DefenceFailReason> {
|
|
|
35
37
|
const foundTargets = matchAwareness.getHostilesNearPoint2d(this.defenceArea, this.radius);
|
|
36
38
|
|
|
37
39
|
if (foundTargets.length === 0) {
|
|
40
|
+
this.logger(`(Defence Mission ${this.getUniqueName()}): No targets found, disbanding.`);
|
|
38
41
|
return disbandMission(DefenceFailReason.NoTargets);
|
|
39
42
|
} else {
|
|
43
|
+
const targetUnit = gameApi.getUnitData(foundTargets[0].unitId);
|
|
44
|
+
this.logger(
|
|
45
|
+
`(Defence Mission ${this.getUniqueName()}): Focused on target ${targetUnit?.name} (${
|
|
46
|
+
foundTargets.length
|
|
47
|
+
} found in area ${this.radius})`,
|
|
48
|
+
);
|
|
40
49
|
this.combatSquad?.setAttackArea({ x: foundTargets[0].x, y: foundTargets[0].y });
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -49,7 +58,7 @@ const DEFENCE_CHECK_TICKS = 30;
|
|
|
49
58
|
// Starting radius around the player's base to trigger defense.
|
|
50
59
|
const DEFENCE_STARTING_RADIUS = 10;
|
|
51
60
|
// Every game tick, we increase the defendable area by this amount.
|
|
52
|
-
const DEFENCE_RADIUS_INCREASE_PER_GAME_TICK = 0.
|
|
61
|
+
const DEFENCE_RADIUS_INCREASE_PER_GAME_TICK = 0.001;
|
|
53
62
|
|
|
54
63
|
export class DefenceMissionFactory implements MissionFactory {
|
|
55
64
|
private lastDefenceCheckAt = 0;
|
|
@@ -65,6 +74,7 @@ export class DefenceMissionFactory implements MissionFactory {
|
|
|
65
74
|
playerData: PlayerData,
|
|
66
75
|
matchAwareness: MatchAwareness,
|
|
67
76
|
missionController: MissionController,
|
|
77
|
+
logger: DebugLogger,
|
|
68
78
|
): void {
|
|
69
79
|
if (gameApi.getCurrentTick() < this.lastDefenceCheckAt + DEFENCE_CHECK_TICKS) {
|
|
70
80
|
return;
|
|
@@ -76,19 +86,29 @@ export class DefenceMissionFactory implements MissionFactory {
|
|
|
76
86
|
const enemiesNearSpawn = matchAwareness.getHostilesNearPoint2d(playerData.startLocation, defendableRadius);
|
|
77
87
|
|
|
78
88
|
if (enemiesNearSpawn.length > 0) {
|
|
89
|
+
logger(
|
|
90
|
+
`Starting defence mission, ${
|
|
91
|
+
enemiesNearSpawn.length
|
|
92
|
+
} found in radius ${defendableRadius} (tick ${gameApi.getCurrentTick()})`,
|
|
93
|
+
);
|
|
79
94
|
missionController.addMission(
|
|
80
|
-
new DefenceMission(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
new DefenceMission(
|
|
96
|
+
"globalDefence",
|
|
97
|
+
1000,
|
|
98
|
+
playerData.startLocation,
|
|
99
|
+
defendableRadius * 1.2,
|
|
100
|
+
logger,
|
|
101
|
+
).then((reason, squad) => {
|
|
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
|
+
}),
|
|
92
112
|
);
|
|
93
113
|
}
|
|
94
114
|
}
|
|
@@ -5,15 +5,16 @@ import { ExpansionSquad } from "../../squad/behaviours/expansionSquad.js";
|
|
|
5
5
|
import { MissionFactory } from "../missionFactories.js";
|
|
6
6
|
import { OneTimeMission } from "./oneTimeMission.js";
|
|
7
7
|
import { MatchAwareness } from "../../awareness.js";
|
|
8
|
-
import { AttackFailReason, AttackMission } from "./attackMission.js";
|
|
9
8
|
import { MissionController } from "../missionController.js";
|
|
9
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* A mission that tries to create an MCV (if it doesn't exist) and deploy it somewhere it can be deployed.
|
|
13
13
|
*/
|
|
14
14
|
export class ExpansionMission extends OneTimeMission {
|
|
15
|
-
constructor(uniqueName: string, priority: number, selectedMcv: number | null
|
|
16
|
-
|
|
15
|
+
constructor(uniqueName: string, priority: number, selectedMcv: number | null,
|
|
16
|
+
logger: DebugLogger) {
|
|
17
|
+
super(uniqueName, priority, () => new ExpansionSquad(selectedMcv), logger);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -27,13 +28,14 @@ export class ExpansionMissionFactory implements MissionFactory {
|
|
|
27
28
|
playerData: PlayerData,
|
|
28
29
|
matchAwareness: MatchAwareness,
|
|
29
30
|
missionController: MissionController,
|
|
31
|
+
logger: DebugLogger
|
|
30
32
|
): void {
|
|
31
33
|
// At this point, only expand if we have a loose MCV.
|
|
32
34
|
const mcvs = gameApi.getVisibleUnits(playerData.name, "self", (r) =>
|
|
33
35
|
gameApi.getGeneralRules().baseUnit.includes(r.name)
|
|
34
36
|
);
|
|
35
37
|
mcvs.forEach((mcv) => {
|
|
36
|
-
missionController.addMission(new ExpansionMission("expand-with-" + mcv, 100, mcv));
|
|
38
|
+
missionController.addMission(new ExpansionMission("expand-with-" + mcv, 100, mcv, logger));
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -6,6 +6,7 @@ import { Squad } from "../../squad/squad.js";
|
|
|
6
6
|
import { MissionFactory } from "../missionFactories.js";
|
|
7
7
|
import { SquadBehaviour } from "../../squad/squadBehaviour.js";
|
|
8
8
|
import { MatchAwareness } from "../../awareness.js";
|
|
9
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A mission that gets dispatched once, and once the squad decides to disband, the mission is disbanded.
|
|
@@ -13,8 +14,8 @@ import { MatchAwareness } from "../../awareness.js";
|
|
|
13
14
|
export abstract class OneTimeMission<T = undefined> extends Mission<T> {
|
|
14
15
|
private hadSquad = false;
|
|
15
16
|
|
|
16
|
-
constructor(uniqueName: string, priority: number, private behaviourFactory: () => SquadBehaviour) {
|
|
17
|
-
super(uniqueName, priority);
|
|
17
|
+
constructor(uniqueName: string, priority: number, private behaviourFactory: () => SquadBehaviour, logger: DebugLogger) {
|
|
18
|
+
super(uniqueName, priority, logger);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
onAiUpdate(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): MissionAction {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Point2D } from "@chronodivide/game-api";
|
|
2
2
|
import { OneTimeMission } from "./oneTimeMission.js";
|
|
3
3
|
import { RetreatSquad } from "../../squad/behaviours/retreatSquad.js";
|
|
4
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
4
5
|
|
|
5
6
|
export class RetreatMission extends OneTimeMission {
|
|
6
|
-
constructor(uniqueName: string, priority: number, retreatToPoint: Point2D, unitIds: number[]) {
|
|
7
|
-
super(uniqueName, priority, () => new RetreatSquad(unitIds, retreatToPoint));
|
|
7
|
+
constructor(uniqueName: string, priority: number, retreatToPoint: Point2D, unitIds: number[], logger: DebugLogger) {
|
|
8
|
+
super(uniqueName, priority, () => new RetreatSquad(unitIds, retreatToPoint), logger);
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -7,13 +7,15 @@ import { Mission } from "../mission.js";
|
|
|
7
7
|
import { AttackMission } from "./attackMission.js";
|
|
8
8
|
import { MissionController } from "../missionController.js";
|
|
9
9
|
import { getUnseenStartingLocations } from "../../common/scout.js";
|
|
10
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* A mission that tries to scout around the map with a cheap, fast unit (usually attack dogs)
|
|
13
14
|
*/
|
|
14
15
|
export class ScoutingMission extends OneTimeMission {
|
|
15
|
-
constructor(uniqueName: string, priority: number
|
|
16
|
-
|
|
16
|
+
constructor(uniqueName: string, priority: number,
|
|
17
|
+
logger: DebugLogger) {
|
|
18
|
+
super(uniqueName, priority, () => new ScoutingSquad(), logger);
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -31,15 +33,15 @@ export class ScoutingMissionFactory implements MissionFactory {
|
|
|
31
33
|
playerData: PlayerData,
|
|
32
34
|
matchAwareness: MatchAwareness,
|
|
33
35
|
missionController: MissionController,
|
|
36
|
+
logger: DebugLogger
|
|
34
37
|
): void {
|
|
35
38
|
if (gameApi.getCurrentTick() < this.lastScoutAt + SCOUT_COOLDOWN_TICKS) {
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
if (candidatePoints.length === 0) {
|
|
41
|
+
if (!matchAwareness.getScoutingManager().hasScoutTargets()) {
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
|
-
if (!missionController.addMission(new ScoutingMission("globalScout", 100))) {
|
|
44
|
+
if (!missionController.addMission(new ScoutingMission("globalScout", 100, logger))) {
|
|
43
45
|
this.lastScoutAt = gameApi.getCurrentTick();
|
|
44
46
|
}
|
|
45
47
|
}
|
|
@@ -51,9 +53,10 @@ export class ScoutingMissionFactory implements MissionFactory {
|
|
|
51
53
|
failedMission: Mission,
|
|
52
54
|
failureReason: undefined,
|
|
53
55
|
missionController: MissionController,
|
|
56
|
+
logger: DebugLogger
|
|
54
57
|
): void {
|
|
55
58
|
if (failedMission instanceof AttackMission) {
|
|
56
|
-
missionController.addMission(new ScoutingMission("globalScout", 100));
|
|
59
|
+
missionController.addMission(new ScoutingMission("globalScout", 100, logger));
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -5,6 +5,7 @@ import { SquadAction, SquadBehaviour, grabCombatants, noop } from "../squadBehav
|
|
|
5
5
|
import { MatchAwareness } from "../../awareness.js";
|
|
6
6
|
import { getDistanceBetweenPoints } from "../../map/map.js";
|
|
7
7
|
import { manageAttackMicro, manageMoveMicro } from "./common.js";
|
|
8
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
8
9
|
|
|
9
10
|
const TARGET_UPDATE_INTERVAL_TICKS = 10;
|
|
10
11
|
const GRAB_INTERVAL_TICKS = 10;
|
|
@@ -52,8 +53,9 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
52
53
|
playerData: PlayerData,
|
|
53
54
|
squad: Squad,
|
|
54
55
|
matchAwareness: MatchAwareness,
|
|
56
|
+
logger: DebugLogger,
|
|
55
57
|
): SquadAction {
|
|
56
|
-
if (!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS) {
|
|
58
|
+
if (squad.getUnitIds().length > 0 && (!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)) {
|
|
57
59
|
this.lastCommand = gameApi.getCurrentTick();
|
|
58
60
|
const centerOfMass = squad.getCenterOfMass();
|
|
59
61
|
const maxDistance = squad.getMaxDistanceToCenterOfMass();
|
|
@@ -81,6 +83,7 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
81
83
|
manageMoveMicro(actionsApi, unit, centerOfMass);
|
|
82
84
|
});
|
|
83
85
|
} else {
|
|
86
|
+
logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`)
|
|
84
87
|
this.state = SquadState.Attacking;
|
|
85
88
|
}
|
|
86
89
|
} else {
|
|
@@ -93,6 +96,7 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
93
96
|
maxDistance > requiredGatherRadius
|
|
94
97
|
) {
|
|
95
98
|
// Switch back to gather mode
|
|
99
|
+
logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`)
|
|
96
100
|
this.state = SquadState.Gathering;
|
|
97
101
|
return noop();
|
|
98
102
|
}
|
|
@@ -4,14 +4,30 @@ import { Squad } from "../squad.js";
|
|
|
4
4
|
import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
|
|
5
5
|
import { MatchAwareness } from "../../awareness.js";
|
|
6
6
|
import { getUnseenStartingLocations } from "../../common/scout.js";
|
|
7
|
+
import { match } from "assert";
|
|
8
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
9
|
+
import _ from "lodash";
|
|
10
|
+
import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
|
|
7
11
|
|
|
8
12
|
const SCOUT_MOVE_COOLDOWN_TICKS = 30;
|
|
9
13
|
|
|
14
|
+
// Max units to spend on a particular scout target.
|
|
15
|
+
const MAX_ATTEMPTS_PER_TARGET = 5;
|
|
16
|
+
|
|
17
|
+
// Maximum ticks to spend trying to scout a target *without making progress towards it*.
|
|
18
|
+
// Every time a unit gets closer to the target, the timer refreshes.
|
|
19
|
+
const MAX_TICKS_PER_TARGET = 600;
|
|
20
|
+
|
|
10
21
|
export class ScoutingSquad implements SquadBehaviour {
|
|
11
|
-
private
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
private scoutTarget: Point2D | null = null;
|
|
23
|
+
private attemptsOnCurrentTarget: number = 0;
|
|
24
|
+
private scoutTargetRefreshedAt: number = 0;
|
|
25
|
+
private lastMoveCommandTick: number = 0;
|
|
26
|
+
|
|
27
|
+
// Minimum distance from a scout to the target.
|
|
28
|
+
private scoutMinDistance?: number;
|
|
29
|
+
|
|
30
|
+
private hadUnit: boolean = false;
|
|
15
31
|
|
|
16
32
|
public onAiUpdate(
|
|
17
33
|
gameApi: GameApi,
|
|
@@ -19,6 +35,7 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
19
35
|
playerData: PlayerData,
|
|
20
36
|
squad: Squad,
|
|
21
37
|
matchAwareness: MatchAwareness,
|
|
38
|
+
logger: DebugLogger,
|
|
22
39
|
): SquadAction {
|
|
23
40
|
const scoutNames = ["ADOG", "DOG", "E1", "E2", "FV", "HTK"];
|
|
24
41
|
const scouts = squad.getUnitsOfTypes(gameApi, ...scoutNames);
|
|
@@ -28,29 +45,70 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
if (scouts.length === 0) {
|
|
31
|
-
|
|
48
|
+
// Count the number of times the scout dies trying to uncover the current scoutTarget.
|
|
49
|
+
if (this.scoutTarget && this.hadUnit) {
|
|
50
|
+
this.attemptsOnCurrentTarget++;
|
|
51
|
+
this.hadUnit = false;
|
|
52
|
+
}
|
|
32
53
|
return requestUnits(scoutNames, 100);
|
|
33
|
-
} else if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
} else if (this.scoutTarget) {
|
|
55
|
+
this.hadUnit = true;
|
|
56
|
+
if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
|
|
57
|
+
logger(
|
|
58
|
+
`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
|
|
59
|
+
);
|
|
60
|
+
this.setScoutTarget(null, 0);
|
|
61
|
+
return noop();
|
|
62
|
+
}
|
|
63
|
+
if (gameApi.getCurrentTick() > this.scoutTargetRefreshedAt + MAX_TICKS_PER_TARGET) {
|
|
64
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too long, moving to next`);
|
|
65
|
+
this.setScoutTarget(null, 0);
|
|
66
|
+
return noop();
|
|
67
|
+
}
|
|
68
|
+
const targetTile = gameApi.mapApi.getTile(this.scoutTarget.x, this.scoutTarget.y);
|
|
69
|
+
if (!targetTile) {
|
|
70
|
+
throw new Error(`target tile ${this.scoutTarget.x},${this.scoutTarget.y} does not exist`);
|
|
71
|
+
}
|
|
72
|
+
if (gameApi.getCurrentTick() > this.lastMoveCommandTick + SCOUT_MOVE_COOLDOWN_TICKS) {
|
|
73
|
+
this.lastMoveCommandTick = gameApi.getCurrentTick();
|
|
74
|
+
scouts.forEach((unit) => {
|
|
75
|
+
if (this.scoutTarget) {
|
|
76
|
+
actionsApi.orderUnits([unit.id], OrderType.AttackMove, this.scoutTarget.x, this.scoutTarget.y);
|
|
44
77
|
}
|
|
78
|
+
});
|
|
79
|
+
// Check that a scout is actually moving closer to the target.
|
|
80
|
+
const newMinDistance = _.min(
|
|
81
|
+
scouts.map((unit) =>
|
|
82
|
+
getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, this.scoutTarget!),
|
|
83
|
+
),
|
|
84
|
+
)!;
|
|
85
|
+
if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
|
|
86
|
+
logger(
|
|
87
|
+
`Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`,
|
|
88
|
+
);
|
|
89
|
+
this.scoutTargetRefreshedAt = gameApi.getCurrentTick();
|
|
90
|
+
this.scoutMinDistance = newMinDistance;
|
|
45
91
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
}
|
|
93
|
+
if (gameApi.mapApi.isVisibleTile(targetTile, playerData.name)) {
|
|
94
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} successfully scouted, moving to next`);
|
|
95
|
+
this.setScoutTarget(null, gameApi.getCurrentTick());
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
const candidatePoint = matchAwareness.getScoutingManager().getNewScoutTarget()?.asPoint2D();
|
|
99
|
+
if (!candidatePoint) {
|
|
100
|
+
logger(`No more scouting targets available, disbanding.`);
|
|
101
|
+
return disband();
|
|
102
|
+
}
|
|
103
|
+
this.setScoutTarget(candidatePoint, gameApi.getCurrentTick());
|
|
53
104
|
}
|
|
54
105
|
return noop();
|
|
55
106
|
}
|
|
107
|
+
|
|
108
|
+
setScoutTarget(point: Point2D | null, currentTick: number) {
|
|
109
|
+
this.attemptsOnCurrentTarget = 0;
|
|
110
|
+
this.scoutTargetRefreshedAt = currentTick;
|
|
111
|
+
this.scoutTarget = point;
|
|
112
|
+
this.scoutMinDistance = undefined;
|
|
113
|
+
}
|
|
56
114
|
}
|
|
@@ -5,6 +5,7 @@ import { SquadAction, SquadBehaviour, disband } from "./squadBehaviour.js";
|
|
|
5
5
|
import { MatchAwareness } from "../awareness.js";
|
|
6
6
|
import { getDistanceBetweenPoints } from "../map/map.js";
|
|
7
7
|
import _ from "lodash";
|
|
8
|
+
import { DebugLogger } from "../common/utils.js";
|
|
8
9
|
|
|
9
10
|
export enum SquadLiveness {
|
|
10
11
|
SquadDead,
|
|
@@ -76,6 +77,7 @@ export class Squad {
|
|
|
76
77
|
actionsApi: ActionsApi,
|
|
77
78
|
playerData: PlayerData,
|
|
78
79
|
matchAwareness: MatchAwareness,
|
|
80
|
+
logger: DebugLogger
|
|
79
81
|
): SquadAction {
|
|
80
82
|
this.updateLiveness(gameApi);
|
|
81
83
|
const movableUnitTiles = this.unitIds
|
|
@@ -100,8 +102,7 @@ export class Squad {
|
|
|
100
102
|
} else if (!this.mission) {
|
|
101
103
|
return disband();
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
return outcome;
|
|
105
|
+
return this.behaviour.onAiUpdate(gameApi, actionsApi, playerData, this, matchAwareness, logger);
|
|
105
106
|
}
|
|
106
107
|
public getMission(): Mission | null {
|
|
107
108
|
return this.mission;
|
|
@@ -2,6 +2,7 @@ import { ActionsApi, GameApi, PlayerData, Point2D } from "@chronodivide/game-api
|
|
|
2
2
|
import { GlobalThreat } from "../threat/threat.js";
|
|
3
3
|
import { Squad } from "./squad.js";
|
|
4
4
|
import { MatchAwareness } from "../awareness.js";
|
|
5
|
+
import { DebugLogger } from "../common/utils.js";
|
|
5
6
|
|
|
6
7
|
export type SquadActionNoop = {
|
|
7
8
|
type: "noop";
|
|
@@ -56,6 +57,7 @@ export interface SquadBehaviour {
|
|
|
56
57
|
actionsApi: ActionsApi,
|
|
57
58
|
playerData: PlayerData,
|
|
58
59
|
squad: Squad,
|
|
59
|
-
matchAwareness: MatchAwareness
|
|
60
|
+
matchAwareness: MatchAwareness,
|
|
61
|
+
logger: DebugLogger
|
|
60
62
|
): SquadAction;
|
|
61
63
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { MatchAwareness } from "../awareness.js";
|
|
15
15
|
import { getDistanceBetween } from "../map/map.js";
|
|
16
16
|
import _ from "lodash";
|
|
17
|
+
import { DebugLogger } from "../common/utils.js";
|
|
17
18
|
|
|
18
19
|
type SquadWithAction<T> = {
|
|
19
20
|
squad: Squad;
|
|
@@ -51,7 +52,7 @@ export class SquadController {
|
|
|
51
52
|
const squadActions: SquadWithAction<SquadAction>[] = this.squads.map((squad) => {
|
|
52
53
|
return {
|
|
53
54
|
squad,
|
|
54
|
-
action: squad.onAiUpdate(gameApi, actionsApi, playerData, matchAwareness),
|
|
55
|
+
action: squad.onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, this.logger),
|
|
55
56
|
};
|
|
56
57
|
});
|
|
57
58
|
// Handle disbands and merges.
|
|
@@ -105,21 +106,43 @@ export class SquadController {
|
|
|
105
106
|
},
|
|
106
107
|
{} as Record<number, SquadWithAction<SquadActionRequestSpecificUnits>>,
|
|
107
108
|
);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
|
|
110
|
+
// Map of Squad ID to Unit Type to Count.
|
|
111
|
+
const newSquadAssignments = Object.entries(unitIdToHighestRequest)
|
|
112
|
+
.flatMap(([id, request]) => {
|
|
113
|
+
const unitId = Number.parseInt(id);
|
|
114
|
+
const unit = gameApi.getUnitData(unitId);
|
|
115
|
+
const { squad: requestingSquad } = request;
|
|
116
|
+
const missionName = requestingSquad.getMission()?.getUniqueName();
|
|
117
|
+
if (!unit) {
|
|
118
|
+
this.logger(`mission ${missionName} requested non-existent unit ${unitId}`);
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
if (!this.unitIdToSquad.has(unitId)) {
|
|
122
|
+
this.addUnitToSquad(requestingSquad, unit);
|
|
123
|
+
return [{ unitName: unit?.name, squad: requestingSquad.getName() }];
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
})
|
|
127
|
+
.reduce(
|
|
128
|
+
(acc, curr) => {
|
|
129
|
+
if (!acc[curr.squad]) {
|
|
130
|
+
acc[curr.squad] = {};
|
|
131
|
+
}
|
|
132
|
+
if (!acc[curr.squad][curr.unitName]) {
|
|
133
|
+
acc[curr.squad][curr.unitName] = 0;
|
|
134
|
+
}
|
|
135
|
+
acc[curr.squad][curr.unitName] = acc[curr.squad][curr.unitName] + 1;
|
|
136
|
+
return acc;
|
|
137
|
+
},
|
|
138
|
+
{} as Record<string, Record<string, number>>,
|
|
139
|
+
);
|
|
140
|
+
Object.entries(newSquadAssignments).forEach(([squad, assignments]) => {
|
|
141
|
+
this.logger(
|
|
142
|
+
`Squad ${squad} received: ${Object.entries(assignments)
|
|
143
|
+
.map(([unitType, count]) => unitType + " x " + count)
|
|
144
|
+
.join(", ")}`,
|
|
145
|
+
);
|
|
123
146
|
});
|
|
124
147
|
|
|
125
148
|
// Request units by type
|
|
@@ -157,33 +180,57 @@ export class SquadController {
|
|
|
157
180
|
.filter((unit) => !!unit && !this.unitIdToSquad.has(unit.id || 0))
|
|
158
181
|
.map((unit) => unit!);
|
|
159
182
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
freeUnit.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
request.action.point.y
|
|
178
|
-
}`,
|
|
183
|
+
type AssignmentWithType = { unitName: string; squad: string; method: "type" | "grab" };
|
|
184
|
+
// [squadName][unitName]['type' | 'grab']
|
|
185
|
+
const newAssignmentsByType = freeUnits
|
|
186
|
+
.flatMap((freeUnit) => {
|
|
187
|
+
if (unitTypeToHighestRequest.hasOwnProperty(freeUnit.name)) {
|
|
188
|
+
const { squad: requestingSquad } = unitTypeToHighestRequest[freeUnit.name];
|
|
189
|
+
this.logger(`granting unit ${freeUnit.id}#${freeUnit.name} to squad ${requestingSquad.getName()}`);
|
|
190
|
+
this.addUnitToSquad(requestingSquad, freeUnit);
|
|
191
|
+
delete unitTypeToHighestRequest[freeUnit.name];
|
|
192
|
+
return [
|
|
193
|
+
{ unitName: freeUnit.name, squad: requestingSquad.getName(), method: "type" },
|
|
194
|
+
] as AssignmentWithType[];
|
|
195
|
+
} else if (grabRequests.length > 0) {
|
|
196
|
+
const grantedSquad = grabRequests.find((request) => {
|
|
197
|
+
return (
|
|
198
|
+
freeUnit.rules.isSelectableCombatant &&
|
|
199
|
+
getDistanceBetween(freeUnit, request.action.point) <= request.action.radius
|
|
179
200
|
);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return
|
|
201
|
+
});
|
|
202
|
+
if (grantedSquad) {
|
|
203
|
+
this.addUnitToSquad(grantedSquad.squad, freeUnit);
|
|
204
|
+
return [
|
|
205
|
+
{ unitName: freeUnit.name, squad: grantedSquad.squad.getName(), method: "grab" },
|
|
206
|
+
] as AssignmentWithType[];
|
|
184
207
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
})
|
|
211
|
+
.reduce(
|
|
212
|
+
(acc, curr) => {
|
|
213
|
+
if (!acc[curr.squad]) {
|
|
214
|
+
acc[curr.squad] = {};
|
|
215
|
+
}
|
|
216
|
+
if (!acc[curr.squad][curr.unitName]) {
|
|
217
|
+
acc[curr.squad][curr.unitName] = { grab: 0, type: 0 };
|
|
218
|
+
}
|
|
219
|
+
acc[curr.squad][curr.unitName][curr.method] = acc[curr.squad][curr.unitName][curr.method] + 1;
|
|
220
|
+
return acc;
|
|
221
|
+
},
|
|
222
|
+
{} as Record<string, Record<string, Record<"type" | "grab", number>>>,
|
|
223
|
+
);
|
|
224
|
+
Object.entries(newAssignmentsByType).forEach(([squad, assignments]) => {
|
|
225
|
+
this.logger(
|
|
226
|
+
`Squad ${squad} received: ${Object.entries(assignments)
|
|
227
|
+
.flatMap(([unitType, methodToCount]) =>
|
|
228
|
+
Object.entries(methodToCount)
|
|
229
|
+
.filter(([, count]) => count > 0)
|
|
230
|
+
.map(([method, count]) => unitType + " x " + count + " (by " + method + ")"),
|
|
231
|
+
)
|
|
232
|
+
.join(", ")}`,
|
|
233
|
+
);
|
|
187
234
|
});
|
|
188
235
|
}
|
|
189
236
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
-
|
|
3
|
-
export class GlobalThreat {
|
|
4
|
-
constructor(
|
|
5
|
-
public certainty: number, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
6
|
-
public totalOffensiveLandThreat: number, // a number that approximates how much land-based firepower our opponents have.
|
|
7
|
-
public totalOffensiveAirThreat: number, // a number that approximates how much airborne firepower our opponents have.
|
|
8
|
-
public totalOffensiveAntiAirThreat: number, // a number that approximates how much anti-air firepower our opponents have.
|
|
9
|
-
public totalDefensiveThreat: number, // a number that approximates how much defensive power our opponents have.
|
|
10
|
-
public totalDefensivePower: number, // a number that approximates how much defensive power we have.
|
|
11
|
-
public totalAvailableAntiGroundFirepower: number, // how much anti-ground power we have
|
|
12
|
-
public totalAvailableAntiAirFirepower: number, // how much anti-air power we have
|
|
13
|
-
public totalAvailableAirPower: number, // how much firepower we have in air units
|
|
14
|
-
) {}
|
|
15
|
-
}
|
|
1
|
+
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
+
|
|
3
|
+
export class GlobalThreat {
|
|
4
|
+
constructor(
|
|
5
|
+
public certainty: number, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
6
|
+
public totalOffensiveLandThreat: number, // a number that approximates how much land-based firepower our opponents have.
|
|
7
|
+
public totalOffensiveAirThreat: number, // a number that approximates how much airborne firepower our opponents have.
|
|
8
|
+
public totalOffensiveAntiAirThreat: number, // a number that approximates how much anti-air firepower our opponents have.
|
|
9
|
+
public totalDefensiveThreat: number, // a number that approximates how much defensive power our opponents have.
|
|
10
|
+
public totalDefensivePower: number, // a number that approximates how much defensive power we have.
|
|
11
|
+
public totalAvailableAntiGroundFirepower: number, // how much anti-ground power we have
|
|
12
|
+
public totalAvailableAntiAirFirepower: number, // how much anti-air power we have
|
|
13
|
+
public totalAvailableAirPower: number, // how much firepower we have in air units
|
|
14
|
+
) {}
|
|
15
|
+
}
|