@supalosa/chronodivide-bot 0.2.2 → 0.3.1

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 (108) hide show
  1. package/.prettierrc +5 -5
  2. package/TODO.md +18 -0
  3. package/dist/bot/bot.js +4 -4
  4. package/dist/bot/bot.js.map +1 -1
  5. package/dist/bot/logic/awareness.js +8 -8
  6. package/dist/bot/logic/awareness.js.map +1 -1
  7. package/dist/bot/logic/building/ArtilleryUnit.js +30 -9
  8. package/dist/bot/logic/building/antiGroundStaticDefence.js +2 -2
  9. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  10. package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
  11. package/dist/bot/logic/building/basicAirUnit.js +3 -2
  12. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  13. package/dist/bot/logic/building/basicBuilding.js +1 -1
  14. package/dist/bot/logic/building/basicBuilding.js.map +1 -1
  15. package/dist/bot/logic/building/basicGroundUnit.js +4 -3
  16. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  17. package/dist/bot/logic/building/building.js +11 -55
  18. package/dist/bot/logic/building/buildingRules.js +162 -0
  19. package/dist/bot/logic/building/buildingRules.js.map +1 -0
  20. package/dist/bot/logic/building/harvester.js.map +1 -1
  21. package/dist/bot/logic/building/massedAntiGroundUnit.js +20 -0
  22. package/dist/bot/logic/building/powerPlant.js +1 -1
  23. package/dist/bot/logic/building/powerPlant.js.map +1 -1
  24. package/dist/bot/logic/building/queueController.js +1 -1
  25. package/dist/bot/logic/building/queueController.js.map +1 -1
  26. package/dist/bot/logic/building/queues.js +19 -0
  27. package/dist/bot/logic/building/resourceCollectionBuilding.js +5 -3
  28. package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -1
  29. package/dist/bot/logic/common/scout.js +49 -32
  30. package/dist/bot/logic/common/scout.js.map +1 -1
  31. package/dist/bot/logic/common/utils.js +50 -1
  32. package/dist/bot/logic/common/utils.js.map +1 -1
  33. package/dist/bot/logic/knowledge.js +1 -0
  34. package/dist/bot/logic/map/map.js +17 -19
  35. package/dist/bot/logic/map/map.js.map +1 -1
  36. package/dist/bot/logic/map/sector.js +10 -13
  37. package/dist/bot/logic/map/sector.js.map +1 -1
  38. package/dist/bot/logic/mission/basicMission.js +26 -0
  39. package/dist/bot/logic/mission/expansionMission.js +32 -0
  40. package/dist/bot/logic/mission/missionFactories.js +2 -0
  41. package/dist/bot/logic/mission/missionFactories.js.map +1 -1
  42. package/dist/bot/logic/mission/missions/attackMission.js +4 -4
  43. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  44. package/dist/bot/logic/mission/missions/defenceMission.js +2 -1
  45. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  46. package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
  47. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
  48. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  49. package/dist/bot/logic/squad/behaviours/attackSquad.js +56 -63
  50. package/dist/bot/logic/squad/behaviours/combatSquad.js +18 -19
  51. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
  52. package/dist/bot/logic/squad/behaviours/common.js +19 -2
  53. package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
  54. package/dist/bot/logic/squad/behaviours/defenceSquad.js +2 -15
  55. package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
  56. package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
  57. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
  58. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +21 -17
  59. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
  60. package/dist/bot/logic/squad/behaviours/squadExpansion.js +31 -0
  61. package/dist/bot/logic/squad/behaviours/squadScouters.js +8 -0
  62. package/dist/bot/logic/squad/squad.js +5 -8
  63. package/dist/bot/logic/squad/squad.js.map +1 -1
  64. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
  65. package/dist/bot/logic/squad/squadController.js +2 -3
  66. package/dist/bot/logic/squad/squadController.js.map +1 -1
  67. package/dist/bot/logic/threat/threatCalculator.js +4 -3
  68. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  69. package/dist/exampleBot.js +6 -6
  70. package/dist/exampleBot.js.map +1 -1
  71. package/package.json +5 -9
  72. package/src/bot/bot.ts +8 -10
  73. package/src/bot/logic/awareness.ts +13 -17
  74. package/src/bot/logic/building/antiGroundStaticDefence.ts +13 -9
  75. package/src/bot/logic/building/artilleryUnit.ts +65 -0
  76. package/src/bot/logic/building/basicAirUnit.ts +10 -8
  77. package/src/bot/logic/building/basicBuilding.ts +1 -1
  78. package/src/bot/logic/building/basicGroundUnit.ts +4 -4
  79. package/src/bot/logic/building/{building.ts → buildingRules.ts} +94 -48
  80. package/src/bot/logic/building/harvester.ts +7 -4
  81. package/src/bot/logic/building/powerPlant.ts +1 -1
  82. package/src/bot/logic/building/queueController.ts +1 -1
  83. package/src/bot/logic/building/resourceCollectionBuilding.ts +8 -12
  84. package/src/bot/logic/common/scout.ts +83 -38
  85. package/src/bot/logic/common/utils.ts +65 -1
  86. package/src/bot/logic/map/map.ts +27 -31
  87. package/src/bot/logic/map/sector.ts +17 -21
  88. package/src/bot/logic/mission/missionFactories.ts +2 -0
  89. package/src/bot/logic/mission/missions/attackMission.ts +27 -27
  90. package/src/bot/logic/mission/missions/defenceMission.ts +3 -3
  91. package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
  92. package/src/bot/logic/mission/missions/retreatMission.ts +2 -2
  93. package/src/bot/logic/squad/behaviours/combatSquad.ts +24 -26
  94. package/src/bot/logic/squad/behaviours/common.ts +33 -3
  95. package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
  96. package/src/bot/logic/squad/behaviours/retreatSquad.ts +2 -2
  97. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +26 -28
  98. package/src/bot/logic/squad/squad.ts +8 -13
  99. package/src/bot/logic/squad/squadBehaviour.ts +9 -10
  100. package/src/bot/logic/squad/squadController.ts +2 -5
  101. package/src/bot/logic/threat/threat.ts +15 -15
  102. package/src/bot/logic/threat/threatCalculator.ts +4 -3
  103. package/src/exampleBot.ts +6 -6
  104. package/dist/bot/logic/awarenessImpl.js +0 -132
  105. package/dist/bot/logic/awarenessImpl.js.map +0 -1
  106. package/dist/bot/logic/building/ArtilleryUnit.js.map +0 -1
  107. package/dist/bot/logic/building/building.js.map +0 -1
  108. package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
@@ -1,11 +1,9 @@
1
- import _ from "lodash";
2
- import { ActionsApi, GameApi, MovementZone, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import { ActionsApi, GameApi, GameMath, MovementZone, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
3
2
  import { Squad } from "../squad.js";
4
3
  import { SquadAction, SquadBehaviour, grabCombatants, noop } from "../squadBehaviour.js";
5
4
  import { MatchAwareness } from "../../awareness.js";
6
- import { getDistanceBetweenPoints } from "../../map/map.js";
7
- import { manageAttackMicro, manageMoveMicro } from "./common.js";
8
- import { DebugLogger } from "../../common/utils.js";
5
+ import { getAttackWeight, manageAttackMicro, manageMoveMicro } from "./common.js";
6
+ import { DebugLogger, maxBy } from "../../common/utils.js";
9
7
 
10
8
  const TARGET_UPDATE_INTERVAL_TICKS = 10;
11
9
  const GRAB_INTERVAL_TICKS = 10;
@@ -38,12 +36,12 @@ export class CombatSquad implements SquadBehaviour {
38
36
  * @param radius
39
37
  */
40
38
  constructor(
41
- private rallyArea: Point2D,
42
- private targetArea: Point2D,
39
+ private rallyArea: Vector2,
40
+ private targetArea: Vector2,
43
41
  private radius: number,
44
42
  ) {}
45
43
 
46
- public setAttackArea(targetArea: Point2D) {
44
+ public setAttackArea(targetArea: Vector2) {
47
45
  this.targetArea = targetArea;
48
46
  }
49
47
 
@@ -55,7 +53,10 @@ export class CombatSquad implements SquadBehaviour {
55
53
  matchAwareness: MatchAwareness,
56
54
  logger: DebugLogger,
57
55
  ): SquadAction {
58
- if (squad.getUnitIds().length > 0 && (!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)) {
56
+ if (
57
+ squad.getUnitIds().length > 0 &&
58
+ (!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)
59
+ ) {
59
60
  this.lastCommand = gameApi.getCurrentTick();
60
61
  const centerOfMass = squad.getCenterOfMass();
61
62
  const maxDistance = squad.getMaxDistanceToCenterOfMass();
@@ -72,7 +73,7 @@ export class CombatSquad implements SquadBehaviour {
72
73
  );
73
74
 
74
75
  if (this.state === SquadState.Gathering) {
75
- const requiredGatherRadius = Math.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
76
+ const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
76
77
  if (
77
78
  centerOfMass &&
78
79
  maxDistance &&
@@ -83,12 +84,12 @@ export class CombatSquad implements SquadBehaviour {
83
84
  manageMoveMicro(actionsApi, unit, centerOfMass);
84
85
  });
85
86
  } else {
86
- logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`)
87
+ logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`);
87
88
  this.state = SquadState.Attacking;
88
89
  }
89
90
  } else {
90
91
  const targetPoint = this.targetArea || playerData.startLocation;
91
- const requiredGatherRadius = Math.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
92
+ const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
92
93
  if (
93
94
  centerOfMass &&
94
95
  maxDistance &&
@@ -96,24 +97,21 @@ export class CombatSquad implements SquadBehaviour {
96
97
  maxDistance > requiredGatherRadius
97
98
  ) {
98
99
  // Switch back to gather mode
99
- logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`)
100
+ logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`);
100
101
  this.state = SquadState.Gathering;
101
102
  return noop();
102
103
  }
103
104
  for (const unit of units) {
104
- if (unit.isIdle) {
105
- const { rx: x, ry: y } = unit.tile;
106
- const range = unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
107
- const nearbyHostiles = matchAwareness.getHostilesNearPoint(x, y, range * 2);
108
- const closest = _.minBy(nearbyHostiles, ({ x: hX, y: hY }) =>
109
- getDistanceBetweenPoints({ x, y }, { x: hX, y: hY }),
110
- );
111
- const closestUnit = closest ? gameApi.getUnitData(closest.unitId) ?? null : null;
112
- if (closestUnit) {
113
- manageAttackMicro(actionsApi, unit, closestUnit);
114
- } else {
115
- manageMoveMicro(actionsApi, unit, targetPoint);
116
- }
105
+ const { rx: x, ry: y } = unit.tile;
106
+ const range = unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
107
+ const nearbyHostiles = matchAwareness
108
+ .getHostilesNearPoint(x, y, range * 2)
109
+ .map(({ unitId }) => gameApi.getUnitData(unitId)) as UnitData[];
110
+ const bestUnit = maxBy(nearbyHostiles, (target) => getAttackWeight(unit, target));
111
+ if (bestUnit) {
112
+ manageAttackMicro(actionsApi, unit, bestUnit);
113
+ } else {
114
+ manageMoveMicro(actionsApi, unit, targetPoint);
117
115
  }
118
116
  }
119
117
  }
@@ -1,8 +1,17 @@
1
- import { ActionsApi, AttackState, ObjectType, OrderType, Point2D, StanceType, UnitData } from "@chronodivide/game-api";
2
- import { getDistanceBetweenUnits } from "../../map/map.js";
1
+ import {
2
+ ActionsApi,
3
+ AttackState,
4
+ ObjectType,
5
+ OrderType,
6
+ StanceType,
7
+ UnitData,
8
+ Vector2,
9
+ ZoneType,
10
+ } from "@chronodivide/game-api";
11
+ import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
3
12
 
4
13
  // Micro methods
5
- export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Point2D) {
14
+ export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Vector2) {
6
15
  if (attacker.name === "E1") {
7
16
  const isDeployed = attacker.stance === StanceType.Deployed;
8
17
  if (isDeployed) {
@@ -37,3 +46,24 @@ export function manageAttackMicro(actionsApi: ActionsApi, attacker: UnitData, ta
37
46
  }
38
47
  actionsApi.orderUnits([attacker.id], orderType, target.id);
39
48
  }
49
+
50
+ /**
51
+ *
52
+ * @param attacker
53
+ * @param target
54
+ * @returns A number describing the weight of the given target for the attacker, or null if it should not attack it.
55
+ */
56
+ export function getAttackWeight(attacker: UnitData, target: UnitData): number | null {
57
+ const { rx: x, ry: y } = attacker.tile;
58
+ const { rx: hX, ry: hY } = target.tile;
59
+
60
+ if (!attacker.primaryWeapon?.projectileRules.isAntiAir && target.zone === ZoneType.Air) {
61
+ return null;
62
+ }
63
+
64
+ if (!attacker.primaryWeapon?.projectileRules.isAntiGround && target.zone === ZoneType.Ground) {
65
+ return null;
66
+ }
67
+
68
+ return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
69
+ }
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { ActionsApi, GameApi, OrderType, PlayerData, Point2D, SideType } from "@chronodivide/game-api";
1
+ import { ActionsApi, GameApi, OrderType, PlayerData, Vector2 } from "@chronodivide/game-api";
2
2
  import { GlobalThreat } from "../../threat/threat.js";
3
3
  import { Squad } from "../squad.js";
4
4
  import { SquadAction, SquadBehaviour, disband, noop, requestSpecificUnits, requestUnits } from "../squadBehaviour.js";
@@ -11,7 +11,7 @@ export class RetreatSquad implements SquadBehaviour {
11
11
 
12
12
  constructor(
13
13
  private unitIds: number[],
14
- private retreatToPoint: Point2D,
14
+ private retreatToPoint: Vector2,
15
15
  ) {}
16
16
 
17
17
  public onAiUpdate(
@@ -1,13 +1,10 @@
1
- import { ActionsApi, GameApi, OrderType, PlayerData, Point2D, SideType } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../../threat/threat.js";
1
+ import { ActionsApi, GameApi, OrderType, PlayerData, Vector2 } from "@chronodivide/game-api";
3
2
  import { Squad } from "../squad.js";
4
3
  import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
5
4
  import { MatchAwareness } from "../../awareness.js";
6
- import { getUnseenStartingLocations } from "../../common/scout.js";
7
- import { match } from "assert";
8
5
  import { DebugLogger } from "../../common/utils.js";
9
- import _ from "lodash";
10
- import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
6
+ import { getDistanceBetweenTileAndPoint } from "../../map/map.js";
7
+ import { PrioritisedScoutTarget } from "../../common/scout.js";
11
8
 
12
9
  const SCOUT_MOVE_COOLDOWN_TICKS = 30;
13
10
 
@@ -19,10 +16,11 @@ const MAX_ATTEMPTS_PER_TARGET = 5;
19
16
  const MAX_TICKS_PER_TARGET = 600;
20
17
 
21
18
  export class ScoutingSquad implements SquadBehaviour {
22
- private scoutTarget: Point2D | null = null;
19
+ private scoutTarget: Vector2 | null = null;
23
20
  private attemptsOnCurrentTarget: number = 0;
24
21
  private scoutTargetRefreshedAt: number = 0;
25
22
  private lastMoveCommandTick: number = 0;
23
+ private scoutTargetIsPermanent: boolean = false;
26
24
 
27
25
  // Minimum distance from a scout to the target.
28
26
  private scoutMinDistance?: number;
@@ -53,17 +51,19 @@ export class ScoutingSquad implements SquadBehaviour {
53
51
  return requestUnits(scoutNames, 100);
54
52
  } else if (this.scoutTarget) {
55
53
  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();
54
+ if (!this.scoutTargetIsPermanent) {
55
+ if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
56
+ logger(
57
+ `Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
58
+ );
59
+ this.setScoutTarget(null, 0);
60
+ return noop();
61
+ }
62
+ if (gameApi.getCurrentTick() > this.scoutTargetRefreshedAt + MAX_TICKS_PER_TARGET) {
63
+ logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too long, moving to next`);
64
+ this.setScoutTarget(null, 0);
65
+ return noop();
66
+ }
67
67
  }
68
68
  const targetTile = gameApi.mapApi.getTile(this.scoutTarget.x, this.scoutTarget.y);
69
69
  if (!targetTile) {
@@ -77,11 +77,8 @@ export class ScoutingSquad implements SquadBehaviour {
77
77
  }
78
78
  });
79
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
- )!;
80
+ const distances = scouts.map((unit) => getDistanceBetweenTileAndPoint(unit.tile, this.scoutTarget!));
81
+ const newMinDistance = Math.min(...distances);
85
82
  if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
86
83
  logger(
87
84
  `Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`,
@@ -95,20 +92,21 @@ export class ScoutingSquad implements SquadBehaviour {
95
92
  this.setScoutTarget(null, gameApi.getCurrentTick());
96
93
  }
97
94
  } else {
98
- const candidatePoint = matchAwareness.getScoutingManager().getNewScoutTarget()?.asPoint2D();
99
- if (!candidatePoint) {
95
+ const nextScoutTarget = matchAwareness.getScoutingManager().getNewScoutTarget();
96
+ if (!nextScoutTarget) {
100
97
  logger(`No more scouting targets available, disbanding.`);
101
98
  return disband();
102
99
  }
103
- this.setScoutTarget(candidatePoint, gameApi.getCurrentTick());
100
+ this.setScoutTarget(nextScoutTarget, gameApi.getCurrentTick());
104
101
  }
105
102
  return noop();
106
103
  }
107
104
 
108
- setScoutTarget(point: Point2D | null, currentTick: number) {
105
+ setScoutTarget(target: PrioritisedScoutTarget | null, currentTick: number) {
109
106
  this.attemptsOnCurrentTarget = 0;
110
107
  this.scoutTargetRefreshedAt = currentTick;
111
- this.scoutTarget = point;
108
+ this.scoutTarget = target?.asVector2() ?? null;
112
109
  this.scoutMinDistance = undefined;
110
+ this.scoutTargetIsPermanent = target?.isPermanent ?? false;
113
111
  }
114
112
  }
@@ -1,10 +1,8 @@
1
- import { ActionsApi, GameApi, PlayerData, Point2D, TechnoRules, Tile, UnitData } from "@chronodivide/game-api";
1
+ import { ActionsApi, GameApi, PlayerData, TechnoRules, Tile, UnitData, Vector2 } from "@chronodivide/game-api";
2
2
  import { Mission } from "../mission/mission.js";
3
- import { GlobalThreat } from "../threat/threat.js";
4
3
  import { SquadAction, SquadBehaviour, disband } from "./squadBehaviour.js";
5
4
  import { MatchAwareness } from "../awareness.js";
6
- import { getDistanceBetweenPoints } from "../map/map.js";
7
- import _ from "lodash";
5
+ import { getDistanceBetweenTileAndPoint } from "../map/map.js";
8
6
  import { DebugLogger } from "../common/utils.js";
9
7
 
10
8
  export enum SquadLiveness {
@@ -19,7 +17,7 @@ export type SquadConstructionRequest = {
19
17
  };
20
18
 
21
19
  const calculateCenterOfMass: (unitTiles: Tile[]) => {
22
- centerOfMass: Point2D;
20
+ centerOfMass: Vector2;
23
21
  maxDistance: number;
24
22
  } | null = (unitTiles) => {
25
23
  if (unitTiles.length === 0) {
@@ -35,14 +33,11 @@ const calculateCenterOfMass: (unitTiles: Tile[]) => {
35
33
  },
36
34
  { x: 0, y: 0 },
37
35
  );
38
- const centerOfMass = {
39
- x: Math.round(sums.x / unitTiles.length),
40
- y: Math.round(sums.y / unitTiles.length),
41
- };
36
+ const centerOfMass = new Vector2(Math.round(sums.x / unitTiles.length), Math.round(sums.y / unitTiles.length));
42
37
 
43
38
  // max distance of units to the center of mass
44
- const distances = unitTiles.map((tile) => getDistanceBetweenPoints({ x: tile.rx, y: tile.ry }, centerOfMass));
45
- const maxDistance = _.max(distances)!;
39
+ const distances = unitTiles.map((tile) => getDistanceBetweenTileAndPoint(tile, centerOfMass));
40
+ const maxDistance = Math.max(...distances);
46
41
  return { centerOfMass, maxDistance };
47
42
  };
48
43
 
@@ -50,7 +45,7 @@ export class Squad {
50
45
  private unitIds: number[] = [];
51
46
  private liveness: SquadLiveness = SquadLiveness.SquadActive;
52
47
  private lastLivenessUpdateTick: number = 0;
53
- private centerOfMass: Point2D | null = null;
48
+ private centerOfMass: Vector2 | null = null;
54
49
  private maxDistanceToCenterOfMass: number | null = null;
55
50
 
56
51
  constructor(
@@ -77,7 +72,7 @@ export class Squad {
77
72
  actionsApi: ActionsApi,
78
73
  playerData: PlayerData,
79
74
  matchAwareness: MatchAwareness,
80
- logger: DebugLogger
75
+ logger: DebugLogger,
81
76
  ): SquadAction {
82
77
  this.updateLiveness(gameApi);
83
78
  const movableUnitTiles = this.unitIds
@@ -1,5 +1,4 @@
1
- import { ActionsApi, GameApi, PlayerData, Point2D } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../threat/threat.js";
1
+ import { ActionsApi, GameApi, PlayerData, Vector2 } from "@chronodivide/game-api";
3
2
  import { Squad } from "./squad.js";
4
3
  import { MatchAwareness } from "../awareness.js";
5
4
  import { DebugLogger } from "../common/utils.js";
@@ -26,22 +25,22 @@ export type SquadActionRequestSpecificUnits = {
26
25
  };
27
26
  export type SquadActionGrabFreeCombatants = {
28
27
  type: "requestCombatants";
29
- point: Point2D;
28
+ point: Vector2;
30
29
  radius: number;
31
30
  };
32
31
 
33
- export const noop = () => ({ type: "noop" } as SquadActionNoop);
32
+ export const noop = () => ({ type: "noop" }) as SquadActionNoop;
34
33
 
35
- export const disband = () => ({ type: "disband" } as SquadActionDisband);
34
+ export const disband = () => ({ type: "disband" }) as SquadActionDisband;
36
35
 
37
36
  export const requestUnits = (unitNames: string[], priority: number) =>
38
- ({ type: "request", unitNames, priority } as SquadActionRequestUnits);
37
+ ({ type: "request", unitNames, priority }) as SquadActionRequestUnits;
39
38
 
40
39
  export const requestSpecificUnits = (unitIds: number[], priority: number) =>
41
- ({ type: "requestSpecific", unitIds, priority } as SquadActionRequestSpecificUnits);
40
+ ({ type: "requestSpecific", unitIds, priority }) as SquadActionRequestSpecificUnits;
42
41
 
43
- export const grabCombatants = (point: Point2D, radius: number) =>
44
- ({ type: "requestCombatants", point, radius } as SquadActionGrabFreeCombatants);
42
+ export const grabCombatants = (point: Vector2, radius: number) =>
43
+ ({ type: "requestCombatants", point, radius }) as SquadActionGrabFreeCombatants;
45
44
 
46
45
  export type SquadAction =
47
46
  | SquadActionNoop
@@ -58,6 +57,6 @@ export interface SquadBehaviour {
58
57
  playerData: PlayerData,
59
58
  squad: Squad,
60
59
  matchAwareness: MatchAwareness,
61
- logger: DebugLogger
60
+ logger: DebugLogger,
62
61
  ): SquadAction;
63
62
  }
@@ -1,7 +1,6 @@
1
1
  // Meta-controller for forming and controlling squads.
2
2
 
3
3
  import { ActionsApi, GameApi, PlayerData, UnitData } from "@chronodivide/game-api";
4
- import { GlobalThreat } from "../threat/threat.js";
5
4
  import { Squad, SquadLiveness } from "./squad.js";
6
5
  import {
7
6
  SquadAction,
@@ -13,8 +12,7 @@ import {
13
12
  } from "./squadBehaviour.js";
14
13
  import { MatchAwareness } from "../awareness.js";
15
14
  import { getDistanceBetween } from "../map/map.js";
16
- import _ from "lodash";
17
- import { DebugLogger } from "../common/utils.js";
15
+ import { countBy } from "../common/utils.js";
18
16
 
19
17
  type SquadWithAction<T> = {
20
18
  squad: Squad;
@@ -181,7 +179,6 @@ export class SquadController {
181
179
  .map((unit) => unit!);
182
180
 
183
181
  type AssignmentWithType = { unitName: string; squad: string; method: "type" | "grab" };
184
- // [squadName][unitName]['type' | 'grab']
185
182
  const newAssignmentsByType = freeUnits
186
183
  .flatMap((freeUnit) => {
187
184
  if (unitTypeToHighestRequest.hasOwnProperty(freeUnit.name)) {
@@ -244,7 +241,7 @@ export class SquadController {
244
241
  }
245
242
 
246
243
  public debugSquads(gameApi: GameApi) {
247
- const unitsInSquad = (unitIds: number[]) => _.countBy(unitIds, (unitId) => gameApi.getUnitData(unitId)?.name);
244
+ const unitsInSquad = (unitIds: number[]) => countBy(unitIds, (unitId) => gameApi.getUnitData(unitId)?.name);
248
245
 
249
246
  this.squads.forEach((squad) => {
250
247
  this.logger(
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { GameApi, MovementZone, ObjectType, PlayerData, UnitData } from "@chronodivide/game-api";
1
+ import { GameApi, GameMath, MovementZone, ObjectType, PlayerData, UnitData } from "@chronodivide/game-api";
2
2
  import { GlobalThreat } from "./threat.js";
3
3
 
4
4
  export function calculateGlobalThreat(game: GameApi, playerData: PlayerData, visibleAreaPercent: number): GlobalThreat {
@@ -75,13 +75,14 @@ function calculateFirepowerForUnit(unitData: UnitData): number {
75
75
  if (unitData.primaryWeapon) {
76
76
  threat +=
77
77
  (hpRatio *
78
- ((unitData.primaryWeapon.rules.damage + 1) * Math.sqrt(unitData.primaryWeapon.rules.range + 1))) /
78
+ ((unitData.primaryWeapon.rules.damage + 1) * GameMath.sqrt(unitData.primaryWeapon.rules.range + 1))) /
79
79
  Math.max(unitData.primaryWeapon.cooldownTicks, 1);
80
80
  }
81
81
  if (unitData.secondaryWeapon) {
82
82
  threat +=
83
83
  (hpRatio *
84
- ((unitData.secondaryWeapon.rules.damage + 1) * Math.sqrt(unitData.secondaryWeapon.rules.range + 1))) /
84
+ ((unitData.secondaryWeapon.rules.damage + 1) *
85
+ GameMath.sqrt(unitData.secondaryWeapon.rules.range + 1))) /
85
86
  Math.max(unitData.secondaryWeapon.cooldownTicks, 1);
86
87
  }
87
88
  return Math.min(800, threat);
package/src/exampleBot.ts CHANGED
@@ -6,17 +6,17 @@ async function main() {
6
6
  Ladder maps:
7
7
  CDR2 1v1 2_malibu_cliffs_le.map
8
8
  CDR2 1v1 4_country_swing_le_v2.map
9
- CDR2 1v1 mp01t4.map
10
- CDR2 1v1 tn04t2.map
11
- CDR2 1v1 mp10s4.map
9
+ CDR2 1v1 mp01t4.map, large map, oil derricks
10
+ CDR2 1v1 tn04t2.map, small map
11
+ CDR2 1v1 mp10s4.map <- depth charge, naval map (not supported)
12
12
  CDR2 1v1 heckcorners.map
13
13
  CDR2 1v1 4_montana_dmz_le.map
14
14
  CDR2 1v1 barrel.map
15
15
 
16
16
  Other maps:
17
- mp03t4
17
+ mp03t4 large map, no oil derricks
18
18
  */
19
- const mapName = "mp01t4.map";
19
+ const mapName = "mp10s4.map";
20
20
  // Bot names must be unique in online mode
21
21
  const botName = `Joe${String(Date.now()).substr(-6)}`;
22
22
  const otherBotName = `Bob${String(Date.now() + 1).substr(-6)}`;
@@ -48,7 +48,7 @@ async function main() {
48
48
  };
49
49
 
50
50
  const offlineSettings = {
51
- agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "French", true)],
51
+ agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "Russians", true)],
52
52
  };
53
53
 
54
54
  const game = await cdapi.createGame({