@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
package/src/engine/spells.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import {WizardState, MissileConfig, ProjectileState} from '../types.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
SHIELD_DECAY_PER_SECOND,
|
|
5
|
-
SHIELD_MIN_BLOCK,
|
|
3
|
+
RULES,
|
|
6
4
|
TICKS_PER_SECOND,
|
|
7
|
-
GCD_DURATION,
|
|
8
|
-
SHIELD_CAST_TIME,
|
|
9
|
-
BLINK_CAST_TIME,
|
|
10
5
|
calculateMissileCastTime,
|
|
11
6
|
calculateWarmupMultiplier,
|
|
12
7
|
} from '../rules.js';
|
|
@@ -21,6 +16,7 @@ export function startCast(wizard: WizardState, spell: 'missile' | 'shield' | 'bl
|
|
|
21
16
|
wizard.castingSpell = spell;
|
|
22
17
|
wizard.castProgress = 0;
|
|
23
18
|
|
|
19
|
+
// Stryker disable next-line ConditionalExpression: `true && config` equivalent for valid calls (spell is always correct type)
|
|
24
20
|
if (spell === 'missile' && config)
|
|
25
21
|
{
|
|
26
22
|
// calculateMissileCastTime includes warmup when lastMissileConfig is passed
|
|
@@ -31,11 +27,12 @@ export function startCast(wizard: WizardState, spell: 'missile' | 'shield' | 'bl
|
|
|
31
27
|
}
|
|
32
28
|
else if (spell === 'shield')
|
|
33
29
|
{
|
|
34
|
-
wizard.castDuration = SHIELD_CAST_TIME;
|
|
30
|
+
wizard.castDuration = RULES.SHIELD_CAST_TIME;
|
|
35
31
|
}
|
|
36
|
-
else
|
|
32
|
+
else
|
|
37
33
|
{
|
|
38
|
-
|
|
34
|
+
// Blink is the only remaining case after missile and shield
|
|
35
|
+
wizard.castDuration = RULES.BLINK_CAST_TIME;
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
@@ -66,7 +63,7 @@ export function completeCast(wizard: WizardState): void
|
|
|
66
63
|
else if (spell === 'missile')
|
|
67
64
|
{
|
|
68
65
|
wizard.state = 'gcd_locked';
|
|
69
|
-
wizard.gcdRemaining = GCD_DURATION;
|
|
66
|
+
wizard.gcdRemaining = RULES.GCD_DURATION;
|
|
70
67
|
}
|
|
71
68
|
else
|
|
72
69
|
{
|
|
@@ -96,8 +93,8 @@ export function updateShield(wizard: WizardState, deltaTicks: number): void
|
|
|
96
93
|
export function calculateShieldBlock(channelDurationTicks: number): number
|
|
97
94
|
{
|
|
98
95
|
const channelSeconds = channelDurationTicks / TICKS_PER_SECOND;
|
|
99
|
-
const decay = channelSeconds * SHIELD_DECAY_PER_SECOND;
|
|
100
|
-
return Math.max(SHIELD_MIN_BLOCK, SHIELD_MAX_BLOCK - decay);
|
|
96
|
+
const decay = channelSeconds * RULES.SHIELD_DECAY_PER_SECOND;
|
|
97
|
+
return Math.max(RULES.SHIELD_MIN_BLOCK, RULES.SHIELD_MAX_BLOCK - decay);
|
|
101
98
|
}
|
|
102
99
|
|
|
103
100
|
/**
|
package/src/engine-version.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* UNITS REFERENCE (100 ticks = 1 second):
|
|
7
7
|
* Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
|
|
8
|
-
* Movement: direction vector, magnitude
|
|
8
|
+
* Movement: direction vector, magnitude clamped to max 1 (speed in [0, 1])
|
|
9
9
|
* Speed: units per tick (player moves at 1 unit/tick = 100 units/sec)
|
|
10
10
|
* Duration: ticks (divide by 100 for seconds)
|
|
11
11
|
* Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Turn rate: degrees per tick the missile can rotate
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {WizardActions, MissileConfig,
|
|
17
|
-
import type {FinalAction, ActionBuilder} from './types.js';
|
|
16
|
+
import {WizardActions, MissileConfig, MissileActions} from '../types.js';
|
|
17
|
+
import type {FinalAction, ActionBuilder, MissileAction, MissileFunction} from './types.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Create a FinalAction (no further chaining possible).
|
|
@@ -63,7 +63,7 @@ function createActionBuilder(baseAction: Omit<WizardActions, 'move'>): ActionBui
|
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* return shield(); // shield and stay still
|
|
66
|
-
* return shield().move(
|
|
66
|
+
* return shield().move(1, 0); // move right while cast starts
|
|
67
67
|
*/
|
|
68
68
|
export function shield(): ActionBuilder
|
|
69
69
|
{
|
|
@@ -90,27 +90,30 @@ export function shield(): ActionBuilder
|
|
|
90
90
|
* - duration: ticks the missile lives (min 10). Range ≈ speed × duration.
|
|
91
91
|
* - turnRate: degrees/tick of homing (0 = straight line, 3 = moderate homing, 5+ = strong).
|
|
92
92
|
* Negative = no homing + minor speed cost reduction.
|
|
93
|
-
* @param ai - Called every tick to control missile steering.
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* Return { turnToward: {x, y} } to home toward a position, or {} to fly straight.
|
|
93
|
+
* @param ai - Called every tick to control missile steering. Use getMissileContext()
|
|
94
|
+
* to read the missile's state (position, rotation, speed, etc.), the full game
|
|
95
|
+
* state (worldState), and a seeded PRNG (random). Return turnToward(x, y) to home
|
|
96
|
+
* toward a position, or flyStraight() to fly straight.
|
|
98
97
|
* @param direction - Launch angle in degrees (0°=right, 90°=down, 180°=left, 270°=up).
|
|
99
98
|
* Tip: use Math.atan2(dy, dx) * (180 / Math.PI) to aim at a target.
|
|
100
99
|
*
|
|
101
100
|
* @example
|
|
102
101
|
* // Straight missile aimed at enemy
|
|
103
102
|
* const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
104
|
-
* return missile({damage: 15, speed: 6, duration: 200, turnRate: 0}, () => (
|
|
103
|
+
* return missile({damage: 15, speed: 6, duration: 200, turnRate: 0}, () => flyStraight(), angle);
|
|
105
104
|
*
|
|
106
105
|
* // Homing missile that tracks enemy
|
|
107
106
|
* return missile(
|
|
108
107
|
* {damage: 10, speed: 5, duration: 300, turnRate: 3},
|
|
109
|
-
* (
|
|
108
|
+
* () => {
|
|
109
|
+
* const ctx = getMissileContext();
|
|
110
|
+
* const enemy = ctx.worldState.enemies[0];
|
|
111
|
+
* return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
112
|
+
* },
|
|
110
113
|
* angle,
|
|
111
114
|
* );
|
|
112
115
|
*/
|
|
113
|
-
export function missile(config: MissileConfig, ai:
|
|
116
|
+
export function missile(config: MissileConfig, ai: MissileFunction, direction: number): ActionBuilder
|
|
114
117
|
{
|
|
115
118
|
return createActionBuilder({
|
|
116
119
|
startCast: {
|
|
@@ -157,7 +160,7 @@ export function blink(x: number, y: number): FinalAction
|
|
|
157
160
|
* Can chain .move() for simultaneous movement.
|
|
158
161
|
*
|
|
159
162
|
* @example
|
|
160
|
-
* return cancel().move(-
|
|
163
|
+
* return cancel().move(-1, 0); // cancel and dodge left
|
|
161
164
|
*/
|
|
162
165
|
export function cancel(): ActionBuilder
|
|
163
166
|
{
|
|
@@ -169,19 +172,17 @@ export function cancel(): ActionBuilder
|
|
|
169
172
|
/**
|
|
170
173
|
* Move in a direction without casting any spell.
|
|
171
174
|
*
|
|
172
|
-
* This is a **direction vector**, not a target position.
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* To move toward a target position, subtract your position:
|
|
177
|
-
* move(target.x - myPos.x, target.y - myPos.y)
|
|
175
|
+
* This is a **direction vector**, not a target position. Values are in the
|
|
176
|
+
* range [-1, 1] where 1 = full speed. Larger values (like raw position
|
|
177
|
+
* deltas) are clamped to full speed automatically.
|
|
178
178
|
*
|
|
179
179
|
* @param x - Horizontal direction (positive = right, negative = left)
|
|
180
180
|
* @param y - Vertical direction (positive = down, negative = up)
|
|
181
181
|
*
|
|
182
182
|
* @example
|
|
183
|
-
* return move(
|
|
184
|
-
* return move(
|
|
183
|
+
* return move(1, 0); // move right at full speed
|
|
184
|
+
* return move(dx / dist, dy / dist); // normalized unit vector = full speed
|
|
185
|
+
* return move(enemy.position.x - myPos.x, enemy.position.y - myPos.y); // raw delta = clamped to full speed
|
|
185
186
|
*/
|
|
186
187
|
export function move(x: number, y: number): FinalAction
|
|
187
188
|
{
|
|
@@ -200,6 +201,60 @@ export function idle(): FinalAction
|
|
|
200
201
|
});
|
|
201
202
|
}
|
|
202
203
|
|
|
204
|
+
// ============================================================
|
|
205
|
+
// MISSILE ACTION BUILDERS
|
|
206
|
+
// ============================================================
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Steer the missile toward a world position.
|
|
210
|
+
* The engine clamps the actual turn to the missile's turnRate.
|
|
211
|
+
*
|
|
212
|
+
* @param x - Target X position (world coordinates)
|
|
213
|
+
* @param y - Target Y position (world coordinates)
|
|
214
|
+
*/
|
|
215
|
+
export function turnToward(x: number, y: number): MissileAction
|
|
216
|
+
{
|
|
217
|
+
return {
|
|
218
|
+
_toMissileAction: () => ({turnToward: {x, y}}),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Steer the missile toward a specific angle (degrees).
|
|
224
|
+
* The engine clamps the actual turn to the missile's turnRate.
|
|
225
|
+
*
|
|
226
|
+
* @param degrees - Target rotation in degrees (0=right, 90=down, 180=left, 270=up)
|
|
227
|
+
*/
|
|
228
|
+
export function turnToAngle(degrees: number): MissileAction
|
|
229
|
+
{
|
|
230
|
+
return {
|
|
231
|
+
_toMissileAction: () => ({turnToAngle: degrees}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Fly straight — no steering this tick.
|
|
237
|
+
*/
|
|
238
|
+
export function flyStraight(): MissileAction
|
|
239
|
+
{
|
|
240
|
+
return {
|
|
241
|
+
_toMissileAction: () => ({}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Extract MissileActions from a MissileAction.
|
|
247
|
+
* Used by the engine to get the actual missile action.
|
|
248
|
+
*/
|
|
249
|
+
export function extractMissileAction(action: MissileAction): MissileActions
|
|
250
|
+
{
|
|
251
|
+
return action._toMissileAction();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================
|
|
255
|
+
// WIZARD ACTION EXTRACTION
|
|
256
|
+
// ============================================================
|
|
257
|
+
|
|
203
258
|
/**
|
|
204
259
|
* Extract WizardActions from a FinalAction.
|
|
205
260
|
* Used by the engine to get the actual action.
|
package/src/hooks/index.ts
CHANGED
|
@@ -10,14 +10,17 @@ export type {
|
|
|
10
10
|
AnalyzedThreat,
|
|
11
11
|
FinalAction,
|
|
12
12
|
ActionBuilder,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
WizardFunction,
|
|
14
|
+
WizardContext,
|
|
15
15
|
MissileConfig,
|
|
16
|
-
|
|
16
|
+
MissileContext,
|
|
17
|
+
MissileAction,
|
|
18
|
+
MissileFunction,
|
|
17
19
|
} from './types.js';
|
|
18
20
|
|
|
19
21
|
// State hooks
|
|
20
22
|
export {
|
|
23
|
+
useRandom,
|
|
21
24
|
useHealth,
|
|
22
25
|
usePosition,
|
|
23
26
|
useVelocity,
|
|
@@ -28,7 +31,9 @@ export {
|
|
|
28
31
|
useCastingSpell,
|
|
29
32
|
useCastProgress,
|
|
30
33
|
useEnemy,
|
|
34
|
+
useProjectiles,
|
|
31
35
|
useMyProjectiles,
|
|
36
|
+
useLastMissileConfig,
|
|
32
37
|
useDamageDealt,
|
|
33
38
|
useDamageTaken,
|
|
34
39
|
useLastHitTick,
|
|
@@ -51,6 +56,10 @@ export {
|
|
|
51
56
|
move,
|
|
52
57
|
idle,
|
|
53
58
|
extractAction,
|
|
59
|
+
turnToward,
|
|
60
|
+
turnToAngle,
|
|
61
|
+
flyStraight,
|
|
62
|
+
extractMissileAction,
|
|
54
63
|
} from './action-builders.js';
|
|
55
64
|
|
|
56
65
|
// Re-export persistence hooks from runtime
|
|
@@ -59,17 +68,16 @@ export {
|
|
|
59
68
|
useEffect,
|
|
60
69
|
useMemo,
|
|
61
70
|
useRef,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
runWizardWithContext,
|
|
72
|
+
withWizardContext,
|
|
73
|
+
withMissileContext,
|
|
74
|
+
getWizardContext,
|
|
75
|
+
getMissileContext,
|
|
65
76
|
} from '../engine/hooks-runtime.js';
|
|
66
77
|
|
|
67
78
|
// Re-export RefObject type
|
|
68
79
|
export type {RefObject} from '../engine/hooks-runtime.js';
|
|
69
80
|
|
|
70
|
-
// Bot wrapper for integration
|
|
71
|
-
export {wrapNewBot, isNewStyleBot} from './bot-wrapper.js';
|
|
72
|
-
|
|
73
81
|
// Parameter tuning hook
|
|
74
82
|
export {useParam} from '../engine/params-runtime.js';
|
|
75
83
|
export type {ParamDeclaration} from '../engine/params-runtime.js';
|
package/src/hooks/state-hooks.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {Position, Velocity, ProjectileState} from '../types.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import {getWizardContext, useMemo} from '../engine/hooks-runtime.js';
|
|
17
|
+
import {RULES, TICKS_PER_SECOND} from '../rules.js';
|
|
18
18
|
import type {EnemyState, AnalyzedThreat} from './types.js';
|
|
19
19
|
import {analyzeThreats} from './threat-analysis.js';
|
|
20
20
|
|
|
@@ -22,12 +22,22 @@ import {analyzeThreats} from './threat-analysis.js';
|
|
|
22
22
|
// SELF STATE HOOKS
|
|
23
23
|
// ============================================================
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Get a seeded random number generator. Returns a function that produces
|
|
27
|
+
* deterministic values in [0, 1) — same seed + same tick = same sequence.
|
|
28
|
+
* Use this instead of Math.random() so replays are deterministic.
|
|
29
|
+
*/
|
|
30
|
+
export function useRandom(): () => number
|
|
31
|
+
{
|
|
32
|
+
return getWizardContext().random;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
/**
|
|
26
36
|
* Get your current health (0-60). Wizard dies at 0.
|
|
27
37
|
*/
|
|
28
38
|
export function useHealth(): number
|
|
29
39
|
{
|
|
30
|
-
return
|
|
40
|
+
return getWizardContext().health;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/**
|
|
@@ -36,7 +46,7 @@ export function useHealth(): number
|
|
|
36
46
|
*/
|
|
37
47
|
export function usePosition(): Position
|
|
38
48
|
{
|
|
39
|
-
const ctx =
|
|
49
|
+
const ctx = getWizardContext();
|
|
40
50
|
return {x: ctx.position.x, y: ctx.position.y};
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -46,7 +56,7 @@ export function usePosition(): Position
|
|
|
46
56
|
*/
|
|
47
57
|
export function useVelocity(): Velocity
|
|
48
58
|
{
|
|
49
|
-
const ctx =
|
|
59
|
+
const ctx = getWizardContext();
|
|
50
60
|
return {x: ctx.velocity.x, y: ctx.velocity.y};
|
|
51
61
|
}
|
|
52
62
|
|
|
@@ -59,7 +69,7 @@ export function useVelocity(): Velocity
|
|
|
59
69
|
*/
|
|
60
70
|
export function useStatus(): 'idle' | 'casting' | 'channeling' | 'gcd_locked'
|
|
61
71
|
{
|
|
62
|
-
return
|
|
72
|
+
return getWizardContext().state;
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
/**
|
|
@@ -72,7 +82,7 @@ export function useStatus(): 'idle' | 'casting' | 'channeling' | 'gcd_locked'
|
|
|
72
82
|
*/
|
|
73
83
|
export function useTicksUntilReady(): number
|
|
74
84
|
{
|
|
75
|
-
const ctx =
|
|
85
|
+
const ctx = getWizardContext();
|
|
76
86
|
|
|
77
87
|
switch (ctx.state)
|
|
78
88
|
{
|
|
@@ -105,7 +115,7 @@ export function useTicksUntilReady(): number
|
|
|
105
115
|
*/
|
|
106
116
|
export function useShieldStrength(): number
|
|
107
117
|
{
|
|
108
|
-
const ctx =
|
|
118
|
+
const ctx = getWizardContext();
|
|
109
119
|
|
|
110
120
|
if (ctx.state !== 'channeling' || ctx.channelingSpell !== 'shield')
|
|
111
121
|
{
|
|
@@ -114,9 +124,27 @@ export function useShieldStrength(): number
|
|
|
114
124
|
|
|
115
125
|
// Calculate shield strength based on channel duration
|
|
116
126
|
const channelDuration = ctx.channelDuration ?? 0;
|
|
117
|
-
const decayPerTick = SHIELD_DECAY_PER_SECOND / TICKS_PER_SECOND;
|
|
118
|
-
const strength = SHIELD_MAX_BLOCK - (channelDuration * decayPerTick);
|
|
119
|
-
return Math.max(SHIELD_MIN_BLOCK, strength);
|
|
127
|
+
const decayPerTick = RULES.SHIELD_DECAY_PER_SECOND / TICKS_PER_SECOND;
|
|
128
|
+
const strength = RULES.SHIELD_MAX_BLOCK - (channelDuration * decayPerTick);
|
|
129
|
+
return Math.max(RULES.SHIELD_MIN_BLOCK, strength);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get all projectiles currently in flight (yours and enemy's).
|
|
134
|
+
* Used for blink safety calculations and threat analysis.
|
|
135
|
+
*/
|
|
136
|
+
export function useProjectiles(): ProjectileState[]
|
|
137
|
+
{
|
|
138
|
+
return getWizardContext().projectiles;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the config of the last missile you fired, or undefined if none fired yet.
|
|
143
|
+
* Used for the warmup system: consecutive similar missiles cast faster.
|
|
144
|
+
*/
|
|
145
|
+
export function useLastMissileConfig(): import('../types.js').MissileConfig | undefined
|
|
146
|
+
{
|
|
147
|
+
return getWizardContext().lastMissileConfig;
|
|
120
148
|
}
|
|
121
149
|
|
|
122
150
|
/**
|
|
@@ -130,7 +158,7 @@ export function useShieldStrength(): number
|
|
|
130
158
|
*/
|
|
131
159
|
export function useBlinkCooldown(): number
|
|
132
160
|
{
|
|
133
|
-
return
|
|
161
|
+
return getWizardContext().blinkCooldown;
|
|
134
162
|
}
|
|
135
163
|
|
|
136
164
|
/**
|
|
@@ -139,7 +167,7 @@ export function useBlinkCooldown(): number
|
|
|
139
167
|
*/
|
|
140
168
|
export function useCastingSpell(): 'missile' | 'shield' | 'blink' | null
|
|
141
169
|
{
|
|
142
|
-
const ctx =
|
|
170
|
+
const ctx = getWizardContext();
|
|
143
171
|
if (ctx.state !== 'casting')
|
|
144
172
|
{
|
|
145
173
|
return null;
|
|
@@ -155,7 +183,7 @@ export function useCastingSpell(): 'missile' | 'shield' | 'blink' | null
|
|
|
155
183
|
*/
|
|
156
184
|
export function useCastProgress(): {current: number; total: number} | null
|
|
157
185
|
{
|
|
158
|
-
const ctx =
|
|
186
|
+
const ctx = getWizardContext();
|
|
159
187
|
if (ctx.state !== 'casting')
|
|
160
188
|
{
|
|
161
189
|
return null;
|
|
@@ -180,7 +208,7 @@ export function useCastProgress(): {current: number; total: number} | null
|
|
|
180
208
|
*/
|
|
181
209
|
export function useEnemy(): EnemyState
|
|
182
210
|
{
|
|
183
|
-
const ctx =
|
|
211
|
+
const ctx = getWizardContext();
|
|
184
212
|
const enemy = ctx.enemies[0];
|
|
185
213
|
|
|
186
214
|
if (!enemy)
|
|
@@ -192,6 +220,10 @@ export function useEnemy(): EnemyState
|
|
|
192
220
|
health: 0,
|
|
193
221
|
status: 'idle',
|
|
194
222
|
castingSpell: null,
|
|
223
|
+
castProgress: 0,
|
|
224
|
+
castDuration: 0,
|
|
225
|
+
gcdRemaining: 0,
|
|
226
|
+
channelDuration: 0,
|
|
195
227
|
shieldStrength: 0,
|
|
196
228
|
};
|
|
197
229
|
}
|
|
@@ -201,8 +233,8 @@ export function useEnemy(): EnemyState
|
|
|
201
233
|
if (enemy.state === 'channeling' && enemy.channelingSpell === 'shield')
|
|
202
234
|
{
|
|
203
235
|
const channelDuration = enemy.channelDuration ?? 0;
|
|
204
|
-
const decayPerTick = SHIELD_DECAY_PER_SECOND / TICKS_PER_SECOND;
|
|
205
|
-
shieldStrength = Math.max(SHIELD_MIN_BLOCK, SHIELD_MAX_BLOCK - (channelDuration * decayPerTick));
|
|
236
|
+
const decayPerTick = RULES.SHIELD_DECAY_PER_SECOND / TICKS_PER_SECOND;
|
|
237
|
+
shieldStrength = Math.max(RULES.SHIELD_MIN_BLOCK, RULES.SHIELD_MAX_BLOCK - (channelDuration * decayPerTick));
|
|
206
238
|
}
|
|
207
239
|
|
|
208
240
|
return {
|
|
@@ -211,6 +243,10 @@ export function useEnemy(): EnemyState
|
|
|
211
243
|
health: enemy.health,
|
|
212
244
|
status: enemy.state,
|
|
213
245
|
castingSpell: enemy.state === 'casting' ? (enemy.castingSpell ?? null) : null,
|
|
246
|
+
castProgress: enemy.castProgress ?? 0,
|
|
247
|
+
castDuration: enemy.castDuration ?? 0,
|
|
248
|
+
gcdRemaining: enemy.gcdRemaining ?? 0,
|
|
249
|
+
channelDuration: enemy.channelDuration ?? 0,
|
|
214
250
|
shieldStrength,
|
|
215
251
|
};
|
|
216
252
|
}
|
|
@@ -225,7 +261,7 @@ export function useEnemy(): EnemyState
|
|
|
225
261
|
*/
|
|
226
262
|
export function useMyProjectiles(): ProjectileState[]
|
|
227
263
|
{
|
|
228
|
-
return
|
|
264
|
+
return getWizardContext().myProjectiles;
|
|
229
265
|
}
|
|
230
266
|
|
|
231
267
|
// ============================================================
|
|
@@ -237,7 +273,7 @@ export function useMyProjectiles(): ProjectileState[]
|
|
|
237
273
|
*/
|
|
238
274
|
export function useDamageDealt(): number
|
|
239
275
|
{
|
|
240
|
-
return
|
|
276
|
+
return getWizardContext().damageDealt;
|
|
241
277
|
}
|
|
242
278
|
|
|
243
279
|
/**
|
|
@@ -245,7 +281,7 @@ export function useDamageDealt(): number
|
|
|
245
281
|
*/
|
|
246
282
|
export function useDamageTaken(): number
|
|
247
283
|
{
|
|
248
|
-
return
|
|
284
|
+
return getWizardContext().damageTaken;
|
|
249
285
|
}
|
|
250
286
|
|
|
251
287
|
/**
|
|
@@ -254,7 +290,7 @@ export function useDamageTaken(): number
|
|
|
254
290
|
*/
|
|
255
291
|
export function useLastHitTick(): number
|
|
256
292
|
{
|
|
257
|
-
return
|
|
293
|
+
return getWizardContext().lastHitTick;
|
|
258
294
|
}
|
|
259
295
|
|
|
260
296
|
// ============================================================
|
|
@@ -267,7 +303,7 @@ export function useLastHitTick(): number
|
|
|
267
303
|
*/
|
|
268
304
|
export function useArenaSize(): {width: number; height: number}
|
|
269
305
|
{
|
|
270
|
-
const ctx =
|
|
306
|
+
const ctx = getWizardContext();
|
|
271
307
|
return {width: ctx.arenaWidth, height: ctx.arenaHeight};
|
|
272
308
|
}
|
|
273
309
|
|
|
@@ -277,7 +313,7 @@ export function useArenaSize(): {width: number; height: number}
|
|
|
277
313
|
*/
|
|
278
314
|
export function useTick(): number
|
|
279
315
|
{
|
|
280
|
-
return
|
|
316
|
+
return getWizardContext().tick;
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
// ============================================================
|
|
@@ -300,7 +336,7 @@ export function useTick(): number
|
|
|
300
336
|
*/
|
|
301
337
|
export function useThreats(): AnalyzedThreat[]
|
|
302
338
|
{
|
|
303
|
-
const ctx =
|
|
339
|
+
const ctx = getWizardContext();
|
|
304
340
|
const ticksUntilReady = useTicksUntilReady();
|
|
305
341
|
|
|
306
342
|
// Memoize to avoid recalculating every call
|
|
@@ -331,7 +367,7 @@ export function useClosestThreat(): AnalyzedThreat | null
|
|
|
331
367
|
*/
|
|
332
368
|
export function useMyThreatsToEnemy(): AnalyzedThreat[]
|
|
333
369
|
{
|
|
334
|
-
const ctx =
|
|
370
|
+
const ctx = getWizardContext();
|
|
335
371
|
const enemy = ctx.enemies[0];
|
|
336
372
|
|
|
337
373
|
if (!enemy)
|