@vibemancer/core 0.1.4 → 0.1.6
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-G5T65F3W.js +10617 -0
- package/dist/chunk-G5T65F3W.js.map +1 -0
- package/dist/index-browser.d.ts +1328 -1051
- package/dist/index-browser.js +59 -17
- package/dist/index.d.ts +53 -3
- package/dist/index.js +204 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -15
- package/src/banned-bot-names.ts +53 -0
- 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-browser.ts +1 -0
- package/src/index.ts +3 -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-74FFJCFF.js +0 -9140
- package/dist/chunk-74FFJCFF.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, 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} from '../../utils/combat.js';
|
|
6
7
|
import {
|
|
7
|
-
getThreats,
|
|
8
8
|
getMostUrgentThreat,
|
|
9
9
|
getDodgeDirectionForMovement,
|
|
10
10
|
isSafeToAttack,
|
|
@@ -12,13 +12,16 @@ import {
|
|
|
12
12
|
getBlinkToIncreaseDistance,
|
|
13
13
|
MIN_BLINK_ESCAPE_DISTANCE,
|
|
14
14
|
shouldForceShield,
|
|
15
|
+
keepOffWalls,
|
|
16
|
+
getPreCastReposition,
|
|
15
17
|
} from '../shared.js';
|
|
16
18
|
import {useParam} from '../../engine/params-runtime.js';
|
|
17
19
|
|
|
18
|
-
const HomingAI:
|
|
20
|
+
const HomingAI: MissileFunction = () =>
|
|
19
21
|
{
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
+
const ctx = getMissileContext();
|
|
23
|
+
const enemy = ctx.worldState.enemies[0];
|
|
24
|
+
return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
22
25
|
};
|
|
23
26
|
const WALL_BUFFER = 50;
|
|
24
27
|
|
|
@@ -36,12 +39,22 @@ function combatMove(
|
|
|
36
39
|
strafeCyclePeriod: number,
|
|
37
40
|
strafeIntensity: number,
|
|
38
41
|
approachSpeed: number,
|
|
39
|
-
|
|
42
|
+
centerBias: number,
|
|
43
|
+
): {x: number; y: number}
|
|
40
44
|
{
|
|
41
45
|
const deltaX = enemy.position.x - position.x;
|
|
42
46
|
const deltaY = enemy.position.y - position.y;
|
|
43
47
|
const normalizedDistance = currentDistance > 0 ? currentDistance : 1;
|
|
44
48
|
|
|
49
|
+
// Approach the center-side of the enemy so knockback pushes toward center, not lava.
|
|
50
|
+
const center = (ARENA_MIN + ARENA_MAX) / 2;
|
|
51
|
+
const toCenterX = center - enemy.position.x;
|
|
52
|
+
const toCenterY = center - enemy.position.y;
|
|
53
|
+
const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
|
|
54
|
+
const approachDirX = deltaX / normalizedDistance + (toCenterX / toCenterDist) * centerBias;
|
|
55
|
+
const approachDirY = deltaY / normalizedDistance + (toCenterY / toCenterDist) * centerBias;
|
|
56
|
+
const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
|
|
57
|
+
|
|
45
58
|
const strafeClockwise = {
|
|
46
59
|
x: -deltaY / normalizedDistance,
|
|
47
60
|
y: deltaX / normalizedDistance,
|
|
@@ -60,10 +73,10 @@ function combatMove(
|
|
|
60
73
|
}
|
|
61
74
|
// Priority 2: Avoid walls
|
|
62
75
|
else if (
|
|
63
|
-
position.x < WALL_BUFFER ||
|
|
64
|
-
position.x >
|
|
65
|
-
position.y < WALL_BUFFER ||
|
|
66
|
-
position.y >
|
|
76
|
+
position.x < ARENA_MIN + WALL_BUFFER ||
|
|
77
|
+
position.x > ARENA_MAX - WALL_BUFFER ||
|
|
78
|
+
position.y < ARENA_MIN + WALL_BUFFER ||
|
|
79
|
+
position.y > ARENA_MAX - WALL_BUFFER
|
|
67
80
|
)
|
|
68
81
|
{
|
|
69
82
|
const futureClockwise = {
|
|
@@ -75,16 +88,16 @@ function combatMove(
|
|
|
75
88
|
y: position.y + strafeCounterClockwise.y * 100,
|
|
76
89
|
};
|
|
77
90
|
const scoreClockwise = Math.min(
|
|
78
|
-
futureClockwise.x,
|
|
79
|
-
|
|
80
|
-
futureClockwise.y,
|
|
81
|
-
|
|
91
|
+
futureClockwise.x - ARENA_MIN,
|
|
92
|
+
ARENA_MAX - futureClockwise.x,
|
|
93
|
+
futureClockwise.y - ARENA_MIN,
|
|
94
|
+
ARENA_MAX - futureClockwise.y,
|
|
82
95
|
);
|
|
83
96
|
const scoreCounterClockwise = Math.min(
|
|
84
|
-
futureCounterClockwise.x,
|
|
85
|
-
|
|
86
|
-
futureCounterClockwise.y,
|
|
87
|
-
|
|
97
|
+
futureCounterClockwise.x - ARENA_MIN,
|
|
98
|
+
ARENA_MAX - futureCounterClockwise.x,
|
|
99
|
+
futureCounterClockwise.y - ARENA_MIN,
|
|
100
|
+
ARENA_MAX - futureCounterClockwise.y,
|
|
88
101
|
);
|
|
89
102
|
strafeDirection =
|
|
90
103
|
scoreClockwise > scoreCounterClockwise
|
|
@@ -101,35 +114,35 @@ function combatMove(
|
|
|
101
114
|
// When actively dodging, commit fully — no approach/retreat dilution
|
|
102
115
|
if (dodgeDirection)
|
|
103
116
|
{
|
|
104
|
-
return {x: strafeDirection.x
|
|
117
|
+
return {x: strafeDirection.x, y: strafeDirection.y};
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
let moveX = strafeDirection.x * strafeIntensity;
|
|
108
121
|
let moveY = strafeDirection.y * strafeIntensity;
|
|
109
122
|
|
|
110
123
|
// Distance management (only when not dodging)
|
|
111
|
-
if (currentDistance > maxDistance)
|
|
124
|
+
if (currentDistance > maxDistance)
|
|
112
125
|
{
|
|
113
|
-
moveX += (
|
|
114
|
-
moveY += (
|
|
126
|
+
moveX += (approachDirX / approachMag) * approachSpeed;
|
|
127
|
+
moveY += (approachDirY / approachMag) * approachSpeed;
|
|
115
128
|
}
|
|
116
129
|
else if (currentDistance < minDistance)
|
|
117
130
|
{
|
|
118
131
|
let retreatX = -(deltaX / normalizedDistance) * approachSpeed;
|
|
119
132
|
let retreatY = -(deltaY / normalizedDistance) * approachSpeed;
|
|
120
|
-
if (position.x < WALL_BUFFER && retreatX < 0) retreatX = 0;
|
|
121
|
-
if (position.x >
|
|
122
|
-
if (position.y < WALL_BUFFER && retreatY < 0) retreatY = 0;
|
|
123
|
-
if (position.y >
|
|
133
|
+
if (position.x < ARENA_MIN + WALL_BUFFER && retreatX < 0) retreatX = 0;
|
|
134
|
+
if (position.x > ARENA_MAX - WALL_BUFFER && retreatX > 0) retreatX = 0;
|
|
135
|
+
if (position.y < ARENA_MIN + WALL_BUFFER && retreatY < 0) retreatY = 0;
|
|
136
|
+
if (position.y > ARENA_MAX - WALL_BUFFER && retreatY > 0) retreatY = 0;
|
|
124
137
|
moveX += retreatX;
|
|
125
138
|
moveY += retreatY;
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
|
|
129
|
-
if (magnitude >
|
|
142
|
+
if (magnitude > 1)
|
|
130
143
|
{
|
|
131
|
-
moveX =
|
|
132
|
-
moveY =
|
|
144
|
+
moveX = moveX / magnitude;
|
|
145
|
+
moveY = moveY / magnitude;
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
return {x: moveX, y: moveY};
|
|
@@ -163,34 +176,47 @@ function combatMove(
|
|
|
163
176
|
*
|
|
164
177
|
* TIER: 1 (base)
|
|
165
178
|
*/
|
|
166
|
-
export
|
|
179
|
+
export function Spellshot(): FinalAction
|
|
167
180
|
{
|
|
168
|
-
const
|
|
169
|
-
const
|
|
181
|
+
const position = usePosition();
|
|
182
|
+
const enemy = useEnemy();
|
|
183
|
+
const status = useStatus();
|
|
184
|
+
const blinkCooldown = useBlinkCooldown();
|
|
185
|
+
const health = useHealth();
|
|
186
|
+
const tick = useTick();
|
|
187
|
+
const threats = useThreats();
|
|
188
|
+
const projectiles = useProjectiles();
|
|
189
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
190
|
+
const castingSpell = useCastingSpell();
|
|
191
|
+
const castProgressData = useCastProgress();
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
const dangerDistance = useParam('dangerDistance', 99, {range: 100, min: 0});
|
|
195
|
+
const targetDistance = useParam('targetDistance', 310, {
|
|
170
196
|
range: 150,
|
|
171
197
|
min: 0,
|
|
172
198
|
});
|
|
173
|
-
const distanceDeadZone = useParam('distanceDeadZone',
|
|
199
|
+
const distanceDeadZone = useParam('distanceDeadZone', 81, {
|
|
174
200
|
range: 50,
|
|
175
201
|
min: 0,
|
|
176
202
|
});
|
|
177
203
|
const minCombatDistance = targetDistance - distanceDeadZone;
|
|
178
204
|
const maxCombatDistance = targetDistance + distanceDeadZone;
|
|
179
|
-
const strafeCyclePeriod = useParam('strafeCyclePeriod',
|
|
205
|
+
const strafeCyclePeriod = useParam('strafeCyclePeriod', 240, {
|
|
180
206
|
range: 75,
|
|
181
207
|
min: 80,
|
|
182
208
|
max: 300,
|
|
183
209
|
});
|
|
184
|
-
const strafeIntensity = useParam('strafeIntensity',
|
|
185
|
-
range:
|
|
186
|
-
min:
|
|
187
|
-
max:
|
|
210
|
+
const strafeIntensity = useParam('strafeIntensity', 0.63, {
|
|
211
|
+
range: 0.4,
|
|
212
|
+
min: 0.2,
|
|
213
|
+
max: 1,
|
|
188
214
|
steps: 7,
|
|
189
215
|
});
|
|
190
|
-
const approachSpeed = useParam('approachSpeed',
|
|
191
|
-
range:
|
|
192
|
-
min:
|
|
193
|
-
max:
|
|
216
|
+
const approachSpeed = useParam('approachSpeed', 0.73, {
|
|
217
|
+
range: 0.3,
|
|
218
|
+
min: 0.1,
|
|
219
|
+
max: 0.8,
|
|
194
220
|
steps: 7,
|
|
195
221
|
});
|
|
196
222
|
const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
|
|
@@ -214,7 +240,7 @@ export const Spellshot: WizardFunction = ({state}) =>
|
|
|
214
240
|
range: 1.5,
|
|
215
241
|
steps: 5,
|
|
216
242
|
});
|
|
217
|
-
const missileMinDuration = useParam('missileMinDuration',
|
|
243
|
+
const missileMinDuration = useParam('missileMinDuration', 49, {
|
|
218
244
|
range: 30,
|
|
219
245
|
min: 20,
|
|
220
246
|
max: 100,
|
|
@@ -226,71 +252,88 @@ export const Spellshot: WizardFunction = ({state}) =>
|
|
|
226
252
|
max: 2.0,
|
|
227
253
|
steps: 5,
|
|
228
254
|
});
|
|
255
|
+
const centerBias = useParam('centerBias', 0.3, {
|
|
256
|
+
range: 0.4,
|
|
257
|
+
min: 0,
|
|
258
|
+
max: 0.8,
|
|
259
|
+
steps: 5,
|
|
260
|
+
});
|
|
261
|
+
const maxBlinkDistance = useParam('maxBlinkDistance', 300, {
|
|
262
|
+
range: 100,
|
|
263
|
+
min: 50,
|
|
264
|
+
max: 300,
|
|
265
|
+
steps: 7,
|
|
266
|
+
});
|
|
229
267
|
|
|
230
|
-
const
|
|
231
|
-
|
|
268
|
+
const distance = distanceTo(position, enemy.position);
|
|
269
|
+
|
|
270
|
+
function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
|
|
232
271
|
{
|
|
233
|
-
|
|
272
|
+
const dx = target.x - position.x;
|
|
273
|
+
const dy = target.y - position.y;
|
|
274
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
275
|
+
if (dist <= maxBlinkDistance) return target;
|
|
276
|
+
const scale = maxBlinkDistance / dist;
|
|
277
|
+
return {x: position.x + dx * scale, y: position.y + dy * scale};
|
|
234
278
|
}
|
|
235
279
|
|
|
236
|
-
const distance = distanceTo(state.position, enemy.position);
|
|
237
|
-
|
|
238
280
|
// Analyze threats using simulation
|
|
239
|
-
const threats = getThreats(state);
|
|
240
281
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
241
282
|
|
|
242
283
|
// === DEFENSE: Handle channeling ===
|
|
243
|
-
if (
|
|
284
|
+
if (status === 'channeling')
|
|
244
285
|
{
|
|
245
286
|
if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
|
|
246
287
|
{
|
|
247
|
-
return
|
|
288
|
+
return cancel();
|
|
248
289
|
}
|
|
249
|
-
return
|
|
290
|
+
return idle();
|
|
250
291
|
}
|
|
251
292
|
|
|
252
293
|
// === DEFENSE: Cancel missile cast if dangerous threat will arrive before we can shield ===
|
|
253
294
|
// Only cancel for: high-damage homing (always) or undodgeable when health is at risk
|
|
254
295
|
if (
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
status === 'casting' &&
|
|
297
|
+
castingSpell === 'missile' &&
|
|
257
298
|
urgentThreat &&
|
|
258
299
|
(!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
|
|
259
300
|
)
|
|
260
301
|
{
|
|
261
302
|
const mustCancel =
|
|
262
303
|
shouldForceShield(urgentThreat) ||
|
|
263
|
-
|
|
304
|
+
health <= urgentThreat.projectile.damage * 2 + 1;
|
|
264
305
|
if (mustCancel)
|
|
265
306
|
{
|
|
266
307
|
const remainingCast =
|
|
267
|
-
(
|
|
308
|
+
((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
|
|
268
309
|
if (
|
|
269
310
|
urgentThreat.ticksToImpact <=
|
|
270
|
-
remainingCast + GCD_DURATION + SHIELD_CAST_TIME + 3
|
|
311
|
+
remainingCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + 3
|
|
271
312
|
)
|
|
272
313
|
{
|
|
273
|
-
const dodgeDir = getDodgeDirectionForMovement(threats,
|
|
314
|
+
const dodgeDir = getDodgeDirectionForMovement(threats, position);
|
|
274
315
|
const cancelMove = combatMove(
|
|
275
|
-
|
|
316
|
+
position,
|
|
276
317
|
enemy,
|
|
277
318
|
distance,
|
|
278
319
|
minCombatDistance,
|
|
279
320
|
maxCombatDistance,
|
|
280
321
|
dodgeDir,
|
|
281
|
-
|
|
322
|
+
tick,
|
|
282
323
|
strafeCyclePeriod,
|
|
283
324
|
strafeIntensity,
|
|
284
325
|
approachSpeed,
|
|
326
|
+
centerBias,
|
|
285
327
|
);
|
|
286
|
-
|
|
328
|
+
const safeCancelMove = keepOffWalls(position, cancelMove);
|
|
329
|
+
return cancel().move(safeCancelMove.x, safeCancelMove.y);
|
|
287
330
|
}
|
|
288
331
|
}
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
// === DEFENSE: Shield undodgeable or high-damage homing threats ===
|
|
292
335
|
if (
|
|
293
|
-
|
|
336
|
+
status === 'idle' &&
|
|
294
337
|
urgentThreat &&
|
|
295
338
|
(!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
|
|
296
339
|
)
|
|
@@ -300,63 +343,81 @@ export const Spellshot: WizardFunction = ({state}) =>
|
|
|
300
343
|
// Start shield at the right time (small buffer for safety)
|
|
301
344
|
if (urgentThreat.ticksToStartShield <= 3)
|
|
302
345
|
{
|
|
303
|
-
return
|
|
304
|
-
move: {x: 0, y: 0},
|
|
305
|
-
startCast: {spell: 'shield'},
|
|
306
|
-
};
|
|
346
|
+
return shield();
|
|
307
347
|
}
|
|
308
348
|
}
|
|
309
349
|
// Can't dodge, can't shield → blink away (emergency)
|
|
310
|
-
else if (
|
|
350
|
+
else if (blinkCooldown === 0)
|
|
311
351
|
{
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
|
|
353
|
+
return blink(blinkTarget.x, blinkTarget.y);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// === Preemptive shield — shield when enemy is casting a missile at close range ===
|
|
358
|
+
if (
|
|
359
|
+
status === 'idle' &&
|
|
360
|
+
distance < 300 &&
|
|
361
|
+
enemy.status === 'casting' &&
|
|
362
|
+
enemy.castingSpell === 'missile'
|
|
363
|
+
)
|
|
364
|
+
{
|
|
365
|
+
const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
|
|
366
|
+
const estimatedFlightTicks = distance / 8;
|
|
367
|
+
const ticksUntilHit = remainingCast + estimatedFlightTicks;
|
|
368
|
+
|
|
369
|
+
if (
|
|
370
|
+
ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
|
|
371
|
+
ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
|
|
372
|
+
)
|
|
373
|
+
{
|
|
374
|
+
return shield();
|
|
316
375
|
}
|
|
317
376
|
}
|
|
318
377
|
|
|
319
378
|
// === DISTANCE BLINK: enemy too close, blink to reestablish kiting range ===
|
|
320
379
|
if (
|
|
321
|
-
|
|
380
|
+
status === 'idle' &&
|
|
322
381
|
distance < dangerDistance &&
|
|
323
|
-
|
|
382
|
+
blinkCooldown === 0 &&
|
|
324
383
|
(!urgentThreat || urgentThreat.bestDodgeDirection !== null)
|
|
325
384
|
)
|
|
326
385
|
{
|
|
327
386
|
const blinkTarget = getBlinkToIncreaseDistance(
|
|
328
|
-
|
|
387
|
+
position,
|
|
329
388
|
enemy.position,
|
|
330
|
-
|
|
389
|
+
projectiles,
|
|
331
390
|
MIN_BLINK_ESCAPE_DISTANCE,
|
|
332
391
|
);
|
|
333
|
-
if (blinkTarget)
|
|
392
|
+
if (blinkTarget)
|
|
334
393
|
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
startCast: {spell: 'blink', target: blinkTarget},
|
|
338
|
-
};
|
|
394
|
+
const capped = capBlinkDistance(blinkTarget);
|
|
395
|
+
return blink(capped.x, capped.y);
|
|
339
396
|
}
|
|
340
397
|
}
|
|
341
398
|
|
|
342
399
|
// Smart combat movement
|
|
343
|
-
const dodgeDirection = getDodgeDirectionForMovement(threats,
|
|
400
|
+
const dodgeDirection = getDodgeDirectionForMovement(threats, position);
|
|
344
401
|
const move = combatMove(
|
|
345
|
-
|
|
402
|
+
position,
|
|
346
403
|
enemy,
|
|
347
404
|
distance,
|
|
348
405
|
minCombatDistance,
|
|
349
406
|
maxCombatDistance,
|
|
350
407
|
dodgeDirection,
|
|
351
|
-
|
|
408
|
+
tick,
|
|
352
409
|
strafeCyclePeriod,
|
|
353
410
|
strafeIntensity,
|
|
354
411
|
approachSpeed,
|
|
412
|
+
centerBias,
|
|
355
413
|
);
|
|
356
|
-
|
|
414
|
+
const safeMove = keepOffWalls(position, move);
|
|
357
415
|
// === OFFENSE: Fast homing missiles ===
|
|
358
|
-
if (
|
|
416
|
+
if (status === 'idle' && distance < 600)
|
|
359
417
|
{
|
|
418
|
+
const reposition = getPreCastReposition(position, enemy.position);
|
|
419
|
+
if (reposition) return moveAction(reposition.x, reposition.y);
|
|
420
|
+
|
|
360
421
|
const missileConfig = {
|
|
361
422
|
damage: missileDamage,
|
|
362
423
|
speed: missileSpeed,
|
|
@@ -366,20 +427,12 @@ export const Spellshot: WizardFunction = ({state}) =>
|
|
|
366
427
|
Math.ceil((distance * durationRangeFactor) / missileSpeed),
|
|
367
428
|
),
|
|
368
429
|
};
|
|
369
|
-
const castTime = getMissileCastTime(missileConfig,
|
|
430
|
+
const castTime = getMissileCastTime(missileConfig, lastMissileConfig);
|
|
370
431
|
if (isSafeToAttack(threats, castTime))
|
|
371
432
|
{
|
|
372
|
-
return
|
|
373
|
-
move,
|
|
374
|
-
startCast: {
|
|
375
|
-
spell: 'missile',
|
|
376
|
-
config: missileConfig,
|
|
377
|
-
missileAI: HomingAI,
|
|
378
|
-
direction: angleTo(state.position, enemy.position),
|
|
379
|
-
},
|
|
380
|
-
};
|
|
433
|
+
return missile(missileConfig, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
|
|
381
434
|
}
|
|
382
435
|
}
|
|
383
436
|
|
|
384
|
-
return
|
|
385
|
-
}
|
|
437
|
+
return moveAction(safeMove.x, safeMove.y);
|
|
438
|
+
}
|