All files / components/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 162 163 164 165 166 167                                                                                              1x                                                                                                               35x 35x             35x 7x             7x 7x 7x     7x 112x     112x 112x 112x   112x 112x           17x 17x     112x 112x           7x               35x 7x     35x        
/**
 * 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 { useEffect, useState, useCallback } from "react";
import { useAudio } from "../../../audio/AudioProvider";
import { audioAssetRegistry } from "../../../audio/AudioAssetRegistry";
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 sounds (most common)
  "attack_punch_light_1",
  "attack_punch_light_2",
  "attack_punch_medium_1",
  "attack_critical_1",
  "attack_light",
  "attack_medium",
  "attack_heavy",
  
  // Hit reactions (essential feedback)
  "hit_light_1",
  "hit_medium_1",
  "hit_heavy_1",
  "hit_critical_1",
  
  // Defense sounds
  "block_success_1",
  "block_break_1",
  "dodge_1",
  
  // Movement
  "stance_change_1",
  
  // Music
  "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: false,
    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;
 
    // Load each asset sequentially to avoid overwhelming the browser
    for (const assetId of CRITICAL_COMBAT_ASSETS) {
      try {
        // Get asset from registry (try SFX first, then Music)
        // Need to use type union since getSFX returns SoundEffect and getMusic returns MusicTrack
        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;