WIP: Current state before PVP combat investigation

This commit is contained in:
Joan
2026-02-03 12:19:28 +01:00
parent 7f42fd6b7f
commit 0b0a23f500
36 changed files with 2423 additions and 1472 deletions

View 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;
};

View File

@@ -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