Implement simplified XP system with overall counter

- Replace scoring system with XP-based progression
- Task-based XP: 2 XP per regular task, 5 XP per scenario step
- Overall XP counter for rankings/leveling (persistent across sessions)
- No overall XP awarded for quit/abandoned sessions (only completed sessions)
- Session XP always displayed but only added to overall on completion
- Simplified from time/activity bonuses to pure task completion rewards
- Updated UI with dual XP display: Session XP / Total XP
- Audio playlist system continues in background during gameplay
This commit is contained in:
dilgenfritz 2025-10-30 11:41:26 -05:00
parent 3e59456aff
commit 84b17a7930
18 changed files with 2132 additions and 489 deletions

80
AUDIO_SYSTEM_CHANGES.md Normal file
View File

@ -0,0 +1,80 @@
# Simplified Audio System Documentation
## Overview
The audio system has been completely refactored to use a simple, dynamic approach with a streamlined directory structure.
## Simplified Directory Structure
### Current Structure:
```
audio/
├── background/ (Contains all background music/audio clips)
└── ambient/ (Empty for now, reserved for future ambient sounds)
```
### Previous Complex Structure (REMOVED):
- ❌ `audio/effects/`
- ❌ `audio/tasks/teasing/`
- ❌ `audio/tasks/intense/`
- ❌ `audio/tasks/instructions/`
- ❌ `audio/punishments/denial/`
- ❌ `audio/punishments/mocking/`
- ❌ `audio/rewards/completion/`
## How It Works
### Single Background Audio Category
- **Before**: Multiple hardcoded categories (tasks, punishments, rewards, teasing, intense, etc.)
- **After**: One dynamic "background" category that uses user-managed audio files
### Dynamic Audio Discovery
- Audio files are loaded from the existing `customAudio` storage system
- Combines all user audio (background, ambient, effects) into one pool
- No more hardcoded file paths that can break
- Automatically refreshes when users add new audio files
### User Experience
1. **Add Audio**: Use "Manage Audio" menu to import audio files
2. **Auto-Play**: Audio automatically plays during tasks, rewards, and consequences
3. **Single Control**: All audio settings now control the same background audio
4. **No Errors**: No more "file not found" errors from missing hardcoded files
## Technical Changes
### AudioManager Simplification
- `playTaskAudio()``playBackgroundAudio()`
- `playPunishmentAudio()``playBackgroundAudio()`
- `playRewardAudio()``playBackgroundAudio()`
- Single audio category instead of multiple complex categories
### Game Integration
- All game events (task start, completion, skip) play from the same audio pool
- Audio automatically refreshes when users add files via "Manage Audio"
- Settings UI still shows separate controls but they all control the same audio
### Benefits
- ✅ No more audio file path errors
- ✅ User has full control over audio content
- ✅ Simple, reliable system
- ✅ Integrates with existing UI
- ✅ No hardcoded dependencies
## For Users
### To Add Background Audio:
1. Place your audio files in the `audio/background/` directory
2. Open the main menu and click "Manage Audio"
3. Click "🔍 Scan Directories" to discover the files
4. Audio will automatically play during the game
### Audio Controls:
- All volume sliders now control the same background audio
- Enable/disable toggles all control the same background audio
- Preview buttons all preview your background audio
## Migration Notes
- Complex directory structure has been simplified to just two folders
- Existing user audio files in "Manage Audio" will automatically work
- All audio files should be placed in `audio/background/` for now
- `audio/ambient/` is reserved for future ambient sounds (leave empty)
- System is now completely user-controlled and dynamic

113
DIRECTORY_SCANNING.md Normal file
View File

@ -0,0 +1,113 @@
# Audio Directory Scanning Feature
## Overview
The directory scanning feature automatically discovers audio files that are already present in your app's audio directory structure and adds them to your audio library.
## How It Works
### Automatic Scanning
- **On Startup**: The app automatically scans for audio files when it launches (desktop mode only)
- **Manual Scanning**: Use the "🔍 Scan Directories" button in the "Manage Audio" menu
### Scanned Directories
The scanner looks for audio files in these directories:
```
audio/
├── background/
└── ambient/
```
### Supported Audio Formats
- **MP3** (most common)
- **WAV** (high quality)
- **OGG** (open source)
- **M4A** (Apple format)
- **AAC** (compressed)
- **FLAC** (lossless)
## Using Directory Scanning
### Desktop Mode (Recommended)
1. **Place Files**: Copy your audio files into the appropriate audio subdirectories
2. **Scan**: Click "🔍 Scan Directories" in the "Manage Audio" menu
3. **Auto-Use**: Scanned files are automatically added to your library and enabled
### File Organization
- **Background Music**`audio/background/`
- **Ambient Sounds**`audio/ambient/`
## Category Mapping
Due to the simplified audio system, all scanned files are categorized as follows:
- Files from `audio/ambient/`**Ambient** category
- Files from `audio/background/`**Background** category
## Benefits
### Easy Bulk Import
- Add many files at once by copying them to directories
- No need to import files one-by-one through the UI
- Perfect for organizing large audio collections
### Automatic Discovery
- New files are found automatically on app restart
- Manual scan button for immediate discovery
- No configuration required
### File Management
- Files stay in organized directory structure
- Easy to add/remove files outside the app
- Clear organization by purpose/category
## Troubleshooting
### No Files Found
- **Check Directories**: Ensure audio files are in the correct subdirectories
- **File Formats**: Verify files are in supported formats (MP3, WAV, OGG, M4A, AAC, FLAC)
- **Permissions**: Make sure the app has read access to the audio directories
- **File Names**: Avoid special characters in file names
### Duplicate Files
- The scanner checks for existing files before adding
- Files already in your library won't be duplicated
- If you see duplicates, use the "🧹 Cleanup" button
### Web Mode Limitations
- Directory scanning only works in desktop mode (Electron)
- Web browsers can't access file system directories
- Use the Import buttons for manual file upload in web mode
## Tips
### Organizing Your Audio
1. **Create Themed Folders**: Group similar audio files together
2. **Descriptive Names**: Use clear, descriptive file names
3. **Test Files**: Place a few test files first, then scan to verify
### Performance
- Scanning is fast for hundreds of files
- Large files (>100MB) may take longer to process
- The app shows progress messages during scanning
### Backup Strategy
- Keep original files in the audio directories
- The app creates references, not copies
- Easy to backup/restore entire audio collection
## Integration
### With Dynamic Audio System
- Scanned files integrate seamlessly with the dynamic audio system
- All audio management features work with scanned files
- Enable/disable scanned files individually
### With Existing Audio
- Scanned files are added to existing audio library
- No conflicts with manually imported files
- Use both methods together for maximum flexibility
## Next Steps
1. **Organize Files**: Place your audio files in the appropriate directories
2. **Scan**: Use the "🔍 Scan Directories" button
3. **Test**: Try playing audio during a game session
4. **Manage**: Use the audio management UI to enable/disable specific files

View File

@ -8,11 +8,20 @@
- ✅ Scenario system with state management
- ✅ Loading overlay and initialization protection
- ✅ Desktop file management integration
- ✅ Continuous audio playlist system
- ✅ XP-based progression system (replaced scoring)
## 🚧 Active Development
- Enhanced user experience improvements
- Bug fixes and stability enhancements
- Performance optimizations
- **NEW XP System Implementation:**
- Time-based XP: 1 XP per 2 minutes of gameplay
- Focus session bonuses: 5 XP per minute
- Webcam mirror bonuses: 5 XP per minute
- Photo rewards: 1 XP per picture taken
- Main game XP conversion from old point system
- No XP awarded for abandoned/quit sessions
## 📋 Feature Backlog
@ -40,6 +49,13 @@
- [ ] Media organization and tagging features
- [ ] Import/export functionality for user content
- [ ] Content validation and quality checks
- [ ] **Custom Audio Playlist Creation** - Allow users to create and save custom audio playlists with:
- Drag-and-drop playlist editor
- Named playlists (e.g., "Intense Session", "Relaxing Background")
- Shuffle/repeat options per playlist
- Playlist sharing and import/export
- Time-based playlist scheduling (different playlists for different game phases)
- Crossfade and transition effects between tracks
### 🔧 Technical Improvements
- [ ] Code refactoring and optimization

View File

@ -0,0 +1,197 @@
/**
* Audio Compatibility Checker and Browser Mode Handler
* This script helps detect and handle audio limitations in browser vs Electron mode
*/
class AudioCompatibilityChecker {
constructor() {
this.isElectron = window.electronAPI !== undefined;
this.isFileProtocol = window.location.protocol === 'file:';
this.browserAudioSupported = this.checkBrowserAudioSupport();
}
checkBrowserAudioSupport() {
// Test if we can create audio elements
try {
const testAudio = new Audio();
return testAudio !== null;
} catch (error) {
console.error('Browser does not support Audio API:', error);
return false;
}
}
async testSingleAudioFile(path) {
return new Promise((resolve) => {
const audio = new Audio();
let resolved = false;
const cleanup = () => {
if (!resolved) {
resolved = true;
audio.pause();
audio.src = '';
audio.remove();
}
};
audio.addEventListener('canplay', () => {
if (!resolved) {
cleanup();
resolve({ success: true, path, error: null });
}
});
audio.addEventListener('error', (e) => {
if (!resolved) {
const errorInfo = {
code: e.target.error?.code,
message: e.target.error?.message,
networkState: e.target.networkState,
readyState: e.target.readyState,
src: e.target.src
};
cleanup();
resolve({ success: false, path, error: errorInfo });
}
});
// Timeout after 3 seconds
setTimeout(() => {
if (!resolved) {
cleanup();
resolve({ success: false, path, error: 'timeout' });
}
}, 3000);
// Try to load the audio
try {
audio.src = path;
audio.load();
} catch (error) {
if (!resolved) {
cleanup();
resolve({ success: false, path, error: error.message });
}
}
});
}
async quickAudioTest() {
console.log('🎵 Audio Compatibility Test Starting...');
console.log(`🎵 Environment: ${this.isElectron ? 'Electron' : 'Browser'}`);
console.log(`🎵 Protocol: ${window.location.protocol}`);
console.log(`🎵 Audio API Support: ${this.browserAudioSupported}`);
// Test a few known audio files
const testFiles = [
'audio/tasks/teasing/u.mp3',
'audio/rewards/completion/u.mp3'
];
const results = [];
for (const file of testFiles) {
console.log(`🎵 Testing: ${file}`);
const result = await this.testSingleAudioFile(file);
results.push(result);
if (result.success) {
console.log(`${file} - Working`);
} else {
console.log(`${file} - Failed:`, result.error);
}
// Small delay between tests
await new Promise(resolve => setTimeout(resolve, 100));
}
return results;
}
getRecommendations() {
const recommendations = [];
if (!this.isElectron && this.isFileProtocol) {
recommendations.push({
type: 'warning',
message: 'Running in browser with file:// protocol',
suggestion: 'Use "npm start" to run the Electron desktop version for full audio support'
});
}
if (!this.browserAudioSupported) {
recommendations.push({
type: 'error',
message: 'Browser does not support HTML5 Audio API',
suggestion: 'Try a different browser or update your current browser'
});
}
if (this.isElectron) {
recommendations.push({
type: 'success',
message: 'Running in Electron desktop mode',
suggestion: 'Audio should work without restrictions'
});
}
return recommendations;
}
async diagnoseAudioIssues() {
console.log('🎵 Audio Diagnostic Report');
console.log('=' .repeat(50));
const recommendations = this.getRecommendations();
recommendations.forEach(rec => {
const icon = rec.type === 'error' ? '❌' : rec.type === 'warning' ? '⚠️' : '✅';
console.log(`${icon} ${rec.message}`);
console.log(` 💡 ${rec.suggestion}`);
});
console.log('\n🎵 Testing Audio Files...');
const testResults = await this.quickAudioTest();
const workingFiles = testResults.filter(r => r.success).length;
const totalFiles = testResults.length;
console.log('\n🎵 Summary:');
console.log(`✅ Working: ${workingFiles}/${totalFiles}`);
console.log(`❌ Failed: ${totalFiles - workingFiles}/${totalFiles}`);
if (workingFiles === 0) {
console.log('\n🎵 No audio files are working. This suggests:');
console.log(' - Browser CORS restrictions (use Electron app)');
console.log(' - Audio files are corrupted or missing');
console.log(' - Browser audio support issues');
} else if (workingFiles < totalFiles) {
console.log('\n🎵 Some audio files are working. Issues may be:');
console.log(' - Specific file corruption');
console.log(' - Filename/path issues');
console.log(' - Audio format compatibility');
} else {
console.log('\n🎵 All tested files are working! 🎉');
}
return {
environment: {
isElectron: this.isElectron,
isFileProtocol: this.isFileProtocol,
audioSupported: this.browserAudioSupported
},
testResults,
recommendations
};
}
}
// Auto-run diagnostic if audio issues are detected
window.audioCompatibilityChecker = new AudioCompatibilityChecker();
// Quick check for common issues
if (!window.audioCompatibilityChecker.isElectron && window.audioCompatibilityChecker.isFileProtocol) {
console.log('🎵 Audio issues detected! Run window.audioCompatibilityChecker.diagnoseAudioIssues() for full report');
}
console.log('🎵 Audio Compatibility Checker loaded');
console.log('🎵 Run: window.audioCompatibilityChecker.diagnoseAudioIssues()');

143
audio-validator.js Normal file
View File

@ -0,0 +1,143 @@
/**
* Audio File Validator
* This script tests which audio files can actually be loaded by the browser
*/
async function validateAudioFiles() {
console.log('🎵 Starting audio file validation...');
const audioStructure = {
tasks: {
teasing: [
'enjoying-a-giant-cock.mp3',
'horny-japanese-babe-japanese-sex.mp3',
'long-and-hard-moan.mp3',
'playing-her-pussy-with-a-dildo-japanese-sex.mp3',
'u.mp3'
],
intense: [
'bree-olson-screaming-orgasm-sound.mp3',
'carmela-bing-screaming-orgasm-sound.mp3',
'moaning-ringtone.mp3',
'multiple-orgasms-with-creamy-pussy.mp3'
],
instructions: [
'addict-to-my-tits-854x480p.mp3',
'deeper.mp3',
'you-love-to-goon.mp3'
]
},
punishments: {
denial: [
'addicted-to-my-tits-tit-worship-mesmerize-joi-mov.mp3',
'dumb-gooner-bitch-4-big-tits.mp3',
'you-will-be-left-thoughtless.mp3'
],
mocking: [
'precise-denial.mp3',
'punishment-episode-cockring-causes-cumshots-cumshots-gameplay-only.mp3',
'quick-jerk-to-tits-in-pink-bra-1080p-ellie-idol.mp3',
'stroke-your-goon-bud.mp3'
]
},
rewards: {
completion: ['u.mp3']
}
};
const results = {
working: [],
failed: [],
total: 0
};
for (const [category, subcategories] of Object.entries(audioStructure)) {
for (const [subcategory, files] of Object.entries(subcategories)) {
for (const file of files) {
const audioPath = `audio/${category}/${subcategory}/${file}`;
results.total++;
try {
const canLoad = await testAudioFile(audioPath);
if (canLoad) {
results.working.push(audioPath);
console.log(`${audioPath} - OK`);
} else {
results.failed.push(audioPath);
console.log(`${audioPath} - FAILED`);
}
} catch (error) {
results.failed.push(audioPath);
console.log(`${audioPath} - ERROR: ${error.message}`);
}
// Small delay to prevent browser overload
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
console.log('\n🎵 Audio Validation Results:');
console.log(`✅ Working files: ${results.working.length}/${results.total}`);
console.log(`❌ Failed files: ${results.failed.length}/${results.total}`);
if (results.failed.length > 0) {
console.log('\n❌ Failed files:');
results.failed.forEach(file => console.log(` - ${file}`));
}
if (results.working.length > 0) {
console.log('\n✅ Working files:');
results.working.forEach(file => console.log(` - ${file}`));
}
return results;
}
async function testAudioFile(audioPath) {
return new Promise((resolve) => {
const audio = new Audio(audioPath);
let resolved = false;
const cleanup = () => {
if (!resolved) {
resolved = true;
audio.pause();
audio.src = '';
}
};
audio.addEventListener('canplay', () => {
if (!resolved) {
cleanup();
resolve(true);
}
});
audio.addEventListener('error', () => {
if (!resolved) {
cleanup();
resolve(false);
}
});
// Timeout after 5 seconds
setTimeout(() => {
if (!resolved) {
cleanup();
resolve(false);
}
}, 5000);
// Start loading
audio.load();
});
}
// Make functions available globally
window.audioValidator = {
validateAudioFiles,
testAudioFile
};
console.log('🎵 Audio validator loaded. Run: window.audioValidator.validateAudioFiles()');

View File

@ -5,10 +5,8 @@ This is a placeholder for test audio.
## To test the audio system:
1. **Add real audio files** to the appropriate directories:
- `/audio/tasks/teasing/` - Add MP3 files like `tease1.mp3`, `whisper1.mp3`
- `/audio/punishments/denial/` - Add MP3 files like `denied.mp3`, `bad_boy.mp3`
- `/audio/punishments/mocking/` - Add MP3 files like `pathetic.mp3`, `weak.mp3`
- `/audio/rewards/completion/` - Add MP3 files like `good_boy.mp3`, `well_done.mp3`
- `/audio/background/` - Add MP3 files for background music during tasks
- `/audio/ambient/` - Add MP3 files for ambient sounds (optional, leave empty for now)
2. **Test the controls**:
- Open the game and go to Options ⚙️
@ -17,10 +15,10 @@ This is a placeholder for test audio.
- Use preview buttons to test audio
3. **Test in gameplay**:
- Start a task → Should play teasing/intense audio
- Complete a task → Should play reward audio
- Skip a task → Should play mocking audio
- Get a consequence task → Should play denial audio
- Start a task → Should play background audio
- Complete a task → Should play background audio
- Skip a task → Should play background audio
- All audio now comes from the background category
## Current Features Working:

55
clear-audio-debug.js Normal file
View File

@ -0,0 +1,55 @@
// Audio Debug Script - Clear corrupted audio data and trigger fresh scan
console.log('🧹 Starting comprehensive audio cleanup...');
// Clear existing customAudio data completely and restart fresh
if (window.game && window.game.dataManager) {
console.log('🗑️ Clearing all existing audio data to start fresh...');
// Completely reset audio storage
window.game.dataManager.set('customAudio', { background: [], ambient: [] });
console.log('✅ Cleared all customAudio storage');
// Clear any cached audio library
if (window.game.audioManager) {
window.game.audioManager.audioLibrary = {
background: { general: [] }
};
console.log('✅ Cleared audio library cache');
}
// Now trigger a fresh directory scan if available
if (window.game.audioManager && window.game.audioManager.scanAudioDirectories) {
console.log('🔍 Triggering fresh directory scan...');
window.game.audioManager.scanAudioDirectories().then((scannedFiles) => {
console.log(`✅ Fresh scan completed - found ${scannedFiles.length} files`);
// Refresh the audio manager
return window.game.audioManager.refreshAudioLibrary();
}).then(() => {
console.log('✅ Audio library refreshed with clean data');
// Refresh the audio gallery
if (window.game.loadAudioGallery) {
window.game.loadAudioGallery();
console.log('✅ Audio gallery refreshed');
}
}).catch(error => {
console.error('❌ Error during fresh scan:', error);
});
} else {
console.log('⚠️ Directory scanning not available - try the "Scan Directories" button in Manage Audio');
// Just refresh what we have
if (window.game.audioManager) {
window.game.audioManager.refreshAudioLibrary().then(() => {
console.log('✅ Audio library refreshed');
});
}
}
} else {
console.error('❌ Game or dataManager not available');
}
console.log('🎯 Cleanup script completed. Check the console for results and try playing audio.');

33
clear-audio-storage.js Normal file
View File

@ -0,0 +1,33 @@
// Clear Audio Storage Script - Reset all audio data to start fresh
console.log('🧹 Clearing all audio storage data...');
// Clear all audio-related storage
if (window.game && window.game.dataManager) {
console.log('🗑️ Clearing customAudio storage...');
window.game.dataManager.set('customAudio', { background: [], ambient: [] });
console.log('🗑️ Clearing audio library cache...');
if (window.game.audioManager) {
window.game.audioManager.audioLibrary = {
background: { general: [] }
};
}
console.log('🔄 Refreshing audio library...');
if (window.game.audioManager) {
window.game.audioManager.refreshAudioLibrary().then(() => {
console.log('✅ Audio library refreshed');
// Refresh the UI
if (window.game.loadAudioGallery) {
window.game.loadAudioGallery();
console.log('✅ Audio gallery refreshed');
}
});
}
console.log('✅ All audio storage cleared successfully');
} else {
console.error('❌ Game or dataManager not available');
}

147
debug-audio-test.js Normal file
View File

@ -0,0 +1,147 @@
/**
* Audio Overlap Debug Test Script
*
* This script helps test that background sounds stop properly when progressing through tasks.
*
* To use:
* 1. Start the game and begin playing
* 2. Open the browser console (F12)
* 3. Copy and paste this script into the console
* 4. Press Enter to run the test
*
* The test will simulate rapid task progression and monitor for audio overlaps.
*/
console.log('🎵 Starting Audio Overlap Test...');
// Function to test audio stopping
function testAudioStopping() {
if (!window.game || !window.game.audioManager) {
console.error('❌ Game or AudioManager not available');
return;
}
const audioManager = window.game.audioManager;
console.log('🎵 Testing audio category stopping...');
// Test rapid audio switching
let testCount = 0;
const maxTests = 5;
const testInterval = setInterval(() => {
testCount++;
console.log(`🎵 Test ${testCount}/${maxTests}: Playing task audio...`);
// Stop all audio first
audioManager.stopAllImmediate();
// Wait a bit, then play new audio
setTimeout(() => {
audioManager.playTaskAudio('teasing', { fadeIn: 500, loop: true });
// Check how many audio elements are playing after a short delay
setTimeout(() => {
const currentlyPlaying = audioManager.getCurrentlyPlaying();
const playingCount = Object.keys(currentlyPlaying).length;
console.log(`🎵 Test ${testCount}: ${playingCount} audio tracks currently playing`);
console.log('🎵 Playing audio details:', currentlyPlaying);
if (playingCount > 1) {
console.warn(`⚠️ WARNING: ${playingCount} tracks playing simultaneously!`);
} else {
console.log('✅ Audio overlap test passed');
}
}, 100);
}, 50);
if (testCount >= maxTests) {
clearInterval(testInterval);
console.log('🎵 Audio overlap test completed');
// Clean up
setTimeout(() => {
audioManager.stopAllImmediate();
console.log('🎵 Test cleanup completed');
}, 2000);
}
}, 1000);
}
// Function to monitor audio during actual gameplay
function monitorGameplayAudio() {
if (!window.game || !window.game.audioManager) {
console.error('❌ Game or AudioManager not available');
return;
}
console.log('🎵 Starting gameplay audio monitoring...');
console.log('🎵 Quickly complete or skip several tasks to test audio overlap prevention');
// Monitor audio every 500ms
const monitorInterval = setInterval(() => {
const currentlyPlaying = window.game.audioManager.getCurrentlyPlaying();
const playingCount = Object.keys(currentlyPlaying).length;
if (playingCount > 1) {
console.warn(`⚠️ AUDIO OVERLAP DETECTED: ${playingCount} tracks playing!`);
console.log('🎵 Overlapping audio details:', currentlyPlaying);
} else if (playingCount === 1) {
console.log(`✅ Single audio track playing (normal)`);
}
}, 500);
// Stop monitoring after 30 seconds
setTimeout(() => {
clearInterval(monitorInterval);
console.log('🎵 Audio monitoring stopped');
}, 30000);
return monitorInterval;
}
// Function to test rapid task progression simulation
function simulateRapidProgression() {
if (!window.game || !window.game.gameState || !window.game.gameState.isRunning) {
console.error('❌ Game not running - start a game first');
return;
}
console.log('🎵 Simulating rapid task progression...');
let progressCount = 0;
const maxProgress = 3;
const progressInterval = setInterval(() => {
progressCount++;
console.log(`🎵 Rapid progression test ${progressCount}/${maxProgress}`);
// Simulate completing a task (this should stop audio and start new audio)
if (window.game.gameState.isRunning) {
window.game.completeTask();
}
if (progressCount >= maxProgress) {
clearInterval(progressInterval);
console.log('🎵 Rapid progression test completed');
}
}, 200); // Very fast progression to test overlap
}
// Run the tests
console.log('🎵 Audio Debug Test Menu:');
console.log('🎵 1. Run testAudioStopping() - Tests basic audio stopping');
console.log('🎵 2. Run monitorGameplayAudio() - Monitors for overlaps during gameplay');
console.log('🎵 3. Run simulateRapidProgression() - Simulates rapid task progression');
console.log('🎵 Example: testAudioStopping()');
// Make functions available globally for manual testing
window.audioDebugTest = {
testAudioStopping,
monitorGameplayAudio,
simulateRapidProgression
};
console.log('🎵 Functions available as window.audioDebugTest.*');
console.log('🎵 Quick test: window.audioDebugTest.testAudioStopping()');

105
electron-audio-debug.js Normal file
View File

@ -0,0 +1,105 @@
/**
* Electron Audio Path Debug Script
* Run this in the console to debug audio path issues in Electron
*/
function debugElectronAudioPaths() {
console.log('🔧 Electron Audio Path Debug');
console.log('=' .repeat(50));
console.log('Environment Info:');
console.log('- Is Electron:', window.electronAPI !== undefined);
console.log('- Current URL:', window.location.href);
console.log('- Current Protocol:', window.location.protocol);
console.log('- Current Directory:', window.location.href.replace('/index.html', ''));
// Test basic audio creation
console.log('\n🎵 Testing Basic Audio Creation:');
try {
const testAudio = new Audio();
console.log('✅ Audio element created successfully');
console.log('- Initial src:', testAudio.src);
// Test setting a simple relative path
testAudio.src = 'audio/tasks/teasing/u.mp3';
console.log('- After setting relative path:', testAudio.src);
// Test setting an absolute file path
const absolutePath = window.location.href.replace('/index.html', '') + '/audio/tasks/teasing/u.mp3';
testAudio.src = absolutePath;
console.log('- After setting absolute path:', testAudio.src);
testAudio.src = '';
} catch (error) {
console.error('❌ Error creating audio element:', error);
}
// Test actual file access
console.log('\n📁 Testing File Access:');
const testPaths = [
'audio/tasks/teasing/u.mp3',
'./audio/tasks/teasing/u.mp3',
window.location.href.replace('/index.html', '') + '/audio/tasks/teasing/u.mp3'
];
testPaths.forEach((path, index) => {
console.log(`\nTest ${index + 1}: ${path}`);
const audio = new Audio();
audio.addEventListener('loadstart', () => {
console.log(` ✅ Load started for: ${path}`);
});
audio.addEventListener('canplay', () => {
console.log(` ✅ Can play: ${path}`);
audio.src = ''; // Clean up
});
audio.addEventListener('error', (e) => {
console.log(` ❌ Error loading: ${path}`);
console.log(` Error details:`, {
error: e.target.error,
src: e.target.src,
networkState: e.target.networkState,
readyState: e.target.readyState
});
});
try {
audio.src = path;
setTimeout(() => {
if (audio.readyState === 0 && audio.networkState !== 2) {
console.log(` ⏰ Timeout for: ${path}`);
}
}, 2000);
} catch (error) {
console.log(` ❌ Exception setting src: ${error.message}`);
}
});
// Test if electronAPI is available and working
if (window.electronAPI) {
console.log('\n⚡ Testing Electron API:');
window.electronAPI.fileExists('audio/tasks/teasing/u.mp3').then(exists => {
console.log('- File exists (relative):', exists);
}).catch(err => {
console.log('- Error checking file existence:', err);
});
window.electronAPI.getAppPath().then(appPath => {
console.log('- App path:', appPath);
}).catch(err => {
console.log('- Error getting app path:', err);
});
}
}
// Auto-run the debug
console.log('🔧 Electron Audio Debug loaded. Running automatic test...');
debugElectronAudioPaths();
// Make function available globally
window.debugElectronAudioPaths = debugElectronAudioPaths;

View File

@ -130,22 +130,22 @@
</label>
</div>
<div class="game-mode-option">
<input type="radio" id="mode-score-target" name="gameMode" value="score-target">
<label for="mode-score-target">
<strong>🏆 Score Target</strong>
<p>Reach the target score to win</p>
<div class="mode-config" id="score-target-config" style="display: none;">
<label>Target Score:
<select id="score-target-select">
<option value="100">100 points</option>
<option value="200">200 points</option>
<option value="300">300 points</option>
<input type="radio" id="mode-xp-target" name="gameMode" value="xp-target">
<label for="mode-xp-target">
<strong>🏆 XP Target</strong>
<p>Reach the target XP to win</p>
<div class="mode-config" id="xp-target-config" style="display: none;">
<label>Target XP:
<select id="xp-target-select">
<option value="100">100 XP</option>
<option value="200">200 XP</option>
<option value="300">300 XP</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-score-input" style="display: none; margin-top: 10px;">
<label>Custom target score:
<input type="number" id="custom-score-value" min="50" max="10000" value="300" style="width: 80px;">
<div id="custom-xp-input" style="display: none; margin-top: 10px;">
<label>Custom target XP:
<input type="number" id="custom-xp-value" min="50" max="10000" value="300" style="width: 80px;">
</label>
</div>
</div>
@ -395,7 +395,6 @@
<div class="upload-controls">
<button id="import-background-audio-btn" class="btn btn-primary">🎶 Background Music</button>
<button id="import-ambient-audio-btn" class="btn btn-success">🌿 Ambient Sounds</button>
<button id="import-effects-audio-btn" class="btn btn-warning">🔊 Sound Effects</button>
<input type="file" id="audio-upload-input" accept="audio/*" multiple style="display: none;">
</div>
<div class="upload-info desktop-feature">
@ -405,6 +404,7 @@
<span>🌐 Web: Limited browser upload functionality</span>
</div>
<div class="directory-controls">
<button id="scan-audio-directories-btn" class="btn btn-primary">🔍 Scan Directories</button>
<button id="audio-storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
<button id="cleanup-invalid-audio-btn" class="btn btn-warning">🧹 Cleanup</button>
<button id="clear-all-audio-btn" class="btn btn-danger">🗑️ Clear All</button>
@ -429,7 +429,6 @@
<div class="audio-tabs">
<button id="background-audio-tab" class="tab-btn active">🎶 Background</button>
<button id="ambient-audio-tab" class="tab-btn">🌿 Ambient</button>
<button id="effects-audio-tab" class="tab-btn">🔊 Effects</button>
</div>
<div class="audio-galleries-container">
@ -440,10 +439,6 @@
<div id="ambient-audio-gallery" class="audio-gallery">
<!-- Ambient audio files will be populated here -->
</div>
<div id="effects-audio-gallery" class="audio-gallery">
<!-- Effects audio files will be populated here -->
</div>
</div>
</div>
@ -1438,12 +1433,6 @@
<!-- Game Screen -->
<div id="game-screen" class="screen">
<div class="task-container">
<div class="task-type">
<span id="task-type-indicator">MAIN TASK</span>
<span id="task-difficulty" class="task-difficulty">MEDIUM</span>
<span id="task-points" class="task-points">+3 pts</span>
</div>
<div class="task-image-container">
<img id="task-image" src="" alt="Task Image" class="task-image">
</div>
@ -1465,8 +1454,12 @@
<div class="game-stats">
<div class="stat">
<span class="stat-label">Score:</span>
<span id="score" class="stat-value">0</span>
<span class="stat-label">Session XP:</span>
<span id="xp" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">Total XP:</span>
<span id="overall-xp" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">Completed:</span>
@ -1501,7 +1494,7 @@
<p>Congratulations! You've completed all available tasks!</p>
<div id="final-stats" class="final-stats">
<p><strong>Game Mode:</strong> <span id="final-game-mode"></span></p>
<p>Final Score: <span id="final-score"></span> points</p>
<p>Final XP: <span id="final-xp"></span> XP</p>
<p>Final Time: <span id="final-time"></span></p>
<p>Tasks Completed: <span id="final-completed"></span></p>
<p>Tasks Skipped: <span id="final-skipped"></span></p>

File diff suppressed because it is too large Load Diff

View File

@ -4,199 +4,140 @@ const gameData = {
mainTasks: [
{
id: 1,
text: "Do 20 jumping jacks",
text: "Hold your breath for 30 seconds while maintaining focus",
difficulty: "Easy"
},
{
id: 2,
text: "Write down 3 things you're grateful for",
text: "Close your eyes and count to 20 without rushing",
difficulty: "Easy"
},
{
id: 3,
text: "Call a friend or family member",
text: "Keep your hands completely still for 45 seconds",
difficulty: "Medium"
},
{
id: 4,
text: "Organize your desk or workspace",
text: "Stare at a fixed point without blinking for 30 seconds",
difficulty: "Medium"
},
{
id: 5,
text: "Take 10 deep breaths",
text: "Take 10 slow, deep breaths while focusing on control",
difficulty: "Easy"
},
{
id: 6,
text: "Read for 15 minutes",
text: "Hold a stress position (arms extended) for 60 seconds",
difficulty: "Medium"
},
{
id: 7,
text: "Do a 5-minute stretch routine",
text: "Repeat 'I am learning self-control' 10 times slowly",
difficulty: "Easy"
},
{
id: 8,
text: "Write in a journal for 10 minutes",
text: "Maintain perfect posture for 2 minutes without moving",
difficulty: "Medium"
},
{
id: 9,
text: "Listen to your favorite song and dance",
text: "Edge for 30 seconds then stop immediately",
difficulty: "Easy"
},
{
id: 10,
text: "Make your bed perfectly",
text: "Close your eyes and focus on breathing for 90 seconds",
difficulty: "Easy"
},
{
id: 11,
text: "Drink a full glass of water",
text: "Hold a challenging yoga pose for 60 seconds",
difficulty: "Easy"
},
{
id: 12,
text: "Compliment someone genuinely",
text: "Count backwards from 100 by 7s without losing focus",
difficulty: "Medium"
},
{
id: 13,
text: "Do 50 push-ups (or modified push-ups)",
text: "Edge for 2 minutes with 3 stops, then wait 30 seconds",
difficulty: "Hard"
},
{
id: 14,
text: "Meditate for 20 minutes",
text: "Hold a plank position for 3 minutes while edging",
difficulty: "Hard"
},
{
id: 15,
text: "Clean and organize an entire room",
text: "Practice edging with varying speeds for 5 minutes",
difficulty: "Hard"
},
{
id: 16,
text: "Learn 10 new words in a foreign language",
text: "Complete a 10-minute endurance session with no release",
difficulty: "Hard"
},
{
id: 17,
text: "Write a one-page story or essay",
text: "Edge to near climax 5 times, then stop for 2 minutes",
difficulty: "Hard"
},
// Interactive Tasks
{
id: 18,
text: "Master the rhythm challenge",
difficulty: "Medium",
interactiveType: "rhythm-tap",
interactiveData: {
beats: 8,
bpm: 120,
tolerance: 300,
requiredAccuracy: 70
},
hint: "Listen to the beat and tap in rhythm. Don't worry about being perfect - 70% accuracy is enough!"
},
{
id: 19,
text: "Prove your focus with precision timing",
difficulty: "Hard",
interactiveType: "rhythm-tap",
interactiveData: {
beats: 12,
bpm: 140,
tolerance: 200,
requiredAccuracy: 85
},
hint: "This is a harder rhythm challenge. Stay focused and maintain the beat!"
},
{
id: 20,
text: "Quick rhythm warm-up",
difficulty: "Easy",
interactiveType: "rhythm-tap",
interactiveData: {
beats: 6,
bpm: 100,
tolerance: 400,
requiredAccuracy: 60
},
hint: "A gentle rhythm to get you started. Just follow the beat!"
},
// Add more interactive tasks for testing
{
id: 21,
text: "Test your rhythm skills",
difficulty: "Medium",
interactiveType: "rhythm-tap",
interactiveData: {
beats: 10,
bpm: 110,
tolerance: 350,
requiredAccuracy: 65
},
hint: "Keep steady and follow the beat!"
},
{
id: 22,
text: "Advanced rhythm challenge",
difficulty: "Hard",
interactiveType: "rhythm-tap",
interactiveData: {
beats: 16,
bpm: 160,
tolerance: 150,
requiredAccuracy: 90
},
hint: "This is intense! Focus and nail those beats!"
}
],
// Consequence tasks - these are triggered when main tasks are skipped
consequenceTasks: [
{
id: 101,
text: "Do 30 push-ups (or modified push-ups)"
text: "Hold the edge for 60 seconds without any movement"
},
{
id: 102,
text: "Clean the bathroom mirror and sink"
text: "Perform 50 squats while maintaining arousal"
},
{
id: 103,
text: "Stand on one foot for 2 minutes"
text: "Stay at the edge for 2 minutes with hands behind back"
},
{
id: 104,
text: "Write 'I will not skip tasks' 20 times"
text: "Write 'I will practice self-control' 25 times"
},
{
id: 105,
text: "Hold a plank position for 1 minute"
text: "Hold a wall sit for 3 minutes while edging"
},
{
id: 106,
text: "Recite the alphabet backwards"
text: "Edge for 30 seconds then cold shower for 1 minute"
},
{
id: 107,
text: "Do 50 jumping jacks"
text: "Maintain edge position for 5 minutes without touching"
},
{
id: 108,
text: "Wash 5 dishes by hand"
text: "Complete 100 push-ups in sets while staying aroused"
},
{
id: 109,
text: "Memorize and recite a short poem"
text: "Kneel in stress position for 10 minutes, no relief"
},
{
id: 110,
text: "Vacuum one room completely"
text: "Edge to near climax then stop for 5 minutes - repeat 3 times"
},
{
id: 111,
text: "Perform degrading poses while edging for 3 minutes"
},
{
id: 112,
text: "Worship your training material for 10 minutes, no touching"
}
],
@ -210,129 +151,129 @@ const gameData = {
// Placeholder images for tasks that don't have specific images
defaultImage: "images/placeholder.jpg",
// Flash Message System - Default encouraging messages
// Flash Message System - Training focused messages
defaultFlashMessages: [
// Motivational messages
{
id: 1,
text: "You're doing amazing! Keep going!",
text: "Your self-control is improving! Keep training!",
category: "motivational",
enabled: true
},
{
id: 2,
text: "Every task completed makes you stronger!",
text: "Every edge builds your endurance!",
category: "motivational",
enabled: true
},
{
id: 3,
text: "Progress, not perfection!",
text: "Discipline over impulse!",
category: "motivational",
enabled: true
},
{
id: 4,
text: "You've got this! Stay focused!",
text: "You're learning to control yourself! Stay focused!",
category: "motivational",
enabled: true
},
{
id: 5,
text: "Small steps lead to big changes!",
text: "Small steps lead to better self-control!",
category: "motivational",
enabled: true
},
// Encouraging messages
{
id: 6,
text: "Your dedication is inspiring!",
text: "Your dedication to training is impressive!",
category: "encouraging",
enabled: true
},
{
id: 7,
text: "Look how far you've come already!",
text: "Look how much your endurance has improved!",
category: "encouraging",
enabled: true
},
{
id: 8,
text: "You're building great habits!",
text: "You're building incredible self-discipline!",
category: "encouraging",
enabled: true
},
{
id: 9,
text: "Believe in yourself - you're capable of amazing things!",
text: "Master your desires - you have the strength!",
category: "encouraging",
enabled: true
},
{
id: 10,
text: "Your future self will thank you!",
text: "Your future self will appreciate this training!",
category: "encouraging",
enabled: true
},
// Achievement messages
{
id: 11,
text: "Great job completing that task!",
text: "Excellent control on that task!",
category: "achievement",
enabled: true
},
{
id: 12,
text: "You're on fire! Keep the streak alive!",
text: "You're on fire! Maintain that discipline!",
category: "achievement",
enabled: true
},
{
id: 13,
text: "Another win in the books!",
text: "Another victory for self-control!",
category: "achievement",
enabled: true
},
{
id: 14,
text: "Excellence in action!",
text: "Perfect execution!",
category: "achievement",
enabled: true
},
{
id: 15,
text: "You're crushing your goals!",
text: "You're mastering your training goals!",
category: "achievement",
enabled: true
},
// Persistence messages
{
id: 16,
text: "Don't give up now - you're so close!",
text: "Don't give in now - show your discipline!",
category: "persistence",
enabled: true
},
{
id: 17,
text: "Every challenge is an opportunity to grow!",
text: "Every challenge strengthens your self-control!",
category: "persistence",
enabled: true
},
{
id: 18,
text: "Push through - greatness awaits!",
text: "Push through - mastery awaits!",
category: "persistence",
enabled: true
},
{
id: 19,
text: "You're stronger than any excuse!",
text: "You're stronger than any urge!",
category: "persistence",
enabled: true
},
{
id: 20,
text: "Champions are made in moments like this!",
text: "True control is forged in moments like this!",
category: "persistence",
enabled: true
}
@ -402,4 +343,4 @@ gameData.mainTasks.push({
});
console.log('🔍 gameData.mainTasks after:', gameData.mainTasks.length);
console.log('✅ Mirror task added successfully');
console.log('✅ Mirror task added successfully');

View File

@ -6,40 +6,21 @@ class AudioManager {
constructor(dataManager, game = null) {
this.dataManager = dataManager;
this.game = game; // Reference to game instance for state checking
this.audioEnabled = true; // Global flag to disable all audio
this.isPlaylistMode = false; // New playlist mode flag
this.currentPlaylistAudio = null; // Current playlist audio element
this.playlistQueue = []; // Queue of audio files to play
this.currentPlaylistIndex = 0; // Current position in playlist
this.audioContext = null;
this.masterGain = null;
// Audio category configurations
// Audio category configurations - simplified to just background audio
this.categories = {
tasks: {
background: {
volume: 0.7,
enabled: true,
currentAudio: null,
fadeTimeout: null
},
punishments: {
volume: 0.8,
enabled: true,
currentAudio: null,
fadeTimeout: null
},
ambient: {
volume: 0.3,
enabled: true,
currentAudio: null,
fadeTimeout: null
},
rewards: {
volume: 0.6,
enabled: true,
currentAudio: null,
fadeTimeout: null
},
instructions: {
volume: 0.9,
enabled: true,
currentAudio: null,
fadeTimeout: null
}
};
@ -53,6 +34,23 @@ class AudioManager {
async init() {
console.log('AudioManager initializing...');
// Check if we're in a problematic browser environment
const isElectron = window.electronAPI !== undefined;
const isFileProtocol = window.location.protocol === 'file:';
if (!isElectron && isFileProtocol) {
console.warn('⚠️ Browser file:// mode detected - audio may not work due to CORS restrictions');
console.warn('💡 For full audio support, run: npm start (Electron desktop app)');
// Option to disable audio in browser mode
const disableAudio = localStorage.getItem('disableBrowserAudio') === 'true';
if (disableAudio) {
console.log('🔇 Audio disabled for browser mode');
this.isInitialized = true;
return;
}
}
// Load saved settings
this.loadSettings();
@ -74,82 +72,146 @@ class AudioManager {
console.log('Available audio categories:', Object.keys(this.audioLibrary));
}
async discoverAudioLibrary() {
// Define the actual file structure based on user's files
const audioStructure = {
tasks: {
teasing: [
'enjoying-a-giant-cock.mp3',
'horny-japanese-babe-japanese-sex.mp3',
'long-and-hard-moan.mp3',
'orgasm-sounds.mp3',
'playing-her-pussy-with-a-dildo-japanese-sex.mp3',
'u.mp3'
],
intense: [], // Will use teasing files if empty
instructions: [] // Will use teasing files if empty
},
punishments: {
denial: [
'addicted-to-my-tits-tit-worship-mesmerize-joi-mov.mp3',
'dumb-gooner-bitch-4-big-tits.mp3',
'you-will-be-left-thoughtless.mp3'
],
mocking: [
'precise-denial.mp3',
'punishment-episode-cockring-causes-cumshots-cumshots-gameplay-only.mp3',
'quick-jerk-to-tits-in-pink-bra-1080p-ellie-idol.mp3',
'stroke-your-goon-bud.mp3'
]
},
ambient: {
background: []
},
rewards: {
completion: ['u.mp3']
}
};
// Initialize the library structure
this.audioLibrary = {};
for (const [category, subcategories] of Object.entries(audioStructure)) {
this.audioLibrary[category] = {};
for (const [subcategory, files] of Object.entries(subcategories)) {
this.audioLibrary[category][subcategory] = [];
// If subcategory is empty, fallback to teasing files for tasks
let filesToUse = files;
if (files.length === 0 && category === 'tasks') {
filesToUse = audioStructure.tasks.teasing;
}
// Check which files actually exist and add them
for (const file of filesToUse) {
const audioPath = `audio/${category}/${subcategory}/${file}`;
// For empty subcategories using fallback, use teasing path
const actualPath = (files.length === 0 && category === 'tasks')
? `audio/tasks/teasing/${file}`
: audioPath;
try {
// Add to library - we'll test loading when actually playing
this.audioLibrary[category][subcategory].push({
name: file.split('.')[0],
path: actualPath,
element: null // Will be created when needed
});
console.log(`Added audio file: ${actualPath}`);
} catch (error) {
console.log(`Error adding audio file: ${actualPath}`, error);
}
}
}
// Clean up corrupted audio data (paths with URL encoding issues)
async cleanupCorruptedAudioData() {
if (!window.game || !window.game.dataManager) {
return;
}
console.log('Audio library discovered:', this.audioLibrary);
const customAudio = window.game.dataManager.get('customAudio') || { background: [], ambient: [] };
let hasCorruption = false;
// Check for and fix corrupted paths
['background', 'ambient'].forEach(category => {
if (customAudio[category]) {
customAudio[category] = customAudio[category].filter(audioFile => {
// Remove files with corrupted paths (containing URL encoding artifacts)
if (audioFile.path && audioFile.path.includes('%08')) {
console.log(`Removing corrupted audio file: ${audioFile.path}`);
hasCorruption = true;
return false;
}
return true;
});
}
});
// Remove any references to old 'effects' category
if (customAudio.effects) {
console.log('Removing deprecated effects category');
delete customAudio.effects;
hasCorruption = true;
}
if (hasCorruption) {
console.log('🧹 Cleaned up corrupted audio data');
window.game.dataManager.set('customAudio', customAudio);
}
}
async discoverAudioLibrary() {
console.log('🎵 Discovering audio library from user-managed files...');
// Clean up any corrupted audio data first
await this.cleanupCorruptedAudioData();
// Instead of hardcoded files, use the dynamic audio management system
// This integrates with the existing "Manage Audio" menu that users can control
// Initialize the library structure
this.audioLibrary = {
background: {
general: [] // Single category for all background audio
}
};
// Get audio files from the existing audio management system
if (window.game && window.game.dataManager) {
const customAudio = window.game.dataManager.get('customAudio') || { background: [], ambient: [] };
// Combine all audio categories into a single "background" category
const allAudioFiles = [
...(customAudio.background || []),
...(customAudio.ambient || [])
];
// Add user's audio files to the library (only if they actually exist)
const validFiles = [];
for (const audioFile of allAudioFiles) {
if (audioFile.enabled !== false) { // Only include enabled files
// Debug path corruption issue
console.log(`Processing audio file:`, audioFile);
console.log(`Path before adding:`, audioFile.path);
// Fix path corruption - decode any URL encoding and normalize path
let cleanPath = audioFile.path;
if (cleanPath.includes('%')) {
try {
cleanPath = decodeURIComponent(cleanPath);
console.log(`Decoded path: ${cleanPath}`);
} catch (e) {
console.warn(`Failed to decode path: ${cleanPath}`, e);
}
}
// For Electron with absolute Windows paths, keep them as-is
// Don't convert backslashes to forward slashes for Windows absolute paths
if (window.electronAPI && cleanPath.match(/^[A-Za-z]:\\/)) {
// Windows absolute path - convert backslashes to forward slashes for web compatibility
cleanPath = cleanPath.replace(/\\/g, '/');
console.log(`Normalized Windows path: ${cleanPath}`);
}
// For Electron, validate file exists before adding
if (window.electronAPI && window.electronAPI.fs) {
try {
const exists = await window.electronAPI.fs.existsSync(cleanPath.replace(/\//g, '\\'));
if (!exists) {
console.warn(`⚠️ File does not exist, skipping: ${cleanPath}`);
continue;
}
} catch (error) {
console.warn(`⚠️ Could not verify file existence: ${cleanPath}`, error);
continue;
}
}
const audioFileObj = {
name: audioFile.name || 'Unnamed Audio',
path: cleanPath,
element: null
};
this.audioLibrary.background.general.push(audioFileObj);
validFiles.push(audioFileObj);
console.log(`Added user audio file: ${cleanPath}`);
}
}
// Update storage to remove non-existent files
if (validFiles.length !== allAudioFiles.length && window.game && window.game.dataManager) {
console.log(`🧹 Removing ${allAudioFiles.length - validFiles.length} non-existent files from storage`);
const updatedCustomAudio = {
background: validFiles.filter(f => f.path.includes('/background/')),
ambient: validFiles.filter(f => f.path.includes('/ambient/'))
};
window.game.dataManager.set('customAudio', updatedCustomAudio);
}
console.log(`🎵 Loaded ${validFiles.length} user audio files`);
}
// If no user audio is available, create empty structure
if (this.audioLibrary.background.general.length === 0) {
console.log('🎵 No user audio files found.');
console.log('🎵 To add background audio:');
console.log(' 1. Go to the main menu');
console.log(' 2. Click "Manage Audio"');
console.log(' 3. Import audio files using the "Import" buttons');
console.log(' 4. Audio will automatically play during tasks');
}
console.log('🎵 Audio library discovery completed:', this.audioLibrary);
}
loadSettings() {
@ -231,11 +293,29 @@ class AudioManager {
async playAudio(category, subcategory, fileName = null, options = {}) {
console.log(`PlayAudio called: ${category}/${subcategory}/${fileName || 'random'}`);
// Check if audio is globally disabled
if (!this.audioEnabled) {
console.log('Audio globally disabled - blocking playback');
return null;
}
// Check if we're in a skip audio state (for consequences)
if (this.game && this.game.gameState && this.game.gameState.skipAudioPlayed) {
console.log('Skip audio played flag set - blocking background audio during consequence transition');
return null;
}
if (!this.isInitialized) {
console.warn('AudioManager not initialized yet');
return null;
}
// Check if we're in browser mode and warn about limitations
const isElectron = window.electronAPI !== undefined;
if (!isElectron) {
console.warn('Running in browser mode - audio may have limitations due to file:// restrictions');
}
// Check if the game is still running - prevent audio after game ends
if (this.game && this.game.gameState && !this.game.gameState.isRunning) {
console.log(`Game is not running - blocking audio playback for ${category}/${subcategory}`);
@ -275,31 +355,120 @@ class AudioManager {
console.log(`Selected audio file: ${audioFile.path}`);
// Validate the audio file path
if (!audioFile.path || audioFile.path.trim() === '') {
console.error(`Invalid audio file path: ${audioFile.path}`);
return null;
}
// Stop any currently playing audio in this category immediately and synchronously
if (this.categories[category].currentAudio) {
const existingAudio = this.categories[category].currentAudio;
existingAudio.pause();
existingAudio.currentTime = 0;
existingAudio.src = ''; // Clear source to fully stop
this.categories[category].currentAudio = null;
console.log(`Forcibly stopped existing audio in category: ${category}`);
if (this.categories[category].currentAudio) {
const existingAudio = this.categories[category].currentAudio;
existingAudio.pause();
existingAudio.currentTime = 0;
existingAudio._stopped = true; // Mark as stopped to prevent further playback
console.log(`[src debug] Stopping previous audio:`, existingAudio.src);
// Don't clear src as it causes corruption - just pause and reset time
// Also remove from DOM if it exists
if (existingAudio.parentNode) {
existingAudio.parentNode.removeChild(existingAudio);
}
this.categories[category].currentAudio = null;
console.log(`Forcibly stopped existing audio in category: ${category}`);
}
// Clear any pending fade timeout for this category
if (this.categories[category].fadeTimeout) {
clearTimeout(this.categories[category].fadeTimeout);
this.categories[category].fadeTimeout = null;
}
// Create and configure audio element
const audio = new Audio(audioFile.path);
const audio = new Audio();
audio._stopped = false; // Flag to prevent orphaned playback
// Set the source with proper error handling
try {
console.log(`Setting audio source to: ${audioFile.path}`);
console.log(`Audio file object:`, audioFile);
// Debug: Check if path is already corrupted
if (audioFile.path.includes('%')) {
console.error(`Path already contains URL encoding: ${audioFile.path}`);
}
// For Electron, construct proper file:// URL for absolute paths
let audioSrc = audioFile.path;
if (window.electronAPI && audioFile.path.match(/^[A-Za-z]:\//)) {
// Absolute Windows path - convert to file:// URL
audioSrc = `file:///${audioFile.path}`;
console.log(`Converted to file URL: ${audioSrc}`);
} else if (window.electronAPI && !audioFile.path.startsWith('file://')) {
// Relative path in Electron - make it relative to app root
audioSrc = audioFile.path;
console.log(`Using relative path: ${audioSrc}`);
}
console.log(`[src debug] Setting audio.src to:`, audioSrc);
audio.src = audioSrc;
console.log(`[src debug] audio.src after assignment:`, audio.src);
} catch (error) {
console.error(`Error setting audio source: ${audioFile.path}`, error);
return null;
}
audio.volume = this.categories[category].volume * this.masterVolume;
audio.loop = options.loop || false;
console.log(`Audio volume set to: ${audio.volume} (category: ${this.categories[category].volume}, master: ${this.masterVolume})`);
// Add error handling
// Add comprehensive error handling with retry logic
audio.addEventListener('error', (e) => {
console.error(`Audio file failed to load: ${audioFile.path}`, e);
console.error('Error details:', {
error: e.target.error,
networkState: e.target.networkState,
readyState: e.target.readyState
readyState: e.target.readyState,
src: e.target.src,
srcAttribute: audio.getAttribute('src'),
srcProperty: audio.src,
errorCode: e.target.error?.code,
errorMessage: e.target.error?.message
});
// Clean up failed audio element
if (this.categories[category].currentAudio === audio) {
this.categories[category].currentAudio = null;
}
// If this was a specific file request and it failed, don't retry
if (fileName) {
console.log(`Specific audio file ${fileName} failed, not retrying`);
return;
}
// For browser mode with file:// restrictions, disable retries to prevent spam
const isElectron = window.electronAPI !== undefined;
if (!isElectron && window.location.protocol === 'file:') {
console.warn('Browser file:// mode detected - disabling audio retries due to CORS restrictions');
return;
}
// For random selection, try a different file if available
const remainingFiles = audioFiles.filter(f => f !== audioFile);
if (remainingFiles.length > 0) {
console.log(`Retrying with different audio file from ${category}/${subcategory}`);
// Try again with a different random file after a short delay
setTimeout(() => {
// Check if game is still running before retrying
if (!window.game || !window.game.running) {
console.log('Game not running - canceling audio retry');
return;
}
this.playAudio(category, subcategory, null, options);
}, 500);
}
});
audio.addEventListener('loadstart', () => {
@ -307,9 +476,17 @@ class AudioManager {
});
audio.addEventListener('canplay', () => {
if (audio._stopped) {
console.log(`Audio ready but stopped - not playing: ${audioFile.path}`);
return;
}
console.log(`Audio ready to play: ${audioFile.path}`);
});
audio.addEventListener('loadeddata', () => {
console.log(`Audio data loaded: ${audioFile.path}`);
});
// Set up event handlers
audio.addEventListener('ended', () => {
if (this.categories[category].currentAudio === audio) {
@ -317,14 +494,23 @@ class AudioManager {
}
});
audio.addEventListener('error', (e) => {
console.error(`Error playing audio ${audioFile.path}:`, e);
});
// Store reference and play
// Store reference
this.categories[category].currentAudio = audio;
// Use a small delay before attempting to play to ensure audio element is ready
try {
// First, load the audio
audio.load();
// Small delay to ensure the audio element is properly initialized
await new Promise(resolve => setTimeout(resolve, 50));
// Check if audio was stopped while loading
if (audio._stopped) {
console.log(`Audio was stopped while loading - not playing: ${audioFile.path}`);
return null;
}
await audio.play();
console.log(`Playing audio: ${category}/${subcategory}/${audioFile.name}`);
@ -336,6 +522,13 @@ class AudioManager {
return audio;
} catch (error) {
console.error(`Failed to play audio: ${audioFile.path}`, error);
console.error(`Play error details:`, {
name: error.name,
message: error.message,
audioSrc: audio.src,
audioReadyState: audio.readyState,
audioNetworkState: audio.networkState
});
this.categories[category].currentAudio = null;
return null;
}
@ -349,17 +542,30 @@ class AudioManager {
const audio = this.categories[category].currentAudio;
console.log(`Stopping audio in category: ${category} with fadeOut: ${fadeOut}ms`);
if (fadeOut > 0) {
this.fadeOut(audio, fadeOut, () => {
audio.pause();
audio.currentTime = 0; // Reset to beginning
console.log(`[src debug] Clearing src of stopped audio:`, audio.src);
audio.src = '';
console.log(`[src debug] src after clearing:`, audio.src);
// Also remove from DOM if it exists
if (audio.parentNode) {
audio.parentNode.removeChild(audio);
}
this.categories[category].currentAudio = null;
console.log(`Audio stopped in category: ${category}`);
});
} else {
audio.pause();
audio.currentTime = 0; // Reset to beginning
audio._stopped = true; // Mark as stopped to prevent further playback
console.log(`[src debug] Stopping audio:`, audio.src);
// Don't clear src as it causes corruption - just pause and reset time
// Also remove from DOM if it exists
if (audio.parentNode) {
audio.parentNode.removeChild(audio);
}
this.categories[category].currentAudio = null;
console.log(`Audio immediately stopped in category: ${category}`);
}
@ -374,12 +580,26 @@ class AudioManager {
// Immediately stop all audio without fade
stopAllImmediate() {
console.log('Stopping all audio immediately...');
// Stop playlist first
this.stopPlaylist();
// Globally disable audio to prevent any new playback
this.audioEnabled = false;
for (const category in this.categories) {
if (this.categories[category].currentAudio) {
const audio = this.categories[category].currentAudio;
audio.pause();
audio.currentTime = 0;
audio.src = ''; // Clear source to fully stop
audio._stopped = true; // Mark as stopped to prevent further playback
// Don't clear src as it causes corruption - just pause and reset
// Also remove from DOM if it exists
if (audio.parentNode) {
audio.parentNode.removeChild(audio);
}
this.categories[category].currentAudio = null;
console.log(`Forcibly stopped audio in category: ${category}`);
}
@ -392,6 +612,161 @@ class AudioManager {
}
}
// Re-enable audio (call when game starts)
enableAudio() {
console.log('Audio re-enabled');
this.audioEnabled = true;
// Clear any stopped audio elements to prevent orphaned events
for (const category in this.categories) {
if (this.categories[category].currentAudio && this.categories[category].currentAudio._stopped) {
// Remove stopped audio elements completely
const stoppedAudio = this.categories[category].currentAudio;
if (stoppedAudio.parentNode) {
stoppedAudio.parentNode.removeChild(stoppedAudio);
}
this.categories[category].currentAudio = null;
console.log(`Cleared stopped audio element in category: ${category}`);
}
}
}
// Start continuous background audio playlist
startBackgroundPlaylist() {
if (!this.audioEnabled) {
console.log('Audio disabled - not starting playlist');
return;
}
// Get background audio files
const backgroundFiles = this.audioLibrary.background?.general || [];
if (backgroundFiles.length === 0) {
console.log('No background audio files available for playlist');
return;
}
// Shuffle the playlist
this.playlistQueue = [...backgroundFiles].sort(() => Math.random() - 0.5);
this.currentPlaylistIndex = 0;
this.isPlaylistMode = true;
console.log(`🎵 Starting background audio playlist with ${this.playlistQueue.length} tracks`);
this.playNextInPlaylist();
}
// Play next track in playlist
playNextInPlaylist() {
if (!this.isPlaylistMode || !this.audioEnabled) {
return;
}
if (this.playlistQueue.length === 0) {
console.log('Playlist is empty');
return;
}
// Stop current playlist audio if playing
if (this.currentPlaylistAudio) {
this.currentPlaylistAudio.pause();
this.currentPlaylistAudio.remove();
}
// Get next track (loop back to start if at end)
if (this.currentPlaylistIndex >= this.playlistQueue.length) {
this.currentPlaylistIndex = 0;
// Re-shuffle for variety
this.playlistQueue = [...this.playlistQueue].sort(() => Math.random() - 0.5);
console.log('🔄 Playlist completed, reshuffling and restarting');
}
const audioFile = this.playlistQueue[this.currentPlaylistIndex];
console.log(`🎵 Playing playlist track ${this.currentPlaylistIndex + 1}/${this.playlistQueue.length}: ${audioFile.name}`);
// Create audio element
this.currentPlaylistAudio = new Audio();
const audio = this.currentPlaylistAudio;
// Set up event handlers
audio.addEventListener('ended', () => {
console.log(`🎵 Track ended: ${audioFile.name}`);
this.currentPlaylistIndex++;
// Small delay before next track
setTimeout(() => this.playNextInPlaylist(), 500);
});
audio.addEventListener('error', (e) => {
console.error(`🎵 Playlist track failed: ${audioFile.name}`, e);
this.currentPlaylistIndex++;
// Try next track after error
setTimeout(() => this.playNextInPlaylist(), 1000);
});
// Set source and play
try {
// Use the same file URL conversion logic as the main playAudio method
let audioSrc = audioFile.path;
if (window.electronAPI && audioFile.path.match(/^[A-Za-z]:\//)) {
// Absolute Windows path - convert to file:// URL
audioSrc = `file:///${audioFile.path}`;
console.log(`🎵 Converted to file URL: ${audioSrc}`);
} else if (window.electronAPI && !audioFile.path.startsWith('file://')) {
// Relative path in Electron - make it relative to app root
audioSrc = audioFile.path;
console.log(`🎵 Using relative path: ${audioSrc}`);
}
audio.src = audioSrc;
audio.volume = this.getMasterVolume() * (this.getCategoryVolume('background') || 0.7);
audio.play().then(() => {
console.log(`🎵 Now playing: ${audioFile.name}`);
}).catch(error => {
console.error(`🎵 Failed to play playlist track: ${audioFile.name}`, error);
this.currentPlaylistIndex++;
setTimeout(() => this.playNextInPlaylist(), 1000);
});
} catch (error) {
console.error(`🎵 Error setting up playlist track: ${audioFile.name}`, error);
this.currentPlaylistIndex++;
setTimeout(() => this.playNextInPlaylist(), 1000);
}
}
// Pause playlist
pausePlaylist() {
if (this.currentPlaylistAudio && !this.currentPlaylistAudio.paused) {
this.currentPlaylistAudio.pause();
console.log('🎵 Playlist paused');
}
}
// Resume playlist
resumePlaylist() {
if (this.currentPlaylistAudio && this.currentPlaylistAudio.paused) {
this.currentPlaylistAudio.play().then(() => {
console.log('🎵 Playlist resumed');
}).catch(error => {
console.error('🎵 Failed to resume playlist', error);
// Try next track if current one fails
this.currentPlaylistIndex++;
setTimeout(() => this.playNextInPlaylist(), 500);
});
}
}
// Stop playlist completely
stopPlaylist() {
this.isPlaylistMode = false;
if (this.currentPlaylistAudio) {
this.currentPlaylistAudio.pause();
this.currentPlaylistAudio.src = '';
this.currentPlaylistAudio.remove();
this.currentPlaylistAudio = null;
}
this.playlistQueue = [];
this.currentPlaylistIndex = 0;
console.log('🎵 Playlist stopped');
}
// Stop all audio
stopAll(fadeOut = 0) {
for (const category in this.categories) {
@ -504,21 +879,175 @@ class AudioManager {
}
}
// Convenient methods for specific audio types
playTaskAudio(intensity = 'teasing', options = {}) {
return this.playAudio('tasks', intensity, null, options);
// Convenient methods for specific audio types - now all use background audio
playTaskAudio(intensity = 'general', options = {}) {
return this.playBackgroundAudio(options);
}
playPunishmentAudio(type = 'denial', options = {}) {
return this.playAudio('punishments', type, null, options);
playPunishmentAudio(type = 'general', options = {}) {
return this.playBackgroundAudio(options);
}
playRewardAudio(options = {}) {
return this.playAudio('rewards', 'completion', null, options);
return this.playBackgroundAudio(options);
}
playInstructionAudio(instruction, options = {}) {
return this.playAudio('instructions', 'commands', instruction, options);
return this.playBackgroundAudio(options);
}
// Main background audio playing method
playBackgroundAudio(options = {}) {
return this.playAudio('background', 'general', null, options);
}
// Enhanced playAudio with fallback support - simplified since we only have one category
async playAudioWithFallback(category, primarySubcategory, fallbackSubcategory, fileName = null, options = {}) {
try {
return await this.playAudio(category, primarySubcategory, fileName, options);
} catch (error) {
console.log(`Audio playback failed for ${category}/${primarySubcategory}`);
return null;
}
}
// Scan audio directories for files
async scanAudioDirectories() {
console.log('🔍 Scanning audio directories...');
const scannedFiles = [];
// Simplified directory structure - only background and ambient
const audioDirectories = [
'audio/background',
'audio/ambient'
];
// For Electron environment, we can use the existing readAudioDirectory API
if (window.electronAPI && window.electronAPI.readAudioDirectory) {
try {
for (const directory of audioDirectories) {
console.log(`Scanning directory: ${directory}`);
// Get the full path to the directory
const appPath = await window.electronAPI.getAppPath();
const fullPath = await window.electronAPI.pathJoin(appPath, directory);
// Check if directory exists before trying to read it
const dirExists = await window.electronAPI.fileExists(fullPath);
if (!dirExists) {
console.log(`Directory does not exist: ${fullPath}`);
continue;
}
const files = await window.electronAPI.readAudioDirectory(fullPath);
for (const file of files) {
scannedFiles.push({
name: file.name,
path: file.path,
title: file.title || file.name,
directory: directory,
enabled: true
});
}
}
console.log(`🔍 Found ${scannedFiles.length} audio files in directories`);
// Add scanned files to the audio library if they're not already in customAudio
if (scannedFiles.length > 0) {
await this.addScannedFilesToLibrary(scannedFiles);
}
return scannedFiles;
} catch (error) {
console.error('Error scanning audio directories:', error);
return [];
}
} else {
console.warn('Directory scanning not available - requires Electron environment');
console.log('💡 To use directory scanning:');
console.log(' 1. Run the app with: npm start');
console.log(' 2. Or manually add audio files via "Manage Audio" menu');
return [];
}
}
// Add scanned files to the audio library
async addScannedFilesToLibrary(scannedFiles) {
if (!window.game || !window.game.dataManager) {
console.warn('DataManager not available for adding scanned files');
return;
}
const customAudio = window.game.dataManager.get('customAudio') || {
background: [],
ambient: []
};
let addedCount = 0;
for (const file of scannedFiles) {
// Check if file is already in the library
const existsInBackground = customAudio.background.some(f => f.path === file.path);
const existsInAmbient = customAudio.ambient.some(f => f.path === file.path);
if (!existsInBackground && !existsInAmbient) {
// Determine which category to add to based on directory
let category = 'background'; // Default category
if (file.directory.includes('ambient')) {
category = 'ambient';
}
// Note: Since we removed effects, tasks, etc., everything else goes to background
customAudio[category].push({
name: file.name,
path: file.path,
enabled: true,
source: 'directory_scan'
});
addedCount++;
console.log(`Added ${file.name} to ${category} category`);
}
}
if (addedCount > 0) {
// Save updated audio library
window.game.dataManager.set('customAudio', customAudio);
// Refresh the audio library to include new files
await this.discoverAudioLibrary();
console.log(`✅ Added ${addedCount} new audio files to library`);
// Show success message to user
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(
`Audio Scan Complete: Added ${addedCount} audio files to library`,
'success'
);
}
} else {
console.log(' No new audio files found during scan');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(
'Audio scan complete - no new files found',
'info'
);
}
}
}
// Refresh audio library - useful when user adds new audio files
async refreshAudioLibrary() {
console.log('🎵 Refreshing audio library...');
await this.discoverAudioLibrary();
console.log('🎵 Audio library refreshed');
}
// Get settings for UI
@ -530,4 +1059,10 @@ class AudioManager {
isInitialized: this.isInitialized
};
}
// Public method to trigger directory scan (for UI buttons)
async triggerDirectoryScan() {
console.log('🔍 Triggering audio directory scan from UI...');
return await this.scanAudioDirectories();
}
}

View File

@ -1183,6 +1183,11 @@ class InteractiveTaskManager {
startBtn.addEventListener('click', () => {
console.log('🧘 Starting focus session');
// Track focus session start for XP (5 XP per minute)
if (this.game && this.game.trackFocusSession) {
this.game.trackFocusSession(true);
}
// Hide start button and show progress
startBtn.style.display = 'none';
progressContainer.style.display = 'block';
@ -1214,6 +1219,11 @@ class InteractiveTaskManager {
if (timeLeft < 0) {
clearInterval(focusInterval);
// Track focus session end for XP
if (this.game && this.game.trackFocusSession) {
this.game.trackFocusSession(false);
}
// Focus session completed
timerDisplay.textContent = 'Complete!';
progressText.textContent = '✓ Focus session completed';
@ -1523,6 +1533,11 @@ class InteractiveTaskManager {
console.log('🧘 🎬 Stopping focus video playback...');
this.focusVideoActive = false;
// End focus session tracking for XP (if still active)
if (this.game && this.game.trackFocusSession) {
this.game.trackFocusSession(false);
}
if (this.focusVideoPlayer) {
// Pause and clear the video
this.focusVideoPlayer.pause();

View File

@ -47,6 +47,22 @@ class FlashMessageManager {
this.messages = gameData.defaultFlashMessages.filter(msg => msg.enabled !== false);
}
}
/**
* Public show method for compatibility with game.js
* @param {string|object} message - Message text or object
* @param {string} type - Message type (info, error, etc.)
*/
show(message, type = 'info') {
// Optionally style based on type
let config = {};
if (type === 'error') {
config = { color: '#fff', backgroundColor: '#d32f2f' };
} else if (type === 'info') {
config = { color: '#fff', backgroundColor: '#1976d2' };
}
this.showMessage(message, config);
}
createMessageElement() {
// Create the overlay element for flash messages

View File

@ -197,6 +197,11 @@ class WebcamManager {
this.currentPhotoSession.photos.push(photoData);
this.capturedPhotos.push(photoData);
// Track photo for XP (1 XP per photo)
if (this.game && this.game.incrementPhotosTaken) {
this.game.incrementPhotosTaken();
}
// Update progress indicators
const photosTaken = this.currentPhotoSession.photos.length;
const photosNeeded = this.currentPhotoSession.photosNeeded;
@ -643,6 +648,11 @@ class WebcamManager {
this.currentPhotoSession.photos.push(photoData);
this.capturedPhotos.push(photoData);
// Track photo for XP (1 XP per photo)
if (this.game && this.game.incrementPhotosTaken) {
this.game.incrementPhotosTaken();
}
// Update photo count
document.getElementById('photo-count').textContent = this.currentPhotoSession.photos.length;
@ -1113,6 +1123,11 @@ class WebcamManager {
}
}
// Track webcam mirror start for XP (5 XP per minute)
if (this.game && this.game.trackWebcamMirror) {
this.game.trackWebcamMirror(true);
}
// Show mirror interface
this.showMirrorInterface(taskData);
return true;
@ -1500,6 +1515,11 @@ class WebcamManager {
closeMirrorMode() {
console.log('🔚 Closing mirror mode');
// Track webcam mirror end for XP
if (this.game && this.game.trackWebcamMirror) {
this.game.trackWebcamMirror(false);
}
// Remove mirror overlay
const overlay = document.getElementById('mirror-overlay');
if (overlay) {

View File

@ -9,8 +9,7 @@ class DesktopFileManager {
};
this.audioDirectories = {
background: null,
ambient: null,
effects: null
ambient: null
};
this.videoDirectories = {
background: null,
@ -35,7 +34,6 @@ class DesktopFileManager {
this.audioDirectories.background = await window.electronAPI.pathJoin(this.appPath, 'audio', 'background');
this.audioDirectories.ambient = await window.electronAPI.pathJoin(this.appPath, 'audio', 'ambient');
this.audioDirectories.effects = await window.electronAPI.pathJoin(this.appPath, 'audio', 'effects');
this.videoDirectories.background = await window.electronAPI.pathJoin(this.appPath, 'videos', 'background');
this.videoDirectories.tasks = await window.electronAPI.pathJoin(this.appPath, 'videos', 'tasks');
@ -48,7 +46,6 @@ class DesktopFileManager {
await window.electronAPI.createDirectory(this.audioDirectories.background);
await window.electronAPI.createDirectory(this.audioDirectories.ambient);
await window.electronAPI.createDirectory(this.audioDirectories.effects);
await window.electronAPI.createDirectory(this.videoDirectories.background);
await window.electronAPI.createDirectory(this.videoDirectories.tasks);
@ -490,22 +487,20 @@ class DesktopFileManager {
async scanAllAudioDirectories() {
if (!this.isElectron) {
this.showNotification('Audio directory scanning only available in desktop version', 'warning');
return { background: [], ambient: [], effects: [] };
return { background: [], ambient: [] };
}
const backgroundAudio = await this.scanDirectoryForAudio('background');
const ambientAudio = await this.scanDirectoryForAudio('ambient');
const effectsAudio = await this.scanDirectoryForAudio('effects');
const results = {
background: backgroundAudio,
ambient: ambientAudio,
effects: effectsAudio
ambient: ambientAudio
};
// Update game audio storage
if (backgroundAudio.length > 0 || ambientAudio.length > 0 || effectsAudio.length > 0) {
await this.updateAudioStorage([...backgroundAudio, ...ambientAudio, ...effectsAudio]);
if (backgroundAudio.length > 0 || ambientAudio.length > 0) {
await this.updateAudioStorage([...backgroundAudio, ...ambientAudio]);
}
return results;
@ -517,7 +512,7 @@ class DesktopFileManager {
}
// Get existing audio
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [] };
// Add new audio files (avoid duplicates)
for (const audio of audioFiles) {
@ -562,7 +557,7 @@ class DesktopFileManager {
const success = await window.electronAPI.deleteFile(audioPath);
if (success) {
// Remove from storage
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [] };
if (customAudio[category]) {
customAudio[category] = customAudio[category].filter(audio => {