@vibemancer/core 0.1.3 → 0.1.5
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/README.md +2 -2
- package/dist/chunk-W7HWJFQ4.js +10599 -0
- package/dist/chunk-W7HWJFQ4.js.map +1 -0
- package/dist/index-browser.d.ts +1300 -1051
- package/dist/index-browser.js +55 -17
- package/dist/index.d.ts +53 -3
- package/dist/index.js +200 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -15
- package/src/bots/Hero.ts +867 -0
- package/src/bots/berserker/01_Stormchaser.ts +162 -120
- package/src/bots/berserker/02_Stormcaller.ts +160 -116
- package/src/bots/berserker/03_Stormforger.ts +162 -129
- package/src/bots/caster/01_Flamecaller.ts +126 -84
- package/src/bots/caster/02_Pyromancer.ts +148 -101
- package/src/bots/caster/03_Infernalist.ts +180 -160
- package/src/bots/defensive/01_Turtle.ts +48 -44
- package/src/bots/defensive/02_Sentinel.ts +57 -53
- package/src/bots/defensive/03_Golem.ts +85 -97
- package/src/bots/duelist/01_Battlemage.ts +157 -122
- package/src/bots/duelist/02_Warmage.ts +166 -121
- package/src/bots/duelist/03_Archmage.ts +198 -178
- package/src/bots/homing/01_Bonemancer.ts +20 -32
- package/src/bots/homing/02_Lich.ts +145 -93
- package/src/bots/homing/03_Archlich.ts +129 -60
- package/src/bots/kiter/01_Spellspinner.ts +145 -107
- package/src/bots/kiter/02_Spellweaver.ts +153 -105
- package/src/bots/kiter/03_Spellbinder.ts +168 -123
- package/src/bots/melee/01_Shadowblade.ts +131 -75
- package/src/bots/melee/02_Nightblade.ts +174 -130
- package/src/bots/melee/03_Voidblade.ts +266 -168
- package/src/bots/registry.ts +44 -37
- package/src/bots/shared.ts +185 -25
- package/src/bots/sniper/01_Spellshot.ts +151 -98
- package/src/bots/sniper/02_Spelltracer.ts +173 -128
- package/src/bots/sniper/03_Spellseeker.ts +177 -152
- package/src/bots/standalone/Critter.ts +46 -52
- package/src/bots/standalone/Doombringer.ts +36 -40
- package/src/bots/standalone/Hogger.ts +120 -89
- package/src/bots/standalone/Rookie.ts +21 -23
- package/src/bots/standalone/TargetDummy.ts +5 -6
- package/src/bots/test/cheater.ts +91 -85
- package/src/bots/test/crasher.ts +26 -28
- package/src/engine/bundle-fight.ts +242 -0
- package/src/engine/hooks-runtime.ts +58 -18
- package/src/engine/manual-match.ts +33 -25
- package/src/engine/missile-templates.ts +25 -43
- package/src/engine/optimizer.ts +7 -7
- package/src/engine/params-runtime.ts +3 -3
- package/src/engine/physics.ts +33 -23
- package/src/engine/sandbox-browser.ts +8 -7
- package/src/engine/sandbox-compile.ts +1 -1
- package/src/engine/sandbox-harness.ts +299 -367
- package/src/engine/sandbox.ts +3 -3
- package/src/engine/simulation.ts +291 -76
- package/src/engine/spells.ts +9 -12
- package/src/engine-version.ts +1 -1
- package/src/hooks/action-builders.ts +76 -21
- package/src/hooks/index.ts +17 -9
- package/src/hooks/state-hooks.ts +61 -25
- package/src/hooks/threat-analysis.ts +44 -20
- package/src/hooks/types.ts +62 -5
- package/src/index.ts +2 -0
- package/src/rules.ts +209 -63
- package/src/stats.ts +2 -2
- package/src/testing.ts +6 -30
- package/src/trace.ts +63 -3
- package/src/types.ts +127 -14
- package/src/utils/angles.ts +1 -0
- package/src/utils/combat.ts +98 -6
- package/src/utils/spatial.ts +3 -3
- package/dist/chunk-B7YYYBEC.js +0 -9140
- package/dist/chunk-B7YYYBEC.js.map +0 -1
- package/src/hooks/bot-wrapper.ts +0 -84
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {FinalAction, MissileFunction} from '../../hooks/index.js';
|
|
2
|
+
import {usePosition, useEnemy, useStatus, useBlinkCooldown, useHealth, useTick, useThreats, useProjectiles, useMyProjectiles, useLastMissileConfig, useCastingSpell, useCastProgress, idle, move as moveAction, missile, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
|
|
2
3
|
import {angleTo} from '../../utils/angles.js';
|
|
3
4
|
import {distanceTo} from '../../utils/distance.js';
|
|
4
|
-
import {
|
|
5
|
+
import {RULES, ARENA_MIN, ARENA_MAX} from '../../rules.js';
|
|
5
6
|
import {getMissileCastTime, getLeadPosition} from '../../utils/combat.js';
|
|
6
7
|
import {
|
|
7
|
-
getThreats,
|
|
8
8
|
getMostUrgentThreat,
|
|
9
9
|
getDodgeDirectionForMovement,
|
|
10
10
|
getBlinkToCenter,
|
|
@@ -13,27 +13,30 @@ import {
|
|
|
13
13
|
MAX_CAST_BUDGET,
|
|
14
14
|
MIN_USEFUL_DAMAGE,
|
|
15
15
|
getEnemyVulnerabilityTicks,
|
|
16
|
+
keepOffWalls,
|
|
17
|
+
getPreCastReposition,
|
|
16
18
|
} from '../shared.js';
|
|
17
19
|
import {useParam} from '../../engine/params-runtime.js';
|
|
18
20
|
|
|
19
21
|
const WALL_BUFFER = 60;
|
|
20
22
|
const SHIELD_BUFFER = 3;
|
|
21
23
|
|
|
22
|
-
const HomingAI:
|
|
24
|
+
const HomingAI: MissileFunction = () =>
|
|
23
25
|
{
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
+
const ctx = getMissileContext();
|
|
27
|
+
const enemy = ctx.worldState.enemies[0];
|
|
28
|
+
return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
const StraightAI:
|
|
31
|
+
const StraightAI: MissileFunction = () => flyStraight();
|
|
29
32
|
|
|
30
33
|
function isNearWall(position: {x: number; y: number}): boolean
|
|
31
34
|
{
|
|
32
35
|
return (
|
|
33
|
-
position.x < WALL_BUFFER ||
|
|
34
|
-
position.x >
|
|
35
|
-
position.y < WALL_BUFFER ||
|
|
36
|
-
position.y >
|
|
36
|
+
position.x < ARENA_MIN + WALL_BUFFER ||
|
|
37
|
+
position.x > ARENA_MAX - WALL_BUFFER ||
|
|
38
|
+
position.y < ARENA_MIN + WALL_BUFFER ||
|
|
39
|
+
position.y > ARENA_MAX - WALL_BUFFER
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -64,8 +67,8 @@ function smartStrafe(
|
|
|
64
67
|
{
|
|
65
68
|
const fCw = {x: position.x + cw.x * 100, y: position.y + cw.y * 100};
|
|
66
69
|
const fCcw = {x: position.x + ccw.x * 100, y: position.y + ccw.y * 100};
|
|
67
|
-
const sCw = Math.min(fCw.x,
|
|
68
|
-
const sCcw = Math.min(fCcw.x,
|
|
70
|
+
const sCw = Math.min(fCw.x - ARENA_MIN, ARENA_MAX - fCw.x, fCw.y - ARENA_MIN, ARENA_MAX - fCw.y);
|
|
71
|
+
const sCcw = Math.min(fCcw.x - ARENA_MIN, ARENA_MAX - fCcw.x, fCcw.y - ARENA_MIN, ARENA_MAX - fCcw.y);
|
|
69
72
|
dir = sCw > sCcw ? cw : ccw;
|
|
70
73
|
}
|
|
71
74
|
else
|
|
@@ -74,7 +77,7 @@ function smartStrafe(
|
|
|
74
77
|
dir = cycle === 0 ? cw : ccw;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const intensity = dodgeDirection ?
|
|
80
|
+
const intensity = dodgeDirection ? 1 : strafeIntensity;
|
|
78
81
|
return {x: dir.x * intensity, y: dir.y * intensity};
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -89,41 +92,81 @@ function smartStrafe(
|
|
|
89
92
|
* PROGRESSION LINE: Bonemancer → Lich → Archlich
|
|
90
93
|
* TIER: 3 (elite Homing line)
|
|
91
94
|
*/
|
|
92
|
-
export
|
|
95
|
+
export function Archlich(): FinalAction
|
|
93
96
|
{
|
|
97
|
+
const position = usePosition();
|
|
98
|
+
const enemy = useEnemy();
|
|
99
|
+
const status = useStatus();
|
|
100
|
+
const blinkCooldown = useBlinkCooldown();
|
|
101
|
+
const health = useHealth();
|
|
102
|
+
const tick = useTick();
|
|
103
|
+
const threats = useThreats();
|
|
104
|
+
const projectiles = useProjectiles();
|
|
105
|
+
const myProjectiles = useMyProjectiles();
|
|
106
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
107
|
+
const castingSpell = useCastingSpell();
|
|
108
|
+
const castProgressData = useCastProgress();
|
|
109
|
+
|
|
110
|
+
|
|
94
111
|
// Same params as Lich — Archlich is Lich + punish mode (disabled by default, optimizer tunes)
|
|
95
|
-
const targetDistance = useParam('targetDistance',
|
|
112
|
+
const targetDistance = useParam('targetDistance', 1160, {range: 175, min: 0});
|
|
96
113
|
const minTurnRate = useParam('minTurnRate', 0.4, {range: 1.5, steps: 5});
|
|
97
114
|
const shieldHealthThreshold = useParam('shieldHealthThreshold', 99, {range: 23, min: 1, steps: 7});
|
|
98
|
-
const strafeCyclePeriod = useParam('strafeCyclePeriod',
|
|
99
|
-
const strafeIntensity = useParam('strafeIntensity',
|
|
100
|
-
const approachSpeed = useParam('approachSpeed',
|
|
101
|
-
const distanceDeadZone = useParam('distanceDeadZone',
|
|
115
|
+
const strafeCyclePeriod = useParam('strafeCyclePeriod', 181, {range: 75, min: 80, max: 300});
|
|
116
|
+
const strafeIntensity = useParam('strafeIntensity', 0.98, {range: 0.4, min: 0.2, max: 1, steps: 7});
|
|
117
|
+
const approachSpeed = useParam('approachSpeed', 0.1, {range: 0.3, min: 0.1, max: 0.7, steps: 7});
|
|
118
|
+
const distanceDeadZone = useParam('distanceDeadZone', 51, {range: 30, min: 5, max: 80, steps: 7});
|
|
102
119
|
const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {range: 75, min: 50, max: 300});
|
|
103
|
-
const blinkWindowMax = useParam('blinkWindowMax',
|
|
104
|
-
const flightTimeDivisor = useParam('flightTimeDivisor',
|
|
120
|
+
const blinkWindowMax = useParam('blinkWindowMax', 80, {range: 30, min: 15, max: 80, steps: 7});
|
|
121
|
+
const flightTimeDivisor = useParam('flightTimeDivisor', 6.6, {range: 4, min: 2, max: 12, steps: 5});
|
|
105
122
|
const punishMinVulnerability = useParam('punishMinVulnerability', 500, {range: 80, min: 30, steps: 7});
|
|
123
|
+
const centerBias = useParam('centerBias', 0.3, {
|
|
124
|
+
range: 0.4,
|
|
125
|
+
min: 0,
|
|
126
|
+
max: 0.8,
|
|
127
|
+
steps: 5,
|
|
128
|
+
});
|
|
129
|
+
const maxBlinkDistance = useParam('maxBlinkDistance', 283, {
|
|
130
|
+
range: 100,
|
|
131
|
+
min: 50,
|
|
132
|
+
max: 300,
|
|
133
|
+
steps: 7,
|
|
134
|
+
});
|
|
106
135
|
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
|
|
137
|
+
{
|
|
138
|
+
const dx = target.x - position.x;
|
|
139
|
+
const dy = target.y - position.y;
|
|
140
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
141
|
+
if (dist <= maxBlinkDistance) return target;
|
|
142
|
+
const scale = maxBlinkDistance / dist;
|
|
143
|
+
return {x: position.x + dx * scale, y: position.y + dy * scale};
|
|
144
|
+
}
|
|
109
145
|
|
|
110
|
-
const distance = distanceTo(
|
|
111
|
-
const threats = getThreats(state);
|
|
146
|
+
const distance = distanceTo(position, enemy.position);
|
|
112
147
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
113
|
-
const dodgeDirection = getDodgeDirectionForMovement(threats,
|
|
114
|
-
const strafe = smartStrafe(
|
|
148
|
+
const dodgeDirection = getDodgeDirectionForMovement(threats, position);
|
|
149
|
+
const strafe = smartStrafe(position, enemy, dodgeDirection, tick, strafeCyclePeriod, strafeIntensity);
|
|
115
150
|
|
|
116
151
|
let moveX = strafe.x;
|
|
117
152
|
let moveY = strafe.y;
|
|
118
153
|
if (!dodgeDirection)
|
|
119
154
|
{
|
|
120
|
-
const dx = enemy.position.x -
|
|
121
|
-
const dy = enemy.position.y -
|
|
155
|
+
const dx = enemy.position.x - position.x;
|
|
156
|
+
const dy = enemy.position.y - position.y;
|
|
122
157
|
const nd = distance > 0 ? distance : 1;
|
|
123
158
|
if (distance > targetDistance + distanceDeadZone)
|
|
124
159
|
{
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
// Approach the center-side of the enemy so knockback pushes toward center, not lava.
|
|
161
|
+
const center = (ARENA_MIN + ARENA_MAX) / 2;
|
|
162
|
+
const toCenterX = center - enemy.position.x;
|
|
163
|
+
const toCenterY = center - enemy.position.y;
|
|
164
|
+
const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
|
|
165
|
+
const approachDirX = dx / nd + (toCenterX / toCenterDist) * centerBias;
|
|
166
|
+
const approachDirY = dy / nd + (toCenterY / toCenterDist) * centerBias;
|
|
167
|
+
const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
|
|
168
|
+
moveX += (approachDirX / approachMag) * approachSpeed;
|
|
169
|
+
moveY += (approachDirY / approachMag) * approachSpeed;
|
|
127
170
|
}
|
|
128
171
|
else if (distance < targetDistance - distanceDeadZone)
|
|
129
172
|
{
|
|
@@ -132,47 +175,73 @@ export const Archlich: WizardFunction = ({state}) =>
|
|
|
132
175
|
}
|
|
133
176
|
}
|
|
134
177
|
const mag = Math.sqrt(moveX * moveX + moveY * moveY);
|
|
135
|
-
if (mag >
|
|
178
|
+
if (mag > 1)
|
|
136
179
|
{
|
|
137
|
-
moveX =
|
|
180
|
+
moveX = moveX / mag; moveY = moveY / mag;
|
|
138
181
|
}
|
|
139
182
|
const move = {x: moveX, y: moveY};
|
|
183
|
+
const safeMove = keepOffWalls(position, move);
|
|
140
184
|
|
|
141
185
|
// === DEFENSE: identical to Lich ===
|
|
142
|
-
if (
|
|
186
|
+
if (status === 'channeling')
|
|
143
187
|
{
|
|
144
|
-
if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold) return
|
|
145
|
-
return
|
|
188
|
+
if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold) return cancel().move(safeMove.x, safeMove.y);
|
|
189
|
+
return idle();
|
|
146
190
|
}
|
|
147
191
|
|
|
148
|
-
if (
|
|
192
|
+
if (status === 'casting' && castingSpell === 'missile' && urgentThreat && urgentThreat.projectile.damage >= health)
|
|
149
193
|
{
|
|
150
|
-
const rem = (
|
|
151
|
-
if (urgentThreat.ticksToImpact <= rem + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER) return
|
|
194
|
+
const rem = ((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
|
|
195
|
+
if (urgentThreat.ticksToImpact <= rem + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER) return cancel().move(safeMove.x, safeMove.y);
|
|
152
196
|
}
|
|
153
197
|
|
|
154
|
-
if (
|
|
198
|
+
if (status === 'idle' && urgentThreat && blinkCooldown === 0 && urgentThreat.ticksToImpact >= 10 && urgentThreat.ticksToImpact <= blinkWindowMax)
|
|
155
199
|
{
|
|
156
|
-
|
|
200
|
+
const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
|
|
201
|
+
return blink(blinkTarget.x, blinkTarget.y);
|
|
157
202
|
}
|
|
158
203
|
|
|
159
|
-
if (
|
|
204
|
+
if (status === 'idle' && urgentThreat)
|
|
160
205
|
{
|
|
161
206
|
const forceShield = shouldForceShield(urgentThreat);
|
|
162
207
|
const undodgeable = !urgentThreat.bestDodgeDirection;
|
|
163
|
-
const healthLow =
|
|
164
|
-
const healthCritical =
|
|
208
|
+
const healthLow = health < shieldHealthThreshold;
|
|
209
|
+
const healthCritical = health <= urgentThreat.projectile.damage * 2 + 1;
|
|
165
210
|
if ((forceShield || (undodgeable && healthLow) || healthCritical) && urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
|
|
166
211
|
{
|
|
167
|
-
return
|
|
212
|
+
return shield();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// === Preemptive shield — shield when enemy is casting a missile at close range ===
|
|
217
|
+
if (
|
|
218
|
+
status === 'idle' &&
|
|
219
|
+
distance < 300 &&
|
|
220
|
+
enemy.status === 'casting' &&
|
|
221
|
+
enemy.castingSpell === 'missile'
|
|
222
|
+
)
|
|
223
|
+
{
|
|
224
|
+
const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
|
|
225
|
+
const estimatedFlightTicks = distance / 8;
|
|
226
|
+
const ticksUntilHit = remainingCast + estimatedFlightTicks;
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
|
|
230
|
+
ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
|
|
231
|
+
)
|
|
232
|
+
{
|
|
233
|
+
return shield();
|
|
168
234
|
}
|
|
169
235
|
}
|
|
170
236
|
|
|
171
237
|
// === OFFENSE ===
|
|
172
|
-
const myIds = new Set(
|
|
173
|
-
const hasLethal =
|
|
174
|
-
if (
|
|
238
|
+
const myIds = new Set(myProjectiles.map((p) => p.id));
|
|
239
|
+
const hasLethal = projectiles.some((p) => !myIds.has(p.id) && p.damage >= health && distanceTo(position, p.position) / p.speed < MAX_CAST_BUDGET + RULES.GCD_DURATION);
|
|
240
|
+
if (status === 'idle' && !hasLethal)
|
|
175
241
|
{
|
|
242
|
+
const reposition = getPreCastReposition(position, enemy.position);
|
|
243
|
+
if (reposition) return moveAction(reposition.x, reposition.y);
|
|
244
|
+
|
|
176
245
|
const enemyHP = Math.ceil(enemy.health);
|
|
177
246
|
|
|
178
247
|
// T3 PUNISH: fast straight missiles during vulnerability
|
|
@@ -182,18 +251,18 @@ export const Archlich: WizardFunction = ({state}) =>
|
|
|
182
251
|
const flight = distance / flightTimeDivisor;
|
|
183
252
|
const pBudget = vulnTicks - flight;
|
|
184
253
|
const ntt = urgentThreat?.ticksToImpact ?? Infinity;
|
|
185
|
-
const sBudget = ntt - GCD_DURATION - SHIELD_CAST_TIME - SHIELD_BUFFER;
|
|
254
|
+
const sBudget = ntt - RULES.GCD_DURATION - RULES.SHIELD_CAST_TIME - SHIELD_BUFFER;
|
|
186
255
|
const eBudget = Math.min(pBudget, sBudget, MAX_CAST_BUDGET);
|
|
187
256
|
if (eBudget > 0)
|
|
188
257
|
{
|
|
189
|
-
const cfg = fitMissileToBudget(eBudget, distance, {minTurnRate: 0, maxDamage: enemyHP, lastMissileConfig:
|
|
258
|
+
const cfg = fitMissileToBudget(eBudget, distance, {minTurnRate: 0, maxDamage: enemyHP, lastMissileConfig: lastMissileConfig});
|
|
190
259
|
if (cfg && cfg.damage >= MIN_USEFUL_DAMAGE)
|
|
191
260
|
{
|
|
192
|
-
const ct = getMissileCastTime(cfg,
|
|
261
|
+
const ct = getMissileCastTime(cfg, lastMissileConfig);
|
|
193
262
|
if (ct + distance / cfg.speed < vulnTicks)
|
|
194
263
|
{
|
|
195
|
-
const aim = cfg.turnRate > 0 ? enemy.position : getLeadPosition(enemy.position, enemy.velocity, cfg.speed,
|
|
196
|
-
return
|
|
264
|
+
const aim = cfg.turnRate > 0 ? enemy.position : getLeadPosition(enemy.position, enemy.velocity, cfg.speed, position);
|
|
265
|
+
return missile(cfg, cfg.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, aim)).move(safeMove.x, safeMove.y);
|
|
197
266
|
}
|
|
198
267
|
}
|
|
199
268
|
}
|
|
@@ -201,20 +270,20 @@ export const Archlich: WizardFunction = ({state}) =>
|
|
|
201
270
|
|
|
202
271
|
// STANDARD MODE: identical to Lich
|
|
203
272
|
let ntt = urgentThreat?.ticksToImpact ?? Infinity;
|
|
204
|
-
if (enemy.
|
|
273
|
+
if (enemy.status === 'casting' && enemy.castingSpell === 'missile')
|
|
205
274
|
{
|
|
206
275
|
const rem = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
|
|
207
276
|
ntt = Math.min(ntt, rem + distance / flightTimeDivisor);
|
|
208
277
|
}
|
|
209
|
-
const sBudget = ntt - GCD_DURATION - SHIELD_CAST_TIME - SHIELD_BUFFER;
|
|
278
|
+
const sBudget = ntt - RULES.GCD_DURATION - RULES.SHIELD_CAST_TIME - SHIELD_BUFFER;
|
|
210
279
|
const budget = Math.min(sBudget, MAX_CAST_BUDGET);
|
|
211
280
|
const minDmg = Math.min(MIN_USEFUL_DAMAGE, enemyHP);
|
|
212
|
-
const cfg = fitMissileToBudget(budget, distance, {minTurnRate, lastMissileConfig:
|
|
281
|
+
const cfg = fitMissileToBudget(budget, distance, {minTurnRate, lastMissileConfig: lastMissileConfig});
|
|
213
282
|
if (cfg && cfg.damage >= minDmg)
|
|
214
283
|
{
|
|
215
|
-
return
|
|
284
|
+
return missile(cfg, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
|
|
216
285
|
}
|
|
217
286
|
}
|
|
218
287
|
|
|
219
|
-
return
|
|
220
|
-
}
|
|
288
|
+
return moveAction(safeMove.x, safeMove.y);
|
|
289
|
+
}
|