@supalosa/chronodivide-bot 0.4.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.env.template +5 -0
  2. package/README.md +54 -47
  3. package/dist/bot/bot.js +14 -35
  4. package/dist/bot/bot.js.map +1 -1
  5. package/dist/bot/logic/awareness.js +13 -8
  6. package/dist/bot/logic/awareness.js.map +1 -1
  7. package/dist/bot/logic/building/ArtilleryUnit.js +2 -29
  8. package/dist/bot/logic/building/antiAirStaticDefence.js +43 -0
  9. package/dist/bot/logic/building/antiAirStaticDefence.js.map +1 -0
  10. package/dist/bot/logic/building/antiGroundStaticDefence.js +10 -20
  11. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  12. package/dist/bot/logic/building/artilleryUnit.js.map +1 -1
  13. package/dist/bot/logic/building/basicAirUnit.js +2 -23
  14. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  15. package/dist/bot/logic/building/basicBuilding.js +3 -2
  16. package/dist/bot/logic/building/basicBuilding.js.map +1 -1
  17. package/dist/bot/logic/building/basicGroundUnit.js +2 -43
  18. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  19. package/dist/bot/logic/building/buildingRules.js +15 -9
  20. package/dist/bot/logic/building/buildingRules.js.map +1 -1
  21. package/dist/bot/logic/building/common.js +19 -0
  22. package/dist/bot/logic/building/common.js.map +1 -0
  23. package/dist/bot/logic/building/harvester.js +2 -1
  24. package/dist/bot/logic/building/harvester.js.map +1 -1
  25. package/dist/bot/logic/building/queueController.js +69 -42
  26. package/dist/bot/logic/building/queueController.js.map +1 -1
  27. package/dist/bot/logic/common/utils.js +21 -0
  28. package/dist/bot/logic/common/utils.js.map +1 -1
  29. package/dist/bot/logic/composition/alliedCompositions.js +13 -0
  30. package/dist/bot/logic/composition/alliedCompositions.js.map +1 -0
  31. package/dist/bot/logic/composition/common.js +2 -0
  32. package/dist/bot/logic/composition/common.js.map +1 -0
  33. package/dist/bot/logic/composition/sovietCompositions.js +13 -0
  34. package/dist/bot/logic/composition/sovietCompositions.js.map +1 -0
  35. package/dist/bot/logic/mission/actionBatcher.js +92 -0
  36. package/dist/bot/logic/mission/actionBatcher.js.map +1 -0
  37. package/dist/bot/logic/mission/behaviours/combatSquad.js +124 -0
  38. package/dist/bot/logic/mission/behaviours/combatSquad.js.map +1 -0
  39. package/dist/bot/logic/mission/behaviours/common.js +56 -0
  40. package/dist/bot/logic/mission/behaviours/common.js.map +1 -0
  41. package/dist/bot/logic/mission/behaviours/engineerSquad.js +39 -0
  42. package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +1 -0
  43. package/dist/bot/logic/mission/behaviours/expansionSquad.js +46 -0
  44. package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +1 -0
  45. package/dist/bot/logic/mission/behaviours/retreatSquad.js +31 -0
  46. package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +1 -0
  47. package/{src/bot/logic/squad/behaviours/scoutingSquad.ts → dist/bot/logic/mission/behaviours/scoutingSquad.js} +27 -51
  48. package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +1 -0
  49. package/dist/bot/logic/mission/mission.js +91 -19
  50. package/dist/bot/logic/mission/mission.js.map +1 -1
  51. package/dist/bot/logic/mission/missionController.js +262 -21
  52. package/dist/bot/logic/mission/missionController.js.map +1 -1
  53. package/dist/bot/logic/mission/missions/attackMission.js +113 -39
  54. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  55. package/dist/bot/logic/mission/missions/basicMission.js +13 -0
  56. package/dist/bot/logic/mission/missions/basicMission.js.map +1 -0
  57. package/dist/bot/logic/mission/missions/defenceMission.js +43 -28
  58. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  59. package/dist/bot/logic/mission/missions/engineerMission.js +37 -7
  60. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -1
  61. package/dist/bot/logic/mission/missions/expansionMission.js +42 -6
  62. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -1
  63. package/dist/bot/logic/mission/missions/missionBehaviour.js +2 -0
  64. package/dist/bot/logic/mission/missions/missionBehaviour.js.map +1 -0
  65. package/dist/bot/logic/mission/missions/retreatMission.js +31 -5
  66. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  67. package/dist/bot/logic/mission/missions/scoutingMission.js +103 -6
  68. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -1
  69. package/dist/bot/logic/mission/missions/squads/combatSquad.js +116 -0
  70. package/dist/bot/logic/mission/missions/squads/combatSquad.js.map +1 -0
  71. package/dist/bot/logic/mission/missions/squads/common.js +58 -0
  72. package/dist/bot/logic/mission/missions/squads/common.js.map +1 -0
  73. package/dist/bot/logic/mission/missions/squads/squad.js +2 -0
  74. package/dist/bot/logic/mission/missions/squads/squad.js.map +1 -0
  75. package/dist/bot/logic/squad/squadController.js +6 -2
  76. package/dist/bot/logic/squad/squadController.js.map +1 -1
  77. package/dist/bot/logic/threat/threatCalculator.js +9 -9
  78. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  79. package/dist/exampleBot.js +50 -18
  80. package/dist/exampleBot.js.map +1 -1
  81. package/package.json +5 -4
  82. package/src/bot/bot.ts +19 -51
  83. package/src/bot/logic/awareness.ts +34 -22
  84. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
  85. package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
  86. package/src/bot/logic/building/artilleryUnit.ts +2 -28
  87. package/src/bot/logic/building/basicAirUnit.ts +2 -33
  88. package/src/bot/logic/building/basicBuilding.ts +8 -6
  89. package/src/bot/logic/building/basicGroundUnit.ts +2 -46
  90. package/src/bot/logic/building/buildingRules.ts +15 -9
  91. package/src/bot/logic/building/common.ts +23 -0
  92. package/src/bot/logic/building/harvester.ts +2 -1
  93. package/src/bot/logic/building/queueController.ts +98 -43
  94. package/src/bot/logic/common/utils.ts +28 -0
  95. package/src/bot/logic/composition/alliedCompositions.ts +22 -0
  96. package/src/bot/logic/composition/common.ts +3 -0
  97. package/src/bot/logic/composition/sovietCompositions.ts +21 -0
  98. package/src/bot/logic/{squad/behaviours → mission}/actionBatcher.ts +66 -7
  99. package/src/bot/logic/mission/mission.ts +186 -37
  100. package/src/bot/logic/mission/missionController.ts +340 -31
  101. package/src/bot/logic/mission/missionFactories.ts +3 -3
  102. package/src/bot/logic/mission/missions/attackMission.ts +181 -44
  103. package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
  104. package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
  105. package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
  106. package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
  107. package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
  108. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/combatSquad.ts +56 -33
  109. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +11 -17
  110. package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
  111. package/src/bot/logic/threat/threatCalculator.ts +10 -10
  112. package/src/exampleBot.ts +56 -24
  113. package/.prettierrc +0 -5
  114. package/TODO.md +0 -15
  115. package/rules.ini +0 -23126
  116. package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
  117. package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -58
  118. package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -64
  119. package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -50
  120. package/src/bot/logic/squad/squad.ts +0 -165
  121. package/src/bot/logic/squad/squadBehaviour.ts +0 -66
  122. package/src/bot/logic/squad/squadBehaviours.ts +0 -8
  123. package/src/bot/logic/squad/squadController.ts +0 -271
@@ -0,0 +1,64 @@
1
+ import { GameApi, PlayerData, TechnoRules, Vector2 } 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 AntiAirStaticDefence implements AiBuildingRules {
7
+ constructor(
8
+ private basePriority: number,
9
+ private baseAmount: number,
10
+ private airStrength: number,
11
+ ) {}
12
+
13
+ getPlacementLocation(
14
+ game: GameApi,
15
+ playerData: PlayerData,
16
+ technoRules: TechnoRules,
17
+ ): { rx: number; ry: number } | undefined {
18
+ // Prefer front towards enemy.
19
+ let startLocation = playerData.startLocation;
20
+ let players = game.getPlayers();
21
+ let enemyFacingLocationCandidates: Vector2[] = [];
22
+ for (let i = 0; i < players.length; ++i) {
23
+ let playerName = players[i];
24
+ if (playerName == playerData.name) {
25
+ continue;
26
+ }
27
+ let enemyPlayer = game.getPlayerData(playerName);
28
+ enemyFacingLocationCandidates.push(
29
+ getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5),
30
+ );
31
+ }
32
+ let selectedLocation =
33
+ enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
34
+ return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 0);
35
+ }
36
+
37
+ getPriority(
38
+ game: GameApi,
39
+ playerData: PlayerData,
40
+ technoRules: TechnoRules,
41
+ threatCache: GlobalThreat | null,
42
+ ): number {
43
+ if (threatCache) {
44
+ let denominator = threatCache.totalAvailableAntiAirFirepower + this.airStrength;
45
+ if (threatCache.totalOffensiveAirThreat > denominator * 1.1) {
46
+ return this.basePriority * (threatCache.totalOffensiveAirThreat / Math.max(1, denominator));
47
+ } else {
48
+ return 0;
49
+ }
50
+ }
51
+ const strengthPerCost = (this.airStrength / technoRules.cost) * 1000;
52
+ const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
53
+ return this.basePriority * (1.0 - numOwned / this.baseAmount) * strengthPerCost;
54
+ }
55
+
56
+ getMaxCount(
57
+ game: GameApi,
58
+ playerData: PlayerData,
59
+ technoRules: TechnoRules,
60
+ threatCache: GlobalThreat | null,
61
+ ): number | null {
62
+ return null;
63
+ }
64
+ }
@@ -2,12 +2,13 @@ import { GameApi, PlayerData, TechnoRules, Vector2 } from "@chronodivide/game-ap
2
2
  import { getPointTowardsOtherPoint } from "../map/map.js";
3
3
  import { GlobalThreat } from "../threat/threat.js";
4
4
  import { AiBuildingRules, getDefaultPlacementLocation, numBuildingsOwnedOfType } from "./buildingRules.js";
5
+ import { getStaticDefencePlacement } from "./common.js";
5
6
 
6
7
  export class AntiGroundStaticDefence implements AiBuildingRules {
7
8
  constructor(
8
9
  private basePriority: number,
9
10
  private baseAmount: number,
10
- private strength: number,
11
+ private groundStrength: number,
11
12
  ) {}
12
13
 
13
14
  getPlacementLocation(
@@ -15,23 +16,7 @@ export class AntiGroundStaticDefence implements AiBuildingRules {
15
16
  playerData: PlayerData,
16
17
  technoRules: TechnoRules,
17
18
  ): { rx: number; ry: number } | undefined {
18
- // Prefer front towards enemy.
19
- let startLocation = playerData.startLocation;
20
- let players = game.getPlayers();
21
- let enemyFacingLocationCandidates: Vector2[] = [];
22
- for (let i = 0; i < players.length; ++i) {
23
- let playerName = players[i];
24
- if (playerName == playerData.name) {
25
- continue;
26
- }
27
- let enemyPlayer = game.getPlayerData(playerName);
28
- enemyFacingLocationCandidates.push(
29
- getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5),
30
- );
31
- }
32
- let selectedLocation =
33
- enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
34
- return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 0);
19
+ return getStaticDefencePlacement(game, playerData, technoRules);
35
20
  }
36
21
 
37
22
  getPriority(
@@ -43,12 +28,14 @@ export class AntiGroundStaticDefence implements AiBuildingRules {
43
28
  // If the enemy's ground power is increasing we should try to keep up.
44
29
  if (threatCache) {
45
30
  let denominator =
46
- threatCache.totalAvailableAntiGroundFirepower + threatCache.totalDefensivePower + this.strength;
31
+ threatCache.totalAvailableAntiGroundFirepower + threatCache.totalDefensivePower + this.groundStrength;
47
32
  if (threatCache.totalOffensiveLandThreat > denominator * 1.1) {
48
33
  return this.basePriority * (threatCache.totalOffensiveLandThreat / Math.max(1, denominator));
34
+ } else {
35
+ return 0;
49
36
  }
50
37
  }
51
- const strengthPerCost = (this.strength / technoRules.cost) * 1000;
38
+ const strengthPerCost = (this.groundStrength / technoRules.cost) * 1000;
52
39
  const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
53
40
  return this.basePriority * (1.0 - numOwned / this.baseAmount) * strengthPerCost;
54
41
  }
@@ -24,34 +24,8 @@ export class ArtilleryUnit implements AiBuildingRules {
24
24
  technoRules: TechnoRules,
25
25
  threatCache: GlobalThreat | null,
26
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);
27
+ // Units aren't built automatically, but are instead requested by missions.
28
+ return 0;
55
29
  }
56
30
 
57
31
  getMaxCount(
@@ -24,39 +24,8 @@ export class BasicAirUnit implements AiBuildingRules {
24
24
  technoRules: TechnoRules,
25
25
  threatCache: GlobalThreat | null,
26
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
- GameMath.sqrt(
52
- threatCache.totalAvailableAirPower / Math.max(1, threatCache.totalOffensiveAntiAirThreat),
53
- ),
54
- ),
55
- );
56
- return this.baseAmount * priority;
57
- }
58
- const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
59
- return this.basePriority * (1.0 - numOwned / this.baseAmount);
27
+ // Units aren't built automatically, but are instead requested by missions.
28
+ return 0;
60
29
  }
61
30
 
62
31
  getMaxCount(
@@ -6,13 +6,13 @@ export class BasicBuilding implements AiBuildingRules {
6
6
  constructor(
7
7
  protected basePriority: number,
8
8
  protected maxNeeded: number,
9
- protected onlyBuildWhenFloatingCreditsAmount?: number
9
+ protected onlyBuildWhenFloatingCreditsAmount?: number,
10
10
  ) {}
11
11
 
12
12
  getPlacementLocation(
13
13
  game: GameApi,
14
14
  playerData: PlayerData,
15
- technoRules: TechnoRules
15
+ technoRules: TechnoRules,
16
16
  ): { rx: number; ry: number } | undefined {
17
17
  return getDefaultPlacementLocation(game, playerData, playerData.startLocation, technoRules);
18
18
  }
@@ -21,7 +21,7 @@ export class BasicBuilding implements AiBuildingRules {
21
21
  game: GameApi,
22
22
  playerData: PlayerData,
23
23
  technoRules: TechnoRules,
24
- threatCache: GlobalThreat | null
24
+ threatCache: GlobalThreat | null,
25
25
  ): number {
26
26
  const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
27
27
  const calcMaxCount = this.getMaxCount(game, playerData, technoRules, threatCache);
@@ -29,18 +29,20 @@ export class BasicBuilding implements AiBuildingRules {
29
29
  return -100;
30
30
  }
31
31
 
32
+ const priority = this.basePriority * (1.0 - numOwned / this.maxNeeded);
33
+
32
34
  if (this.onlyBuildWhenFloatingCreditsAmount && playerData.credits < this.onlyBuildWhenFloatingCreditsAmount) {
33
- return -100;
35
+ return priority * (playerData.credits / this.onlyBuildWhenFloatingCreditsAmount);
34
36
  }
35
37
 
36
- return this.basePriority * (1.0 - numOwned / this.maxNeeded);
38
+ return priority;
37
39
  }
38
40
 
39
41
  getMaxCount(
40
42
  game: GameApi,
41
43
  playerData: PlayerData,
42
44
  technoRules: TechnoRules,
43
- threatCache: GlobalThreat | null
45
+ threatCache: GlobalThreat | null,
44
46
  ): number | null {
45
47
  return this.maxNeeded;
46
48
  }
@@ -24,52 +24,8 @@ export class BasicGroundUnit implements AiBuildingRules {
24
24
  technoRules: TechnoRules,
25
25
  threatCache: GlobalThreat | null,
26
26
  ): number {
27
- const numOwned = numBuildingsOwnedOfType(game, playerData, technoRules);
28
- if (threatCache) {
29
- let priority = this.basePriority;
30
- if (this.antiGroundPower > 0) {
31
- // If the enemy's power is increasing we should try to keep up.
32
- if (threatCache.totalOffensiveLandThreat > threatCache.totalAvailableAntiGroundFirepower) {
33
- priority +=
34
- this.antiGroundPower *
35
- this.basePriority *
36
- (threatCache.totalOffensiveLandThreat /
37
- Math.max(1, threatCache.totalAvailableAntiGroundFirepower));
38
- } else {
39
- // But also, if our power dwarfs the enemy, keep pressing the advantage.
40
- priority +=
41
- (this.antiGroundPower *
42
- this.basePriority *
43
- GameMath.sqrt(
44
- threatCache.totalAvailableAntiGroundFirepower /
45
- Math.max(
46
- 1,
47
- threatCache.totalOffensiveLandThreat + threatCache.totalDefensiveThreat,
48
- ),
49
- )) /
50
- (numOwned + 1);
51
- }
52
- }
53
- if (this.antiAirPower > 0) {
54
- if (threatCache.totalOffensiveAirThreat > threatCache.totalAvailableAntiAirFirepower) {
55
- priority +=
56
- this.antiAirPower *
57
- this.basePriority *
58
- (threatCache.totalOffensiveAirThreat / Math.max(1, threatCache.totalAvailableAntiAirFirepower));
59
- } else {
60
- priority +=
61
- (this.antiAirPower *
62
- this.basePriority *
63
- GameMath.sqrt(
64
- threatCache.totalAvailableAntiAirFirepower /
65
- Math.max(1, threatCache.totalOffensiveAirThreat + threatCache.totalDefensiveThreat),
66
- )) /
67
- (numOwned + 1);
68
- }
69
- }
70
- return priority;
71
- }
72
- return this.basePriority * (1.0 - numOwned / this.baseAmount);
27
+ // Units aren't built automatically, but are instead requested by missions.
28
+ return 0;
73
29
  }
74
30
 
75
31
  getMaxCount(
@@ -20,6 +20,7 @@ import { PowerPlant } from "./powerPlant.js";
20
20
  import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
21
21
  import { Harvester } from "./harvester.js";
22
22
  import { uniqBy } from "../common/utils.js";
23
+ import { AntiAirStaticDefence } from "./antiAirStaticDefence.js";
23
24
 
24
25
  export interface AiBuildingRules {
25
26
  getPriority(
@@ -183,7 +184,7 @@ export function getDefaultPlacementLocation(
183
184
  // Priority 0 = don't build.
184
185
  export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
185
186
 
186
- export const DEFAULT_BUILDING_PRIORITY = 1;
187
+ export const DEFAULT_BUILDING_PRIORITY = 0;
187
188
 
188
189
  export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
189
190
  // Allied
@@ -192,7 +193,6 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
192
193
  ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
193
194
  ["GAPILE", new BasicBuilding(12, 1)], // Barracks
194
195
  ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
195
- ["ENGINEER", new BasicBuilding(10, 1, 1000)], // Engineer
196
196
  ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
197
197
  ["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
198
198
  ["AMRADR", new BasicBuilding(10, 1, 500)], // Airforce Command (USA)
@@ -200,11 +200,13 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
200
200
  ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
201
201
  ["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
202
202
 
203
- ["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
204
- ["ATESLA", new AntiGroundStaticDefence(5, 1, 10)], // Prism Cannon
203
+ ["GAPILL", new AntiGroundStaticDefence(2, 1, 5)], // Pillbox
204
+ ["ATESLA", new AntiGroundStaticDefence(2, 1, 10)], // Prism Cannon
205
+ ["NASAM", new AntiAirStaticDefence(2, 1, 5)], // Prism Cannon
205
206
  ["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
206
207
 
207
- ["E1", new BasicGroundUnit(2, 3, 0.25, 0)], // GI
208
+ ["E1", new BasicGroundUnit(2, 2, 0.2, 0)], // GI
209
+ ["ENGINEER", new BasicGroundUnit(1, 0, 0)], // Engineer
208
210
  ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
209
211
  ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
210
212
  ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
@@ -220,7 +222,6 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
220
222
  ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
221
223
  ["NAHAND", new BasicBuilding(12, 1)], // Barracks
222
224
  ["HARV", new Harvester(15, 4, 2)], // War Miner
223
- ["SENGINEER", new BasicBuilding(10, 1, 1000)], // Soviet Engineer
224
225
  ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
225
226
  ["NARADR", new BasicBuilding(10, 1, 500)], // Radar
226
227
  ["NANRCT", new PowerPlant()], // Nuclear Reactor
@@ -228,11 +229,16 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
228
229
 
229
230
  ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
230
231
 
231
- ["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
232
- ["TESLA", new AntiGroundStaticDefence(5, 1, 10)], // Tesla Coil
232
+ ["NALASR", new AntiGroundStaticDefence(2, 1, 5)], // Sentry Gun
233
+ ["NAFLAK", new AntiAirStaticDefence(2, 1, 5)], // Flak Cannon
234
+ ["TESLA", new AntiGroundStaticDefence(2, 1, 10)], // Tesla Coil
233
235
  ["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
234
236
 
235
- ["E2", new BasicGroundUnit(2, 3, 0.25, 0)], // Conscript
237
+ ["E2", new BasicGroundUnit(2, 2, 0.2, 0)], // Conscript
238
+ ["SENGINEER", new BasicGroundUnit(1, 0, 0)], // Soviet Engineer
239
+ ["FLAKT", new BasicGroundUnit(2, 2, 0.1, 0.3)], // Flak Trooper
240
+ ["YURI", new BasicGroundUnit(1, 1, 1, 0)], // Yuri
241
+ ["DOG", new BasicGroundUnit(1, 1, 0, 0)], // Soviet Attack Dog
236
242
  ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
237
243
  ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
238
244
  ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
@@ -0,0 +1,23 @@
1
+ import { GameApi, PlayerData, TechnoRules, Vector2 } from "@chronodivide/game-api";
2
+ import { getPointTowardsOtherPoint } from "../map/map.js";
3
+ import { getDefaultPlacementLocation } from "./buildingRules.js";
4
+
5
+ export const getStaticDefencePlacement = (game: GameApi, playerData: PlayerData, technoRules: TechnoRules) => {
6
+ // Prefer front towards enemy.
7
+ let startLocation = playerData.startLocation;
8
+ let players = game.getPlayers();
9
+ let enemyFacingLocationCandidates: Vector2[] = [];
10
+ for (let i = 0; i < players.length; ++i) {
11
+ let playerName = players[i];
12
+ if (playerName == playerData.name) {
13
+ continue;
14
+ }
15
+ let enemyPlayer = game.getPlayerData(playerName);
16
+ enemyFacingLocationCandidates.push(
17
+ getPointTowardsOtherPoint(game, startLocation, enemyPlayer.startLocation, 4, 16, 1.5),
18
+ );
19
+ }
20
+ let selectedLocation =
21
+ enemyFacingLocationCandidates[Math.floor(game.generateRandom() * enemyFacingLocationCandidates.length)];
22
+ return getDefaultPlacementLocation(game, playerData, selectedLocation, technoRules, false, 0);
23
+ };
@@ -3,6 +3,7 @@ import { GlobalThreat } from "../threat/threat.js";
3
3
  import { BasicGroundUnit } from "./basicGroundUnit.js";
4
4
 
5
5
  const IDEAL_HARVESTERS_PER_REFINERY = 2;
6
+ const MAX_HARVESTERS_PER_REFINERY = 4;
6
7
 
7
8
  export class Harvester extends BasicGroundUnit {
8
9
  constructor(
@@ -23,7 +24,7 @@ export class Harvester extends BasicGroundUnit {
23
24
  const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
24
25
  const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
25
26
 
26
- const boost = harvesters < this.minNeeded ? 3 : 1;
27
+ const boost = harvesters < this.minNeeded ? 3 : harvesters > refineries * MAX_HARVESTERS_PER_REFINERY ? 0 : 1;
27
28
 
28
29
  return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
29
30
  }
@@ -14,6 +14,7 @@ import {
14
14
  DEFAULT_BUILDING_PRIORITY,
15
15
  getDefaultPlacementLocation,
16
16
  } from "./buildingRules.js";
17
+ import { DebugLogger } from "../common/utils";
17
18
 
18
19
  export const QUEUES = [
19
20
  QueueType.Structures,
@@ -43,9 +44,16 @@ export const queueTypeToName = (queue: QueueType) => {
43
44
  }
44
45
  };
45
46
 
46
- const DEBUG_BUILD_QUEUES = true;
47
+ type QueueState = {
48
+ queue: QueueType;
49
+ /** sorted in ascending order (last item is the topItem) */
50
+ items: TechnoRulesWithPriority[];
51
+ topItem: TechnoRulesWithPriority | undefined;
52
+ };
47
53
 
48
54
  export class QueueController {
55
+ private queueStates: QueueState[] = [];
56
+
49
57
  constructor() {}
50
58
 
51
59
  public onAiUpdate(
@@ -54,23 +62,35 @@ export class QueueController {
54
62
  actionsApi: ActionsApi,
55
63
  playerData: PlayerData,
56
64
  threatCache: GlobalThreat | null,
65
+ unitTypeRequests: Map<string, number>,
57
66
  logger: (message: string) => void,
58
67
  ) {
59
- const decisions = QUEUES.map((queueType) => {
68
+ this.queueStates = QUEUES.map((queueType) => {
60
69
  const options = productionApi.getAvailableObjects(queueType);
70
+ const items = this.getPrioritiesForBuildingOptions(
71
+ game,
72
+ options,
73
+ threatCache,
74
+ playerData,
75
+ unitTypeRequests,
76
+ logger,
77
+ );
78
+ const topItem = items.length > 0 ? items[items.length - 1] : undefined;
61
79
  return {
62
80
  queue: queueType,
63
- decision: this.getBestOptionForBuilding(game, options, threatCache, playerData, logger),
81
+ items,
82
+ // only if the top item has a priority above zero
83
+ topItem: topItem && topItem.priority > 0 ? topItem : undefined,
64
84
  };
65
- }).filter((decision) => decision.decision != null);
66
- let totalWeightAcrossQueues = decisions
67
- .map((decision) => decision.decision?.priority!)
85
+ });
86
+ const totalWeightAcrossQueues = this.queueStates
87
+ .map((decision) => decision.topItem?.priority!)
68
88
  .reduce((pV, cV) => pV + cV, 0);
69
- let totalCostAcrossQueues = decisions
70
- .map((decision) => decision.decision?.unit.cost!)
89
+ const totalCostAcrossQueues = this.queueStates
90
+ .map((decision) => decision.topItem?.unit.cost!)
71
91
  .reduce((pV, cV) => pV + cV, 0);
72
92
 
73
- decisions.forEach((decision) => {
93
+ this.queueStates.forEach((decision) => {
74
94
  this.updateBuildQueue(
75
95
  game,
76
96
  productionApi,
@@ -78,7 +98,7 @@ export class QueueController {
78
98
  playerData,
79
99
  threatCache,
80
100
  decision.queue,
81
- decision.decision,
101
+ decision.topItem,
82
102
  totalWeightAcrossQueues,
83
103
  totalCostAcrossQueues,
84
104
  logger,
@@ -86,15 +106,17 @@ export class QueueController {
86
106
  });
87
107
 
88
108
  // Repair is simple - just repair everything that's damaged.
89
- game.getVisibleUnits(playerData.name, "self", (r) => r.repairable).forEach((unitId) => {
90
- const unit = game.getUnitData(unitId);
91
- if (!unit || !unit.hitPoints || !unit.maxHitPoints || unit.hasWrenchRepair) {
92
- return;
93
- }
94
- if (unit.hitPoints < unit.maxHitPoints) {
95
- actionsApi.toggleRepairWrench(unitId);
96
- }
97
- });
109
+ if (playerData.credits > 0) {
110
+ game.getVisibleUnits(playerData.name, "self", (r) => r.repairable).forEach((unitId) => {
111
+ const unit = game.getUnitData(unitId);
112
+ if (!unit || !unit.hitPoints || !unit.maxHitPoints || unit.hasWrenchRepair) {
113
+ return;
114
+ }
115
+ if (unit.hitPoints < unit.maxHitPoints) {
116
+ actionsApi.toggleRepairWrench(unitId);
117
+ }
118
+ });
119
+ }
98
120
  }
99
121
 
100
122
  private updateBuildQueue(
@@ -139,20 +161,24 @@ export class QueueController {
139
161
  }
140
162
  }
141
163
  } else if (queueData.status == QueueStatus.Active && queueData.items.length > 0 && decision != null) {
142
- // Consider cancelling if something else is significantly higher priority.
143
- const current = queueData.items[0].rules;
144
- const options = productionApi.getAvailableObjects(queueType);
145
- if (decision.unit != current) {
164
+ // Consider cancelling if something else is significantly higher priority than what is currently being produced.
165
+ const currentProduction = queueData.items[0].rules;
166
+ if (decision.unit != currentProduction) {
146
167
  // Changing our mind.
147
- let currentItemPriority = this.getPriorityForBuildingOption(current, game, playerData, threatCache);
168
+ let currentItemPriority = this.getPriorityForBuildingOption(
169
+ currentProduction,
170
+ game,
171
+ playerData,
172
+ threatCache,
173
+ );
148
174
  let newItemPriority = decision.priority;
149
175
  if (newItemPriority > currentItemPriority * 2) {
150
176
  logger(
151
- `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${current.name} because ${
177
+ `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${currentProduction.name} because ${
152
178
  decision.unit.name
153
179
  } has 2x higher priority.`,
154
180
  );
155
- actionsApi.unqueueFromProduction(queueData.type, current.name, current.type, 1);
181
+ actionsApi.unqueueFromProduction(queueData.type, currentProduction.name, currentProduction.type, 1);
156
182
  }
157
183
  } else {
158
184
  // Not changing our mind, but maybe other queues are more important for now.
@@ -181,32 +207,29 @@ export class QueueController {
181
207
  }
182
208
  }
183
209
 
184
- private getBestOptionForBuilding(
210
+ private getPrioritiesForBuildingOptions(
185
211
  game: GameApi,
186
212
  options: TechnoRules[],
187
213
  threatCache: GlobalThreat | null,
188
214
  playerData: PlayerData,
189
- logger: (message: string) => void,
190
- ): TechnoRulesWithPriority | undefined {
215
+ unitTypeRequests: Map<string, number>,
216
+ logger: DebugLogger,
217
+ ): TechnoRulesWithPriority[] {
191
218
  let priorityQueue: TechnoRulesWithPriority[] = [];
192
219
  options.forEach((option) => {
193
- let priority = this.getPriorityForBuildingOption(option, game, playerData, threatCache);
194
- if (priority > 0) {
195
- priorityQueue.push({ unit: option, priority: priority });
220
+ const calculatedPriority = this.getPriorityForBuildingOption(option, game, playerData, threatCache);
221
+ // Get the higher of the dynamic and the mission priority for the unit.
222
+ const actualPriority = Math.max(
223
+ calculatedPriority,
224
+ unitTypeRequests.get(option.name) ?? calculatedPriority,
225
+ );
226
+ if (actualPriority > 0) {
227
+ priorityQueue.push({ unit: option, priority: actualPriority });
196
228
  }
197
229
  });
198
230
 
199
- priorityQueue = priorityQueue.sort((a, b) => {
200
- return a.priority - b.priority;
201
- });
202
- if (priorityQueue.length > 0) {
203
- if (DEBUG_BUILD_QUEUES && game.getCurrentTick() % 100 === 0) {
204
- let queueString = priorityQueue.map((item) => item.unit.name + "(" + item.priority + ")").join(", ");
205
- logger(`Build priority currently: ${queueString}`);
206
- }
207
- }
208
-
209
- return priorityQueue.pop();
231
+ priorityQueue = priorityQueue.sort((a, b) => a.priority - b.priority);
232
+ return priorityQueue;
210
233
  }
211
234
 
212
235
  private getPriorityForBuildingOption(
@@ -239,4 +262,36 @@ export class QueueController {
239
262
  return getDefaultPlacementLocation(game, playerData, playerData.startLocation, objectReady);
240
263
  }
241
264
  }
265
+
266
+ public getGlobalDebugText(gameApi: GameApi, productionApi: ProductionApi) {
267
+ const productionState = QUEUES.reduce((prev, queueType) => {
268
+ if (productionApi.getQueueData(queueType).size === 0) {
269
+ return prev;
270
+ }
271
+ const paused = productionApi.getQueueData(queueType).status === QueueStatus.OnHold;
272
+ return (
273
+ prev +
274
+ " [" +
275
+ queueTypeToName(queueType) +
276
+ (paused ? " PAUSED" : "") +
277
+ ": " +
278
+ productionApi
279
+ .getQueueData(queueType)
280
+ .items.map((item) => item.rules.name + (item.quantity > 1 ? "x" + item.quantity : "")) +
281
+ "]"
282
+ );
283
+ }, "");
284
+
285
+ const queueStates = this.queueStates
286
+ .filter((queueState) => queueState.items.length > 0)
287
+ .map((queueState) => {
288
+ const queueString = queueState.items
289
+ .map((item) => item.unit.name + "(" + Math.round(item.priority * 10) / 10 + ")")
290
+ .join(", ");
291
+ return `${queueTypeToName(queueState.queue)} Prios: ${queueString}\n`;
292
+ })
293
+ .join("");
294
+
295
+ return `Production: ${productionState}\n${queueStates}`;
296
+ }
242
297
  }