@supalosa/chronodivide-bot 0.2.1 → 0.2.2-b
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/README.md +3 -3
- package/TODO.md +20 -0
- package/dist/bot/bot.js +7 -3
- package/dist/bot/bot.js.map +1 -0
- package/dist/bot/logic/awareness.js +13 -4
- package/dist/bot/logic/awareness.js.map +1 -0
- package/dist/bot/logic/building/ArtilleryUnit.js +29 -8
- package/dist/bot/logic/building/antiGroundStaticDefence.js +3 -2
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -0
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +2 -1
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -0
- package/dist/bot/logic/building/basicBuilding.js +2 -1
- package/dist/bot/logic/building/basicBuilding.js.map +1 -0
- package/dist/bot/logic/building/basicGroundUnit.js +2 -1
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -0
- package/dist/bot/logic/building/buildingRules.js +168 -0
- package/dist/bot/logic/building/buildingRules.js.map +1 -0
- package/dist/bot/logic/building/harvester.js +1 -0
- package/dist/bot/logic/building/harvester.js.map +1 -0
- package/dist/bot/logic/building/powerPlant.js +2 -1
- package/dist/bot/logic/building/powerPlant.js.map +1 -0
- package/dist/bot/logic/building/queueController.js +2 -1
- package/dist/bot/logic/building/queueController.js.map +1 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js +2 -1
- package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -0
- package/dist/bot/logic/common/scout.js +100 -0
- package/dist/bot/logic/common/scout.js.map +1 -0
- package/dist/bot/logic/common/utils.js +51 -0
- package/dist/bot/logic/common/utils.js.map +1 -0
- package/dist/bot/logic/map/map.js +9 -25
- package/dist/bot/logic/map/map.js.map +1 -0
- package/dist/bot/logic/map/sector.js +33 -1
- package/dist/bot/logic/map/sector.js.map +1 -0
- package/dist/bot/logic/mission/mission.js +3 -1
- package/dist/bot/logic/mission/mission.js.map +1 -0
- package/dist/bot/logic/mission/missionController.js +3 -2
- package/dist/bot/logic/mission/missionController.js.map +1 -0
- package/dist/bot/logic/mission/missionFactories.js +3 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -0
- package/dist/bot/logic/mission/missions/attackMission.js +8 -7
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/defenceMission.js +11 -6
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -0
- 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/expansionMission.js +5 -4
- package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/oneTimeMission.js +3 -2
- package/dist/bot/logic/mission/missions/oneTimeMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js +3 -2
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/scoutingMission.js +8 -9
- package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/combatSquad.js +19 -17
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/common.js +20 -2
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -0
- 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/expansionSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +66 -18
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -0
- package/dist/bot/logic/squad/squad.js +4 -5
- package/dist/bot/logic/squad/squad.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js +1 -0
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js +1 -0
- package/dist/bot/logic/squad/squadBehaviours.js.map +1 -0
- package/dist/bot/logic/squad/squadController.js +58 -18
- package/dist/bot/logic/squad/squadController.js.map +1 -0
- package/dist/bot/logic/threat/threat.js +1 -0
- package/dist/bot/logic/threat/threat.js.map +1 -0
- package/dist/bot/logic/threat/threatCalculator.js +1 -0
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -0
- package/dist/exampleBot.js +7 -6
- package/dist/exampleBot.js.map +1 -0
- package/package.json +7 -8
- package/src/bot/bot.ts +10 -7
- package/src/bot/logic/awareness.ts +22 -9
- package/src/bot/logic/building/antiGroundStaticDefence.ts +60 -60
- package/src/bot/logic/building/artilleryUnit.ts +68 -0
- package/src/bot/logic/building/basicAirUnit.ts +68 -68
- package/src/bot/logic/building/basicBuilding.ts +47 -47
- package/src/bot/logic/building/basicGroundUnit.ts +1 -1
- package/src/bot/logic/building/buildingRules.ts +233 -0
- package/src/bot/logic/building/powerPlant.ts +32 -32
- package/src/bot/logic/building/queueController.ts +1 -1
- package/src/bot/logic/building/resourceCollectionBuilding.ts +56 -56
- package/src/bot/logic/common/scout.ts +127 -1
- package/src/bot/logic/common/utils.ts +65 -0
- package/src/bot/logic/map/map.ts +70 -84
- package/src/bot/logic/map/sector.ts +46 -4
- package/src/bot/logic/mission/mission.ts +2 -2
- package/src/bot/logic/mission/missionController.ts +2 -3
- package/src/bot/logic/mission/missionFactories.ts +5 -0
- package/src/bot/logic/mission/missions/attackMission.ts +24 -20
- package/src/bot/logic/mission/missions/defenceMission.ts +34 -14
- package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
- package/src/bot/logic/mission/missions/expansionMission.ts +6 -4
- package/src/bot/logic/mission/missions/oneTimeMission.ts +3 -2
- package/src/bot/logic/mission/missions/retreatMission.ts +3 -2
- package/src/bot/logic/mission/missions/scoutingMission.ts +9 -6
- package/src/bot/logic/squad/behaviours/combatSquad.ts +20 -18
- package/src/bot/logic/squad/behaviours/common.ts +32 -2
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +79 -26
- package/src/bot/logic/squad/squad.ts +4 -4
- package/src/bot/logic/squad/squadBehaviour.ts +3 -1
- package/src/bot/logic/squad/squadController.ts +89 -44
- package/src/exampleBot.ts +6 -6
- package/tsconfig.json +73 -73
- package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
- package/src/bot/logic/building/building.ts +0 -127
package/dist/exampleBot.js
CHANGED
|
@@ -5,17 +5,17 @@ async function main() {
|
|
|
5
5
|
Ladder maps:
|
|
6
6
|
CDR2 1v1 2_malibu_cliffs_le.map
|
|
7
7
|
CDR2 1v1 4_country_swing_le_v2.map
|
|
8
|
-
CDR2 1v1 mp01t4.map
|
|
9
|
-
CDR2 1v1 tn04t2.map
|
|
10
|
-
CDR2 1v1 mp10s4.map
|
|
8
|
+
CDR2 1v1 mp01t4.map, large map, oil derricks
|
|
9
|
+
CDR2 1v1 tn04t2.map, small map
|
|
10
|
+
CDR2 1v1 mp10s4.map <- depth charge, naval map (not supported)
|
|
11
11
|
CDR2 1v1 heckcorners.map
|
|
12
12
|
CDR2 1v1 4_montana_dmz_le.map
|
|
13
13
|
CDR2 1v1 barrel.map
|
|
14
14
|
|
|
15
15
|
Other maps:
|
|
16
|
-
mp03t4
|
|
16
|
+
mp03t4 large map, no oil derricks
|
|
17
17
|
*/
|
|
18
|
-
const mapName = "
|
|
18
|
+
const mapName = "tn04t2.map";
|
|
19
19
|
// Bot names must be unique in online mode
|
|
20
20
|
const botName = `Joe${String(Date.now()).substr(-6)}`;
|
|
21
21
|
const otherBotName = `Bob${String(Date.now() + 1).substr(-6)}`;
|
|
@@ -42,7 +42,7 @@ async function main() {
|
|
|
42
42
|
agents: [new SupalosaBot(botName, "Americans"), { name: otherBotName, country: "French" }],
|
|
43
43
|
};
|
|
44
44
|
const offlineSettings = {
|
|
45
|
-
agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "
|
|
45
|
+
agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "Russians", true)],
|
|
46
46
|
};
|
|
47
47
|
const game = await cdapi.createGame({
|
|
48
48
|
...offlineSettings,
|
|
@@ -67,3 +67,4 @@ main().catch((e) => {
|
|
|
67
67
|
console.error(e);
|
|
68
68
|
process.exit(1);
|
|
69
69
|
});
|
|
70
|
+
//# sourceMappingURL=exampleBot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exampleBot.js","sourceRoot":"","sources":["../src/exampleBot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,KAAK,UAAU,IAAI;IACf;;;;;;;;;;;;;MAaE;IACF,MAAM,OAAO,GAAG,YAAY,CAAC;IAC7B,0CAA0C;IAC1C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/D,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;IAE9C,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,UAAW,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,UAAW,CAAC,CAAC;IAEtD;;;;;;;;;;;;MAYE;IAEF,MAAM,cAAc,GAAG;QACnB,MAAM,EAAE,IAAY;QACpB,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAW;QAClC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAW;QAClC,MAAM,EAAE,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAsB;KAClH,CAAC;IAEF,MAAM,eAAe,GAAG;QACpB,MAAM,EAAE,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,IAAI,WAAW,CAAC,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;KACvG,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC;QAChC,GAAG,eAAe;QAClB,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,KAAK;QACnB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjD,SAAS,EAAE,CAAC;QACZ,OAAO;QACP,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,IAAI;QACf,YAAY,EAAE,KAAK;QACnB,SAAS,EAAE,CAAC;KACf,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE;QACvB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;KACvB;IAED,IAAI,CAAC,UAAU,EAAE,CAAC;IAClB,IAAI,CAAC,OAAO,EAAE,CAAC;AACnB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supalosa/chronodivide-bot",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2b",
|
|
4
4
|
"description": "Example bot for Chrono Divide",
|
|
5
5
|
"repository": "https://github.com/Supalosa/supalosa-chronodivide-bot",
|
|
6
6
|
"main": "dist/exampleBot.js",
|
|
@@ -13,17 +13,16 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "UNLICENSED",
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@
|
|
16
|
+
"@chronodivide/game-api": "^0.45.0",
|
|
17
17
|
"@types/node": "^14.17.32",
|
|
18
18
|
"prettier": "3.0.3",
|
|
19
19
|
"typescript": "^4.3.5"
|
|
20
20
|
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@chronodivide/game-api": "^0.45.0"
|
|
23
|
+
},
|
|
21
24
|
"dependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"@
|
|
24
|
-
"@types/luxon": "^3.3.2",
|
|
25
|
-
"lodash": "^4.17.21",
|
|
26
|
-
"luxon": "^3.4.3",
|
|
27
|
-
"priority-queue-typescript": "^1.0.1"
|
|
25
|
+
"@datastructures-js/priority-queue": "^6.3.0",
|
|
26
|
+
"@timohausmann/quadtree-ts": "^2.0.0-beta.1"
|
|
28
27
|
}
|
|
29
28
|
}
|
package/src/bot/bot.ts
CHANGED
|
@@ -10,21 +10,22 @@ import {
|
|
|
10
10
|
FactoryType,
|
|
11
11
|
} from "@chronodivide/game-api";
|
|
12
12
|
|
|
13
|
-
import { Duration } from "luxon";
|
|
14
|
-
|
|
15
13
|
import { determineMapBounds } from "./logic/map/map.js";
|
|
16
14
|
import { SectorCache } from "./logic/map/sector.js";
|
|
17
15
|
import { MissionController } from "./logic/mission/missionController.js";
|
|
18
16
|
import { SquadController } from "./logic/squad/squadController.js";
|
|
19
17
|
import { QUEUES, QueueController, queueTypeToName } from "./logic/building/queueController.js";
|
|
20
|
-
import { MatchAwareness
|
|
18
|
+
import { MatchAwareness, MatchAwarenessImpl } from "./logic/awareness.js";
|
|
19
|
+
import { formatTimeDuration } from "./logic/common/utils.js";
|
|
21
20
|
|
|
22
21
|
const DEBUG_TIMESTAMP_OUTPUT_INTERVAL_SECONDS = 60;
|
|
22
|
+
|
|
23
|
+
// Number of ticks per second at the base speed.
|
|
23
24
|
const NATURAL_TICK_RATE = 15;
|
|
24
25
|
const BOT_AUTO_SURRENDER_TIME_SECONDS = 7200; // 7200; // 2 hours (approx 30 mins in real game)
|
|
25
26
|
|
|
26
27
|
export class SupalosaBot extends Bot {
|
|
27
|
-
private tickRatio
|
|
28
|
+
private tickRatio?: number;
|
|
28
29
|
private knownMapBounds: Point2D | undefined;
|
|
29
30
|
private missionController: MissionController;
|
|
30
31
|
private squadController: SquadController;
|
|
@@ -50,13 +51,15 @@ export class SupalosaBot extends Bot {
|
|
|
50
51
|
this.tickRatio = Math.ceil(gameRate / botRate);
|
|
51
52
|
|
|
52
53
|
this.knownMapBounds = determineMapBounds(game.mapApi);
|
|
54
|
+
const myPlayer = game.getPlayerData(this.name);
|
|
53
55
|
|
|
54
56
|
this.matchAwareness = new MatchAwarenessImpl(
|
|
55
57
|
null,
|
|
56
58
|
new SectorCache(game.mapApi, this.knownMapBounds),
|
|
57
|
-
|
|
59
|
+
myPlayer.startLocation,
|
|
58
60
|
(message, sayInGame) => this.logBotStatus(message, sayInGame),
|
|
59
61
|
);
|
|
62
|
+
this.matchAwareness.onGameStart(game, myPlayer);
|
|
60
63
|
|
|
61
64
|
this.logBotStatus(`Map bounds: ${this.knownMapBounds.x}, ${this.knownMapBounds.y}`);
|
|
62
65
|
}
|
|
@@ -72,7 +75,7 @@ export class SupalosaBot extends Bot {
|
|
|
72
75
|
this.logDebugState(game);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
if (game.getCurrentTick() % this.tickRatio === 0) {
|
|
78
|
+
if (game.getCurrentTick() % this.tickRatio! === 0) {
|
|
76
79
|
const myPlayer = game.getPlayerData(this.name);
|
|
77
80
|
|
|
78
81
|
this.matchAwareness.onAiUpdate(game, myPlayer);
|
|
@@ -122,7 +125,7 @@ export class SupalosaBot extends Bot {
|
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
private getHumanTimestamp(game: GameApi) {
|
|
125
|
-
return
|
|
128
|
+
return formatTimeDuration(game.getCurrentTick() / NATURAL_TICK_RATE);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
private logBotStatus(message: string, sayInGame: boolean = false) {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { GameApi, ObjectType, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
|
|
2
2
|
import { SectorCache } from "./map/sector";
|
|
3
3
|
import { GlobalThreat } from "./threat/threat";
|
|
4
|
-
import { calculateGlobalThreat } from "
|
|
5
|
-
import { determineMapBounds, getDistanceBetweenPoints, getPointTowardsOtherPoint } from "
|
|
6
|
-
import { Circle, Quadtree
|
|
4
|
+
import { calculateGlobalThreat } from "./threat/threatCalculator.js";
|
|
5
|
+
import { determineMapBounds, getDistanceBetweenPoints, getPointTowardsOtherPoint } from "./map/map.js";
|
|
6
|
+
import { Circle, Quadtree } from "@timohausmann/quadtree-ts";
|
|
7
|
+
import { ScoutingManager } from "./common/scout.js";
|
|
8
|
+
|
|
9
|
+
export type UnitPositionQuery = { x: number; y: number; unitId: number };
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* The bot's understanding of the current state of the game.
|
|
@@ -31,6 +34,8 @@ export interface MatchAwareness {
|
|
|
31
34
|
*/
|
|
32
35
|
getMainRallyPoint(): Point2D;
|
|
33
36
|
|
|
37
|
+
onGameStart(gameApi: GameApi, playerData: PlayerData): void;
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
40
|
* Update the internal state of the Ai.
|
|
36
41
|
* @param gameApi
|
|
@@ -42,6 +47,8 @@ export interface MatchAwareness {
|
|
|
42
47
|
* True if the AI should initiate an attack.
|
|
43
48
|
*/
|
|
44
49
|
shouldAttack(): boolean;
|
|
50
|
+
|
|
51
|
+
getScoutingManager(): ScoutingManager;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
const SECTORS_TO_UPDATE_PER_CYCLE = 8;
|
|
@@ -51,7 +58,6 @@ const RALLY_POINT_UPDATE_INTERVAL_TICKS = 60;
|
|
|
51
58
|
const THREAT_UPDATE_INTERVAL_TICKS = 30;
|
|
52
59
|
|
|
53
60
|
type QTUnit = Circle<number>;
|
|
54
|
-
export type UnitPositionQuery = { x: number; y: number; unitId: number };
|
|
55
61
|
|
|
56
62
|
const rebuildQuadtree = (quadtree: Quadtree<QTUnit>, units: UnitData[]) => {
|
|
57
63
|
quadtree.clear();
|
|
@@ -64,6 +70,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
64
70
|
private _shouldAttack: boolean = false;
|
|
65
71
|
|
|
66
72
|
private hostileQuadTree: Quadtree<QTUnit>;
|
|
73
|
+
private scoutingManager: ScoutingManager;
|
|
67
74
|
|
|
68
75
|
constructor(
|
|
69
76
|
private threatCache: GlobalThreat | null,
|
|
@@ -73,6 +80,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
73
80
|
) {
|
|
74
81
|
const { x: width, y: height } = sectorCache.getMapBounds();
|
|
75
82
|
this.hostileQuadTree = new Quadtree({ width, height });
|
|
83
|
+
this.scoutingManager = new ScoutingManager(logger);
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
getHostilesNearPoint2d(point: Point2D, radius: number): UnitPositionQuery[] {
|
|
@@ -96,6 +104,9 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
96
104
|
getMainRallyPoint(): Point2D {
|
|
97
105
|
return this.mainRallyPoint;
|
|
98
106
|
}
|
|
107
|
+
getScoutingManager(): ScoutingManager {
|
|
108
|
+
return this.scoutingManager;
|
|
109
|
+
}
|
|
99
110
|
|
|
100
111
|
shouldAttack(): boolean {
|
|
101
112
|
return this._shouldAttack;
|
|
@@ -121,11 +132,17 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
121
132
|
return hostilePlayerNames.includes(unit.owner);
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
public onGameStart(gameApi: GameApi, playerData: PlayerData) {
|
|
136
|
+
this.scoutingManager.onGameStart(gameApi, playerData, this.sectorCache);
|
|
137
|
+
}
|
|
138
|
+
|
|
124
139
|
onAiUpdate(game: GameApi, playerData: PlayerData): void {
|
|
125
140
|
const sectorCache = this.sectorCache;
|
|
126
141
|
|
|
127
142
|
sectorCache.updateSectors(game.getCurrentTick(), SECTORS_TO_UPDATE_PER_CYCLE, game.mapApi, playerData);
|
|
128
143
|
|
|
144
|
+
this.scoutingManager.onAiUpdate(game, playerData);
|
|
145
|
+
|
|
129
146
|
let updateRatio = sectorCache?.getSectorUpdateRatio(game.getCurrentTick() - game.getTickRate() * 60);
|
|
130
147
|
if (updateRatio && updateRatio < 1.0) {
|
|
131
148
|
this.logger(`${updateRatio * 100.0}% of sectors updated in last 60 seconds.`);
|
|
@@ -143,11 +160,7 @@ export class MatchAwarenessImpl implements MatchAwareness {
|
|
|
143
160
|
.map((other) => other.name);
|
|
144
161
|
|
|
145
162
|
// Build the quadtree, if this is too slow we should consider doing this periodically.
|
|
146
|
-
const hostileUnitIds = game.getVisibleUnits(
|
|
147
|
-
playerData.name,
|
|
148
|
-
"hostile",
|
|
149
|
-
(r) => r.isSelectableCombatant || r.type === ObjectType.Building,
|
|
150
|
-
);
|
|
163
|
+
const hostileUnitIds = game.getVisibleUnits(playerData.name, "hostile");
|
|
151
164
|
try {
|
|
152
165
|
const hostileUnits = hostileUnitIds
|
|
153
166
|
.map((id) => game.getUnitData(id))
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { GameApi, PlayerData, Point2D, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { getPointTowardsOtherPoint } from "../map/map.js";
|
|
3
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
5
|
-
|
|
6
|
-
export class AntiGroundStaticDefence implements AiBuildingRules {
|
|
7
|
-
constructor(private basePriority: number, private baseAmount: number, private strength: number) {}
|
|
8
|
-
|
|
9
|
-
getPlacementLocation(
|
|
10
|
-
game: GameApi,
|
|
11
|
-
playerData: PlayerData,
|
|
12
|
-
technoRules: TechnoRules
|
|
13
|
-
): { rx: number; ry: number } | undefined {
|
|
14
|
-
// Prefer front towards enemy.
|
|
15
|
-
let startLocation = playerData.startLocation;
|
|
16
|
-
let players = game.getPlayers();
|
|
17
|
-
let enemyFacingLocationCandidates: Point2D[] = [];
|
|
18
|
-
for (let i = 0; i < players.length; ++i) {
|
|
19
|
-
let playerName = players[i];
|
|
20
|
-
if (playerName == playerData.name) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
let enemyPlayer = game.getPlayerData(playerName);
|
|
24
|
-
enemyFacingLocationCandidates.push(
|
|
25
|
-
getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5)
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
let selectedLocation =
|
|
29
|
-
enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
|
|
30
|
-
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getPriority(
|
|
34
|
-
game: GameApi,
|
|
35
|
-
playerData: PlayerData,
|
|
36
|
-
technoRules: TechnoRules,
|
|
37
|
-
threatCache: GlobalThreat | null
|
|
38
|
-
): number {
|
|
39
|
-
// If the enemy's ground power is increasing we should try to keep up.
|
|
40
|
-
if (threatCache) {
|
|
41
|
-
let denominator =
|
|
42
|
-
threatCache.totalAvailableAntiGroundFirepower + threatCache.totalDefensivePower + this.strength;
|
|
43
|
-
if (threatCache.totalOffensiveLandThreat > denominator * 1.1) {
|
|
44
|
-
return this.basePriority * (threatCache.totalOffensiveLandThreat / Math.max(1, denominator));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const strengthPerCost = (this.strength / technoRules.cost) * 1000;
|
|
48
|
-
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
49
|
-
return this.basePriority * (1.0 - numOwned / this.baseAmount) * strengthPerCost;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getMaxCount(
|
|
53
|
-
game: GameApi,
|
|
54
|
-
playerData: PlayerData,
|
|
55
|
-
technoRules: TechnoRules,
|
|
56
|
-
threatCache: GlobalThreat | null
|
|
57
|
-
): number | null {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
import { GameApi, PlayerData, Point2D, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { getPointTowardsOtherPoint } from "../map/map.js";
|
|
3
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
5
|
+
|
|
6
|
+
export class AntiGroundStaticDefence implements AiBuildingRules {
|
|
7
|
+
constructor(private basePriority: number, private baseAmount: number, private strength: number) {}
|
|
8
|
+
|
|
9
|
+
getPlacementLocation(
|
|
10
|
+
game: GameApi,
|
|
11
|
+
playerData: PlayerData,
|
|
12
|
+
technoRules: TechnoRules
|
|
13
|
+
): { rx: number; ry: number } | undefined {
|
|
14
|
+
// Prefer front towards enemy.
|
|
15
|
+
let startLocation = playerData.startLocation;
|
|
16
|
+
let players = game.getPlayers();
|
|
17
|
+
let enemyFacingLocationCandidates: Point2D[] = [];
|
|
18
|
+
for (let i = 0; i < players.length; ++i) {
|
|
19
|
+
let playerName = players[i];
|
|
20
|
+
if (playerName == playerData.name) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
let enemyPlayer = game.getPlayerData(playerName);
|
|
24
|
+
enemyFacingLocationCandidates.push(
|
|
25
|
+
getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
let selectedLocation =
|
|
29
|
+
enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
|
|
30
|
+
return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getPriority(
|
|
34
|
+
game: GameApi,
|
|
35
|
+
playerData: PlayerData,
|
|
36
|
+
technoRules: TechnoRules,
|
|
37
|
+
threatCache: GlobalThreat | null
|
|
38
|
+
): number {
|
|
39
|
+
// If the enemy's ground power is increasing we should try to keep up.
|
|
40
|
+
if (threatCache) {
|
|
41
|
+
let denominator =
|
|
42
|
+
threatCache.totalAvailableAntiGroundFirepower + threatCache.totalDefensivePower + this.strength;
|
|
43
|
+
if (threatCache.totalOffensiveLandThreat > denominator * 1.1) {
|
|
44
|
+
return this.basePriority * (threatCache.totalOffensiveLandThreat / Math.max(1, denominator));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const strengthPerCost = (this.strength / technoRules.cost) * 1000;
|
|
48
|
+
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
49
|
+
return this.basePriority * (1.0 - numOwned / this.baseAmount) * strengthPerCost;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getMaxCount(
|
|
53
|
+
game: GameApi,
|
|
54
|
+
playerData: PlayerData,
|
|
55
|
+
technoRules: TechnoRules,
|
|
56
|
+
threatCache: GlobalThreat | null
|
|
57
|
+
): number | null {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
4
|
+
|
|
5
|
+
export class ArtilleryUnit implements AiBuildingRules {
|
|
6
|
+
constructor(private basePriority: number,
|
|
7
|
+
private artilleryPower: number,
|
|
8
|
+
private antiGroundPower: number,
|
|
9
|
+
private baseAmount: number) {}
|
|
10
|
+
|
|
11
|
+
getPlacementLocation(
|
|
12
|
+
game: GameApi,
|
|
13
|
+
playerData: PlayerData,
|
|
14
|
+
technoRules: TechnoRules
|
|
15
|
+
): { rx: number; ry: number } | undefined {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getPriority(
|
|
20
|
+
game: GameApi,
|
|
21
|
+
playerData: PlayerData,
|
|
22
|
+
technoRules: TechnoRules,
|
|
23
|
+
threatCache: GlobalThreat | null
|
|
24
|
+
): number {
|
|
25
|
+
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
26
|
+
let priority = this.basePriority;
|
|
27
|
+
// If the enemy's defensive power is increasing we will start to build these.
|
|
28
|
+
if (threatCache && threatCache.totalDefensivePower > threatCache.totalAvailableAntiGroundFirepower) {
|
|
29
|
+
priority += (
|
|
30
|
+
this.artilleryPower *
|
|
31
|
+
(threatCache.totalAvailableAntiGroundFirepower / Math.max(1, threatCache.totalDefensivePower))
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (threatCache && this.antiGroundPower > 0) {
|
|
35
|
+
// If the enemy's power is increasing we should try to keep up.
|
|
36
|
+
if (threatCache.totalOffensiveLandThreat > threatCache.totalAvailableAntiGroundFirepower) {
|
|
37
|
+
priority +=
|
|
38
|
+
this.antiGroundPower *
|
|
39
|
+
this.basePriority *
|
|
40
|
+
(threatCache.totalOffensiveLandThreat /
|
|
41
|
+
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
|
+
Math.sqrt(
|
|
48
|
+
threatCache.totalAvailableAntiGroundFirepower /
|
|
49
|
+
Math.max(
|
|
50
|
+
1,
|
|
51
|
+
threatCache.totalOffensiveLandThreat + threatCache.totalDefensiveThreat,
|
|
52
|
+
),
|
|
53
|
+
)) /
|
|
54
|
+
(numOwned + 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return priority * (1.0 - numOwned / this.baseAmount);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getMaxCount(
|
|
61
|
+
game: GameApi,
|
|
62
|
+
playerData: PlayerData,
|
|
63
|
+
technoRules: TechnoRules,
|
|
64
|
+
threatCache: GlobalThreat | null
|
|
65
|
+
): number | null {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
4
|
-
|
|
5
|
-
export class BasicAirUnit implements AiBuildingRules {
|
|
6
|
-
constructor(
|
|
7
|
-
private basePriority: number,
|
|
8
|
-
private baseAmount: number,
|
|
9
|
-
private antiGroundPower: number = 1, // boolean for now, but will eventually be used in weighting.
|
|
10
|
-
private antiAirPower: number = 0
|
|
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
|
-
// If the enemy's anti-air power is low we might build more.
|
|
28
|
-
if (threatCache) {
|
|
29
|
-
let priority = 0;
|
|
30
|
-
if (
|
|
31
|
-
this.antiGroundPower > 0 &&
|
|
32
|
-
threatCache.totalOffensiveLandThreat > threatCache.totalAvailableAntiGroundFirepower
|
|
33
|
-
) {
|
|
34
|
-
priority +=
|
|
35
|
-
this.basePriority *
|
|
36
|
-
(threatCache.totalOffensiveLandThreat / Math.max(1, threatCache.totalAvailableAntiGroundFirepower));
|
|
37
|
-
}
|
|
38
|
-
if (
|
|
39
|
-
this.antiAirPower > 0 &&
|
|
40
|
-
threatCache.totalOffensiveAirThreat > threatCache.totalAvailableAntiAirFirepower
|
|
41
|
-
) {
|
|
42
|
-
priority +=
|
|
43
|
-
this.basePriority *
|
|
44
|
-
(threatCache.totalOffensiveAirThreat / Math.max(1, threatCache.totalAvailableAntiAirFirepower));
|
|
45
|
-
}
|
|
46
|
-
// sqrt so we don't build too much of one unit type.
|
|
47
|
-
priority += Math.min(
|
|
48
|
-
1.0,
|
|
49
|
-
Math.max(
|
|
50
|
-
1,
|
|
51
|
-
Math.sqrt(threatCache.totalAvailableAirPower / Math.max(1, threatCache.totalOffensiveAntiAirThreat))
|
|
52
|
-
)
|
|
53
|
-
);
|
|
54
|
-
return this.baseAmount * priority;
|
|
55
|
-
}
|
|
56
|
-
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
57
|
-
return this.basePriority * (1.0 - numOwned / this.baseAmount);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getMaxCount(
|
|
61
|
-
game: GameApi,
|
|
62
|
-
playerData: PlayerData,
|
|
63
|
-
technoRules: TechnoRules,
|
|
64
|
-
threatCache: GlobalThreat | null
|
|
65
|
-
): number | null {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
3
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
4
|
+
|
|
5
|
+
export class BasicAirUnit implements AiBuildingRules {
|
|
6
|
+
constructor(
|
|
7
|
+
private basePriority: number,
|
|
8
|
+
private baseAmount: number,
|
|
9
|
+
private antiGroundPower: number = 1, // boolean for now, but will eventually be used in weighting.
|
|
10
|
+
private antiAirPower: number = 0
|
|
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
|
+
// If the enemy's anti-air power is low we might build more.
|
|
28
|
+
if (threatCache) {
|
|
29
|
+
let priority = 0;
|
|
30
|
+
if (
|
|
31
|
+
this.antiGroundPower > 0 &&
|
|
32
|
+
threatCache.totalOffensiveLandThreat > threatCache.totalAvailableAntiGroundFirepower
|
|
33
|
+
) {
|
|
34
|
+
priority +=
|
|
35
|
+
this.basePriority *
|
|
36
|
+
(threatCache.totalOffensiveLandThreat / Math.max(1, threatCache.totalAvailableAntiGroundFirepower));
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
this.antiAirPower > 0 &&
|
|
40
|
+
threatCache.totalOffensiveAirThreat > threatCache.totalAvailableAntiAirFirepower
|
|
41
|
+
) {
|
|
42
|
+
priority +=
|
|
43
|
+
this.basePriority *
|
|
44
|
+
(threatCache.totalOffensiveAirThreat / Math.max(1, threatCache.totalAvailableAntiAirFirepower));
|
|
45
|
+
}
|
|
46
|
+
// sqrt so we don't build too much of one unit type.
|
|
47
|
+
priority += Math.min(
|
|
48
|
+
1.0,
|
|
49
|
+
Math.max(
|
|
50
|
+
1,
|
|
51
|
+
Math.sqrt(threatCache.totalAvailableAirPower / Math.max(1, threatCache.totalOffensiveAntiAirThreat))
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
return this.baseAmount * priority;
|
|
55
|
+
}
|
|
56
|
+
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
57
|
+
return this.basePriority * (1.0 - numOwned / this.baseAmount);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getMaxCount(
|
|
61
|
+
game: GameApi,
|
|
62
|
+
playerData: PlayerData,
|
|
63
|
+
technoRules: TechnoRules,
|
|
64
|
+
threatCache: GlobalThreat | null
|
|
65
|
+
): number | null {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
-
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./
|
|
3
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
-
|
|
5
|
-
export class BasicBuilding implements AiBuildingRules {
|
|
6
|
-
constructor(
|
|
7
|
-
protected basePriority: number,
|
|
8
|
-
protected maxNeeded: number,
|
|
9
|
-
protected onlyBuildWhenFloatingCreditsAmount?: number
|
|
10
|
-
) {}
|
|
11
|
-
|
|
12
|
-
getPlacementLocation(
|
|
13
|
-
game: GameApi,
|
|
14
|
-
playerData: PlayerData,
|
|
15
|
-
technoRules: TechnoRules
|
|
16
|
-
): { rx: number; ry: number } | undefined {
|
|
17
|
-
return getDefaultPlacementLocation(game, playerData, playerData.startLocation, technoRules);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getPriority(
|
|
21
|
-
game: GameApi,
|
|
22
|
-
playerData: PlayerData,
|
|
23
|
-
technoRules: TechnoRules,
|
|
24
|
-
threatCache: GlobalThreat | null
|
|
25
|
-
): number {
|
|
26
|
-
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
27
|
-
const calcMaxCount = this.getMaxCount(game, playerData, technoRules, threatCache);
|
|
28
|
-
if (numOwned >= (calcMaxCount ?? this.maxNeeded)) {
|
|
29
|
-
return -100;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (this.onlyBuildWhenFloatingCreditsAmount && playerData.credits < this.onlyBuildWhenFloatingCreditsAmount) {
|
|
33
|
-
return -100;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return this.basePriority * (1.0 - numOwned / this.maxNeeded);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getMaxCount(
|
|
40
|
-
game: GameApi,
|
|
41
|
-
playerData: PlayerData,
|
|
42
|
-
technoRules: TechnoRules,
|
|
43
|
-
threatCache: GlobalThreat | null
|
|
44
|
-
): number | null {
|
|
45
|
-
return this.maxNeeded;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
import { GameApi, PlayerData, TechnoRules } from "@chronodivide/game-api";
|
|
2
|
+
import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
|
|
3
|
+
import { GlobalThreat } from "../threat/threat.js";
|
|
4
|
+
|
|
5
|
+
export class BasicBuilding implements AiBuildingRules {
|
|
6
|
+
constructor(
|
|
7
|
+
protected basePriority: number,
|
|
8
|
+
protected maxNeeded: number,
|
|
9
|
+
protected onlyBuildWhenFloatingCreditsAmount?: number
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
getPlacementLocation(
|
|
13
|
+
game: GameApi,
|
|
14
|
+
playerData: PlayerData,
|
|
15
|
+
technoRules: TechnoRules
|
|
16
|
+
): { rx: number; ry: number } | undefined {
|
|
17
|
+
return getDefaultPlacementLocation(game, playerData, playerData.startLocation, technoRules);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getPriority(
|
|
21
|
+
game: GameApi,
|
|
22
|
+
playerData: PlayerData,
|
|
23
|
+
technoRules: TechnoRules,
|
|
24
|
+
threatCache: GlobalThreat | null
|
|
25
|
+
): number {
|
|
26
|
+
const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
|
|
27
|
+
const calcMaxCount = this.getMaxCount(game, playerData, technoRules, threatCache);
|
|
28
|
+
if (numOwned >= (calcMaxCount ?? this.maxNeeded)) {
|
|
29
|
+
return -100;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.onlyBuildWhenFloatingCreditsAmount && playerData.credits < this.onlyBuildWhenFloatingCreditsAmount) {
|
|
33
|
+
return -100;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this.basePriority * (1.0 - numOwned / this.maxNeeded);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getMaxCount(
|
|
40
|
+
game: GameApi,
|
|
41
|
+
playerData: PlayerData,
|
|
42
|
+
technoRules: TechnoRules,
|
|
43
|
+
threatCache: GlobalThreat | null
|
|
44
|
+
): number | null {
|
|
45
|
+
return this.maxNeeded;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GameApi, 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(
|