@supalosa/chronodivide-bot 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +71 -46
  2. package/dist/bot/bot.js +27 -183
  3. package/dist/bot/logic/awareness.js +122 -0
  4. package/dist/bot/logic/building/basicGroundUnit.js +8 -6
  5. package/dist/bot/logic/building/building.js +6 -3
  6. package/dist/bot/logic/building/harvester.js +1 -1
  7. package/dist/bot/logic/building/queueController.js +4 -21
  8. package/dist/bot/logic/common/scout.js +10 -0
  9. package/dist/bot/logic/knowledge.js +1 -0
  10. package/dist/bot/logic/map/map.js +6 -0
  11. package/dist/bot/logic/map/sector.js +6 -1
  12. package/dist/bot/logic/mission/basicMission.js +1 -5
  13. package/dist/bot/logic/mission/expansionMission.js +22 -4
  14. package/dist/bot/logic/mission/mission.js +49 -2
  15. package/dist/bot/logic/mission/missionController.js +67 -34
  16. package/dist/bot/logic/mission/missionFactories.js +10 -0
  17. package/dist/bot/logic/mission/missions/attackMission.js +109 -0
  18. package/dist/bot/logic/mission/missions/defenceMission.js +62 -0
  19. package/dist/bot/logic/mission/missions/expansionMission.js +24 -0
  20. package/dist/bot/logic/mission/missions/oneTimeMission.js +26 -0
  21. package/dist/bot/logic/mission/missions/retreatMission.js +7 -0
  22. package/dist/bot/logic/mission/missions/scoutingMission.js +38 -0
  23. package/dist/bot/logic/squad/behaviours/attackSquad.js +82 -0
  24. package/dist/bot/logic/squad/behaviours/combatSquad.js +99 -0
  25. package/dist/bot/logic/squad/behaviours/common.js +37 -0
  26. package/dist/bot/logic/squad/behaviours/defenceSquad.js +48 -0
  27. package/dist/bot/logic/squad/behaviours/expansionSquad.js +42 -0
  28. package/dist/bot/logic/squad/behaviours/retreatSquad.js +32 -0
  29. package/dist/bot/logic/squad/behaviours/scoutingSquad.js +38 -0
  30. package/dist/bot/logic/squad/behaviours/squadExpansion.js +26 -13
  31. package/dist/bot/logic/squad/squad.js +68 -15
  32. package/dist/bot/logic/squad/squadBehaviour.js +5 -5
  33. package/dist/bot/logic/squad/squadBehaviours.js +6 -0
  34. package/dist/bot/logic/squad/squadController.js +106 -15
  35. package/dist/exampleBot.js +22 -7
  36. package/package.json +29 -24
  37. package/src/bot/bot.ts +178 -378
  38. package/src/bot/logic/awareness.ts +220 -0
  39. package/src/bot/logic/building/ArtilleryUnit.ts +2 -2
  40. package/src/bot/logic/building/antiGroundStaticDefence.ts +2 -2
  41. package/src/bot/logic/building/basicAirUnit.ts +2 -2
  42. package/src/bot/logic/building/basicBuilding.ts +2 -2
  43. package/src/bot/logic/building/basicGroundUnit.ts +83 -78
  44. package/src/bot/logic/building/building.ts +125 -120
  45. package/src/bot/logic/building/harvester.ts +27 -27
  46. package/src/bot/logic/building/powerPlant.ts +1 -1
  47. package/src/bot/logic/building/queueController.ts +17 -38
  48. package/src/bot/logic/building/resourceCollectionBuilding.ts +1 -1
  49. package/src/bot/logic/common/scout.ts +12 -0
  50. package/src/bot/logic/map/map.ts +11 -3
  51. package/src/bot/logic/map/sector.ts +136 -130
  52. package/src/bot/logic/mission/mission.ts +83 -47
  53. package/src/bot/logic/mission/missionController.ts +103 -51
  54. package/src/bot/logic/mission/missionFactories.ts +46 -0
  55. package/src/bot/logic/mission/missions/attackMission.ts +152 -0
  56. package/src/bot/logic/mission/missions/defenceMission.ts +104 -0
  57. package/src/bot/logic/mission/missions/expansionMission.ts +49 -0
  58. package/src/bot/logic/mission/missions/oneTimeMission.ts +32 -0
  59. package/src/bot/logic/mission/missions/retreatMission.ts +9 -0
  60. package/src/bot/logic/mission/missions/scoutingMission.ts +59 -0
  61. package/src/bot/logic/squad/behaviours/combatSquad.ts +125 -0
  62. package/src/bot/logic/squad/behaviours/common.ts +37 -0
  63. package/src/bot/logic/squad/behaviours/expansionSquad.ts +59 -0
  64. package/src/bot/logic/squad/behaviours/retreatSquad.ts +46 -0
  65. package/src/bot/logic/squad/behaviours/scoutingSquad.ts +56 -0
  66. package/src/bot/logic/squad/squad.ts +163 -97
  67. package/src/bot/logic/squad/squadBehaviour.ts +61 -43
  68. package/src/bot/logic/squad/squadBehaviours.ts +8 -0
  69. package/src/bot/logic/squad/squadController.ts +190 -66
  70. package/src/exampleBot.ts +19 -4
  71. package/tsconfig.json +1 -1
  72. package/src/bot/logic/mission/basicMission.ts +0 -42
  73. package/src/bot/logic/mission/expansionMission.ts +0 -25
  74. package/src/bot/logic/squad/behaviours/squadExpansion.ts +0 -33
@@ -1,120 +1,125 @@
1
- import { BuildingPlacementData, GameApi, PlayerData, Point2D, TechnoRules } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../threat/threat.js";
3
- import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
4
- import { ArtilleryUnit } from "./ArtilleryUnit.js";
5
- import { BasicAirUnit } from "./basicAirUnit.js";
6
- import { BasicBuilding } from "./basicBuilding.js";
7
- import { BasicGroundUnit } from "./basicGroundUnit.js";
8
- import { PowerPlant } from "./powerPlant.js";
9
- import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
10
- import { Harvester } from "./harvester.js";
11
-
12
- export interface AiBuildingRules {
13
- getPriority(
14
- game: GameApi,
15
- playerData: PlayerData,
16
- technoRules: TechnoRules,
17
- threatCache: GlobalThreat | undefined
18
- ): number;
19
-
20
- getPlacementLocation(
21
- game: GameApi,
22
- playerData: PlayerData,
23
- technoRules: TechnoRules
24
- ): { rx: number; ry: number } | undefined;
25
-
26
- getMaxCount(
27
- game: GameApi,
28
- playerData: PlayerData,
29
- technoRules: TechnoRules,
30
- threatCache: GlobalThreat | undefined
31
- ): number | null;
32
- }
33
-
34
- export function numBuildingsOwnedOfType(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
35
- return game.getVisibleUnits(playerData.name, "self", (r) => r == technoRules).length;
36
- }
37
-
38
- export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, name: string): number {
39
- return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
40
- }
41
-
42
- export function getDefaultPlacementLocation(
43
- game: GameApi,
44
- playerData: PlayerData,
45
- startPoint: Point2D,
46
- technoRules: TechnoRules,
47
- space: number = 1
48
- ): { rx: number; ry: number } | undefined {
49
- // Random location, preferably near start location.
50
- let startX = startPoint.x;
51
- let startY = startPoint.y;
52
- let size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
53
- if (!size) {
54
- return undefined;
55
- }
56
- let largestSize = Math.max(size.foundation.height, size.foundation.width);
57
- for (let searchRadius = largestSize; searchRadius < 25 + largestSize; ++searchRadius) {
58
- for (let xx = startX - searchRadius; xx < startX + searchRadius; ++xx) {
59
- for (let yy = startY - searchRadius; yy < startY + searchRadius; ++yy) {
60
- let tile = game.mapApi.getTile(xx, yy);
61
- if (tile && game.canPlaceBuilding(playerData.name, technoRules.name, tile)) {
62
- return { rx: xx, ry: yy };
63
- }
64
- }
65
- }
66
- }
67
- console.log("Can't find a place to put the " + technoRules.name);
68
- return undefined;
69
- }
70
-
71
- // Priority 0 = don't build.
72
- export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
73
-
74
- export const DEFAULT_BUILDING_PRIORITY = 1;
75
-
76
- export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
77
- // Allied
78
- ["GAPOWR", new PowerPlant()],
79
- ["GAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
80
- ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
81
- ["GAPILE", new BasicBuilding(12, 1)], // Barracks
82
- ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
83
- ["ENGINEER", new BasicBuilding(1, 1, 10000)], // Engineer
84
- ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
85
- ["GAAIRC", new BasicBuilding(8, 1, 6000)], // Airforce Command
86
-
87
- ["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
88
- ["ATESLA", new AntiGroundStaticDefence(5, 1, 10)], // Prism Cannon
89
- ["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
90
-
91
- ["E1", new BasicGroundUnit(5, 3, 0.25, 0)], // GI
92
- ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
93
- ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
94
- ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
95
- ["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
96
- ["SREF", new ArtilleryUnit(9, 1)], // Prism Tank
97
- ["CLEG", new BasicGroundUnit(0, 0)], // Chrono Legionnaire (Disabled - we don't handle the warped out phase properly and it tends to bug both bots out)
98
- ["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
99
-
100
- // Soviet
101
- ["NAPOWR", new PowerPlant()],
102
- ["NAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
103
- ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
104
- ["NAHAND", new BasicBuilding(12, 1)], // Barracks
105
- ["HARV", new Harvester(15, 4, 2)], // War Miner
106
- ["SENGINEER", new BasicBuilding(1, 1, 10000)], // Soviet Engineer
107
- ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
108
- ["NARADR", new BasicBuilding(8, 1, 4000)], // Radar
109
-
110
- ["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
111
- ["TESLA", new AntiGroundStaticDefence(5, 1, 10)], // Tesla Coil
112
- ["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
113
-
114
- ["E2", new BasicGroundUnit(5, 3, 0.25, 0)], // Conscript
115
- ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
116
- ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
117
- ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
118
- ["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
119
- ["V3", new ArtilleryUnit(9, 1)], // V3 Rocket Launcher
120
- ]);
1
+ import { BuildingPlacementData, GameApi, PlayerData, Point2D, TechnoRules } from "@chronodivide/game-api";
2
+ import { GlobalThreat } from "../threat/threat.js";
3
+ import { AntiGroundStaticDefence } from "./antiGroundStaticDefence.js";
4
+ import { ArtilleryUnit } from "./ArtilleryUnit.js";
5
+ import { BasicAirUnit } from "./basicAirUnit.js";
6
+ import { BasicBuilding } from "./basicBuilding.js";
7
+ import { BasicGroundUnit } from "./basicGroundUnit.js";
8
+ import { PowerPlant } from "./powerPlant.js";
9
+ import { ResourceCollectionBuilding } from "./resourceCollectionBuilding.js";
10
+ import { Harvester } from "./harvester.js";
11
+
12
+ export interface AiBuildingRules {
13
+ getPriority(
14
+ game: GameApi,
15
+ playerData: PlayerData,
16
+ technoRules: TechnoRules,
17
+ threatCache: GlobalThreat | null,
18
+ ): number;
19
+
20
+ getPlacementLocation(
21
+ game: GameApi,
22
+ playerData: PlayerData,
23
+ technoRules: TechnoRules,
24
+ ): { rx: number; ry: number } | undefined;
25
+
26
+ getMaxCount(
27
+ game: GameApi,
28
+ playerData: PlayerData,
29
+ technoRules: TechnoRules,
30
+ threatCache: GlobalThreat | null,
31
+ ): number | null;
32
+ }
33
+
34
+ export function numBuildingsOwnedOfType(game: GameApi, playerData: PlayerData, technoRules: TechnoRules): number {
35
+ return game.getVisibleUnits(playerData.name, "self", (r) => r == technoRules).length;
36
+ }
37
+
38
+ export function numBuildingsOwnedOfName(game: GameApi, playerData: PlayerData, name: string): number {
39
+ return game.getVisibleUnits(playerData.name, "self", (r) => r.name === name).length;
40
+ }
41
+
42
+ export function getDefaultPlacementLocation(
43
+ game: GameApi,
44
+ playerData: PlayerData,
45
+ startPoint: Point2D,
46
+ technoRules: TechnoRules,
47
+ space: number = 1,
48
+ ): { rx: number; ry: number } | undefined {
49
+ // Random location, preferably near start location.
50
+ let startX = startPoint.x;
51
+ let startY = startPoint.y;
52
+ let size: BuildingPlacementData = game.getBuildingPlacementData(technoRules.name);
53
+ if (!size) {
54
+ return undefined;
55
+ }
56
+ let largestSize = Math.max(size.foundation.height, size.foundation.width);
57
+ for (let searchRadius = largestSize; searchRadius < 25 + largestSize; ++searchRadius) {
58
+ for (let xx = startX - searchRadius; xx < startX + searchRadius; ++xx) {
59
+ for (let yy = startY - searchRadius; yy < startY + searchRadius; ++yy) {
60
+ let tile = game.mapApi.getTile(xx, yy);
61
+ if (tile && game.canPlaceBuilding(playerData.name, technoRules.name, tile)) {
62
+ return { rx: xx, ry: yy };
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ // Priority 0 = don't build.
71
+ export type TechnoRulesWithPriority = { unit: TechnoRules; priority: number };
72
+
73
+ export const DEFAULT_BUILDING_PRIORITY = 1;
74
+
75
+ export const BUILDING_NAME_TO_RULES = new Map<string, AiBuildingRules>([
76
+ // Allied
77
+ ["GAPOWR", new PowerPlant()],
78
+ ["GAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
79
+ ["GAWEAP", new BasicBuilding(15, 1)], // War Factory
80
+ ["GAPILE", new BasicBuilding(12, 1)], // Barracks
81
+ ["CMIN", new Harvester(15, 4, 2)], // Chrono Miner
82
+ ["ENGINEER", new BasicBuilding(1, 1, 10000)], // Engineer
83
+ ["GADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
84
+ ["GAAIRC", new BasicBuilding(8, 1, 6000)], // Airforce Command
85
+
86
+ ["GATECH", new BasicBuilding(20, 1, 4000)], // Allied Battle Lab
87
+
88
+ ["GAPILL", new AntiGroundStaticDefence(5, 1, 5)], // Pillbox
89
+ ["ATESLA", new AntiGroundStaticDefence(5, 1, 10)], // Prism Cannon
90
+ ["GAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
91
+
92
+ ["E1", new BasicGroundUnit(2, 3, 0.25, 0)], // GI
93
+ ["MTNK", new BasicGroundUnit(10, 3, 2, 0)], // Grizzly Tank
94
+ ["MGTK", new BasicGroundUnit(10, 1, 2.5, 0)], // Mirage Tank
95
+ ["FV", new BasicGroundUnit(5, 2, 0.5, 1)], // IFV
96
+ ["JUMPJET", new BasicAirUnit(10, 1, 1, 1)], // Rocketeer
97
+ ["ORCA", new BasicAirUnit(7, 1, 2, 0)], // Rocketeer
98
+ ["SREF", new ArtilleryUnit(9, 1)], // Prism Tank
99
+ ["CLEG", new BasicGroundUnit(0, 0)], // Chrono Legionnaire (Disabled - we don't handle the warped out phase properly and it tends to bug both bots out)
100
+ ["SHAD", new BasicGroundUnit(0, 0)], // Nighthawk (Disabled)
101
+
102
+ // Soviet
103
+ ["NAPOWR", new PowerPlant()],
104
+ ["NAREFN", new ResourceCollectionBuilding(10, 3)], // Refinery
105
+ ["NAWEAP", new BasicBuilding(15, 1)], // War Factory
106
+ ["NAHAND", new BasicBuilding(12, 1)], // Barracks
107
+ ["HARV", new Harvester(15, 4, 2)], // War Miner
108
+ ["SENGINEER", new BasicBuilding(1, 1, 10000)], // Soviet Engineer
109
+ ["NADEPT", new BasicBuilding(1, 1, 10000)], // Repair Depot
110
+ ["NARADR", new BasicBuilding(8, 1, 4000)], // Radar
111
+ ["NANRCT", new PowerPlant()], // Nuclear Reactor
112
+
113
+ ["NATECH", new BasicBuilding(20, 1, 4000)], // Soviet Battle Lab
114
+
115
+ ["NALASR", new AntiGroundStaticDefence(5, 1, 5)], // Sentry Gun
116
+ ["TESLA", new AntiGroundStaticDefence(5, 1, 10)], // Tesla Coil
117
+ ["NAWALL", new AntiGroundStaticDefence(0, 0, 0)], // Walls
118
+
119
+ ["E2", new BasicGroundUnit(2, 3, 0.25, 0)], // Conscript
120
+ ["HTNK", new BasicGroundUnit(10, 3, 3, 0)], // Rhino Tank
121
+ ["APOC", new BasicGroundUnit(6, 1, 5, 0)], // Apocalypse Tank
122
+ ["HTK", new BasicGroundUnit(5, 2, 0.33, 1.5)], // Flak Track
123
+ ["ZEP", new BasicAirUnit(5, 1, 5, 1)], // Kirov
124
+ ["V3", new ArtilleryUnit(9, 1)], // V3 Rocket Launcher
125
+ ]);
@@ -1,27 +1,27 @@
1
- import { GameApi, PlayerData, Point2D, TechnoRules, Tile } from "@chronodivide/game-api";
2
- import { GlobalThreat } from "../threat/threat.js";
3
- import { BasicBuilding } from "./basicBuilding.js";
4
- import { BasicGroundUnit } from "./basicGroundUnit.js";
5
-
6
- const IDEAL_HARVESTERS_PER_REFINERY = 2;
7
-
8
- export class Harvester extends BasicGroundUnit {
9
- constructor(basePriority: number, baseAmount: number, private minNeeded: number) {
10
- super(basePriority, baseAmount, 0, 0);
11
- }
12
-
13
- // Priority goes up when we have fewer than this many refineries.
14
- getPriority(
15
- game: GameApi,
16
- playerData: PlayerData,
17
- technoRules: TechnoRules,
18
- threatCache: GlobalThreat | undefined
19
- ): number {
20
- const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
21
- const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
22
-
23
- const boost = harvesters < this.minNeeded ? 5 : 1;
24
-
25
- return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
26
- }
27
- }
1
+ import { GameApi, PlayerData, Point2D, TechnoRules, Tile } from "@chronodivide/game-api";
2
+ import { GlobalThreat } from "../threat/threat.js";
3
+ import { BasicBuilding } from "./basicBuilding.js";
4
+ import { BasicGroundUnit } from "./basicGroundUnit.js";
5
+
6
+ const IDEAL_HARVESTERS_PER_REFINERY = 2;
7
+
8
+ export class Harvester extends BasicGroundUnit {
9
+ constructor(basePriority: number, baseAmount: number, private minNeeded: number) {
10
+ super(basePriority, baseAmount, 0, 0);
11
+ }
12
+
13
+ // Priority goes up when we have fewer than this many refineries.
14
+ getPriority(
15
+ game: GameApi,
16
+ playerData: PlayerData,
17
+ technoRules: TechnoRules,
18
+ threatCache: GlobalThreat | null
19
+ ): number {
20
+ const refineries = game.getVisibleUnits(playerData.name, "self", (r) => r.refinery).length;
21
+ const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
22
+
23
+ const boost = harvesters < this.minNeeded ? 3 : 1;
24
+
25
+ return this.basePriority * (refineries / Math.max(harvesters / IDEAL_HARVESTERS_PER_REFINERY, 1)) * boost;
26
+ }
27
+ }
@@ -25,7 +25,7 @@ export class PowerPlant implements AiBuildingRules {
25
25
  game: GameApi,
26
26
  playerData: PlayerData,
27
27
  technoRules: TechnoRules,
28
- threatCache: GlobalThreat | undefined
28
+ threatCache: GlobalThreat | null
29
29
  ): number | null {
30
30
  return null;
31
31
  }
@@ -43,27 +43,18 @@ export const queueTypeToName = (queue: QueueType) => {
43
43
  }
44
44
  };
45
45
 
46
- // Repair buildings at this ratio of the maxHitpoints.
47
- const REPAIR_HITPOINTS_RATIO = 0.9;
48
-
49
- // Don't repair buildings more often than this.
50
- const REPAIR_COOLDOWN_TICKS = 15;
51
-
52
46
  const DEBUG_BUILD_QUEUES = true;
53
47
 
54
48
  export class QueueController {
55
- constructor(
56
- private buildingIdToLastHitpoints: Map<number, number> = new Map(),
57
- private buildingIdLastRepairedAtTick: Map<number, number> = new Map()
58
- ) {}
49
+ constructor() {}
59
50
 
60
51
  public onAiUpdate(
61
52
  game: GameApi,
62
53
  productionApi: ProductionApi,
63
54
  actionsApi: ActionsApi,
64
55
  playerData: PlayerData,
65
- threatCache: GlobalThreat | undefined,
66
- logger: (message: string) => void
56
+ threatCache: GlobalThreat | null,
57
+ logger: (message: string) => void,
67
58
  ) {
68
59
  const decisions = QUEUES.map((queueType) => {
69
60
  const options = productionApi.getAvailableObjects(queueType);
@@ -90,29 +81,17 @@ export class QueueController {
90
81
  decision.decision,
91
82
  totalWeightAcrossQueues,
92
83
  totalCostAcrossQueues,
93
- logger
84
+ logger,
94
85
  );
95
86
  });
96
87
 
97
88
  // Repair is simple - just repair everything that's damaged.
98
- // Unfortunately there doesn't seem to be an API to determine if something is being repaired, so we have to remember it.
99
89
  game.getVisibleUnits(playerData.name, "self", (r) => r.repairable).forEach((unitId) => {
100
90
  const unit = game.getUnitData(unitId);
101
- if (!unit || !unit.hitPoints || !unit.maxHitPoints) {
91
+ if (!unit || !unit.hitPoints || !unit.maxHitPoints || unit.hasWrenchRepair) {
102
92
  return;
103
93
  }
104
- const lastKnownHitpoints = this.buildingIdToLastHitpoints.get(unitId) || unit.hitPoints;
105
- const buildingLastRepairedAt = this.buildingIdLastRepairedAtTick.get(unitId) || 0;
106
- // Only repair if HP is going down and if we haven't recently repaired it
107
- if (
108
- unit.hitPoints <= lastKnownHitpoints &&
109
- game.getCurrentTick() > buildingLastRepairedAt + REPAIR_COOLDOWN_TICKS &&
110
- unit.hitPoints < unit.maxHitPoints * REPAIR_HITPOINTS_RATIO
111
- ) {
112
- actionsApi.toggleRepairWrench(unitId);
113
- this.buildingIdLastRepairedAtTick.set(unitId, game.getCurrentTick());
114
- }
115
- this.buildingIdToLastHitpoints.set(unitId, unit.hitPoints);
94
+ actionsApi.toggleRepairWrench(unitId);
116
95
  });
117
96
  }
118
97
 
@@ -121,16 +100,16 @@ export class QueueController {
121
100
  productionApi: ProductionApi,
122
101
  actionsApi: ActionsApi,
123
102
  playerData: PlayerData,
124
- threatCache: GlobalThreat | undefined,
103
+ threatCache: GlobalThreat | null,
125
104
  queueType: QueueType,
126
105
  decision: TechnoRulesWithPriority | undefined,
127
106
  totalWeightAcrossQueues: number,
128
107
  totalCostAcrossQueues: number,
129
- logger: (message: string) => void
108
+ logger: (message: string) => void,
130
109
  ): void {
131
110
  const myCredits = playerData.credits;
132
111
 
133
- let queueData = productionApi.getQueueData(queueType);
112
+ const queueData = productionApi.getQueueData(queueType);
134
113
  if (queueData.status == QueueStatus.Idle) {
135
114
  // Start building the decided item.
136
115
  if (decision !== undefined) {
@@ -145,7 +124,7 @@ export class QueueController {
145
124
  let location: { rx: number; ry: number } | undefined = this.getBestLocationForStructure(
146
125
  game,
147
126
  playerData,
148
- objectReady
127
+ objectReady,
149
128
  );
150
129
  if (location !== undefined) {
151
130
  actionsApi.placeBuilding(objectReady.name, location.rx, location.ry);
@@ -163,7 +142,7 @@ export class QueueController {
163
142
  logger(
164
143
  `Dequeueing queue ${queueTypeToName(queueData.type)} unit ${current.name} because ${
165
144
  decision.unit.name
166
- } has 2x higher priority.`
145
+ } has 2x higher priority.`,
167
146
  );
168
147
  actionsApi.unqueueFromProduction(queueData.type, current.name, current.type, 1);
169
148
  }
@@ -173,7 +152,7 @@ export class QueueController {
173
152
  logger(
174
153
  `Pausing queue ${queueTypeToName(queueData.type)} because weight is low (${
175
154
  decision.priority
176
- }/${totalWeightAcrossQueues})`
155
+ }/${totalWeightAcrossQueues})`,
177
156
  );
178
157
  actionsApi.pauseProduction(queueData.type);
179
158
  }
@@ -187,7 +166,7 @@ export class QueueController {
187
166
  logger(
188
167
  `Resuming queue ${queueTypeToName(queueData.type)} because weight is high (${
189
168
  decision.priority
190
- }/${totalWeightAcrossQueues})`
169
+ }/${totalWeightAcrossQueues})`,
191
170
  );
192
171
  actionsApi.resumeProduction(queueData.type);
193
172
  }
@@ -197,9 +176,9 @@ export class QueueController {
197
176
  private getBestOptionForBuilding(
198
177
  game: GameApi,
199
178
  options: TechnoRules[],
200
- threatCache: GlobalThreat | undefined,
179
+ threatCache: GlobalThreat | null,
201
180
  playerData: PlayerData,
202
- logger: (message: string) => void
181
+ logger: (message: string) => void,
203
182
  ): TechnoRulesWithPriority | undefined {
204
183
  let priorityQueue: TechnoRulesWithPriority[] = [];
205
184
  options.forEach((option) => {
@@ -226,7 +205,7 @@ export class QueueController {
226
205
  option: TechnoRules,
227
206
  game: GameApi,
228
207
  playerStatus: PlayerData,
229
- threatCache: GlobalThreat | undefined
208
+ threatCache: GlobalThreat | null,
230
209
  ) {
231
210
  if (BUILDING_NAME_TO_RULES.has(option.name)) {
232
211
  let logic = BUILDING_NAME_TO_RULES.get(option.name)!;
@@ -242,7 +221,7 @@ export class QueueController {
242
221
  private getBestLocationForStructure(
243
222
  game: GameApi,
244
223
  playerData: PlayerData,
245
- objectReady: TechnoRules
224
+ objectReady: TechnoRules,
246
225
  ): { rx: number; ry: number } | undefined {
247
226
  if (BUILDING_NAME_TO_RULES.has(objectReady.name)) {
248
227
  let logic = BUILDING_NAME_TO_RULES.get(objectReady.name)!;
@@ -48,7 +48,7 @@ export class ResourceCollectionBuilding extends BasicBuilding {
48
48
  game: GameApi,
49
49
  playerData: PlayerData,
50
50
  technoRules: TechnoRules,
51
- threatCache: GlobalThreat | undefined
51
+ threatCache: GlobalThreat | null
52
52
  ): number | null {
53
53
  const harvesters = game.getVisibleUnits(playerData.name, "self", (r) => r.harvester).length;
54
54
  return Math.max(1, harvesters * 2);
@@ -0,0 +1,12 @@
1
+ import { GameApi, PlayerData } from "@chronodivide/game-api";
2
+
3
+ export const getUnseenStartingLocations = (gameApi: GameApi, playerData: PlayerData) => {
4
+ const unseenStartingLocations = gameApi.mapApi.getStartingLocations().filter((startingLocation) => {
5
+ if (startingLocation == playerData.startLocation) {
6
+ return false;
7
+ }
8
+ let tile = gameApi.mapApi.getTile(startingLocation.x, startingLocation.y);
9
+ return tile ? !gameApi.mapApi.isVisibleTile(tile, playerData.name) : false;
10
+ });
11
+ return unseenStartingLocations;
12
+ };
@@ -1,4 +1,4 @@
1
- import { GameApi, MapApi, PlayerData, Point2D } from "@chronodivide/game-api";
1
+ import { GameApi, MapApi, PlayerData, Point2D, UnitData } from "@chronodivide/game-api";
2
2
 
3
3
  // Expensive one-time call to determine the size of the map.
4
4
  // The result is a point just outside the bounds of the map.
@@ -35,7 +35,7 @@ export function calculateAreaVisibility(
35
35
  mapApi: MapApi,
36
36
  playerData: PlayerData,
37
37
  startPoint: Point2D,
38
- endPoint: Point2D,
38
+ endPoint: Point2D
39
39
  ): { visibleTiles: number; validTiles: number } {
40
40
  let validTiles: number = 0,
41
41
  visibleTiles: number = 0;
@@ -60,7 +60,7 @@ export function getPointTowardsOtherPoint(
60
60
  endLocation: Point2D,
61
61
  minRadius: number,
62
62
  maxRadius: number,
63
- randomAngle: number,
63
+ randomAngle: number
64
64
  ): Point2D {
65
65
  let radius = minRadius + Math.round(gameApi.generateRandom() * (maxRadius - minRadius));
66
66
  let directionToSpawn = Math.atan2(endLocation.y - startLocation.y, endLocation.x - startLocation.x);
@@ -74,3 +74,11 @@ export function getPointTowardsOtherPoint(
74
74
  export function getDistanceBetweenPoints(startLocation: Point2D, endLocation: Point2D): number {
75
75
  return Math.sqrt((startLocation.x - endLocation.x) ** 2 + (startLocation.y - endLocation.y) ** 2);
76
76
  }
77
+
78
+ export function getDistanceBetweenUnits(unit1: UnitData, unit2: UnitData): number {
79
+ return getDistanceBetweenPoints({ x: unit1.tile.rx, y: unit1.tile.ry }, { x: unit2.tile.rx, y: unit2.tile.ry });
80
+ }
81
+
82
+ export function getDistanceBetween(unit: UnitData, point: Point2D): number {
83
+ return getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, point);
84
+ }