@supalosa/chronodivide-bot 0.1.1 → 0.2.0
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 +71 -46
- package/dist/bot/bot.js +27 -183
- package/dist/bot/logic/awareness.js +122 -0
- package/dist/bot/logic/building/basicGroundUnit.js +8 -6
- package/dist/bot/logic/building/building.js +6 -3
- package/dist/bot/logic/building/harvester.js +1 -1
- package/dist/bot/logic/building/queueController.js +4 -21
- package/dist/bot/logic/common/scout.js +10 -0
- package/dist/bot/logic/knowledge.js +1 -0
- package/dist/bot/logic/map/map.js +6 -0
- package/dist/bot/logic/map/sector.js +6 -1
- package/dist/bot/logic/mission/basicMission.js +1 -5
- package/dist/bot/logic/mission/expansionMission.js +22 -4
- package/dist/bot/logic/mission/mission.js +49 -2
- package/dist/bot/logic/mission/missionController.js +67 -34
- package/dist/bot/logic/mission/missionFactories.js +10 -0
- package/dist/bot/logic/mission/missions/attackMission.js +109 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +62 -0
- package/dist/bot/logic/mission/missions/expansionMission.js +24 -0
- package/dist/bot/logic/mission/missions/oneTimeMission.js +26 -0
- package/dist/bot/logic/mission/missions/retreatMission.js +7 -0
- package/dist/bot/logic/mission/missions/scoutingMission.js +38 -0
- package/dist/bot/logic/squad/behaviours/attackSquad.js +82 -0
- package/dist/bot/logic/squad/behaviours/combatSquad.js +99 -0
- package/dist/bot/logic/squad/behaviours/common.js +37 -0
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +48 -0
- package/dist/bot/logic/squad/behaviours/expansionSquad.js +42 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +32 -0
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +38 -0
- package/dist/bot/logic/squad/behaviours/squadExpansion.js +26 -13
- package/dist/bot/logic/squad/squad.js +68 -15
- package/dist/bot/logic/squad/squadBehaviour.js +5 -5
- package/dist/bot/logic/squad/squadBehaviours.js +6 -0
- package/dist/bot/logic/squad/squadController.js +106 -15
- package/dist/exampleBot.js +22 -7
- package/package.json +29 -24
- package/src/bot/bot.ts +178 -378
- package/src/bot/logic/awareness.ts +220 -0
- package/src/bot/logic/building/ArtilleryUnit.ts +2 -2
- package/src/bot/logic/building/antiGroundStaticDefence.ts +2 -2
- package/src/bot/logic/building/basicAirUnit.ts +2 -2
- package/src/bot/logic/building/basicBuilding.ts +2 -2
- package/src/bot/logic/building/basicGroundUnit.ts +83 -78
- package/src/bot/logic/building/building.ts +125 -120
- package/src/bot/logic/building/harvester.ts +27 -27
- package/src/bot/logic/building/powerPlant.ts +1 -1
- package/src/bot/logic/building/queueController.ts +17 -38
- package/src/bot/logic/building/resourceCollectionBuilding.ts +1 -1
- package/src/bot/logic/common/scout.ts +12 -0
- package/src/bot/logic/map/map.ts +11 -3
- package/src/bot/logic/map/sector.ts +136 -130
- package/src/bot/logic/mission/mission.ts +83 -47
- package/src/bot/logic/mission/missionController.ts +103 -51
- package/src/bot/logic/mission/missionFactories.ts +46 -0
- package/src/bot/logic/mission/missions/attackMission.ts +152 -0
- package/src/bot/logic/mission/missions/defenceMission.ts +104 -0
- package/src/bot/logic/mission/missions/expansionMission.ts +49 -0
- package/src/bot/logic/mission/missions/oneTimeMission.ts +32 -0
- package/src/bot/logic/mission/missions/retreatMission.ts +9 -0
- package/src/bot/logic/mission/missions/scoutingMission.ts +59 -0
- package/src/bot/logic/squad/behaviours/combatSquad.ts +125 -0
- package/src/bot/logic/squad/behaviours/common.ts +37 -0
- package/src/bot/logic/squad/behaviours/expansionSquad.ts +59 -0
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +46 -0
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +56 -0
- package/src/bot/logic/squad/squad.ts +163 -97
- package/src/bot/logic/squad/squadBehaviour.ts +61 -43
- package/src/bot/logic/squad/squadBehaviours.ts +8 -0
- package/src/bot/logic/squad/squadController.ts +190 -66
- package/src/exampleBot.ts +19 -4
- package/tsconfig.json +1 -1
- package/src/bot/logic/mission/basicMission.ts +0 -42
- package/src/bot/logic/mission/expansionMission.ts +0 -25
- package/src/bot/logic/squad/behaviours/squadExpansion.ts +0 -33
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData, Point2D, SideType } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../../threat/threat.js";
|
|
3
|
+
import { Squad } from "../squad.js";
|
|
4
|
+
import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
|
|
5
|
+
import { MatchAwareness } from "../../awareness.js";
|
|
6
|
+
import { getUnseenStartingLocations } from "../../common/scout.js";
|
|
7
|
+
|
|
8
|
+
const SCOUT_MOVE_COOLDOWN_TICKS = 30;
|
|
9
|
+
|
|
10
|
+
export class ScoutingSquad implements SquadBehaviour {
|
|
11
|
+
private scoutingWith: {
|
|
12
|
+
unitId: number;
|
|
13
|
+
gameTick: number;
|
|
14
|
+
} | null = null;
|
|
15
|
+
|
|
16
|
+
public onAiUpdate(
|
|
17
|
+
gameApi: GameApi,
|
|
18
|
+
actionsApi: ActionsApi,
|
|
19
|
+
playerData: PlayerData,
|
|
20
|
+
squad: Squad,
|
|
21
|
+
matchAwareness: MatchAwareness,
|
|
22
|
+
): SquadAction {
|
|
23
|
+
const scoutNames = ["ADOG", "DOG", "E1", "E2", "FV", "HTK"];
|
|
24
|
+
const scouts = squad.getUnitsOfTypes(gameApi, ...scoutNames);
|
|
25
|
+
|
|
26
|
+
if ((matchAwareness.getSectorCache().getOverallVisibility() || 0) > 0.9) {
|
|
27
|
+
return disband();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (scouts.length === 0) {
|
|
31
|
+
this.scoutingWith = null;
|
|
32
|
+
return requestUnits(scoutNames, 100);
|
|
33
|
+
} else if (
|
|
34
|
+
!this.scoutingWith ||
|
|
35
|
+
gameApi.getCurrentTick() > this.scoutingWith.gameTick + SCOUT_MOVE_COOLDOWN_TICKS
|
|
36
|
+
) {
|
|
37
|
+
const candidatePoints = getUnseenStartingLocations(gameApi, playerData);
|
|
38
|
+
scouts.forEach((unit) => {
|
|
39
|
+
if (candidatePoints.length > 0) {
|
|
40
|
+
if (unit?.isIdle) {
|
|
41
|
+
const scoutLocation =
|
|
42
|
+
candidatePoints[Math.floor(gameApi.generateRandom() * candidatePoints.length)];
|
|
43
|
+
actionsApi.orderUnits([unit.id], OrderType.AttackMove, scoutLocation.x, scoutLocation.y);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Add a cooldown to scout attempts.
|
|
49
|
+
this.scoutingWith = {
|
|
50
|
+
unitId: scouts[0].id,
|
|
51
|
+
gameTick: gameApi.getCurrentTick(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return noop();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,97 +1,163 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules, UnitData } from "@chronodivide/game-api";
|
|
2
|
-
import { Mission } from "../mission/mission.js";
|
|
3
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
-
import { SquadAction, SquadBehaviour } from "./squadBehaviour.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.unitIds
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
import { ActionsApi, GameApi, PlayerData, Point2D, TechnoRules, Tile, UnitData } from "@chronodivide/game-api";
|
|
2
|
+
import { Mission } from "../mission/mission.js";
|
|
3
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
+
import { SquadAction, SquadBehaviour, disband } from "./squadBehaviour.js";
|
|
5
|
+
import { MatchAwareness } from "../awareness.js";
|
|
6
|
+
import { getDistanceBetweenPoints } from "../map/map.js";
|
|
7
|
+
import _ from "lodash";
|
|
8
|
+
|
|
9
|
+
export enum SquadLiveness {
|
|
10
|
+
SquadDead,
|
|
11
|
+
SquadActive,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SquadConstructionRequest = {
|
|
15
|
+
squad: Squad;
|
|
16
|
+
unitType: TechnoRules;
|
|
17
|
+
priority: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const calculateCenterOfMass: (unitTiles: Tile[]) => {
|
|
21
|
+
centerOfMass: Point2D;
|
|
22
|
+
maxDistance: number;
|
|
23
|
+
} | null = (unitTiles) => {
|
|
24
|
+
if (unitTiles.length === 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
// TODO: use median here
|
|
28
|
+
const sums = unitTiles.reduce(
|
|
29
|
+
({ x, y }, tile) => {
|
|
30
|
+
return {
|
|
31
|
+
x: x + (tile?.rx || 0),
|
|
32
|
+
y: y + (tile?.ry || 0),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
{ x: 0, y: 0 },
|
|
36
|
+
);
|
|
37
|
+
const centerOfMass = {
|
|
38
|
+
x: Math.round(sums.x / unitTiles.length),
|
|
39
|
+
y: Math.round(sums.y / unitTiles.length),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// max distance of units to the center of mass
|
|
43
|
+
const distances = unitTiles.map((tile) => getDistanceBetweenPoints({ x: tile.rx, y: tile.ry }, centerOfMass));
|
|
44
|
+
const maxDistance = _.max(distances)!;
|
|
45
|
+
return { centerOfMass, maxDistance };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class Squad {
|
|
49
|
+
private unitIds: number[] = [];
|
|
50
|
+
private liveness: SquadLiveness = SquadLiveness.SquadActive;
|
|
51
|
+
private lastLivenessUpdateTick: number = 0;
|
|
52
|
+
private centerOfMass: Point2D | null = null;
|
|
53
|
+
private maxDistanceToCenterOfMass: number | null = null;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
private name: string,
|
|
57
|
+
private behaviour: SquadBehaviour,
|
|
58
|
+
private mission: Mission<any> | null,
|
|
59
|
+
private killable = false,
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
public getName(): string {
|
|
63
|
+
return this.name;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getCenterOfMass() {
|
|
67
|
+
return this.centerOfMass;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getMaxDistanceToCenterOfMass() {
|
|
71
|
+
return this.maxDistanceToCenterOfMass;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public onAiUpdate(
|
|
75
|
+
gameApi: GameApi,
|
|
76
|
+
actionsApi: ActionsApi,
|
|
77
|
+
playerData: PlayerData,
|
|
78
|
+
matchAwareness: MatchAwareness,
|
|
79
|
+
): SquadAction {
|
|
80
|
+
this.updateLiveness(gameApi);
|
|
81
|
+
const movableUnitTiles = this.unitIds
|
|
82
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
83
|
+
.filter((unit) => unit?.canMove)
|
|
84
|
+
.map((unit) => unit?.tile)
|
|
85
|
+
.filter((tile) => !!tile) as Tile[];
|
|
86
|
+
const tileMetrics = calculateCenterOfMass(movableUnitTiles);
|
|
87
|
+
if (tileMetrics) {
|
|
88
|
+
this.centerOfMass = tileMetrics.centerOfMass;
|
|
89
|
+
this.maxDistanceToCenterOfMass = tileMetrics.maxDistance;
|
|
90
|
+
} else {
|
|
91
|
+
this.centerOfMass = null;
|
|
92
|
+
this.maxDistanceToCenterOfMass = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.mission && this.mission.isActive() == false) {
|
|
96
|
+
// Orphaned squad, might get picked up later.
|
|
97
|
+
this.mission.removeSquad();
|
|
98
|
+
this.mission = null;
|
|
99
|
+
return disband();
|
|
100
|
+
} else if (!this.mission) {
|
|
101
|
+
return disband();
|
|
102
|
+
}
|
|
103
|
+
let outcome = this.behaviour.onAiUpdate(gameApi, actionsApi, playerData, this, matchAwareness);
|
|
104
|
+
return outcome;
|
|
105
|
+
}
|
|
106
|
+
public getMission(): Mission | null {
|
|
107
|
+
return this.mission;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public setMission(mission: Mission | null) {
|
|
111
|
+
if (this.mission != undefined && this.mission != mission) {
|
|
112
|
+
this.mission.removeSquad();
|
|
113
|
+
}
|
|
114
|
+
this.mission = mission;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public getUnitIds(): number[] {
|
|
118
|
+
return this.unitIds;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public getUnits(gameApi: GameApi): UnitData[] {
|
|
122
|
+
return this.unitIds
|
|
123
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
124
|
+
.filter((unit) => unit != null)
|
|
125
|
+
.map((unit) => unit!);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public getUnitsOfTypes(gameApi: GameApi, ...names: string[]): UnitData[] {
|
|
129
|
+
return this.unitIds
|
|
130
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
131
|
+
.filter((unit) => !!unit && names.includes(unit.name))
|
|
132
|
+
.map((unit) => unit!);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public getUnitsMatching(gameApi: GameApi, filter: (r: UnitData) => boolean): UnitData[] {
|
|
136
|
+
return this.unitIds
|
|
137
|
+
.map((unitId) => gameApi.getUnitData(unitId))
|
|
138
|
+
.filter((unit) => !!unit && filter(unit))
|
|
139
|
+
.map((unit) => unit!);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public removeUnit(unitIdToRemove: number): void {
|
|
143
|
+
this.unitIds = this.unitIds.filter((unitId) => unitId != unitIdToRemove);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public addUnit(unitIdToAdd: number): void {
|
|
147
|
+
this.unitIds.push(unitIdToAdd);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private updateLiveness(gameApi: GameApi) {
|
|
151
|
+
this.unitIds = this.unitIds.filter((unitId) => gameApi.getUnitData(unitId));
|
|
152
|
+
this.lastLivenessUpdateTick = gameApi.getCurrentTick();
|
|
153
|
+
if (this.killable && this.unitIds.length == 0) {
|
|
154
|
+
if (this.liveness == SquadLiveness.SquadActive) {
|
|
155
|
+
this.liveness = SquadLiveness.SquadDead;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public getLiveness() {
|
|
161
|
+
return this.liveness;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -1,43 +1,61 @@
|
|
|
1
|
-
import { GameApi, PlayerData,
|
|
2
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export type SquadActionNoop = {
|
|
7
|
-
type: "noop";
|
|
8
|
-
};
|
|
9
|
-
export type SquadActionDisband = {
|
|
10
|
-
type: "disband";
|
|
11
|
-
};
|
|
12
|
-
export type SquadActionMergeInto = {
|
|
13
|
-
type: "mergeInto";
|
|
14
|
-
mergeInto: Squad;
|
|
15
|
-
};
|
|
16
|
-
export type
|
|
17
|
-
type: "
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
import { ActionsApi, GameApi, PlayerData, Point2D } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
+
import { Squad } from "./squad.js";
|
|
4
|
+
import { MatchAwareness } from "../awareness.js";
|
|
5
|
+
|
|
6
|
+
export type SquadActionNoop = {
|
|
7
|
+
type: "noop";
|
|
8
|
+
};
|
|
9
|
+
export type SquadActionDisband = {
|
|
10
|
+
type: "disband";
|
|
11
|
+
};
|
|
12
|
+
export type SquadActionMergeInto = {
|
|
13
|
+
type: "mergeInto";
|
|
14
|
+
mergeInto: Squad;
|
|
15
|
+
};
|
|
16
|
+
export type SquadActionRequestUnits = {
|
|
17
|
+
type: "request";
|
|
18
|
+
unitNames: string[];
|
|
19
|
+
priority: number;
|
|
20
|
+
};
|
|
21
|
+
export type SquadActionRequestSpecificUnits = {
|
|
22
|
+
type: "requestSpecific";
|
|
23
|
+
unitIds: number[];
|
|
24
|
+
priority: number;
|
|
25
|
+
};
|
|
26
|
+
export type SquadActionGrabFreeCombatants = {
|
|
27
|
+
type: "requestCombatants";
|
|
28
|
+
point: Point2D;
|
|
29
|
+
radius: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const noop = () => ({ type: "noop" } as SquadActionNoop);
|
|
33
|
+
|
|
34
|
+
export const disband = () => ({ type: "disband" } as SquadActionDisband);
|
|
35
|
+
|
|
36
|
+
export const requestUnits = (unitNames: string[], priority: number) =>
|
|
37
|
+
({ type: "request", unitNames, priority } as SquadActionRequestUnits);
|
|
38
|
+
|
|
39
|
+
export const requestSpecificUnits = (unitIds: number[], priority: number) =>
|
|
40
|
+
({ type: "requestSpecific", unitIds, priority } as SquadActionRequestSpecificUnits);
|
|
41
|
+
|
|
42
|
+
export const grabCombatants = (point: Point2D, radius: number) =>
|
|
43
|
+
({ type: "requestCombatants", point, radius } as SquadActionGrabFreeCombatants);
|
|
44
|
+
|
|
45
|
+
export type SquadAction =
|
|
46
|
+
| SquadActionNoop
|
|
47
|
+
| SquadActionDisband
|
|
48
|
+
| SquadActionMergeInto
|
|
49
|
+
| SquadActionRequestUnits
|
|
50
|
+
| SquadActionRequestSpecificUnits
|
|
51
|
+
| SquadActionGrabFreeCombatants;
|
|
52
|
+
|
|
53
|
+
export interface SquadBehaviour {
|
|
54
|
+
onAiUpdate(
|
|
55
|
+
gameApi: GameApi,
|
|
56
|
+
actionsApi: ActionsApi,
|
|
57
|
+
playerData: PlayerData,
|
|
58
|
+
squad: Squad,
|
|
59
|
+
matchAwareness: MatchAwareness
|
|
60
|
+
): SquadAction;
|
|
61
|
+
}
|