@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.
Files changed (74) hide show
  1. package/README.md +71 -46
  2. package/dist/bot/bot.js +27 -183
  3. package/dist/bot/logic/awareness.js +122 -0
  4. package/dist/bot/logic/building/basicGroundUnit.js +8 -6
  5. package/dist/bot/logic/building/building.js +6 -3
  6. package/dist/bot/logic/building/harvester.js +1 -1
  7. package/dist/bot/logic/building/queueController.js +4 -21
  8. package/dist/bot/logic/common/scout.js +10 -0
  9. package/dist/bot/logic/knowledge.js +1 -0
  10. package/dist/bot/logic/map/map.js +6 -0
  11. package/dist/bot/logic/map/sector.js +6 -1
  12. package/dist/bot/logic/mission/basicMission.js +1 -5
  13. package/dist/bot/logic/mission/expansionMission.js +22 -4
  14. package/dist/bot/logic/mission/mission.js +49 -2
  15. package/dist/bot/logic/mission/missionController.js +67 -34
  16. package/dist/bot/logic/mission/missionFactories.js +10 -0
  17. package/dist/bot/logic/mission/missions/attackMission.js +109 -0
  18. package/dist/bot/logic/mission/missions/defenceMission.js +62 -0
  19. package/dist/bot/logic/mission/missions/expansionMission.js +24 -0
  20. package/dist/bot/logic/mission/missions/oneTimeMission.js +26 -0
  21. package/dist/bot/logic/mission/missions/retreatMission.js +7 -0
  22. package/dist/bot/logic/mission/missions/scoutingMission.js +38 -0
  23. package/dist/bot/logic/squad/behaviours/attackSquad.js +82 -0
  24. package/dist/bot/logic/squad/behaviours/combatSquad.js +99 -0
  25. package/dist/bot/logic/squad/behaviours/common.js +37 -0
  26. package/dist/bot/logic/squad/behaviours/defenceSquad.js +48 -0
  27. package/dist/bot/logic/squad/behaviours/expansionSquad.js +42 -0
  28. package/dist/bot/logic/squad/behaviours/retreatSquad.js +32 -0
  29. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +38 -0
  30. package/dist/bot/logic/squad/behaviours/squadExpansion.js +26 -13
  31. package/dist/bot/logic/squad/squad.js +68 -15
  32. package/dist/bot/logic/squad/squadBehaviour.js +5 -5
  33. package/dist/bot/logic/squad/squadBehaviours.js +6 -0
  34. package/dist/bot/logic/squad/squadController.js +106 -15
  35. package/dist/exampleBot.js +22 -7
  36. package/package.json +29 -24
  37. package/src/bot/bot.ts +178 -378
  38. package/src/bot/logic/awareness.ts +220 -0
  39. package/src/bot/logic/building/ArtilleryUnit.ts +2 -2
  40. package/src/bot/logic/building/antiGroundStaticDefence.ts +2 -2
  41. package/src/bot/logic/building/basicAirUnit.ts +2 -2
  42. package/src/bot/logic/building/basicBuilding.ts +2 -2
  43. package/src/bot/logic/building/basicGroundUnit.ts +83 -78
  44. package/src/bot/logic/building/building.ts +125 -120
  45. package/src/bot/logic/building/harvester.ts +27 -27
  46. package/src/bot/logic/building/powerPlant.ts +1 -1
  47. package/src/bot/logic/building/queueController.ts +17 -38
  48. package/src/bot/logic/building/resourceCollectionBuilding.ts +1 -1
  49. package/src/bot/logic/common/scout.ts +12 -0
  50. package/src/bot/logic/map/map.ts +11 -3
  51. package/src/bot/logic/map/sector.ts +136 -130
  52. package/src/bot/logic/mission/mission.ts +83 -47
  53. package/src/bot/logic/mission/missionController.ts +103 -51
  54. package/src/bot/logic/mission/missionFactories.ts +46 -0
  55. package/src/bot/logic/mission/missions/attackMission.ts +152 -0
  56. package/src/bot/logic/mission/missions/defenceMission.ts +104 -0
  57. package/src/bot/logic/mission/missions/expansionMission.ts +49 -0
  58. package/src/bot/logic/mission/missions/oneTimeMission.ts +32 -0
  59. package/src/bot/logic/mission/missions/retreatMission.ts +9 -0
  60. package/src/bot/logic/mission/missions/scoutingMission.ts +59 -0
  61. package/src/bot/logic/squad/behaviours/combatSquad.ts +125 -0
  62. package/src/bot/logic/squad/behaviours/common.ts +37 -0
  63. package/src/bot/logic/squad/behaviours/expansionSquad.ts +59 -0
  64. package/src/bot/logic/squad/behaviours/retreatSquad.ts +46 -0
  65. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +56 -0
  66. package/src/bot/logic/squad/squad.ts +163 -97
  67. package/src/bot/logic/squad/squadBehaviour.ts +61 -43
  68. package/src/bot/logic/squad/squadBehaviours.ts +8 -0
  69. package/src/bot/logic/squad/squadController.ts +190 -66
  70. package/src/exampleBot.ts +19 -4
  71. package/tsconfig.json +1 -1
  72. package/src/bot/logic/mission/basicMission.ts +0 -42
  73. package/src/bot/logic/mission/expansionMission.ts +0 -25
  74. 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
- export enum SquadLiveness {
7
- SquadDead,
8
- SquadActive,
9
- }
10
-
11
- export type SquadConstructionRequest = {
12
- squad: Squad;
13
- unitType: TechnoRules;
14
- priority: number;
15
- // quantity: number
16
- };
17
-
18
- export class Squad {
19
- constructor(
20
- private name: string,
21
- private behaviour: SquadBehaviour,
22
- private mission: Mission | undefined,
23
- private unitIds: number[] = [],
24
- private liveness: SquadLiveness = SquadLiveness.SquadActive,
25
- private lastLivenessUpdateTick: number = 0,
26
- ) {}
27
-
28
- public getName(): string {
29
- return this.name;
30
- }
31
-
32
- public onAiUpdate(gameApi: GameApi, playerData: PlayerData, threatData: GlobalThreat | undefined): SquadAction {
33
- this.updateLiveness(gameApi);
34
- if (this.mission && this.mission.isActive() == false) {
35
- // Orphaned squad, might get picked up later.
36
- this.mission.removeSquad(this);
37
- this.mission = undefined;
38
- }
39
- let outcome = this.behaviour.onAiUpdate(gameApi, playerData, this, threatData);
40
- return outcome;
41
- }
42
-
43
- public getMission(): Mission | undefined {
44
- return this.mission;
45
- }
46
-
47
- public setMission(mission: Mission | undefined) {
48
- if (this.mission != undefined && this.mission != mission) {
49
- this.mission.removeSquad(this);
50
- }
51
- this.mission = mission;
52
- }
53
-
54
- public getUnitIds(): number[] {
55
- return this.unitIds;
56
- }
57
-
58
- public getUnits(gameApi: GameApi): UnitData[] {
59
- return this.unitIds
60
- .map((unitId) => gameApi.getUnitData(unitId))
61
- .filter((unit) => unit != null)
62
- .map((unit) => unit!);
63
- }
64
-
65
- public getUnitsOfType(gameApi: GameApi, f: (r: UnitData | undefined) => boolean): UnitData[] {
66
- return this.unitIds
67
- .map((unitId) => gameApi.getUnitData(unitId))
68
- .filter(f)
69
- .map((unit) => unit!);
70
- }
71
-
72
- public removeUnit(unitIdToRemove: number): void {
73
- this.unitIds = this.unitIds.filter((unitId) => unitId != unitIdToRemove);
74
- }
75
-
76
- public clearUnits(): void {
77
- this.unitIds = [];
78
- }
79
-
80
- public addUnit(unitIdToAdd: number): void {
81
- this.unitIds.push(unitIdToAdd);
82
- }
83
-
84
- private updateLiveness(gameApi: GameApi) {
85
- this.unitIds = this.unitIds.filter((unitId) => gameApi.getUnitData(unitId));
86
- this.lastLivenessUpdateTick = gameApi.getCurrentTick();
87
- if (this.unitIds.length == 0) {
88
- if (this.liveness == SquadLiveness.SquadActive) {
89
- this.liveness = SquadLiveness.SquadDead;
90
- }
91
- }
92
- }
93
-
94
- public getLiveness() {
95
- return this.liveness;
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, TechnoRules } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../threat/threat.js";
3
- import { SquadExpansion } from "./behaviours/squadExpansion.js";
4
- import { Squad } from "./squad.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 SquadActionClaimUnits = {
17
- type: "claimUnit";
18
- unitIds: number[];
19
- };
20
-
21
- export type SquadAction = SquadActionNoop | SquadActionDisband | SquadActionMergeInto | SquadActionClaimUnits;
22
-
23
- export interface SquadBehaviour {
24
- onAiUpdate(
25
- gameApi: GameApi,
26
- playerData: PlayerData,
27
- squad: Squad,
28
- threatData: GlobalThreat | undefined,
29
- ): SquadAction;
30
-
31
- // State the desired composition of the Squad.
32
- getDesiredComposition(
33
- gameApi: GameApi,
34
- playerData: PlayerData,
35
- squad: Squad,
36
- threatData: GlobalThreat | undefined,
37
- ): { unitName: string; priority: number; amount: number }[];
38
- }
39
-
40
- export const allSquadBehaviours: SquadBehaviour[] = [
41
- //new SquadScouters(),
42
- new SquadExpansion(),
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
+ }
@@ -0,0 +1,8 @@
1
+ import { ExpansionSquad } from "./behaviours/expansionSquad.js";
2
+ import { SquadBehaviour } from "./squadBehaviour.js";
3
+
4
+ /*
5
+ export const ALL_SQUAD_BEHAVIOURS: SquadBehaviour[] = [
6
+ //new SquadScouters(),
7
+ new SquadExpansion(),
8
+ ];*/