@vibemancer/core 0.1.4 → 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-74FFJCFF.js +0 -9140
- package/dist/chunk-74FFJCFF.js.map +0 -1
- package/src/hooks/bot-wrapper.ts +0 -84
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {FinalAction, MissileFunction} from '../../hooks/index.js';
|
|
2
|
+
import {usePosition, useEnemy, useStatus, useBlinkCooldown, useTick, useThreats, useLastMissileConfig, useCastingSpell, 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,
|
|
11
11
|
getBlinkToCenter,
|
|
12
12
|
shouldForceShield,
|
|
13
|
+
keepOffWalls,
|
|
14
|
+
getPreCastReposition,
|
|
13
15
|
} from '../shared.js';
|
|
14
16
|
import {useParam} from '../../engine/params-runtime.js';
|
|
15
17
|
|
|
@@ -21,19 +23,20 @@ const SHIELD_BUFFER = 3;
|
|
|
21
23
|
// Range: 5 × 180 = 900 units (covers arena)
|
|
22
24
|
const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
|
|
23
25
|
|
|
24
|
-
const HomingAI:
|
|
26
|
+
const HomingAI: MissileFunction = () =>
|
|
25
27
|
{
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
+
const ctx = getMissileContext();
|
|
29
|
+
const enemy = ctx.worldState.enemies[0];
|
|
30
|
+
return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
28
31
|
};
|
|
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
|
|
|
@@ -77,16 +80,16 @@ function smartStrafe(
|
|
|
77
80
|
y: position.y + strafeCounterClockwise.y * 100,
|
|
78
81
|
};
|
|
79
82
|
const scoreClockwise = Math.min(
|
|
80
|
-
futureClockwise.x,
|
|
81
|
-
|
|
82
|
-
futureClockwise.y,
|
|
83
|
-
|
|
83
|
+
futureClockwise.x - ARENA_MIN,
|
|
84
|
+
ARENA_MAX - futureClockwise.x,
|
|
85
|
+
futureClockwise.y - ARENA_MIN,
|
|
86
|
+
ARENA_MAX - futureClockwise.y,
|
|
84
87
|
);
|
|
85
88
|
const scoreCounterClockwise = Math.min(
|
|
86
|
-
futureCounterClockwise.x,
|
|
87
|
-
|
|
88
|
-
futureCounterClockwise.y,
|
|
89
|
-
|
|
89
|
+
futureCounterClockwise.x - ARENA_MIN,
|
|
90
|
+
ARENA_MAX - futureCounterClockwise.x,
|
|
91
|
+
futureCounterClockwise.y - ARENA_MIN,
|
|
92
|
+
ARENA_MAX - futureCounterClockwise.y,
|
|
90
93
|
);
|
|
91
94
|
strafeDir =
|
|
92
95
|
scoreClockwise > scoreCounterClockwise
|
|
@@ -99,7 +102,7 @@ function smartStrafe(
|
|
|
99
102
|
strafeDir = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
const intensity = dodgeDirection ?
|
|
105
|
+
const intensity = dodgeDirection ? 1 : strafeIntensity;
|
|
103
106
|
return {x: strafeDir.x * intensity, y: strafeDir.y * intensity};
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -119,30 +122,40 @@ function smartStrafe(
|
|
|
119
122
|
*
|
|
120
123
|
* TIER: 1 (base)
|
|
121
124
|
*/
|
|
122
|
-
export
|
|
125
|
+
export function Flamecaller(): FinalAction
|
|
123
126
|
{
|
|
124
|
-
const
|
|
127
|
+
const position = usePosition();
|
|
128
|
+
const enemy = useEnemy();
|
|
129
|
+
const status = useStatus();
|
|
130
|
+
const blinkCooldown = useBlinkCooldown();
|
|
131
|
+
const tick = useTick();
|
|
132
|
+
const threats = useThreats();
|
|
133
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
134
|
+
const castingSpell = useCastingSpell();
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
const targetDistance = useParam('targetDistance', 498, {
|
|
125
138
|
range: 150,
|
|
126
139
|
min: 0,
|
|
127
140
|
});
|
|
128
|
-
const strafeCyclePeriod = useParam('strafeCyclePeriod',
|
|
141
|
+
const strafeCyclePeriod = useParam('strafeCyclePeriod', 211, {
|
|
129
142
|
range: 75,
|
|
130
143
|
min: 80,
|
|
131
144
|
max: 300,
|
|
132
145
|
});
|
|
133
|
-
const strafeIntensity = useParam('strafeIntensity',
|
|
134
|
-
range:
|
|
135
|
-
min:
|
|
136
|
-
max:
|
|
146
|
+
const strafeIntensity = useParam('strafeIntensity', 0.87, {
|
|
147
|
+
range: 0.4,
|
|
148
|
+
min: 0.2,
|
|
149
|
+
max: 1,
|
|
137
150
|
steps: 7,
|
|
138
151
|
});
|
|
139
|
-
const approachSpeed = useParam('approachSpeed',
|
|
140
|
-
range:
|
|
141
|
-
min:
|
|
142
|
-
max:
|
|
152
|
+
const approachSpeed = useParam('approachSpeed', 0.1, {
|
|
153
|
+
range: 0.3,
|
|
154
|
+
min: 0.1,
|
|
155
|
+
max: 0.7,
|
|
143
156
|
steps: 7,
|
|
144
157
|
});
|
|
145
|
-
const distanceDeadZone = useParam('distanceDeadZone',
|
|
158
|
+
const distanceDeadZone = useParam('distanceDeadZone', 73, {
|
|
146
159
|
range: 30,
|
|
147
160
|
min: 5,
|
|
148
161
|
max: 80,
|
|
@@ -153,30 +166,39 @@ export const Flamecaller: WizardFunction = ({state}) =>
|
|
|
153
166
|
min: 50,
|
|
154
167
|
max: 300,
|
|
155
168
|
});
|
|
156
|
-
const
|
|
157
|
-
range:
|
|
158
|
-
min:
|
|
159
|
-
max:
|
|
169
|
+
const centerBias = useParam('centerBias', 0.05, {
|
|
170
|
+
range: 0.4,
|
|
171
|
+
min: 0,
|
|
172
|
+
max: 0.8,
|
|
160
173
|
steps: 5,
|
|
161
174
|
});
|
|
175
|
+
const maxBlinkDistance = useParam('maxBlinkDistance', 204, {
|
|
176
|
+
range: 100,
|
|
177
|
+
min: 50,
|
|
178
|
+
max: 300,
|
|
179
|
+
steps: 7,
|
|
180
|
+
});
|
|
162
181
|
|
|
163
|
-
const
|
|
164
|
-
|
|
182
|
+
const distance = distanceTo(position, enemy.position);
|
|
183
|
+
|
|
184
|
+
function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
|
|
165
185
|
{
|
|
166
|
-
|
|
186
|
+
const dx = target.x - position.x;
|
|
187
|
+
const dy = target.y - position.y;
|
|
188
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
189
|
+
if (dist <= maxBlinkDistance) return target;
|
|
190
|
+
const scale = maxBlinkDistance / dist;
|
|
191
|
+
return {x: position.x + dx * scale, y: position.y + dy * scale};
|
|
167
192
|
}
|
|
168
193
|
|
|
169
|
-
const distance = distanceTo(state.position, enemy.position);
|
|
170
|
-
|
|
171
|
-
const threats = getThreats(state);
|
|
172
194
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
173
195
|
|
|
174
|
-
const dodgeDirection = getDodgeDirectionForMovement(threats,
|
|
196
|
+
const dodgeDirection = getDodgeDirectionForMovement(threats, position);
|
|
175
197
|
const strafe = smartStrafe(
|
|
176
|
-
|
|
198
|
+
position,
|
|
177
199
|
enemy,
|
|
178
200
|
dodgeDirection,
|
|
179
|
-
|
|
201
|
+
tick,
|
|
180
202
|
strafeCyclePeriod,
|
|
181
203
|
strafeIntensity,
|
|
182
204
|
);
|
|
@@ -185,18 +207,26 @@ export const Flamecaller: WizardFunction = ({state}) =>
|
|
|
185
207
|
let moveX = strafe.x;
|
|
186
208
|
let moveY = strafe.y;
|
|
187
209
|
|
|
188
|
-
if (!dodgeDirection)
|
|
210
|
+
if (!dodgeDirection)
|
|
189
211
|
{
|
|
190
|
-
const deltaX = enemy.position.x -
|
|
191
|
-
const deltaY = enemy.position.y -
|
|
212
|
+
const deltaX = enemy.position.x - position.x;
|
|
213
|
+
const deltaY = enemy.position.y - position.y;
|
|
192
214
|
const normalizedDistance = distance > 0 ? distance : 1;
|
|
193
215
|
|
|
194
|
-
if (distance > targetDistance + distanceDeadZone)
|
|
216
|
+
if (distance > targetDistance + distanceDeadZone)
|
|
195
217
|
{
|
|
196
|
-
|
|
197
|
-
|
|
218
|
+
// Approach the center-side of the enemy so knockback pushes toward center, not lava.
|
|
219
|
+
const center = (ARENA_MIN + ARENA_MAX) / 2;
|
|
220
|
+
const toCenterX = center - enemy.position.x;
|
|
221
|
+
const toCenterY = center - enemy.position.y;
|
|
222
|
+
const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
|
|
223
|
+
const approachDirX = deltaX / normalizedDistance + (toCenterX / toCenterDist) * centerBias;
|
|
224
|
+
const approachDirY = deltaY / normalizedDistance + (toCenterY / toCenterDist) * centerBias;
|
|
225
|
+
const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
|
|
226
|
+
moveX += (approachDirX / approachMag) * approachSpeed;
|
|
227
|
+
moveY += (approachDirY / approachMag) * approachSpeed;
|
|
198
228
|
}
|
|
199
|
-
else if (distance < targetDistance - distanceDeadZone)
|
|
229
|
+
else if (distance < targetDistance - distanceDeadZone)
|
|
200
230
|
{
|
|
201
231
|
moveX -= (deltaX / normalizedDistance) * approachSpeed;
|
|
202
232
|
moveY -= (deltaY / normalizedDistance) * approachSpeed;
|
|
@@ -204,33 +234,34 @@ export const Flamecaller: WizardFunction = ({state}) =>
|
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
|
|
207
|
-
if (magnitude >
|
|
237
|
+
if (magnitude > 1)
|
|
208
238
|
{
|
|
209
|
-
moveX =
|
|
210
|
-
moveY =
|
|
239
|
+
moveX = moveX / magnitude;
|
|
240
|
+
moveY = moveY / magnitude;
|
|
211
241
|
}
|
|
212
242
|
|
|
213
243
|
const move = {x: moveX, y: moveY};
|
|
244
|
+
const safeMove = keepOffWalls(position, move);
|
|
214
245
|
|
|
215
246
|
// === DEFENSE: Handle channeling ===
|
|
216
|
-
if (
|
|
247
|
+
if (status === 'channeling')
|
|
217
248
|
{
|
|
218
249
|
if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
|
|
219
250
|
{
|
|
220
|
-
return
|
|
251
|
+
return cancel().move(safeMove.x, safeMove.y);
|
|
221
252
|
}
|
|
222
|
-
return
|
|
253
|
+
return idle();
|
|
223
254
|
}
|
|
224
255
|
|
|
225
256
|
// === DEFENSE: Let shield cast finish ===
|
|
226
|
-
if (
|
|
257
|
+
if (status === 'casting' && castingSpell === 'shield')
|
|
227
258
|
{
|
|
228
|
-
return
|
|
259
|
+
return idle();
|
|
229
260
|
}
|
|
230
261
|
|
|
231
262
|
// === DEFENSE: Shield undodgeable or high-damage homing threats ===
|
|
232
263
|
if (
|
|
233
|
-
|
|
264
|
+
status === 'idle' &&
|
|
234
265
|
urgentThreat &&
|
|
235
266
|
(!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
|
|
236
267
|
)
|
|
@@ -239,48 +270,59 @@ export const Flamecaller: WizardFunction = ({state}) =>
|
|
|
239
270
|
{
|
|
240
271
|
if (urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
|
|
241
272
|
{
|
|
242
|
-
return
|
|
243
|
-
move: {x: 0, y: 0},
|
|
244
|
-
startCast: {spell: 'shield'},
|
|
245
|
-
};
|
|
273
|
+
return shield();
|
|
246
274
|
}
|
|
247
275
|
}
|
|
248
|
-
else if (
|
|
276
|
+
else if (blinkCooldown === 0)
|
|
249
277
|
{
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
278
|
+
const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
|
|
279
|
+
return blink(blinkTarget.x, blinkTarget.y);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// === Preemptive shield — shield when enemy is casting a missile at close range ===
|
|
284
|
+
if (
|
|
285
|
+
status === 'idle' &&
|
|
286
|
+
distance < 300 &&
|
|
287
|
+
enemy.status === 'casting' &&
|
|
288
|
+
enemy.castingSpell === 'missile'
|
|
289
|
+
)
|
|
290
|
+
{
|
|
291
|
+
const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
|
|
292
|
+
const estimatedFlightTicks = distance / 8;
|
|
293
|
+
const ticksUntilHit = remainingCast + estimatedFlightTicks;
|
|
294
|
+
|
|
295
|
+
if (
|
|
296
|
+
ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
|
|
297
|
+
ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
|
|
298
|
+
)
|
|
299
|
+
{
|
|
300
|
+
return shield();
|
|
254
301
|
}
|
|
255
302
|
}
|
|
256
303
|
|
|
257
304
|
// === OFFENSE: Fire fixed homing missile when safe ===
|
|
258
|
-
if (
|
|
305
|
+
if (status === 'idle')
|
|
259
306
|
{
|
|
307
|
+
const reposition = getPreCastReposition(position, enemy.position);
|
|
308
|
+
if (reposition) return moveAction(reposition.x, reposition.y);
|
|
309
|
+
|
|
260
310
|
const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
|
|
261
311
|
const castTime = getMissileCastTime(
|
|
262
312
|
MISSILE_CONFIG,
|
|
263
|
-
|
|
313
|
+
lastMissileConfig,
|
|
264
314
|
);
|
|
265
315
|
const safeWindow =
|
|
266
|
-
castTime + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
316
|
+
castTime + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
267
317
|
|
|
268
318
|
if (
|
|
269
319
|
nextThreatTime > safeWindow &&
|
|
270
|
-
isSafeToAttack(threats,
|
|
320
|
+
isSafeToAttack(threats, castTime)
|
|
271
321
|
)
|
|
272
322
|
{
|
|
273
|
-
return
|
|
274
|
-
move,
|
|
275
|
-
startCast: {
|
|
276
|
-
spell: 'missile',
|
|
277
|
-
config: MISSILE_CONFIG,
|
|
278
|
-
missileAI: HomingAI,
|
|
279
|
-
direction: angleTo(state.position, enemy.position),
|
|
280
|
-
},
|
|
281
|
-
};
|
|
323
|
+
return missile(MISSILE_CONFIG, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
|
|
282
324
|
}
|
|
283
325
|
}
|
|
284
326
|
|
|
285
|
-
return
|
|
286
|
-
}
|
|
327
|
+
return moveAction(safeMove.x, safeMove.y);
|
|
328
|
+
}
|