@supalosa/chronodivide-bot 0.2.1 → 0.2.2

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