All files / components/screens/combat/hooks usePreloadCombatAudio.ts

92.3% Statements 24/26
83.33% Branches 5/6
100% Functions 4/4
92% Lines 23/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161                                                                                              1x                                                                                                     80x 80x             80x 7x             7x 7x 7x   7x 112x 112x 112x     112x   112x 112x           17x 17x     112x 112x           7x               80x 7x     80x        
/**
 * usePreloadCombatAudio Hook - Preload Critical Combat Audio Assets
 *
 * Preloads essential combat audio assets when CombatScreen3D mounts
 * to ensure audio plays without delay during combat.
 *
 * This hook loads:
 * - Attack sounds (light, medium, heavy, critical)
 * - Hit reaction sounds (all intensities)
 * - Block and dodge sounds
 * - Stance change sounds
 * - Combat theme music
 *
 * @returns Loading state and error information
 */
 
import { useCallback, useEffect, useState } from "react";
import { audioAssetRegistry } from "../../../../audio/AudioAssetRegistry";
import { useAudio } from "../../../../audio/AudioProvider";
import type { AudioAsset } from "../../../../audio/types";
 
export interface PreloadCombatAudioState {
  /**
   * Whether audio assets are currently loading
   */
  readonly isLoading: boolean;
 
  /**
   * Whether all critical assets have been loaded
   */
  readonly isLoaded: boolean;
 
  /**
   * Any errors that occurred during loading
   */
  readonly errors: readonly string[];
 
  /**
   * Progress percentage (0-100)
   */
  readonly progress: number;
}
 
/**
 * Critical combat audio assets to preload
 * These are the most frequently used sounds in combat
 */
const CRITICAL_COMBAT_ASSETS = [
  "attack_punch_light_1",
  "attack_punch_light_2",
  "attack_punch_medium_1",
  "attack_critical_1",
  "attack_light",
  "attack_medium",
  "attack_heavy",
 
  "hit_light_1",
  "hit_medium_1",
  "hit_heavy_1",
  "hit_critical_1",
 
  "block_success_1",
  "block_break_1",
  "dodge_1",
 
  "stance_change_1",
 
  "combat_theme",
] as const;
 
/**
 * Hook to preload critical combat audio assets
 *
 * This hook preloads essential combat sounds when the component mounts,
 * ensuring audio plays without delay during combat. It loads assets
 * sequentially to avoid overwhelming the browser and provides progress
 * tracking for loading indicators.
 *
 * @returns Preload state with loading status, progress, and errors
 *
 * @example
 * ```tsx
 * const CombatScreen3D = () => {
 *   const { isLoading, isLoaded, progress, errors } = usePreloadCombatAudio();
 *
 *   if (isLoading) {
 *     return <LoadingScreen progress={progress} />;
 *   }
 *
 *   if (errors.length > 0) {
 *     console.warn('Some audio failed to load:', errors);
 *   }
 *
 *   return <CombatScene />;
 * };
 * ```
 */
export function usePreloadCombatAudio(): PreloadCombatAudioState {
  const audio = useAudio();
  const [state, setState] = useState<PreloadCombatAudioState>({
    isLoading: true,
    isLoaded: false,
    errors: [],
    progress: 0,
  });
 
  const preloadAssets = useCallback(async () => {
    setState({
      isLoading: true,
      isLoaded: false,
      errors: [],
      progress: 0,
    });
 
    const errors: string[] = [];
    let loadedCount = 0;
    const totalCount = CRITICAL_COMBAT_ASSETS.length;
 
    for (const assetId of CRITICAL_COMBAT_ASSETS) {
      try {
        const sfxAsset = audioAssetRegistry.getSFX(assetId);
        const musicAsset = sfxAsset
          ? undefined
          : audioAssetRegistry.getMusic(assetId);
        const asset = sfxAsset ?? musicAsset;
 
        if (asset) {
          await audio.loadAsset(asset as AudioAsset);
        } else E{
          console.warn(`Combat audio asset not found: ${assetId}`);
          errors.push(`Asset not found: ${assetId}`);
        }
      } catch (error) {
        console.warn(`Failed to preload combat audio: ${assetId}`, error);
        errors.push(`Failed to load: ${assetId}`);
      }
 
      loadedCount++;
      setState((prev) => ({
        ...prev,
        progress: Math.round((loadedCount / totalCount) * 100),
      }));
    }
 
    setState({
      isLoading: false,
      isLoaded: true,
      errors,
      progress: 100,
    });
  }, [audio]);
 
  useEffect(() => {
    preloadAssets();
  }, [preloadAssets]);
 
  return state;
}
 
export default usePreloadCombatAudio;