@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.
Files changed (118) hide show
  1. package/README.md +11 -3
  2. package/dist/bot/bot.js +17 -9
  3. package/dist/bot/bot.js.map +1 -0
  4. package/dist/bot/logic/awareness.js +12 -2
  5. package/dist/bot/logic/awareness.js.map +1 -0
  6. package/dist/bot/logic/building/ArtilleryUnit.js +29 -8
  7. package/dist/bot/logic/building/antiGroundStaticDefence.js +3 -2
  8. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
  9. package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
  10. package/dist/bot/logic/building/basicAirUnit.js +2 -1
  11. package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
  12. package/dist/bot/logic/building/basicBuilding.js +2 -1
  13. package/dist/bot/logic/building/basicBuilding.js.map +1 -0
  14. package/dist/bot/logic/building/basicGroundUnit.js +2 -1
  15. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
  16. package/dist/bot/logic/building/building.js +2 -0
  17. package/dist/bot/logic/building/buildingRules.js +168 -0
  18. package/dist/bot/logic/building/buildingRules.js.map +1 -0
  19. package/dist/bot/logic/building/harvester.js +1 -0
  20. package/dist/bot/logic/building/harvester.js.map +1 -0
  21. package/dist/bot/logic/building/powerPlant.js +2 -1
  22. package/dist/bot/logic/building/powerPlant.js.map +1 -0
  23. package/dist/bot/logic/building/queueController.js +2 -1
  24. package/dist/bot/logic/building/queueController.js.map +1 -0
  25. package/dist/bot/logic/building/resourceCollectionBuilding.js +2 -1
  26. package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -0
  27. package/dist/bot/logic/common/scout.js +100 -0
  28. package/dist/bot/logic/common/scout.js.map +1 -0
  29. package/dist/bot/logic/common/utils.js +14 -0
  30. package/dist/bot/logic/common/utils.js.map +1 -0
  31. package/dist/bot/logic/map/map.js +9 -25
  32. package/dist/bot/logic/map/map.js.map +1 -0
  33. package/dist/bot/logic/map/sector.js +33 -1
  34. package/dist/bot/logic/map/sector.js.map +1 -0
  35. package/dist/bot/logic/mission/mission.js +3 -1
  36. package/dist/bot/logic/mission/mission.js.map +1 -0
  37. package/dist/bot/logic/mission/missionController.js +5 -4
  38. package/dist/bot/logic/mission/missionController.js.map +1 -0
  39. package/dist/bot/logic/mission/missionFactories.js +3 -0
  40. package/dist/bot/logic/mission/missionFactories.js.map +1 -0
  41. package/dist/bot/logic/mission/missions/attackMission.js +9 -10
  42. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
  43. package/dist/bot/logic/mission/missions/defenceMission.js +12 -7
  44. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
  45. package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
  46. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
  47. package/dist/bot/logic/mission/missions/expansionMission.js +5 -4
  48. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -0
  49. package/dist/bot/logic/mission/missions/oneTimeMission.js +3 -2
  50. package/dist/bot/logic/mission/missions/oneTimeMission.js.map +1 -0
  51. package/dist/bot/logic/mission/missions/retreatMission.js +3 -2
  52. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -0
  53. package/dist/bot/logic/mission/missions/scoutingMission.js +8 -9
  54. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -0
  55. package/dist/bot/logic/squad/behaviours/combatSquad.js +20 -18
  56. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
  57. package/dist/bot/logic/squad/behaviours/common.js +25 -5
  58. package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
  59. package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
  60. package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
  61. package/dist/bot/logic/squad/behaviours/expansionSquad.js +1 -0
  62. package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -0
  63. package/dist/bot/logic/squad/behaviours/retreatSquad.js +6 -10
  64. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -0
  65. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +66 -18
  66. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -0
  67. package/dist/bot/logic/squad/squad.js +4 -5
  68. package/dist/bot/logic/squad/squad.js.map +1 -0
  69. package/dist/bot/logic/squad/squadBehaviour.js +1 -0
  70. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -0
  71. package/dist/bot/logic/squad/squadBehaviours.js +1 -0
  72. package/dist/bot/logic/squad/squadBehaviours.js.map +1 -0
  73. package/dist/bot/logic/squad/squadController.js +73 -23
  74. package/dist/bot/logic/squad/squadController.js.map +1 -0
  75. package/dist/bot/logic/threat/threat.js +1 -0
  76. package/dist/bot/logic/threat/threat.js.map +1 -0
  77. package/dist/bot/logic/threat/threatCalculator.js +1 -0
  78. package/dist/bot/logic/threat/threatCalculator.js.map +1 -0
  79. package/dist/exampleBot.js +29 -12
  80. package/dist/exampleBot.js.map +1 -0
  81. package/package.json +15 -7
  82. package/src/bot/bot.ts +21 -16
  83. package/src/bot/logic/awareness.ts +22 -5
  84. package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
  85. package/src/bot/logic/building/artilleryUnit.ts +68 -0
  86. package/src/bot/logic/building/basicAirUnit.ts +68 -68
  87. package/src/bot/logic/building/basicBuilding.ts +47 -47
  88. package/src/bot/logic/building/basicGroundUnit.ts +1 -1
  89. package/src/bot/logic/building/buildingRules.ts +233 -0
  90. package/src/bot/logic/building/powerPlant.ts +32 -32
  91. package/src/bot/logic/building/queueController.ts +1 -1
  92. package/src/bot/logic/building/resourceCollectionBuilding.ts +56 -56
  93. package/src/bot/logic/common/scout.ts +127 -1
  94. package/src/bot/logic/common/utils.ts +17 -0
  95. package/src/bot/logic/map/map.ts +70 -84
  96. package/src/bot/logic/map/sector.ts +46 -4
  97. package/src/bot/logic/mission/mission.ts +2 -2
  98. package/src/bot/logic/mission/missionController.ts +7 -6
  99. package/src/bot/logic/mission/missionFactories.ts +5 -0
  100. package/src/bot/logic/mission/missions/attackMission.ts +19 -12
  101. package/src/bot/logic/mission/missions/defenceMission.ts +35 -15
  102. package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
  103. package/src/bot/logic/mission/missions/expansionMission.ts +6 -4
  104. package/src/bot/logic/mission/missions/oneTimeMission.ts +3 -2
  105. package/src/bot/logic/mission/missions/retreatMission.ts +3 -2
  106. package/src/bot/logic/mission/missions/scoutingMission.ts +9 -6
  107. package/src/bot/logic/squad/behaviours/combatSquad.ts +22 -18
  108. package/src/bot/logic/squad/behaviours/common.ts +38 -5
  109. package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
  110. package/src/bot/logic/squad/behaviours/retreatSquad.ts +10 -12
  111. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +79 -26
  112. package/src/bot/logic/squad/squad.ts +4 -4
  113. package/src/bot/logic/squad/squadBehaviour.ts +3 -1
  114. package/src/bot/logic/squad/squadController.ts +135 -70
  115. package/src/exampleBot.ts +31 -12
  116. package/tsconfig.json +73 -73
  117. package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
  118. package/src/bot/logic/building/building.ts +0 -125
@@ -1,16 +1,13 @@
1
- import { AttackState, GameApi, ObjectType, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
2
- import { OneTimeMission } from "./oneTimeMission.js";
1
+ import { GameApi, ObjectType, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
3
2
  import { CombatSquad } from "../../squad/behaviours/combatSquad.js";
4
3
  import { Mission, MissionAction, disbandMission, noop } from "../mission.js";
5
- import { GlobalThreat } from "../../threat/threat.js";
6
4
  import { Squad } from "../../squad/squad.js";
7
- import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
8
5
  import { MissionFactory } from "../missionFactories.js";
9
6
  import { MatchAwareness } from "../../awareness.js";
10
7
  import { MissionController } from "../missionController.js";
11
- import { match } from "assert";
12
8
  import { RetreatMission } from "./retreatMission.js";
13
- import _ from "lodash";
9
+ import maxBy from "lodash.maxby";
10
+ import { DebugLogger } from "../../common/utils.js";
14
11
 
15
12
  export enum AttackFailReason {
16
13
  NoTargets = 0,
@@ -31,8 +28,9 @@ export class AttackMission extends Mission<AttackFailReason> {
31
28
  private rallyArea: Point2D,
32
29
  private attackArea: Point2D,
33
30
  private radius: number,
31
+ logger: DebugLogger,
34
32
  ) {
35
- super(uniqueName, priority);
33
+ super(uniqueName, priority, logger);
36
34
  }
37
35
 
38
36
  onAiUpdate(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): MissionAction {
@@ -88,7 +86,7 @@ export class AttackMissionFactory implements MissionFactory {
88
86
  .map((unitId) => gameApi.getUnitData(unitId))
89
87
  .filter((u) => !!u && gameApi.getPlayerData(u.owner).isCombatant) as UnitData[];
90
88
 
91
- const maxUnit = _.maxBy(enemyUnits, (u) => getTargetWeight(u, tryFocusHarvester));
89
+ const maxUnit = maxBy(enemyUnits, (u) => getTargetWeight(u, tryFocusHarvester));
92
90
  if (maxUnit) {
93
91
  return { x: maxUnit.tile.rx, y: maxUnit.tile.ry };
94
92
  }
@@ -104,6 +102,7 @@ export class AttackMissionFactory implements MissionFactory {
104
102
  playerData: PlayerData,
105
103
  matchAwareness: MatchAwareness,
106
104
  missionController: MissionController,
105
+ logger: DebugLogger,
107
106
  ): void {
108
107
  if (!matchAwareness.shouldAttack()) {
109
108
  return;
@@ -124,18 +123,26 @@ export class AttackMissionFactory implements MissionFactory {
124
123
  // TODO: not using a fixed value here. But performance slows to a crawl when this is unique.
125
124
  const squadName = "globalAttack";
126
125
 
127
- const tryAttack = missionController
128
- .addMission(new AttackMission(squadName, 100, matchAwareness.getMainRallyPoint(), attackArea, attackRadius))
129
- ?.then((reason, squad) => {
126
+ const tryAttack = missionController.addMission(
127
+ new AttackMission(
128
+ squadName,
129
+ 100,
130
+ matchAwareness.getMainRallyPoint(),
131
+ attackArea,
132
+ attackRadius,
133
+ logger,
134
+ ).then((reason, squad) => {
130
135
  missionController.addMission(
131
136
  new RetreatMission(
132
137
  "retreat-from-" + squadName + gameApi.getCurrentTick(),
133
138
  100,
134
139
  matchAwareness.getMainRallyPoint(),
135
140
  squad?.getUnitIds() ?? [],
141
+ logger,
136
142
  ),
137
143
  );
138
- });
144
+ }),
145
+ );
139
146
  if (tryAttack) {
140
147
  this.lastAttackAt = gameApi.getCurrentTick();
141
148
  }
@@ -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
  }
@@ -47,9 +56,9 @@ export class DefenceMission extends Mission<DefenceFailReason> {
47
56
  const DEFENCE_CHECK_TICKS = 30;
48
57
 
49
58
  // Starting radius around the player's base to trigger defense.
50
- const DEFENCE_STARTING_RADIUS = 20;
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.005;
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("globalDefence", 1000, playerData.startLocation, defendableRadius * 1.2)?.then(
81
- (reason, squad) => {
82
- missionController.addMission(
83
- new RetreatMission(
84
- "retreat-from-globalDefence" + gameApi.getCurrentTick(),
85
- 100,
86
- matchAwareness.getMainRallyPoint(),
87
- squad?.getUnitIds() ?? [],
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
  }
@@ -0,0 +1,61 @@
1
+ import { GameApi, PlayerData } from "@chronodivide/game-api";
2
+ import { GlobalThreat } from "../../threat/threat.js";
3
+ import { Mission } from "../mission.js";
4
+ import { ExpansionSquad } from "../../squad/behaviours/expansionSquad.js";
5
+ import { MissionFactory } from "../missionFactories.js";
6
+ import { OneTimeMission } from "./oneTimeMission.js";
7
+ import { MatchAwareness } from "../../awareness.js";
8
+ import { MissionController } from "../missionController.js";
9
+ import { DebugLogger } from "../../common/utils.js";
10
+ import { EngineerSquad } from "../../squad/behaviours/engineerSquad.js";
11
+
12
+ /**
13
+ * A mission that tries to send an engineer into a building (e.g. to capture tech building or repair bridge)
14
+ */
15
+ export class EngineerMission extends OneTimeMission {
16
+ constructor(uniqueName: string, priority: number, selectedTechBuilding: number,
17
+ logger: DebugLogger) {
18
+ super(uniqueName, priority, () => new EngineerSquad(selectedTechBuilding), logger);
19
+ }
20
+ }
21
+
22
+ // Only try to capture tech buildings within this radius of the starting point.
23
+ const MAX_TECH_CAPTURE_RADIUS = 50;
24
+
25
+ const TECH_CHECK_INTERVAL_TICKS = 300;
26
+
27
+ export class EngineerMissionFactory implements MissionFactory {
28
+ private lastCheckAt = 0;
29
+
30
+ getName(): string {
31
+ return "EngineerMissionFactory";
32
+ }
33
+
34
+ maybeCreateMissions(
35
+ gameApi: GameApi,
36
+ playerData: PlayerData,
37
+ matchAwareness: MatchAwareness,
38
+ missionController: MissionController,
39
+ logger: DebugLogger
40
+ ): void {
41
+ if (!(gameApi.getCurrentTick() > this.lastCheckAt + TECH_CHECK_INTERVAL_TICKS)) {
42
+ return;
43
+ }
44
+ this.lastCheckAt = gameApi.getCurrentTick();
45
+ const eligibleTechBuildings = gameApi.getVisibleUnits(playerData.name, "hostile", (r) => r.capturable && r.produceCashAmount > 0);
46
+
47
+ eligibleTechBuildings.forEach((techBuildingId) => {
48
+ missionController.addMission(new EngineerMission("capture-" + techBuildingId, 100, techBuildingId, logger));
49
+ });
50
+ }
51
+
52
+ onMissionFailed(
53
+ gameApi: GameApi,
54
+ playerData: PlayerData,
55
+ matchAwareness: MatchAwareness,
56
+ failedMission: Mission,
57
+ failureReason: undefined,
58
+ missionController: MissionController,
59
+ ): void {
60
+ }
61
+ }
@@ -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
- super(uniqueName, priority, () => new ExpansionSquad(selectedMcv));
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
- super(uniqueName, priority, () => new ScoutingSquad());
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
- const candidatePoints = getUnseenStartingLocations(gameApi, playerData);
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
  }
@@ -1,10 +1,11 @@
1
- import _ from "lodash";
2
- import { ActionsApi, GameApi, MovementZone, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import maxBy from "lodash.maxby";
2
+ import { ActionsApi, GameApi, MovementZone, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
3
3
  import { Squad } from "../squad.js";
4
4
  import { SquadAction, SquadBehaviour, grabCombatants, noop } from "../squadBehaviour.js";
5
5
  import { MatchAwareness } from "../../awareness.js";
6
6
  import { getDistanceBetweenPoints } from "../../map/map.js";
7
- import { manageAttackMicro, manageMoveMicro } from "./common.js";
7
+ import { getAttackWeight, 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,12 @@ 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 (
59
+ squad.getUnitIds().length > 0 &&
60
+ (!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)
61
+ ) {
57
62
  this.lastCommand = gameApi.getCurrentTick();
58
63
  const centerOfMass = squad.getCenterOfMass();
59
64
  const maxDistance = squad.getMaxDistanceToCenterOfMass();
@@ -81,6 +86,7 @@ export class CombatSquad implements SquadBehaviour {
81
86
  manageMoveMicro(actionsApi, unit, centerOfMass);
82
87
  });
83
88
  } else {
89
+ logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`);
84
90
  this.state = SquadState.Attacking;
85
91
  }
86
92
  } else {
@@ -92,24 +98,22 @@ export class CombatSquad implements SquadBehaviour {
92
98
  gameApi.mapApi.getTile(centerOfMass.x, centerOfMass.y) !== undefined &&
93
99
  maxDistance > requiredGatherRadius
94
100
  ) {
95
- // Switch back to gather mode.
101
+ // Switch back to gather mode
102
+ logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`);
96
103
  this.state = SquadState.Gathering;
97
104
  return noop();
98
105
  }
99
106
  for (const unit of units) {
100
- if (unit.isIdle) {
101
- const { rx: x, ry: y } = unit.tile;
102
- const range = unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
103
- const nearbyHostiles = matchAwareness.getHostilesNearPoint(x, y, range * 2);
104
- const closest = _.minBy(nearbyHostiles, ({ x: hX, y: hY }) =>
105
- getDistanceBetweenPoints({ x, y }, { x: hX, y: hY }),
106
- );
107
- const closestUnit = closest ? gameApi.getUnitData(closest.unitId) ?? null : null;
108
- if (closestUnit) {
109
- manageAttackMicro(actionsApi, unit, closestUnit);
110
- } else {
111
- manageMoveMicro(actionsApi, unit, targetPoint);
112
- }
107
+ const { rx: x, ry: y } = unit.tile;
108
+ const range = unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
109
+ const nearbyHostiles = matchAwareness
110
+ .getHostilesNearPoint(x, y, range * 2)
111
+ .map(({ unitId }) => gameApi.getUnitData(unitId)) as UnitData[];
112
+ const bestUnit = maxBy(nearbyHostiles, (target) => getAttackWeight(unit, target));
113
+ if (bestUnit) {
114
+ manageAttackMicro(actionsApi, unit, bestUnit);
115
+ } else {
116
+ manageMoveMicro(actionsApi, unit, targetPoint);
113
117
  }
114
118
  }
115
119
  }
@@ -1,10 +1,21 @@
1
- import { ActionsApi, ObjectType, OrderType, Point2D, UnitData } from "@chronodivide/game-api";
2
- import { getDistanceBetweenUnits } from "../../map/map.js";
1
+ import {
2
+ ActionsApi,
3
+ AttackState,
4
+ ObjectType,
5
+ OrderType,
6
+ Point2D,
7
+ StanceType,
8
+ UnitData,
9
+ ZoneType,
10
+ } from "@chronodivide/game-api";
11
+ import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
12
+ import { Zone } from "luxon";
3
13
 
4
14
  // Micro methods
5
15
  export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Point2D) {
6
16
  if (attacker.name === "E1") {
7
- if (!attacker.canMove) {
17
+ const isDeployed = attacker.stance === StanceType.Deployed;
18
+ if (isDeployed) {
8
19
  actionsApi.orderUnits([attacker.id], OrderType.DeploySelected);
9
20
  }
10
21
  }
@@ -16,10 +27,11 @@ export function manageAttackMicro(actionsApi: ActionsApi, attacker: UnitData, ta
16
27
  if (attacker.name === "E1") {
17
28
  // Para (deployed weapon) range is 5.
18
29
  const deployedWeaponRange = attacker.secondaryWeapon?.maxRange || 5;
19
- if (attacker.canMove && distance <= deployedWeaponRange * 0.8) {
30
+ const isDeployed = attacker.stance === StanceType.Deployed;
31
+ if (!isDeployed && (distance <= deployedWeaponRange || attacker.attackState === AttackState.JustFired)) {
20
32
  actionsApi.orderUnits([attacker.id], OrderType.DeploySelected);
21
33
  return;
22
- } else if (!attacker.canMove && distance > deployedWeaponRange) {
34
+ } else if (isDeployed && distance > deployedWeaponRange) {
23
35
  actionsApi.orderUnits([attacker.id], OrderType.DeploySelected);
24
36
  return;
25
37
  }
@@ -35,3 +47,24 @@ export function manageAttackMicro(actionsApi: ActionsApi, attacker: UnitData, ta
35
47
  }
36
48
  actionsApi.orderUnits([attacker.id], orderType, target.id);
37
49
  }
50
+
51
+ /**
52
+ *
53
+ * @param attacker
54
+ * @param target
55
+ * @returns A number describing the weight of the given target for the attacker, or null if it should not attack it.
56
+ */
57
+ export function getAttackWeight(attacker: UnitData, target: UnitData): number | null {
58
+ const { rx: x, ry: y } = attacker.tile;
59
+ const { rx: hX, ry: hY } = target.tile;
60
+
61
+ if (!attacker.primaryWeapon?.projectileRules.isAntiAir && target.zone === ZoneType.Air) {
62
+ return null;
63
+ }
64
+
65
+ if (!attacker.primaryWeapon?.projectileRules.isAntiGround && target.zone === ZoneType.Ground) {
66
+ return null;
67
+ }
68
+
69
+ return 1000000 - getDistanceBetweenPoints({ x, y }, { x: hX, y: hY });
70
+ }
@@ -0,0 +1,53 @@
1
+ import { ActionsApi, GameApi, OrderType, PlayerData, SideType } from "@chronodivide/game-api";
2
+ import { Squad } from "../squad.js";
3
+ import { SquadAction, SquadBehaviour, disband, noop, requestSpecificUnits, requestUnits } from "../squadBehaviour.js";
4
+ import { MatchAwareness } from "../../awareness.js";
5
+
6
+ const CAPTURE_COOLDOWN_TICKS = 30;
7
+
8
+ // Capture squad
9
+ export class EngineerSquad implements SquadBehaviour {
10
+ private hasAttemptedCaptureWith: {
11
+ unitId: number;
12
+ gameTick: number;
13
+ } | null = null;
14
+
15
+ /**
16
+ * @param captureTarget ID of the target to try and capture/send engineer into.
17
+ */
18
+ constructor(private captureTarget: number) {
19
+ };
20
+
21
+ public onAiUpdate(
22
+ gameApi: GameApi,
23
+ actionsApi: ActionsApi,
24
+ playerData: PlayerData,
25
+ squad: Squad,
26
+ matchAwareness: MatchAwareness
27
+ ): SquadAction {
28
+ const engineerTypes = ["ENGINEER", "SENGINEER"];
29
+ const engineers = squad.getUnitsOfTypes(gameApi, ...engineerTypes);
30
+ if (engineers.length === 0) {
31
+ // Perhaps we deployed already (or the unit was destroyed), end the mission.
32
+ if (this.hasAttemptedCaptureWith !== null) {
33
+ return disband();
34
+ }
35
+ return requestUnits(engineerTypes, 100);
36
+ } else if (
37
+ !this.hasAttemptedCaptureWith ||
38
+ gameApi.getCurrentTick() > this.hasAttemptedCaptureWith.gameTick + CAPTURE_COOLDOWN_TICKS
39
+ ) {
40
+ actionsApi.orderUnits(
41
+ engineers.map((engineer) => engineer.id),
42
+ OrderType.Capture,
43
+ this.captureTarget
44
+ );
45
+ // Add a cooldown to deploy attempts.
46
+ this.hasAttemptedCaptureWith = {
47
+ unitId: engineers[0].id,
48
+ gameTick: gameApi.getCurrentTick(),
49
+ };
50
+ }
51
+ return noop();
52
+ }
53
+ }
@@ -7,8 +7,6 @@ import { MatchAwareness } from "../../awareness.js";
7
7
  const SCOUT_MOVE_COOLDOWN_TICKS = 30;
8
8
 
9
9
  export class RetreatSquad implements SquadBehaviour {
10
- private hasRequestedUnits: boolean = false;
11
- private moveOrderSentAt: number | null = null;
12
10
  private createdAt: number | null = null;
13
11
 
14
12
  constructor(
@@ -28,19 +26,19 @@ export class RetreatSquad implements SquadBehaviour {
28
26
  }
29
27
  if (squad.getUnitIds().length > 0) {
30
28
  // Only send the order once we have managed to claim some units.
31
- console.log(`Retreat squad ordered ${squad.getUnitIds()} to retreat`);
32
- actionsApi.orderUnits(squad.getUnitIds(), OrderType.Move, this.retreatToPoint.x, this.retreatToPoint.y);
33
- if (!this.moveOrderSentAt) {
34
- this.moveOrderSentAt = gameApi.getCurrentTick();
35
- }
29
+ actionsApi.orderUnits(
30
+ squad.getUnitIds(),
31
+ OrderType.AttackMove,
32
+ this.retreatToPoint.x,
33
+ this.retreatToPoint.y,
34
+ );
35
+ return disband();
36
36
  }
37
- if (
38
- (this.moveOrderSentAt && gameApi.getCurrentTick() > this.moveOrderSentAt + 60) ||
39
- (this.createdAt && gameApi.getCurrentTick() > this.createdAt + 240)
40
- ) {
37
+ if (this.createdAt && gameApi.getCurrentTick() > this.createdAt + 240) {
38
+ // Disband automatically after 240 ticks in case we couldn't actually claim any units.
41
39
  return disband();
42
40
  } else {
43
- return requestSpecificUnits(this.unitIds, 100);
41
+ return requestSpecificUnits(this.unitIds, 1000);
44
42
  }
45
43
  }
46
44
  }