@supalosa/chronodivide-bot 0.5.3 → 0.6.4
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/.env.template +4 -4
- package/.github/workflows/npm-publish.yml +24 -0
- package/README.md +108 -97
- package/dist/bot/bot.js +105 -105
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/logic/awareness.js +136 -136
- package/dist/bot/logic/building/antiAirStaticDefence.js +42 -42
- package/dist/bot/logic/building/antiGroundStaticDefence.js +34 -30
- package/dist/bot/logic/building/antiGroundStaticDefence.js.map +1 -1
- package/dist/bot/logic/building/{ArtilleryUnit.js → artilleryUnit.js} +18 -18
- package/dist/bot/logic/building/basicAirUnit.js +19 -19
- package/dist/bot/logic/building/basicBuilding.js +26 -26
- package/dist/bot/logic/building/basicGroundUnit.js +19 -19
- package/dist/bot/logic/building/buildingRules.js +175 -174
- package/dist/bot/logic/building/buildingRules.js.map +1 -1
- package/dist/bot/logic/building/common.js +19 -18
- package/dist/bot/logic/building/common.js.map +1 -1
- package/dist/bot/logic/building/harvester.js +16 -16
- package/dist/bot/logic/building/powerPlant.js +20 -20
- package/dist/bot/logic/building/queueController.js +183 -183
- package/dist/bot/logic/building/resourceCollectionBuilding.js +36 -36
- package/dist/bot/logic/common/scout.js +126 -126
- package/dist/bot/logic/common/utils.js +95 -85
- package/dist/bot/logic/common/utils.js.map +1 -1
- package/dist/bot/logic/composition/alliedCompositions.js +12 -12
- package/dist/bot/logic/composition/common.js +1 -1
- package/dist/bot/logic/composition/sovietCompositions.js +12 -12
- package/dist/bot/logic/map/map.js +44 -44
- package/dist/bot/logic/map/sector.js +137 -137
- package/dist/bot/logic/mission/actionBatcher.js +91 -91
- package/dist/bot/logic/mission/mission.js +122 -122
- package/dist/bot/logic/mission/missionController.js +321 -321
- package/dist/bot/logic/mission/missionFactories.js +12 -12
- package/dist/bot/logic/mission/missions/attackMission.js +214 -214
- package/dist/bot/logic/mission/missions/defenceMission.js +82 -82
- package/dist/bot/logic/mission/missions/engineerMission.js +63 -63
- package/dist/bot/logic/mission/missions/expansionMission.js +60 -60
- package/dist/bot/logic/mission/missions/retreatMission.js +33 -33
- package/dist/bot/logic/mission/missions/scoutingMission.js +133 -133
- package/dist/bot/logic/mission/missions/squads/combatSquad.js +115 -115
- package/dist/bot/logic/mission/missions/squads/common.js +57 -57
- package/dist/bot/logic/mission/missions/squads/squad.js +1 -1
- package/dist/bot/logic/threat/threat.js +22 -22
- package/dist/bot/logic/threat/threatCalculator.js +73 -73
- package/dist/exampleBot.js +100 -112
- package/dist/exampleBot.js.map +1 -1
- package/package.json +32 -29
- package/src/bot/bot.ts +161 -161
- package/src/bot/logic/awareness.ts +245 -245
- package/src/bot/logic/building/antiAirStaticDefence.ts +64 -64
- package/src/bot/logic/building/antiGroundStaticDefence.ts +55 -51
- package/src/bot/logic/building/artilleryUnit.ts +39 -39
- package/src/bot/logic/building/basicAirUnit.ts +39 -39
- package/src/bot/logic/building/basicBuilding.ts +49 -49
- package/src/bot/logic/building/basicGroundUnit.ts +39 -39
- package/src/bot/logic/building/buildingRules.ts +250 -247
- package/src/bot/logic/building/common.ts +21 -23
- package/src/bot/logic/building/harvester.ts +31 -31
- package/src/bot/logic/building/powerPlant.ts +32 -32
- package/src/bot/logic/building/queueController.ts +297 -297
- package/src/bot/logic/building/resourceCollectionBuilding.ts +52 -52
- package/src/bot/logic/common/scout.ts +183 -183
- package/src/bot/logic/common/utils.ts +120 -112
- package/src/bot/logic/composition/alliedCompositions.ts +22 -22
- package/src/bot/logic/composition/common.ts +3 -3
- package/src/bot/logic/composition/sovietCompositions.ts +21 -21
- package/src/bot/logic/map/map.ts +66 -66
- package/src/bot/logic/map/sector.ts +174 -174
- package/src/bot/logic/mission/actionBatcher.ts +124 -124
- package/src/bot/logic/mission/mission.ts +232 -232
- package/src/bot/logic/mission/missionController.ts +413 -413
- package/src/bot/logic/mission/missionFactories.ts +51 -51
- package/src/bot/logic/mission/missions/attackMission.ts +336 -336
- package/src/bot/logic/mission/missions/defenceMission.ts +151 -151
- package/src/bot/logic/mission/missions/engineerMission.ts +113 -113
- package/src/bot/logic/mission/missions/expansionMission.ts +104 -104
- package/src/bot/logic/mission/missions/retreatMission.ts +54 -54
- package/src/bot/logic/mission/missions/scoutingMission.ts +186 -186
- package/src/bot/logic/mission/missions/squads/combatSquad.ts +160 -160
- package/src/bot/logic/mission/missions/squads/common.ts +63 -63
- package/src/bot/logic/mission/missions/squads/squad.ts +19 -19
- package/src/bot/logic/threat/threatCalculator.ts +100 -100
- package/src/exampleBot.ts +111 -124
- package/tsconfig.json +73 -73
- package/dist/bot/logic/building/building.js +0 -82
- package/dist/bot/logic/building/massedAntiGroundUnit.js +0 -20
- package/dist/bot/logic/building/queues.js +0 -19
- package/dist/bot/logic/knowledge.js +0 -1
- package/dist/bot/logic/mission/basicMission.js +0 -26
- package/dist/bot/logic/mission/behaviours/combatSquad.js +0 -124
- package/dist/bot/logic/mission/behaviours/combatSquad.js.map +0 -1
- package/dist/bot/logic/mission/behaviours/common.js +0 -56
- package/dist/bot/logic/mission/behaviours/common.js.map +0 -1
- package/dist/bot/logic/mission/behaviours/engineerSquad.js +0 -39
- package/dist/bot/logic/mission/behaviours/engineerSquad.js.map +0 -1
- package/dist/bot/logic/mission/behaviours/expansionSquad.js +0 -46
- package/dist/bot/logic/mission/behaviours/expansionSquad.js.map +0 -1
- package/dist/bot/logic/mission/behaviours/retreatSquad.js +0 -31
- package/dist/bot/logic/mission/behaviours/retreatSquad.js.map +0 -1
- package/dist/bot/logic/mission/behaviours/scoutingSquad.js +0 -94
- package/dist/bot/logic/mission/behaviours/scoutingSquad.js.map +0 -1
- package/dist/bot/logic/mission/expansionMission.js +0 -32
- package/dist/bot/logic/mission/missions/basicMission.js +0 -13
- package/dist/bot/logic/mission/missions/basicMission.js.map +0 -1
- package/dist/bot/logic/mission/missions/missionBehaviour.js +0 -2
- package/dist/bot/logic/mission/missions/missionBehaviour.js.map +0 -1
- package/dist/bot/logic/mission/missions/oneTimeMission.js +0 -27
- package/dist/bot/logic/mission/missions/oneTimeMission.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/actionBatcher.js +0 -36
- package/dist/bot/logic/squad/behaviours/actionBatcher.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/attackSquad.js +0 -82
- package/dist/bot/logic/squad/behaviours/combatSquad.js +0 -106
- package/dist/bot/logic/squad/behaviours/combatSquad.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/common.js +0 -55
- package/dist/bot/logic/squad/behaviours/common.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/defenceSquad.js +0 -48
- package/dist/bot/logic/squad/behaviours/engineerSquad.js +0 -38
- package/dist/bot/logic/squad/behaviours/engineerSquad.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/expansionSquad.js +0 -45
- package/dist/bot/logic/squad/behaviours/expansionSquad.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/retreatSquad.js +0 -31
- package/dist/bot/logic/squad/behaviours/retreatSquad.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js +0 -93
- package/dist/bot/logic/squad/behaviours/scoutingSquad.js.map +0 -1
- package/dist/bot/logic/squad/behaviours/squadExpansion.js +0 -31
- package/dist/bot/logic/squad/behaviours/squadScouters.js +0 -8
- package/dist/bot/logic/squad/squad.js +0 -126
- package/dist/bot/logic/squad/squad.js.map +0 -1
- package/dist/bot/logic/squad/squadBehaviour.js +0 -6
- package/dist/bot/logic/squad/squadBehaviour.js.map +0 -1
- package/dist/bot/logic/squad/squadBehaviours.js +0 -7
- package/dist/bot/logic/squad/squadBehaviours.js.map +0 -1
- package/dist/bot/logic/squad/squadController.js +0 -215
- package/dist/bot/logic/squad/squadController.js.map +0 -1
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import { GameMath, MovementZone, } from "@chronodivide/game-api";
|
|
2
|
-
import { getAttackWeight, manageAttackMicro, manageMoveMicro } from "./common.js";
|
|
3
|
-
import { isOwnedByNeutral, maxBy, minBy } from "../../../common/utils.js";
|
|
4
|
-
import { noop } from "../../mission.js";
|
|
5
|
-
const TARGET_UPDATE_INTERVAL_TICKS = 10;
|
|
6
|
-
// Units must be in a certain radius of the center of mass before attacking.
|
|
7
|
-
// This scales for number of units in the squad though.
|
|
8
|
-
const MIN_GATHER_RADIUS = 5;
|
|
9
|
-
// If the radius expands beyond this amount then we should switch back to gathering mode.
|
|
10
|
-
const MAX_GATHER_RADIUS = 15;
|
|
11
|
-
const GATHER_RATIO = 10;
|
|
12
|
-
const ATTACK_SCAN_AREA = 15;
|
|
13
|
-
var SquadState;
|
|
14
|
-
(function (SquadState) {
|
|
15
|
-
SquadState[SquadState["Gathering"] = 0] = "Gathering";
|
|
16
|
-
SquadState[SquadState["Attacking"] = 1] = "Attacking";
|
|
17
|
-
})(SquadState || (SquadState = {}));
|
|
18
|
-
export class CombatSquad {
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* @param rallyArea the initial location to grab combatants
|
|
22
|
-
* @param targetArea
|
|
23
|
-
* @param radius
|
|
24
|
-
*/
|
|
25
|
-
constructor(rallyArea, targetArea, radius) {
|
|
26
|
-
this.rallyArea = rallyArea;
|
|
27
|
-
this.targetArea = targetArea;
|
|
28
|
-
this.radius = radius;
|
|
29
|
-
this.lastCommand = null;
|
|
30
|
-
this.state = SquadState.Gathering;
|
|
31
|
-
this.lastOrderGiven = {};
|
|
32
|
-
}
|
|
33
|
-
getGlobalDebugText() {
|
|
34
|
-
return this.debugLastTarget ?? "<none>";
|
|
35
|
-
}
|
|
36
|
-
setAttackArea(targetArea) {
|
|
37
|
-
this.targetArea = targetArea;
|
|
38
|
-
}
|
|
39
|
-
onAiUpdate(gameApi, actionsApi, actionBatcher, playerData, mission, matchAwareness, logger) {
|
|
40
|
-
if (mission.getUnitIds().length > 0 &&
|
|
41
|
-
(!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)) {
|
|
42
|
-
this.lastCommand = gameApi.getCurrentTick();
|
|
43
|
-
const centerOfMass = mission.getCenterOfMass();
|
|
44
|
-
const maxDistance = mission.getMaxDistanceToCenterOfMass();
|
|
45
|
-
const units = mission.getUnitsMatching(gameApi, (r) => r.rules.isSelectableCombatant);
|
|
46
|
-
// Only use ground units for center of mass.
|
|
47
|
-
const groundUnits = mission.getUnitsMatching(gameApi, (r) => r.rules.isSelectableCombatant &&
|
|
48
|
-
(r.rules.movementZone === MovementZone.Infantry ||
|
|
49
|
-
r.rules.movementZone === MovementZone.Normal ||
|
|
50
|
-
r.rules.movementZone === MovementZone.InfantryDestroyer));
|
|
51
|
-
if (this.state === SquadState.Gathering) {
|
|
52
|
-
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
|
|
53
|
-
if (centerOfMass &&
|
|
54
|
-
maxDistance &&
|
|
55
|
-
gameApi.mapApi.getTile(centerOfMass.x, centerOfMass.y) !== undefined &&
|
|
56
|
-
maxDistance > requiredGatherRadius) {
|
|
57
|
-
units.forEach((unit) => {
|
|
58
|
-
this.submitActionIfNew(actionBatcher, manageMoveMicro(unit, centerOfMass));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
logger(`CombatSquad ${mission.getUniqueName()} switching back to attack mode (${maxDistance})`);
|
|
63
|
-
this.state = SquadState.Attacking;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
const targetPoint = this.targetArea || playerData.startLocation;
|
|
68
|
-
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
|
|
69
|
-
if (centerOfMass &&
|
|
70
|
-
maxDistance &&
|
|
71
|
-
gameApi.mapApi.getTile(centerOfMass.x, centerOfMass.y) !== undefined &&
|
|
72
|
-
maxDistance > requiredGatherRadius) {
|
|
73
|
-
// Switch back to gather mode
|
|
74
|
-
logger(`CombatSquad ${mission.getUniqueName()} switching back to gather (${maxDistance})`);
|
|
75
|
-
this.state = SquadState.Gathering;
|
|
76
|
-
return noop();
|
|
77
|
-
}
|
|
78
|
-
// The unit with the shortest range chooses the target. Otherwise, a base range of 5 is chosen.
|
|
79
|
-
const getRangeForUnit = (unit) => unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
|
|
80
|
-
const attackLeader = minBy(units, getRangeForUnit);
|
|
81
|
-
if (!attackLeader) {
|
|
82
|
-
return noop();
|
|
83
|
-
}
|
|
84
|
-
// Find units within double the range of the leader.
|
|
85
|
-
const nearbyHostiles = matchAwareness
|
|
86
|
-
.getHostilesNearPoint(attackLeader.tile.rx, attackLeader.tile.ry, ATTACK_SCAN_AREA)
|
|
87
|
-
.map(({ unitId }) => gameApi.getUnitData(unitId))
|
|
88
|
-
.filter((unit) => !isOwnedByNeutral(unit));
|
|
89
|
-
for (const unit of units) {
|
|
90
|
-
const bestUnit = maxBy(nearbyHostiles, (target) => getAttackWeight(unit, target));
|
|
91
|
-
if (bestUnit) {
|
|
92
|
-
this.submitActionIfNew(actionBatcher, manageAttackMicro(unit, bestUnit));
|
|
93
|
-
this.debugLastTarget = `Unit ${bestUnit.id.toString()}`;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
this.submitActionIfNew(actionBatcher, manageMoveMicro(unit, targetPoint));
|
|
97
|
-
this.debugLastTarget = `@${targetPoint.x},${targetPoint.y}`;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return noop();
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Sends an action to the acitonBatcher if and only if the action is different from the last action we submitted to it.
|
|
106
|
-
* Prevents spamming redundant orders, which affects performance and can also ccause the unit to sit around doing nothing.
|
|
107
|
-
*/
|
|
108
|
-
submitActionIfNew(actionBatcher, action) {
|
|
109
|
-
const lastAction = this.lastOrderGiven[action.unitId];
|
|
110
|
-
if (!lastAction || !lastAction.isSameAs(action)) {
|
|
111
|
-
actionBatcher.push(action);
|
|
112
|
-
this.lastOrderGiven[action.unitId] = action;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
1
|
+
import { GameMath, MovementZone, } from "@chronodivide/game-api";
|
|
2
|
+
import { getAttackWeight, manageAttackMicro, manageMoveMicro } from "./common.js";
|
|
3
|
+
import { isOwnedByNeutral, maxBy, minBy } from "../../../common/utils.js";
|
|
4
|
+
import { noop } from "../../mission.js";
|
|
5
|
+
const TARGET_UPDATE_INTERVAL_TICKS = 10;
|
|
6
|
+
// Units must be in a certain radius of the center of mass before attacking.
|
|
7
|
+
// This scales for number of units in the squad though.
|
|
8
|
+
const MIN_GATHER_RADIUS = 5;
|
|
9
|
+
// If the radius expands beyond this amount then we should switch back to gathering mode.
|
|
10
|
+
const MAX_GATHER_RADIUS = 15;
|
|
11
|
+
const GATHER_RATIO = 10;
|
|
12
|
+
const ATTACK_SCAN_AREA = 15;
|
|
13
|
+
var SquadState;
|
|
14
|
+
(function (SquadState) {
|
|
15
|
+
SquadState[SquadState["Gathering"] = 0] = "Gathering";
|
|
16
|
+
SquadState[SquadState["Attacking"] = 1] = "Attacking";
|
|
17
|
+
})(SquadState || (SquadState = {}));
|
|
18
|
+
export class CombatSquad {
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param rallyArea the initial location to grab combatants
|
|
22
|
+
* @param targetArea
|
|
23
|
+
* @param radius
|
|
24
|
+
*/
|
|
25
|
+
constructor(rallyArea, targetArea, radius) {
|
|
26
|
+
this.rallyArea = rallyArea;
|
|
27
|
+
this.targetArea = targetArea;
|
|
28
|
+
this.radius = radius;
|
|
29
|
+
this.lastCommand = null;
|
|
30
|
+
this.state = SquadState.Gathering;
|
|
31
|
+
this.lastOrderGiven = {};
|
|
32
|
+
}
|
|
33
|
+
getGlobalDebugText() {
|
|
34
|
+
return this.debugLastTarget ?? "<none>";
|
|
35
|
+
}
|
|
36
|
+
setAttackArea(targetArea) {
|
|
37
|
+
this.targetArea = targetArea;
|
|
38
|
+
}
|
|
39
|
+
onAiUpdate(gameApi, actionsApi, actionBatcher, playerData, mission, matchAwareness, logger) {
|
|
40
|
+
if (mission.getUnitIds().length > 0 &&
|
|
41
|
+
(!this.lastCommand || gameApi.getCurrentTick() > this.lastCommand + TARGET_UPDATE_INTERVAL_TICKS)) {
|
|
42
|
+
this.lastCommand = gameApi.getCurrentTick();
|
|
43
|
+
const centerOfMass = mission.getCenterOfMass();
|
|
44
|
+
const maxDistance = mission.getMaxDistanceToCenterOfMass();
|
|
45
|
+
const units = mission.getUnitsMatching(gameApi, (r) => r.rules.isSelectableCombatant);
|
|
46
|
+
// Only use ground units for center of mass.
|
|
47
|
+
const groundUnits = mission.getUnitsMatching(gameApi, (r) => r.rules.isSelectableCombatant &&
|
|
48
|
+
(r.rules.movementZone === MovementZone.Infantry ||
|
|
49
|
+
r.rules.movementZone === MovementZone.Normal ||
|
|
50
|
+
r.rules.movementZone === MovementZone.InfantryDestroyer));
|
|
51
|
+
if (this.state === SquadState.Gathering) {
|
|
52
|
+
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MIN_GATHER_RADIUS;
|
|
53
|
+
if (centerOfMass &&
|
|
54
|
+
maxDistance &&
|
|
55
|
+
gameApi.mapApi.getTile(centerOfMass.x, centerOfMass.y) !== undefined &&
|
|
56
|
+
maxDistance > requiredGatherRadius) {
|
|
57
|
+
units.forEach((unit) => {
|
|
58
|
+
this.submitActionIfNew(actionBatcher, manageMoveMicro(unit, centerOfMass));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
logger(`CombatSquad ${mission.getUniqueName()} switching back to attack mode (${maxDistance})`);
|
|
63
|
+
this.state = SquadState.Attacking;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const targetPoint = this.targetArea || playerData.startLocation;
|
|
68
|
+
const requiredGatherRadius = GameMath.sqrt(groundUnits.length) * GATHER_RATIO + MAX_GATHER_RADIUS;
|
|
69
|
+
if (centerOfMass &&
|
|
70
|
+
maxDistance &&
|
|
71
|
+
gameApi.mapApi.getTile(centerOfMass.x, centerOfMass.y) !== undefined &&
|
|
72
|
+
maxDistance > requiredGatherRadius) {
|
|
73
|
+
// Switch back to gather mode
|
|
74
|
+
logger(`CombatSquad ${mission.getUniqueName()} switching back to gather (${maxDistance})`);
|
|
75
|
+
this.state = SquadState.Gathering;
|
|
76
|
+
return noop();
|
|
77
|
+
}
|
|
78
|
+
// The unit with the shortest range chooses the target. Otherwise, a base range of 5 is chosen.
|
|
79
|
+
const getRangeForUnit = (unit) => unit.primaryWeapon?.maxRange ?? unit.secondaryWeapon?.maxRange ?? 5;
|
|
80
|
+
const attackLeader = minBy(units, getRangeForUnit);
|
|
81
|
+
if (!attackLeader) {
|
|
82
|
+
return noop();
|
|
83
|
+
}
|
|
84
|
+
// Find units within double the range of the leader.
|
|
85
|
+
const nearbyHostiles = matchAwareness
|
|
86
|
+
.getHostilesNearPoint(attackLeader.tile.rx, attackLeader.tile.ry, ATTACK_SCAN_AREA)
|
|
87
|
+
.map(({ unitId }) => gameApi.getUnitData(unitId))
|
|
88
|
+
.filter((unit) => !isOwnedByNeutral(unit));
|
|
89
|
+
for (const unit of units) {
|
|
90
|
+
const bestUnit = maxBy(nearbyHostiles, (target) => getAttackWeight(unit, target));
|
|
91
|
+
if (bestUnit) {
|
|
92
|
+
this.submitActionIfNew(actionBatcher, manageAttackMicro(unit, bestUnit));
|
|
93
|
+
this.debugLastTarget = `Unit ${bestUnit.id.toString()}`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.submitActionIfNew(actionBatcher, manageMoveMicro(unit, targetPoint));
|
|
97
|
+
this.debugLastTarget = `@${targetPoint.x},${targetPoint.y}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return noop();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Sends an action to the acitonBatcher if and only if the action is different from the last action we submitted to it.
|
|
106
|
+
* Prevents spamming redundant orders, which affects performance and can also ccause the unit to sit around doing nothing.
|
|
107
|
+
*/
|
|
108
|
+
submitActionIfNew(actionBatcher, action) {
|
|
109
|
+
const lastAction = this.lastOrderGiven[action.unitId];
|
|
110
|
+
if (!lastAction || !lastAction.isSameAs(action)) {
|
|
111
|
+
actionBatcher.push(action);
|
|
112
|
+
this.lastOrderGiven[action.unitId] = action;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
116
|
//# sourceMappingURL=combatSquad.js.map
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { AttackState, ObjectType, OrderType, StanceType, Vector2, ZoneType } from "@chronodivide/game-api";
|
|
2
|
-
import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../../map/map.js";
|
|
3
|
-
import { BatchableAction } from "../../actionBatcher.js";
|
|
4
|
-
const NONCE_GI_DEPLOY = 0;
|
|
5
|
-
const NONCE_GI_UNDEPLOY = 1;
|
|
6
|
-
// Micro methods
|
|
7
|
-
export function manageMoveMicro(attacker, attackPoint) {
|
|
8
|
-
if (attacker.name === "E1") {
|
|
9
|
-
const isDeployed = attacker.stance === StanceType.Deployed;
|
|
10
|
-
if (isDeployed) {
|
|
11
|
-
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_UNDEPLOY);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return BatchableAction.toPoint(attacker.id, OrderType.AttackMove, attackPoint);
|
|
15
|
-
}
|
|
16
|
-
export function manageAttackMicro(attacker, target) {
|
|
17
|
-
const distance = getDistanceBetweenUnits(attacker, target);
|
|
18
|
-
if (attacker.name === "E1") {
|
|
19
|
-
// Para (deployed weapon) range is 5.
|
|
20
|
-
const deployedWeaponRange = attacker.secondaryWeapon?.maxRange || 5;
|
|
21
|
-
const isDeployed = attacker.stance === StanceType.Deployed;
|
|
22
|
-
if (!isDeployed && (distance <= deployedWeaponRange || attacker.attackState === AttackState.JustFired)) {
|
|
23
|
-
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_DEPLOY);
|
|
24
|
-
}
|
|
25
|
-
else if (isDeployed && distance > deployedWeaponRange) {
|
|
26
|
-
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_UNDEPLOY);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
let targetData = target;
|
|
30
|
-
let orderType = OrderType.Attack;
|
|
31
|
-
const primaryWeaponRange = attacker.primaryWeapon?.maxRange || 5;
|
|
32
|
-
if (targetData?.type == ObjectType.Building && distance < primaryWeaponRange * 0.8) {
|
|
33
|
-
orderType = OrderType.Attack;
|
|
34
|
-
}
|
|
35
|
-
else if (targetData?.rules.canDisguise) {
|
|
36
|
-
// Special case for mirage tank/spy as otherwise they just sit next to it.
|
|
37
|
-
orderType = OrderType.Attack;
|
|
38
|
-
}
|
|
39
|
-
return BatchableAction.toTargetId(attacker.id, orderType, target.id);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
* @param attacker
|
|
44
|
-
* @param target
|
|
45
|
-
* @returns A number describing the weight of the given target for the attacker, or null if it should not attack it.
|
|
46
|
-
*/
|
|
47
|
-
export function getAttackWeight(attacker, target) {
|
|
48
|
-
const { rx: x, ry: y } = attacker.tile;
|
|
49
|
-
const { rx: hX, ry: hY } = target.tile;
|
|
50
|
-
if (!attacker.primaryWeapon?.projectileRules.isAntiAir && target.zone === ZoneType.Air) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
if (!attacker.primaryWeapon?.projectileRules.isAntiGround && target.zone === ZoneType.Ground) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
|
|
57
|
-
}
|
|
1
|
+
import { AttackState, ObjectType, OrderType, StanceType, Vector2, ZoneType } from "@chronodivide/game-api";
|
|
2
|
+
import { getDistanceBetweenPoints, getDistanceBetweenUnits } from "../../../map/map.js";
|
|
3
|
+
import { BatchableAction } from "../../actionBatcher.js";
|
|
4
|
+
const NONCE_GI_DEPLOY = 0;
|
|
5
|
+
const NONCE_GI_UNDEPLOY = 1;
|
|
6
|
+
// Micro methods
|
|
7
|
+
export function manageMoveMicro(attacker, attackPoint) {
|
|
8
|
+
if (attacker.name === "E1") {
|
|
9
|
+
const isDeployed = attacker.stance === StanceType.Deployed;
|
|
10
|
+
if (isDeployed) {
|
|
11
|
+
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_UNDEPLOY);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return BatchableAction.toPoint(attacker.id, OrderType.AttackMove, attackPoint);
|
|
15
|
+
}
|
|
16
|
+
export function manageAttackMicro(attacker, target) {
|
|
17
|
+
const distance = getDistanceBetweenUnits(attacker, target);
|
|
18
|
+
if (attacker.name === "E1") {
|
|
19
|
+
// Para (deployed weapon) range is 5.
|
|
20
|
+
const deployedWeaponRange = attacker.secondaryWeapon?.maxRange || 5;
|
|
21
|
+
const isDeployed = attacker.stance === StanceType.Deployed;
|
|
22
|
+
if (!isDeployed && (distance <= deployedWeaponRange || attacker.attackState === AttackState.JustFired)) {
|
|
23
|
+
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_DEPLOY);
|
|
24
|
+
}
|
|
25
|
+
else if (isDeployed && distance > deployedWeaponRange) {
|
|
26
|
+
return BatchableAction.noTarget(attacker.id, OrderType.DeploySelected, NONCE_GI_UNDEPLOY);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
let targetData = target;
|
|
30
|
+
let orderType = OrderType.Attack;
|
|
31
|
+
const primaryWeaponRange = attacker.primaryWeapon?.maxRange || 5;
|
|
32
|
+
if (targetData?.type == ObjectType.Building && distance < primaryWeaponRange * 0.8) {
|
|
33
|
+
orderType = OrderType.Attack;
|
|
34
|
+
}
|
|
35
|
+
else if (targetData?.rules.canDisguise) {
|
|
36
|
+
// Special case for mirage tank/spy as otherwise they just sit next to it.
|
|
37
|
+
orderType = OrderType.Attack;
|
|
38
|
+
}
|
|
39
|
+
return BatchableAction.toTargetId(attacker.id, orderType, target.id);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param attacker
|
|
44
|
+
* @param target
|
|
45
|
+
* @returns A number describing the weight of the given target for the attacker, or null if it should not attack it.
|
|
46
|
+
*/
|
|
47
|
+
export function getAttackWeight(attacker, target) {
|
|
48
|
+
const { rx: x, ry: y } = attacker.tile;
|
|
49
|
+
const { rx: hX, ry: hY } = target.tile;
|
|
50
|
+
if (!attacker.primaryWeapon?.projectileRules.isAntiAir && target.zone === ZoneType.Air) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!attacker.primaryWeapon?.projectileRules.isAntiGround && target.zone === ZoneType.Ground) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return 1000000 - getDistanceBetweenPoints(new Vector2(x, y), new Vector2(hX, hY));
|
|
57
|
+
}
|
|
58
58
|
//# sourceMappingURL=common.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export {};
|
|
2
2
|
//# sourceMappingURL=squad.js.map
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
-
export class GlobalThreat {
|
|
3
|
-
constructor(certainty, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
4
|
-
totalOffensiveLandThreat, // a number that approximates how much land-based firepower our opponents have.
|
|
5
|
-
totalOffensiveAirThreat, // a number that approximates how much airborne firepower our opponents have.
|
|
6
|
-
totalOffensiveAntiAirThreat, // a number that approximates how much anti-air firepower our opponents have.
|
|
7
|
-
totalDefensiveThreat, // a number that approximates how much defensive power our opponents have.
|
|
8
|
-
totalDefensivePower, // a number that approximates how much defensive power we have.
|
|
9
|
-
totalAvailableAntiGroundFirepower, // how much anti-ground power we have
|
|
10
|
-
totalAvailableAntiAirFirepower, // how much anti-air power we have
|
|
11
|
-
totalAvailableAirPower) {
|
|
12
|
-
this.certainty = certainty;
|
|
13
|
-
this.totalOffensiveLandThreat = totalOffensiveLandThreat;
|
|
14
|
-
this.totalOffensiveAirThreat = totalOffensiveAirThreat;
|
|
15
|
-
this.totalOffensiveAntiAirThreat = totalOffensiveAntiAirThreat;
|
|
16
|
-
this.totalDefensiveThreat = totalDefensiveThreat;
|
|
17
|
-
this.totalDefensivePower = totalDefensivePower;
|
|
18
|
-
this.totalAvailableAntiGroundFirepower = totalAvailableAntiGroundFirepower;
|
|
19
|
-
this.totalAvailableAntiAirFirepower = totalAvailableAntiAirFirepower;
|
|
20
|
-
this.totalAvailableAirPower = totalAvailableAirPower;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
// A periodically-refreshed cache of known threats to a bot so we can use it in decision making.
|
|
2
|
+
export class GlobalThreat {
|
|
3
|
+
constructor(certainty, // 0.0 - 1.0 based on approximate visibility around the map.
|
|
4
|
+
totalOffensiveLandThreat, // a number that approximates how much land-based firepower our opponents have.
|
|
5
|
+
totalOffensiveAirThreat, // a number that approximates how much airborne firepower our opponents have.
|
|
6
|
+
totalOffensiveAntiAirThreat, // a number that approximates how much anti-air firepower our opponents have.
|
|
7
|
+
totalDefensiveThreat, // a number that approximates how much defensive power our opponents have.
|
|
8
|
+
totalDefensivePower, // a number that approximates how much defensive power we have.
|
|
9
|
+
totalAvailableAntiGroundFirepower, // how much anti-ground power we have
|
|
10
|
+
totalAvailableAntiAirFirepower, // how much anti-air power we have
|
|
11
|
+
totalAvailableAirPower) {
|
|
12
|
+
this.certainty = certainty;
|
|
13
|
+
this.totalOffensiveLandThreat = totalOffensiveLandThreat;
|
|
14
|
+
this.totalOffensiveAirThreat = totalOffensiveAirThreat;
|
|
15
|
+
this.totalOffensiveAntiAirThreat = totalOffensiveAntiAirThreat;
|
|
16
|
+
this.totalDefensiveThreat = totalDefensiveThreat;
|
|
17
|
+
this.totalDefensivePower = totalDefensivePower;
|
|
18
|
+
this.totalAvailableAntiGroundFirepower = totalAvailableAntiGroundFirepower;
|
|
19
|
+
this.totalAvailableAntiAirFirepower = totalAvailableAntiAirFirepower;
|
|
20
|
+
this.totalAvailableAirPower = totalAvailableAirPower;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
23
|
//# sourceMappingURL=threat.js.map
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { GameMath, MovementZone, ObjectType } from "@chronodivide/game-api";
|
|
2
|
-
import { GlobalThreat } from "./threat.js";
|
|
3
|
-
export function calculateGlobalThreat(game, playerData, visibleAreaPercent) {
|
|
4
|
-
let groundUnits = game.getVisibleUnits(playerData.name, "enemy", (r) => r.type == ObjectType.Vehicle || r.type == ObjectType.Infantry);
|
|
5
|
-
let airUnits = game.getVisibleUnits(playerData.name, "enemy", (r) => r.movementZone == MovementZone.Fly);
|
|
6
|
-
let groundDefence = game
|
|
7
|
-
.getVisibleUnits(playerData.name, "enemy", (r) => r.type == ObjectType.Building)
|
|
8
|
-
.filter((unitId) => isAntiGround(game, unitId));
|
|
9
|
-
let antiAirPower = game
|
|
10
|
-
.getVisibleUnits(playerData.name, "enemy", (r) => r.type != ObjectType.Building)
|
|
11
|
-
.filter((unitId) => isAntiAir(game, unitId));
|
|
12
|
-
let ourAntiGroundUnits = game
|
|
13
|
-
.getVisibleUnits(playerData.name, "self", (r) => r.isSelectableCombatant)
|
|
14
|
-
.filter((unitId) => isAntiGround(game, unitId));
|
|
15
|
-
let ourAntiAirUnits = game
|
|
16
|
-
.getVisibleUnits(playerData.name, "self", (r) => r.isSelectableCombatant || r.type === ObjectType.Building)
|
|
17
|
-
.filter((unitId) => isAntiAir(game, unitId));
|
|
18
|
-
let ourGroundDefence = game
|
|
19
|
-
.getVisibleUnits(playerData.name, "self", (r) => r.type === ObjectType.Building)
|
|
20
|
-
.filter((unitId) => isAntiGround(game, unitId));
|
|
21
|
-
let ourAirUnits = game.getVisibleUnits(playerData.name, "self", (r) => r.movementZone == MovementZone.Fly && r.isSelectableCombatant);
|
|
22
|
-
let observedGroundThreat = calculateFirepowerForUnits(game, groundUnits);
|
|
23
|
-
let observedAirThreat = calculateFirepowerForUnits(game, airUnits);
|
|
24
|
-
let observedAntiAirThreat = calculateFirepowerForUnits(game, antiAirPower);
|
|
25
|
-
let observedGroundDefence = calculateFirepowerForUnits(game, groundDefence);
|
|
26
|
-
let ourAntiGroundPower = calculateFirepowerForUnits(game, ourAntiGroundUnits);
|
|
27
|
-
let ourAntiAirPower = calculateFirepowerForUnits(game, ourAntiAirUnits);
|
|
28
|
-
let ourAirPower = calculateFirepowerForUnits(game, ourAirUnits);
|
|
29
|
-
let ourGroundDefencePower = calculateFirepowerForUnits(game, ourGroundDefence);
|
|
30
|
-
return new GlobalThreat(visibleAreaPercent, observedGroundThreat, observedAirThreat, observedAntiAirThreat, observedGroundDefence, ourGroundDefencePower, ourAntiGroundPower, ourAntiAirPower, ourAirPower);
|
|
31
|
-
}
|
|
32
|
-
function isAntiGround(gameApi, unitId) {
|
|
33
|
-
let unit = gameApi.getUnitData(unitId);
|
|
34
|
-
if (unit && unit.primaryWeapon) {
|
|
35
|
-
return unit.primaryWeapon.projectileRules.isAntiGround;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
function isAntiAir(gameApi, unitId) {
|
|
40
|
-
let unit = gameApi.getUnitData(unitId);
|
|
41
|
-
if (unit && unit.primaryWeapon) {
|
|
42
|
-
return unit.primaryWeapon.projectileRules.isAntiAir;
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
function calculateFirepowerForUnit(unitData) {
|
|
47
|
-
let threat = 0;
|
|
48
|
-
let hpRatio = unitData.hitPoints / Math.max(1, unitData.maxHitPoints);
|
|
49
|
-
if (unitData.primaryWeapon) {
|
|
50
|
-
threat +=
|
|
51
|
-
(hpRatio *
|
|
52
|
-
((unitData.primaryWeapon.rules.damage + 1) * GameMath.sqrt(unitData.primaryWeapon.rules.range + 1))) /
|
|
53
|
-
Math.max(unitData.primaryWeapon.rules.rof, 1);
|
|
54
|
-
}
|
|
55
|
-
if (unitData.secondaryWeapon) {
|
|
56
|
-
threat +=
|
|
57
|
-
(hpRatio *
|
|
58
|
-
((unitData.secondaryWeapon.rules.damage + 1) *
|
|
59
|
-
GameMath.sqrt(unitData.secondaryWeapon.rules.range + 1))) /
|
|
60
|
-
Math.max(unitData.secondaryWeapon.rules.rof, 1);
|
|
61
|
-
}
|
|
62
|
-
return Math.min(800, threat);
|
|
63
|
-
}
|
|
64
|
-
function calculateFirepowerForUnits(game, unitIds) {
|
|
65
|
-
let threat = 0;
|
|
66
|
-
unitIds.forEach((unitId) => {
|
|
67
|
-
let unitData = game.getUnitData(unitId);
|
|
68
|
-
if (unitData) {
|
|
69
|
-
threat += calculateFirepowerForUnit(unitData);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
return threat;
|
|
73
|
-
}
|
|
1
|
+
import { GameMath, MovementZone, ObjectType } from "@chronodivide/game-api";
|
|
2
|
+
import { GlobalThreat } from "./threat.js";
|
|
3
|
+
export function calculateGlobalThreat(game, playerData, visibleAreaPercent) {
|
|
4
|
+
let groundUnits = game.getVisibleUnits(playerData.name, "enemy", (r) => r.type == ObjectType.Vehicle || r.type == ObjectType.Infantry);
|
|
5
|
+
let airUnits = game.getVisibleUnits(playerData.name, "enemy", (r) => r.movementZone == MovementZone.Fly);
|
|
6
|
+
let groundDefence = game
|
|
7
|
+
.getVisibleUnits(playerData.name, "enemy", (r) => r.type == ObjectType.Building)
|
|
8
|
+
.filter((unitId) => isAntiGround(game, unitId));
|
|
9
|
+
let antiAirPower = game
|
|
10
|
+
.getVisibleUnits(playerData.name, "enemy", (r) => r.type != ObjectType.Building)
|
|
11
|
+
.filter((unitId) => isAntiAir(game, unitId));
|
|
12
|
+
let ourAntiGroundUnits = game
|
|
13
|
+
.getVisibleUnits(playerData.name, "self", (r) => r.isSelectableCombatant)
|
|
14
|
+
.filter((unitId) => isAntiGround(game, unitId));
|
|
15
|
+
let ourAntiAirUnits = game
|
|
16
|
+
.getVisibleUnits(playerData.name, "self", (r) => r.isSelectableCombatant || r.type === ObjectType.Building)
|
|
17
|
+
.filter((unitId) => isAntiAir(game, unitId));
|
|
18
|
+
let ourGroundDefence = game
|
|
19
|
+
.getVisibleUnits(playerData.name, "self", (r) => r.type === ObjectType.Building)
|
|
20
|
+
.filter((unitId) => isAntiGround(game, unitId));
|
|
21
|
+
let ourAirUnits = game.getVisibleUnits(playerData.name, "self", (r) => r.movementZone == MovementZone.Fly && r.isSelectableCombatant);
|
|
22
|
+
let observedGroundThreat = calculateFirepowerForUnits(game, groundUnits);
|
|
23
|
+
let observedAirThreat = calculateFirepowerForUnits(game, airUnits);
|
|
24
|
+
let observedAntiAirThreat = calculateFirepowerForUnits(game, antiAirPower);
|
|
25
|
+
let observedGroundDefence = calculateFirepowerForUnits(game, groundDefence);
|
|
26
|
+
let ourAntiGroundPower = calculateFirepowerForUnits(game, ourAntiGroundUnits);
|
|
27
|
+
let ourAntiAirPower = calculateFirepowerForUnits(game, ourAntiAirUnits);
|
|
28
|
+
let ourAirPower = calculateFirepowerForUnits(game, ourAirUnits);
|
|
29
|
+
let ourGroundDefencePower = calculateFirepowerForUnits(game, ourGroundDefence);
|
|
30
|
+
return new GlobalThreat(visibleAreaPercent, observedGroundThreat, observedAirThreat, observedAntiAirThreat, observedGroundDefence, ourGroundDefencePower, ourAntiGroundPower, ourAntiAirPower, ourAirPower);
|
|
31
|
+
}
|
|
32
|
+
function isAntiGround(gameApi, unitId) {
|
|
33
|
+
let unit = gameApi.getUnitData(unitId);
|
|
34
|
+
if (unit && unit.primaryWeapon) {
|
|
35
|
+
return unit.primaryWeapon.projectileRules.isAntiGround;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function isAntiAir(gameApi, unitId) {
|
|
40
|
+
let unit = gameApi.getUnitData(unitId);
|
|
41
|
+
if (unit && unit.primaryWeapon) {
|
|
42
|
+
return unit.primaryWeapon.projectileRules.isAntiAir;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
function calculateFirepowerForUnit(unitData) {
|
|
47
|
+
let threat = 0;
|
|
48
|
+
let hpRatio = unitData.hitPoints / Math.max(1, unitData.maxHitPoints);
|
|
49
|
+
if (unitData.primaryWeapon) {
|
|
50
|
+
threat +=
|
|
51
|
+
(hpRatio *
|
|
52
|
+
((unitData.primaryWeapon.rules.damage + 1) * GameMath.sqrt(unitData.primaryWeapon.rules.range + 1))) /
|
|
53
|
+
Math.max(unitData.primaryWeapon.rules.rof, 1);
|
|
54
|
+
}
|
|
55
|
+
if (unitData.secondaryWeapon) {
|
|
56
|
+
threat +=
|
|
57
|
+
(hpRatio *
|
|
58
|
+
((unitData.secondaryWeapon.rules.damage + 1) *
|
|
59
|
+
GameMath.sqrt(unitData.secondaryWeapon.rules.range + 1))) /
|
|
60
|
+
Math.max(unitData.secondaryWeapon.rules.rof, 1);
|
|
61
|
+
}
|
|
62
|
+
return Math.min(800, threat);
|
|
63
|
+
}
|
|
64
|
+
function calculateFirepowerForUnits(game, unitIds) {
|
|
65
|
+
let threat = 0;
|
|
66
|
+
unitIds.forEach((unitId) => {
|
|
67
|
+
let unitData = game.getUnitData(unitId);
|
|
68
|
+
if (unitData) {
|
|
69
|
+
threat += calculateFirepowerForUnit(unitData);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return threat;
|
|
73
|
+
}
|
|
74
74
|
//# sourceMappingURL=threatCalculator.js.map
|