@rpgjs/action-battle 5.0.0-alpha.28

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.
@@ -0,0 +1,482 @@
1
+ import { RpgEvent, RpgPlayer } from '@rpgjs/server';
2
+ type RpgEventWithBattleAi = RpgEvent & {
3
+ battleAi: BattleAi;
4
+ };
5
+ /**
6
+ * Hit result data returned after applying damage
7
+ *
8
+ * Contains information about the hit including damage dealt,
9
+ * knockback parameters, and whether the target was defeated.
10
+ * Used by hooks to customize hit behavior.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const hitResult: HitResult = {
15
+ * damage: 25,
16
+ * knockbackForce: 50,
17
+ * knockbackDuration: 300,
18
+ * defeated: false,
19
+ * attacker: this.event,
20
+ * target: player
21
+ * };
22
+ * ```
23
+ */
24
+ export interface HitResult {
25
+ /** Damage dealt to the target */
26
+ damage: number;
27
+ /** Knockback force applied (from weapon or default) */
28
+ knockbackForce: number;
29
+ /** Knockback duration in milliseconds */
30
+ knockbackDuration: number;
31
+ /** Whether the target was defeated */
32
+ defeated: boolean;
33
+ /** The entity that attacked */
34
+ attacker: RpgEvent | RpgPlayer;
35
+ /** The entity that was hit */
36
+ target: RpgPlayer | RpgEvent;
37
+ }
38
+ /**
39
+ * Hook options for customizing hit behavior
40
+ *
41
+ * Allows overriding knockback parameters and adding custom effects
42
+ * when a hit is applied.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const hooks: ApplyHitHooks = {
47
+ * onBeforeHit(result) {
48
+ * // Reduce knockback for armored enemies
49
+ * if (result.target.hasState('armored')) {
50
+ * result.knockbackForce *= 0.5;
51
+ * }
52
+ * return result;
53
+ * },
54
+ * onAfterHit(result) {
55
+ * // Add poison effect on hit
56
+ * if (Math.random() < 0.3) {
57
+ * result.target.addState('poison');
58
+ * }
59
+ * }
60
+ * };
61
+ * ```
62
+ */
63
+ export interface ApplyHitHooks {
64
+ /**
65
+ * Called before the hit is applied
66
+ * Can modify the hit result before damage and knockback
67
+ *
68
+ * @param result - The hit result data
69
+ * @returns Modified hit result or void to use original
70
+ */
71
+ onBeforeHit?: (result: HitResult) => HitResult | void;
72
+ /**
73
+ * Called after the hit is applied
74
+ * Used for side effects like adding states, playing sounds, etc.
75
+ *
76
+ * @param result - The final hit result data
77
+ */
78
+ onAfterHit?: (result: HitResult) => void;
79
+ }
80
+ /**
81
+ * AI Debug Logger
82
+ *
83
+ * Conditional logging utility for AI behavior debugging.
84
+ * Enable by setting `AiDebug.enabled = true` or via environment variable `RPGJS_DEBUG_AI=1`
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // Enable debug logging
89
+ * AiDebug.enabled = true;
90
+ *
91
+ * // Or filter by event ID
92
+ * AiDebug.filterEventId = 'goblin-1';
93
+ * ```
94
+ */
95
+ export declare const AiDebug: {
96
+ /** Enable/disable all AI debug logs */
97
+ enabled: boolean;
98
+ /** Filter logs to a specific event ID (null = all events) */
99
+ filterEventId: string | null;
100
+ /** Log categories to enable (empty = all) */
101
+ categories: string[];
102
+ /**
103
+ * Log an AI debug message
104
+ *
105
+ * @param category - Log category (e.g., 'state', 'attack', 'movement', 'damage')
106
+ * @param eventId - Event ID for filtering
107
+ * @param message - Log message
108
+ * @param data - Optional additional data
109
+ */
110
+ log(category: string, eventId: string | undefined, message: string, data?: any): void;
111
+ };
112
+ /**
113
+ * AI State enumeration
114
+ *
115
+ * Defines the different states an AI can be in, each with its own behavior.
116
+ */
117
+ export declare enum AiState {
118
+ Idle = "idle",
119
+ Alert = "alert",
120
+ Combat = "combat",
121
+ Flee = "flee",
122
+ Stunned = "stunned"
123
+ }
124
+ /**
125
+ * Enemy Type enumeration
126
+ *
127
+ * Defines different enemy archetypes with unique behaviors.
128
+ * Stats (HP, ATK, etc.) should be set on the event itself via onInit.
129
+ */
130
+ export declare enum EnemyType {
131
+ Aggressive = "aggressive",
132
+ Defensive = "defensive",
133
+ Ranged = "ranged",
134
+ Tank = "tank",
135
+ Berserker = "berserker"
136
+ }
137
+ /**
138
+ * Attack Pattern enumeration
139
+ *
140
+ * Different attack patterns the AI can use.
141
+ */
142
+ export declare enum AttackPattern {
143
+ Melee = "melee",
144
+ Combo = "combo",
145
+ Charged = "charged",
146
+ Zone = "zone",
147
+ DashAttack = "dashAttack"
148
+ }
149
+ /**
150
+ * Default knockback configuration
151
+ *
152
+ * Used when no weapon is equipped or weapon doesn't specify knockback.
153
+ */
154
+ export declare const DEFAULT_KNOCKBACK: {
155
+ /** Default knockback force */
156
+ force: number;
157
+ /** Default knockback duration in milliseconds */
158
+ duration: number;
159
+ };
160
+ /**
161
+ * Advanced Battle AI Controller for events
162
+ *
163
+ * This class provides intelligent combat behavior control for events.
164
+ * It uses the existing RPGJS API for stats, skills, items, etc.
165
+ * The AI only manages behavior - the event's stats should be configured
166
+ * in onInit using standard RPGJS methods.
167
+ *
168
+ * ## Usage with RPGJS API
169
+ *
170
+ * Configure the event stats using standard RPGJS methods:
171
+ * - `this.hp = 100` - Set health
172
+ * - `this.learnSkill(FireBall)` - Learn a skill
173
+ * - `this.addItem(Potion, 3)` - Add items
174
+ * - `this.equip(Sword)` - Equip items
175
+ * - `this.setClass(WarriorClass)` - Set class
176
+ * - `this.param[ATK] = 20` - Set parameters
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * function GoblinEnemy() {
181
+ * return {
182
+ * name: "Goblin",
183
+ * onInit() {
184
+ * this.setGraphic("goblin");
185
+ *
186
+ * // Configure stats using RPGJS API
187
+ * this.hp = 80;
188
+ * this.param[ATK] = 15;
189
+ * this.param[PDEF] = 5;
190
+ * this.learnSkill(Slash);
191
+ *
192
+ * // Apply AI behavior
193
+ * new BattleAi(this, {
194
+ * enemyType: EnemyType.Aggressive,
195
+ * attackSkill: Slash
196
+ * });
197
+ * }
198
+ * };
199
+ * }
200
+ * ```
201
+ */
202
+ export declare class BattleAi {
203
+ private event;
204
+ private target;
205
+ private lastAttackTime;
206
+ private updateInterval?;
207
+ /**
208
+ * Log AI debug message for this event
209
+ */
210
+ private debugLog;
211
+ private state;
212
+ private stateStartTime;
213
+ private stunnedUntil;
214
+ private enemyType;
215
+ private attackCooldown;
216
+ private visionRange;
217
+ private attackRange;
218
+ private dodgeChance;
219
+ private dodgeCooldown;
220
+ private lastDodgeTime;
221
+ private fleeThreshold;
222
+ private attackSkill;
223
+ private attackPatterns;
224
+ private comboCount;
225
+ private comboMax;
226
+ private chargingAttack;
227
+ private groupBehavior;
228
+ private nearbyEnemies;
229
+ private groupUpdateInterval;
230
+ private patrolWaypoints;
231
+ private currentPatrolIndex;
232
+ private lastHpCheck;
233
+ private recentDamageTaken;
234
+ private damageCheckInterval;
235
+ private isMovingToTarget;
236
+ private onDefeatedCallback?;
237
+ private lastFacingDirection;
238
+ /**
239
+ * Create a new Battle AI Controller
240
+ *
241
+ * The AI controls behavior only. Stats should be set on the event
242
+ * using standard RPGJS methods (hp, param, learnSkill, etc.)
243
+ *
244
+ * @param event - The event to control
245
+ * @param options - AI behavior configuration
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * // In your event's onInit
250
+ * this.hp = 100;
251
+ * this.param[ATK] = 20;
252
+ * this.learnSkill(FireBall);
253
+ *
254
+ * new BattleAi(this, {
255
+ * enemyType: EnemyType.Ranged,
256
+ * attackSkill: FireBall,
257
+ * visionRange: 200,
258
+ * fleeThreshold: 0.2
259
+ * });
260
+ * ```
261
+ */
262
+ constructor(event: RpgEventWithBattleAi, options?: {
263
+ enemyType?: EnemyType;
264
+ attackCooldown?: number;
265
+ visionRange?: number;
266
+ attackRange?: number;
267
+ dodgeChance?: number;
268
+ dodgeCooldown?: number;
269
+ fleeThreshold?: number;
270
+ attackSkill?: any;
271
+ attackPatterns?: AttackPattern[];
272
+ patrolWaypoints?: Array<{
273
+ x: number;
274
+ y: number;
275
+ }>;
276
+ groupBehavior?: boolean;
277
+ /** Callback called when the AI is defeated */
278
+ onDefeated?: (event: RpgEvent) => void;
279
+ });
280
+ /**
281
+ * Apply enemy type-specific behavior modifiers
282
+ *
283
+ * This only affects AI behavior (cooldowns, ranges, dodge).
284
+ * Stats should be set on the event itself.
285
+ */
286
+ private applyEnemyTypeBehavior;
287
+ /**
288
+ * Setup vision detection
289
+ */
290
+ private setupVision;
291
+ /**
292
+ * Start the AI behavior loop
293
+ */
294
+ private startAiBehaviorLoop;
295
+ /**
296
+ * Change AI state with validated transitions
297
+ */
298
+ private changeState;
299
+ /**
300
+ * Main AI behavior update loop
301
+ */
302
+ private updateAiBehavior;
303
+ /**
304
+ * Update idle behavior (patrolling)
305
+ */
306
+ private updateIdleBehavior;
307
+ /**
308
+ * Update alert behavior
309
+ */
310
+ private updateAlertBehavior;
311
+ /**
312
+ * Update combat behavior
313
+ */
314
+ private updateCombatBehavior;
315
+ /**
316
+ * Update flee behavior
317
+ */
318
+ private updateFleeBehavior;
319
+ /**
320
+ * Select and perform an attack pattern
321
+ */
322
+ private selectAndPerformAttack;
323
+ /**
324
+ * Select attack pattern with weighted probability
325
+ */
326
+ private selectAttackPattern;
327
+ /**
328
+ * Perform attack pattern
329
+ */
330
+ private performAttackPattern;
331
+ /**
332
+ * Perform melee attack
333
+ * Uses skill if configured, otherwise creates hitbox
334
+ */
335
+ private performMeleeAttack;
336
+ /**
337
+ * Perform basic hitbox attack when no skill is set
338
+ */
339
+ private performBasicHitbox;
340
+ /**
341
+ * Apply hit to target using RPGJS damage system with knockback
342
+ *
343
+ * Calculates damage using RPGJS formula, applies knockback based on
344
+ * equipped weapon's knockbackForce property, and triggers visual effects.
345
+ * Supports hooks for customizing behavior.
346
+ *
347
+ * @param target - The player or entity being hit
348
+ * @param hooks - Optional hooks for customizing hit behavior
349
+ * @returns The hit result containing damage and knockback info
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * // Basic hit
354
+ * this.applyHit(player);
355
+ *
356
+ * // With custom hooks
357
+ * this.applyHit(player, {
358
+ * onBeforeHit(result) {
359
+ * result.knockbackForce *= 1.5; // Increase knockback
360
+ * return result;
361
+ * },
362
+ * onAfterHit(result) {
363
+ * console.log(`Dealt ${result.damage} damage!`);
364
+ * }
365
+ * });
366
+ * ```
367
+ */
368
+ private applyHit;
369
+ /**
370
+ * Get knockback force from equipped weapon
371
+ *
372
+ * Retrieves the knockbackForce property from the event's equipped weapon.
373
+ * Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set.
374
+ *
375
+ * @returns Knockback force value
376
+ *
377
+ * @example
378
+ * ```ts
379
+ * // Weapon with knockbackForce: 80
380
+ * const force = this.getWeaponKnockbackForce(); // 80
381
+ *
382
+ * // No weapon equipped
383
+ * const force = this.getWeaponKnockbackForce(); // 50 (default)
384
+ * ```
385
+ */
386
+ private getWeaponKnockbackForce;
387
+ /**
388
+ * Perform combo attack
389
+ */
390
+ private performComboAttack;
391
+ /**
392
+ * Perform charged attack
393
+ */
394
+ private performChargedAttack;
395
+ /**
396
+ * Perform zone attack (360 degrees)
397
+ */
398
+ private performZoneAttack;
399
+ /**
400
+ * Perform dash attack
401
+ */
402
+ private performDashAttack;
403
+ /**
404
+ * Face the current target with hysteresis to prevent animation flickering
405
+ *
406
+ * Uses multiple strategies to prevent flickering:
407
+ * 1. When very close to target (collision), keep current direction
408
+ * 2. When near diagonal, require significant difference to change
409
+ * 3. Only change if direction is clearly wrong (opposite)
410
+ */
411
+ private faceTarget;
412
+ /**
413
+ * Try to dodge
414
+ */
415
+ private tryDodge;
416
+ private canDodge;
417
+ private shouldDodge;
418
+ /**
419
+ * Flee from target
420
+ */
421
+ private fleeFromTarget;
422
+ /**
423
+ * Retreat from target (temporary)
424
+ */
425
+ private retreatFromTarget;
426
+ /**
427
+ * Check damage taken for retreat decision
428
+ */
429
+ private checkDamageTaken;
430
+ /**
431
+ * Start patrol
432
+ */
433
+ private startPatrol;
434
+ /**
435
+ * Update group behavior
436
+ */
437
+ private updateGroupBehavior;
438
+ /**
439
+ * Find nearby enemies
440
+ */
441
+ private findNearbyEnemies;
442
+ /**
443
+ * Apply formation around target
444
+ */
445
+ private applyFormation;
446
+ /**
447
+ * Handle player entering vision
448
+ */
449
+ onDetectInShape(player: InstanceType<typeof RpgPlayer>, shape: any): void;
450
+ /**
451
+ * Handle player leaving vision
452
+ */
453
+ onDetectOutShape(player: InstanceType<typeof RpgPlayer>, shape: any): void;
454
+ /**
455
+ * Handle taking damage (called from server.ts)
456
+ *
457
+ * This triggers state changes like stun and flee check.
458
+ * The actual damage is applied externally via RPGJS API.
459
+ */
460
+ takeDamage(attacker: RpgPlayer): boolean;
461
+ /**
462
+ * Kill this AI
463
+ *
464
+ * Stops all movements, cleans up resources, calls the onDefeated hook,
465
+ * and removes the event from the map.
466
+ */
467
+ private kill;
468
+ /**
469
+ * Get distance between entities
470
+ */
471
+ private getDistance;
472
+ getHealth(): number;
473
+ getMaxHealth(): number;
474
+ getTarget(): InstanceType<typeof RpgPlayer> | null;
475
+ getState(): AiState;
476
+ getEnemyType(): EnemyType;
477
+ /**
478
+ * Clean up
479
+ */
480
+ destroy(): void;
481
+ }
482
+ export {};
@@ -0,0 +1,25 @@
1
+ import client from "./index2.js";
2
+ import { createModule } from "@rpgjs/common";
3
+ import { AiDebug, AiState, AttackPattern, BattleAi, DEFAULT_KNOCKBACK, EnemyType } from "./index3.js";
4
+ import { DEFAULT_PLAYER_ATTACK_HITBOXES, applyPlayerHitToEvent, getPlayerWeaponKnockbackForce } from "./index4.js";
5
+ const server = null;
6
+ function provideActionBattle() {
7
+ return createModule("ActionBattle", [
8
+ {
9
+ server,
10
+ client
11
+ }
12
+ ]);
13
+ }
14
+ export {
15
+ AiDebug,
16
+ AiState,
17
+ AttackPattern,
18
+ BattleAi,
19
+ DEFAULT_KNOCKBACK,
20
+ DEFAULT_PLAYER_ATTACK_HITBOXES,
21
+ EnemyType,
22
+ applyPlayerHitToEvent,
23
+ getPlayerWeaponKnockbackForce,
24
+ provideActionBattle
25
+ };
@@ -0,0 +1,13 @@
1
+ import { PrebuiltComponentAnimations } from "@rpgjs/client";
2
+ import { defineModule } from "@rpgjs/common";
3
+ const client = defineModule({
4
+ componentAnimations: [
5
+ {
6
+ id: "hit",
7
+ component: PrebuiltComponentAnimations.Hit
8
+ }
9
+ ]
10
+ });
11
+ export {
12
+ client as default
13
+ };