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:
parent
3e59456aff
commit
84b17a7930
|
|
@ -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
|
||||
|
|
@ -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
|
||||
16
ROADMAP.md
16
ROADMAP.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()');
|
||||
|
|
@ -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()');
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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()');
|
||||
|
|
@ -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;
|
||||
49
index.html
49
index.html
|
|
@ -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>
|
||||
|
|
|
|||
643
src/core/game.js
643
src/core/game.js
File diff suppressed because it is too large
Load Diff
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue