@vibemancer/core 0.1.0
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 +28 -0
- package/dist/chunk-L7Z7OFXD.js +9140 -0
- package/dist/chunk-L7Z7OFXD.js.map +1 -0
- package/dist/index-browser.d.ts +2602 -0
- package/dist/index-browser.js +407 -0
- package/dist/index-browser.js.map +1 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +750 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
- package/src/bots/berserker/01_Stormchaser.ts +457 -0
- package/src/bots/berserker/02_Stormcaller.ts +417 -0
- package/src/bots/berserker/03_Stormforger.ts +481 -0
- package/src/bots/caster/01_Flamecaller.ts +286 -0
- package/src/bots/caster/02_Pyromancer.ts +350 -0
- package/src/bots/caster/03_Infernalist.ts +492 -0
- package/src/bots/defensive/01_Turtle.ts +151 -0
- package/src/bots/defensive/02_Sentinel.ts +134 -0
- package/src/bots/defensive/03_Golem.ts +357 -0
- package/src/bots/duelist/01_Battlemage.ts +433 -0
- package/src/bots/duelist/02_Warmage.ts +438 -0
- package/src/bots/duelist/03_Archmage.ts +588 -0
- package/src/bots/homing/01_Bonemancer.ts +67 -0
- package/src/bots/homing/02_Lich.ts +356 -0
- package/src/bots/homing/03_Archlich.ts +220 -0
- package/src/bots/index.ts +30 -0
- package/src/bots/kiter/01_Spellspinner.ts +398 -0
- package/src/bots/kiter/02_Spellweaver.ts +378 -0
- package/src/bots/kiter/03_Spellbinder.ts +448 -0
- package/src/bots/melee/01_Shadowblade.ts +270 -0
- package/src/bots/melee/02_Nightblade.ts +437 -0
- package/src/bots/melee/03_Voidblade.ts +582 -0
- package/src/bots/registry.ts +207 -0
- package/src/bots/shared.ts +472 -0
- package/src/bots/sniper/01_Spellshot.ts +385 -0
- package/src/bots/sniper/02_Spelltracer.ts +441 -0
- package/src/bots/sniper/03_Spellseeker.ts +546 -0
- package/src/bots/standalone/Critter.ts +89 -0
- package/src/bots/standalone/Doombringer.ts +91 -0
- package/src/bots/standalone/Hogger.ts +228 -0
- package/src/bots/standalone/Rookie.ts +50 -0
- package/src/bots/standalone/TargetDummy.ts +21 -0
- package/src/bots/test/cheater.ts +405 -0
- package/src/bots/test/crasher.ts +81 -0
- package/src/engine/hooks-runtime.ts +394 -0
- package/src/engine/manual-match.ts +289 -0
- package/src/engine/missile-templates.ts +155 -0
- package/src/engine/optimizer.ts +220 -0
- package/src/engine/params-runtime.ts +189 -0
- package/src/engine/physics.ts +143 -0
- package/src/engine/sandbox-browser.ts +671 -0
- package/src/engine/sandbox-compile.ts +197 -0
- package/src/engine/sandbox-harness.ts +367 -0
- package/src/engine/sandbox.ts +332 -0
- package/src/engine/simulation.ts +828 -0
- package/src/engine/spells.ts +128 -0
- package/src/engine-version.ts +11 -0
- package/src/hooks/action-builders.ts +210 -0
- package/src/hooks/bot-wrapper.ts +84 -0
- package/src/hooks/index.ts +75 -0
- package/src/hooks/state-hooks.ts +354 -0
- package/src/hooks/threat-analysis.ts +365 -0
- package/src/hooks/types.ts +142 -0
- package/src/index-browser.ts +30 -0
- package/src/index.ts +24 -0
- package/src/rules.ts +254 -0
- package/src/stats.ts +262 -0
- package/src/testing.ts +207 -0
- package/src/trace.ts +430 -0
- package/src/types.ts +193 -0
- package/src/utils/angles.ts +47 -0
- package/src/utils/combat.ts +279 -0
- package/src/utils/distance.ts +21 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/movement.ts +108 -0
- package/src/utils/random.ts +65 -0
- package/src/utils/spatial.ts +63 -0
- package/src/utils/targeting.ts +45 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {WizardState, MissileConfig, ProjectileState} from '../types.js';
|
|
2
|
+
import {
|
|
3
|
+
SHIELD_MAX_BLOCK,
|
|
4
|
+
SHIELD_DECAY_PER_SECOND,
|
|
5
|
+
SHIELD_MIN_BLOCK,
|
|
6
|
+
TICKS_PER_SECOND,
|
|
7
|
+
GCD_DURATION,
|
|
8
|
+
SHIELD_CAST_TIME,
|
|
9
|
+
BLINK_CAST_TIME,
|
|
10
|
+
calculateMissileCastTime,
|
|
11
|
+
calculateWarmupMultiplier,
|
|
12
|
+
} from '../rules.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Start casting a spell.
|
|
16
|
+
* For missiles, applies warmup system (bonus for similar, penalty for switching).
|
|
17
|
+
*/
|
|
18
|
+
export function startCast(wizard: WizardState, spell: 'missile' | 'shield' | 'blink', config?: MissileConfig): void
|
|
19
|
+
{
|
|
20
|
+
wizard.state = 'casting';
|
|
21
|
+
wizard.castingSpell = spell;
|
|
22
|
+
wizard.castProgress = 0;
|
|
23
|
+
|
|
24
|
+
if (spell === 'missile' && config)
|
|
25
|
+
{
|
|
26
|
+
// calculateMissileCastTime includes warmup when lastMissileConfig is passed
|
|
27
|
+
const castTimeSec = calculateMissileCastTime(config, wizard.lastMissileConfig);
|
|
28
|
+
wizard.castDuration = Math.max(1, Math.round(castTimeSec * TICKS_PER_SECOND));
|
|
29
|
+
// Store warmup multiplier for visualization
|
|
30
|
+
wizard.warmupMultiplier = calculateWarmupMultiplier(wizard.lastMissileConfig, config);
|
|
31
|
+
}
|
|
32
|
+
else if (spell === 'shield')
|
|
33
|
+
{
|
|
34
|
+
wizard.castDuration = SHIELD_CAST_TIME;
|
|
35
|
+
}
|
|
36
|
+
else if (spell === 'blink')
|
|
37
|
+
{
|
|
38
|
+
wizard.castDuration = BLINK_CAST_TIME;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Cancel the current cast.
|
|
44
|
+
*/
|
|
45
|
+
export function cancelCast(wizard: WizardState): void
|
|
46
|
+
{
|
|
47
|
+
wizard.state = 'idle';
|
|
48
|
+
delete wizard.castingSpell;
|
|
49
|
+
delete wizard.castProgress;
|
|
50
|
+
delete wizard.castDuration;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Complete the current cast and trigger the spell effect.
|
|
55
|
+
*/
|
|
56
|
+
export function completeCast(wizard: WizardState): void
|
|
57
|
+
{
|
|
58
|
+
const spell = wizard.castingSpell;
|
|
59
|
+
|
|
60
|
+
if (spell === 'shield')
|
|
61
|
+
{
|
|
62
|
+
wizard.state = 'channeling';
|
|
63
|
+
wizard.channelingSpell = 'shield';
|
|
64
|
+
wizard.channelDuration = 0;
|
|
65
|
+
}
|
|
66
|
+
else if (spell === 'missile')
|
|
67
|
+
{
|
|
68
|
+
wizard.state = 'gcd_locked';
|
|
69
|
+
wizard.gcdRemaining = GCD_DURATION;
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
// Blink: no GCD — can act immediately after teleporting
|
|
74
|
+
wizard.state = 'idle';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
delete wizard.castingSpell;
|
|
78
|
+
delete wizard.castProgress;
|
|
79
|
+
delete wizard.castDuration;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Update the shield channel state.
|
|
84
|
+
*/
|
|
85
|
+
export function updateShield(wizard: WizardState, deltaTicks: number): void
|
|
86
|
+
{
|
|
87
|
+
if (wizard.state === 'channeling' && wizard.channelingSpell === 'shield')
|
|
88
|
+
{
|
|
89
|
+
wizard.channelDuration = (wizard.channelDuration ?? 0) + deltaTicks;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculate the current block percentage of a shield based on channel duration.
|
|
95
|
+
*/
|
|
96
|
+
export function calculateShieldBlock(channelDurationTicks: number): number
|
|
97
|
+
{
|
|
98
|
+
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);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Apply damage to a wizard, considering shield mitigation.
|
|
105
|
+
*
|
|
106
|
+
* Shield blocks a percentage of damage based on channel duration:
|
|
107
|
+
* - Fresh shield (0s): 90% block → 10% damage through
|
|
108
|
+
* - Decayed shield: block % decreases over time (20%/sec)
|
|
109
|
+
* - Minimum: 30% block → 70% damage through
|
|
110
|
+
*
|
|
111
|
+
* (Design doc lines 196-212)
|
|
112
|
+
*/
|
|
113
|
+
export function applyDamage(wizard: WizardState, damage: number, _projectile?: ProjectileState): number
|
|
114
|
+
{
|
|
115
|
+
if (wizard.invincible) return 0;
|
|
116
|
+
|
|
117
|
+
let finalDamage = damage;
|
|
118
|
+
|
|
119
|
+
if (wizard.state === 'channeling' && wizard.channelingSpell === 'shield')
|
|
120
|
+
{
|
|
121
|
+
const blockPercent = calculateShieldBlock(wizard.channelDuration ?? 0);
|
|
122
|
+
// Simple block: damage through = damage * (1 - blockPercent)
|
|
123
|
+
finalDamage = damage * (1 - blockPercent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
wizard.health = Math.max(0, wizard.health - finalDamage);
|
|
127
|
+
return finalDamage;
|
|
128
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine version — milliseconds since the Unix epoch.
|
|
3
|
+
*
|
|
4
|
+
* Bumped automatically by `scripts/update-engine-version.mjs` on every build.
|
|
5
|
+
* The same value is propagated to `packages/functions/src/engine-version.generated.ts`
|
|
6
|
+
* so cloud functions and the core engine always agree on a single version per deploy.
|
|
7
|
+
*
|
|
8
|
+
* Used to gate spectator replays: a recorded match can only be re-simulated when
|
|
9
|
+
* the runtime engine version matches the version that produced the match.
|
|
10
|
+
*/
|
|
11
|
+
export const ENGINE_VERSION = 1777556890699;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIBEMANCER - ACTION BUILDERS
|
|
3
|
+
*
|
|
4
|
+
* Fluent API for constructing bot actions with type-safe chaining.
|
|
5
|
+
*
|
|
6
|
+
* UNITS REFERENCE (100 ticks = 1 second):
|
|
7
|
+
* Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
|
|
8
|
+
* Movement: direction vector, magnitude auto-normalized to max 100
|
|
9
|
+
* Speed: units per tick (player moves at 1 unit/tick = 100 units/sec)
|
|
10
|
+
* Duration: ticks (divide by 100 for seconds)
|
|
11
|
+
* Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
|
|
12
|
+
* Damage: raw HP removed on hit (wizard has 60 HP)
|
|
13
|
+
* Turn rate: degrees per tick the missile can rotate
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {WizardActions, MissileConfig, MissileAIFunction} from '../types.js';
|
|
17
|
+
import type {FinalAction, ActionBuilder} from './types.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a FinalAction (no further chaining possible).
|
|
21
|
+
*/
|
|
22
|
+
function createFinalAction(action: WizardActions): FinalAction
|
|
23
|
+
{
|
|
24
|
+
return {
|
|
25
|
+
_toAction: () => action,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create an ActionBuilder (can chain .move()).
|
|
31
|
+
*/
|
|
32
|
+
function createActionBuilder(baseAction: Omit<WizardActions, 'move'>): ActionBuilder
|
|
33
|
+
{
|
|
34
|
+
const action: WizardActions = {
|
|
35
|
+
...baseAction,
|
|
36
|
+
move: {x: 0, y: 0},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
_toAction: () => action,
|
|
41
|
+
move(x: number, y: number): FinalAction
|
|
42
|
+
{
|
|
43
|
+
return createFinalAction({
|
|
44
|
+
...action,
|
|
45
|
+
move: {x, y},
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================
|
|
52
|
+
// ACTION BUILDERS
|
|
53
|
+
// ============================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Channel a shield that blocks incoming damage.
|
|
57
|
+
*
|
|
58
|
+
* Starts at 90% block, decays by 20% per second, minimum 30%.
|
|
59
|
+
* Takes 20 ticks (0.2s) to activate. Movement is disabled while channeling.
|
|
60
|
+
* Cancel anytime with cancel(). Triggers 100-tick (1s) GCD after cancel.
|
|
61
|
+
*
|
|
62
|
+
* Can chain .move() — movement applies during the 20-tick cast, NOT during channel.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* return shield(); // shield and stay still
|
|
66
|
+
* return shield().move(100, 0); // move right while cast starts
|
|
67
|
+
*/
|
|
68
|
+
export function shield(): ActionBuilder
|
|
69
|
+
{
|
|
70
|
+
return createActionBuilder({
|
|
71
|
+
startCast: {spell: 'shield'},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Cast a missile spell.
|
|
77
|
+
*
|
|
78
|
+
* Cast time scales with damage, speed, duration, and turn rate — bigger missiles
|
|
79
|
+
* take longer to cast. While casting you move at 50% speed. After firing, 100-tick
|
|
80
|
+
* (1s) GCD before next spell.
|
|
81
|
+
*
|
|
82
|
+
* Repeated similar missiles cast 20% faster (warmup bonus). Switching styles
|
|
83
|
+
* incurs a 20% penalty.
|
|
84
|
+
*
|
|
85
|
+
* Can chain .move() for simultaneous movement while casting.
|
|
86
|
+
*
|
|
87
|
+
* @param config - Missile stats:
|
|
88
|
+
* - damage: HP removed on hit (1-60 typical). Also sets hitbox: radius = 2 + 0.1×damage.
|
|
89
|
+
* - speed: units/tick (min 1.5). Player moves at 1 u/t, so 5 = 5× player speed.
|
|
90
|
+
* - duration: ticks the missile lives (min 10). Range ≈ speed × duration.
|
|
91
|
+
* - turnRate: degrees/tick of homing (0 = straight line, 3 = moderate homing, 5+ = strong).
|
|
92
|
+
* Negative = no homing + minor speed cost reduction.
|
|
93
|
+
* @param ai - Called every tick to control missile steering. Receives:
|
|
94
|
+
* - missileState: the missile's position, rotation (degrees), speed, remainingTicks
|
|
95
|
+
* - worldState: full game state (all wizards, projectiles)
|
|
96
|
+
* - random(): seeded PRNG [0, 1)
|
|
97
|
+
* Return { turnToward: {x, y} } to home toward a position, or {} to fly straight.
|
|
98
|
+
* @param direction - Launch angle in degrees (0°=right, 90°=down, 180°=left, 270°=up).
|
|
99
|
+
* Tip: use Math.atan2(dy, dx) * (180 / Math.PI) to aim at a target.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Straight missile aimed at enemy
|
|
103
|
+
* const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
104
|
+
* return missile({damage: 15, speed: 6, duration: 200, turnRate: 0}, () => ({}), angle);
|
|
105
|
+
*
|
|
106
|
+
* // Homing missile that tracks enemy
|
|
107
|
+
* return missile(
|
|
108
|
+
* {damage: 10, speed: 5, duration: 300, turnRate: 3},
|
|
109
|
+
* ({worldState}) => ({turnToward: worldState.enemies[0]?.position}),
|
|
110
|
+
* angle,
|
|
111
|
+
* );
|
|
112
|
+
*/
|
|
113
|
+
export function missile(config: MissileConfig, ai: MissileAIFunction, direction: number): ActionBuilder
|
|
114
|
+
{
|
|
115
|
+
return createActionBuilder({
|
|
116
|
+
startCast: {
|
|
117
|
+
spell: 'missile',
|
|
118
|
+
config,
|
|
119
|
+
missileAI: ai,
|
|
120
|
+
direction,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Teleport to an absolute position on the arena.
|
|
127
|
+
*
|
|
128
|
+
* Max range: 300 units from current position (clamped by engine if further).
|
|
129
|
+
* Cast time: 10 ticks (0.1s). Cooldown scales with distance:
|
|
130
|
+
* - 100 units → 100 ticks (1s)
|
|
131
|
+
* - 300 units → 2000 ticks (20s)
|
|
132
|
+
*
|
|
133
|
+
* Cannot chain .move() — blink IS the movement.
|
|
134
|
+
*
|
|
135
|
+
* @param x - Target X position (0-800, absolute world coordinate)
|
|
136
|
+
* @param y - Target Y position (0-800, absolute world coordinate)
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* return blink(400, 400); // blink to center
|
|
140
|
+
* return blink(enemy.position.x, enemy.position.y); // blink to enemy
|
|
141
|
+
*/
|
|
142
|
+
export function blink(x: number, y: number): FinalAction
|
|
143
|
+
{
|
|
144
|
+
return createFinalAction({
|
|
145
|
+
move: {x: 0, y: 0}, // No manual movement with blink
|
|
146
|
+
startCast: {
|
|
147
|
+
spell: 'blink',
|
|
148
|
+
target: {x, y},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Cancel current cast or channel (e.g. stop shielding to attack).
|
|
155
|
+
*
|
|
156
|
+
* Canceling a cast/channel triggers 100-tick (1s) GCD.
|
|
157
|
+
* Can chain .move() for simultaneous movement.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* return cancel().move(-100, 0); // cancel and dodge left
|
|
161
|
+
*/
|
|
162
|
+
export function cancel(): ActionBuilder
|
|
163
|
+
{
|
|
164
|
+
return createActionBuilder({
|
|
165
|
+
cancel: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Move in a direction without casting any spell.
|
|
171
|
+
*
|
|
172
|
+
* This is a **direction vector**, not a target position. The engine normalizes
|
|
173
|
+
* the magnitude to max 100, then moves at 1 unit/tick (100 units/sec).
|
|
174
|
+
* Positive X = right, positive Y = down.
|
|
175
|
+
*
|
|
176
|
+
* To move toward a target position, subtract your position:
|
|
177
|
+
* move(target.x - myPos.x, target.y - myPos.y)
|
|
178
|
+
*
|
|
179
|
+
* @param x - Horizontal direction (positive = right, negative = left)
|
|
180
|
+
* @param y - Vertical direction (positive = down, negative = up)
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* return move(100, 0); // move right
|
|
184
|
+
* return move(enemy.position.x - myPos.x, enemy.position.y - myPos.y); // move toward enemy
|
|
185
|
+
*/
|
|
186
|
+
export function move(x: number, y: number): FinalAction
|
|
187
|
+
{
|
|
188
|
+
return createFinalAction({
|
|
189
|
+
move: {x, y},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Do nothing — no action, no movement.
|
|
195
|
+
*/
|
|
196
|
+
export function idle(): FinalAction
|
|
197
|
+
{
|
|
198
|
+
return createFinalAction({
|
|
199
|
+
move: {x: 0, y: 0},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract WizardActions from a FinalAction.
|
|
205
|
+
* Used by the engine to get the actual action.
|
|
206
|
+
*/
|
|
207
|
+
export function extractAction(finalAction: FinalAction): WizardActions
|
|
208
|
+
{
|
|
209
|
+
return finalAction._toAction();
|
|
210
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIBEMANCER - BOT WRAPPER
|
|
3
|
+
*
|
|
4
|
+
* Utilities to convert between old-style (WizardFunction) and new-style (BotFunction) bots.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {WizardFunction, WizardActions, GameState, GameConfig} from '../types.js';
|
|
8
|
+
import {withBotContext} from '../engine/hooks-runtime.js';
|
|
9
|
+
import {extractAction} from './action-builders.js';
|
|
10
|
+
import type {BotFunction, BotContext} from './types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert a new-style bot (using hooks) to an old-style bot (WizardFunction).
|
|
14
|
+
*
|
|
15
|
+
* This allows new bots to work with the existing simulation.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const NewBot: BotFunction = () => {
|
|
19
|
+
* const health = useHealth();
|
|
20
|
+
* return health < 10 ? shield() : idle();
|
|
21
|
+
* };
|
|
22
|
+
*
|
|
23
|
+
* // Convert to work with simulate()
|
|
24
|
+
* const oldStyleBot = wrapNewBot(NewBot);
|
|
25
|
+
* simulate(oldStyleBot, opponent);
|
|
26
|
+
*/
|
|
27
|
+
export function wrapNewBot(newBot: BotFunction): WizardFunction
|
|
28
|
+
{
|
|
29
|
+
return ({state, config}: {state: GameState; config: GameConfig; random: () => number}): WizardActions =>
|
|
30
|
+
{
|
|
31
|
+
// Create the BotContext from GameState
|
|
32
|
+
// Note: entityId is inherited from the simulation's outer runWithHooks call
|
|
33
|
+
const botContext: BotContext = {
|
|
34
|
+
entityId: 'unused', // Not used - entity ID comes from simulation
|
|
35
|
+
tick: state.tick,
|
|
36
|
+
position: state.position,
|
|
37
|
+
velocity: state.velocity,
|
|
38
|
+
health: state.health,
|
|
39
|
+
maxHealth: state.maxHealth,
|
|
40
|
+
state: state.state,
|
|
41
|
+
castingSpell: state.castingSpell,
|
|
42
|
+
castProgress: state.castProgress,
|
|
43
|
+
castDuration: state.castDuration,
|
|
44
|
+
channelingSpell: state.channelingSpell,
|
|
45
|
+
channelDuration: state.channelDuration,
|
|
46
|
+
gcdRemaining: state.gcdRemaining,
|
|
47
|
+
blinkCooldown: state.blinkCooldown,
|
|
48
|
+
enemies: state.enemies.map((e) => ({
|
|
49
|
+
id: e.id,
|
|
50
|
+
position: e.position,
|
|
51
|
+
velocity: e.velocity,
|
|
52
|
+
health: e.health,
|
|
53
|
+
state: e.state,
|
|
54
|
+
castingSpell: e.castingSpell,
|
|
55
|
+
channelingSpell: e.channelingSpell,
|
|
56
|
+
channelDuration: e.channelDuration,
|
|
57
|
+
})),
|
|
58
|
+
projectiles: state.projectiles,
|
|
59
|
+
myProjectiles: state.myProjectiles,
|
|
60
|
+
arenaWidth: config.arenaSize.width,
|
|
61
|
+
arenaHeight: config.arenaSize.height,
|
|
62
|
+
damageDealt: state.damageDealt,
|
|
63
|
+
damageTaken: state.damageTaken,
|
|
64
|
+
lastHitTick: state.lastHitTick,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Run the new-style bot with context
|
|
68
|
+
// Use withBotContext (not runBotWithContext) to preserve the entity ID
|
|
69
|
+
// set by the simulation's outer runWithHooks call
|
|
70
|
+
const finalAction = withBotContext(botContext, newBot);
|
|
71
|
+
|
|
72
|
+
// Extract WizardActions from FinalAction
|
|
73
|
+
return extractAction(finalAction);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Type guard to check if a bot is a new-style BotFunction.
|
|
79
|
+
* New-style bots have 0 parameters, old-style bots have at least 1.
|
|
80
|
+
*/
|
|
81
|
+
export function isNewStyleBot(bot: WizardFunction | BotFunction): bot is BotFunction
|
|
82
|
+
{
|
|
83
|
+
return bot.length === 0;
|
|
84
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIBEMANCER - HOOKS API
|
|
3
|
+
*
|
|
4
|
+
* Main export file for the hooks-based bot API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
EnemyState,
|
|
10
|
+
AnalyzedThreat,
|
|
11
|
+
FinalAction,
|
|
12
|
+
ActionBuilder,
|
|
13
|
+
BotFunction,
|
|
14
|
+
BotContext,
|
|
15
|
+
MissileConfig,
|
|
16
|
+
MissileAIFunction,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
|
|
19
|
+
// State hooks
|
|
20
|
+
export {
|
|
21
|
+
useHealth,
|
|
22
|
+
usePosition,
|
|
23
|
+
useVelocity,
|
|
24
|
+
useStatus,
|
|
25
|
+
useTicksUntilReady,
|
|
26
|
+
useShieldStrength,
|
|
27
|
+
useBlinkCooldown,
|
|
28
|
+
useCastingSpell,
|
|
29
|
+
useCastProgress,
|
|
30
|
+
useEnemy,
|
|
31
|
+
useMyProjectiles,
|
|
32
|
+
useDamageDealt,
|
|
33
|
+
useDamageTaken,
|
|
34
|
+
useLastHitTick,
|
|
35
|
+
useArenaSize,
|
|
36
|
+
useTick,
|
|
37
|
+
useThreats,
|
|
38
|
+
useClosestThreat,
|
|
39
|
+
useMyThreatsToEnemy,
|
|
40
|
+
} from './state-hooks.js';
|
|
41
|
+
|
|
42
|
+
// Threat analysis (for advanced users who want direct access)
|
|
43
|
+
export {analyzeThreats} from './threat-analysis.js';
|
|
44
|
+
|
|
45
|
+
// Action builders
|
|
46
|
+
export {
|
|
47
|
+
shield,
|
|
48
|
+
missile,
|
|
49
|
+
blink,
|
|
50
|
+
cancel,
|
|
51
|
+
move,
|
|
52
|
+
idle,
|
|
53
|
+
extractAction,
|
|
54
|
+
} from './action-builders.js';
|
|
55
|
+
|
|
56
|
+
// Re-export persistence hooks from runtime
|
|
57
|
+
export {
|
|
58
|
+
useState,
|
|
59
|
+
useEffect,
|
|
60
|
+
useMemo,
|
|
61
|
+
useRef,
|
|
62
|
+
runBotWithContext,
|
|
63
|
+
withBotContext,
|
|
64
|
+
getBotContext,
|
|
65
|
+
} from '../engine/hooks-runtime.js';
|
|
66
|
+
|
|
67
|
+
// Re-export RefObject type
|
|
68
|
+
export type {RefObject} from '../engine/hooks-runtime.js';
|
|
69
|
+
|
|
70
|
+
// Bot wrapper for integration
|
|
71
|
+
export {wrapNewBot, isNewStyleBot} from './bot-wrapper.js';
|
|
72
|
+
|
|
73
|
+
// Parameter tuning hook
|
|
74
|
+
export {useParam} from '../engine/params-runtime.js';
|
|
75
|
+
export type {ParamDeclaration} from '../engine/params-runtime.js';
|