import { RpgEvent, RpgPlayer, BarComponentOptions, ComponentLayout } from '@rpgjs/server'; import { ActionBattleEnemyAttackProfileMap } from './core/enemy-attack-profiles'; import { ActionBattleAiSimpleBehavior, ActionBattleAiTreeInput } from './core/ai-behavior-tree'; import { ActionBattleAiPreset, ActionBattleDamageResult, ActionBattleEntity, ActionBattleTargetSelector } from './core/contracts'; import { NormalizedActionBattleHitReactionProfile, ActionBattleAnimationOptions, ActionBattleAudioOptions } from './types'; type RpgEventWithBattleAi = RpgEvent & { battleAi?: BattleAi; }; export interface BattleAiRewardItem { item?: any; itemId?: string; amount?: number; chance?: number; } export interface BattleAiRewards { exp?: number; gold?: number; items?: Array; showNotification?: boolean; } export interface BattleAiDefeatReward { readonly awarded: boolean; giveTo(player?: RpgPlayer | null): void; } export interface BattleAiDefeatedContext { event: RpgEvent; attacker?: ActionBattleEntity; reward: BattleAiDefeatReward; remove: () => void; } export type BattleAiDefeatedCallback = (context: BattleAiDefeatedContext) => void; export type BattleAiLegacyDefeatedCallback = (event: RpgEvent, attacker?: ActionBattleEntity) => void; export interface BattleAiHealthBarOptions { /** Style forwarded to the standard RPGJS `Components.hpBar()` factory. */ style?: BarComponentOptions; /** Optional label template. Use `null` to render only the bar. */ text?: string | null; /** Layout forwarded to the entity's `componentsTop` block. */ layout?: ComponentLayout; } export interface BattleAiDeathPresentationOptions { /** CanvasEngine FX preset emitted once when defeat starts. */ effect?: string; /** Time the non-colliding sprite remains available to client visuals. */ durationMs?: number; /** Particle scale forwarded to the visual preset. */ scale?: number; /** Whether the impact preset should shake the local camera. */ shake?: boolean; } export interface BattleAiBaseOptions { preset?: string | ActionBattleAiPreset; faction?: string; targets?: ActionBattleTargetSelector; enemyType?: EnemyType; attackCooldown?: number; visionRange?: number; attackRange?: number; dodgeChance?: number; dodgeCooldown?: number; fleeThreshold?: number; attackSkill?: any; attackPatterns?: AttackPattern[]; attackProfiles?: ActionBattleEnemyAttackProfileMap; patrolWaypoints?: Array<{ x: number; y: number; }>; groupBehavior?: boolean; moveToCooldown?: number; retreatCooldown?: number; poise?: number; hitstunMs?: number; invincibilityMs?: number; behavior?: { baseScore?: number; updateInterval?: number; minStateDuration?: number; assaultThreshold?: number; retreatThreshold?: number; }; behaviorKey?: string; tree?: ActionBattleAiTreeInput; behaviorTree?: ActionBattleAiTreeInput; simpleBehavior?: ActionBattleAiSimpleBehavior; animations?: ActionBattleAnimationOptions; rewards?: BattleAiRewards; autoAwardRewards?: boolean; presentation?: { role?: "enemy" | "boss" | "hidden"; name?: string; /** * Adds the standard RPGJS HP component above the rendered graphic. * Enabled by default for visible battle AI entities. */ healthBar?: boolean | BattleAiHealthBarOptions; /** * Client-only defeat presentation. Gameplay collision and AI are disabled * immediately by the authoritative server. */ death?: false | BattleAiDeathPresentationOptions; /** Optional battle track used while this enemy threatens a player. */ music?: { battle?: string; priority?: number; }; /** Sounds emitted by this enemy when attacking or reacting. */ audio?: ActionBattleAudioOptions; }; } export interface BattleAiOptions extends BattleAiBaseOptions { /** Callback called when the AI is defeated */ onDefeated?: BattleAiDefeatedCallback; } export interface BattleAiLegacyOptions extends BattleAiBaseOptions { /** @deprecated Use the context callback signature instead. */ onDefeated?: BattleAiLegacyDefeatedCallback; } /** * Hit result data returned after applying damage * * Contains information about the hit including damage dealt, * knockback parameters, and whether the target was defeated. * Used by hooks to customize hit behavior. * * @example * ```ts * const hitResult: HitResult = { * damage: 25, * knockbackForce: 50, * knockbackDuration: 300, * defeated: false, * attacker: this.event, * target: player * }; * ``` */ export interface HitResult { /** Damage dealt to the target */ damage: number; /** Knockback force applied (from weapon or default) */ knockbackForce: number; /** Knockback duration in milliseconds */ knockbackDuration: number; /** Whether the target was defeated */ defeated: boolean; /** The entity that attacked */ attacker: RpgEvent | RpgPlayer; /** The entity that was hit */ target: RpgPlayer | RpgEvent; defense?: { kind: "guard" | "parry"; staggerMs: number; }; } /** * Hook options for customizing hit behavior * * Allows overriding knockback parameters and adding custom effects * when a hit is applied. * * @example * ```ts * const hooks: ApplyHitHooks = { * onBeforeHit(result) { * // Reduce knockback for armored enemies * if (result.target.hasState('armored')) { * result.knockbackForce *= 0.5; * } * return result; * }, * onAfterHit(result) { * // Add poison effect on hit * if (Math.random() < 0.3) { * result.target.addState('poison'); * } * } * }; * ``` */ export interface ApplyHitHooks { /** * Called before the hit is applied * Can modify the hit result before damage and knockback * * @param result - The hit result data * @returns Modified hit result or void to use original */ onBeforeHit?: (result: HitResult) => HitResult | void; /** * Called after the hit is applied * Used for side effects like adding states, playing sounds, etc. * * @param result - The final hit result data */ onAfterHit?: (result: HitResult) => void; } /** * AI Debug Logger * * Conditional logging utility for AI behavior debugging. * Enable by setting `AiDebug.enabled = true` or via environment variable `RPGJS_DEBUG_AI=1` * * @example * ```ts * // Enable debug logging * AiDebug.enabled = true; * * // Or filter by event ID * AiDebug.filterEventId = 'goblin-1'; * ``` */ export declare const AiDebug: { /** Enable/disable all AI debug logs */ enabled: boolean; /** Filter logs to a specific event ID (null = all events) */ filterEventId: string | null; /** Log categories to enable (empty = all) */ categories: string[]; /** * Log an AI debug message * * @param category - Log category (e.g., 'state', 'attack', 'movement', 'damage') * @param eventId - Event ID for filtering * @param message - Log message * @param data - Optional additional data */ log(category: string, eventId: string | undefined, message: string, data?: any): void; }; /** * AI State enumeration * * Defines the different states an AI can be in, each with its own behavior. */ export declare enum AiState { Idle = "idle", Alert = "alert", Combat = "combat", Flee = "flee", Stunned = "stunned" } /** * Enemy Type enumeration * * Defines different enemy archetypes with unique behaviors. * Stats (HP, ATK, etc.) should be set on the event itself via onInit. */ export declare enum EnemyType { Aggressive = "aggressive", Defensive = "defensive", Ranged = "ranged", Tank = "tank", Berserker = "berserker" } /** * Attack Pattern enumeration * * Different attack patterns the AI can use. */ export declare enum AttackPattern { Melee = "melee", Combo = "combo", Charged = "charged", Zone = "zone", DashAttack = "dashAttack" } /** * Default knockback configuration * * Used when no weapon is equipped or weapon doesn't specify knockback. */ export declare const DEFAULT_KNOCKBACK: { /** Default knockback force */ force: number; /** Default knockback duration in milliseconds */ duration: number; }; /** * Advanced Battle AI Controller for events * * This class provides intelligent combat behavior control for events. * It uses the existing RPGJS API for stats, skills, items, etc. * The AI only manages behavior - the event's stats should be configured * in onInit using standard RPGJS methods. * * ## Usage with RPGJS API * * Configure the event stats using standard RPGJS methods: * - `this.hp = 100` - Set health * - `this.learnSkill(FireBall)` - Learn a skill * - `this.addItem(Potion, 3)` - Add items * - `this.equip(Sword)` - Equip items * - `this.setClass(WarriorClass)` - Set class * - `this.param[ATK] = 20` - Set parameters * * @example * ```ts * function GoblinEnemy() { * return { * name: "Goblin", * onInit() { * this.setGraphic("goblin"); * * // Configure stats using RPGJS API * this.hp = 80; * this.param[ATK] = 15; * this.param[PDEF] = 5; * this.learnSkill(Slash); * * // Apply AI behavior * new BattleAi(this, { * enemyType: EnemyType.Aggressive, * attackSkill: Slash * }); * } * }; * } * ``` */ export declare class BattleAi { private event; private target; private lastAttackTime; private nextAttackAt; private attackDirection?; private updateInterval?; /** * Log AI debug message for this event */ private debugLog; private traceLog; private lockActionUntil; private lockForAttack; private state; private stateStartTime; private stunnedUntil; private enemyType; private faction?; private targets; private attackCooldown; private visionRange; private attackRange; private dodgeChance; private dodgeCooldown; private lastDodgeTime; private fleeThreshold; private attackSkill; private attackPatterns; private configuredAttackPatterns?; private attackProfiles; private animations?; private lastAttackPattern; private skillCooldowns; private lastCombatAction; private comboCount; private comboMax; private chargingAttack; private groupBehavior; private nearbyEnemies; private groupUpdateInterval; private patrolWaypoints; private currentPatrolIndex; private lastHpCheck; private recentDamageTaken; private damageCheckInterval; private isMovingToTarget; private onDefeatedCallback?; private rewards?; private autoAwardRewards; private presentation; private defeated; private lastFacingDirection; private behaviorScore; private behaviorMode; private behaviorLastUpdate; private behaviorUpdateInterval; private behaviorAssaultThreshold; private behaviorRetreatThreshold; private behaviorMinStateDuration; private behaviorEnabled; private moveToCooldown; private lastMoveToTime; private retreatCooldown; private lastRetreatTime; private lastStrafeTime; private strafeSide; private actionLockedUntil; private lastActionLockTraceTime; private lastMoveToCooldownTraceTime; private lastMoveToCooldownTraceSignature; private lastTargetMovementSkipTraceTime; private timers; private behaviorKey?; private behaviorTree?; private aiMemory; private poise; private hitstunMs; private invincibilityMs; private visionShape?; private visionSetupRetries; private maxVisionSetupRetries; private destroyed; /** The module is inactive for this event: no loop, no vision, no health bar. */ readonly disabled: boolean; private lastNoTargetTraceTime; /** * Create a new Battle AI Controller * * The AI controls behavior only. Stats should be set on the event * using standard RPGJS methods (hp, param, learnSkill, etc.) * * @param event - The event to control * @param options - AI behavior configuration * * @example * ```ts * // In your event's onInit * this.hp = 100; * this.param[ATK] = 20; * this.learnSkill(FireBall); * * new BattleAi(this, { * enemyType: EnemyType.Ranged, * attackSkill: FireBall, * visionRange: 200, * fleeThreshold: 0.2 * }); * ``` */ constructor(event: RpgEventWithBattleAi, options?: BattleAiOptions); constructor(event: RpgEventWithBattleAi, options?: BattleAiLegacyOptions); getPresentation(): { role: "enemy" | "boss" | "hidden"; name?: string; death: false | Required; music?: { battle?: string; priority?: number; }; audio?: ActionBattleAudioOptions; }; /** * Apply enemy type-specific behavior modifiers * * This only affects AI behavior (cooldowns, ranges, dodge). * Stats should be set on the event itself. */ private applyEnemyTypeBehavior; /** * Setup vision detection */ private setupVision; private scheduleVisionSetup; /** * Whether the event is still the one registered on its map. A map reload removes its events * without clearing `event.map`: the AI of a replaced event would go on, invisible, and attack. */ private isEventOnMap; /** * Start the AI behavior loop */ private startAiBehaviorLoop; /** * Change AI state with validated transitions */ private changeState; /** * Main AI behavior update loop */ private updateAiBehavior; /** * Update idle behavior (patrolling) */ private updateIdleBehavior; /** * Update alert behavior */ private updateAlertBehavior; /** * Update combat behavior */ private updateCombatBehavior; /** * Update flee behavior */ private updateFleeBehavior; private getHpPercent; private getLearnedCombatSkills; private evaluateSkillActions; private scoreSkillAction; private selectCombatAction; private performPlannedBasicAttack; private performPlannedSkill; private debugDecision; private handleSmartCombatMovement; /** * Select and perform an attack pattern */ private selectAndPerformAttack; /** * Select attack pattern with weighted probability */ private selectAttackPattern; /** * Keep special attacks appropriate for the current distance and avoid * immediately repeating one when another pattern is available. */ private selectAttackCandidates; /** * Perform attack pattern */ private performAttackPattern; /** * Perform melee attack * Uses skill if configured, otherwise creates hitbox */ private performMeleeAttack; private executeMeleeAttack; /** * Perform basic hitbox attack when no skill is set */ private performBasicHitbox; private resolveBasicHitboxes; private queryHitboxCandidates; private processHitboxHits; /** * Apply hit to target using RPGJS damage system with knockback * * Calculates damage using RPGJS formula, applies knockback based on * equipped weapon's knockbackForce property, and triggers visual effects. * Supports hooks for customizing behavior. * * @param target - The player or entity being hit * @param hooks - Optional hooks for customizing hit behavior * @returns The hit result containing damage and knockback info * * @example * ```ts * // Basic hit * this.applyHit(player); * * // With custom hooks * this.applyHit(player, { * onBeforeHit(result) { * result.knockbackForce *= 1.5; // Increase knockback * return result; * }, * onAfterHit(result) { * console.log(`Dealt ${result.damage} damage!`); * } * }); * ``` */ private applyHit; /** * Get knockback force from equipped weapon * * Retrieves the knockbackForce property from the event's equipped weapon. * Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set. * * @returns Knockback force value * * @example * ```ts * // Weapon with knockbackForce: 80 * const force = this.getWeaponKnockbackForce(); // 80 * * // No weapon equipped * const force = this.getWeaponKnockbackForce(); // 50 (default) * ``` */ private getWeaponKnockbackForce; /** * Perform combo attack */ private performComboAttack; /** * Perform charged attack */ private performChargedAttack; /** * Perform zone attack (360 degrees) */ private performZoneAttack; /** * Perform dash attack */ private performDashAttack; private getAttackProfile; private playAttackVisual; private telegraphAttack; private scheduleAttackStartup; /** * Face the current target with hysteresis to prevent animation flickering * * Uses multiple strategies to prevent flickering: * 1. When very close to target (collision), keep current direction * 2. When near diagonal, require significant difference to change * 3. Only change if direction is clearly wrong (opposite) */ private faceTarget; /** * Try to dodge */ private tryDodge; private canDodge; private shouldDodge; /** * Flee from target */ private fleeFromTarget; /** * Retreat from target (temporary) */ private retreatFromTarget; /** * Check damage taken for retreat decision */ private checkDamageTaken; /** * Start patrol */ private startPatrol; /** * Update group behavior */ private updateGroupBehavior; /** * Find nearby enemies */ private findNearbyEnemies; /** * Apply formation around target */ private applyFormation; /** * Handle player entering vision */ onDetectInShape(target: ActionBattleEntity, shape: any): void; private engageTarget; /** * Handle player leaving vision */ onDetectOutShape(target: ActionBattleEntity, shape: any): void; /** * Handle taking damage (called from server.ts) * * This triggers state changes like stun and flee check. * The actual damage is applied externally via RPGJS API. */ takeDamage(attacker: ActionBattleEntity): boolean; /** * Interrupt this enemy with an authoritative stagger. * * Used by parries and heavy reactions. The visual is emitted separately so * games can replace the default presentation without changing AI ownership. */ stagger(durationMs?: number, source?: ActionBattleEntity): void; handleDamage(attacker: ActionBattleEntity, damageResult: ActionBattleDamageResult & { reaction?: NormalizedActionBattleHitReactionProfile; skill?: any; metadata?: Record; }): boolean; /** * Kill this AI * * Stops all movements, cleans up resources, calls the onDefeated hook, * and removes the event from the map. */ private kill; /** * Get distance between entities */ private getDistance; private resolveUsable; private getSkillCooldownKey; private getSkillCooldown; private markSkillUsed; private isAttackReady; private canTarget; private findNearestTarget; private isTargetDefeated; private clearTarget; private updateBehavior; private applyCustomBehavior; private applyAiDecision; private createAiTreeContext; private executeAiIntents; private executeAiIntent; private executeTeleport; private executeKeepDistance; private executeRequestedAttack; private executeRequestedSkill; private handleTacticalMovement; private handleAssaultMovement; private resolveMoveTarget; private lastMoveTargetSignature?; private requestMoveTo; private requestTargetMovement; private schedule; getHealth(): number; getMaxHealth(): number; getTarget(): ActionBattleEntity | null; getState(): AiState; getFaction(): string | undefined; setFaction(faction: string | undefined): void; getTargets(): ActionBattleTargetSelector; setTargets(targets: ActionBattleTargetSelector): void; getEnemyType(): EnemyType; private syncThreat; private removeThreat; /** * Clean up */ destroy(): void; } export {};