@supalosa/chronodivide-bot 0.3.1 → 0.5.1

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 (149) hide show
  1. package/.env.template +5 -0
  2. package/README.md +57 -39
  3. package/dist/bot/bot.js +27 -37
  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/awarenessImpl.js +132 -0
  8. package/dist/bot/logic/awarenessImpl.js.map +1 -0
  9. package/dist/bot/logic/building/ArtilleryUnit.js +2 -29
  10. package/dist/bot/logic/building/ArtilleryUnit.js.map +1 -0
  11. package/dist/bot/logic/building/antiAirStaticDefence.js +43 -0
  12. package/dist/bot/logic/building/antiAirStaticDefence.js.map +1 -0
  13. package/dist/bot/logic/building/antiGroundStaticDefence.js +8 -5
  14. package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
  15. package/dist/bot/logic/building/basicAirUnit.js +2 -23
  16. package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
  17. package/dist/bot/logic/building/basicBuilding.js +3 -2
  18. package/dist/bot/logic/building/basicBuilding.js.map +1 -1
  19. package/dist/bot/logic/building/basicGroundUnit.js +2 -43
  20. package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
  21. package/dist/bot/logic/building/building.js +55 -11
  22. package/dist/bot/logic/building/building.js.map +1 -0
  23. package/dist/bot/logic/building/buildingRules.js +62 -50
  24. package/dist/bot/logic/building/buildingRules.js.map +1 -1
  25. package/dist/bot/logic/building/common.js +19 -0
  26. package/dist/bot/logic/building/common.js.map +1 -0
  27. package/dist/bot/logic/building/harvester.js +2 -1
  28. package/dist/bot/logic/building/harvester.js.map +1 -1
  29. package/dist/bot/logic/building/queueController.js +73 -41
  30. package/dist/bot/logic/building/queueController.js.map +1 -1
  31. package/dist/bot/logic/common/utils.js +35 -0
  32. package/dist/bot/logic/common/utils.js.map +1 -1
  33. package/dist/bot/logic/composition/alliedCompositions.js +13 -0
  34. package/dist/bot/logic/composition/alliedCompositions.js.map +1 -0
  35. package/dist/bot/logic/composition/common.js +2 -0
  36. package/dist/bot/logic/composition/common.js.map +1 -0
  37. package/dist/bot/logic/composition/sovietCompositions.js +13 -0
  38. package/dist/bot/logic/composition/sovietCompositions.js.map +1 -0
  39. package/dist/bot/logic/mission/actionBatcher.js +92 -0
  40. package/dist/bot/logic/mission/actionBatcher.js.map +1 -0
  41. package/dist/bot/logic/mission/behaviours/combatSquad.js +124 -0
  42. package/dist/bot/logic/mission/behaviours/combatSquad.js.map +1 -0
  43. package/dist/bot/logic/mission/behaviours/common.js +58 -0
  44. package/dist/bot/logic/mission/behaviours/common.js.map +1 -0
  45. package/dist/bot/logic/mission/behaviours/engineerSquad.js +39 -0
  46. package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +1 -0
  47. package/dist/bot/logic/mission/behaviours/expansionSquad.js +46 -0
  48. package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +1 -0
  49. package/dist/bot/logic/mission/behaviours/retreatSquad.js +31 -0
  50. package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +1 -0
  51. package/{src/bot/logic/squad/behaviours/scoutingSquad.ts → dist/bot/logic/mission/behaviours/scoutingSquad.js} +29 -47
  52. package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +1 -0
  53. package/dist/bot/logic/mission/mission.js +91 -19
  54. package/dist/bot/logic/mission/mission.js.map +1 -1
  55. package/dist/bot/logic/mission/missionController.js +262 -21
  56. package/dist/bot/logic/mission/missionController.js.map +1 -1
  57. package/dist/bot/logic/mission/missions/attackMission.js +159 -52
  58. package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
  59. package/dist/bot/logic/mission/missions/basicMission.js +13 -0
  60. package/dist/bot/logic/mission/missions/basicMission.js.map +1 -0
  61. package/dist/bot/logic/mission/missions/defenceMission.js +43 -28
  62. package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
  63. package/dist/bot/logic/mission/missions/engineerMission.js +37 -7
  64. package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -1
  65. package/dist/bot/logic/mission/missions/expansionMission.js +42 -6
  66. package/dist/bot/logic/mission/missions/expansionMission.js.map +1 -1
  67. package/dist/bot/logic/mission/missions/missionBehaviour.js +2 -0
  68. package/dist/bot/logic/mission/missions/missionBehaviour.js.map +1 -0
  69. package/dist/bot/logic/mission/missions/retreatMission.js +31 -5
  70. package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
  71. package/dist/bot/logic/mission/missions/scoutingMission.js +103 -6
  72. package/dist/bot/logic/mission/missions/scoutingMission.js.map +1 -1
  73. package/dist/bot/logic/mission/missions/squads/combatSquad.js +116 -0
  74. package/dist/bot/logic/mission/missions/squads/combatSquad.js.map +1 -0
  75. package/dist/bot/logic/mission/missions/squads/common.js +58 -0
  76. package/dist/bot/logic/mission/missions/squads/common.js.map +1 -0
  77. package/dist/bot/logic/mission/missions/squads/squad.js +2 -0
  78. package/dist/bot/logic/mission/missions/squads/squad.js.map +1 -0
  79. package/dist/bot/logic/squad/behaviours/attackSquad.js +63 -56
  80. package/dist/bot/logic/squad/behaviours/combatSquad.js +19 -18
  81. package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
  82. package/dist/bot/logic/squad/behaviours/common.js +2 -19
  83. package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
  84. package/dist/bot/logic/squad/behaviours/defenceSquad.js +15 -2
  85. package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
  86. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +17 -21
  87. package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
  88. package/dist/bot/logic/squad/squad.js +8 -5
  89. package/dist/bot/logic/squad/squad.js.map +1 -1
  90. package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
  91. package/dist/bot/logic/squad/squadController.js +3 -2
  92. package/dist/bot/logic/squad/squadController.js.map +1 -1
  93. package/dist/bot/logic/threat/threatCalculator.js +5 -5
  94. package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
  95. package/dist/exampleBot.js +53 -16
  96. package/dist/exampleBot.js.map +1 -1
  97. package/package.json +5 -4
  98. package/src/bot/bot.ts +38 -53
  99. package/src/bot/logic/awareness.ts +34 -22
  100. package/src/bot/logic/building/antiAirStaticDefence.ts +64 -0
  101. package/src/bot/logic/building/antiGroundStaticDefence.ts +7 -20
  102. package/src/bot/logic/building/artilleryUnit.ts +2 -28
  103. package/src/bot/logic/building/basicAirUnit.ts +2 -33
  104. package/src/bot/logic/building/basicBuilding.ts +8 -6
  105. package/src/bot/logic/building/basicGroundUnit.ts +2 -46
  106. package/src/bot/logic/building/buildingRules.ts +73 -57
  107. package/src/bot/logic/building/common.ts +23 -0
  108. package/src/bot/logic/building/harvester.ts +2 -1
  109. package/src/bot/logic/building/queueController.ts +105 -42
  110. package/src/bot/logic/common/utils.ts +47 -0
  111. package/src/bot/logic/composition/alliedCompositions.ts +22 -0
  112. package/src/bot/logic/composition/common.ts +3 -0
  113. package/src/bot/logic/composition/sovietCompositions.ts +21 -0
  114. package/src/bot/logic/mission/actionBatcher.ts +124 -0
  115. package/src/bot/logic/mission/mission.ts +186 -37
  116. package/src/bot/logic/mission/missionController.ts +340 -31
  117. package/src/bot/logic/mission/missionFactories.ts +3 -3
  118. package/src/bot/logic/mission/missions/attackMission.ts +234 -56
  119. package/src/bot/logic/mission/missions/defenceMission.ts +72 -45
  120. package/src/bot/logic/mission/missions/engineerMission.ts +67 -15
  121. package/src/bot/logic/mission/missions/expansionMission.ts +67 -14
  122. package/src/bot/logic/mission/missions/retreatMission.ts +50 -6
  123. package/src/bot/logic/mission/missions/scoutingMission.ts +138 -14
  124. package/src/bot/logic/mission/missions/squads/combatSquad.ts +160 -0
  125. package/src/bot/logic/{squad/behaviours → mission/missions/squads}/common.ts +14 -20
  126. package/src/bot/logic/mission/missions/squads/squad.ts +19 -0
  127. package/src/bot/logic/threat/threat.ts +15 -15
  128. package/src/bot/logic/threat/threatCalculator.ts +10 -10
  129. package/src/exampleBot.ts +59 -19
  130. package/.prettierrc +0 -5
  131. package/TODO.md +0 -18
  132. package/dist/bot/logic/building/artilleryUnit.js.map +0 -1
  133. package/dist/bot/logic/building/massedAntiGroundUnit.js +0 -20
  134. package/dist/bot/logic/building/queues.js +0 -19
  135. package/dist/bot/logic/knowledge.js +0 -1
  136. package/dist/bot/logic/mission/basicMission.js +0 -26
  137. package/dist/bot/logic/mission/expansionMission.js +0 -32
  138. package/dist/bot/logic/squad/behaviours/squadExpansion.js +0 -31
  139. package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
  140. package/rules.ini +0 -23126
  141. package/src/bot/logic/mission/missions/oneTimeMission.ts +0 -33
  142. package/src/bot/logic/squad/behaviours/combatSquad.ts +0 -127
  143. package/src/bot/logic/squad/behaviours/engineerSquad.ts +0 -53
  144. package/src/bot/logic/squad/behaviours/expansionSquad.ts +0 -59
  145. package/src/bot/logic/squad/behaviours/retreatSquad.ts +0 -44
  146. package/src/bot/logic/squad/squad.ts +0 -159
  147. package/src/bot/logic/squad/squadBehaviour.ts +0 -62
  148. package/src/bot/logic/squad/squadBehaviours.ts +0 -8
  149. package/src/bot/logic/squad/squadController.ts +0 -254
@@ -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, 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(
@@ -2,6 +2,7 @@ import {
2
2
  BuildingPlacementData,
3
3
  GameApi,
4
4
  GameMath,
5
+ LandType,
5
6
  ObjectType,
6
7
  PlayerData,
7
8
  Size,
@@ -19,6 +20,7 @@ import { PowerPlant } from "./powerPlant.js";
19
20
  import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
20
21
  import { Harvester } from "./harvester.js";
21
22
  import { uniqBy } from "../common/utils.js";
23
+ import { AntiAirStaticDefence } from "./antiAirStaticDefence.js";
22
24
 
23
25
  export interface AiBuildingRules {
24
26
  getPriority(
@@ -51,21 +53,23 @@ export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, n
51
53
  }
52
54
 
53
55
  /**
54
- * Computes a rect 'centered' around a structure of a certain size with additional radius.
56
+ * Computes a rect 'centered' around a structure of a certain size with an additional radius (`adjacent`).
57
+ * The radius is optionally expanded by the size of the new building.
55
58
  *
56
- * This is essentially the placeable area around a given structure.
59
+ * This is essentially the candidate placement around a given structure.
57
60
  *
58
61
  * @param point Top-left location of the inner rect.
59
62
  * @param t Size of the inner rect.
60
- * @param adjacent Size of the outer rect.
63
+ * @param adjacent Amount to expand the building's inner rect by (so buildings must be adjacent by this many tiles)
64
+ * @param newBuildingSize? Size of the new building
61
65
  * @returns
62
66
  */
63
- function computeAdjacentRect(point: Vector2, t: Size, adjacent: number) {
67
+ function computeAdjacentRect(point: Vector2, t: Size, adjacent: number, newBuildingSize?: Size) {
64
68
  return {
65
- x: point.x - adjacent,
66
- y: point.y - adjacent,
67
- width: t.width + 2 * adjacent,
68
- height: t.height + 2 * adjacent,
69
+ x: point.x - adjacent - (newBuildingSize?.width || 0),
70
+ y: point.y - adjacent - (newBuildingSize?.height || 0),
71
+ width: t.width + 2 * adjacent + (newBuildingSize?.width || 0),
72
+ height: t.height + 2 * adjacent + (newBuildingSize?.height || 0),
69
73
  };
70
74
  }
71
75
 
@@ -73,8 +77,9 @@ export function getAdjacencyTiles(
73
77
  game: GameApi,
74
78
  playerData: PlayerData,
75
79
  technoRules: TechnoRules,
80
+ onWater: boolean,
76
81
  minimumSpace: number,
77
- ) {
82
+ ): Tile[] {
78
83
  const placementRules = game.getBuildingPlacementData(technoRules.name);
79
84
  const { width: newBuildingWidth, height: newBuildingHeight } = placementRules.foundation;
80
85
  const tiles = [];
@@ -82,44 +87,48 @@ export function getAdjacencyTiles(
82
87
  const removedTiles = new Set<string>();
83
88
  for (let buildingId of buildings) {
84
89
  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;
96
- }
97
- const adjacentTiles = game.mapApi.getTilesInRect(baseTile, {
90
+ if (!building?.rules?.baseNormal) {
91
+ // This building is not considered for adjacency checks.
92
+ continue;
93
+ }
94
+ const { foundation, tile } = building;
95
+ const buildingBase = new Vector2(tile.rx, tile.ry);
96
+ const buildingSize = {
97
+ width: foundation?.width,
98
+ height: foundation?.height,
99
+ };
100
+ const range = computeAdjacentRect(buildingBase, buildingSize, technoRules.adjacent, placementRules.foundation);
101
+ const baseTile = game.mapApi.getTile(range.x, range.y);
102
+ if (!baseTile) {
103
+ continue;
104
+ }
105
+ const adjacentTiles = game.mapApi
106
+ .getTilesInRect(baseTile, {
98
107
  width: range.width,
99
108
  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),
109
+ })
110
+ .filter((tile) => !onWater || tile.landType === LandType.Water);
111
+ tiles.push(...adjacentTiles);
112
+
113
+ // Prevent placing the new building on tiles that would cause it to overlap with this building.
114
+ const modifiedBase = new Vector2(
115
+ buildingBase.x - (newBuildingWidth - 1),
116
+ buildingBase.y - (newBuildingHeight - 1),
117
+ );
118
+ const modifiedSize = {
119
+ width: buildingSize.width + (newBuildingWidth - 1),
120
+ height: buildingSize.height + (newBuildingHeight - 1),
121
+ };
122
+ const blockedRect = computeAdjacentRect(modifiedBase, modifiedSize, minimumSpace);
123
+ const buildingTiles = adjacentTiles.filter((tile) => {
124
+ return (
125
+ tile.rx >= blockedRect.x &&
126
+ tile.rx < blockedRect.x + blockedRect.width &&
127
+ tile.ry >= blockedRect.y &&
128
+ tile.ry < blockedRect.y + blockedRect.height
107
129
  );
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));
122
- }
130
+ });
131
+ buildingTiles.forEach((buildingTile) => removedTiles.add(buildingTile.id));
123
132
  }
124
133
  // Remove duplicate tiles.
125
134
  const withDuplicatesRemoved = uniqBy(tiles, (tile) => tile.id);
@@ -151,17 +160,18 @@ function distance(x1: number, y1: number, x2: number, y2: number) {
151
160
  export function getDefaultPlacementLocation(
152
161
  game: GameApi,
153
162
  playerData: PlayerData,
154
- startPoint: Vector2,
163
+ idealPoint: Vector2,
155
164
  technoRules: TechnoRules,
165
+ onWater: boolean = false,
156
166
  minSpace: number = 1,
157
167
  ): { rx: number; ry: number } | undefined {
158
- // Random location, preferably near start location.
168
+ // Closest possible location near `startPoint`.
159
169
  const size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
160
170
  if (!size) {
161
171
  return undefined;
162
172
  }
163
- const tiles = getAdjacencyTiles(game, playerData, technoRules, minSpace);
164
- const tileDistances = getTileDistances(startPoint, tiles);
173
+ const tiles = getAdjacencyTiles(game, playerData, technoRules, onWater, minSpace);
174
+ const tileDistances = getTileDistances(idealPoint, tiles);
165
175
 
166
176
  for (let tileDistance of tileDistances) {
167
177
  if (tileDistance.tile && game.canPlaceBuilding(playerData.name, technoRules.name, tileDistance.tile)) {
@@ -174,7 +184,7 @@ export function getDefaultPlacementLocation(
174
184
  // Priority 0 = don't build.
175
185
  export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
176
186
 
177
- export const DEFAULT_BUILDING_PRIORITY = 1;
187
+ export const DEFAULT_BUILDING_PRIORITY = 0;
178
188
 
179
189
  export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
180
190
  // Allied
@@ -183,18 +193,20 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
183
193
  ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
184
194
  ["GAPILE", new BasicBuilding(12, 1)], // Barracks
185
195
  ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
186
- ["ENGINEER", new BasicBuilding(10, 1, 1000)], // Engineer
187
196
  ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
188
197
  ["GAAIRC", new BasicBuilding(10, 1, 500)], // Airforce Command
198
+ ["AMRADR", new BasicBuilding(10, 1, 500)], // Airforce Command (USA)
189
199
 
190
200
  ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
191
201
  ["GAYARD", new BasicBuilding(0, 0, 0)], // Naval Yard, disabled
192
202
 
193
- ["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
194
- ["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
195
206
  ["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
196
207
 
197
- ["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
198
210
  ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
199
211
  ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
200
212
  ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
@@ -210,7 +222,6 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
210
222
  ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
211
223
  ["NAHAND", new BasicBuilding(12, 1)], // Barracks
212
224
  ["HARV", new Harvester(15, 4, 2)], // War Miner
213
- ["SENGINEER", new BasicBuilding(10, 1, 1000)], // Soviet Engineer
214
225
  ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
215
226
  ["NARADR", new BasicBuilding(10, 1, 500)], // Radar
216
227
  ["NANRCT", new PowerPlant()], // Nuclear Reactor
@@ -218,11 +229,16 @@ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
218
229
 
219
230
  ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
220
231
 
221
- ["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
222
- ["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
223
235
  ["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
224
236
 
225
- ["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
226
242
  ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
227
243
  ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
228
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
  }