@supalosa/chronodivide-bot 0.2.2 → 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/.prettierrc +5 -5
- package/TODO.md +18 -0
- package/dist/bot/bot.js +4 -4
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/logic/awareness.js +8 -8
- package/dist/bot/logic/awareness.js.map +1 -1
- package/dist/bot/logic/building/ArtilleryUnit.js +30 -9
- package/dist/bot/logic/building/antiGroundStaticDefence.js +2 -2
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +3 -2
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
- package/dist/bot/logic/building/basicBuilding.js +1 -1
- package/dist/bot/logic/building/basicBuilding.js.map +1 -1
- package/dist/bot/logic/building/basicGroundUnit.js +4 -3
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
- package/dist/bot/logic/building/building.js +11 -55
- package/dist/bot/logic/building/buildingRules.js +162 -0
- package/dist/bot/logic/building/buildingRules.js.map +1 -0
- package/dist/bot/logic/building/harvester.js.map +1 -1
- package/dist/bot/logic/building/massedAntiGroundUnit.js +20 -0
- package/dist/bot/logic/building/powerPlant.js +1 -1
- package/dist/bot/logic/building/powerPlant.js.map +1 -1
- package/dist/bot/logic/building/queueController.js +1 -1
- package/dist/bot/logic/building/queueController.js.map +1 -1
- package/dist/bot/logic/building/queues.js +19 -0
- 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/common/utils.js +50 -1
- package/dist/bot/logic/common/utils.js.map +1 -1
- package/dist/bot/logic/knowledge.js +1 -0
- 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/basicMission.js +26 -0
- package/dist/bot/logic/mission/expansionMission.js +32 -0
- package/dist/bot/logic/mission/missionFactories.js +2 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -1
- package/dist/bot/logic/mission/missions/attackMission.js +4 -4
- 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/engineerMission.js +34 -0
- package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/attackSquad.js +56 -63
- package/dist/bot/logic/squad/behaviours/combatSquad.js +18 -19
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/common.js +19 -2
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +2 -15
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +22 -18
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/squadExpansion.js +31 -0
- package/dist/bot/logic/squad/behaviours/squadScouters.js +8 -0
- package/dist/bot/logic/squad/squad.js +5 -8
- 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 +37 -25
- 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 +6 -6
- package/dist/exampleBot.js.map +1 -1
- package/package.json +5 -9
- package/src/bot/bot.ts +8 -10
- package/src/bot/logic/awareness.ts +13 -17
- package/src/bot/logic/building/antiGroundStaticDefence.ts +13 -9
- package/src/bot/logic/building/artilleryUnit.ts +65 -0
- package/src/bot/logic/building/basicAirUnit.ts +10 -8
- package/src/bot/logic/building/basicBuilding.ts +1 -1
- package/src/bot/logic/building/basicGroundUnit.ts +4 -4
- package/src/bot/logic/building/{building.ts → buildingRules.ts} +94 -48
- package/src/bot/logic/building/harvester.ts +7 -4
- package/src/bot/logic/building/powerPlant.ts +1 -1
- package/src/bot/logic/building/queueController.ts +1 -1
- package/src/bot/logic/building/resourceCollectionBuilding.ts +8 -12
- package/src/bot/logic/common/scout.ts +83 -38
- package/src/bot/logic/common/utils.ts +65 -1
- package/src/bot/logic/map/map.ts +27 -31
- package/src/bot/logic/map/sector.ts +17 -21
- package/src/bot/logic/mission/missionFactories.ts +2 -0
- package/src/bot/logic/mission/missions/attackMission.ts +27 -27
- package/src/bot/logic/mission/missions/defenceMission.ts +3 -3
- package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
- package/src/bot/logic/mission/missions/retreatMission.ts +2 -2
- package/src/bot/logic/squad/behaviours/combatSquad.ts +24 -26
- package/src/bot/logic/squad/behaviours/common.ts +33 -3
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +2 -2
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +26 -28
- package/src/bot/logic/squad/squad.ts +8 -13
- package/src/bot/logic/squad/squadBehaviour.ts +9 -10
- package/src/bot/logic/squad/squadController.ts +2 -5
- package/src/bot/logic/threat/threat.ts +15 -15
- package/src/bot/logic/threat/threatCalculator.ts +4 -3
- package/src/exampleBot.ts +6 -6
- package/dist/bot/logic/awarenessImpl.js +0 -132
- package/dist/bot/logic/awarenessImpl.js.map +0 -1
- package/dist/bot/logic/building/ArtilleryUnit.js.map +0 -1
- package/dist/bot/logic/building/building.js.map +0 -1
- package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GameApi, ObjectType, PlayerData,
|
|
1
|
+
import { GameApi, ObjectType, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
|
|
2
2
|
import { SectorCache } from "./map/sector";
|
|
3
3
|
import { GlobalThreat } from "./threat/threat";
|
|
4
4
|
import { calculateGlobalThreat } from "./threat/threatCalculator.js";
|
|
@@ -25,14 +25,14 @@ export interface MatchAwareness {
|
|
|
25
25
|
/**
|
|
26
26
|
* Returns the enemy unit IDs in a certain radius of a point
|
|
27
27
|
*/
|
|
28
|
-
getHostilesNearPoint2d(point:
|
|
28
|
+
getHostilesNearPoint2d(point: Vector2, radius: number): UnitPositionQuery[];
|
|
29
29
|
|
|
30
30
|
getHostilesNearPoint(x: number, y: number, radius: number): UnitPositionQuery[];
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Returns the main rally point for the AI, which updates every few ticks.
|
|
34
34
|
*/
|
|
35
|
-
getMainRallyPoint():
|
|
35
|
+
getMainRallyPoint(): Vector2;
|
|
36
36
|
|
|
37
37
|
onGameStart(gameApi: GameApi, playerData: PlayerData): void;
|
|
38
38
|
|
|
@@ -53,7 +53,7 @@ export interface MatchAwareness {
|
|
|
53
53
|
|
|
54
54
|
const SECTORS_TO_UPDATE_PER_CYCLE = 8;
|
|
55
55
|
|
|
56
|
-
const RALLY_POINT_UPDATE_INTERVAL_TICKS =
|
|
56
|
+
const RALLY_POINT_UPDATE_INTERVAL_TICKS = 90;
|
|
57
57
|
|
|
58
58
|
const THREAT_UPDATE_INTERVAL_TICKS = 30;
|
|
59
59
|
|
|
@@ -75,15 +75,15 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
75
75
|
constructor(
|
|
76
76
|
private threatCache: GlobalThreat | null,
|
|
77
77
|
private sectorCache: SectorCache,
|
|
78
|
-
private mainRallyPoint:
|
|
78
|
+
private mainRallyPoint: Vector2,
|
|
79
79
|
private logger: (message: string, sayInGame?: boolean) => void,
|
|
80
80
|
) {
|
|
81
|
-
const {
|
|
81
|
+
const { width, height } = sectorCache.getMapBounds();
|
|
82
82
|
this.hostileQuadTree = new Quadtree({ width, height });
|
|
83
83
|
this.scoutingManager = new ScoutingManager(logger);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
getHostilesNearPoint2d(point:
|
|
86
|
+
getHostilesNearPoint2d(point: Vector2, radius: number): UnitPositionQuery[] {
|
|
87
87
|
return this.getHostilesNearPoint(point.x, point.y, radius);
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
91
91
|
const intersections = this.hostileQuadTree.retrieve(new Circle({ x: searchX, y: searchY, r: radius }));
|
|
92
92
|
return intersections
|
|
93
93
|
.map(({ x, y, data: unitId }) => ({ x, y, unitId: unitId! }))
|
|
94
|
-
.filter(({ x, y }) => getDistanceBetweenPoints(
|
|
94
|
+
.filter(({ x, y }) => getDistanceBetweenPoints(new Vector2(x, y), new Vector2(searchX, searchY)) <= radius)
|
|
95
95
|
.filter(({ unitId }) => !!unitId);
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -101,7 +101,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
101
101
|
getSectorCache(): SectorCache {
|
|
102
102
|
return this.sectorCache;
|
|
103
103
|
}
|
|
104
|
-
getMainRallyPoint():
|
|
104
|
+
getMainRallyPoint(): Vector2 {
|
|
105
105
|
return this.mainRallyPoint;
|
|
106
106
|
}
|
|
107
107
|
getScoutingManager(): ScoutingManager {
|
|
@@ -113,11 +113,11 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
private checkShouldAttack(threatCache: GlobalThreat, threatFactor: number) {
|
|
116
|
-
let scaledGroundPower =
|
|
116
|
+
let scaledGroundPower = threatCache.totalAvailableAntiGroundFirepower * 1.1;
|
|
117
117
|
let scaledGroundThreat =
|
|
118
118
|
(threatFactor * threatCache.totalOffensiveLandThreat + threatCache.totalDefensiveThreat) * 1.1;
|
|
119
119
|
|
|
120
|
-
let scaledAirPower =
|
|
120
|
+
let scaledAirPower = threatCache.totalAvailableAirPower * 1.1;
|
|
121
121
|
let scaledAirThreat =
|
|
122
122
|
(threatFactor * threatCache.totalOffensiveAntiAirThreat + threatCache.totalDefensiveThreat) * 1.1;
|
|
123
123
|
|
|
@@ -141,7 +141,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
141
141
|
|
|
142
142
|
sectorCache.updateSectors(game.getCurrentTick(), SECTORS_TO_UPDATE_PER_CYCLE, game.mapApi, playerData);
|
|
143
143
|
|
|
144
|
-
this.scoutingManager.onAiUpdate(game, playerData);
|
|
144
|
+
this.scoutingManager.onAiUpdate(game, playerData, sectorCache);
|
|
145
145
|
|
|
146
146
|
let updateRatio = sectorCache?.getSectorUpdateRatio(game.getCurrentTick() - game.getTickRate() * 60);
|
|
147
147
|
if (updateRatio && updateRatio < 1.0) {
|
|
@@ -160,11 +160,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
160
160
|
.map((other) => other.name);
|
|
161
161
|
|
|
162
162
|
// Build the quadtree, if this is too slow we should consider doing this periodically.
|
|
163
|
-
const hostileUnitIds = game.getVisibleUnits(
|
|
164
|
-
playerData.name,
|
|
165
|
-
"hostile",
|
|
166
|
-
(r) => r.isSelectableCombatant || r.type === ObjectType.Building,
|
|
167
|
-
);
|
|
163
|
+
const hostileUnitIds = game.getVisibleUnits(playerData.name, "hostile");
|
|
168
164
|
try {
|
|
169
165
|
const hostileUnits = hostileUnitIds
|
|
170
166
|
.map((id) => game.getUnitData(id))
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import { GameApi, PlayerData,
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules, Vector2 } from "@chronodivide/game-api";
|
|
2
2
|
import { getPointTowardsOtherPoint } from "../map/map.js";
|
|
3
3
|
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
4
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
5
5
|
|
|
6
6
|
export class AntiGroundStaticDefence implements AiBuildingRules {
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(
|
|
8
|
+
private basePriority: number,
|
|
9
|
+
private baseAmount: number,
|
|
10
|
+
private strength: number,
|
|
11
|
+
) {}
|
|
8
12
|
|
|
9
13
|
getPlacementLocation(
|
|
10
14
|
game: GameApi,
|
|
11
15
|
playerData: PlayerData,
|
|
12
|
-
technoRules: TechnoRules
|
|
16
|
+
technoRules: TechnoRules,
|
|
13
17
|
): { rx: number; ry: number } | undefined {
|
|
14
18
|
// Prefer front towards enemy.
|
|
15
19
|
let startLocation = playerData.startLocation;
|
|
16
20
|
let players = game.getPlayers();
|
|
17
|
-
let enemyFacingLocationCandidates:
|
|
21
|
+
let enemyFacingLocationCandidates: Vector2[] = [];
|
|
18
22
|
for (let i = 0; i < players.length; ++i) {
|
|
19
23
|
let playerName = players[i];
|
|
20
24
|
if (playerName == playerData.name) {
|
|
@@ -22,19 +26,19 @@ export class AntiGroundStaticDefence implements AiBuildingRules {
|
|
|
22
26
|
}
|
|
23
27
|
let enemyPlayer = game.getPlayerData(playerName);
|
|
24
28
|
enemyFacingLocationCandidates.push(
|
|
25
|
-
getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5)
|
|
29
|
+
getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5),
|
|
26
30
|
);
|
|
27
31
|
}
|
|
28
32
|
let selectedLocation =
|
|
29
33
|
enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
|
|
30
|
-
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules);
|
|
34
|
+
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, 0);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
getPriority(
|
|
34
38
|
game: GameApi,
|
|
35
39
|
playerData: PlayerData,
|
|
36
40
|
technoRules: TechnoRules,
|
|
37
|
-
threatCache: GlobalThreat | null
|
|
41
|
+
threatCache: GlobalThreat | null,
|
|
38
42
|
): number {
|
|
39
43
|
// If the enemy's ground power is increasing we should try to keep up.
|
|
40
44
|
if (threatCache) {
|
|
@@ -53,7 +57,7 @@ export class AntiGroundStaticDefence implements AiBuildingRules {
|
|
|
53
57
|
game: GameApi,
|
|
54
58
|
playerData: PlayerData,
|
|
55
59
|
technoRules: TechnoRules,
|
|
56
|
-
threatCache: GlobalThreat | null
|
|
60
|
+
threatCache: GlobalThreat | null,
|
|
57
61
|
): number | null {
|
|
58
62
|
return null;
|
|
59
63
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GameApi, GameMath, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
+
import { AiBuildingRules, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
4
|
+
|
|
5
|
+
export class ArtilleryUnit implements AiBuildingRules {
|
|
6
|
+
constructor(
|
|
7
|
+
private basePriority: number,
|
|
8
|
+
private artilleryPower: number,
|
|
9
|
+
private antiGroundPower: number,
|
|
10
|
+
private baseAmount: number,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
getPlacementLocation(
|
|
14
|
+
game: GameApi,
|
|
15
|
+
playerData: PlayerData,
|
|
16
|
+
technoRules: TechnoRules,
|
|
17
|
+
): { rx: number; ry: number } | undefined {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getPriority(
|
|
22
|
+
game: GameApi,
|
|
23
|
+
playerData: PlayerData,
|
|
24
|
+
technoRules: TechnoRules,
|
|
25
|
+
threatCache: GlobalThreat | null,
|
|
26
|
+
): number {
|
|
27
|
+
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
28
|
+
let priority = this.basePriority;
|
|
29
|
+
// If the enemy's defensive power is increasing we will start to build these.
|
|
30
|
+
if (threatCache && threatCache.totalDefensivePower > threatCache.totalAvailableAntiGroundFirepower) {
|
|
31
|
+
priority +=
|
|
32
|
+
this.artilleryPower *
|
|
33
|
+
(threatCache.totalAvailableAntiGroundFirepower / Math.max(1, threatCache.totalDefensivePower));
|
|
34
|
+
}
|
|
35
|
+
if (threatCache && this.antiGroundPower > 0) {
|
|
36
|
+
// If the enemy's power is increasing we should try to keep up.
|
|
37
|
+
if (threatCache.totalOffensiveLandThreat > threatCache.totalAvailableAntiGroundFirepower) {
|
|
38
|
+
priority +=
|
|
39
|
+
this.antiGroundPower *
|
|
40
|
+
this.basePriority *
|
|
41
|
+
(threatCache.totalOffensiveLandThreat / Math.max(1, threatCache.totalAvailableAntiGroundFirepower));
|
|
42
|
+
} else {
|
|
43
|
+
// But also, if our power dwarfs the enemy, keep pressing the advantage.
|
|
44
|
+
priority +=
|
|
45
|
+
(this.antiGroundPower *
|
|
46
|
+
this.basePriority *
|
|
47
|
+
GameMath.sqrt(
|
|
48
|
+
threatCache.totalAvailableAntiGroundFirepower /
|
|
49
|
+
Math.max(1, threatCache.totalOffensiveLandThreat + threatCache.totalDefensiveThreat),
|
|
50
|
+
)) /
|
|
51
|
+
(numOwned + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return priority * (1.0 - numOwned / this.baseAmount);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getMaxCount(
|
|
58
|
+
game: GameApi,
|
|
59
|
+
playerData: PlayerData,
|
|
60
|
+
technoRules: TechnoRules,
|
|
61
|
+
threatCache: GlobalThreat | null,
|
|
62
|
+
): number | null {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
1
|
+
import { GameApi, GameMath, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
3
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
4
4
|
|
|
5
5
|
export class BasicAirUnit implements AiBuildingRules {
|
|
6
6
|
constructor(
|
|
7
7
|
private basePriority: number,
|
|
8
8
|
private baseAmount: number,
|
|
9
9
|
private antiGroundPower: number = 1, // boolean for now, but will eventually be used in weighting.
|
|
10
|
-
private antiAirPower: number = 0
|
|
10
|
+
private antiAirPower: number = 0,
|
|
11
11
|
) {}
|
|
12
12
|
|
|
13
13
|
getPlacementLocation(
|
|
14
14
|
game: GameApi,
|
|
15
15
|
playerData: PlayerData,
|
|
16
|
-
technoRules: TechnoRules
|
|
16
|
+
technoRules: TechnoRules,
|
|
17
17
|
): { rx: number; ry: number } | undefined {
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
@@ -22,7 +22,7 @@ export class BasicAirUnit implements AiBuildingRules {
|
|
|
22
22
|
game: GameApi,
|
|
23
23
|
playerData: PlayerData,
|
|
24
24
|
technoRules: TechnoRules,
|
|
25
|
-
threatCache: GlobalThreat | null
|
|
25
|
+
threatCache: GlobalThreat | null,
|
|
26
26
|
): number {
|
|
27
27
|
// If the enemy's anti-air power is low we might build more.
|
|
28
28
|
if (threatCache) {
|
|
@@ -48,8 +48,10 @@ export class BasicAirUnit implements AiBuildingRules {
|
|
|
48
48
|
1.0,
|
|
49
49
|
Math.max(
|
|
50
50
|
1,
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
GameMath.sqrt(
|
|
52
|
+
threatCache.totalAvailableAirPower / Math.max(1, threatCache.totalOffensiveAntiAirThreat),
|
|
53
|
+
),
|
|
54
|
+
),
|
|
53
55
|
);
|
|
54
56
|
return this.baseAmount * priority;
|
|
55
57
|
}
|
|
@@ -61,7 +63,7 @@ export class BasicAirUnit implements AiBuildingRules {
|
|
|
61
63
|
game: GameApi,
|
|
62
64
|
playerData: PlayerData,
|
|
63
65
|
technoRules: TechnoRules,
|
|
64
|
-
threatCache: GlobalThreat | null
|
|
66
|
+
threatCache: GlobalThreat | null,
|
|
65
67
|
): number | null {
|
|
66
68
|
return null;
|
|
67
69
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
2
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
3
3
|
import { GlobalThreat } from "../threat/threat.js";
|
|
4
4
|
|
|
5
5
|
export class BasicBuilding implements AiBuildingRules {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
1
|
+
import { GameApi, GameMath, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
3
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
4
4
|
|
|
5
5
|
export class BasicGroundUnit implements AiBuildingRules {
|
|
6
6
|
constructor(
|
|
@@ -40,7 +40,7 @@ export class BasicGroundUnit implements AiBuildingRules {
|
|
|
40
40
|
priority +=
|
|
41
41
|
(this.antiGroundPower *
|
|
42
42
|
this.basePriority *
|
|
43
|
-
|
|
43
|
+
GameMath.sqrt(
|
|
44
44
|
threatCache.totalAvailableAntiGroundFirepower /
|
|
45
45
|
Math.max(
|
|
46
46
|
1,
|
|
@@ -60,7 +60,7 @@ export class BasicGroundUnit implements AiBuildingRules {
|
|
|
60
60
|
priority +=
|
|
61
61
|
(this.antiAirPower *
|
|
62
62
|
this.basePriority *
|
|
63
|
-
|
|
63
|
+
GameMath.sqrt(
|
|
64
64
|
threatCache.totalAvailableAntiAirFirepower /
|
|
65
65
|
Math.max(1, threatCache.totalOffensiveAirThreat + threatCache.totalDefensiveThreat),
|
|
66
66
|
)) /
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BuildingPlacementData,
|
|
3
3
|
GameApi,
|
|
4
|
+
GameMath,
|
|
4
5
|
ObjectType,
|
|
5
6
|
PlayerData,
|
|
6
|
-
Point2D,
|
|
7
7
|
Size,
|
|
8
8
|
TechnoRules,
|
|
9
9
|
Tile,
|
|
10
|
+
Vector2,
|
|
10
11
|
} from "@chronodivide/game-api";
|
|
11
12
|
import { GlobalThreat } from "../threat/threat.js";
|
|
12
13
|
import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
|
|
13
|
-
import { ArtilleryUnit } from "./
|
|
14
|
+
import { ArtilleryUnit } from "./artilleryUnit.js";
|
|
14
15
|
import { BasicAirUnit } from "./basicAirUnit.js";
|
|
15
16
|
import { BasicBuilding } from "./basicBuilding.js";
|
|
16
17
|
import { BasicGroundUnit } from "./basicGroundUnit.js";
|
|
17
18
|
import { PowerPlant } from "./powerPlant.js";
|
|
18
19
|
import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
|
|
19
20
|
import { Harvester } from "./harvester.js";
|
|
21
|
+
import { uniqBy } from "../common/utils.js";
|
|
20
22
|
|
|
21
23
|
export interface AiBuildingRules {
|
|
22
24
|
getPriority(
|
|
@@ -48,74 +50,118 @@ export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, n
|
|
|
48
50
|
return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Computes a rect 'centered' around a structure of a certain size with additional radius.
|
|
55
|
+
*
|
|
56
|
+
* This is essentially the placeable area around a given structure.
|
|
57
|
+
*
|
|
58
|
+
* @param point Top-left location of the inner rect.
|
|
59
|
+
* @param t Size of the inner rect.
|
|
60
|
+
* @param adjacent Size of the outer rect.
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
function computeAdjacentRect(point: Vector2, t: Size, adjacent: number) {
|
|
52
64
|
return {
|
|
53
65
|
x: point.x - adjacent,
|
|
54
66
|
y: point.y - adjacent,
|
|
55
67
|
width: t.width + 2 * adjacent,
|
|
56
|
-
height: t.height + 2 * adjacent
|
|
68
|
+
height: t.height + 2 * adjacent,
|
|
57
69
|
};
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
|
|
72
|
+
export function getAdjacencyTiles(
|
|
73
|
+
game: GameApi,
|
|
74
|
+
playerData: PlayerData,
|
|
75
|
+
technoRules: TechnoRules,
|
|
76
|
+
minimumSpace: number,
|
|
77
|
+
) {
|
|
78
|
+
const placementRules = game.getBuildingPlacementData(technoRules.name);
|
|
79
|
+
const { width: newBuildingWidth, height: newBuildingHeight } = placementRules.foundation;
|
|
80
|
+
const tiles = [];
|
|
81
|
+
const buildings = game.getVisibleUnits(playerData.name, "self", (r: TechnoRules) => r.type === ObjectType.Building);
|
|
82
|
+
const removedTiles = new Set<string>();
|
|
83
|
+
for (let buildingId of buildings) {
|
|
84
|
+
const building = game.getUnitData(buildingId);
|
|
85
|
+
if (building?.rules?.baseNormal) {
|
|
86
|
+
const { foundation, tile } = building;
|
|
87
|
+
const buildingBase = new Vector2(tile.rx, tile.ry);
|
|
88
|
+
const buildingSize = {
|
|
89
|
+
width: foundation?.width,
|
|
90
|
+
height: foundation?.height,
|
|
91
|
+
};
|
|
92
|
+
const range = computeAdjacentRect(buildingBase, buildingSize, technoRules.adjacent);
|
|
93
|
+
const baseTile = game.mapApi.getTile(range.x, range.y);
|
|
94
|
+
if (!baseTile) {
|
|
95
|
+
continue;
|
|
71
96
|
}
|
|
72
|
-
|
|
97
|
+
const adjacentTiles = game.mapApi.getTilesInRect(baseTile, {
|
|
98
|
+
width: range.width,
|
|
99
|
+
height: range.height,
|
|
100
|
+
});
|
|
101
|
+
tiles.push(...adjacentTiles);
|
|
102
|
+
|
|
103
|
+
// Prevent placing the new building on tiles that would cause it to overlap with this building.
|
|
104
|
+
const modifiedBase = new Vector2(
|
|
105
|
+
buildingBase.x - (newBuildingWidth - 1),
|
|
106
|
+
buildingBase.y - (newBuildingHeight - 1),
|
|
107
|
+
);
|
|
108
|
+
const modifiedSize = {
|
|
109
|
+
width: buildingSize.width + (newBuildingWidth - 1),
|
|
110
|
+
height: buildingSize.height + (newBuildingHeight - 1),
|
|
111
|
+
};
|
|
112
|
+
const blockedRect = computeAdjacentRect(modifiedBase, modifiedSize, minimumSpace);
|
|
113
|
+
const buildingTiles = adjacentTiles.filter((tile) => {
|
|
114
|
+
return (
|
|
115
|
+
tile.rx >= blockedRect.x &&
|
|
116
|
+
tile.rx < blockedRect.x + blockedRect.width &&
|
|
117
|
+
tile.ry >= blockedRect.y &&
|
|
118
|
+
tile.ry < blockedRect.y + blockedRect.height
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
buildingTiles.forEach((buildingTile) => removedTiles.add(buildingTile.id));
|
|
73
122
|
}
|
|
74
123
|
}
|
|
75
|
-
|
|
124
|
+
// Remove duplicate tiles.
|
|
125
|
+
const withDuplicatesRemoved = uniqBy(tiles, (tile) => tile.id);
|
|
126
|
+
// Remove tiles containing buildings and potentially area around them removed as well.
|
|
127
|
+
return withDuplicatesRemoved.filter((tile) => !removedTiles.has(tile.id));
|
|
76
128
|
}
|
|
77
129
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
ret.sort((a,b)=>{
|
|
89
|
-
return a.distance - b. distance
|
|
90
|
-
})
|
|
91
|
-
return ret
|
|
130
|
+
function getTileDistances(startPoint: Vector2, tiles: Tile[]) {
|
|
131
|
+
return tiles
|
|
132
|
+
.map((tile) => ({
|
|
133
|
+
tile,
|
|
134
|
+
distance: distance(tile.rx, tile.ry, startPoint.x, startPoint.y),
|
|
135
|
+
}))
|
|
136
|
+
.sort((a, b) => {
|
|
137
|
+
return a.distance - b.distance;
|
|
138
|
+
});
|
|
92
139
|
}
|
|
93
140
|
|
|
94
|
-
function distance(x1: number, y1
|
|
95
|
-
var dx = x1 - x2
|
|
141
|
+
function distance(x1: number, y1: number, x2: number, y2: number) {
|
|
142
|
+
var dx = x1 - x2;
|
|
96
143
|
var dy = y1 - y2;
|
|
97
144
|
let tmp = dx * dx + dy * dy;
|
|
98
145
|
if (0 === tmp) {
|
|
99
|
-
return 0
|
|
146
|
+
return 0;
|
|
100
147
|
}
|
|
101
|
-
return
|
|
148
|
+
return GameMath.sqrt(tmp);
|
|
102
149
|
}
|
|
103
150
|
|
|
104
|
-
|
|
105
151
|
export function getDefaultPlacementLocation(
|
|
106
152
|
game: GameApi,
|
|
107
153
|
playerData: PlayerData,
|
|
108
|
-
startPoint:
|
|
154
|
+
startPoint: Vector2,
|
|
109
155
|
technoRules: TechnoRules,
|
|
110
|
-
|
|
156
|
+
minSpace: number = 1,
|
|
111
157
|
): { rx: number; ry: number } | undefined {
|
|
112
158
|
// Random location, preferably near start location.
|
|
113
|
-
|
|
159
|
+
const size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
|
|
114
160
|
if (!size) {
|
|
115
161
|
return undefined;
|
|
116
162
|
}
|
|
117
|
-
|
|
118
|
-
|
|
163
|
+
const tiles = getAdjacencyTiles(game, playerData, technoRules, minSpace);
|
|
164
|
+
const tileDistances = getTileDistances(startPoint, tiles);
|
|
119
165
|
|
|
120
166
|
for (let tileDistance of tileDistances) {
|
|
121
167
|
if (tileDistance.tile && game.canPlaceBuilding(playerData.name, technoRules.name, tileDistance.tile)) {
|
|
@@ -137,9 +183,9 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
|
|
|
137
183
|
["GAWEAP", new BasicBuilding(15, 1)], // War Factory
|
|
138
184
|
["GAPILE", new BasicBuilding(12, 1)], // Barracks
|
|
139
185
|
["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
|
|
140
|
-
["ENGINEER", new BasicBuilding(
|
|
186
|
+
["ENGINEER", new BasicBuilding(10, 1, 1000)], // Engineer
|
|
141
187
|
["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
|
|
142
|
-
["GAAIRC", new BasicBuilding(
|
|
188
|
+
["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
|
|
143
189
|
|
|
144
190
|
["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
|
|
145
191
|
["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
|
|
@@ -154,7 +200,7 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
|
|
|
154
200
|
["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
|
|
155
201
|
["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
|
|
156
202
|
["ORCA", new BasicAirUnit(7, 1, 2, 0)], // Rocketeer
|
|
157
|
-
["SREF", new ArtilleryUnit(
|
|
203
|
+
["SREF", new ArtilleryUnit(10, 5, 3, 3)], // Prism Tank
|
|
158
204
|
["CLEG", new BasicGroundUnit(0, 0)], // Chrono Legionnaire (Disabled - we don't handle the warped out phase properly and it tends to bug both bots out)
|
|
159
205
|
["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
|
|
160
206
|
|
|
@@ -164,9 +210,9 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
|
|
|
164
210
|
["NAWEAP", new BasicBuilding(15, 1)], // War Factory
|
|
165
211
|
["NAHAND", new BasicBuilding(12, 1)], // Barracks
|
|
166
212
|
["HARV", new Harvester(15, 4, 2)], // War Miner
|
|
167
|
-
["SENGINEER", new BasicBuilding(
|
|
213
|
+
["SENGINEER", new BasicBuilding(10, 1, 1000)], // Soviet Engineer
|
|
168
214
|
["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
|
|
169
|
-
["NARADR", new BasicBuilding(
|
|
215
|
+
["NARADR", new BasicBuilding(10, 1, 500)], // Radar
|
|
170
216
|
["NANRCT", new PowerPlant()], // Nuclear Reactor
|
|
171
217
|
["NAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
|
|
172
218
|
|
|
@@ -181,5 +227,5 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
|
|
|
181
227
|
["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
|
|
182
228
|
["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
|
|
183
229
|
["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
|
|
184
|
-
["V3", new ArtilleryUnit(9,
|
|
230
|
+
["V3", new ArtilleryUnit(9, 10, 0, 3)], // V3 Rocket Launcher
|
|
185
231
|
]);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { GameApi, PlayerData,
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import { BasicBuilding } from "./basicBuilding.js";
|
|
4
3
|
import { BasicGroundUnit } from "./basicGroundUnit.js";
|
|
5
4
|
|
|
6
5
|
const IDEAL_HARVESTERS_PER_REFINERY = 2;
|
|
7
6
|
|
|
8
7
|
export class Harvester extends BasicGroundUnit {
|
|
9
|
-
constructor(
|
|
8
|
+
constructor(
|
|
9
|
+
basePriority: number,
|
|
10
|
+
baseAmount: number,
|
|
11
|
+
private minNeeded: number,
|
|
12
|
+
) {
|
|
10
13
|
super(basePriority, baseAmount, 0, 0);
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -15,7 +18,7 @@ export class Harvester extends BasicGroundUnit {
|
|
|
15
18
|
game: GameApi,
|
|
16
19
|
playerData: PlayerData,
|
|
17
20
|
technoRules: TechnoRules,
|
|
18
|
-
threatCache: GlobalThreat | null
|
|
21
|
+
threatCache: GlobalThreat | null,
|
|
19
22
|
): number {
|
|
20
23
|
const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
|
|
21
24
|
const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { AiBuildingRules, getDefaultPlacementLocation } from "./
|
|
2
|
+
import { AiBuildingRules, getDefaultPlacementLocation } from "./buildingRules.js";
|
|
3
3
|
import { GlobalThreat } from "../threat/threat.js";
|
|
4
4
|
|
|
5
5
|
export class PowerPlant implements AiBuildingRules {
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { GameApi,
|
|
1
|
+
import { GameApi, GameMath, PlayerData, TechnoRules, Tile } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "../threat/threat.js";
|
|
3
3
|
import { BasicBuilding } from "./basicBuilding.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
getDefaultPlacementLocation,
|
|
7
|
-
numBuildingsOwnedOfName,
|
|
8
|
-
numBuildingsOwnedOfType,
|
|
9
|
-
} from "./building.js";
|
|
4
|
+
import { getDefaultPlacementLocation } from "./buildingRules.js";
|
|
5
|
+
import { Vector2 } from "three";
|
|
10
6
|
|
|
11
7
|
export class ResourceCollectionBuilding extends BasicBuilding {
|
|
12
8
|
constructor(basePriority: number, maxNeeded: number, onlyBuildWhenFloatingCreditsAmount?: number) {
|
|
@@ -16,7 +12,7 @@ export class ResourceCollectionBuilding extends BasicBuilding {
|
|
|
16
12
|
getPlacementLocation(
|
|
17
13
|
game: GameApi,
|
|
18
14
|
playerData: PlayerData,
|
|
19
|
-
technoRules: TechnoRules
|
|
15
|
+
technoRules: TechnoRules,
|
|
20
16
|
): { rx: number; ry: number } | undefined {
|
|
21
17
|
// Prefer spawning close to ore.
|
|
22
18
|
let selectedLocation = playerData.startLocation;
|
|
@@ -27,9 +23,9 @@ export class ResourceCollectionBuilding extends BasicBuilding {
|
|
|
27
23
|
for (let i = 0; i < allTileResourceData.length; ++i) {
|
|
28
24
|
let tileResourceData = allTileResourceData[i];
|
|
29
25
|
if (tileResourceData.spawnsOre) {
|
|
30
|
-
let dist =
|
|
26
|
+
let dist = GameMath.sqrt(
|
|
31
27
|
(selectedLocation.x - tileResourceData.tile.rx) ** 2 +
|
|
32
|
-
(selectedLocation.y - tileResourceData.tile.ry) ** 2
|
|
28
|
+
(selectedLocation.y - tileResourceData.tile.ry) ** 2,
|
|
33
29
|
);
|
|
34
30
|
if (closeOreDist == undefined || dist < closeOreDist) {
|
|
35
31
|
closeOreDist = dist;
|
|
@@ -38,7 +34,7 @@ export class ResourceCollectionBuilding extends BasicBuilding {
|
|
|
38
34
|
}
|
|
39
35
|
}
|
|
40
36
|
if (closeOre) {
|
|
41
|
-
selectedLocation =
|
|
37
|
+
selectedLocation = new Vector2(closeOre.rx, closeOre.ry);
|
|
42
38
|
}
|
|
43
39
|
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules);
|
|
44
40
|
}
|
|
@@ -48,7 +44,7 @@ export class ResourceCollectionBuilding extends BasicBuilding {
|
|
|
48
44
|
game: GameApi,
|
|
49
45
|
playerData: PlayerData,
|
|
50
46
|
technoRules: TechnoRules,
|
|
51
|
-
threatCache: GlobalThreat | null
|
|
47
|
+
threatCache: GlobalThreat | null,
|
|
52
48
|
): number | null {
|
|
53
49
|
const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
|
|
54
50
|
return Math.max(1, harvesters * 2);
|