WIP: Current state before PVP combat investigation
This commit is contained in:
115
pwa/src/contexts/AudioContext.tsx
Normal file
115
pwa/src/contexts/AudioContext.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { isElectronApp } from '../utils/assetPath';
|
||||
|
||||
interface AudioContextType {
|
||||
masterVolume: number;
|
||||
musicVolume: number;
|
||||
sfxVolume: number;
|
||||
isMuted: boolean;
|
||||
setMasterVolume: (val: number) => void;
|
||||
setMusicVolume: (val: number) => void;
|
||||
setSfxVolume: (val: number) => void;
|
||||
setIsMuted: (val: boolean) => void;
|
||||
playSfx: (path: string, fallbackPath?: string) => void;
|
||||
}
|
||||
|
||||
const AudioContext = createContext<AudioContextType | undefined>(undefined);
|
||||
|
||||
export const AudioProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
// Initialize state from localStorage or defaults
|
||||
const [masterVolume, setMasterVolumeState] = useState(() => {
|
||||
const saved = localStorage.getItem('audio_masterVolume');
|
||||
return saved ? parseFloat(saved) : 1.0;
|
||||
});
|
||||
const [musicVolume, setMusicVolumeState] = useState(() => {
|
||||
const saved = localStorage.getItem('audio_musicVolume');
|
||||
return saved ? parseFloat(saved) : 0.5;
|
||||
});
|
||||
const [sfxVolume, setSfxVolumeState] = useState(() => {
|
||||
const saved = localStorage.getItem('audio_sfxVolume');
|
||||
return saved ? parseFloat(saved) : 0.8;
|
||||
});
|
||||
const [isMuted, setIsMutedState] = useState(() => {
|
||||
const saved = localStorage.getItem('audio_isMuted');
|
||||
return saved ? JSON.parse(saved) : false;
|
||||
});
|
||||
|
||||
// Persistence wrappers
|
||||
const setMasterVolume = (val: number) => {
|
||||
setMasterVolumeState(val);
|
||||
localStorage.setItem('audio_masterVolume', val.toString());
|
||||
};
|
||||
|
||||
const setMusicVolume = (val: number) => {
|
||||
setMusicVolumeState(val);
|
||||
localStorage.setItem('audio_musicVolume', val.toString());
|
||||
};
|
||||
|
||||
const setSfxVolume = (val: number) => {
|
||||
setSfxVolumeState(val);
|
||||
localStorage.setItem('audio_sfxVolume', val.toString());
|
||||
};
|
||||
|
||||
const setIsMuted = (val: boolean) => {
|
||||
setIsMutedState(val);
|
||||
localStorage.setItem('audio_isMuted', JSON.stringify(val));
|
||||
};
|
||||
|
||||
const playSfx = (path: string, fallbackPath?: string) => {
|
||||
if (isMuted) return;
|
||||
|
||||
// Calculate effective volume
|
||||
const effectiveVolume = masterVolume * sfxVolume;
|
||||
if (effectiveVolume <= 0) return;
|
||||
|
||||
// Handle path correction for Electron vs Browser
|
||||
const resolvePath = (p: string) => {
|
||||
if (p.startsWith('http') || p.startsWith('file')) return p;
|
||||
// Ensure leading slash for browser, dot slash for electron relative
|
||||
const cleanPath = p.startsWith('/') ? p.slice(1) : p;
|
||||
return isElectronApp() ? `./${cleanPath}` : `/${cleanPath}`;
|
||||
};
|
||||
|
||||
const primarySrc = resolvePath(path);
|
||||
const audio = new Audio(primarySrc);
|
||||
audio.volume = effectiveVolume;
|
||||
|
||||
const playPromise = audio.play();
|
||||
|
||||
playPromise.catch((error) => {
|
||||
// If primary fails (e.g. 404 or format issue), try fallback
|
||||
console.warn(`SFX failed: ${path}`, error);
|
||||
if (fallbackPath) {
|
||||
const fallbackSrc = resolvePath(fallbackPath);
|
||||
console.log(`Trying fallback SFX: ${fallbackPath}`);
|
||||
const fallbackAudio = new Audio(fallbackSrc);
|
||||
fallbackAudio.volume = effectiveVolume;
|
||||
fallbackAudio.play().catch(e => console.error(`Fallback SFX failed: ${fallbackPath}`, e));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AudioContext.Provider value={{
|
||||
masterVolume,
|
||||
musicVolume,
|
||||
sfxVolume,
|
||||
isMuted,
|
||||
setMasterVolume,
|
||||
setMusicVolume,
|
||||
setSfxVolume,
|
||||
setIsMuted,
|
||||
playSfx
|
||||
}}>
|
||||
{children}
|
||||
</AudioContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAudio = () => {
|
||||
const context = useContext(AudioContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAudio must be used within an AudioProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,6 +1,11 @@
|
||||
import { createContext, useState, useEffect, ReactNode } from 'react'
|
||||
import { createContext, useState, useEffect, ReactNode, useContext } from 'react'
|
||||
import api, { authApi, characterApi, Account, Character } from '../services/api'
|
||||
|
||||
// ... (interface remains same) ...
|
||||
|
||||
export const useAuth = () => useContext(AuthContext)
|
||||
|
||||
|
||||
interface AuthContextType {
|
||||
isAuthenticated: boolean
|
||||
loading: boolean
|
||||
|
||||
Reference in New Issue
Block a user