@supalosa/chronodivide-bot 0.2.2 → 0.3.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.
- package/.prettierrc +5 -5
- package/TODO.md +18 -0
- package/dist/bot/bot.js +4 -4
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/logic/awareness.js +8 -8
- package/dist/bot/logic/awareness.js.map +1 -1
- package/dist/bot/logic/building/ArtilleryUnit.js +30 -9
- package/dist/bot/logic/building/antiGroundStaticDefence.js +2 -2
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
- package/dist/bot/logic/building/artilleryUnit.js.map +1 -0
- package/dist/bot/logic/building/basicAirUnit.js +3 -2
- package/dist/bot/logic/building/basicAirUnit.js.map +1 -1
- package/dist/bot/logic/building/basicBuilding.js +1 -1
- package/dist/bot/logic/building/basicBuilding.js.map +1 -1
- package/dist/bot/logic/building/basicGroundUnit.js +4 -3
- package/dist/bot/logic/building/basicGroundUnit.js.map +1 -1
- package/dist/bot/logic/building/building.js +11 -55
- package/dist/bot/logic/building/buildingRules.js +162 -0
- package/dist/bot/logic/building/buildingRules.js.map +1 -0
- package/dist/bot/logic/building/harvester.js.map +1 -1
- package/dist/bot/logic/building/massedAntiGroundUnit.js +20 -0
- package/dist/bot/logic/building/powerPlant.js +1 -1
- package/dist/bot/logic/building/powerPlant.js.map +1 -1
- package/dist/bot/logic/building/queueController.js +1 -1
- package/dist/bot/logic/building/queueController.js.map +1 -1
- package/dist/bot/logic/building/queues.js +19 -0
- package/dist/bot/logic/building/resourceCollectionBuilding.js +5 -3
- package/dist/bot/logic/building/resourceCollectionBuilding.js.map +1 -1
- package/dist/bot/logic/common/scout.js +49 -32
- package/dist/bot/logic/common/scout.js.map +1 -1
- package/dist/bot/logic/common/utils.js +50 -1
- package/dist/bot/logic/common/utils.js.map +1 -1
- package/dist/bot/logic/knowledge.js +1 -0
- package/dist/bot/logic/map/map.js +17 -19
- package/dist/bot/logic/map/map.js.map +1 -1
- package/dist/bot/logic/map/sector.js +10 -13
- package/dist/bot/logic/map/sector.js.map +1 -1
- package/dist/bot/logic/mission/basicMission.js +26 -0
- package/dist/bot/logic/mission/expansionMission.js +32 -0
- package/dist/bot/logic/mission/missionFactories.js +2 -0
- package/dist/bot/logic/mission/missionFactories.js.map +1 -1
- package/dist/bot/logic/mission/missions/attackMission.js +4 -4
- package/dist/bot/logic/mission/missions/attackMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/defenceMission.js +2 -1
- package/dist/bot/logic/mission/missions/defenceMission.js.map +1 -1
- package/dist/bot/logic/mission/missions/engineerMission.js +34 -0
- package/dist/bot/logic/mission/missions/engineerMission.js.map +1 -0
- package/dist/bot/logic/mission/missions/retreatMission.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/attackSquad.js +56 -63
- package/dist/bot/logic/squad/behaviours/combatSquad.js +18 -19
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/common.js +19 -2
- package/dist/bot/logic/squad/behaviours/common.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +2 -15
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +36 -0
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +1 -0
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +21 -17
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +1 -1
- package/dist/bot/logic/squad/behaviours/squadExpansion.js +31 -0
- package/dist/bot/logic/squad/behaviours/squadScouters.js +8 -0
- package/dist/bot/logic/squad/squad.js +5 -8
- package/dist/bot/logic/squad/squad.js.map +1 -1
- package/dist/bot/logic/squad/squadBehaviour.js.map +1 -1
- package/dist/bot/logic/squad/squadController.js +2 -3
- package/dist/bot/logic/squad/squadController.js.map +1 -1
- package/dist/bot/logic/threat/threatCalculator.js +4 -3
- package/dist/bot/logic/threat/threatCalculator.js.map +1 -1
- package/dist/exampleBot.js +6 -6
- package/dist/exampleBot.js.map +1 -1
- package/package.json +5 -9
- package/src/bot/bot.ts +8 -10
- package/src/bot/logic/awareness.ts +13 -17
- package/src/bot/logic/building/antiGroundStaticDefence.ts +13 -9
- package/src/bot/logic/building/artilleryUnit.ts +65 -0
- package/src/bot/logic/building/basicAirUnit.ts +10 -8
- package/src/bot/logic/building/basicBuilding.ts +1 -1
- package/src/bot/logic/building/basicGroundUnit.ts +4 -4
- package/src/bot/logic/building/{building.ts → buildingRules.ts} +94 -48
- package/src/bot/logic/building/harvester.ts +7 -4
- package/src/bot/logic/building/powerPlant.ts +1 -1
- package/src/bot/logic/building/queueController.ts +1 -1
- package/src/bot/logic/building/resourceCollectionBuilding.ts +8 -12
- package/src/bot/logic/common/scout.ts +83 -38
- package/src/bot/logic/common/utils.ts +65 -1
- package/src/bot/logic/map/map.ts +27 -31
- package/src/bot/logic/map/sector.ts +17 -21
- package/src/bot/logic/mission/missionFactories.ts +2 -0
- package/src/bot/logic/mission/missions/attackMission.ts +27 -27
- package/src/bot/logic/mission/missions/defenceMission.ts +3 -3
- package/src/bot/logic/mission/missions/engineerMission.ts +61 -0
- package/src/bot/logic/mission/missions/retreatMission.ts +2 -2
- package/src/bot/logic/squad/behaviours/combatSquad.ts +24 -26
- package/src/bot/logic/squad/behaviours/common.ts +33 -3
- package/src/bot/logic/squad/behaviours/engineerSquad.ts +53 -0
- package/src/bot/logic/squad/behaviours/retreatSquad.ts +2 -2
- package/src/bot/logic/squad/behaviours/scoutingSquad.ts +26 -28
- package/src/bot/logic/squad/squad.ts +8 -13
- package/src/bot/logic/squad/squadBehaviour.ts +9 -10
- package/src/bot/logic/squad/squadController.ts +2 -5
- package/src/bot/logic/threat/threat.ts +15 -15
- package/src/bot/logic/threat/threatCalculator.ts +4 -3
- package/src/exampleBot.ts +6 -6
- package/dist/bot/logic/awarenessImpl.js +0 -132
- package/dist/bot/logic/awarenessImpl.js.map +0 -1
- package/dist/bot/logic/building/ArtilleryUnit.js.map +0 -1
- package/dist/bot/logic/building/building.js.map +0 -1
- package/src/bot/logic/building/ArtilleryUnit.ts +0 -43
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ActionsApi, GameApi, MovementZone, PlayerData, Point2D } from "@chronodivide/game-api";
|
|
1
|
+
import { ActionsApi, GameApi, GameMath, MovementZone, PlayerData, UnitData, Vector2 } from "@chronodivide/game-api";
|
|
3
2
|
import { Squad } from "../squad.js";
|
|
4
3
|
import { SquadAction, SquadBehaviour, grabCombatants, noop } from "../squadBehaviour.js";
|
|
5
4
|
import { MatchAwareness } from "../../awareness.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { DebugLogger } from "../../common/utils.js";
|
|
5
|
+
import { getAttackWeight, manageAttackMicro, manageMoveMicro } from "./common.js";
|
|
6
|
+
import { DebugLogger, maxBy } from "../../common/utils.js";
|
|
9
7
|
|
|
10
8
|
const TARGET_UPDATE_INTERVAL_TICKS = 10;
|
|
11
9
|
const GRAB_INTERVAL_TICKS = 10;
|
|
@@ -38,12 +36,12 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
38
36
|
* @param radius
|
|
39
37
|
*/
|
|
40
38
|
constructor(
|
|
41
|
-
private rallyArea:
|
|
42
|
-
private targetArea:
|
|
39
|
+
private rallyArea: Vector2,
|
|
40
|
+
private targetArea: Vector2,
|
|
43
41
|
private radius: number,
|
|
44
42
|
) {}
|
|
45
43
|
|
|
46
|
-
public setAttackArea(targetArea:
|
|
44
|
+
public setAttackArea(targetArea: Vector2) {
|
|
47
45
|
this.targetArea = targetArea;
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -55,7 +53,10 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
55
53
|
matchAwareness: MatchAwareness,
|
|
56
54
|
logger: DebugLogger,
|
|
57
55
|
): SquadAction {
|
|
58
|
-
if (
|
|
56
|
+
if (
|
|
57
|
+
squad.getUnitIds().length > 0 &&
|
|
58
|
+
(!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)
|
|
59
|
+
) {
|
|
59
60
|
this.lastCommand = gameApi.getCurrentTick();
|
|
60
61
|
const centerOfMass = squad.getCenterOfMass();
|
|
61
62
|
const maxDistance = squad.getMaxDistanceToCenterOfMass();
|
|
@@ -72,7 +73,7 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
72
73
|
);
|
|
73
74
|
|
|
74
75
|
if (this.state === SquadState.Gathering) {
|
|
75
|
-
const requiredGatherRadius =
|
|
76
|
+
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
|
|
76
77
|
if (
|
|
77
78
|
centerOfMass &&
|
|
78
79
|
maxDistance &&
|
|
@@ -83,12 +84,12 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
83
84
|
manageMoveMicro(actionsApi, unit, centerOfMass);
|
|
84
85
|
});
|
|
85
86
|
} else {
|
|
86
|
-
logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`)
|
|
87
|
+
logger(`CombatSquad ${squad.getName()} switching back to attack mode (${maxDistance})`);
|
|
87
88
|
this.state = SquadState.Attacking;
|
|
88
89
|
}
|
|
89
90
|
} else {
|
|
90
91
|
const targetPoint = this.targetArea || playerData.startLocation;
|
|
91
|
-
const requiredGatherRadius =
|
|
92
|
+
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
|
|
92
93
|
if (
|
|
93
94
|
centerOfMass &&
|
|
94
95
|
maxDistance &&
|
|
@@ -96,24 +97,21 @@ export class CombatSquad implements SquadBehaviour {
|
|
|
96
97
|
maxDistance > requiredGatherRadius
|
|
97
98
|
) {
|
|
98
99
|
// Switch back to gather mode
|
|
99
|
-
logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`)
|
|
100
|
+
logger(`CombatSquad ${squad.getName()} switching back to gather (${maxDistance})`);
|
|
100
101
|
this.state = SquadState.Gathering;
|
|
101
102
|
return noop();
|
|
102
103
|
}
|
|
103
104
|
for (const unit of units) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} else {
|
|
115
|
-
manageMoveMicro(actionsApi, unit, targetPoint);
|
|
116
|
-
}
|
|
105
|
+
const { rx: x, ry: y } = unit.tile;
|
|
106
|
+
const range = unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
|
|
107
|
+
const nearbyHostiles = matchAwareness
|
|
108
|
+
.getHostilesNearPoint(x, y, range * 2)
|
|
109
|
+
.map(({ unitId }) => gameApi.getUnitData(unitId)) as UnitData[];
|
|
110
|
+
const bestUnit = maxBy(nearbyHostiles, (target) => getAttackWeight(unit, target));
|
|
111
|
+
if (bestUnit) {
|
|
112
|
+
manageAttackMicro(actionsApi, unit, bestUnit);
|
|
113
|
+
} else {
|
|
114
|
+
manageMoveMicro(actionsApi, unit, targetPoint);
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ActionsApi,
|
|
3
|
+
AttackState,
|
|
4
|
+
ObjectType,
|
|
5
|
+
OrderType,
|
|
6
|
+
StanceType,
|
|
7
|
+
UnitData,
|
|
8
|
+
Vector2,
|
|
9
|
+
ZoneType,
|
|
10
|
+
} from "@chronodivide/game-api";
|
|
11
|
+
import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../map/map.js";
|
|
3
12
|
|
|
4
13
|
// Micro methods
|
|
5
|
-
export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint:
|
|
14
|
+
export function manageMoveMicro(actionsApi: ActionsApi, attacker: UnitData, attackPoint: Vector2) {
|
|
6
15
|
if (attacker.name === "E1") {
|
|
7
16
|
const isDeployed = attacker.stance === StanceType.Deployed;
|
|
8
17
|
if (isDeployed) {
|
|
@@ -37,3 +46,24 @@ export function manageAttackMicro(actionsApi: ActionsApi, attacker: UnitData, ta
|
|
|
37
46
|
}
|
|
38
47
|
actionsApi.orderUnits([attacker.id], orderType, target.id);
|
|
39
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param attacker
|
|
53
|
+
* @param target
|
|
54
|
+
* @returns A number describing the weight of the given target for the attacker, or null if it should not attack it.
|
|
55
|
+
*/
|
|
56
|
+
export function getAttackWeight(attacker: UnitData, target: UnitData): number | null {
|
|
57
|
+
const { rx: x, ry: y } = attacker.tile;
|
|
58
|
+
const { rx: hX, ry: hY } = target.tile;
|
|
59
|
+
|
|
60
|
+
if (!attacker.primaryWeapon?.projectileRules.isAntiAir && target.zone === ZoneType.Air) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!attacker.primaryWeapon?.projectileRules.isAntiGround && target.zone === ZoneType.Ground) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
|
|
69
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData, SideType } from "@chronodivide/game-api";
|
|
2
|
+
import { Squad } from "../squad.js";
|
|
3
|
+
import { SquadAction, SquadBehaviour, disband, noop, requestSpecificUnits, requestUnits } from "../squadBehaviour.js";
|
|
4
|
+
import { MatchAwareness } from "../../awareness.js";
|
|
5
|
+
|
|
6
|
+
const CAPTURE_COOLDOWN_TICKS = 30;
|
|
7
|
+
|
|
8
|
+
// Capture squad
|
|
9
|
+
export class EngineerSquad implements SquadBehaviour {
|
|
10
|
+
private hasAttemptedCaptureWith: {
|
|
11
|
+
unitId: number;
|
|
12
|
+
gameTick: number;
|
|
13
|
+
} | null = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param captureTarget ID of the target to try and capture/send engineer into.
|
|
17
|
+
*/
|
|
18
|
+
constructor(private captureTarget: number) {
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
public onAiUpdate(
|
|
22
|
+
gameApi: GameApi,
|
|
23
|
+
actionsApi: ActionsApi,
|
|
24
|
+
playerData: PlayerData,
|
|
25
|
+
squad: Squad,
|
|
26
|
+
matchAwareness: MatchAwareness
|
|
27
|
+
): SquadAction {
|
|
28
|
+
const engineerTypes = ["ENGINEER", "SENGINEER"];
|
|
29
|
+
const engineers = squad.getUnitsOfTypes(gameApi, ...engineerTypes);
|
|
30
|
+
if (engineers.length === 0) {
|
|
31
|
+
// Perhaps we deployed already (or the unit was destroyed), end the mission.
|
|
32
|
+
if (this.hasAttemptedCaptureWith !== null) {
|
|
33
|
+
return disband();
|
|
34
|
+
}
|
|
35
|
+
return requestUnits(engineerTypes, 100);
|
|
36
|
+
} else if (
|
|
37
|
+
!this.hasAttemptedCaptureWith ||
|
|
38
|
+
gameApi.getCurrentTick() > this.hasAttemptedCaptureWith.gameTick + CAPTURE_COOLDOWN_TICKS
|
|
39
|
+
) {
|
|
40
|
+
actionsApi.orderUnits(
|
|
41
|
+
engineers.map((engineer) => engineer.id),
|
|
42
|
+
OrderType.Capture,
|
|
43
|
+
this.captureTarget
|
|
44
|
+
);
|
|
45
|
+
// Add a cooldown to deploy attempts.
|
|
46
|
+
this.hasAttemptedCaptureWith = {
|
|
47
|
+
unitId: engineers[0].id,
|
|
48
|
+
gameTick: gameApi.getCurrentTick(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return noop();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, OrderType, PlayerData,
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData, Vector2 } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "../../threat/threat.js";
|
|
3
3
|
import { Squad } from "../squad.js";
|
|
4
4
|
import { SquadAction, SquadBehaviour, disband, noop, requestSpecificUnits, requestUnits } from "../squadBehaviour.js";
|
|
@@ -11,7 +11,7 @@ export class RetreatSquad implements SquadBehaviour {
|
|
|
11
11
|
|
|
12
12
|
constructor(
|
|
13
13
|
private unitIds: number[],
|
|
14
|
-
private retreatToPoint:
|
|
14
|
+
private retreatToPoint: Vector2,
|
|
15
15
|
) {}
|
|
16
16
|
|
|
17
17
|
public onAiUpdate(
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, OrderType, PlayerData,
|
|
2
|
-
import { GlobalThreat } from "../../threat/threat.js";
|
|
1
|
+
import { ActionsApi, GameApi, OrderType, PlayerData, Vector2 } from "@chronodivide/game-api";
|
|
3
2
|
import { Squad } from "../squad.js";
|
|
4
3
|
import { SquadAction, SquadBehaviour, disband, noop, requestUnits } from "../squadBehaviour.js";
|
|
5
4
|
import { MatchAwareness } from "../../awareness.js";
|
|
6
|
-
import { getUnseenStartingLocations } from "../../common/scout.js";
|
|
7
|
-
import { match } from "assert";
|
|
8
5
|
import { DebugLogger } from "../../common/utils.js";
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
6
|
+
import { getDistanceBetweenTileAndPoint } from "../../map/map.js";
|
|
7
|
+
import { PrioritisedScoutTarget } from "../../common/scout.js";
|
|
11
8
|
|
|
12
9
|
const SCOUT_MOVE_COOLDOWN_TICKS = 30;
|
|
13
10
|
|
|
@@ -19,10 +16,11 @@ const MAX_ATTEMPTS_PER_TARGET = 5;
|
|
|
19
16
|
const MAX_TICKS_PER_TARGET = 600;
|
|
20
17
|
|
|
21
18
|
export class ScoutingSquad implements SquadBehaviour {
|
|
22
|
-
private scoutTarget:
|
|
19
|
+
private scoutTarget: Vector2 | null = null;
|
|
23
20
|
private attemptsOnCurrentTarget: number = 0;
|
|
24
21
|
private scoutTargetRefreshedAt: number = 0;
|
|
25
22
|
private lastMoveCommandTick: number = 0;
|
|
23
|
+
private scoutTargetIsPermanent: boolean = false;
|
|
26
24
|
|
|
27
25
|
// Minimum distance from a scout to the target.
|
|
28
26
|
private scoutMinDistance?: number;
|
|
@@ -53,17 +51,19 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
53
51
|
return requestUnits(scoutNames, 100);
|
|
54
52
|
} else if (this.scoutTarget) {
|
|
55
53
|
this.hadUnit = true;
|
|
56
|
-
if (this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
if (!this.scoutTargetIsPermanent) {
|
|
55
|
+
if (this.attemptsOnCurrentTarget > MAX_ATTEMPTS_PER_TARGET) {
|
|
56
|
+
logger(
|
|
57
|
+
`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too many attempts, moving to next`,
|
|
58
|
+
);
|
|
59
|
+
this.setScoutTarget(null, 0);
|
|
60
|
+
return noop();
|
|
61
|
+
}
|
|
62
|
+
if (gameApi.getCurrentTick() > this.scoutTargetRefreshedAt + MAX_TICKS_PER_TARGET) {
|
|
63
|
+
logger(`Scout target ${this.scoutTarget.x},${this.scoutTarget.y} took too long, moving to next`);
|
|
64
|
+
this.setScoutTarget(null, 0);
|
|
65
|
+
return noop();
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
68
|
const targetTile = gameApi.mapApi.getTile(this.scoutTarget.x, this.scoutTarget.y);
|
|
69
69
|
if (!targetTile) {
|
|
@@ -77,11 +77,8 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
// Check that a scout is actually moving closer to the target.
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
getDistanceBetweenPoints({ x: unit.tile.rx, y: unit.tile.ry }, this.scoutTarget!),
|
|
83
|
-
),
|
|
84
|
-
)!;
|
|
80
|
+
const distances = scouts.map((unit) => getDistanceBetweenTileAndPoint(unit.tile, this.scoutTarget!));
|
|
81
|
+
const newMinDistance = Math.min(...distances);
|
|
85
82
|
if (!this.scoutMinDistance || newMinDistance < this.scoutMinDistance) {
|
|
86
83
|
logger(
|
|
87
84
|
`Scout timeout refreshed because unit moved closer to point (${newMinDistance} < ${this.scoutMinDistance})`,
|
|
@@ -95,20 +92,21 @@ export class ScoutingSquad implements SquadBehaviour {
|
|
|
95
92
|
this.setScoutTarget(null, gameApi.getCurrentTick());
|
|
96
93
|
}
|
|
97
94
|
} else {
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
95
|
+
const nextScoutTarget = matchAwareness.getScoutingManager().getNewScoutTarget();
|
|
96
|
+
if (!nextScoutTarget) {
|
|
100
97
|
logger(`No more scouting targets available, disbanding.`);
|
|
101
98
|
return disband();
|
|
102
99
|
}
|
|
103
|
-
this.setScoutTarget(
|
|
100
|
+
this.setScoutTarget(nextScoutTarget, gameApi.getCurrentTick());
|
|
104
101
|
}
|
|
105
102
|
return noop();
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
setScoutTarget(
|
|
105
|
+
setScoutTarget(target: PrioritisedScoutTarget | null, currentTick: number) {
|
|
109
106
|
this.attemptsOnCurrentTarget = 0;
|
|
110
107
|
this.scoutTargetRefreshedAt = currentTick;
|
|
111
|
-
this.scoutTarget =
|
|
108
|
+
this.scoutTarget = target?.asVector2() ?? null;
|
|
112
109
|
this.scoutMinDistance = undefined;
|
|
110
|
+
this.scoutTargetIsPermanent = target?.isPermanent ?? false;
|
|
113
111
|
}
|
|
114
112
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, PlayerData,
|
|
1
|
+
import { ActionsApi, GameApi, PlayerData, TechnoRules, Tile, UnitData, Vector2 } from "@chronodivide/game-api";
|
|
2
2
|
import { Mission } from "../mission/mission.js";
|
|
3
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
4
3
|
import { SquadAction, SquadBehaviour, disband } from "./squadBehaviour.js";
|
|
5
4
|
import { MatchAwareness } from "../awareness.js";
|
|
6
|
-
import {
|
|
7
|
-
import _ from "lodash";
|
|
5
|
+
import { getDistanceBetweenTileAndPoint } from "../map/map.js";
|
|
8
6
|
import { DebugLogger } from "../common/utils.js";
|
|
9
7
|
|
|
10
8
|
export enum SquadLiveness {
|
|
@@ -19,7 +17,7 @@ export type SquadConstructionRequest = {
|
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
const calculateCenterOfMass: (unitTiles: Tile[]) => {
|
|
22
|
-
centerOfMass:
|
|
20
|
+
centerOfMass: Vector2;
|
|
23
21
|
maxDistance: number;
|
|
24
22
|
} | null = (unitTiles) => {
|
|
25
23
|
if (unitTiles.length === 0) {
|
|
@@ -35,14 +33,11 @@ const calculateCenterOfMass: (unitTiles: Tile[]) => {
|
|
|
35
33
|
},
|
|
36
34
|
{ x: 0, y: 0 },
|
|
37
35
|
);
|
|
38
|
-
const centerOfMass =
|
|
39
|
-
x: Math.round(sums.x / unitTiles.length),
|
|
40
|
-
y: Math.round(sums.y / unitTiles.length),
|
|
41
|
-
};
|
|
36
|
+
const centerOfMass = new Vector2(Math.round(sums.x / unitTiles.length), Math.round(sums.y / unitTiles.length));
|
|
42
37
|
|
|
43
38
|
// max distance of units to the center of mass
|
|
44
|
-
const distances = unitTiles.map((tile) =>
|
|
45
|
-
const maxDistance =
|
|
39
|
+
const distances = unitTiles.map((tile) => getDistanceBetweenTileAndPoint(tile, centerOfMass));
|
|
40
|
+
const maxDistance = Math.max(...distances);
|
|
46
41
|
return { centerOfMass, maxDistance };
|
|
47
42
|
};
|
|
48
43
|
|
|
@@ -50,7 +45,7 @@ export class Squad {
|
|
|
50
45
|
private unitIds: number[] = [];
|
|
51
46
|
private liveness: SquadLiveness = SquadLiveness.SquadActive;
|
|
52
47
|
private lastLivenessUpdateTick: number = 0;
|
|
53
|
-
private centerOfMass:
|
|
48
|
+
private centerOfMass: Vector2 | null = null;
|
|
54
49
|
private maxDistanceToCenterOfMass: number | null = null;
|
|
55
50
|
|
|
56
51
|
constructor(
|
|
@@ -77,7 +72,7 @@ export class Squad {
|
|
|
77
72
|
actionsApi: ActionsApi,
|
|
78
73
|
playerData: PlayerData,
|
|
79
74
|
matchAwareness: MatchAwareness,
|
|
80
|
-
logger: DebugLogger
|
|
75
|
+
logger: DebugLogger,
|
|
81
76
|
): SquadAction {
|
|
82
77
|
this.updateLiveness(gameApi);
|
|
83
78
|
const movableUnitTiles = this.unitIds
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ActionsApi, GameApi, PlayerData,
|
|
2
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
1
|
+
import { ActionsApi, GameApi, PlayerData, Vector2 } from "@chronodivide/game-api";
|
|
3
2
|
import { Squad } from "./squad.js";
|
|
4
3
|
import { MatchAwareness } from "../awareness.js";
|
|
5
4
|
import { DebugLogger } from "../common/utils.js";
|
|
@@ -26,22 +25,22 @@ export type SquadActionRequestSpecificUnits = {
|
|
|
26
25
|
};
|
|
27
26
|
export type SquadActionGrabFreeCombatants = {
|
|
28
27
|
type: "requestCombatants";
|
|
29
|
-
point:
|
|
28
|
+
point: Vector2;
|
|
30
29
|
radius: number;
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
export const noop = () => ({ type: "noop" } as SquadActionNoop
|
|
32
|
+
export const noop = () => ({ type: "noop" }) as SquadActionNoop;
|
|
34
33
|
|
|
35
|
-
export const disband = () => ({ type: "disband" } as SquadActionDisband
|
|
34
|
+
export const disband = () => ({ type: "disband" }) as SquadActionDisband;
|
|
36
35
|
|
|
37
36
|
export const requestUnits = (unitNames: string[], priority: number) =>
|
|
38
|
-
({ type: "request", unitNames, priority } as SquadActionRequestUnits
|
|
37
|
+
({ type: "request", unitNames, priority }) as SquadActionRequestUnits;
|
|
39
38
|
|
|
40
39
|
export const requestSpecificUnits = (unitIds: number[], priority: number) =>
|
|
41
|
-
({ type: "requestSpecific", unitIds, priority } as SquadActionRequestSpecificUnits
|
|
40
|
+
({ type: "requestSpecific", unitIds, priority }) as SquadActionRequestSpecificUnits;
|
|
42
41
|
|
|
43
|
-
export const grabCombatants = (point:
|
|
44
|
-
({ type: "requestCombatants", point, radius } as SquadActionGrabFreeCombatants
|
|
42
|
+
export const grabCombatants = (point: Vector2, radius: number) =>
|
|
43
|
+
({ type: "requestCombatants", point, radius }) as SquadActionGrabFreeCombatants;
|
|
45
44
|
|
|
46
45
|
export type SquadAction =
|
|
47
46
|
| SquadActionNoop
|
|
@@ -58,6 +57,6 @@ export interface SquadBehaviour {
|
|
|
58
57
|
playerData: PlayerData,
|
|
59
58
|
squad: Squad,
|
|
60
59
|
matchAwareness: MatchAwareness,
|
|
61
|
-
logger: DebugLogger
|
|
60
|
+
logger: DebugLogger,
|
|
62
61
|
): SquadAction;
|
|
63
62
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Meta-controller for forming and controlling squads.
|
|
2
2
|
|
|
3
3
|
import { ActionsApi, GameApi, PlayerData, UnitData } from "@chronodivide/game-api";
|
|
4
|
-
import { GlobalThreat } from "../threat/threat.js";
|
|
5
4
|
import { Squad, SquadLiveness } from "./squad.js";
|
|
6
5
|
import {
|
|
7
6
|
SquadAction,
|
|
@@ -13,8 +12,7 @@ import {
|
|
|
13
12
|
} from "./squadBehaviour.js";
|
|
14
13
|
import { MatchAwareness } from "../awareness.js";
|
|
15
14
|
import { getDistanceBetween } from "../map/map.js";
|
|
16
|
-
import
|
|
17
|
-
import { DebugLogger } from "../common/utils.js";
|
|
15
|
+
import { countBy } from "../common/utils.js";
|
|
18
16
|
|
|
19
17
|
type SquadWithAction<T> = {
|
|
20
18
|
squad: Squad;
|
|
@@ -181,7 +179,6 @@ export class SquadController {
|
|
|
181
179
|
.map((unit) => unit!);
|
|
182
180
|
|
|
183
181
|
type AssignmentWithType = { unitName: string; squad: string; method: "type" | "grab" };
|
|
184
|
-
// [squadName][unitName]['type' | 'grab']
|
|
185
182
|
const newAssignmentsByType = freeUnits
|
|
186
183
|
.flatMap((freeUnit) => {
|
|
187
184
|
if (unitTypeToHighestRequest.hasOwnProperty(freeUnit.name)) {
|
|
@@ -244,7 +241,7 @@ export class SquadController {
|
|
|
244
241
|
}
|
|
245
242
|
|
|
246
243
|
public debugSquads(gameApi: GameApi) {
|
|
247
|
-
const unitsInSquad = (unitIds: number[]) =>
|
|
244
|
+
const unitsInSquad = (unitIds: number[]) => countBy(unitIds, (unitId) => gameApi.getUnitData(unitId)?.name);
|
|
248
245
|
|
|
249
246
|
this.squads.forEach((squad) => {
|
|
250
247
|
this.logger(
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
-
|
|
3
|
-
export class GlobalThreat {
|
|
4
|
-
constructor(
|
|
5
|
-
public certainty: number, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
6
|
-
public totalOffensiveLandThreat: number, // a number that approximates how much land-based firepower our opponents have.
|
|
7
|
-
public totalOffensiveAirThreat: number, // a number that approximates how much airborne firepower our opponents have.
|
|
8
|
-
public totalOffensiveAntiAirThreat: number, // a number that approximates how much anti-air firepower our opponents have.
|
|
9
|
-
public totalDefensiveThreat: number, // a number that approximates how much defensive power our opponents have.
|
|
10
|
-
public totalDefensivePower: number, // a number that approximates how much defensive power we have.
|
|
11
|
-
public totalAvailableAntiGroundFirepower: number, // how much anti-ground power we have
|
|
12
|
-
public totalAvailableAntiAirFirepower: number, // how much anti-air power we have
|
|
13
|
-
public totalAvailableAirPower: number, // how much firepower we have in air units
|
|
14
|
-
) {}
|
|
15
|
-
}
|
|
1
|
+
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
+
|
|
3
|
+
export class GlobalThreat {
|
|
4
|
+
constructor(
|
|
5
|
+
public certainty: number, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
6
|
+
public totalOffensiveLandThreat: number, // a number that approximates how much land-based firepower our opponents have.
|
|
7
|
+
public totalOffensiveAirThreat: number, // a number that approximates how much airborne firepower our opponents have.
|
|
8
|
+
public totalOffensiveAntiAirThreat: number, // a number that approximates how much anti-air firepower our opponents have.
|
|
9
|
+
public totalDefensiveThreat: number, // a number that approximates how much defensive power our opponents have.
|
|
10
|
+
public totalDefensivePower: number, // a number that approximates how much defensive power we have.
|
|
11
|
+
public totalAvailableAntiGroundFirepower: number, // how much anti-ground power we have
|
|
12
|
+
public totalAvailableAntiAirFirepower: number, // how much anti-air power we have
|
|
13
|
+
public totalAvailableAirPower: number, // how much firepower we have in air units
|
|
14
|
+
) {}
|
|
15
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GameApi, MovementZone, ObjectType, PlayerData, UnitData } from "@chronodivide/game-api";
|
|
1
|
+
import { GameApi, GameMath, MovementZone, ObjectType, PlayerData, UnitData } from "@chronodivide/game-api";
|
|
2
2
|
import { GlobalThreat } from "./threat.js";
|
|
3
3
|
|
|
4
4
|
export function calculateGlobalThreat(game: GameApi, playerData: PlayerData, visibleAreaPercent: number): GlobalThreat {
|
|
@@ -75,13 +75,14 @@ function calculateFirepowerForUnit(unitData: UnitData): number {
|
|
|
75
75
|
if (unitData.primaryWeapon) {
|
|
76
76
|
threat +=
|
|
77
77
|
(hpRatio *
|
|
78
|
-
((unitData.primaryWeapon.rules.damage + 1) *
|
|
78
|
+
((unitData.primaryWeapon.rules.damage + 1) * GameMath.sqrt(unitData.primaryWeapon.rules.range + 1))) /
|
|
79
79
|
Math.max(unitData.primaryWeapon.cooldownTicks, 1);
|
|
80
80
|
}
|
|
81
81
|
if (unitData.secondaryWeapon) {
|
|
82
82
|
threat +=
|
|
83
83
|
(hpRatio *
|
|
84
|
-
((unitData.secondaryWeapon.rules.damage + 1) *
|
|
84
|
+
((unitData.secondaryWeapon.rules.damage + 1) *
|
|
85
|
+
GameMath.sqrt(unitData.secondaryWeapon.rules.range + 1))) /
|
|
85
86
|
Math.max(unitData.secondaryWeapon.cooldownTicks, 1);
|
|
86
87
|
}
|
|
87
88
|
return Math.min(800, threat);
|
package/src/exampleBot.ts
CHANGED
|
@@ -6,17 +6,17 @@ async function main() {
|
|
|
6
6
|
Ladder maps:
|
|
7
7
|
CDR2 1v1 2_malibu_cliffs_le.map
|
|
8
8
|
CDR2 1v1 4_country_swing_le_v2.map
|
|
9
|
-
CDR2 1v1 mp01t4.map
|
|
10
|
-
CDR2 1v1 tn04t2.map
|
|
11
|
-
CDR2 1v1 mp10s4.map
|
|
9
|
+
CDR2 1v1 mp01t4.map, large map, oil derricks
|
|
10
|
+
CDR2 1v1 tn04t2.map, small map
|
|
11
|
+
CDR2 1v1 mp10s4.map <- depth charge, naval map (not supported)
|
|
12
12
|
CDR2 1v1 heckcorners.map
|
|
13
13
|
CDR2 1v1 4_montana_dmz_le.map
|
|
14
14
|
CDR2 1v1 barrel.map
|
|
15
15
|
|
|
16
16
|
Other maps:
|
|
17
|
-
mp03t4
|
|
17
|
+
mp03t4 large map, no oil derricks
|
|
18
18
|
*/
|
|
19
|
-
const mapName = "
|
|
19
|
+
const mapName = "mp10s4.map";
|
|
20
20
|
// Bot names must be unique in online mode
|
|
21
21
|
const botName = `Joe${String(Date.now()).substr(-6)}`;
|
|
22
22
|
const otherBotName = `Bob${String(Date.now() + 1).substr(-6)}`;
|
|
@@ -48,7 +48,7 @@ async function main() {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const offlineSettings = {
|
|
51
|
-
agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "
|
|
51
|
+
agents: [new SupalosaBot(botName, "French", false), new SupalosaBot(otherBotName, "Russians", true)],
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const game = await cdapi.createGame({
|