@supalosa/chronodivide-bot 0.2.2-b → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/TODO.md +0 -2
  2. package/dist/bot/bot.js +2 -2
  3. package/dist/bot/bot.js.map +1 -1
  4. package/dist/bot/logic/awareness.js +7 -6
  5. package/dist/bot/logic/awareness.js.map +1 -1
  6. package/dist/bot/logic/building/ArtilleryUnit.js +6 -5
  7. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  8. package/dist/bot/logic/building/artilleryUnit.js.map +1 -1
  9. package/dist/bot/logic/building/basicAirUnit.js +2 -1
  10. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  11. package/dist/bot/logic/building/basicGroundUnit.js +3 -2
  12. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  13. package/dist/bot/logic/building/buildingRules.js +4 -10
  14. package/dist/bot/logic/building/buildingRules.js.map +1 -1
  15. package/dist/bot/logic/building/harvester.js.map +1 -1
  16. package/dist/bot/logic/building/resourceCollectionBuilding.js +5 -3
  17. package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -1
  18. package/dist/bot/logic/common/scout.js +49 -32
  19. package/dist/bot/logic/common/scout.js.map +1 -1
  20. package/dist/bot/logic/map/map.js +17 -19
  21. package/dist/bot/logic/map/map.js.map +1 -1
  22. package/dist/bot/logic/map/sector.js +10 -13
  23. package/dist/bot/logic/map/sector.js.map +1 -1
  24. package/dist/bot/logic/mission/missions/attackMission.js +2 -2
  25. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  26. package/dist/bot/logic/mission/missions/defenceMission.js +2 -1
  27. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  28. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  29. package/dist/bot/logic/squad/behaviours/combatSquad.js +3 -3
  30. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
  31. package/dist/bot/logic/squad/behaviours/common.js +2 -2
  32. package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
  33. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
  34. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +21 -17
  35. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
  36. package/dist/bot/logic/squad/squad.js +4 -6
  37. package/dist/bot/logic/squad/squad.js.map +1 -1
  38. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
  39. package/dist/bot/logic/squad/squadController.js +35 -23
  40. package/dist/bot/logic/squad/squadController.js.map +1 -1
  41. package/dist/bot/logic/threat/threatCalculator.js +4 -3
  42. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  43. package/dist/exampleBot.js +1 -1
  44. package/package.json +3 -3
  45. package/src/bot/bot.ts +4 -5
  46. package/src/bot/logic/awareness.ts +12 -12
  47. package/src/bot/logic/building/antiGroundStaticDefence.ts +11 -7
  48. package/src/bot/logic/building/artilleryUnit.ts +14 -17
  49. package/src/bot/logic/building/basicAirUnit.ts +9 -7
  50. package/src/bot/logic/building/basicGroundUnit.ts +3 -3
  51. package/src/bot/logic/building/buildingRules.ts +11 -13
  52. package/src/bot/logic/building/harvester.ts +7 -4
  53. package/src/bot/logic/building/resourceCollectionBuilding.ts +8 -12
  54. package/src/bot/logic/common/scout.ts +83 -38
  55. package/src/bot/logic/map/map.ts +26 -30
  56. package/src/bot/logic/map/sector.ts +17 -21
  57. package/src/bot/logic/mission/missions/attackMission.ts +5 -5
  58. package/src/bot/logic/mission/missions/defenceMission.ts +3 -3
  59. package/src/bot/logic/mission/missions/retreatMission.ts +2 -2
  60. package/src/bot/logic/squad/behaviours/combatSquad.ts +6 -6
  61. package/src/bot/logic/squad/behaviours/common.ts +3 -3
  62. package/src/bot/logic/squad/behaviours/retreatSquad.ts +2 -2
  63. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +25 -22
  64. package/src/bot/logic/squad/squad.ts +6 -10
  65. package/src/bot/logic/squad/squadBehaviour.ts +9 -10
  66. package/src/bot/logic/squad/squadController.ts +0 -1
  67. package/src/bot/logic/threat/threatCalculator.ts +100 -99
  68. package/src/exampleBot.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { GameApi, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import { GameApi, GameMath, PlayerData, Vector2 } from "@chronodivide/game-api";
2
2
  import { Sector, SectorCache } from "../map/sector";
3
3
  import { DebugLogger } from "./utils";
4
4
  import { PriorityQueue } from "@datastructures-js/priority-queue";
@@ -14,14 +14,18 @@ export const getUnseenStartingLocations = (gameApi: GameApi, playerData: PlayerD
14
14
  return unseenStartingLocations;
15
15
  };
16
16
 
17
- class PrioritisedScoutTarget {
18
- private _targetPoint2D?: Point2D;
17
+ export class PrioritisedScoutTarget {
18
+ private _targetPoint?: Vector2;
19
19
  private _targetSector?: Sector;
20
20
  private _priority: number;
21
21
 
22
- constructor(priority: number, target: Point2D | Sector) {
22
+ constructor(
23
+ priority: number,
24
+ target: Vector2 | Sector,
25
+ private permanent: boolean = false,
26
+ ) {
23
27
  if (target.hasOwnProperty("x") && target.hasOwnProperty("y")) {
24
- this._targetPoint2D = target as Point2D;
28
+ this._targetPoint = target as Vector2;
25
29
  } else if (target.hasOwnProperty("sectorStartPoint")) {
26
30
  this._targetSector = target as Sector;
27
31
  } else {
@@ -34,49 +38,49 @@ class PrioritisedScoutTarget {
34
38
  return this._priority;
35
39
  }
36
40
 
37
- asPoint2D() {
38
- return this._targetPoint2D ?? this._targetSector?.sectorStartPoint ?? null;
41
+ asVector2() {
42
+ return this._targetPoint ?? this._targetSector?.sectorStartPoint ?? null;
39
43
  }
40
44
 
41
45
  get targetSector() {
42
46
  return this._targetSector;
43
47
  }
48
+
49
+ get isPermanent() {
50
+ return this.permanent;
51
+ }
44
52
  }
45
53
 
46
54
  const ENEMY_SPAWN_POINT_PRIORITY = 100;
47
55
 
48
56
  // Amount of sectors around the starting sector to try to scout.
49
- const NEARBY_SECTOR_RADIUS = 2;
57
+ const NEARBY_SECTOR_STARTING_RADIUS = 2;
50
58
  const NEARBY_SECTOR_BASE_PRIORITY = 1000;
51
59
 
60
+ // Amount of ticks per 'radius' to expand for scouting.
61
+ const SCOUTING_RADIUS_EXPANSION_TICKS = 9000; // 10 minutes
62
+
52
63
  export class ScoutingManager {
53
64
  private scoutingQueue: PriorityQueue<PrioritisedScoutTarget>;
54
65
 
66
+ private queuedRadius = NEARBY_SECTOR_STARTING_RADIUS;
67
+
55
68
  constructor(private logger: DebugLogger) {
56
69
  // Order by descending priority.
57
- this.scoutingQueue = new PriorityQueue((a: PrioritisedScoutTarget, b: PrioritisedScoutTarget) => b.priority - a.priority);
70
+ this.scoutingQueue = new PriorityQueue(
71
+ (a: PrioritisedScoutTarget, b: PrioritisedScoutTarget) => b.priority - a.priority,
72
+ );
58
73
  }
59
74
 
60
- onGameStart(gameApi: GameApi, playerData: PlayerData, sectorCache: SectorCache) {
61
- // Queue hostile starting locations with high priority.
62
- gameApi.mapApi
63
- .getStartingLocations()
64
- .filter((startingLocation) => {
65
- if (startingLocation == playerData.startLocation) {
66
- return false;
67
- }
68
- let tile = gameApi.mapApi.getTile(startingLocation.x, startingLocation.y);
69
- return tile ? !gameApi.mapApi.isVisibleTile(tile, playerData.name) : false;
70
- })
71
- .map((tile) => new PrioritisedScoutTarget(ENEMY_SPAWN_POINT_PRIORITY, tile))
72
- .forEach((target) => {
73
- this.logger(`Adding ${target.asPoint2D()?.x},${target.asPoint2D()?.y} to initial scouting queue`);
74
- this.scoutingQueue.enqueue(target);
75
- });
76
-
77
- // Queue nearby sectors.
78
- const { x: startX, y: startY } = playerData.startLocation;
79
- const { x: sectorsX, y: sectorsY } = sectorCache.getSectorBounds();
75
+ addRadiusToScout(
76
+ gameApi: GameApi,
77
+ centerPoint: Vector2,
78
+ sectorCache: SectorCache,
79
+ radius: number,
80
+ startingPriority: number,
81
+ ) {
82
+ const { x: startX, y: startY } = centerPoint;
83
+ const { width: sectorsX, height: sectorsY } = sectorCache.getSectorBounds();
80
84
  const startingSector = sectorCache.getSectorCoordinatesForWorldPosition(startX, startY);
81
85
 
82
86
  if (!startingSector) {
@@ -84,24 +88,25 @@ export class ScoutingManager {
84
88
  }
85
89
 
86
90
  for (
87
- let x: number = Math.max(0, startingSector.sectorX - NEARBY_SECTOR_RADIUS);
88
- x <= Math.min(sectorsX, startingSector.sectorX + NEARBY_SECTOR_RADIUS);
91
+ let x: number = Math.max(0, startingSector.sectorX - radius);
92
+ x < Math.min(sectorsX, startingSector.sectorX + radius);
89
93
  ++x
90
94
  ) {
91
95
  for (
92
- let y: number = Math.max(0, startingSector.sectorY - NEARBY_SECTOR_RADIUS);
93
- y <= Math.min(sectorsY, startingSector.sectorY + NEARBY_SECTOR_RADIUS);
96
+ let y: number = Math.max(0, startingSector.sectorY - radius);
97
+ y < Math.min(sectorsY, startingSector.sectorY + radius);
94
98
  ++y
95
99
  ) {
96
100
  if (x === startingSector?.sectorX && y === startingSector?.sectorY) {
97
101
  continue;
98
102
  }
99
103
  // Make it scout closer sectors first.
100
- const distanceFactor = Math.pow(x - startingSector.sectorX, 2) + Math.pow(y - startingSector.sectorY, 2);
104
+ const distanceFactor =
105
+ GameMath.pow(x - startingSector.sectorX, 2) + GameMath.pow(y - startingSector.sectorY, 2);
101
106
  const sector = sectorCache.getSector(x, y);
102
107
  if (sector) {
103
- const maybeTarget = new PrioritisedScoutTarget(NEARBY_SECTOR_BASE_PRIORITY - distanceFactor, sector);
104
- const maybePoint = maybeTarget.asPoint2D();
108
+ const maybeTarget = new PrioritisedScoutTarget(startingPriority - distanceFactor, sector);
109
+ const maybePoint = maybeTarget.asVector2();
105
110
  if (maybePoint && gameApi.mapApi.getTile(maybePoint.x, maybePoint.y)) {
106
111
  this.scoutingQueue.enqueue(maybeTarget);
107
112
  }
@@ -110,12 +115,39 @@ export class ScoutingManager {
110
115
  }
111
116
  }
112
117
 
113
- onAiUpdate(gameApi: GameApi, playerData: PlayerData) {
118
+ onGameStart(gameApi: GameApi, playerData: PlayerData, sectorCache: SectorCache) {
119
+ // Queue hostile starting locations with high priority and as permanent scouting candidates.
120
+ gameApi.mapApi
121
+ .getStartingLocations()
122
+ .filter((startingLocation) => {
123
+ if (startingLocation == playerData.startLocation) {
124
+ return false;
125
+ }
126
+ let tile = gameApi.mapApi.getTile(startingLocation.x, startingLocation.y);
127
+ return tile ? !gameApi.mapApi.isVisibleTile(tile, playerData.name) : false;
128
+ })
129
+ .map((tile) => new PrioritisedScoutTarget(ENEMY_SPAWN_POINT_PRIORITY, tile, true))
130
+ .forEach((target) => {
131
+ this.logger(`Adding ${target.asVector2()?.x},${target.asVector2()?.y} to initial scouting queue`);
132
+ this.scoutingQueue.enqueue(target);
133
+ });
134
+
135
+ // Queue sectors near the spawn point.
136
+ this.addRadiusToScout(
137
+ gameApi,
138
+ playerData.startLocation,
139
+ sectorCache,
140
+ NEARBY_SECTOR_STARTING_RADIUS,
141
+ NEARBY_SECTOR_BASE_PRIORITY,
142
+ );
143
+ }
144
+
145
+ onAiUpdate(gameApi: GameApi, playerData: PlayerData, sectorCache: SectorCache) {
114
146
  const currentHead = this.scoutingQueue.front();
115
147
  if (!currentHead) {
116
148
  return;
117
149
  }
118
- const head = currentHead.asPoint2D();
150
+ const head = currentHead.asVector2();
119
151
  if (!head) {
120
152
  this.scoutingQueue.dequeue();
121
153
  return;
@@ -126,6 +158,19 @@ export class ScoutingManager {
126
158
  this.logger(`head point is visible, dequeueing`);
127
159
  this.scoutingQueue.dequeue();
128
160
  }
161
+
162
+ const requiredRadius = Math.floor(gameApi.getCurrentTick() / SCOUTING_RADIUS_EXPANSION_TICKS);
163
+ if (requiredRadius > this.queuedRadius) {
164
+ this.logger(`expanding scouting radius from ${this.queuedRadius} to ${requiredRadius}`);
165
+ this.addRadiusToScout(
166
+ gameApi,
167
+ playerData.startLocation,
168
+ sectorCache,
169
+ requiredRadius,
170
+ NEARBY_SECTOR_BASE_PRIORITY,
171
+ );
172
+ this.queuedRadius = requiredRadius;
173
+ }
129
174
  }
130
175
 
131
176
  getNewScoutTarget() {
@@ -1,27 +1,15 @@
1
- import { GameApi, MapApi, PlayerData, Point2D, Tile, UnitData } from "@chronodivide/game-api";
1
+ import { GameApi, GameMath, MapApi, PlayerData, Size, Tile, UnitData, Vector2 } from "@chronodivide/game-api";
2
2
  import { maxBy } from "../common/utils.js";
3
3
 
4
- const MAX_WIDTH_AND_HEIGHT = 500;
5
-
6
- // Expensive one-time call to determine the size of the map.
7
- // The result is a point just outside the bounds of the map.
8
- export function determineMapBounds(mapApi: MapApi): Point2D {
9
- // Probably want to ask for an API change to get this.
10
- // Note that the maps is not always a rectangle!
11
- const zeroTile = { rx: 0, ry: 0 } as Tile;
12
- const allTiles = mapApi.getTilesInRect(zeroTile, { width: MAX_WIDTH_AND_HEIGHT, height: MAX_WIDTH_AND_HEIGHT });
13
-
14
- const maxX = maxBy(allTiles, (tile) => tile.rx)?.rx!;
15
- const maxY = maxBy(allTiles, (tile) => tile.ry)?.ry!;
16
-
17
- return { x: maxX, y: maxY };
4
+ export function determineMapBounds(mapApi: MapApi): Size {
5
+ return mapApi.getRealMapSize();
18
6
  }
19
7
 
20
8
  export function calculateAreaVisibility(
21
9
  mapApi: MapApi,
22
10
  playerData: PlayerData,
23
- startPoint: Point2D,
24
- endPoint: Point2D,
11
+ startPoint: Vector2,
12
+ endPoint: Vector2,
25
13
  ): { visibleTiles: number; validTiles: number } {
26
14
  let validTiles: number = 0,
27
15
  visibleTiles: number = 0;
@@ -42,29 +30,37 @@ export function calculateAreaVisibility(
42
30
 
43
31
  export function getPointTowardsOtherPoint(
44
32
  gameApi: GameApi,
45
- startLocation: Point2D,
46
- endLocation: Point2D,
33
+ startLocation: Vector2,
34
+ endLocation: Vector2,
47
35
  minRadius: number,
48
36
  maxRadius: number,
49
37
  randomAngle: number,
50
- ): Point2D {
38
+ ): Vector2 {
39
+ // TODO: Use proper vector maths here.
51
40
  let radius = minRadius + Math.round(gameApi.generateRandom() * (maxRadius - minRadius));
52
- let directionToSpawn = Math.atan2(endLocation.y - startLocation.y, endLocation.x - startLocation.x);
41
+ let directionToEndLocation = GameMath.atan2(endLocation.y - startLocation.y, endLocation.x - startLocation.x);
53
42
  let randomisedDirection =
54
- directionToSpawn - (randomAngle * (Math.PI / 12) + 2 * randomAngle * gameApi.generateRandom() * (Math.PI / 12));
55
- let candidatePointX = Math.round(startLocation.x + Math.cos(randomisedDirection) * radius);
56
- let candidatePointY = Math.round(startLocation.y + Math.sin(randomisedDirection) * radius);
57
- return { x: candidatePointX, y: candidatePointY };
43
+ directionToEndLocation -
44
+ (randomAngle * (Math.PI / 12) + 2 * randomAngle * gameApi.generateRandom() * (Math.PI / 12));
45
+ let candidatePointX = Math.round(startLocation.x + GameMath.cos(randomisedDirection) * radius);
46
+ let candidatePointY = Math.round(startLocation.y + GameMath.sin(randomisedDirection) * radius);
47
+ return new Vector2(candidatePointX, candidatePointY);
48
+ }
49
+
50
+ export function getDistanceBetweenPoints(startLocation: Vector2, endLocation: Vector2): number {
51
+ // TODO: Remove this now we have Vector2s.
52
+ return startLocation.distanceTo(endLocation);
58
53
  }
59
54
 
60
- export function getDistanceBetweenPoints(startLocation: Point2D, endLocation: Point2D): number {
61
- return Math.sqrt((startLocation.x - endLocation.x) ** 2 + (startLocation.y - endLocation.y) ** 2);
55
+ export function getDistanceBetweenTileAndPoint(tile: Tile, vector: Vector2): number {
56
+ // TODO: Remove this now we have Vector2s.
57
+ return new Vector2(tile.rx, tile.ry).distanceTo(vector);
62
58
  }
63
59
 
64
60
  export function getDistanceBetweenUnits(unit1: UnitData, unit2: UnitData): number {
65
- return getDistanceBetweenPoints({ x: unit1.tile.rx, y: unit1.tile.ry }, { x: unit2.tile.rx, y: unit2.tile.ry });
61
+ return new Vector2(unit1.tile.rx, unit1.tile.ry).distanceTo(new Vector2(unit2.tile.rx, unit2.tile.ry));
66
62
  }
67
63
 
68
- export function getDistanceBetween(unit: UnitData, point: Point2D): number {
69
- return getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, point);
64
+ export function getDistanceBetween(unit: UnitData, point: Vector2): number {
65
+ return getDistanceBetweenPoints(new Vector2(unit.tile.rx, unit.tile.ry), point);
70
66
  }
@@ -1,6 +1,6 @@
1
1
  // A sector is a uniform-sized segment of the map.
2
2
 
3
- import { MapApi, PlayerData, Point2D, Tile } from "@chronodivide/game-api";
3
+ import { MapApi, PlayerData, Size, Tile, Vector2 } from "@chronodivide/game-api";
4
4
  import { calculateAreaVisibility } from "./map.js";
5
5
 
6
6
  export const SECTOR_SIZE = 8;
@@ -11,7 +11,7 @@ export class Sector {
11
11
  private sectorLastExploredAt: number | undefined;
12
12
 
13
13
  constructor(
14
- public sectorStartPoint: Point2D,
14
+ public sectorStartPoint: Vector2,
15
15
  public sectorStartTile: Tile | undefined,
16
16
  public sectorVisibilityPct: number | undefined,
17
17
  public sectorVisibilityLastCheckTick: number | undefined,
@@ -40,23 +40,25 @@ export class Sector {
40
40
 
41
41
  export class SectorCache {
42
42
  private sectors: Sector[][] = [];
43
- private mapBounds: Point2D;
43
+ private mapBounds: Size;
44
44
  private sectorsX: number;
45
45
  private sectorsY: number;
46
46
  private lastUpdatedSectorX: number | undefined;
47
47
  private lastUpdatedSectorY: number | undefined;
48
48
 
49
- constructor(mapApi: MapApi, mapBounds: Point2D) {
49
+ constructor(mapApi: MapApi, mapBounds: Size) {
50
50
  this.mapBounds = mapBounds;
51
- this.sectorsX = Math.ceil(mapBounds.x / SECTOR_SIZE);
52
- this.sectorsY = Math.ceil(mapBounds.y / SECTOR_SIZE);
51
+ this.sectorsX = Math.ceil(mapBounds.width / SECTOR_SIZE);
52
+ this.sectorsY = Math.ceil(mapBounds.height / SECTOR_SIZE);
53
53
  this.sectors = new Array(this.sectorsX);
54
54
  for (let xx = 0; xx < this.sectorsX; ++xx) {
55
55
  this.sectors[xx] = new Array(this.sectorsY);
56
56
  for (let yy = 0; yy < this.sectorsY; ++yy) {
57
+ const tileX = xx * SECTOR_SIZE;
58
+ const tileY = yy * SECTOR_SIZE;
57
59
  this.sectors[xx][yy] = new Sector(
58
- { x: xx * SECTOR_SIZE, y: yy * SECTOR_SIZE },
59
- mapApi.getTile(xx * SECTOR_SIZE, yy * SECTOR_SIZE),
60
+ new Vector2(tileX, tileY),
61
+ mapApi.getTile(tileX, tileY),
60
62
  undefined,
61
63
  undefined,
62
64
  );
@@ -64,7 +66,7 @@ export class SectorCache {
64
66
  }
65
67
  }
66
68
 
67
- public getMapBounds(): Point2D {
69
+ public getMapBounds(): Size {
68
70
  return this.mapBounds;
69
71
  }
70
72
 
@@ -86,10 +88,7 @@ export class SectorCache {
86
88
  if (sector) {
87
89
  sector.sectorVisibilityLastCheckTick = currentGameTick;
88
90
  let sp = sector.sectorStartPoint;
89
- let ep = {
90
- x: sector.sectorStartPoint.x + SECTOR_SIZE,
91
- y: sector.sectorStartPoint.y + SECTOR_SIZE,
92
- };
91
+ let ep = new Vector2(sp.x + SECTOR_SIZE, sp.y + SECTOR_SIZE);
93
92
  let visibility = calculateAreaVisibility(mapApi, playerData, sp, ep);
94
93
  if (visibility.validTiles > 0) {
95
94
  sector.sectorVisibilityPct = visibility.visibleTiles / visibility.validTiles;
@@ -151,21 +150,18 @@ export class SectorCache {
151
150
  return this.sectors[sectorX][sectorY];
152
151
  }
153
152
 
154
- public getSectorBounds(): Point2D {
155
- return {
156
- x: this.sectorsX,
157
- y: this.sectorsY,
158
- }
153
+ public getSectorBounds(): Size {
154
+ return { width: this.sectorsX, height: this.sectorsY };
159
155
  }
160
156
 
161
157
  public getSectorCoordinatesForWorldPosition(x: number, y: number) {
162
- if (x < 0 || x >= this.mapBounds.x || y < 0 || y >= this.mapBounds.y) {
158
+ if (x < 0 || x >= this.mapBounds.width || y < 0 || y >= this.mapBounds.height) {
163
159
  return undefined;
164
160
  }
165
161
  return {
166
162
  sectorX: Math.floor(x / SECTOR_SIZE),
167
- sectorY: Math.floor(y / SECTOR_SIZE)
168
- }
163
+ sectorY: Math.floor(y / SECTOR_SIZE),
164
+ };
169
165
  }
170
166
 
171
167
  public getSectorForWorldPosition(x: number, y: number): Sector | undefined {
@@ -1,4 +1,4 @@
1
- import { GameApi, ObjectType, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
1
+ import { GameApi, ObjectType, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
2
2
  import { CombatSquad } from "../../squad/behaviours/combatSquad.js";
3
3
  import { Mission, MissionAction, disbandMission, noop } from "../mission.js";
4
4
  import { Squad } from "../../squad/squad.js";
@@ -24,8 +24,8 @@ export class AttackMission extends Mission<AttackFailReason> {
24
24
  constructor(
25
25
  uniqueName: string,
26
26
  priority: number,
27
- private rallyArea: Point2D,
28
- private attackArea: Point2D,
27
+ private rallyArea: Vector2,
28
+ private attackArea: Vector2,
29
29
  private radius: number,
30
30
  logger: DebugLogger,
31
31
  ) {
@@ -76,7 +76,7 @@ export class AttackMissionFactory implements MissionFactory {
76
76
  return "AttackMissionFactory";
77
77
  }
78
78
 
79
- generateTarget(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): Point2D | null {
79
+ generateTarget(gameApi: GameApi, playerData: PlayerData, matchAwareness: MatchAwareness): Vector2 | null {
80
80
  // Randomly decide between harvester and base.
81
81
  try {
82
82
  const tryFocusHarvester = gameApi.generateRandomInt(0, 1) === 0;
@@ -87,7 +87,7 @@ export class AttackMissionFactory implements MissionFactory {
87
87
 
88
88
  const maxUnit = maxBy(enemyUnits, (u) => getTargetWeight(u, tryFocusHarvester));
89
89
  if (maxUnit) {
90
- return { x: maxUnit.tile.rx, y: maxUnit.tile.ry };
90
+ return new Vector2(maxUnit.tile.rx, maxUnit.tile.ry);
91
91
  }
92
92
  } catch (err) {
93
93
  // There's a crash here when accessing a building that got destroyed. Will catch and ignore or now.
@@ -1,4 +1,4 @@
1
- import { GameApi, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import { GameApi, PlayerData, Vector2 } from "@chronodivide/game-api";
2
2
  import { MatchAwareness } from "../../awareness.js";
3
3
  import { MissionController } from "../missionController.js";
4
4
  import { Mission, MissionAction, disbandMission, noop } from "../mission.js";
@@ -21,7 +21,7 @@ export class DefenceMission extends Mission<DefenceFailReason> {
21
21
  constructor(
22
22
  uniqueName: string,
23
23
  priority: number,
24
- private defenceArea: Point2D,
24
+ private defenceArea: Vector2,
25
25
  private radius: number,
26
26
  logger: DebugLogger,
27
27
  ) {
@@ -46,7 +46,7 @@ export class DefenceMission extends Mission<DefenceFailReason> {
46
46
  foundTargets.length
47
47
  } found in area ${this.radius})`,
48
48
  );
49
- this.combatSquad?.setAttackArea({ x: foundTargets[0].x, y: foundTargets[0].y });
49
+ this.combatSquad?.setAttackArea(new Vector2(foundTargets[0].x, foundTargets[0].y));
50
50
  }
51
51
  }
52
52
  return noop();
@@ -1,10 +1,10 @@
1
- import { Point2D } from "@chronodivide/game-api";
2
1
  import { OneTimeMission } from "./oneTimeMission.js";
3
2
  import { RetreatSquad } from "../../squad/behaviours/retreatSquad.js";
4
3
  import { DebugLogger } from "../../common/utils.js";
4
+ import { Vector2 } from "@chronodivide/game-api";
5
5
 
6
6
  export class RetreatMission extends OneTimeMission {
7
- constructor(uniqueName: string, priority: number, retreatToPoint: Point2D, unitIds: number[], logger: DebugLogger) {
7
+ constructor(uniqueName: string, priority: number, retreatToPoint: Vector2, unitIds: number[], logger: DebugLogger) {
8
8
  super(uniqueName, priority, () => new RetreatSquad(unitIds, retreatToPoint), logger);
9
9
  }
10
10
  }
@@ -1,4 +1,4 @@
1
- import { ActionsApi, GameApi, MovementZone, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
1
+ import { ActionsApi, GameApi, GameMath, MovementZone, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
2
2
  import { Squad } from "../squad.js";
3
3
  import { SquadAction, SquadBehaviour, grabCombatants, noop } from "../squadBehaviour.js";
4
4
  import { MatchAwareness } from "../../awareness.js";
@@ -36,12 +36,12 @@ export class CombatSquad implements SquadBehaviour {
36
36
  * @param radius
37
37
  */
38
38
  constructor(
39
- private rallyArea: Point2D,
40
- private targetArea: Point2D,
39
+ private rallyArea: Vector2,
40
+ private targetArea: Vector2,
41
41
  private radius: number,
42
42
  ) {}
43
43
 
44
- public setAttackArea(targetArea: Point2D) {
44
+ public setAttackArea(targetArea: Vector2) {
45
45
  this.targetArea = targetArea;
46
46
  }
47
47
 
@@ -73,7 +73,7 @@ export class CombatSquad implements SquadBehaviour {
73
73
  );
74
74
 
75
75
  if (this.state === SquadState.Gathering) {
76
- const requiredGatherRadius = Math.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
76
+ const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
77
77
  if (
78
78
  centerOfMass &&
79
79
  maxDistance &&
@@ -89,7 +89,7 @@ export class CombatSquad implements SquadBehaviour {
89
89
  }
90
90
  } else {
91
91
  const targetPoint = this.targetArea || playerData.startLocation;
92
- const requiredGatherRadius = Math.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
92
+ const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
93
93
  if (
94
94
  centerOfMass &&
95
95
  maxDistance &&
@@ -3,15 +3,15 @@ import {
3
3
  AttackState,
4
4
  ObjectType,
5
5
  OrderType,
6
- Point2D,
7
6
  StanceType,
8
7
  UnitData,
8
+ Vector2,
9
9
  ZoneType,
10
10
  } from "@chronodivide/game-api";
11
11
  import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
12
12
 
13
13
  // Micro methods
14
- export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Point2D) {
14
+ export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Vector2) {
15
15
  if (attacker.name === "E1") {
16
16
  const isDeployed = attacker.stance === StanceType.Deployed;
17
17
  if (isDeployed) {
@@ -65,5 +65,5 @@ export function getAttackWeight(attacker: UnitData, target: UnitData): number |
65
65
  return null;
66
66
  }
67
67
 
68
- return 1000000 - getDistanceBetweenPoints({ x, y }, { x: hX, y: hY });
68
+ return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
69
69
  }
@@ -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,9 +1,10 @@
1
- import { ActionsApi, GameApi, OrderType, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import { ActionsApi, GameApi, OrderType, PlayerData, Vector2 } from "@chronodivide/game-api";
2
2
  import { Squad } from "../squad.js";
3
3
  import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
4
4
  import { MatchAwareness } from "../../awareness.js";
5
5
  import { DebugLogger } from "../../common/utils.js";
6
- import { getDistanceBetweenPoints } from "../../map/map.js";
6
+ import { getDistanceBetweenTileAndPoint } from "../../map/map.js";
7
+ import { PrioritisedScoutTarget } from "../../common/scout.js";
7
8
 
8
9
  const SCOUT_MOVE_COOLDOWN_TICKS = 30;
9
10
 
@@ -15,10 +16,11 @@ const MAX_ATTEMPTS_PER_TARGET = 5;
15
16
  const MAX_TICKS_PER_TARGET = 600;
16
17
 
17
18
  export class ScoutingSquad implements SquadBehaviour {
18
- private scoutTarget: Point2D | null = null;
19
+ private scoutTarget: Vector2 | null = null;
19
20
  private attemptsOnCurrentTarget: number = 0;
20
21
  private scoutTargetRefreshedAt: number = 0;
21
22
  private lastMoveCommandTick: number = 0;
23
+ private scoutTargetIsPermanent: boolean = false;
22
24
 
23
25
  // Minimum distance from a scout to the target.
24
26
  private scoutMinDistance?: number;
@@ -49,17 +51,19 @@ export class ScoutingSquad implements SquadBehaviour {
49
51
  return requestUnits(scoutNames, 100);
50
52
  } else if (this.scoutTarget) {
51
53
  this.hadUnit = true;
52
- if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
53
- logger(
54
- `Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
55
- );
56
- this.setScoutTarget(null, 0);
57
- return noop();
58
- }
59
- if (gameApi.getCurrentTick() > this.scoutTargetRefreshedAt + MAX_TICKS_PER_TARGET) {
60
- logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too long, moving to next`);
61
- this.setScoutTarget(null, 0);
62
- 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
+ }
63
67
  }
64
68
  const targetTile = gameApi.mapApi.getTile(this.scoutTarget.x, this.scoutTarget.y);
65
69
  if (!targetTile) {
@@ -73,9 +77,7 @@ export class ScoutingSquad implements SquadBehaviour {
73
77
  }
74
78
  });
75
79
  // Check that a scout is actually moving closer to the target.
76
- const distances = scouts.map((unit) =>
77
- getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, this.scoutTarget!),
78
- );
80
+ const distances = scouts.map((unit) => getDistanceBetweenTileAndPoint(unit.tile, this.scoutTarget!));
79
81
  const newMinDistance = Math.min(...distances);
80
82
  if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
81
83
  logger(
@@ -90,20 +92,21 @@ export class ScoutingSquad implements SquadBehaviour {
90
92
  this.setScoutTarget(null, gameApi.getCurrentTick());
91
93
  }
92
94
  } else {
93
- const candidatePoint = matchAwareness.getScoutingManager().getNewScoutTarget()?.asPoint2D();
94
- if (!candidatePoint) {
95
+ const nextScoutTarget = matchAwareness.getScoutingManager().getNewScoutTarget();
96
+ if (!nextScoutTarget) {
95
97
  logger(`No more scouting targets available, disbanding.`);
96
98
  return disband();
97
99
  }
98
- this.setScoutTarget(candidatePoint, gameApi.getCurrentTick());
100
+ this.setScoutTarget(nextScoutTarget, gameApi.getCurrentTick());
99
101
  }
100
102
  return noop();
101
103
  }
102
104
 
103
- setScoutTarget(point: Point2D | null, currentTick: number) {
105
+ setScoutTarget(target: PrioritisedScoutTarget | null, currentTick: number) {
104
106
  this.attemptsOnCurrentTarget = 0;
105
107
  this.scoutTargetRefreshedAt = currentTick;
106
- this.scoutTarget = point;
108
+ this.scoutTarget = target?.asVector2() ?? null;
107
109
  this.scoutMinDistance = undefined;
110
+ this.scoutTargetIsPermanent = target?.isPermanent ?? false;
108
111
  }
109
112
  }