@vibemancer/core 0.1.0 → 0.1.1
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 -28
- package/dist/{chunk-L7Z7OFXD.js → chunk-7VDJHO22.js} +3 -3
- package/dist/chunk-7VDJHO22.js.map +1 -0
- package/dist/index-browser.d.ts +1 -1
- package/dist/index-browser.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +79 -78
- package/src/bots/berserker/01_Stormchaser.ts +457 -457
- package/src/bots/berserker/02_Stormcaller.ts +417 -417
- package/src/bots/berserker/03_Stormforger.ts +481 -481
- package/src/bots/caster/01_Flamecaller.ts +286 -286
- package/src/bots/caster/02_Pyromancer.ts +350 -350
- package/src/bots/caster/03_Infernalist.ts +492 -492
- package/src/bots/defensive/01_Turtle.ts +151 -151
- package/src/bots/defensive/02_Sentinel.ts +134 -134
- package/src/bots/defensive/03_Golem.ts +357 -357
- package/src/bots/duelist/01_Battlemage.ts +433 -433
- package/src/bots/duelist/02_Warmage.ts +438 -438
- package/src/bots/duelist/03_Archmage.ts +588 -588
- package/src/bots/homing/01_Bonemancer.ts +67 -67
- package/src/bots/homing/02_Lich.ts +356 -356
- package/src/bots/homing/03_Archlich.ts +220 -220
- package/src/bots/kiter/01_Spellspinner.ts +398 -398
- package/src/bots/kiter/02_Spellweaver.ts +378 -378
- package/src/bots/kiter/03_Spellbinder.ts +448 -448
- package/src/bots/melee/01_Shadowblade.ts +270 -270
- package/src/bots/melee/02_Nightblade.ts +437 -437
- package/src/bots/melee/03_Voidblade.ts +582 -582
- package/src/bots/sniper/01_Spellshot.ts +385 -385
- package/src/bots/sniper/02_Spelltracer.ts +441 -441
- package/src/bots/sniper/03_Spellseeker.ts +546 -546
- package/src/bots/standalone/Critter.ts +89 -89
- package/src/bots/standalone/Doombringer.ts +91 -91
- package/src/bots/standalone/Hogger.ts +228 -228
- package/src/bots/standalone/Rookie.ts +50 -50
- package/src/bots/standalone/TargetDummy.ts +21 -21
- package/src/bots/test/cheater.ts +405 -405
- package/src/bots/test/crasher.ts +81 -81
- package/src/engine/hooks-runtime.ts +394 -394
- package/src/engine/manual-match.ts +289 -289
- package/src/engine/missile-templates.ts +155 -155
- package/src/engine/physics.ts +143 -143
- package/src/engine/simulation.ts +828 -828
- package/src/engine/spells.ts +128 -128
- package/src/engine-version.ts +1 -1
- package/src/index.ts +23 -23
- package/src/rules.ts +254 -254
- package/src/types.ts +193 -193
- package/src/utils/index.ts +6 -6
- package/dist/chunk-L7Z7OFXD.js.map +0 -1
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
import {WizardFunction, MissileAIFunction} from '../../types.js';
|
|
2
|
-
import {angleTo} from '../../utils/angles.js';
|
|
3
|
-
import {distanceTo} from '../../utils/distance.js';
|
|
4
|
-
import {ARENA_SIZE} from '../../rules.js';
|
|
5
|
-
import {
|
|
6
|
-
getThreats,
|
|
7
|
-
getMostUrgentThreat,
|
|
8
|
-
getDodgeDirectionForMovement,
|
|
9
|
-
getBlinkToCenter,
|
|
10
|
-
} from '../shared.js';
|
|
11
|
-
const WALL_BUFFER = 60;
|
|
12
|
-
const WALL_PUSH = 30; // How hard to push away from walls
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Homing AI that sometimes forgets what it's doing.
|
|
16
|
-
*/
|
|
17
|
-
const ChaoticHoming: MissileAIFunction = ({worldState, random}) =>
|
|
18
|
-
{
|
|
19
|
-
const enemy = worldState.enemies[0];
|
|
20
|
-
// 80% chance to actually track, 20% chance to just fly straight
|
|
21
|
-
if (random() < 0.8)
|
|
22
|
-
{
|
|
23
|
-
return {turnToward: enemy?.position};
|
|
24
|
-
}
|
|
25
|
-
return {};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Bot: Hogger
|
|
30
|
-
*
|
|
31
|
-
* BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
|
|
32
|
-
* Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
|
|
33
|
-
* moves erratically but still somewhat toward/away from the enemy, shields when
|
|
34
|
-
* in real danger, and blinks unpredictably. The randomness makes Hogger hard to
|
|
35
|
-
* predict — you never know if the next missile will be a slow tracker or a fast
|
|
36
|
-
* snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
|
|
37
|
-
* to actually win fights.
|
|
38
|
-
*
|
|
39
|
-
* NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
|
|
40
|
-
* Forest who infamously kills unprepared lowbies. He's technically a basic mob
|
|
41
|
-
* but hits way harder than expected. This bot is the Critter that learned to
|
|
42
|
-
* fight — still chaotic, still a bit dumb, but capable of ending you if you
|
|
43
|
-
* underestimate it. "Hogger" is one of WoW's most recognizable references and
|
|
44
|
-
* perfectly captures "deceptively dangerous chaos."
|
|
45
|
-
*
|
|
46
|
-
* STANDALONE — no tier progression. There's only one Hogger.
|
|
47
|
-
*/
|
|
48
|
-
export const Hogger: WizardFunction = ({state, random}) =>
|
|
49
|
-
{
|
|
50
|
-
const enemy = state.enemies[0];
|
|
51
|
-
if (!enemy)
|
|
52
|
-
{
|
|
53
|
-
return {move: {x: 0, y: 0}};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const distance = distanceTo(state.position, enemy.position);
|
|
57
|
-
const threats = getThreats(state);
|
|
58
|
-
const urgentThreat = getMostUrgentThreat(threats);
|
|
59
|
-
|
|
60
|
-
// Erratic movement: mostly toward/away from enemy with random strafe
|
|
61
|
-
const deltaX = enemy.position.x - state.position.x;
|
|
62
|
-
const deltaY = enemy.position.y - state.position.y;
|
|
63
|
-
const normalizedDistance = distance > 0 ? distance : 1;
|
|
64
|
-
|
|
65
|
-
// Dodge if there's a real threat
|
|
66
|
-
const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
|
|
67
|
-
|
|
68
|
-
let moveX: number;
|
|
69
|
-
let moveY: number;
|
|
70
|
-
|
|
71
|
-
if (dodgeDirection)
|
|
72
|
-
{
|
|
73
|
-
// Actually dodge (Hogger isn't stupid)
|
|
74
|
-
moveX = dodgeDirection.x * 100;
|
|
75
|
-
moveY = dodgeDirection.y * 100;
|
|
76
|
-
}
|
|
77
|
-
else
|
|
78
|
-
{
|
|
79
|
-
// Random strafe component
|
|
80
|
-
const perpX = -deltaY / normalizedDistance;
|
|
81
|
-
const perpY = deltaX / normalizedDistance;
|
|
82
|
-
const strafeIntensity = random() * 80 - 40; // -40 to 40
|
|
83
|
-
|
|
84
|
-
// Approach/retreat: prefer medium range (250-400), but sometimes just wander
|
|
85
|
-
let approachIntensity = 0;
|
|
86
|
-
if (distance > 400)
|
|
87
|
-
{
|
|
88
|
-
approachIntensity = 40 + random() * 30; // approach
|
|
89
|
-
}
|
|
90
|
-
else if (distance < 200)
|
|
91
|
-
{
|
|
92
|
-
approachIntensity = -(30 + random() * 30); // retreat
|
|
93
|
-
}
|
|
94
|
-
else
|
|
95
|
-
{
|
|
96
|
-
approachIntensity = random() * 60 - 30; // random walk
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
moveX =
|
|
100
|
-
perpX * strafeIntensity +
|
|
101
|
-
(deltaX / normalizedDistance) * approachIntensity;
|
|
102
|
-
moveY =
|
|
103
|
-
perpY * strafeIntensity +
|
|
104
|
-
(deltaY / normalizedDistance) * approachIntensity;
|
|
105
|
-
|
|
106
|
-
// Wall avoidance (Hogger doesn't like corners)
|
|
107
|
-
if (state.position.x < WALL_BUFFER) moveX += WALL_PUSH;
|
|
108
|
-
if (state.position.x > ARENA_SIZE - WALL_BUFFER) moveX -= WALL_PUSH;
|
|
109
|
-
if (state.position.y < WALL_BUFFER) moveY += WALL_PUSH;
|
|
110
|
-
if (state.position.y > ARENA_SIZE - WALL_BUFFER) moveY -= WALL_PUSH;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
|
|
114
|
-
if (magnitude > 100)
|
|
115
|
-
{
|
|
116
|
-
moveX = (moveX / magnitude) * 100;
|
|
117
|
-
moveY = (moveY / magnitude) * 100;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const move = {x: moveX, y: moveY};
|
|
121
|
-
|
|
122
|
-
// === DEFENSE: Shield if in real danger ===
|
|
123
|
-
if (state.state === 'channeling')
|
|
124
|
-
{
|
|
125
|
-
if (!urgentThreat || urgentThreat.ticksToImpact > 120)
|
|
126
|
-
{
|
|
127
|
-
return {move, cancel: true};
|
|
128
|
-
}
|
|
129
|
-
return {move: {x: 0, y: 0}};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
state.state === 'idle' &&
|
|
134
|
-
urgentThreat &&
|
|
135
|
-
!urgentThreat.bestDodgeDirection
|
|
136
|
-
)
|
|
137
|
-
{
|
|
138
|
-
if (urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= 3)
|
|
139
|
-
{
|
|
140
|
-
return {
|
|
141
|
-
move: {x: 0, y: 0},
|
|
142
|
-
startCast: {spell: 'shield'},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
else if (!urgentThreat.canBlockInTime && state.blinkCooldown === 0)
|
|
146
|
-
{
|
|
147
|
-
return {
|
|
148
|
-
move: {x: 0, y: 0},
|
|
149
|
-
startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// === OFFENSE: Random blink (sometimes offensive, sometimes random) ===
|
|
155
|
-
if (state.state === 'idle' && state.blinkCooldown === 0 && random() < 0.03)
|
|
156
|
-
{
|
|
157
|
-
// 60% chance blink toward enemy, 40% chance random blink
|
|
158
|
-
if (random() < 0.6 && distance > 300)
|
|
159
|
-
{
|
|
160
|
-
return {
|
|
161
|
-
move: {x: 0, y: 0},
|
|
162
|
-
startCast: {spell: 'blink', target: enemy.position},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
else
|
|
166
|
-
{
|
|
167
|
-
return {
|
|
168
|
-
move: {x: 0, y: 0},
|
|
169
|
-
startCast: {
|
|
170
|
-
spell: 'blink',
|
|
171
|
-
target: {
|
|
172
|
-
x: 100 + random() * 600,
|
|
173
|
-
y: 100 + random() * 600,
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// === OFFENSE: Chaotic missiles ===
|
|
181
|
-
if (state.state === 'idle' && distance < 500)
|
|
182
|
-
{
|
|
183
|
-
// Random missile config each time
|
|
184
|
-
const roll = random();
|
|
185
|
-
let damage: number;
|
|
186
|
-
let speed: number;
|
|
187
|
-
let turnRate: number;
|
|
188
|
-
let duration: number;
|
|
189
|
-
|
|
190
|
-
if (roll < 0.3)
|
|
191
|
-
{
|
|
192
|
-
// Fast snipe
|
|
193
|
-
damage = 8 + Math.floor(random() * 5); // 8-12
|
|
194
|
-
speed = 6 + random() * 2; // 6-8
|
|
195
|
-
turnRate = 0;
|
|
196
|
-
duration = 80 + Math.floor(random() * 40); // 80-120
|
|
197
|
-
}
|
|
198
|
-
else if (roll < 0.7)
|
|
199
|
-
{
|
|
200
|
-
// Homing tracker
|
|
201
|
-
damage = 7 + Math.floor(random() * 5); // 7-11
|
|
202
|
-
speed = 3 + random() * 2; // 3-5
|
|
203
|
-
turnRate = 1 + random() * 2; // 1-3
|
|
204
|
-
duration = 150 + Math.floor(random() * 100); // 150-250
|
|
205
|
-
}
|
|
206
|
-
else
|
|
207
|
-
{
|
|
208
|
-
// Big slow bomb
|
|
209
|
-
damage = 12 + Math.floor(random() * 4); // 12-15
|
|
210
|
-
speed = 2 + random() * 2; // 2-4
|
|
211
|
-
turnRate = 0.5 + random() * 1.5; // 0.5-2
|
|
212
|
-
duration = 200 + Math.floor(random() * 100); // 200-300
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
move,
|
|
217
|
-
startCast: {
|
|
218
|
-
spell: 'missile',
|
|
219
|
-
config: {damage, speed, turnRate, duration},
|
|
220
|
-
missileAI: ChaoticHoming,
|
|
221
|
-
direction:
|
|
222
|
-
angleTo(state.position, enemy.position) + (random() * 20 - 10), // ±10° aim wobble
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return {move};
|
|
228
|
-
};
|
|
1
|
+
import {WizardFunction, MissileAIFunction} from '../../types.js';
|
|
2
|
+
import {angleTo} from '../../utils/angles.js';
|
|
3
|
+
import {distanceTo} from '../../utils/distance.js';
|
|
4
|
+
import {ARENA_SIZE} from '../../rules.js';
|
|
5
|
+
import {
|
|
6
|
+
getThreats,
|
|
7
|
+
getMostUrgentThreat,
|
|
8
|
+
getDodgeDirectionForMovement,
|
|
9
|
+
getBlinkToCenter,
|
|
10
|
+
} from '../shared.js';
|
|
11
|
+
const WALL_BUFFER = 60;
|
|
12
|
+
const WALL_PUSH = 30; // How hard to push away from walls
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Homing AI that sometimes forgets what it's doing.
|
|
16
|
+
*/
|
|
17
|
+
const ChaoticHoming: MissileAIFunction = ({worldState, random}) =>
|
|
18
|
+
{
|
|
19
|
+
const enemy = worldState.enemies[0];
|
|
20
|
+
// 80% chance to actually track, 20% chance to just fly straight
|
|
21
|
+
if (random() < 0.8)
|
|
22
|
+
{
|
|
23
|
+
return {turnToward: enemy?.position};
|
|
24
|
+
}
|
|
25
|
+
return {};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Bot: Hogger
|
|
30
|
+
*
|
|
31
|
+
* BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
|
|
32
|
+
* Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
|
|
33
|
+
* moves erratically but still somewhat toward/away from the enemy, shields when
|
|
34
|
+
* in real danger, and blinks unpredictably. The randomness makes Hogger hard to
|
|
35
|
+
* predict — you never know if the next missile will be a slow tracker or a fast
|
|
36
|
+
* snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
|
|
37
|
+
* to actually win fights.
|
|
38
|
+
*
|
|
39
|
+
* NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
|
|
40
|
+
* Forest who infamously kills unprepared lowbies. He's technically a basic mob
|
|
41
|
+
* but hits way harder than expected. This bot is the Critter that learned to
|
|
42
|
+
* fight — still chaotic, still a bit dumb, but capable of ending you if you
|
|
43
|
+
* underestimate it. "Hogger" is one of WoW's most recognizable references and
|
|
44
|
+
* perfectly captures "deceptively dangerous chaos."
|
|
45
|
+
*
|
|
46
|
+
* STANDALONE — no tier progression. There's only one Hogger.
|
|
47
|
+
*/
|
|
48
|
+
export const Hogger: WizardFunction = ({state, random}) =>
|
|
49
|
+
{
|
|
50
|
+
const enemy = state.enemies[0];
|
|
51
|
+
if (!enemy)
|
|
52
|
+
{
|
|
53
|
+
return {move: {x: 0, y: 0}};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const distance = distanceTo(state.position, enemy.position);
|
|
57
|
+
const threats = getThreats(state);
|
|
58
|
+
const urgentThreat = getMostUrgentThreat(threats);
|
|
59
|
+
|
|
60
|
+
// Erratic movement: mostly toward/away from enemy with random strafe
|
|
61
|
+
const deltaX = enemy.position.x - state.position.x;
|
|
62
|
+
const deltaY = enemy.position.y - state.position.y;
|
|
63
|
+
const normalizedDistance = distance > 0 ? distance : 1;
|
|
64
|
+
|
|
65
|
+
// Dodge if there's a real threat
|
|
66
|
+
const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
|
|
67
|
+
|
|
68
|
+
let moveX: number;
|
|
69
|
+
let moveY: number;
|
|
70
|
+
|
|
71
|
+
if (dodgeDirection)
|
|
72
|
+
{
|
|
73
|
+
// Actually dodge (Hogger isn't stupid)
|
|
74
|
+
moveX = dodgeDirection.x * 100;
|
|
75
|
+
moveY = dodgeDirection.y * 100;
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
{
|
|
79
|
+
// Random strafe component
|
|
80
|
+
const perpX = -deltaY / normalizedDistance;
|
|
81
|
+
const perpY = deltaX / normalizedDistance;
|
|
82
|
+
const strafeIntensity = random() * 80 - 40; // -40 to 40
|
|
83
|
+
|
|
84
|
+
// Approach/retreat: prefer medium range (250-400), but sometimes just wander
|
|
85
|
+
let approachIntensity = 0;
|
|
86
|
+
if (distance > 400)
|
|
87
|
+
{
|
|
88
|
+
approachIntensity = 40 + random() * 30; // approach
|
|
89
|
+
}
|
|
90
|
+
else if (distance < 200)
|
|
91
|
+
{
|
|
92
|
+
approachIntensity = -(30 + random() * 30); // retreat
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
{
|
|
96
|
+
approachIntensity = random() * 60 - 30; // random walk
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
moveX =
|
|
100
|
+
perpX * strafeIntensity +
|
|
101
|
+
(deltaX / normalizedDistance) * approachIntensity;
|
|
102
|
+
moveY =
|
|
103
|
+
perpY * strafeIntensity +
|
|
104
|
+
(deltaY / normalizedDistance) * approachIntensity;
|
|
105
|
+
|
|
106
|
+
// Wall avoidance (Hogger doesn't like corners)
|
|
107
|
+
if (state.position.x < WALL_BUFFER) moveX += WALL_PUSH;
|
|
108
|
+
if (state.position.x > ARENA_SIZE - WALL_BUFFER) moveX -= WALL_PUSH;
|
|
109
|
+
if (state.position.y < WALL_BUFFER) moveY += WALL_PUSH;
|
|
110
|
+
if (state.position.y > ARENA_SIZE - WALL_BUFFER) moveY -= WALL_PUSH;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
|
|
114
|
+
if (magnitude > 100)
|
|
115
|
+
{
|
|
116
|
+
moveX = (moveX / magnitude) * 100;
|
|
117
|
+
moveY = (moveY / magnitude) * 100;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const move = {x: moveX, y: moveY};
|
|
121
|
+
|
|
122
|
+
// === DEFENSE: Shield if in real danger ===
|
|
123
|
+
if (state.state === 'channeling')
|
|
124
|
+
{
|
|
125
|
+
if (!urgentThreat || urgentThreat.ticksToImpact > 120)
|
|
126
|
+
{
|
|
127
|
+
return {move, cancel: true};
|
|
128
|
+
}
|
|
129
|
+
return {move: {x: 0, y: 0}};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
state.state === 'idle' &&
|
|
134
|
+
urgentThreat &&
|
|
135
|
+
!urgentThreat.bestDodgeDirection
|
|
136
|
+
)
|
|
137
|
+
{
|
|
138
|
+
if (urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= 3)
|
|
139
|
+
{
|
|
140
|
+
return {
|
|
141
|
+
move: {x: 0, y: 0},
|
|
142
|
+
startCast: {spell: 'shield'},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
else if (!urgentThreat.canBlockInTime && state.blinkCooldown === 0)
|
|
146
|
+
{
|
|
147
|
+
return {
|
|
148
|
+
move: {x: 0, y: 0},
|
|
149
|
+
startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// === OFFENSE: Random blink (sometimes offensive, sometimes random) ===
|
|
155
|
+
if (state.state === 'idle' && state.blinkCooldown === 0 && random() < 0.03)
|
|
156
|
+
{
|
|
157
|
+
// 60% chance blink toward enemy, 40% chance random blink
|
|
158
|
+
if (random() < 0.6 && distance > 300)
|
|
159
|
+
{
|
|
160
|
+
return {
|
|
161
|
+
move: {x: 0, y: 0},
|
|
162
|
+
startCast: {spell: 'blink', target: enemy.position},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
else
|
|
166
|
+
{
|
|
167
|
+
return {
|
|
168
|
+
move: {x: 0, y: 0},
|
|
169
|
+
startCast: {
|
|
170
|
+
spell: 'blink',
|
|
171
|
+
target: {
|
|
172
|
+
x: 100 + random() * 600,
|
|
173
|
+
y: 100 + random() * 600,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// === OFFENSE: Chaotic missiles ===
|
|
181
|
+
if (state.state === 'idle' && distance < 500)
|
|
182
|
+
{
|
|
183
|
+
// Random missile config each time
|
|
184
|
+
const roll = random();
|
|
185
|
+
let damage: number;
|
|
186
|
+
let speed: number;
|
|
187
|
+
let turnRate: number;
|
|
188
|
+
let duration: number;
|
|
189
|
+
|
|
190
|
+
if (roll < 0.3)
|
|
191
|
+
{
|
|
192
|
+
// Fast snipe
|
|
193
|
+
damage = 8 + Math.floor(random() * 5); // 8-12
|
|
194
|
+
speed = 6 + random() * 2; // 6-8
|
|
195
|
+
turnRate = 0;
|
|
196
|
+
duration = 80 + Math.floor(random() * 40); // 80-120
|
|
197
|
+
}
|
|
198
|
+
else if (roll < 0.7)
|
|
199
|
+
{
|
|
200
|
+
// Homing tracker
|
|
201
|
+
damage = 7 + Math.floor(random() * 5); // 7-11
|
|
202
|
+
speed = 3 + random() * 2; // 3-5
|
|
203
|
+
turnRate = 1 + random() * 2; // 1-3
|
|
204
|
+
duration = 150 + Math.floor(random() * 100); // 150-250
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
{
|
|
208
|
+
// Big slow bomb
|
|
209
|
+
damage = 12 + Math.floor(random() * 4); // 12-15
|
|
210
|
+
speed = 2 + random() * 2; // 2-4
|
|
211
|
+
turnRate = 0.5 + random() * 1.5; // 0.5-2
|
|
212
|
+
duration = 200 + Math.floor(random() * 100); // 200-300
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
move,
|
|
217
|
+
startCast: {
|
|
218
|
+
spell: 'missile',
|
|
219
|
+
config: {damage, speed, turnRate, duration},
|
|
220
|
+
missileAI: ChaoticHoming,
|
|
221
|
+
direction:
|
|
222
|
+
angleTo(state.position, enemy.position) + (random() * 20 - 10), // ±10° aim wobble
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {move};
|
|
228
|
+
};
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import {WizardFunction} from '../../types.js';
|
|
2
|
-
import {angleTo} from '../../utils/angles.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Bot: Rookie
|
|
6
|
-
*
|
|
7
|
-
* BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
|
|
8
|
-
* the enemy. No movement, no dodging, no shielding. Knows one spell and uses
|
|
9
|
-
* it on cooldown. The wizard equivalent of an FPS player who stands in the open
|
|
10
|
-
* and holds left-click.
|
|
11
|
-
*
|
|
12
|
-
* NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
|
|
13
|
-
* knows what they're doing. This bot is a day-one player who learned how to cast
|
|
14
|
-
* missile and nothing else. No movement, no defense, just raw "I press the button."
|
|
15
|
-
* We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
|
|
16
|
-
* the same thing — a beginner who doesn't know any better.
|
|
17
|
-
*
|
|
18
|
-
* STANDALONE — no tier progression. Rookies either learn to play a real class
|
|
19
|
-
* or quit. This bot represents the rock-bottom of "at least it shoots."
|
|
20
|
-
*/
|
|
21
|
-
export const Rookie: WizardFunction = ({state}) =>
|
|
22
|
-
{
|
|
23
|
-
const enemy = state.enemies[0];
|
|
24
|
-
if (!enemy)
|
|
25
|
-
{
|
|
26
|
-
return {move: {x: 0, y: 0}};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (state.state === 'idle')
|
|
30
|
-
{
|
|
31
|
-
return {
|
|
32
|
-
move: {x: 0, y: 0},
|
|
33
|
-
startCast: {
|
|
34
|
-
spell: 'missile',
|
|
35
|
-
config: {
|
|
36
|
-
damage: 10,
|
|
37
|
-
speed: 5,
|
|
38
|
-
turnRate: 0,
|
|
39
|
-
duration: 200,
|
|
40
|
-
},
|
|
41
|
-
missileAI: () => ({}),
|
|
42
|
-
direction: angleTo(state.position, enemy.position),
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
move: {x: 0, y: 0},
|
|
49
|
-
};
|
|
50
|
-
};
|
|
1
|
+
import {WizardFunction} from '../../types.js';
|
|
2
|
+
import {angleTo} from '../../utils/angles.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bot: Rookie
|
|
6
|
+
*
|
|
7
|
+
* BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
|
|
8
|
+
* the enemy. No movement, no dodging, no shielding. Knows one spell and uses
|
|
9
|
+
* it on cooldown. The wizard equivalent of an FPS player who stands in the open
|
|
10
|
+
* and holds left-click.
|
|
11
|
+
*
|
|
12
|
+
* NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
|
|
13
|
+
* knows what they're doing. This bot is a day-one player who learned how to cast
|
|
14
|
+
* missile and nothing else. No movement, no defense, just raw "I press the button."
|
|
15
|
+
* We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
|
|
16
|
+
* the same thing — a beginner who doesn't know any better.
|
|
17
|
+
*
|
|
18
|
+
* STANDALONE — no tier progression. Rookies either learn to play a real class
|
|
19
|
+
* or quit. This bot represents the rock-bottom of "at least it shoots."
|
|
20
|
+
*/
|
|
21
|
+
export const Rookie: WizardFunction = ({state}) =>
|
|
22
|
+
{
|
|
23
|
+
const enemy = state.enemies[0];
|
|
24
|
+
if (!enemy)
|
|
25
|
+
{
|
|
26
|
+
return {move: {x: 0, y: 0}};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (state.state === 'idle')
|
|
30
|
+
{
|
|
31
|
+
return {
|
|
32
|
+
move: {x: 0, y: 0},
|
|
33
|
+
startCast: {
|
|
34
|
+
spell: 'missile',
|
|
35
|
+
config: {
|
|
36
|
+
damage: 10,
|
|
37
|
+
speed: 5,
|
|
38
|
+
turnRate: 0,
|
|
39
|
+
duration: 200,
|
|
40
|
+
},
|
|
41
|
+
missileAI: () => ({}),
|
|
42
|
+
direction: angleTo(state.position, enemy.position),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
move: {x: 0, y: 0},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {WizardFunction} from '../../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Bot: TargetDummy
|
|
5
|
-
*
|
|
6
|
-
* BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
|
|
7
|
-
*
|
|
8
|
-
* NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
|
|
9
|
-
* practice objects found in capital cities. Every WoW/FFXIV player has beaten
|
|
10
|
-
* on a target dummy to test DPS rotations. That's exactly what this bot is —
|
|
11
|
-
* a punching bag for testing missile mechanics and baseline damage output.
|
|
12
|
-
* Nobody calls them "training dummies"; the player term is always "target dummy."
|
|
13
|
-
*
|
|
14
|
-
* STANDALONE — no tier progression. It's a test fixture, not a combatant.
|
|
15
|
-
*/
|
|
16
|
-
export const TargetDummy: WizardFunction = () =>
|
|
17
|
-
{
|
|
18
|
-
return {
|
|
19
|
-
move: {x: 0, y: 0},
|
|
20
|
-
};
|
|
21
|
-
};
|
|
1
|
+
import {WizardFunction} from '../../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bot: TargetDummy
|
|
5
|
+
*
|
|
6
|
+
* BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
|
|
7
|
+
*
|
|
8
|
+
* NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
|
|
9
|
+
* practice objects found in capital cities. Every WoW/FFXIV player has beaten
|
|
10
|
+
* on a target dummy to test DPS rotations. That's exactly what this bot is —
|
|
11
|
+
* a punching bag for testing missile mechanics and baseline damage output.
|
|
12
|
+
* Nobody calls them "training dummies"; the player term is always "target dummy."
|
|
13
|
+
*
|
|
14
|
+
* STANDALONE — no tier progression. It's a test fixture, not a combatant.
|
|
15
|
+
*/
|
|
16
|
+
export const TargetDummy: WizardFunction = (_props) =>
|
|
17
|
+
{
|
|
18
|
+
return {
|
|
19
|
+
move: {x: 0, y: 0},
|
|
20
|
+
};
|
|
21
|
+
};
|