@supalosa/chronodivide-bot 0.2.0 → 0.2.2-a
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/README.md +11 -3
- package/dist/bot/bot.js +17 -9
- 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/building/ArtilleryUnit.js +29 -8
- package/dist/bot/logic/building/antiGroundStaticDefence.js +3 -2
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +2 -1
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
- package/dist/bot/logic/building/basicBuilding.js +2 -1
- package/dist/bot/logic/building/basicBuilding.js.map +1 -0
- package/dist/bot/logic/building/basicGroundUnit.js +2 -1
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
- package/dist/bot/logic/building/building.js +2 -0
- package/dist/bot/logic/building/buildingRules.js +168 -0
- package/dist/bot/logic/building/buildingRules.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 +2 -1
- package/dist/bot/logic/building/powerPlant.js.map +1 -0
- package/dist/bot/logic/building/queueController.js +2 -1
- package/dist/bot/logic/building/queueController.js.map +1 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js +2 -1
- 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 +14 -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 +5 -4
- package/dist/bot/logic/mission/missionController.js.map +1 -0
- package/dist/bot/logic/mission/missionFactories.js +3 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -0
- package/dist/bot/logic/mission/missions/attackMission.js +9 -10
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +12 -7
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
- package/dist/bot/logic/mission/missions/engineerMission.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/combatSquad.js +20 -18
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/common.js +25 -5
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
- 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 +6 -10
- 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 +4 -5
- 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 +73 -23
- 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 +29 -12
- package/dist/exampleBot.js.map +1 -0
- package/package.json +15 -7
- package/src/bot/bot.ts +21 -16
- package/src/bot/logic/awareness.ts +22 -5
- package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
- package/src/bot/logic/building/artilleryUnit.ts +68 -0
- package/src/bot/logic/building/basicAirUnit.ts +68 -68
- package/src/bot/logic/building/basicBuilding.ts +47 -47
- package/src/bot/logic/building/basicGroundUnit.ts +1 -1
- package/src/bot/logic/building/buildingRules.ts +233 -0
- package/src/bot/logic/building/powerPlant.ts +32 -32
- package/src/bot/logic/building/queueController.ts +1 -1
- 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 +17 -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 +7 -6
- package/src/bot/logic/mission/missionFactories.ts +5 -0
- package/src/bot/logic/mission/missions/attackMission.ts +19 -12
- package/src/bot/logic/mission/missions/defenceMission.ts +35 -15
- package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
- 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 +22 -18
- package/src/bot/logic/squad/behaviours/common.ts +38 -5
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +10 -12
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +79 -26
- package/src/bot/logic/squad/squad.ts +4 -4
- package/src/bot/logic/squad/squadBehaviour.ts +3 -1
- package/src/bot/logic/squad/squadController.ts +135 -70
- package/src/exampleBot.ts +31 -12
- package/tsconfig.json +73 -73
- package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
- package/src/bot/logic/building/building.ts +0 -125
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, OrderType, PlayerData, Point2D
|
|
2
|
-
import { GlobalThreat } from "../../threat/threat.js";
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData, Point2D } from "@chronodivide/game-api";
|
|
3
2
|
import { Squad } from "../squad.js";
|
|
4
3
|
import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
|
|
5
4
|
import { MatchAwareness } from "../../awareness.js";
|
|
6
|
-
import {
|
|
5
|
+
import { DebugLogger } from "../../common/utils.js";
|
|
6
|
+
import { getDistanceBetweenPoints } from "../../map/map.js";
|
|
7
7
|
|
|
8
8
|
const SCOUT_MOVE_COOLDOWN_TICKS = 30;
|
|
9
9
|
|
|
10
|
+
// Max units to spend on a particular scout target.
|
|
11
|
+
const MAX_ATTEMPTS_PER_TARGET = 5;
|
|
12
|
+
|
|
13
|
+
// Maximum ticks to spend trying to scout a target *without making progress towards it*.
|
|
14
|
+
// Every time a unit gets closer to the target, the timer refreshes.
|
|
15
|
+
const MAX_TICKS_PER_TARGET = 600;
|
|
16
|
+
|
|
10
17
|
export class ScoutingSquad implements SquadBehaviour {
|
|
11
|
-
private
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
private scoutTarget: Point2D | null = null;
|
|
19
|
+
private attemptsOnCurrentTarget: number = 0;
|
|
20
|
+
private scoutTargetRefreshedAt: number = 0;
|
|
21
|
+
private lastMoveCommandTick: number = 0;
|
|
22
|
+
|
|
23
|
+
// Minimum distance from a scout to the target.
|
|
24
|
+
private scoutMinDistance?: number;
|
|
25
|
+
|
|
26
|
+
private hadUnit: boolean = false;
|
|
15
27
|
|
|
16
28
|
public onAiUpdate(
|
|
17
29
|
gameApi: GameApi,
|
|
@@ -19,6 +31,7 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
19
31
|
playerData: PlayerData,
|
|
20
32
|
squad: Squad,
|
|
21
33
|
matchAwareness: MatchAwareness,
|
|
34
|
+
logger: DebugLogger,
|
|
22
35
|
): SquadAction {
|
|
23
36
|
const scoutNames = ["ADOG", "DOG", "E1", "E2", "FV", "HTK"];
|
|
24
37
|
const scouts = squad.getUnitsOfTypes(gameApi, ...scoutNames);
|
|
@@ -28,29 +41,69 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
if (scouts.length === 0) {
|
|
31
|
-
|
|
44
|
+
// Count the number of times the scout dies trying to uncover the current scoutTarget.
|
|
45
|
+
if (this.scoutTarget && this.hadUnit) {
|
|
46
|
+
this.attemptsOnCurrentTarget++;
|
|
47
|
+
this.hadUnit = false;
|
|
48
|
+
}
|
|
32
49
|
return requestUnits(scoutNames, 100);
|
|
33
|
-
} else if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
} else if (this.scoutTarget) {
|
|
51
|
+
this.hadUnit = true;
|
|
52
|
+
if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
|
|
53
|
+
logger(
|
|
54
|
+
`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
|
|
55
|
+
);
|
|
56
|
+
this.setScoutTarget(null, 0);
|
|
57
|
+
return noop();
|
|
58
|
+
}
|
|
59
|
+
if (gameApi.getCurrentTick() > this.scoutTargetRefreshedAt + MAX_TICKS_PER_TARGET) {
|
|
60
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too long, moving to next`);
|
|
61
|
+
this.setScoutTarget(null, 0);
|
|
62
|
+
return noop();
|
|
63
|
+
}
|
|
64
|
+
const targetTile = gameApi.mapApi.getTile(this.scoutTarget.x, this.scoutTarget.y);
|
|
65
|
+
if (!targetTile) {
|
|
66
|
+
throw new Error(`target tile ${this.scoutTarget.x},${this.scoutTarget.y} does not exist`);
|
|
67
|
+
}
|
|
68
|
+
if (gameApi.getCurrentTick() > this.lastMoveCommandTick + SCOUT_MOVE_COOLDOWN_TICKS) {
|
|
69
|
+
this.lastMoveCommandTick = gameApi.getCurrentTick();
|
|
70
|
+
scouts.forEach((unit) => {
|
|
71
|
+
if (this.scoutTarget) {
|
|
72
|
+
actionsApi.orderUnits([unit.id], OrderType.AttackMove, this.scoutTarget.x, this.scoutTarget.y);
|
|
44
73
|
}
|
|
74
|
+
});
|
|
75
|
+
// Check that a scout is actually moving closer to the target.
|
|
76
|
+
const distances = scouts.map((unit) =>
|
|
77
|
+
getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, this.scoutTarget!),
|
|
78
|
+
);
|
|
79
|
+
const newMinDistance = Math.min(...distances);
|
|
80
|
+
if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
|
|
81
|
+
logger(
|
|
82
|
+
`Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`,
|
|
83
|
+
);
|
|
84
|
+
this.scoutTargetRefreshedAt = gameApi.getCurrentTick();
|
|
85
|
+
this.scoutMinDistance = newMinDistance;
|
|
45
86
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
}
|
|
88
|
+
if (gameApi.mapApi.isVisibleTile(targetTile, playerData.name)) {
|
|
89
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} successfully scouted, moving to next`);
|
|
90
|
+
this.setScoutTarget(null, gameApi.getCurrentTick());
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
const candidatePoint = matchAwareness.getScoutingManager().getNewScoutTarget()?.asPoint2D();
|
|
94
|
+
if (!candidatePoint) {
|
|
95
|
+
logger(`No more scouting targets available, disbanding.`);
|
|
96
|
+
return disband();
|
|
97
|
+
}
|
|
98
|
+
this.setScoutTarget(candidatePoint, gameApi.getCurrentTick());
|
|
53
99
|
}
|
|
54
100
|
return noop();
|
|
55
101
|
}
|
|
102
|
+
|
|
103
|
+
setScoutTarget(point: Point2D | null, currentTick: number) {
|
|
104
|
+
this.attemptsOnCurrentTarget = 0;
|
|
105
|
+
this.scoutTargetRefreshedAt = currentTick;
|
|
106
|
+
this.scoutTarget = point;
|
|
107
|
+
this.scoutMinDistance = undefined;
|
|
108
|
+
}
|
|
56
109
|
}
|
|
@@ -4,7 +4,7 @@ import { GlobalThreat } from "../threat/threat.js";
|
|
|
4
4
|
import { SquadAction, SquadBehaviour, disband } from "./squadBehaviour.js";
|
|
5
5
|
import { MatchAwareness } from "../awareness.js";
|
|
6
6
|
import { getDistanceBetweenPoints } from "../map/map.js";
|
|
7
|
-
import
|
|
7
|
+
import { DebugLogger } from "../common/utils.js";
|
|
8
8
|
|
|
9
9
|
export enum SquadLiveness {
|
|
10
10
|
SquadDead,
|
|
@@ -41,7 +41,7 @@ const calculateCenterOfMass: (unitTiles: Tile[]) => {
|
|
|
41
41
|
|
|
42
42
|
// max distance of units to the center of mass
|
|
43
43
|
const distances = unitTiles.map((tile) => getDistanceBetweenPoints({ x: tile.rx, y: tile.ry }, centerOfMass));
|
|
44
|
-
const maxDistance =
|
|
44
|
+
const maxDistance = Math.max(...distances);
|
|
45
45
|
return { centerOfMass, maxDistance };
|
|
46
46
|
};
|
|
47
47
|
|
|
@@ -76,6 +76,7 @@ export class Squad {
|
|
|
76
76
|
actionsApi: ActionsApi,
|
|
77
77
|
playerData: PlayerData,
|
|
78
78
|
matchAwareness: MatchAwareness,
|
|
79
|
+
logger: DebugLogger,
|
|
79
80
|
): SquadAction {
|
|
80
81
|
this.updateLiveness(gameApi);
|
|
81
82
|
const movableUnitTiles = this.unitIds
|
|
@@ -100,8 +101,7 @@ export class Squad {
|
|
|
100
101
|
} else if (!this.mission) {
|
|
101
102
|
return disband();
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
return outcome;
|
|
104
|
+
return this.behaviour.onAiUpdate(gameApi, actionsApi, playerData, this, matchAwareness, logger);
|
|
105
105
|
}
|
|
106
106
|
public getMission(): Mission | null {
|
|
107
107
|
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
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Meta-controller for forming and controlling squads.
|
|
2
2
|
|
|
3
3
|
import { ActionsApi, GameApi, PlayerData, UnitData } from "@chronodivide/game-api";
|
|
4
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
5
4
|
import { Squad, SquadLiveness } from "./squad.js";
|
|
6
5
|
import {
|
|
7
6
|
SquadAction,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
} from "./squadBehaviour.js";
|
|
14
13
|
import { MatchAwareness } from "../awareness.js";
|
|
15
14
|
import { getDistanceBetween } from "../map/map.js";
|
|
15
|
+
import countBy from "lodash.countby";
|
|
16
16
|
|
|
17
17
|
type SquadWithAction<T> = {
|
|
18
18
|
squad: Squad;
|
|
@@ -23,14 +23,13 @@ export class SquadController {
|
|
|
23
23
|
private squads: Squad[] = [];
|
|
24
24
|
private unitIdToSquad: Map<number, Squad> = new Map();
|
|
25
25
|
|
|
26
|
-
constructor() {}
|
|
26
|
+
constructor(private logger: (message: string, sayInGame?: boolean) => void) {}
|
|
27
27
|
|
|
28
28
|
public onAiUpdate(
|
|
29
29
|
gameApi: GameApi,
|
|
30
30
|
actionsApi: ActionsApi,
|
|
31
31
|
playerData: PlayerData,
|
|
32
32
|
matchAwareness: MatchAwareness,
|
|
33
|
-
logger: (message: string) => void
|
|
34
33
|
) {
|
|
35
34
|
// Remove dead squads or those where the mission is dead.
|
|
36
35
|
this.squads = this.squads.filter((squad) => squad.getLiveness() !== SquadLiveness.SquadDead);
|
|
@@ -41,7 +40,7 @@ export class SquadController {
|
|
|
41
40
|
this.squads.forEach((squad) => {
|
|
42
41
|
squad.getUnitIds().forEach((unitId) => {
|
|
43
42
|
if (this.unitIdToSquad.has(unitId)) {
|
|
44
|
-
logger(`WARNING: unit ${unitId} is in multiple squads, please debug.`);
|
|
43
|
+
this.logger(`WARNING: unit ${unitId} is in multiple squads, please debug.`);
|
|
45
44
|
} else {
|
|
46
45
|
this.unitIdToSquad.set(unitId, squad);
|
|
47
46
|
}
|
|
@@ -51,7 +50,7 @@ export class SquadController {
|
|
|
51
50
|
const squadActions: SquadWithAction<SquadAction>[] = this.squads.map((squad) => {
|
|
52
51
|
return {
|
|
53
52
|
squad,
|
|
54
|
-
action: squad.onAiUpdate(gameApi, actionsApi, playerData, matchAwareness),
|
|
53
|
+
action: squad.onAiUpdate(gameApi, actionsApi, playerData, matchAwareness, this.logger),
|
|
55
54
|
};
|
|
56
55
|
});
|
|
57
56
|
// Handle disbands and merges.
|
|
@@ -61,7 +60,7 @@ export class SquadController {
|
|
|
61
60
|
squadActions
|
|
62
61
|
.filter((a) => isDisband(a.action))
|
|
63
62
|
.forEach((a) => {
|
|
64
|
-
logger(`Squad ${a.squad.getName()} disbanding as requested.`);
|
|
63
|
+
this.logger(`Squad ${a.squad.getName()} disbanding as requested.`);
|
|
65
64
|
a.squad.getMission()?.removeSquad();
|
|
66
65
|
a.squad.getUnitIds().forEach((unitId) => {
|
|
67
66
|
this.unitIdToSquad.delete(unitId);
|
|
@@ -73,8 +72,8 @@ export class SquadController {
|
|
|
73
72
|
.forEach((a) => {
|
|
74
73
|
let mergeInto = a.action as SquadActionMergeInto;
|
|
75
74
|
if (disbandedSquads.has(mergeInto.mergeInto.getName())) {
|
|
76
|
-
logger(
|
|
77
|
-
`Squad ${a.squad.getName()} tried to merge into disbanded squad ${mergeInto.mergeInto.getName()}, cancelling
|
|
75
|
+
this.logger(
|
|
76
|
+
`Squad ${a.squad.getName()} tried to merge into disbanded squad ${mergeInto.mergeInto.getName()}, cancelling.`,
|
|
78
77
|
);
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
@@ -88,58 +87,88 @@ export class SquadController {
|
|
|
88
87
|
const isRequestSpecific = (a: SquadAction) => a.type === "requestSpecific";
|
|
89
88
|
const unitIdToHighestRequest = squadActions
|
|
90
89
|
.filter((a) => isRequestSpecific(a.action))
|
|
91
|
-
.reduce(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (prev
|
|
90
|
+
.reduce(
|
|
91
|
+
(prev, a) => {
|
|
92
|
+
const squadWithAction = a as SquadWithAction<SquadActionRequestSpecificUnits>;
|
|
93
|
+
const { unitIds } = squadWithAction.action;
|
|
94
|
+
unitIds.forEach((unitId) => {
|
|
95
|
+
if (prev.hasOwnProperty(unitId)) {
|
|
96
|
+
if (prev[unitId].action.priority > prev[unitId].action.priority) {
|
|
97
|
+
prev[unitId] = squadWithAction;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
97
100
|
prev[unitId] = squadWithAction;
|
|
98
101
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
102
|
+
});
|
|
103
|
+
return prev;
|
|
104
|
+
},
|
|
105
|
+
{} as Record<number, SquadWithAction<SquadActionRequestSpecificUnits>>,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Map of Squad ID to Unit Type to Count.
|
|
109
|
+
const newSquadAssignments = Object.entries(unitIdToHighestRequest)
|
|
110
|
+
.flatMap(([id, request]) => {
|
|
111
|
+
const unitId = Number.parseInt(id);
|
|
112
|
+
const unit = gameApi.getUnitData(unitId);
|
|
113
|
+
const { squad: requestingSquad } = request;
|
|
114
|
+
const missionName = requestingSquad.getMission()?.getUniqueName();
|
|
115
|
+
if (!unit) {
|
|
116
|
+
this.logger(`mission ${missionName} requested non-existent unit ${unitId}`);
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
if (!this.unitIdToSquad.has(unitId)) {
|
|
120
|
+
this.addUnitToSquad(requestingSquad, unit);
|
|
121
|
+
return [{ unitName: unit?.name, squad: requestingSquad.getName() }];
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
})
|
|
125
|
+
.reduce(
|
|
126
|
+
(acc, curr) => {
|
|
127
|
+
if (!acc[curr.squad]) {
|
|
128
|
+
acc[curr.squad] = {};
|
|
101
129
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.addUnitToSquad(requestingSquad, unit);
|
|
117
|
-
}
|
|
130
|
+
if (!acc[curr.squad][curr.unitName]) {
|
|
131
|
+
acc[curr.squad][curr.unitName] = 0;
|
|
132
|
+
}
|
|
133
|
+
acc[curr.squad][curr.unitName] = acc[curr.squad][curr.unitName] + 1;
|
|
134
|
+
return acc;
|
|
135
|
+
},
|
|
136
|
+
{} as Record<string, Record<string, number>>,
|
|
137
|
+
);
|
|
138
|
+
Object.entries(newSquadAssignments).forEach(([squad, assignments]) => {
|
|
139
|
+
this.logger(
|
|
140
|
+
`Squad ${squad} received: ${Object.entries(assignments)
|
|
141
|
+
.map(([unitType, count]) => unitType + " x " + count)
|
|
142
|
+
.join(", ")}`,
|
|
143
|
+
);
|
|
118
144
|
});
|
|
119
145
|
|
|
120
146
|
// Request units by type
|
|
121
147
|
const isRequest = (a: SquadAction) => a.type === "request";
|
|
122
148
|
const unitTypeToHighestRequest = squadActions
|
|
123
149
|
.filter((a) => isRequest(a.action))
|
|
124
|
-
.reduce(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (prev
|
|
150
|
+
.reduce(
|
|
151
|
+
(prev, a) => {
|
|
152
|
+
const squadWithAction = a as SquadWithAction<SquadActionRequestUnits>;
|
|
153
|
+
const { unitNames } = squadWithAction.action;
|
|
154
|
+
unitNames.forEach((unitName) => {
|
|
155
|
+
if (prev.hasOwnProperty(unitName)) {
|
|
156
|
+
if (prev[unitName].action.priority > prev[unitName].action.priority) {
|
|
157
|
+
prev[unitName] = squadWithAction;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
130
160
|
prev[unitName] = squadWithAction;
|
|
131
161
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
}, {} as Record<string, SquadWithAction<SquadActionRequestUnits>>);
|
|
162
|
+
});
|
|
163
|
+
return prev;
|
|
164
|
+
},
|
|
165
|
+
{} as Record<string, SquadWithAction<SquadActionRequestUnits>>,
|
|
166
|
+
);
|
|
138
167
|
|
|
139
168
|
// Request combat-capable units in an area
|
|
140
169
|
const isGrab = (a: SquadAction) => a.type === "requestCombatants";
|
|
141
170
|
const grabRequests = squadActions.filter((a) =>
|
|
142
|
-
isGrab(a.action)
|
|
171
|
+
isGrab(a.action),
|
|
143
172
|
) as SquadWithAction<SquadActionGrabFreeCombatants>[];
|
|
144
173
|
|
|
145
174
|
// Find loose units
|
|
@@ -149,33 +178,57 @@ export class SquadController {
|
|
|
149
178
|
.filter((unit) => !!unit && !this.unitIdToSquad.has(unit.id || 0))
|
|
150
179
|
.map((unit) => unit!);
|
|
151
180
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
freeUnit.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
request.action.point.y
|
|
170
|
-
}`
|
|
181
|
+
type AssignmentWithType = { unitName: string; squad: string; method: "type" | "grab" };
|
|
182
|
+
// [squadName][unitName]['type' | 'grab']
|
|
183
|
+
const newAssignmentsByType = freeUnits
|
|
184
|
+
.flatMap((freeUnit) => {
|
|
185
|
+
if (unitTypeToHighestRequest.hasOwnProperty(freeUnit.name)) {
|
|
186
|
+
const { squad: requestingSquad } = unitTypeToHighestRequest[freeUnit.name];
|
|
187
|
+
this.logger(`granting unit ${freeUnit.id}#${freeUnit.name} to squad ${requestingSquad.getName()}`);
|
|
188
|
+
this.addUnitToSquad(requestingSquad, freeUnit);
|
|
189
|
+
delete unitTypeToHighestRequest[freeUnit.name];
|
|
190
|
+
return [
|
|
191
|
+
{ unitName: freeUnit.name, squad: requestingSquad.getName(), method: "type" },
|
|
192
|
+
] as AssignmentWithType[];
|
|
193
|
+
} else if (grabRequests.length > 0) {
|
|
194
|
+
const grantedSquad = grabRequests.find((request) => {
|
|
195
|
+
return (
|
|
196
|
+
freeUnit.rules.isSelectableCombatant &&
|
|
197
|
+
getDistanceBetween(freeUnit, request.action.point) <= request.action.radius
|
|
171
198
|
);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return
|
|
199
|
+
});
|
|
200
|
+
if (grantedSquad) {
|
|
201
|
+
this.addUnitToSquad(grantedSquad.squad, freeUnit);
|
|
202
|
+
return [
|
|
203
|
+
{ unitName: freeUnit.name, squad: grantedSquad.squad.getName(), method: "grab" },
|
|
204
|
+
] as AssignmentWithType[];
|
|
176
205
|
}
|
|
177
|
-
}
|
|
178
|
-
|
|
206
|
+
}
|
|
207
|
+
return [];
|
|
208
|
+
})
|
|
209
|
+
.reduce(
|
|
210
|
+
(acc, curr) => {
|
|
211
|
+
if (!acc[curr.squad]) {
|
|
212
|
+
acc[curr.squad] = {};
|
|
213
|
+
}
|
|
214
|
+
if (!acc[curr.squad][curr.unitName]) {
|
|
215
|
+
acc[curr.squad][curr.unitName] = { grab: 0, type: 0 };
|
|
216
|
+
}
|
|
217
|
+
acc[curr.squad][curr.unitName][curr.method] = acc[curr.squad][curr.unitName][curr.method] + 1;
|
|
218
|
+
return acc;
|
|
219
|
+
},
|
|
220
|
+
{} as Record<string, Record<string, Record<"type" | "grab", number>>>,
|
|
221
|
+
);
|
|
222
|
+
Object.entries(newAssignmentsByType).forEach(([squad, assignments]) => {
|
|
223
|
+
this.logger(
|
|
224
|
+
`Squad ${squad} received: ${Object.entries(assignments)
|
|
225
|
+
.flatMap(([unitType, methodToCount]) =>
|
|
226
|
+
Object.entries(methodToCount)
|
|
227
|
+
.filter(([, count]) => count > 0)
|
|
228
|
+
.map(([method, count]) => unitType + " x " + count + " (by " + method + ")"),
|
|
229
|
+
)
|
|
230
|
+
.join(", ")}`,
|
|
231
|
+
);
|
|
179
232
|
});
|
|
180
233
|
}
|
|
181
234
|
|
|
@@ -187,4 +240,16 @@ export class SquadController {
|
|
|
187
240
|
public registerSquad(squad: Squad) {
|
|
188
241
|
this.squads.push(squad);
|
|
189
242
|
}
|
|
243
|
+
|
|
244
|
+
public debugSquads(gameApi: GameApi) {
|
|
245
|
+
const unitsInSquad = (unitIds: number[]) => countBy(unitIds, (unitId) => gameApi.getUnitData(unitId)?.name);
|
|
246
|
+
|
|
247
|
+
this.squads.forEach((squad) => {
|
|
248
|
+
this.logger(
|
|
249
|
+
`Squad ${squad.getName()}: ${Object.entries(unitsInSquad(squad.getUnitIds()))
|
|
250
|
+
.map(([unitName, count]) => `${unitName} x ${count}`)
|
|
251
|
+
.join(", ")}`,
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
190
255
|
}
|
package/src/exampleBot.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cdapi } from "@chronodivide/game-api";
|
|
1
|
+
import { Agent, Bot, cdapi } from "@chronodivide/game-api";
|
|
2
2
|
import { SupalosaBot } from "./bot/bot.js";
|
|
3
3
|
|
|
4
4
|
async function main() {
|
|
@@ -6,17 +6,17 @@ async function main() {
|
|
|
6
6
|
Ladder maps:
|
|
7
7
|
CDR2 1v1 2_malibu_cliffs_le.map
|
|
8
8
|
CDR2 1v1 4_country_swing_le_v2.map
|
|
9
|
-
CDR2 1v1 mp01t4.map
|
|
10
|
-
CDR2 1v1 tn04t2.map
|
|
11
|
-
CDR2 1v1 mp10s4.map
|
|
9
|
+
CDR2 1v1 mp01t4.map, large map, oil derricks
|
|
10
|
+
CDR2 1v1 tn04t2.map, small map
|
|
11
|
+
CDR2 1v1 mp10s4.map <- depth charge, naval map (not supported)
|
|
12
12
|
CDR2 1v1 heckcorners.map
|
|
13
13
|
CDR2 1v1 4_montana_dmz_le.map
|
|
14
14
|
CDR2 1v1 barrel.map
|
|
15
15
|
|
|
16
16
|
Other maps:
|
|
17
|
-
mp03t4
|
|
17
|
+
mp03t4 large map, no oil derricks
|
|
18
18
|
*/
|
|
19
|
-
const mapName = "
|
|
19
|
+
const mapName = "tn04t2.map";
|
|
20
20
|
// Bot names must be unique in online mode
|
|
21
21
|
const botName = `Joe${String(Date.now()).substr(-6)}`;
|
|
22
22
|
const otherBotName = `Bob${String(Date.now() + 1).substr(-6)}`;
|
|
@@ -26,14 +26,33 @@ async function main() {
|
|
|
26
26
|
console.log("Server URL: " + process.env.SERVER_URL!);
|
|
27
27
|
console.log("Client URL: " + process.env.CLIENT_URL!);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/*
|
|
30
|
+
Countries:
|
|
31
|
+
0=Americans
|
|
32
|
+
1=Alliance -> Korea
|
|
33
|
+
2=French
|
|
34
|
+
3=Germans
|
|
35
|
+
4=British
|
|
36
|
+
|
|
37
|
+
5=Africans -> Libya
|
|
38
|
+
6=Arabs -> Iraq
|
|
39
|
+
7=Confederation -> Cuba
|
|
40
|
+
8=Russians
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const onlineSettings = {
|
|
44
|
+
online: true as true,
|
|
32
45
|
serverUrl: process.env.SERVER_URL!,
|
|
33
46
|
clientUrl: process.env.CLIENT_URL!,
|
|
34
|
-
agents: [new SupalosaBot(botName, "Americans"), { name: otherBotName, country: "French" }]
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
agents: [new SupalosaBot(botName, "Americans"), { name: otherBotName, country: "French" }] as [Bot, ...Agent[]],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const offlineSettings = {
|
|
51
|
+
agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "Russians", true)],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const game = await cdapi.createGame({
|
|
55
|
+
...offlineSettings,
|
|
37
56
|
buildOffAlly: false,
|
|
38
57
|
cratesAppear: false,
|
|
39
58
|
credits: 10000,
|