@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,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {FinalAction, MissileFunction} from '../../hooks/index.js';
|
|
2
|
+
import {usePosition, useEnemy, useStatus, useBlinkCooldown, useHealth, useThreats, useLastMissileConfig, useCastingSpell, useCastProgress, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
|
|
2
3
|
import {distanceTo} from '../../utils/distance.js';
|
|
3
4
|
import {angleTo} from '../../utils/angles.js';
|
|
4
|
-
import {
|
|
5
|
-
GCD_DURATION,
|
|
6
|
-
SHIELD_CAST_TIME,
|
|
7
|
-
BLINK_CAST_TIME,
|
|
8
|
-
} from '../../rules.js';
|
|
5
|
+
import {RULES} from '../../rules.js';
|
|
9
6
|
import {getMissileCastTime, getLeadPosition} from '../../utils/combat.js';
|
|
10
7
|
import {
|
|
11
|
-
getThreats,
|
|
12
8
|
getMostUrgentThreat,
|
|
13
9
|
getBlinkToCenter,
|
|
14
10
|
fitMissileToBudget,
|
|
@@ -16,6 +12,8 @@ import {
|
|
|
16
12
|
MAX_CAST_BUDGET,
|
|
17
13
|
MIN_USEFUL_DAMAGE,
|
|
18
14
|
getEnemyVulnerabilityTicks,
|
|
15
|
+
keepOffWalls,
|
|
16
|
+
getLavaEvade,
|
|
19
17
|
} from '../shared.js';
|
|
20
18
|
import {useParam} from '../../engine/params-runtime.js';
|
|
21
19
|
|
|
@@ -25,16 +23,17 @@ const SHIELD_HOLD_TICKS = 5;
|
|
|
25
23
|
/**
|
|
26
24
|
* Standard homing missile AI — tracks enemy position.
|
|
27
25
|
*/
|
|
28
|
-
const HomingAI:
|
|
26
|
+
const HomingAI: MissileFunction = () =>
|
|
29
27
|
{
|
|
30
|
-
const
|
|
31
|
-
|
|
28
|
+
const ctx = getMissileContext();
|
|
29
|
+
const enemy = ctx.worldState.enemies[0];
|
|
30
|
+
return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* Straight missile AI — no homing, flies in initial direction.
|
|
36
35
|
*/
|
|
37
|
-
const StraightAI:
|
|
36
|
+
const StraightAI: MissileFunction = () => flyStraight();
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
39
|
* Bot: Golem
|
|
@@ -54,12 +53,23 @@ const StraightAI: MissileAIFunction = () => ({});
|
|
|
54
53
|
* PROGRESSION LINE: Turtle → Sentinel → Golem
|
|
55
54
|
* TIER: 3 (elite Defensive line)
|
|
56
55
|
*/
|
|
57
|
-
export
|
|
56
|
+
export function Golem(): FinalAction
|
|
58
57
|
{
|
|
58
|
+
const position = usePosition();
|
|
59
|
+
const enemy = useEnemy();
|
|
60
|
+
const status = useStatus();
|
|
61
|
+
const blinkCooldown = useBlinkCooldown();
|
|
62
|
+
const health = useHealth();
|
|
63
|
+
const threats = useThreats();
|
|
64
|
+
const lastMissileConfig = useLastMissileConfig();
|
|
65
|
+
const castingSpell = useCastingSpell();
|
|
66
|
+
const castProgressData = useCastProgress();
|
|
67
|
+
|
|
68
|
+
|
|
59
69
|
// Positive turn rate ensures homing missiles that hit moving targets.
|
|
60
70
|
// Hardcoded — optimizer incorrectly pushes this negative for stationary matchups.
|
|
61
71
|
const minTurnRate = 0.5;
|
|
62
|
-
const preemptiveShieldRange = useParam('preemptiveShieldRange',
|
|
72
|
+
const preemptiveShieldRange = useParam('preemptiveShieldRange', 65, {
|
|
63
73
|
range: 85,
|
|
64
74
|
min: 0,
|
|
65
75
|
steps: 7,
|
|
@@ -68,44 +78,44 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
68
78
|
range: 75,
|
|
69
79
|
min: 10,
|
|
70
80
|
});
|
|
71
|
-
const punishMinVulnerability = useParam('punishMinVulnerability',
|
|
81
|
+
const punishMinVulnerability = useParam('punishMinVulnerability', 293, {
|
|
72
82
|
range: 30,
|
|
73
83
|
min: 10,
|
|
74
84
|
steps: 5,
|
|
75
85
|
});
|
|
76
86
|
const preferBlinkDodge =
|
|
77
87
|
useParam('preferBlinkDodge', 1, {min: 0, max: 1, steps: 2}) >= 0.5;
|
|
78
|
-
const cancelHeavyDmg = useParam('cancelHeavyDmg',
|
|
88
|
+
const cancelHeavyDmg = useParam('cancelHeavyDmg', 26, {
|
|
79
89
|
range: 15,
|
|
80
90
|
min: 5,
|
|
81
91
|
max: 40,
|
|
82
92
|
steps: 5,
|
|
83
93
|
});
|
|
84
|
-
const cancelMediumDmg = useParam('cancelMediumDmg',
|
|
94
|
+
const cancelMediumDmg = useParam('cancelMediumDmg', 21, {
|
|
85
95
|
range: 10,
|
|
86
96
|
min: 3,
|
|
87
97
|
max: 25,
|
|
88
98
|
steps: 5,
|
|
89
99
|
});
|
|
90
|
-
const cancelCastRatio = useParam('cancelCastRatio',
|
|
100
|
+
const cancelCastRatio = useParam('cancelCastRatio', 1, {
|
|
91
101
|
range: 0.4,
|
|
92
102
|
min: 0.3,
|
|
93
103
|
max: 1.0,
|
|
94
104
|
steps: 5,
|
|
95
105
|
});
|
|
96
|
-
const cancelLightDmg = useParam('cancelLightDmg',
|
|
106
|
+
const cancelLightDmg = useParam('cancelLightDmg', 1, {
|
|
97
107
|
range: 5,
|
|
98
108
|
min: 1,
|
|
99
109
|
max: 15,
|
|
100
110
|
steps: 5,
|
|
101
111
|
});
|
|
102
|
-
const cancelEarlyCastRatio = useParam('cancelEarlyCastRatio', 0.
|
|
112
|
+
const cancelEarlyCastRatio = useParam('cancelEarlyCastRatio', 0.66, {
|
|
103
113
|
range: 0.3,
|
|
104
114
|
min: 0.0,
|
|
105
115
|
max: 0.7,
|
|
106
116
|
steps: 5,
|
|
107
117
|
});
|
|
108
|
-
const blinkWindowMax = useParam('blinkWindowMax',
|
|
118
|
+
const blinkWindowMax = useParam('blinkWindowMax', 52, {
|
|
109
119
|
range: 40,
|
|
110
120
|
min: 20,
|
|
111
121
|
max: 120,
|
|
@@ -117,82 +127,85 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
117
127
|
max: 15,
|
|
118
128
|
steps: 5,
|
|
119
129
|
});
|
|
120
|
-
const flightTimeDivisor = useParam('flightTimeDivisor',
|
|
130
|
+
const flightTimeDivisor = useParam('flightTimeDivisor', 3.1, {
|
|
121
131
|
range: 4,
|
|
122
132
|
min: 2,
|
|
123
133
|
max: 12,
|
|
124
134
|
steps: 5,
|
|
125
135
|
});
|
|
126
136
|
|
|
127
|
-
const enemy = state.enemies[0];
|
|
128
|
-
const threats = getThreats(state);
|
|
129
137
|
const urgentThreat = getMostUrgentThreat(threats);
|
|
130
138
|
const hittingThreats = threats.filter((t) => t.willHit);
|
|
131
139
|
|
|
140
|
+
// Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
|
|
141
|
+
// shielded — knockback ignores shields. Highest priority.
|
|
142
|
+
const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
|
|
143
|
+
if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
|
|
144
|
+
|
|
132
145
|
// === STEP 1: CHANNELING — hold through volleys, cancel when safe ===
|
|
133
|
-
if (
|
|
146
|
+
if (status === 'channeling' && 'shield' === 'shield')
|
|
134
147
|
{
|
|
135
148
|
// Hold if any missile is about to hit
|
|
136
149
|
const imminentThreat = hittingThreats.find(
|
|
137
150
|
(t) => t.ticksToImpact <= SHIELD_HOLD_TICKS,
|
|
138
151
|
);
|
|
139
|
-
if (imminentThreat)
|
|
152
|
+
if (imminentThreat)
|
|
140
153
|
{
|
|
141
|
-
return
|
|
154
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
// Hold if next threat will arrive before we can reshield
|
|
145
158
|
const nextHitting = hittingThreats[0];
|
|
146
|
-
const ticksToReshield = SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
147
|
-
if (nextHitting && nextHitting.ticksToImpact <= ticksToReshield)
|
|
159
|
+
const ticksToReshield = RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
160
|
+
if (nextHitting && nextHitting.ticksToImpact <= ticksToReshield)
|
|
148
161
|
{
|
|
149
|
-
return
|
|
162
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
// Cancel shield if no threat or threat is far enough
|
|
153
|
-
if (!nextHitting || nextHitting.ticksToImpact > shieldCancelThreshold)
|
|
166
|
+
if (!nextHitting || nextHitting.ticksToImpact > shieldCancelThreshold)
|
|
154
167
|
{
|
|
155
|
-
return
|
|
168
|
+
return cancel();
|
|
156
169
|
}
|
|
157
|
-
return
|
|
170
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
// If casting shield, let it finish
|
|
161
|
-
if (
|
|
174
|
+
if (status === 'casting' && castingSpell === 'shield')
|
|
162
175
|
{
|
|
163
|
-
return
|
|
176
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
// === STEP 2: PROGRESSIVE CAST-CANCEL — evaluate damage tradeoff ===
|
|
167
180
|
if (
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
status === 'casting' &&
|
|
182
|
+
castingSpell === 'missile' &&
|
|
170
183
|
urgentThreat
|
|
171
184
|
)
|
|
172
185
|
{
|
|
173
186
|
// Sentinel is stationary — can't dodge. Must always consider canceling.
|
|
174
|
-
const remainingCast = (
|
|
187
|
+
const remainingCast = ((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
|
|
175
188
|
const defenseTime =
|
|
176
|
-
|
|
177
|
-
? BLINK_CAST_TIME
|
|
178
|
-
: SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
189
|
+
blinkCooldown === 0
|
|
190
|
+
? RULES.BLINK_CAST_TIME
|
|
191
|
+
: RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
179
192
|
const willArriveInWindow =
|
|
180
|
-
urgentThreat.ticksToImpact <= remainingCast + GCD_DURATION + defenseTime;
|
|
193
|
+
urgentThreat.ticksToImpact <= remainingCast + RULES.GCD_DURATION + defenseTime;
|
|
181
194
|
|
|
182
195
|
if (willArriveInWindow)
|
|
183
196
|
{
|
|
184
197
|
const incomingDamage = urgentThreat.projectile.damage;
|
|
185
|
-
const castRatio = (
|
|
198
|
+
const castRatio = ((castProgressData?.current) ?? 0) / ((castProgressData?.total) ?? 1);
|
|
186
199
|
|
|
187
200
|
// Always cancel for lethal or heavy damage
|
|
188
|
-
if (incomingDamage >=
|
|
201
|
+
if (incomingDamage >= health || incomingDamage >= cancelHeavyDmg)
|
|
189
202
|
{
|
|
190
|
-
return
|
|
203
|
+
return cancel();
|
|
191
204
|
}
|
|
192
205
|
// Significant damage + early/mid cast: cancel
|
|
193
206
|
if (incomingDamage >= cancelMediumDmg && castRatio < cancelCastRatio)
|
|
194
207
|
{
|
|
195
|
-
return
|
|
208
|
+
return cancel();
|
|
196
209
|
}
|
|
197
210
|
// Medium damage + very early cast: cancel
|
|
198
211
|
if (
|
|
@@ -200,7 +213,7 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
200
213
|
castRatio < cancelEarlyCastRatio
|
|
201
214
|
)
|
|
202
215
|
{
|
|
203
|
-
return
|
|
216
|
+
return cancel();
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
}
|
|
@@ -208,42 +221,36 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
208
221
|
// === STEP 3: BLINK-DODGE — 100% damage avoidance (optimizer-controlled) ===
|
|
209
222
|
if (
|
|
210
223
|
preferBlinkDodge &&
|
|
211
|
-
|
|
224
|
+
status === 'idle' &&
|
|
212
225
|
urgentThreat &&
|
|
213
|
-
|
|
214
|
-
urgentThreat.ticksToImpact >= BLINK_CAST_TIME &&
|
|
226
|
+
blinkCooldown === 0 &&
|
|
227
|
+
urgentThreat.ticksToImpact >= RULES.BLINK_CAST_TIME &&
|
|
215
228
|
urgentThreat.ticksToImpact <= blinkWindowMax
|
|
216
229
|
)
|
|
217
230
|
{
|
|
218
|
-
return
|
|
219
|
-
move: {x: 0, y: 0},
|
|
220
|
-
startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
|
|
221
|
-
};
|
|
231
|
+
return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
// === STEP 3b: SHIELD — fallback when blink unavailable (90% block) ===
|
|
225
235
|
if (
|
|
226
|
-
|
|
236
|
+
status === 'idle' &&
|
|
227
237
|
urgentThreat &&
|
|
228
238
|
urgentThreat.canBlockInTime &&
|
|
229
239
|
urgentThreat.ticksToStartShield <= SHIELD_BUFFER
|
|
230
240
|
)
|
|
231
241
|
{
|
|
232
|
-
return
|
|
233
|
-
move: {x: 0, y: 0},
|
|
234
|
-
startCast: {spell: 'shield'},
|
|
235
|
-
};
|
|
242
|
+
return shield();
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
// === STEP 4: PREEMPTIVE SHIELD — enemy casting at close range ===
|
|
239
246
|
if (
|
|
240
|
-
|
|
247
|
+
status === 'idle' &&
|
|
241
248
|
enemy &&
|
|
242
|
-
enemy.
|
|
249
|
+
enemy.status === 'casting' &&
|
|
243
250
|
enemy.castingSpell === 'missile'
|
|
244
251
|
)
|
|
245
252
|
{
|
|
246
|
-
const distance = distanceTo(
|
|
253
|
+
const distance = distanceTo(position, enemy.position);
|
|
247
254
|
if (distance < preemptiveShieldRange)
|
|
248
255
|
{
|
|
249
256
|
const remainingCast =
|
|
@@ -252,25 +259,22 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
252
259
|
const ticksUntilHit = remainingCast + estimatedFlightTicks;
|
|
253
260
|
if (
|
|
254
261
|
ticksUntilHit <
|
|
255
|
-
SHIELD_CAST_TIME + SHIELD_BUFFER + preemptiveShieldBuffer
|
|
262
|
+
RULES.SHIELD_CAST_TIME + SHIELD_BUFFER + preemptiveShieldBuffer
|
|
256
263
|
)
|
|
257
264
|
{
|
|
258
|
-
return
|
|
259
|
-
move: {x: 0, y: 0},
|
|
260
|
-
startCast: {spell: 'shield'},
|
|
261
|
-
};
|
|
265
|
+
return shield();
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
// === STEP 5: OFFENSE — punish mode + standard counterattack ===
|
|
267
|
-
if (
|
|
271
|
+
if (status === 'idle' && enemy)
|
|
268
272
|
{
|
|
269
273
|
const offenseDefenseTime =
|
|
270
|
-
|
|
271
|
-
? BLINK_CAST_TIME
|
|
272
|
-
: SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
273
|
-
const distance = distanceTo(
|
|
274
|
+
blinkCooldown === 0
|
|
275
|
+
? RULES.BLINK_CAST_TIME
|
|
276
|
+
: RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
|
|
277
|
+
const distance = distanceTo(position, enemy.position);
|
|
274
278
|
const enemyHP = Math.ceil(enemy.health);
|
|
275
279
|
const vulnerabilityTicks = getEnemyVulnerabilityTicks(enemy);
|
|
276
280
|
|
|
@@ -280,7 +284,7 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
280
284
|
const flightTimeEstimate = distance / flightTimeDivisor;
|
|
281
285
|
const punishBudget = vulnerabilityTicks - flightTimeEstimate;
|
|
282
286
|
const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
|
|
283
|
-
const safeBudget = nextThreatTime - GCD_DURATION - offenseDefenseTime;
|
|
287
|
+
const safeBudget = nextThreatTime - RULES.GCD_DURATION - offenseDefenseTime;
|
|
284
288
|
const effectiveBudget = Math.min(
|
|
285
289
|
punishBudget,
|
|
286
290
|
safeBudget,
|
|
@@ -292,12 +296,12 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
292
296
|
const config = fitMissileToBudget(effectiveBudget, distance, {
|
|
293
297
|
minTurnRate: 0,
|
|
294
298
|
maxDamage: enemyHP,
|
|
295
|
-
lastMissileConfig:
|
|
299
|
+
lastMissileConfig: lastMissileConfig,
|
|
296
300
|
});
|
|
297
301
|
|
|
298
302
|
if (config && config.damage >= MIN_USEFUL_DAMAGE)
|
|
299
303
|
{
|
|
300
|
-
const castTime = getMissileCastTime(config,
|
|
304
|
+
const castTime = getMissileCastTime(config, lastMissileConfig);
|
|
301
305
|
const actualFlight = distance / config.speed;
|
|
302
306
|
|
|
303
307
|
if (
|
|
@@ -312,17 +316,9 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
312
316
|
enemy.position,
|
|
313
317
|
enemy.velocity,
|
|
314
318
|
config.speed,
|
|
315
|
-
|
|
319
|
+
position,
|
|
316
320
|
);
|
|
317
|
-
return
|
|
318
|
-
move: {x: 0, y: 0},
|
|
319
|
-
startCast: {
|
|
320
|
-
spell: 'missile',
|
|
321
|
-
config,
|
|
322
|
-
missileAI: config.turnRate > 0 ? HomingAI : StraightAI,
|
|
323
|
-
direction: angleTo(state.position, aimTarget),
|
|
324
|
-
},
|
|
325
|
-
};
|
|
321
|
+
return missile(config, config.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, aimTarget)).move(keepOffWalls(position).x, keepOffWalls(position).y);
|
|
326
322
|
}
|
|
327
323
|
}
|
|
328
324
|
}
|
|
@@ -330,28 +326,20 @@ export const Golem: WizardFunction = ({state}) =>
|
|
|
330
326
|
|
|
331
327
|
// --- STANDARD MODE: adaptive homing in safe windows ---
|
|
332
328
|
const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
|
|
333
|
-
const safeBudget = nextThreatTime - GCD_DURATION - offenseDefenseTime;
|
|
329
|
+
const safeBudget = nextThreatTime - RULES.GCD_DURATION - offenseDefenseTime;
|
|
334
330
|
const budgetTicks = Math.min(safeBudget, MAX_CAST_BUDGET);
|
|
335
331
|
|
|
336
332
|
const config = fitMissileToBudget(budgetTicks, distance, {
|
|
337
333
|
minTurnRate,
|
|
338
334
|
maxDamage: enemyHP,
|
|
339
|
-
lastMissileConfig:
|
|
335
|
+
lastMissileConfig: lastMissileConfig,
|
|
340
336
|
});
|
|
341
337
|
|
|
342
338
|
if (config && config.damage >= Math.min(MIN_USEFUL_DAMAGE, enemyHP))
|
|
343
339
|
{
|
|
344
|
-
return
|
|
345
|
-
move: {x: 0, y: 0},
|
|
346
|
-
startCast: {
|
|
347
|
-
spell: 'missile',
|
|
348
|
-
config,
|
|
349
|
-
missileAI: config.turnRate > 0 ? HomingAI : StraightAI,
|
|
350
|
-
direction: angleTo(state.position, enemy.position),
|
|
351
|
-
},
|
|
352
|
-
};
|
|
340
|
+
return missile(config, config.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
|
|
353
341
|
}
|
|
354
342
|
}
|
|
355
343
|
|
|
356
|
-
return
|
|
357
|
-
}
|
|
344
|
+
{ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
|
|
345
|
+
}
|