@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.
- package/TODO.md +0 -2
- package/dist/bot/bot.js +2 -2
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/logic/awareness.js +7 -6
- package/dist/bot/logic/awareness.js.map +1 -1
- package/dist/bot/logic/building/ArtilleryUnit.js +6 -5
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -1
- package/dist/bot/logic/building/basicAirUnit.js +2 -1
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
- package/dist/bot/logic/building/basicGroundUnit.js +3 -2
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
- package/dist/bot/logic/building/buildingRules.js +4 -10
- package/dist/bot/logic/building/buildingRules.js.map +1 -1
- package/dist/bot/logic/building/harvester.js.map +1 -1
- package/dist/bot/logic/building/resourceCollectionBuilding.js +5 -3
- package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -1
- package/dist/bot/logic/common/scout.js +49 -32
- package/dist/bot/logic/common/scout.js.map +1 -1
- package/dist/bot/logic/map/map.js +17 -19
- package/dist/bot/logic/map/map.js.map +1 -1
- package/dist/bot/logic/map/sector.js +10 -13
- package/dist/bot/logic/map/sector.js.map +1 -1
- package/dist/bot/logic/mission/missions/attackMission.js +2 -2
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/defenceMission.js +2 -1
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/combatSquad.js +3 -3
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/common.js +2 -2
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +21 -17
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
- package/dist/bot/logic/squad/squad.js +4 -6
- package/dist/bot/logic/squad/squad.js.map +1 -1
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
- package/dist/bot/logic/squad/squadController.js +35 -23
- package/dist/bot/logic/squad/squadController.js.map +1 -1
- package/dist/bot/logic/threat/threatCalculator.js +4 -3
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
- package/dist/exampleBot.js +1 -1
- package/package.json +3 -3
- package/src/bot/bot.ts +4 -5
- package/src/bot/logic/awareness.ts +12 -12
- package/src/bot/logic/building/antiGroundStaticDefence.ts +11 -7
- package/src/bot/logic/building/artilleryUnit.ts +14 -17
- package/src/bot/logic/building/basicAirUnit.ts +9 -7
- package/src/bot/logic/building/basicGroundUnit.ts +3 -3
- package/src/bot/logic/building/buildingRules.ts +11 -13
- package/src/bot/logic/building/harvester.ts +7 -4
- package/src/bot/logic/building/resourceCollectionBuilding.ts +8 -12
- package/src/bot/logic/common/scout.ts +83 -38
- package/src/bot/logic/map/map.ts +26 -30
- package/src/bot/logic/map/sector.ts +17 -21
- package/src/bot/logic/mission/missions/attackMission.ts +5 -5
- package/src/bot/logic/mission/missions/defenceMission.ts +3 -3
- package/src/bot/logic/mission/missions/retreatMission.ts +2 -2
- package/src/bot/logic/squad/behaviours/combatSquad.ts +6 -6
- package/src/bot/logic/squad/behaviours/common.ts +3 -3
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +2 -2
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +25 -22
- package/src/bot/logic/squad/squad.ts +6 -10
- package/src/bot/logic/squad/squadBehaviour.ts +9 -10
- package/src/bot/logic/squad/squadController.ts +0 -1
- package/src/bot/logic/threat/threatCalculator.ts +100 -99
- package/src/exampleBot.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GameApi, PlayerData,
|
|
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
|
|
17
|
+
export class PrioritisedScoutTarget {
|
|
18
|
+
private _targetPoint?: Vector2;
|
|
19
19
|
private _targetSector?: Sector;
|
|
20
20
|
private _priority: number;
|
|
21
21
|
|
|
22
|
-
constructor(
|
|
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.
|
|
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
|
-
|
|
38
|
-
return this.
|
|
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
|
|
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(
|
|
70
|
+
this.scoutingQueue = new PriorityQueue(
|
|
71
|
+
(a: PrioritisedScoutTarget, b: PrioritisedScoutTarget) => b.priority - a.priority,
|
|
72
|
+
);
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 -
|
|
88
|
-
x
|
|
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 -
|
|
93
|
-
y
|
|
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 =
|
|
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(
|
|
104
|
-
const maybePoint = maybeTarget.
|
|
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
|
-
|
|
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.
|
|
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() {
|
package/src/bot/logic/map/map.ts
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
|
-
import { GameApi, MapApi, PlayerData,
|
|
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
|
-
|
|
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:
|
|
24
|
-
endPoint:
|
|
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:
|
|
46
|
-
endLocation:
|
|
33
|
+
startLocation: Vector2,
|
|
34
|
+
endLocation: Vector2,
|
|
47
35
|
minRadius: number,
|
|
48
36
|
maxRadius: number,
|
|
49
37
|
randomAngle: number,
|
|
50
|
-
):
|
|
38
|
+
): Vector2 {
|
|
39
|
+
// TODO: Use proper vector maths here.
|
|
51
40
|
let radius = minRadius + Math.round(gameApi.generateRandom() * (maxRadius - minRadius));
|
|
52
|
-
let
|
|
41
|
+
let directionToEndLocation = GameMath.atan2(endLocation.y - startLocation.y, endLocation.x - startLocation.x);
|
|
53
42
|
let randomisedDirection =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let
|
|
57
|
-
|
|
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
|
|
61
|
-
|
|
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
|
|
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:
|
|
69
|
-
return getDistanceBetweenPoints(
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
49
|
+
constructor(mapApi: MapApi, mapBounds: Size) {
|
|
50
50
|
this.mapBounds = mapBounds;
|
|
51
|
-
this.sectorsX = Math.ceil(mapBounds.
|
|
52
|
-
this.sectorsY = Math.ceil(mapBounds.
|
|
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
|
-
|
|
59
|
-
mapApi.getTile(
|
|
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():
|
|
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():
|
|
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.
|
|
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,
|
|
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:
|
|
28
|
-
private attackArea:
|
|
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):
|
|
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
|
|
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,
|
|
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:
|
|
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(
|
|
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:
|
|
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,
|
|
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:
|
|
40
|
-
private targetArea:
|
|
39
|
+
private rallyArea: Vector2,
|
|
40
|
+
private targetArea: Vector2,
|
|
41
41
|
private radius: number,
|
|
42
42
|
) {}
|
|
43
43
|
|
|
44
|
-
public setAttackArea(targetArea:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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(
|
|
68
|
+
return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
|
|
69
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, OrderType, PlayerData,
|
|
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:
|
|
14
|
+
private retreatToPoint: Vector2,
|
|
15
15
|
) {}
|
|
16
16
|
|
|
17
17
|
public onAiUpdate(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, OrderType, PlayerData,
|
|
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 {
|
|
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:
|
|
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.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
94
|
-
if (!
|
|
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(
|
|
100
|
+
this.setScoutTarget(nextScoutTarget, gameApi.getCurrentTick());
|
|
99
101
|
}
|
|
100
102
|
return noop();
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
setScoutTarget(
|
|
105
|
+
setScoutTarget(target: PrioritisedScoutTarget | null, currentTick: number) {
|
|
104
106
|
this.attemptsOnCurrentTarget = 0;
|
|
105
107
|
this.scoutTargetRefreshedAt = currentTick;
|
|
106
|
-
this.scoutTarget =
|
|
108
|
+
this.scoutTarget = target?.asVector2() ?? null;
|
|
107
109
|
this.scoutMinDistance = undefined;
|
|
110
|
+
this.scoutTargetIsPermanent = target?.isPermanent ?? false;
|
|
108
111
|
}
|
|
109
112
|
}
|