Fix video loading and Quick Play image system migration
- Fixed porn cinema video loading to use correct linkedIndividualVideos storage - Added CSS !important overrides to ensure video card visibility - Migrated Quick Play from hardcoded image paths to linked storage system - Added async getLinkedImages() methods to Game and PopupImageManager classes - Fixed TypeError crashes from readDirectory async/sync issues - Added comprehensive debug logging for media loading processes
This commit is contained in:
parent
bdcb9bea18
commit
8a25b51168
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\drew\\webGame\\",
|
||||
"Documents": [],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\drew\\webGame\\",
|
||||
"Documents": [],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": -1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{56df62a4-05a3-4e5b-aa1a-99371ccfb997}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
# Unity Setup & Prototype Guide - Phase 1
|
||||
|
||||
## 🎯 **Goal: Validate Unity Video Performance vs Web**
|
||||
|
||||
---
|
||||
|
||||
## 📋 **Step 1: Unity Project Setup**
|
||||
|
||||
### **Create New Project:**
|
||||
1. **Open Unity Hub**
|
||||
2. **New Project** → **3D Core** template
|
||||
3. **Project Name**: `Gooner-Training-Academy-Unity`
|
||||
4. **Unity Version**: 2022.3 LTS (recommended)
|
||||
5. **Location**: Choose appropriate folder
|
||||
|
||||
### **Essential Packages to Install:**
|
||||
1. **Window** → **Package Manager**
|
||||
2. **Install these packages:**
|
||||
- **TextMeshPro** (for UI text)
|
||||
- **Video Player** (should be built-in)
|
||||
- **UI Toolkit** (modern UI system)
|
||||
- **Newtonsoft Json** (for data serialization)
|
||||
|
||||
---
|
||||
|
||||
## 🎬 **Step 2: Basic Video Player Test**
|
||||
|
||||
### **Create Basic Scene:**
|
||||
1. **Create Empty GameObject** → Name: `VideoManager`
|
||||
2. **Add Component** → **Video Player**
|
||||
3. **Create UI Canvas** → **Screen Space - Overlay**
|
||||
4. **Add Button** to Canvas → Name: `PlayButton`
|
||||
|
||||
### **Test Single Video:**
|
||||
```csharp
|
||||
// Create: Assets/Scripts/BasicVideoTest.cs
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class BasicVideoTest : MonoBehaviour
|
||||
{
|
||||
[Header("Video Settings")]
|
||||
public VideoPlayer videoPlayer;
|
||||
public Button playButton;
|
||||
public RenderTexture renderTexture;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Setup render texture for video output
|
||||
renderTexture = new RenderTexture(1920, 1080, 24);
|
||||
videoPlayer.targetTexture = renderTexture;
|
||||
|
||||
// Setup button
|
||||
playButton.onClick.AddListener(ToggleVideo);
|
||||
|
||||
// Load test video (you'll need to provide a video file)
|
||||
videoPlayer.url = System.IO.Path.Combine(Application.streamingAssetsPath, "test-video.mp4");
|
||||
videoPlayer.Prepare();
|
||||
}
|
||||
|
||||
void ToggleVideo()
|
||||
{
|
||||
if (videoPlayer.isPlaying)
|
||||
{
|
||||
videoPlayer.Pause();
|
||||
playButton.GetComponentInChildren<Text>().text = "Play";
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPlayer.Play();
|
||||
playButton.GetComponentInChildren<Text>().text = "Pause";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎮 **Step 3: Quad Video Prototype**
|
||||
|
||||
### **Create Quad Layout:**
|
||||
1. **Create 4 UI Panels** in Canvas (2x2 grid layout)
|
||||
2. **Add RawImage** to each panel
|
||||
3. **Create 4 VideoPlayer GameObjects**
|
||||
|
||||
### **Quad Video Manager:**
|
||||
```csharp
|
||||
// Create: Assets/Scripts/QuadVideoManager.cs
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class QuadVideoManager : MonoBehaviour
|
||||
{
|
||||
[Header("Video Players")]
|
||||
public VideoPlayer[] videoPlayers = new VideoPlayer[4];
|
||||
|
||||
[Header("UI Elements")]
|
||||
public RawImage[] videoDisplays = new RawImage[4];
|
||||
public Button[] playButtons = new Button[4];
|
||||
public Slider[] volumeSliders = new Slider[4];
|
||||
|
||||
[Header("Video Settings")]
|
||||
public RenderTexture[] renderTextures = new RenderTexture[4];
|
||||
public string[] testVideoUrls = new string[4];
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetupQuadVideos();
|
||||
}
|
||||
|
||||
void SetupQuadVideos()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
// Create render texture for each video
|
||||
renderTextures[i] = new RenderTexture(1920, 1080, 24);
|
||||
|
||||
// Setup video player
|
||||
videoPlayers[i].targetTexture = renderTextures[i];
|
||||
videoPlayers[i].isLooping = true;
|
||||
videoPlayers[i].audioOutputMode = VideoAudioOutputMode.Direct;
|
||||
|
||||
// Assign render texture to UI
|
||||
videoDisplays[i].texture = renderTextures[i];
|
||||
|
||||
// Setup controls
|
||||
int index = i; // Capture for closure
|
||||
playButtons[i].onClick.AddListener(() => ToggleVideo(index));
|
||||
volumeSliders[i].onValueChanged.AddListener((value) => SetVolume(index, value));
|
||||
|
||||
// Load test video
|
||||
if (i < testVideoUrls.Length && !string.IsNullOrEmpty(testVideoUrls[i]))
|
||||
{
|
||||
videoPlayers[i].url = testVideoUrls[i];
|
||||
videoPlayers[i].Prepare();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleVideo(int index)
|
||||
{
|
||||
if (videoPlayers[index].isPlaying)
|
||||
{
|
||||
videoPlayers[index].Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPlayers[index].Play();
|
||||
}
|
||||
}
|
||||
|
||||
void SetVolume(int index, float volume)
|
||||
{
|
||||
videoPlayers[index].SetDirectAudioVolume(0, volume);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Monitor performance
|
||||
if (Input.GetKeyDown(KeyCode.P))
|
||||
{
|
||||
Debug.Log($"FPS: {1f / Time.deltaTime:F1}");
|
||||
Debug.Log($"Videos playing: {GetPlayingVideoCount()}");
|
||||
}
|
||||
}
|
||||
|
||||
int GetPlayingVideoCount()
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < videoPlayers.Length; i++)
|
||||
{
|
||||
if (videoPlayers[i].isPlaying) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 **Step 4: Setup Test Videos**
|
||||
|
||||
### **Create StreamingAssets Folder:**
|
||||
1. **Assets** → **Create** → **Folder** → Name: `StreamingAssets`
|
||||
2. **Copy 4 test videos** into this folder (MP4 format recommended)
|
||||
3. **Name them**: `test-video-1.mp4`, `test-video-2.mp4`, etc.
|
||||
|
||||
### **Update Video URLs:**
|
||||
```csharp
|
||||
// In QuadVideoManager, set testVideoUrls in inspector:
|
||||
testVideoUrls[0] = Application.streamingAssetsPath + "/test-video-1.mp4";
|
||||
testVideoUrls[1] = Application.streamingAssetsPath + "/test-video-2.mp4";
|
||||
testVideoUrls[2] = Application.streamingAssetsPath + "/test-video-3.mp4";
|
||||
testVideoUrls[3] = Application.streamingAssetsPath + "/test-video-4.mp4";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 **Step 5: Performance Testing**
|
||||
|
||||
### **Performance Monitor Script:**
|
||||
```csharp
|
||||
// Create: Assets/Scripts/PerformanceMonitor.cs
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class PerformanceMonitor : MonoBehaviour
|
||||
{
|
||||
[Header("UI")]
|
||||
public Text fpsText;
|
||||
public Text memoryText;
|
||||
public Text videoCountText;
|
||||
|
||||
private float deltaTime = 0.0f;
|
||||
private QuadVideoManager quadManager;
|
||||
|
||||
void Start()
|
||||
{
|
||||
quadManager = FindObjectOfType<QuadVideoManager>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
|
||||
|
||||
if (fpsText != null)
|
||||
{
|
||||
float fps = 1.0f / deltaTime;
|
||||
fpsText.text = $"FPS: {fps:0.}";
|
||||
|
||||
// Color code based on performance
|
||||
if (fps >= 60) fpsText.color = Color.green;
|
||||
else if (fps >= 30) fpsText.color = Color.yellow;
|
||||
else fpsText.color = Color.red;
|
||||
}
|
||||
|
||||
if (memoryText != null)
|
||||
{
|
||||
long memory = System.GC.GetTotalMemory(false);
|
||||
memoryText.text = $"Memory: {memory / 1024 / 1024}MB";
|
||||
}
|
||||
|
||||
if (videoCountText != null && quadManager != null)
|
||||
{
|
||||
videoCountText.text = $"Playing: {quadManager.GetPlayingVideoCount()}/4";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Step 6: Test Plan**
|
||||
|
||||
### **Performance Benchmarks:**
|
||||
1. **Baseline Test**: Single video @ 1080p
|
||||
2. **Dual Test**: 2 videos simultaneously
|
||||
3. **Quad Test**: 4 videos simultaneously
|
||||
4. **Stress Test**: 4 videos + UI interactions
|
||||
|
||||
### **Metrics to Track:**
|
||||
- **FPS**: Target 60+ with 4 videos
|
||||
- **Memory Usage**: Target <2GB
|
||||
- **CPU Usage**: Monitor in Task Manager
|
||||
- **Video Switching Speed**: Target <100ms
|
||||
|
||||
### **Comparison to Web:**
|
||||
- **Run your web QuadVideoPlayer** alongside Unity
|
||||
- **Monitor both performance profiles**
|
||||
- **Document frame rate differences**
|
||||
|
||||
---
|
||||
|
||||
## 📋 **Your Immediate Tasks:**
|
||||
|
||||
### **Today:**
|
||||
1. ✅ Create Unity project with packages
|
||||
2. ✅ Implement BasicVideoTest script
|
||||
3. ✅ Test single video playback
|
||||
|
||||
### **This Week:**
|
||||
1. ✅ Build QuadVideoManager system
|
||||
2. ✅ Add performance monitoring
|
||||
3. ✅ Compare web vs Unity performance
|
||||
4. ✅ Document results for migration decision
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Expected Results:**
|
||||
|
||||
**If Unity performs significantly better:**
|
||||
- **Proceed with full migration**
|
||||
- **Start porting game logic systems**
|
||||
- **Plan Unity-specific enhancements**
|
||||
|
||||
**If performance is similar:**
|
||||
- **Re-evaluate web optimizations**
|
||||
- **Consider hybrid approach**
|
||||
- **Focus on Unity for future features**
|
||||
|
||||
---
|
||||
|
||||
**Ready to start? Begin with the Unity project setup and let me know when you have the basic project created!** 🎮
|
||||
2479
index.html
2479
index.html
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
622
src/core/game.js
622
src/core/game.js
|
|
@ -21,11 +21,12 @@ function validateDependencies() {
|
|||
|
||||
// Game state management
|
||||
class TaskChallengeGame {
|
||||
constructor() {
|
||||
constructor(options = {}) {
|
||||
// Show loading overlay immediately
|
||||
this.showLoadingOverlay();
|
||||
this.loadingProgress = 0;
|
||||
this.isInitialized = false;
|
||||
this.options = options; // Store options for later use
|
||||
|
||||
// Initialize data management system first
|
||||
this.dataManager = new DataManager();
|
||||
|
|
@ -136,34 +137,48 @@ class TaskChallengeGame {
|
|||
this.initializeEventListeners();
|
||||
this.setupKeyboardShortcuts();
|
||||
this.setupWindowResizeHandling();
|
||||
this.initializeCustomTasks();
|
||||
this.initializeCustomTasks(this.options);
|
||||
|
||||
// Simple override for desktop mode - skip complex discovery
|
||||
// Simple override for desktop mode - use linked images instead of old discovery
|
||||
if (typeof DesktopFileManager !== 'undefined' && window.electronAPI) {
|
||||
console.log('🖥️ Desktop mode detected - using simplified image discovery');
|
||||
// Give desktop file manager time to scan, then force completion
|
||||
setTimeout(() => {
|
||||
const customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
|
||||
let taskImages = [];
|
||||
let consequenceImages = [];
|
||||
|
||||
if (Array.isArray(customImages)) {
|
||||
taskImages = customImages;
|
||||
} else {
|
||||
taskImages = customImages.task || [];
|
||||
consequenceImages = customImages.consequence || [];
|
||||
console.log('🖥️ Desktop mode detected - using linked images');
|
||||
// Give desktop file manager time to scan, then load linked images
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const linkedImages = await this.getLinkedImages();
|
||||
|
||||
// Separate images into task and consequence categories if needed
|
||||
// For now, use all linked images as task images
|
||||
const taskImages = linkedImages.map(img => img.path);
|
||||
const consequenceImages = []; // Could implement categorization later
|
||||
|
||||
gameData.discoveredTaskImages = taskImages;
|
||||
gameData.discoveredConsequenceImages = consequenceImages;
|
||||
console.log(`📸 Desktop mode - Linked images loaded: ${gameData.discoveredTaskImages.length} task images, ${gameData.discoveredConsequenceImages.length} consequence images`);
|
||||
} catch (error) {
|
||||
console.error('📸 Error loading linked images:', error);
|
||||
// Fallback to empty arrays
|
||||
gameData.discoveredTaskImages = [];
|
||||
gameData.discoveredConsequenceImages = [];
|
||||
}
|
||||
|
||||
gameData.discoveredTaskImages = taskImages.map(img => typeof img === 'string' ? img : img.name);
|
||||
gameData.discoveredConsequenceImages = consequenceImages.map(img => typeof img === 'string' ? img : img.name);
|
||||
console.log(`📸 Desktop mode - Task images: ${gameData.discoveredTaskImages.length}, Consequence images: ${gameData.discoveredConsequenceImages.length}`);
|
||||
|
||||
this.imageDiscoveryComplete = true;
|
||||
this.showScreen('start-screen');
|
||||
|
||||
// Only show start screen if it exists (not in dedicated mode like Quick Play)
|
||||
if (document.getElementById('start-screen')) {
|
||||
this.showScreen('start-screen');
|
||||
} else {
|
||||
console.log('📱 Running in dedicated screen mode - skipping start screen display');
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
this.discoverImages().then(() => {
|
||||
this.showScreen('start-screen');
|
||||
// Only show start screen if it exists (not in dedicated mode like Quick Play)
|
||||
if (document.getElementById('start-screen')) {
|
||||
this.showScreen('start-screen');
|
||||
} else {
|
||||
console.log('📱 Running in dedicated screen mode - skipping start screen display');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('🚨 Error in discoverImages():', error);
|
||||
// Fallback: just mark as complete and show start screen
|
||||
|
|
@ -261,12 +276,17 @@ class TaskChallengeGame {
|
|||
setTimeout(async () => {
|
||||
if (this.fileManager && this.fileManager.isElectron) {
|
||||
console.log('🔍 Auto-scanning directories on startup...');
|
||||
await this.fileManager.scanAllDirectories();
|
||||
|
||||
// Reload video player manager after scanning
|
||||
if (window.videoPlayerManager && window.videoPlayerManager.loadVideoFiles) {
|
||||
console.log('🔄 Reloading video library after directory scan...');
|
||||
await window.videoPlayerManager.loadVideoFiles();
|
||||
try {
|
||||
// Refresh all linked directories instead of scanning all
|
||||
await this.fileManager.refreshAllDirectories();
|
||||
|
||||
// Reload video player manager after scanning
|
||||
if (window.videoPlayerManager && window.videoPlayerManager.loadVideoFiles) {
|
||||
console.log('🔄 Reloading video library after directory scan...');
|
||||
await window.videoPlayerManager.loadVideoFiles();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Auto-scan failed:', error);
|
||||
}
|
||||
}
|
||||
}, 1000); // Wait 1 second for initialization to complete
|
||||
|
|
@ -294,7 +314,110 @@ class TaskChallengeGame {
|
|||
}, 100);
|
||||
}
|
||||
|
||||
initializeCustomTasks() {
|
||||
/**
|
||||
* Get all images from linked directories and individual files (same as main library)
|
||||
*/
|
||||
async getLinkedImages() {
|
||||
const allImages = [];
|
||||
|
||||
try {
|
||||
// Get linked directories
|
||||
let linkedDirs;
|
||||
try {
|
||||
linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||||
if (!Array.isArray(linkedDirs)) {
|
||||
linkedDirs = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedImageDirectories:', e);
|
||||
linkedDirs = [];
|
||||
}
|
||||
|
||||
// Get individual linked images
|
||||
let linkedIndividualImages;
|
||||
try {
|
||||
linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
|
||||
if (!Array.isArray(linkedIndividualImages)) {
|
||||
linkedIndividualImages = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedIndividualImages:', e);
|
||||
linkedIndividualImages = [];
|
||||
}
|
||||
|
||||
console.log(`📸 Game found ${linkedDirs.length} linked directories and ${linkedIndividualImages.length} individual images`);
|
||||
|
||||
// Load images from linked directories using Electron API
|
||||
if (window.electronAPI && linkedDirs.length > 0) {
|
||||
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
|
||||
|
||||
for (const dir of linkedDirs) {
|
||||
try {
|
||||
console.log(`📸 Scanning directory: ${dir.path}`);
|
||||
|
||||
let files = [];
|
||||
if (window.electronAPI.readDirectory) {
|
||||
const result = window.electronAPI.readDirectory(dir.path);
|
||||
|
||||
// Handle both sync and async results
|
||||
if (result && typeof result.then === 'function') {
|
||||
try {
|
||||
files = await result;
|
||||
console.log(`📸 Async result - found ${files ? files.length : 0} files`);
|
||||
} catch (asyncError) {
|
||||
console.error(`📸 Async error for ${dir.path}:`, asyncError);
|
||||
continue;
|
||||
}
|
||||
} else if (Array.isArray(result)) {
|
||||
files = result;
|
||||
console.log(`📸 Sync result - found ${files.length} files`);
|
||||
} else {
|
||||
console.error(`📸 Unexpected result type for ${dir.path}:`, typeof result);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (files && Array.isArray(files)) {
|
||||
const imageFiles = files.filter(file => imageExtensions.test(file.name));
|
||||
console.log(`📸 Found ${imageFiles.length} image files in ${dir.name}`);
|
||||
|
||||
imageFiles.forEach(file => {
|
||||
allImages.push({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
category: 'directory',
|
||||
directory: dir.name
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log(`📸 No valid files array for ${dir.path}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`📸 Error loading images from directory ${dir.path}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add individual linked images
|
||||
linkedIndividualImages.forEach(image => {
|
||||
allImages.push({
|
||||
name: image.name || 'Unknown Image',
|
||||
path: image.path,
|
||||
category: 'individual',
|
||||
directory: 'Individual Images'
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`📸 Game loaded ${allImages.length} total linked images`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('📸 Error loading linked images in Game:', error);
|
||||
}
|
||||
|
||||
return allImages;
|
||||
}
|
||||
|
||||
initializeCustomTasks(options = {}) {
|
||||
// Load custom tasks from localStorage or use defaults
|
||||
const savedMainTasks = localStorage.getItem('customMainTasks');
|
||||
const savedConsequenceTasks = localStorage.getItem('customConsequenceTasks');
|
||||
|
|
@ -308,18 +431,56 @@ class TaskChallengeGame {
|
|||
}
|
||||
|
||||
// Always add interactive tasks - mode filtering happens later
|
||||
this.addInteractiveTasksToGameData();
|
||||
this.addInteractiveTasksToGameData(options);
|
||||
|
||||
// Filter out scenario tasks if disabled in options
|
||||
if (options && options.includeScenarioTasks === false) {
|
||||
console.log('🎭 Filtering out scenario tasks from game data');
|
||||
const originalCount = gameData.mainTasks.length;
|
||||
gameData.mainTasks = gameData.mainTasks.filter(task => {
|
||||
// Remove tasks with scenario-adventure interactive type
|
||||
if (task.interactiveType === 'scenario-adventure') {
|
||||
console.log('🗑️ Removing scenario task:', task.id || task.text);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
console.log(`📊 Filtered tasks: ${originalCount} → ${gameData.mainTasks.length}`);
|
||||
}
|
||||
|
||||
console.log(`Loaded ${gameData.mainTasks.length} main tasks and ${gameData.consequenceTasks.length} consequence tasks`);
|
||||
|
||||
// Debug: log interactive tasks
|
||||
const interactiveTasks = gameData.mainTasks.filter(t => t.interactiveType);
|
||||
console.log(`Interactive tasks available: ${interactiveTasks.length}`, interactiveTasks.map(t => `${t.id}:${t.interactiveType}`));
|
||||
|
||||
// Debug: check for scenario tasks specifically
|
||||
const scenarioTasks = gameData.mainTasks.filter(t => t.interactiveType === 'scenario-adventure');
|
||||
console.log(`🎭 Scenario tasks in final data: ${scenarioTasks.length}`, scenarioTasks.map(t => t.id || t.text));
|
||||
}
|
||||
|
||||
addInteractiveTasksToGameData() {
|
||||
// Define our interactive tasks - Scenario Adventures only
|
||||
const interactiveTasks = [
|
||||
addInteractiveTasksToGameData(options = {}) {
|
||||
// Check configuration options (default: include all for backward compatibility)
|
||||
const includeScenarioTasks = options.includeScenarioTasks !== false;
|
||||
const includeStandardTasks = options.includeStandardTasks !== false;
|
||||
|
||||
console.log('🎯 Task filtering options:', options);
|
||||
console.log('🎭 Include scenario tasks:', includeScenarioTasks);
|
||||
console.log('📝 Include standard tasks:', includeStandardTasks);
|
||||
|
||||
// Define our interactive tasks
|
||||
const interactiveTasks = [];
|
||||
|
||||
// Add mirror task (considered a standard interactive task)
|
||||
if (includeStandardTasks) {
|
||||
// Mirror task is already in gameData.js, so we don't need to add it here
|
||||
console.log('🪞 Mirror tasks enabled (from gameData.js)');
|
||||
}
|
||||
|
||||
// Only add scenario tasks if enabled
|
||||
if (includeScenarioTasks) {
|
||||
console.log('🎭 Scenario tasks enabled - adding scenario adventures');
|
||||
interactiveTasks.push(
|
||||
{
|
||||
id: 'scenario-training-session',
|
||||
text: "Enter a guided training session",
|
||||
|
|
@ -1400,7 +1561,10 @@ class TaskChallengeGame {
|
|||
},
|
||||
hint: "Learn the value of unquestioning obedience"
|
||||
}
|
||||
];
|
||||
); // Close the interactiveTasks.push
|
||||
} else {
|
||||
console.log('🎭 Scenario tasks disabled');
|
||||
}
|
||||
|
||||
// Add interactive tasks to gameData.mainTasks if they don't already exist
|
||||
for (const interactiveTask of interactiveTasks) {
|
||||
|
|
@ -1620,78 +1784,98 @@ class TaskChallengeGame {
|
|||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
// Helper function to safely add event listeners
|
||||
const safeAddListener = (id, event, handler) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.addEventListener(event, handler);
|
||||
} else {
|
||||
console.log(`⚠️ Element '${id}' not found, skipping event listener`);
|
||||
}
|
||||
};
|
||||
|
||||
// Screen navigation
|
||||
document.getElementById('start-btn').addEventListener('click', () => this.startGame());
|
||||
document.getElementById('resume-btn').addEventListener('click', () => this.resumeGame());
|
||||
document.getElementById('quit-btn').addEventListener('click', () => this.quitGame());
|
||||
document.getElementById('play-again-btn').addEventListener('click', () => this.resetGame());
|
||||
safeAddListener('start-btn', 'click', () => this.startGame());
|
||||
safeAddListener('resume-btn', 'click', () => this.resumeGame());
|
||||
safeAddListener('quit-btn', 'click', () => this.quitGame());
|
||||
safeAddListener('play-again-btn', 'click', () => this.resetGame());
|
||||
|
||||
// Game mode selection
|
||||
this.initializeGameModeListeners();
|
||||
|
||||
// Game actions
|
||||
document.getElementById('complete-btn').addEventListener('click', () => this.completeTask());
|
||||
document.getElementById('skip-btn').addEventListener('click', () => this.skipTask());
|
||||
document.getElementById('mercy-skip-btn').addEventListener('click', () => this.mercySkip());
|
||||
document.getElementById('pause-btn').addEventListener('click', () => this.pauseGame());
|
||||
// Game actions - support both main game and Quick Play button IDs
|
||||
safeAddListener('complete-btn', 'click', () => this.completeTask());
|
||||
safeAddListener('complete-task', 'click', () => this.completeTask());
|
||||
safeAddListener('skip-btn', 'click', () => this.skipTask());
|
||||
safeAddListener('skip-task', 'click', () => this.skipTask());
|
||||
safeAddListener('mercy-skip-btn', 'click', () => this.mercySkip());
|
||||
safeAddListener('pause-btn', 'click', () => this.pauseGame());
|
||||
|
||||
// Theme selector
|
||||
document.getElementById('theme-dropdown').addEventListener('change', (e) => this.changeTheme(e.target.value));
|
||||
safeAddListener('theme-dropdown', 'change', (e) => this.changeTheme(e.target.value));
|
||||
|
||||
// Options menu toggle
|
||||
document.getElementById('options-menu-btn').addEventListener('click', () => this.toggleOptionsMenu());
|
||||
safeAddListener('options-menu-btn', 'click', () => this.toggleOptionsMenu());
|
||||
|
||||
// Window cleanup - stop audio when app is closed
|
||||
let audioCleanupDone = false;
|
||||
window.addEventListener('beforeunload', () => {
|
||||
console.log('Window closing - stopping all audio');
|
||||
this.audioManager.stopAllImmediate();
|
||||
if (!audioCleanupDone && !window.isForceExiting) {
|
||||
console.log('Window closing - stopping all audio');
|
||||
audioCleanupDone = true;
|
||||
try {
|
||||
this.audioManager.stopAllImmediate();
|
||||
} catch (error) {
|
||||
console.warn('Error during audio cleanup:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Music controls
|
||||
document.getElementById('music-toggle').addEventListener('click', () => this.toggleMusic());
|
||||
document.getElementById('music-toggle-compact').addEventListener('click', (e) => {
|
||||
safeAddListener('music-toggle', 'click', () => this.toggleMusic());
|
||||
safeAddListener('music-toggle-compact', 'click', (e) => {
|
||||
e.stopPropagation(); // Prevent event bubbling
|
||||
// The hover panel will show automatically, just indicate it's interactive
|
||||
});
|
||||
document.getElementById('loop-btn').addEventListener('click', () => this.toggleLoop());
|
||||
document.getElementById('shuffle-btn').addEventListener('click', () => this.toggleShuffle());
|
||||
document.getElementById('track-selector').addEventListener('change', (e) => this.changeTrack(parseInt(e.target.value)));
|
||||
document.getElementById('volume-slider').addEventListener('input', (e) => this.changeVolume(parseInt(e.target.value)));
|
||||
safeAddListener('loop-btn', 'click', () => this.toggleLoop());
|
||||
safeAddListener('shuffle-btn', 'click', () => this.toggleShuffle());
|
||||
safeAddListener('track-selector', 'change', (e) => this.changeTrack(parseInt(e.target.value)));
|
||||
safeAddListener('volume-slider', 'input', (e) => this.changeVolume(parseInt(e.target.value)));
|
||||
|
||||
// Task management
|
||||
document.getElementById('manage-tasks-btn').addEventListener('click', () => this.showTaskManagement());
|
||||
document.getElementById('back-to-start-btn').addEventListener('click', () => this.showScreen('start-screen'));
|
||||
document.getElementById('add-task-btn').addEventListener('click', () => this.addNewTask());
|
||||
document.getElementById('reset-tasks-btn').addEventListener('click', () => this.resetToDefaultTasks());
|
||||
document.getElementById('main-tasks-tab').addEventListener('click', () => this.showTaskTab('main'));
|
||||
document.getElementById('consequence-tasks-tab').addEventListener('click', () => this.showTaskTab('consequence'));
|
||||
document.getElementById('new-task-type').addEventListener('change', () => this.toggleDifficultyDropdown());
|
||||
safeAddListener('manage-tasks-btn', 'click', () => this.showTaskManagement());
|
||||
safeAddListener('back-to-start-btn', 'click', () => this.showScreen('start-screen'));
|
||||
safeAddListener('add-task-btn', 'click', () => this.addNewTask());
|
||||
safeAddListener('reset-tasks-btn', 'click', () => this.resetToDefaultTasks());
|
||||
safeAddListener('main-tasks-tab', 'click', () => this.showTaskTab('main'));
|
||||
safeAddListener('consequence-tasks-tab', 'click', () => this.showTaskTab('consequence'));
|
||||
safeAddListener('new-task-type', 'change', () => this.toggleDifficultyDropdown());
|
||||
|
||||
// Data management
|
||||
document.getElementById('export-btn').addEventListener('click', () => this.exportData());
|
||||
document.getElementById('import-btn').addEventListener('click', () => this.importData());
|
||||
document.getElementById('import-file').addEventListener('change', (e) => this.handleFileImport(e));
|
||||
document.getElementById('stats-btn').addEventListener('click', () => this.showStats());
|
||||
document.getElementById('help-btn').addEventListener('click', () => this.showHelp());
|
||||
document.getElementById('close-stats').addEventListener('click', () => this.hideStats());
|
||||
document.getElementById('close-help').addEventListener('click', () => this.hideHelp());
|
||||
document.getElementById('reset-stats-btn').addEventListener('click', () => this.resetStats());
|
||||
document.getElementById('export-stats-btn').addEventListener('click', () => this.exportStatsOnly());
|
||||
safeAddListener('export-btn', 'click', () => this.exportData());
|
||||
safeAddListener('import-btn', 'click', () => this.importData());
|
||||
safeAddListener('import-file', 'change', (e) => this.handleFileImport(e));
|
||||
safeAddListener('stats-btn', 'click', () => this.showStats());
|
||||
safeAddListener('help-btn', 'click', () => this.showHelp());
|
||||
safeAddListener('close-stats', 'click', () => this.hideStats());
|
||||
safeAddListener('close-help', 'click', () => this.hideHelp());
|
||||
safeAddListener('reset-stats-btn', 'click', () => this.resetStats());
|
||||
safeAddListener('export-stats-btn', 'click', () => this.exportStatsOnly());
|
||||
|
||||
// Audio controls
|
||||
this.initializeAudioControls();
|
||||
|
||||
// Image management - only the main button, others will be attached when screen is shown
|
||||
document.getElementById('manage-images-btn').addEventListener('click', () => this.showImageManagement());
|
||||
safeAddListener('manage-images-btn', 'click', () => this.showImageManagement());
|
||||
|
||||
// Audio management - only the main button, others will be attached when screen is shown
|
||||
document.getElementById('manage-audio-btn').addEventListener('click', () => this.showAudioManagement());
|
||||
safeAddListener('manage-audio-btn', 'click', () => this.showAudioManagement());
|
||||
|
||||
// Photo gallery management
|
||||
document.getElementById('photo-gallery-btn').addEventListener('click', () => this.showPhotoGallery());
|
||||
safeAddListener('photo-gallery-btn', 'click', () => this.showPhotoGallery());
|
||||
|
||||
// Annoyance management - main button and basic controls
|
||||
document.getElementById('manage-annoyance-btn').addEventListener('click', () => this.showAnnoyanceManagement());
|
||||
safeAddListener('manage-annoyance-btn', 'click', () => this.showAnnoyanceManagement());
|
||||
|
||||
// Load saved theme
|
||||
this.loadSavedTheme();
|
||||
|
|
@ -1762,8 +1946,14 @@ class TaskChallengeGame {
|
|||
}
|
||||
|
||||
initializeAudioControls() {
|
||||
// Volume sliders
|
||||
// Check if audio controls exist before trying to initialize them
|
||||
const masterVolumeSlider = document.getElementById('master-volume');
|
||||
if (!masterVolumeSlider) {
|
||||
console.log('⚠️ Audio controls not found, skipping audio control initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
// Volume sliders
|
||||
const taskAudioSlider = document.getElementById('task-audio-volume');
|
||||
const punishmentAudioSlider = document.getElementById('punishment-audio-volume');
|
||||
const rewardAudioSlider = document.getElementById('reward-audio-volume');
|
||||
|
|
@ -2068,41 +2258,23 @@ class TaskChallengeGame {
|
|||
});
|
||||
});
|
||||
|
||||
// Add listeners for dropdown changes (if elements exist)
|
||||
const timeLimitSelect = document.getElementById('time-limit-select');
|
||||
if (timeLimitSelect) {
|
||||
console.log('⏱️ Found time limit select, adding listener');
|
||||
timeLimitSelect.addEventListener('change', () => {
|
||||
console.log('⏱️ Time limit select changed');
|
||||
this.handleTimeLimitChange();
|
||||
// Add listeners for Quick Play controls
|
||||
const playTimeSelect = document.getElementById('play-time-select');
|
||||
if (playTimeSelect) {
|
||||
console.log('⏱️ Found play time select, adding listener');
|
||||
playTimeSelect.addEventListener('change', () => {
|
||||
console.log('⏱️ Play time select changed');
|
||||
this.handleQuickPlayTimeChange();
|
||||
});
|
||||
} else {
|
||||
console.log('⚠️ Time limit select not found');
|
||||
}
|
||||
|
||||
const xpTargetSelect = document.getElementById('xp-target-select');
|
||||
if (xpTargetSelect) {
|
||||
console.log('⭐ Found XP target select, adding listener');
|
||||
xpTargetSelect.addEventListener('change', () => {
|
||||
console.log('⭐ XP target select changed');
|
||||
this.handleXpTargetChange();
|
||||
});
|
||||
} else {
|
||||
console.log('⚠️ XP target select not found');
|
||||
console.log('⚠️ Play time select not found');
|
||||
}
|
||||
|
||||
// Add listeners for custom input changes (if elements exist)
|
||||
const customTimeValue = document.getElementById('custom-time-value');
|
||||
if (customTimeValue) {
|
||||
customTimeValue.addEventListener('input', () => {
|
||||
this.handleCustomTimeChange();
|
||||
});
|
||||
}
|
||||
|
||||
const customXpValue = document.getElementById('custom-xp-value');
|
||||
if (customXpValue) {
|
||||
customXpValue.addEventListener('input', () => {
|
||||
this.handleCustomXpChange();
|
||||
const customPlayTimeValue = document.getElementById('custom-play-time-value');
|
||||
if (customPlayTimeValue) {
|
||||
customPlayTimeValue.addEventListener('input', () => {
|
||||
this.handleCustomPlayTimeChange();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2119,32 +2291,22 @@ class TaskChallengeGame {
|
|||
console.log(`🎮 Game mode changed to: ${selectedMode}`);
|
||||
|
||||
// Show/hide configuration options based on selected mode (if elements exist)
|
||||
const timedConfig = document.getElementById('timed-config');
|
||||
const xpTargetConfig = document.getElementById('xp-target-config');
|
||||
const quickPlayConfig = document.getElementById('quick-play-config');
|
||||
|
||||
// Hide all configs first
|
||||
if (timedConfig) {
|
||||
timedConfig.style.display = 'none';
|
||||
}
|
||||
if (xpTargetConfig) {
|
||||
xpTargetConfig.style.display = 'none';
|
||||
}
|
||||
// Hide all configs first (in case there are any old ones)
|
||||
const allConfigs = document.querySelectorAll('.mode-config');
|
||||
allConfigs.forEach(config => config.style.display = 'none');
|
||||
|
||||
// Show appropriate config
|
||||
if (selectedMode === 'timed' && timedConfig) {
|
||||
timedConfig.style.display = 'block';
|
||||
console.log('⏱️ Showing timed configuration options');
|
||||
this.handleTimeLimitChange();
|
||||
} else if (selectedMode === 'xp-target' && xpTargetConfig) {
|
||||
xpTargetConfig.style.display = 'block';
|
||||
console.log('⭐ Showing XP target configuration options');
|
||||
this.handleXpTargetChange();
|
||||
// Show Quick Play config (it's the only mode now)
|
||||
if (selectedMode === 'quick-play' && quickPlayConfig) {
|
||||
quickPlayConfig.style.display = 'block';
|
||||
console.log('⚡ Showing Quick Play configuration options');
|
||||
this.handleQuickPlayTimeChange();
|
||||
}
|
||||
|
||||
console.log(`Game state updated:`, {
|
||||
gameMode: this.gameState.gameMode,
|
||||
timeLimit: this.gameState.timeLimit,
|
||||
xpTarget: this.gameState.xpTarget
|
||||
timeLimit: this.gameState.timeLimit
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2222,6 +2384,43 @@ class TaskChallengeGame {
|
|||
}
|
||||
}
|
||||
|
||||
handleQuickPlayTimeChange() {
|
||||
const playTimeSelect = document.getElementById('play-time-select');
|
||||
const customPlayTimeInput = document.getElementById('custom-play-time-input');
|
||||
|
||||
if (!playTimeSelect) {
|
||||
console.log('⚠️ Play time select element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedValue = playTimeSelect.value;
|
||||
console.log(`⚡ Play time selection: ${selectedValue}`);
|
||||
|
||||
if (customPlayTimeInput) {
|
||||
if (selectedValue === 'custom') {
|
||||
customPlayTimeInput.style.display = 'block';
|
||||
console.log('⚡ Showing custom play time input');
|
||||
this.handleCustomPlayTimeChange();
|
||||
} else {
|
||||
customPlayTimeInput.style.display = 'none';
|
||||
this.gameState.timeLimit = parseInt(selectedValue);
|
||||
console.log(`⚡ Play time set to: ${this.gameState.timeLimit} seconds`);
|
||||
}
|
||||
} else if (selectedValue !== 'custom') {
|
||||
this.gameState.timeLimit = parseInt(selectedValue);
|
||||
console.log(`⚡ Play time set to: ${this.gameState.timeLimit} seconds`);
|
||||
}
|
||||
}
|
||||
|
||||
handleCustomPlayTimeChange() {
|
||||
const customPlayTimeValue = document.getElementById('custom-play-time-value');
|
||||
if (customPlayTimeValue) {
|
||||
const minutes = parseInt(customPlayTimeValue.value) || 15;
|
||||
this.gameState.timeLimit = minutes * 60; // Convert minutes to seconds
|
||||
console.log(`Custom play time set to ${minutes} minutes (${this.gameState.timeLimit} seconds)`);
|
||||
}
|
||||
}
|
||||
|
||||
// XP Calculation Methods
|
||||
calculateTimeBasedXp() {
|
||||
if (!this.gameState.sessionStartTime) return 0;
|
||||
|
|
@ -4593,11 +4792,64 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
}
|
||||
|
||||
// Get selected game mode
|
||||
const selectedMode = document.querySelector('input[name="gameMode"]:checked')?.value || 'standard';
|
||||
const selectedMode = document.querySelector('input[name="gameMode"]:checked')?.value || 'quick-play';
|
||||
if (window.gameModeManager) {
|
||||
window.gameModeManager.currentMode = selectedMode;
|
||||
// Map the new mode names to the original game engine modes
|
||||
this.gameState.gameMode = window.gameModeManager.getGameModeForEngine();
|
||||
|
||||
// Apply Quick Play settings
|
||||
if (selectedMode === 'quick-play' && window.quickPlaySettings) {
|
||||
// Set play time
|
||||
this.gameState.timeLimit = window.quickPlaySettings.getPlayTime();
|
||||
console.log(`⚡ Quick Play time limit set to: ${this.gameState.timeLimit} seconds`);
|
||||
|
||||
// Configure background audio
|
||||
const backgroundAudioEnabled = window.quickPlaySettings.isBackgroundAudioEnabled();
|
||||
if (this.audioManager) {
|
||||
if (backgroundAudioEnabled) {
|
||||
this.audioManager.resumePlaylist();
|
||||
console.log('🎵 Background audio enabled for Quick Play');
|
||||
} else {
|
||||
this.audioManager.pausePlaylist();
|
||||
console.log('🔇 Background audio disabled for Quick Play');
|
||||
}
|
||||
}
|
||||
|
||||
// Configure popup images
|
||||
if (this.popupImageManager) {
|
||||
const popupFrequency = window.quickPlaySettings.getPopupFrequency();
|
||||
const popupDuration = window.quickPlaySettings.getPopupDuration();
|
||||
|
||||
// Update popup image settings using the correct method
|
||||
this.popupImageManager.updateConfig({
|
||||
displayDuration: popupDuration * 1000, // Convert to milliseconds
|
||||
});
|
||||
|
||||
// Update periodic popup settings
|
||||
if (this.popupImageManager.updatePeriodicSettings) {
|
||||
this.popupImageManager.updatePeriodicSettings({
|
||||
minInterval: popupFrequency,
|
||||
maxInterval: popupFrequency,
|
||||
displayDuration: popupDuration
|
||||
});
|
||||
}
|
||||
console.log(`🖼️ Popup images: ${popupFrequency}s frequency, ${popupDuration}s duration`);
|
||||
}
|
||||
|
||||
// Configure flash messages
|
||||
if (this.flashMessageManager) {
|
||||
const messageFrequency = window.quickPlaySettings.getMessageFrequency();
|
||||
const messageDuration = window.quickPlaySettings.getMessageDuration();
|
||||
|
||||
// Update flash message settings
|
||||
this.flashMessageManager.updateConfig({
|
||||
displayDuration: messageDuration * 1000, // Convert to milliseconds
|
||||
intervalDelay: messageFrequency * 1000 // Convert to milliseconds
|
||||
});
|
||||
console.log(`💬 Flash messages: ${messageFrequency}s frequency, ${messageDuration}s duration`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback if gameModeManager is not available
|
||||
this.gameState.gameMode = selectedMode === 'timed' ? 'timed' :
|
||||
|
|
@ -4841,7 +5093,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
const taskContainer = document.querySelector('.task-container');
|
||||
const taskTypeIndicator = document.getElementById('task-type-indicator');
|
||||
const mercySkipBtn = document.getElementById('mercy-skip-btn');
|
||||
const skipBtn = document.getElementById('skip-btn');
|
||||
const skipBtn = document.getElementById('skip-btn') || document.getElementById('skip-task');
|
||||
|
||||
// Note: task-difficulty and task-points elements were removed during XP conversion
|
||||
// Note: task-type-indicator element may not exist in current HTML
|
||||
|
|
@ -4859,15 +5111,17 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
}
|
||||
|
||||
// Update skip button text for scenario modes
|
||||
if (isScenarioMode) {
|
||||
if (isScenarioMode && skipBtn) {
|
||||
skipBtn.textContent = 'Give Up';
|
||||
skipBtn.className = 'btn btn-danger'; // Change to red for more serious action
|
||||
} else {
|
||||
} else if (skipBtn) {
|
||||
skipBtn.textContent = 'Skip';
|
||||
skipBtn.className = 'btn btn-warning'; // Default yellow
|
||||
}
|
||||
|
||||
taskText.textContent = this.gameState.currentTask.text;
|
||||
if (taskText) {
|
||||
taskText.textContent = this.gameState.currentTask.text;
|
||||
}
|
||||
|
||||
// Hide task image for scenario games to prevent scrolling issues
|
||||
if (isScenarioMode) {
|
||||
|
|
@ -4892,7 +5146,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
}
|
||||
|
||||
// Set image with error handling (only for non-scenario modes)
|
||||
if (!isScenarioMode) {
|
||||
if (!isScenarioMode && taskImage) {
|
||||
taskImage.src = this.gameState.currentTask.image;
|
||||
taskImage.onerror = () => {
|
||||
console.log('Image failed to load:', this.gameState.currentTask.image);
|
||||
|
|
@ -4924,25 +5178,33 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
taskTypeIndicator.classList.add('consequence');
|
||||
}
|
||||
// Hide regular skip button for consequence tasks
|
||||
skipBtn.style.display = 'none';
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show mercy skip button for consequence tasks
|
||||
const originalTask = this.findOriginalSkippedTask();
|
||||
if (originalTask) {
|
||||
if (originalTask && mercySkipBtn) {
|
||||
// In XP system, mercy skip costs 5 XP (flat rate as per ROADMAP)
|
||||
const mercyCost = 5;
|
||||
const canAfford = this.gameState.xp >= mercyCost;
|
||||
|
||||
if (canAfford) {
|
||||
mercySkipBtn.style.display = 'block';
|
||||
document.getElementById('mercy-skip-cost').textContent = `-${mercyCost} XP`;
|
||||
const mercyCostElement = document.getElementById('mercy-skip-cost');
|
||||
if (mercyCostElement) {
|
||||
mercyCostElement.textContent = `-${mercyCost} XP`;
|
||||
}
|
||||
mercySkipBtn.disabled = false;
|
||||
} else {
|
||||
mercySkipBtn.style.display = 'block';
|
||||
document.getElementById('mercy-skip-cost').textContent = `-${mercyCost} XP (Not enough!)`;
|
||||
const mercyCostElement = document.getElementById('mercy-skip-cost');
|
||||
if (mercyCostElement) {
|
||||
mercyCostElement.textContent = `-${mercyCost} XP (Not enough!)`;
|
||||
}
|
||||
mercySkipBtn.disabled = true;
|
||||
}
|
||||
} else {
|
||||
} else if (mercySkipBtn) {
|
||||
mercySkipBtn.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
|
|
@ -4952,13 +5214,23 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
}
|
||||
|
||||
// Show regular skip button for main tasks
|
||||
skipBtn.style.display = 'block';
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'block';
|
||||
}
|
||||
// Hide mercy skip button for main tasks
|
||||
mercySkipBtn.style.display = 'none';
|
||||
if (mercySkipBtn) {
|
||||
mercySkipBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Note: In XP system, no need to display difficulty/points separately
|
||||
// The XP is awarded based on time and activities, not task difficulty
|
||||
}
|
||||
|
||||
// Show complete button for all tasks (support both ID patterns)
|
||||
const completeBtn = document.getElementById('complete-btn') || document.getElementById('complete-task');
|
||||
if (completeBtn) {
|
||||
completeBtn.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
findOriginalSkippedTask() {
|
||||
|
|
@ -5714,32 +5986,39 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
formattedTime = this.formatTime(remainingMs);
|
||||
|
||||
// Change color when time is running low (less than 30 seconds)
|
||||
const timerElement = document.getElementById('timer');
|
||||
if (remainingMs <= 30000) {
|
||||
timerElement.style.color = '#ff4757';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else if (remainingMs <= 60000) {
|
||||
timerElement.style.color = '#ffa502';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else {
|
||||
timerElement.style.color = '';
|
||||
timerElement.style.fontWeight = '';
|
||||
const timerElement = document.getElementById('timer') || document.getElementById('game-timer');
|
||||
if (timerElement) {
|
||||
if (remainingMs <= 30000) {
|
||||
timerElement.style.color = '#ff4757';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else if (remainingMs <= 60000) {
|
||||
timerElement.style.color = '#ffa502';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else {
|
||||
timerElement.style.color = '';
|
||||
timerElement.style.fontWeight = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal elapsed timer for other modes
|
||||
formattedTime = this.formatTime(elapsed);
|
||||
}
|
||||
|
||||
document.getElementById('timer').textContent = formattedTime;
|
||||
const timerDisplayElement = document.getElementById('timer') || document.getElementById('game-timer');
|
||||
if (timerDisplayElement) {
|
||||
timerDisplayElement.textContent = formattedTime;
|
||||
}
|
||||
|
||||
// Update timer status
|
||||
const timerStatus = document.getElementById('timer-status');
|
||||
if (this.gameState.isPaused) {
|
||||
timerStatus.textContent = '(PAUSED)';
|
||||
} else if (this.gameState.gameMode === 'timed') {
|
||||
timerStatus.textContent = '(TIME LEFT)';
|
||||
} else {
|
||||
timerStatus.textContent = '';
|
||||
if (timerStatus) {
|
||||
if (this.gameState.isPaused) {
|
||||
timerStatus.textContent = '(PAUSED)';
|
||||
} else if (this.gameState.gameMode === 'timed') {
|
||||
timerStatus.textContent = '(TIME LEFT)';
|
||||
} else {
|
||||
timerStatus.textContent = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5752,10 +6031,18 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
}
|
||||
|
||||
updateStats() {
|
||||
document.getElementById('completed-count').textContent = this.gameState.completedCount;
|
||||
document.getElementById('skipped-count').textContent = this.gameState.skippedCount;
|
||||
document.getElementById('consequence-count').textContent = this.gameState.consequenceCount;
|
||||
document.getElementById('xp').textContent = this.gameState.xp || 0;
|
||||
// Helper function to safely update element text content
|
||||
const safeUpdateText = (id, value) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
}
|
||||
};
|
||||
|
||||
safeUpdateText('completed-count', this.gameState.completedCount);
|
||||
safeUpdateText('skipped-count', this.gameState.skippedCount);
|
||||
safeUpdateText('consequence-count', this.gameState.consequenceCount);
|
||||
safeUpdateText('xp', this.gameState.xp || 0);
|
||||
|
||||
// Update streak display
|
||||
const streakElement = document.getElementById('current-streak');
|
||||
|
|
@ -6453,6 +6740,12 @@ class MusicManager {
|
|||
const volumePercent = document.getElementById('volume-percent');
|
||||
const trackSelector = document.getElementById('track-selector');
|
||||
|
||||
// Check if UI elements exist before updating them
|
||||
if (!volumeSlider || !volumePercent || !trackSelector) {
|
||||
// UI elements not available (probably in dedicated screen mode)
|
||||
return;
|
||||
}
|
||||
|
||||
volumeSlider.value = this.volume;
|
||||
volumePercent.textContent = `${this.volume}%`;
|
||||
|
||||
|
|
@ -6482,6 +6775,8 @@ class MusicManager {
|
|||
|
||||
updateLoopButton() {
|
||||
const loopBtn = document.getElementById('loop-btn');
|
||||
if (!loopBtn) return;
|
||||
|
||||
switch(this.loopMode) {
|
||||
case 0:
|
||||
loopBtn.classList.remove('active');
|
||||
|
|
@ -6503,6 +6798,8 @@ class MusicManager {
|
|||
|
||||
updateShuffleButton() {
|
||||
const shuffleBtn = document.getElementById('shuffle-btn');
|
||||
if (!shuffleBtn) return;
|
||||
|
||||
if (this.shuffleMode) {
|
||||
shuffleBtn.classList.add('active');
|
||||
shuffleBtn.title = 'Shuffle: On';
|
||||
|
|
@ -6700,6 +6997,12 @@ class MusicManager {
|
|||
const trackSelector = document.getElementById('track-selector');
|
||||
const currentTrack = this.tracks[this.currentTrackIndex];
|
||||
|
||||
// Check if UI elements exist before updating them
|
||||
if (!toggleBtn || !statusSpan || !trackSelector) {
|
||||
// UI elements not available (probably in dedicated screen mode)
|
||||
return;
|
||||
}
|
||||
|
||||
// Update track selector
|
||||
trackSelector.value = this.currentTrackIndex;
|
||||
|
||||
|
|
@ -8502,6 +8805,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
if (window.game && window.game.isInitialized) {
|
||||
enableElements();
|
||||
console.log('✅ Game fully initialized and ready for interaction');
|
||||
|
||||
// Dispatch game ready event for other systems
|
||||
window.dispatchEvent(new CustomEvent('gameReady', {
|
||||
detail: { game: window.game }
|
||||
}));
|
||||
} else {
|
||||
setTimeout(checkInitialization, 100);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,12 @@
|
|||
|
||||
class GameModeManager {
|
||||
constructor() {
|
||||
this.currentMode = 'standard'; // Default to standard mode
|
||||
this.currentMode = 'quick-play'; // Default to quick play mode
|
||||
this.availableModes = {
|
||||
'standard': {
|
||||
name: 'Standard Game',
|
||||
description: 'Classic endless task mode with regular tasks only',
|
||||
icon: '🎮'
|
||||
},
|
||||
'timed': {
|
||||
name: 'Timed Challenge',
|
||||
description: 'Race against the clock to complete as many tasks as possible',
|
||||
icon: '⏰'
|
||||
},
|
||||
'scored': {
|
||||
name: 'Score Target',
|
||||
description: 'Reach a target score by completing tasks based on difficulty',
|
||||
icon: '🎯'
|
||||
'quick-play': {
|
||||
name: 'Quick Play',
|
||||
description: 'Timed challenge mode with customizable options',
|
||||
icon: '⚡'
|
||||
},
|
||||
'photography-studio': {
|
||||
name: 'Photography Studio',
|
||||
|
|
@ -92,62 +82,83 @@ class GameModeManager {
|
|||
console.log('🔍 Looking for .game-mode-selection element:', gameModeSelection);
|
||||
|
||||
if (gameModeSelection) {
|
||||
console.log('✅ Found game mode selection, replacing with new modes');
|
||||
console.log('✅ Found game mode selection, replacing with updated modes');
|
||||
gameModeSelection.innerHTML = `
|
||||
<h3>Choose Your Game Mode</h3>
|
||||
<div class="game-mode-grid">
|
||||
${Object.entries(this.availableModes).map(([key, mode]) => `
|
||||
<div class="game-mode-card" data-mode="${key}">
|
||||
<div class="game-mode-card ${key === 'quick-play' ? 'selected' : ''}" data-mode="${key}">
|
||||
<div class="mode-icon">${mode.icon}</div>
|
||||
<div class="mode-info">
|
||||
<h4>${mode.name}</h4>
|
||||
<p>${mode.description}</p>
|
||||
</div>
|
||||
<input type="radio" name="gameMode" value="${key}" id="mode-${key}" ${key === 'standard' ? 'checked' : ''}>
|
||||
<input type="radio" name="gameMode" value="${key}" id="mode-${key}" ${key === 'quick-play' ? 'checked' : ''}>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="mode-options" id="mode-options">
|
||||
<!-- Timed Challenge Configuration -->
|
||||
<div class="mode-config" id="timed-config" style="display: none;">
|
||||
<!-- Quick Play Configuration -->
|
||||
<div class="mode-config" id="quick-play-config" style="display: block;">
|
||||
<div class="config-section">
|
||||
<h4>⏱️ Time Limit Configuration</h4>
|
||||
<label>Time Limit:
|
||||
<select id="time-limit-select">
|
||||
<option value="300">5 minutes</option>
|
||||
<option value="600">10 minutes</option>
|
||||
<option value="900">15 minutes</option>
|
||||
<option value="1200">20 minutes</option>
|
||||
<option value="1800">30 minutes</option>
|
||||
<option value="custom">Custom...</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="custom-time-input" style="display: none; margin-top: 10px;">
|
||||
<label>Custom time (minutes):
|
||||
<input type="number" id="custom-time-value" min="1" max="180" value="15" style="width: 60px;">
|
||||
<h4>⚡ Quick Play Options</h4>
|
||||
|
||||
<!-- Play Time Configuration -->
|
||||
<div class="option-group">
|
||||
<label>⏱️ Play Time:
|
||||
<select id="play-time-select">
|
||||
<option value="300">5 minutes</option>
|
||||
<option value="600">10 minutes</option>
|
||||
<option value="900">15 minutes</option>
|
||||
<option value="1200">20 minutes</option>
|
||||
<option value="1800">30 minutes</option>
|
||||
<option value="custom">Custom...</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="custom-play-time-input" style="display: none; margin-top: 10px;">
|
||||
<label>Custom time (minutes):
|
||||
<input type="number" id="custom-play-time-value" min="1" max="180" value="15" style="width: 60px;">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background Audio Configuration -->
|
||||
<div class="option-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="enable-background-audio" checked>
|
||||
🎵 Enable Background Audio Tracks
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Score Target Configuration -->
|
||||
<div class="mode-config" id="score-target-config" style="display: none;">
|
||||
<div class="config-section">
|
||||
<h4>🏆 Score Target Configuration</h4>
|
||||
<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>
|
||||
<option value="500">500 points</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;">
|
||||
</label>
|
||||
<!-- Popup Image Configuration -->
|
||||
<div class="option-group">
|
||||
<h5>🖼️ Popup Images</h5>
|
||||
<div class="sub-option">
|
||||
<label>Frequency (seconds between popups):
|
||||
<input type="number" id="popup-frequency" min="10" max="300" value="60" style="width: 70px;">
|
||||
</label>
|
||||
</div>
|
||||
<div class="sub-option">
|
||||
<label>Duration (seconds displayed):
|
||||
<input type="number" id="popup-duration" min="2" max="30" value="8" style="width: 70px;">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Flash Message Configuration -->
|
||||
<div class="option-group">
|
||||
<h5>💬 Flash Messages</h5>
|
||||
<div class="sub-option">
|
||||
<label>Frequency (seconds between messages):
|
||||
<input type="number" id="message-frequency" min="5" max="300" value="45" style="width: 70px;">
|
||||
</label>
|
||||
</div>
|
||||
<div class="sub-option">
|
||||
<label>Duration (seconds displayed):
|
||||
<input type="number" id="message-duration" min="1" max="10" value="3" style="width: 70px;">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -160,38 +171,80 @@ class GameModeManager {
|
|||
<input type="checkbox" id="include-custom-tasks" checked>
|
||||
Include your custom tasks alongside scenarios
|
||||
</label>
|
||||
<div class="frequency-control" id="custom-task-frequency">
|
||||
<label>Custom task frequency:
|
||||
<span id="frequency-display">30%</span>
|
||||
</label>
|
||||
<input type="range" id="frequency-slider" min="10" max="70" value="30" step="10">
|
||||
<small>How often custom tasks appear between scenarios</small>
|
||||
<div class="frequency-control">
|
||||
<label>Custom Task Frequency: <span id="frequency-display">30%</span></label>
|
||||
<input type="range" id="custom-task-frequency" min="0" max="100" value="30" step="10">
|
||||
<small class="help-text">How often custom tasks appear between scenario tasks</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<h4>⚙️ Scenario Settings</h4>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="randomize-scenario-order" checked>
|
||||
Randomize scenario order
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="allow-scenario-repeats">
|
||||
Allow scenario repeats after completion
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listeners for the new controls
|
||||
this.setupQuickPlayListeners();
|
||||
|
||||
// Verify the radio buttons were created
|
||||
const radioButtons = gameModeSelection.querySelectorAll('input[name="gameMode"]');
|
||||
console.log(`🔘 Created ${radioButtons.length} radio buttons:`, Array.from(radioButtons).map(r => r.value));
|
||||
} else {
|
||||
console.error('❌ Could not find .game-mode-selection element');
|
||||
console.log('⚠️ Game mode selection element not found - skipping UI setup (likely in dedicated screen)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup event listeners for Quick Play configuration
|
||||
*/
|
||||
setupQuickPlayListeners() {
|
||||
// Play time selection
|
||||
const playTimeSelect = document.getElementById('play-time-select');
|
||||
const customPlayTimeInput = document.getElementById('custom-play-time-input');
|
||||
const customPlayTimeValue = document.getElementById('custom-play-time-value');
|
||||
|
||||
if (playTimeSelect) {
|
||||
playTimeSelect.addEventListener('change', () => {
|
||||
if (playTimeSelect.value === 'custom') {
|
||||
customPlayTimeInput.style.display = 'block';
|
||||
} else {
|
||||
customPlayTimeInput.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Store all settings in a global object for easy access
|
||||
window.quickPlaySettings = {
|
||||
getPlayTime: () => {
|
||||
const select = document.getElementById('play-time-select');
|
||||
if (select.value === 'custom') {
|
||||
const customValue = document.getElementById('custom-play-time-value');
|
||||
return parseInt(customValue.value) * 60; // Convert minutes to seconds
|
||||
} else {
|
||||
return parseInt(select.value); // Already in seconds
|
||||
}
|
||||
},
|
||||
isBackgroundAudioEnabled: () => {
|
||||
const checkbox = document.getElementById('enable-background-audio');
|
||||
return checkbox ? checkbox.checked : true;
|
||||
},
|
||||
getPopupFrequency: () => {
|
||||
const input = document.getElementById('popup-frequency');
|
||||
return input ? parseInt(input.value) : 60;
|
||||
},
|
||||
getPopupDuration: () => {
|
||||
const input = document.getElementById('popup-duration');
|
||||
return input ? parseInt(input.value) : 8;
|
||||
},
|
||||
getMessageFrequency: () => {
|
||||
const input = document.getElementById('message-frequency');
|
||||
return input ? parseInt(input.value) : 45;
|
||||
},
|
||||
getMessageDuration: () => {
|
||||
const input = document.getElementById('message-duration');
|
||||
return input ? parseInt(input.value) : 3;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove hint buttons from scenarios in game.js
|
||||
*/
|
||||
|
|
@ -264,26 +317,19 @@ class GameModeManager {
|
|||
const allConfigs = document.querySelectorAll('.mode-config');
|
||||
allConfigs.forEach(config => config.style.display = 'none');
|
||||
|
||||
// Show scenario config for scenario modes
|
||||
if (mode !== 'standard' && mode !== 'timed' && mode !== 'scored') {
|
||||
// Show appropriate config based on mode
|
||||
if (mode === 'quick-play') {
|
||||
const quickPlayConfig = document.getElementById('quick-play-config');
|
||||
if (quickPlayConfig) {
|
||||
quickPlayConfig.style.display = 'block';
|
||||
console.log('⚡ Showing Quick Play configuration');
|
||||
}
|
||||
} else {
|
||||
// Show scenario config for scenario modes
|
||||
const scenarioConfig = document.getElementById('scenario-config');
|
||||
if (scenarioConfig) {
|
||||
scenarioConfig.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// For timed and scored modes, show the original configuration UI
|
||||
if (mode === 'timed') {
|
||||
const timedConfig = document.getElementById('timed-config');
|
||||
if (timedConfig) {
|
||||
timedConfig.style.display = 'block';
|
||||
console.log('⏱️ Showing timed challenge configuration');
|
||||
}
|
||||
} else if (mode === 'scored') {
|
||||
const scoreTargetConfig = document.getElementById('score-target-config');
|
||||
if (scoreTargetConfig) {
|
||||
scoreTargetConfig.style.display = 'block';
|
||||
console.log('🏆 Showing score target configuration');
|
||||
console.log('🎮 Showing scenario configuration');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,7 +344,7 @@ class GameModeManager {
|
|||
* Get tasks for the current game mode
|
||||
*/
|
||||
getTasksForMode() {
|
||||
if (this.currentMode === 'standard' || this.currentMode === 'timed' || this.currentMode === 'scored') {
|
||||
if (this.currentMode === 'standard' || this.currentMode === 'timed' || this.currentMode === 'scored' || this.currentMode === 'quick-play') {
|
||||
return this.getStandardTasks();
|
||||
} else {
|
||||
return this.getScenarioModeTasks();
|
||||
|
|
@ -309,15 +355,12 @@ class GameModeManager {
|
|||
* Map new mode names to original game functionality
|
||||
*/
|
||||
getGameModeForEngine() {
|
||||
switch(this.currentMode) {
|
||||
case 'standard':
|
||||
return 'complete-all';
|
||||
case 'timed':
|
||||
return 'timed';
|
||||
case 'scored':
|
||||
return 'score-target';
|
||||
default:
|
||||
return 'complete-all'; // Scenario modes use complete-all as base
|
||||
// Quick Play mode uses timed mode as the base
|
||||
if (this.currentMode === 'quick-play') {
|
||||
return 'timed';
|
||||
} else {
|
||||
// Scenario modes use complete-all as base
|
||||
return 'complete-all';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -652,7 +695,7 @@ class GameModeManager {
|
|||
* Check if current mode is scenario-based
|
||||
*/
|
||||
isScenarioMode() {
|
||||
return this.currentMode && this.currentMode !== 'standard';
|
||||
return this.currentMode && this.currentMode !== 'standard' && this.currentMode !== 'quick-play';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,87 +4,87 @@ const gameData = {
|
|||
mainTasks: [
|
||||
{
|
||||
id: 1,
|
||||
text: "Hold your breath for 30 seconds while maintaining focus",
|
||||
text: "Watch porn and stroke slowly for 2 minutes, no cumming",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Close your eyes and count to 20 without rushing",
|
||||
text: "Edge while watching BBC content sitting in chair for 3 minutes",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Keep your hands completely still for 45 seconds",
|
||||
text: "Stroke on your knees while watching lesbian porn for 5 minutes",
|
||||
difficulty: "Medium"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "Stare at a fixed point without blinking for 30 seconds",
|
||||
text: "Watch cuckold videos standing up and edge for 4 minutes without release",
|
||||
difficulty: "Medium"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Take 10 slow, deep breaths while focusing on control",
|
||||
text: "Goon to compilation videos sitting for 6 minutes, multiple edges",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
text: "Hold a stress position (arms extended) for 60 seconds",
|
||||
text: "Watch interracial porn on your back, legs up, stroke for 7 minutes",
|
||||
difficulty: "Medium"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
text: "Repeat 'I am learning self-control' 10 times slowly",
|
||||
text: "Edge to femdom content while lying face down for 4 minutes",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
text: "Maintain perfect posture for 2 minutes without moving",
|
||||
text: "Watch sissy hypno standing while stroking with your non-dominant hand for 8 minutes",
|
||||
difficulty: "Medium"
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
text: "Edge for 30 seconds then stop immediately",
|
||||
text: "Goon to gangbang videos sitting in chair for 10 minutes, edge every 2 minutes",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
text: "Close your eyes and focus on breathing for 90 seconds",
|
||||
text: "Watch MILF porn while in doggy position, stroke for 6 minutes",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
text: "Hold a challenging yoga pose for 60 seconds",
|
||||
text: "Edge to cuckold captions standing for 12 minutes with multiple stops",
|
||||
difficulty: "Easy"
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
text: "Count backwards from 100 by 7s without losing focus",
|
||||
text: "Watch trans porn while stroking sitting and standing alternately for 9 minutes",
|
||||
difficulty: "Medium"
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
text: "Edge for 2 minutes with 3 stops, then wait 30 seconds",
|
||||
text: "Goon session: 15 minutes of continuous stroking sitting to random videos",
|
||||
difficulty: "Hard"
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
text: "Hold a plank position for 3 minutes while edging",
|
||||
text: "Watch hotwife content while edging on knees for 12 minutes",
|
||||
difficulty: "Hard"
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
text: "Practice edging with varying speeds for 5 minutes",
|
||||
text: "Extended goon session: 15 minutes of edging standing to BBC compilations",
|
||||
difficulty: "Hard"
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
text: "Complete a 10-minute endurance session with no release",
|
||||
text: "Marathon edge session: 15 minutes alternating sitting/standing while watching porn",
|
||||
difficulty: "Hard"
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
text: "Edge to near climax 5 times, then stop for 2 minutes",
|
||||
text: "Ultimate goon challenge: 15 minutes of edging in doggy position to humiliation content",
|
||||
difficulty: "Hard"
|
||||
},
|
||||
],
|
||||
|
|
@ -93,51 +93,51 @@ const gameData = {
|
|||
consequenceTasks: [
|
||||
{
|
||||
id: 101,
|
||||
text: "Hold the edge for 60 seconds without any movement"
|
||||
text: "Watch your girlfriend with another man fantasy while sitting in chastity for 5 minutes"
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
text: "Perform 50 squats while maintaining arousal"
|
||||
text: "Stand in corner and repeat 'I am a sissy slut' 25 times out loud"
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
text: "Stay at the edge for 2 minutes with hands behind back"
|
||||
text: "Watch cuckold porn sitting while in panties, no touching for 8 minutes"
|
||||
},
|
||||
{
|
||||
id: 104,
|
||||
text: "Write 'I will practice self-control' 25 times"
|
||||
text: "Stand and recite 'I am inferior to real men' 50 times while watching BBC porn"
|
||||
},
|
||||
{
|
||||
id: 105,
|
||||
text: "Hold a wall sit for 3 minutes while edging"
|
||||
text: "Wear feminine clothing and pose submissively sitting for 10 minutes"
|
||||
},
|
||||
{
|
||||
id: 106,
|
||||
text: "Edge for 30 seconds then cold shower for 1 minute"
|
||||
text: "Practice sucking motions on a dildo while standing and watching gay porn for 5 minutes"
|
||||
},
|
||||
{
|
||||
id: 107,
|
||||
text: "Maintain edge position for 5 minutes without touching"
|
||||
text: "Kneel and worship alpha male images for 8 minutes, no touching"
|
||||
},
|
||||
{
|
||||
id: 108,
|
||||
text: "Complete 100 push-ups in sets while staying aroused"
|
||||
text: "Sit in feminine attire and practice feminine poses while denied release for 12 minutes"
|
||||
},
|
||||
{
|
||||
id: 109,
|
||||
text: "Kneel in stress position for 10 minutes, no relief"
|
||||
text: "Listen to sissy hypno audio while standing in feminine attire for 10 minutes"
|
||||
},
|
||||
{
|
||||
id: 110,
|
||||
text: "Edge to near climax then stop for 5 minutes - repeat 3 times"
|
||||
text: "Watch hotwife videos sitting while locked in chastity, repeat 'I am a cuckold' 50 times"
|
||||
},
|
||||
{
|
||||
id: 111,
|
||||
text: "Perform degrading poses while edging for 3 minutes"
|
||||
text: "Practice walking in heels while standing and watching feminization content for 8 minutes"
|
||||
},
|
||||
{
|
||||
id: 112,
|
||||
text: "Worship your training material for 10 minutes, no touching"
|
||||
text: "Sit and study alpha male photos while reciting why you're inferior for 15 minutes"
|
||||
}
|
||||
],
|
||||
|
||||
|
|
@ -156,124 +156,124 @@ const gameData = {
|
|||
// Motivational messages
|
||||
{
|
||||
id: 1,
|
||||
text: "Your self-control is improving! Keep training!",
|
||||
text: "Good goon! Keep stroking and watching!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Every edge builds your endurance!",
|
||||
text: "Every edge makes you more submissive!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Discipline over impulse!",
|
||||
text: "Porn is your new reality!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "You're learning to control yourself! Stay focused!",
|
||||
text: "You're becoming such a good little gooner!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Small steps lead to better self-control!",
|
||||
text: "Real men fuck while you watch!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
// Encouraging messages
|
||||
{
|
||||
id: 6,
|
||||
text: "Your dedication to training is impressive!",
|
||||
text: "Your dedication to gooning is impressive!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
text: "Look how much your endurance has improved!",
|
||||
text: "Look how much porn you can handle now!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
text: "You're building incredible self-discipline!",
|
||||
text: "You're building incredible porn tolerance!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
text: "Master your desires - you have the strength!",
|
||||
text: "Accept your place as a beta watcher!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
text: "Your future self will appreciate this training!",
|
||||
text: "Your future is endless gooning sessions!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
// Achievement messages
|
||||
{
|
||||
id: 11,
|
||||
text: "Excellent control on that task!",
|
||||
text: "Excellent edging performance!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
text: "You're on fire! Maintain that discipline!",
|
||||
text: "You're such a dedicated gooner!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
text: "Another victory for self-control!",
|
||||
text: "Another victory for porn addiction!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
text: "Perfect execution!",
|
||||
text: "Perfect submission!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
text: "You're mastering your training goals!",
|
||||
text: "You're mastering the art of denial!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
// Persistence messages
|
||||
{
|
||||
id: 16,
|
||||
text: "Don't give in now - show your discipline!",
|
||||
text: "Don't stop gooning now - embrace it!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
text: "Every challenge strengthens your self-control!",
|
||||
text: "Every stroke makes you weaker!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
text: "Push through - mastery awaits!",
|
||||
text: "Push deeper into submission!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
text: "You're stronger than any urge!",
|
||||
text: "You belong on your knees watching!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
text: "True control is forged in moments like this!",
|
||||
text: "True submission is forged through endless edging!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ class PopupImageManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get random image for periodic popups (from both folders)
|
||||
* Get random image for periodic popups (from linked directories and individual files)
|
||||
*/
|
||||
getRandomPeriodicImage() {
|
||||
const taskImages = gameData?.discoveredTaskImages || [];
|
||||
const consequenceImages = gameData?.discoveredConsequenceImages || [];
|
||||
const allImages = [...taskImages, ...consequenceImages];
|
||||
// Get images from the same sources as the main library
|
||||
const allImages = this.getLinkedImages();
|
||||
|
||||
if (allImages.length === 0) {
|
||||
console.log('⚠️ No linked images available for popups');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ class PopupImageManager {
|
|||
const { history } = this.periodicSystem;
|
||||
|
||||
const availableImages = allImages.filter(img => {
|
||||
const imagePath = typeof img === 'string' ? img : (img.cachedPath || img.originalName);
|
||||
const imagePath = typeof img === 'string' ? img : (img.path || img.cachedPath || img.originalName);
|
||||
return !disabledImages.includes(imagePath) && !history.includes(imagePath);
|
||||
});
|
||||
|
||||
|
|
@ -147,6 +147,83 @@ class PopupImageManager {
|
|||
return selectedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images from linked directories and individual files (same as main library)
|
||||
*/
|
||||
getLinkedImages() {
|
||||
const allImages = [];
|
||||
|
||||
try {
|
||||
// Get linked directories
|
||||
let linkedDirs;
|
||||
try {
|
||||
linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||||
if (!Array.isArray(linkedDirs)) {
|
||||
linkedDirs = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedImageDirectories:', e);
|
||||
linkedDirs = [];
|
||||
}
|
||||
|
||||
// Get individual linked images
|
||||
let linkedIndividualImages;
|
||||
try {
|
||||
linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
|
||||
if (!Array.isArray(linkedIndividualImages)) {
|
||||
linkedIndividualImages = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedIndividualImages:', e);
|
||||
linkedIndividualImages = [];
|
||||
}
|
||||
|
||||
console.log(`📸 PopupImageManager found ${linkedDirs.length} linked directories and ${linkedIndividualImages.length} individual images`);
|
||||
|
||||
// Load images from linked directories using Electron API
|
||||
if (window.electronAPI && linkedDirs.length > 0) {
|
||||
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
|
||||
|
||||
for (const dir of linkedDirs) {
|
||||
try {
|
||||
if (window.electronAPI.readDirectory) {
|
||||
const files = window.electronAPI.readDirectory(dir.path);
|
||||
const imageFiles = files.filter(file => imageExtensions.test(file.name));
|
||||
|
||||
imageFiles.forEach(file => {
|
||||
allImages.push({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
category: 'directory',
|
||||
directory: dir.name
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading images from directory ${dir.path}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add individual linked images
|
||||
linkedIndividualImages.forEach(image => {
|
||||
allImages.push({
|
||||
name: image.name || 'Unknown Image',
|
||||
path: image.path,
|
||||
category: 'individual',
|
||||
directory: 'Individual Images'
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`📸 PopupImageManager loaded ${allImages.length} total images for popups`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('📸 Error loading linked images:', error);
|
||||
}
|
||||
|
||||
return allImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display periodic popup
|
||||
*/
|
||||
|
|
@ -859,12 +936,19 @@ class PopupImageManager {
|
|||
}
|
||||
|
||||
getImageSrc(imageData) {
|
||||
// Handle both old path format and new cached metadata format
|
||||
// Handle both old path format and new linked image format
|
||||
if (typeof imageData === 'string') {
|
||||
return imageData;
|
||||
} else if (imageData.dataUrl) {
|
||||
return imageData.dataUrl;
|
||||
} else if (imageData.path) {
|
||||
// New linked image format - convert path for Electron if needed
|
||||
if (window.electronAPI && imageData.path.match(/^[A-Za-z]:\\/)) {
|
||||
return `file:///${imageData.path.replace(/\\/g, '/')}`;
|
||||
}
|
||||
return imageData.path;
|
||||
} else {
|
||||
// Fallback to old format
|
||||
return imageData.cachedPath || imageData.originalName || '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,23 @@ class VideoLibrary {
|
|||
|
||||
// Content container
|
||||
this.libraryContent = document.getElementById('library-content');
|
||||
|
||||
// Debug: Log which elements were found
|
||||
console.log(`📁 VideoLibrary elements initialized:`, {
|
||||
gridViewBtn: !!this.gridViewBtn,
|
||||
listViewBtn: !!this.listViewBtn,
|
||||
sortSelect: !!this.sortSelect,
|
||||
sortDirectionBtn: !!this.sortDirectionBtn,
|
||||
searchInput: !!this.searchInput,
|
||||
refreshBtn: !!this.refreshBtn,
|
||||
createPlaylistBtn: !!this.createPlaylistBtn,
|
||||
selectModeBtn: !!this.selectModeBtn,
|
||||
libraryContent: !!this.libraryContent
|
||||
});
|
||||
|
||||
if (!this.libraryContent) {
|
||||
console.error(`📁 ❌ library-content element not found!`);
|
||||
}
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
|
|
@ -61,106 +78,135 @@ class VideoLibrary {
|
|||
|
||||
async loadVideoLibrary() {
|
||||
try {
|
||||
console.log('📁 Loading video library...');
|
||||
|
||||
// Check if desktop file manager is available
|
||||
if (!window.desktopFileManager) {
|
||||
console.error('Desktop file manager not available');
|
||||
this.displayEmptyLibrary('Desktop file manager not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for desktop file manager to be fully initialized
|
||||
// Check if video directories have been set up
|
||||
let retries = 0;
|
||||
const maxRetries = 20; // Wait up to 2 seconds
|
||||
|
||||
while (retries < maxRetries) {
|
||||
if (window.desktopFileManager.videoDirectories &&
|
||||
window.desktopFileManager.videoDirectories.background) {
|
||||
console.log('✅ Desktop file manager video directories are ready');
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`⏳ Waiting for video directories to initialize... (${retries + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (retries >= maxRetries) {
|
||||
console.warn('⚠️ Video directories not initialized after waiting, falling back to stored videos');
|
||||
this.loadStoredVideos();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get video files from unified video library
|
||||
let allVideos = [];
|
||||
console.log('📁 Loading video library for Porn Cinema...');
|
||||
|
||||
// Use the same video loading logic as the main library interface
|
||||
let linkedDirs;
|
||||
try {
|
||||
if (window.desktopFileManager) {
|
||||
allVideos = window.desktopFileManager.getAllVideos();
|
||||
console.log(`📁 Got ${allVideos.length} videos from desktop file manager`);
|
||||
|
||||
// If no videos from desktop file manager, try fallback methods
|
||||
if (allVideos.length === 0) {
|
||||
console.log('📁 No videos from desktop file manager, trying unified storage...');
|
||||
|
||||
// Try unified storage first - read fresh from localStorage to get preserved data
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
console.log(`📁 Found ${allVideos.length} videos in unified storage`);
|
||||
|
||||
// If still no videos, try legacy video storage
|
||||
if (allVideos.length === 0) {
|
||||
console.log('📁 No videos in unified storage, trying legacy videoFiles...');
|
||||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||||
allVideos = Object.values(storedVideos).flat();
|
||||
console.log(`📁 Found ${allVideos.length} videos in legacy storage`);
|
||||
} else {
|
||||
console.log('📁 Using videos from unified storage, skipping legacy check');
|
||||
}
|
||||
} else {
|
||||
console.log('📁 Using videos from desktop file manager');
|
||||
}
|
||||
} else {
|
||||
// Fallback to unified storage
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
|
||||
if (!Array.isArray(linkedDirs)) {
|
||||
linkedDirs = [];
|
||||
}
|
||||
|
||||
console.log(`📁 Found ${allVideos.length} videos total (unified library)`);
|
||||
} catch (error) {
|
||||
console.error('Error loading video library:', error);
|
||||
|
||||
// Fallback to localStorage
|
||||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||||
console.log('📁 Falling back to stored videos');
|
||||
|
||||
allVideos = Object.values(storedVideos).flat();
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
|
||||
linkedDirs = [];
|
||||
}
|
||||
|
||||
if (!allVideos || allVideos.length === 0) {
|
||||
console.log('No videos found in any storage method');
|
||||
this.displayEmptyLibrary('No videos found. Upload some videos first!');
|
||||
let linkedIndividualVideos;
|
||||
try {
|
||||
linkedIndividualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
|
||||
if (!Array.isArray(linkedIndividualVideos)) {
|
||||
linkedIndividualVideos = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
|
||||
linkedIndividualVideos = [];
|
||||
}
|
||||
|
||||
console.log(`📁 Found ${linkedDirs.length} linked directories and ${linkedIndividualVideos.length} individual videos in localStorage`);
|
||||
|
||||
const allVideos = [];
|
||||
|
||||
// Load videos from linked directories using Electron API
|
||||
if (window.electronAPI && linkedDirs.length > 0) {
|
||||
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
|
||||
|
||||
for (const dir of linkedDirs) {
|
||||
try {
|
||||
console.log(`🎬 Scanning directory: ${dir.path}`);
|
||||
|
||||
// Use video-specific directory reading for better results
|
||||
let files = [];
|
||||
if (window.electronAPI.readVideoDirectory) {
|
||||
console.log(`🎬 Using readVideoDirectory for ${dir.path}`);
|
||||
files = await window.electronAPI.readVideoDirectory(dir.path);
|
||||
} else if (window.electronAPI.readVideoDirectoryRecursive) {
|
||||
console.log(`🎬 Using readVideoDirectoryRecursive for ${dir.path}`);
|
||||
files = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
|
||||
} else if (window.electronAPI.readDirectory) {
|
||||
console.log(`🎬 Using generic readDirectory for ${dir.path}`);
|
||||
const allFiles = await window.electronAPI.readDirectory(dir.path);
|
||||
files = allFiles.filter(file => videoExtensions.test(file.name));
|
||||
}
|
||||
|
||||
console.log(`🎬 Found ${files ? files.length : 0} videos in ${dir.path}`);
|
||||
|
||||
if (files && files.length > 0) {
|
||||
files.forEach(file => {
|
||||
allVideos.push({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
size: file.size || 0,
|
||||
duration: file.duration || 0,
|
||||
format: this.getFormatFromPath(file.path),
|
||||
dateAdded: new Date().toISOString(),
|
||||
category: 'directory',
|
||||
directory: dir.path
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error loading videos from directory ${dir.path}:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add individual linked video files using the same key as main library
|
||||
linkedIndividualVideos.forEach(video => {
|
||||
allVideos.push({
|
||||
name: video.name || 'Unknown Video',
|
||||
path: video.path,
|
||||
size: video.size || 0,
|
||||
duration: video.duration || 0,
|
||||
format: this.getFormatFromPath(video.path),
|
||||
dateAdded: video.dateAdded || new Date().toISOString(),
|
||||
category: 'individual',
|
||||
directory: 'Individual Videos'
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`📁 Total videos loaded: ${allVideos.length}`);
|
||||
console.log(`📁 From directories: ${allVideos.filter(v => v.category === 'directory').length}`);
|
||||
console.log(`📁 Individual files: ${allVideos.filter(v => v.category === 'individual').length}`);
|
||||
|
||||
console.log(`📁 Final total videos loaded: ${allVideos.length}`);
|
||||
|
||||
if (allVideos.length === 0) {
|
||||
console.log('No videos found in current library system');
|
||||
this.displayEmptyLibrary('No videos found. Add video directories or files in the main library first!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Process video data
|
||||
// Process video data with enhanced metadata
|
||||
this.videos = allVideos.map(video => ({
|
||||
name: video.title || video.name || 'Unknown Video',
|
||||
path: video.path || video.filePath,
|
||||
name: video.name,
|
||||
path: video.path,
|
||||
size: video.size || 0,
|
||||
duration: video.duration || 0,
|
||||
thumbnail: video.thumbnail || null,
|
||||
resolution: video.resolution || 'Unknown',
|
||||
format: video.format || this.getFormatFromPath(video.path || video.filePath),
|
||||
format: video.format || this.getFormatFromPath(video.path),
|
||||
bitrate: video.bitrate || 0,
|
||||
dateAdded: video.dateAdded || new Date().toISOString(),
|
||||
category: video.category || 'unknown',
|
||||
directory: video.directory || 'Unknown',
|
||||
qualities: this.detectVideoQualities(video)
|
||||
}));
|
||||
|
||||
console.log(`📁 Loaded ${this.videos.length} videos`);
|
||||
console.log(`📁 Processed ${this.videos.length} videos for library display`);
|
||||
|
||||
// Debug: Show some sample video names and paths
|
||||
if (this.videos.length > 0) {
|
||||
console.log(`📁 Sample videos loaded:`);
|
||||
this.videos.slice(0, 5).forEach((video, index) => {
|
||||
console.log(` ${index + 1}. "${video.name}" (${video.category}) - ${video.path}`);
|
||||
});
|
||||
if (this.videos.length > 5) {
|
||||
console.log(` ... and ${this.videos.length - 5} more videos`);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply current filters and display
|
||||
this.applyFiltersAndSort();
|
||||
|
|
@ -239,6 +285,8 @@ class VideoLibrary {
|
|||
video.name.toLowerCase().includes(this.searchQuery)
|
||||
);
|
||||
|
||||
console.log(`📁 Filter applied: "${this.searchQuery}" -> ${this.filteredVideos.length}/${this.videos.length} videos`);
|
||||
|
||||
// Apply sorting
|
||||
this.filteredVideos.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
|
@ -272,11 +320,16 @@ class VideoLibrary {
|
|||
}
|
||||
|
||||
displayLibrary() {
|
||||
console.log(`📁 Displaying library: ${this.filteredVideos.length} videos in ${this.currentView} view`);
|
||||
|
||||
if (this.filteredVideos.length === 0) {
|
||||
console.log(`📁 No videos to display (total videos: ${this.videos.length}, search: "${this.searchQuery}")`);
|
||||
this.displayEmptyLibrary('No videos match your search');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📁 First few videos being displayed:`, this.filteredVideos.slice(0, 3).map(v => v.name));
|
||||
|
||||
const containerClass = this.currentView === 'grid' ? 'library-grid' : 'library-list';
|
||||
|
||||
this.libraryContent.innerHTML = `
|
||||
|
|
@ -285,6 +338,22 @@ class VideoLibrary {
|
|||
</div>
|
||||
`;
|
||||
|
||||
console.log(`📁 HTML generated for ${this.filteredVideos.length} videos`);
|
||||
|
||||
// Debug: Check if libraryContent element exists and verify HTML
|
||||
console.log(`📁 libraryContent element:`, this.libraryContent);
|
||||
console.log(`📁 libraryContent innerHTML length:`, this.libraryContent ? this.libraryContent.innerHTML.length : 'N/A');
|
||||
|
||||
if (this.libraryContent && this.filteredVideos.length > 0) {
|
||||
// Check if the HTML actually contains video cards
|
||||
const videoCards = this.libraryContent.querySelectorAll('.video-card, .video-list-item');
|
||||
console.log(`📁 Video cards found in DOM:`, videoCards.length);
|
||||
|
||||
if (videoCards.length === 0) {
|
||||
console.log(`📁 No video cards found! Checking generated HTML sample:`, this.libraryContent.innerHTML.substring(0, 500));
|
||||
}
|
||||
}
|
||||
|
||||
// Attach click events to video elements
|
||||
this.attachVideoEvents();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,17 +218,35 @@ class InteractiveTaskManager {
|
|||
async completeInteractiveTask() {
|
||||
console.log('🔍 CompleteInteractiveTask called - currentInteractiveTask:', this.currentInteractiveTask);
|
||||
|
||||
if (!this.currentInteractiveTask) {
|
||||
console.log('❌ No current interactive task found!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if game is still running - prevent completion after game has ended
|
||||
if (!this.game.gameState.isRunning) {
|
||||
console.log('Interactive task completion cancelled - game no longer running');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle scenario completion even if currentInteractiveTask is null
|
||||
if (!this.currentInteractiveTask) {
|
||||
console.log('⚠️ No current interactive task found - checking for scenario completion');
|
||||
|
||||
// Check if this is a scenario completion
|
||||
const currentTask = this.game.gameState.currentTask;
|
||||
if (currentTask && currentTask.interactiveType === 'scenario-adventure' &&
|
||||
currentTask.scenarioState && currentTask.scenarioState.completed) {
|
||||
console.log('✅ Found completed scenario - proceeding with completion');
|
||||
this.showFeedback('success', 'Scenario completed successfully! 🎉');
|
||||
setTimeout(() => {
|
||||
if (this.game.gameState.isRunning) {
|
||||
this.cleanupInteractiveTask();
|
||||
this.game.completeTask();
|
||||
}
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('❌ No current interactive task or completed scenario found!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔍 Attempting to complete interactive task:`, this.currentInteractiveTask);
|
||||
console.log(`🔍 Task interactiveType: ${this.currentInteractiveTask.interactiveType}`);
|
||||
console.log(`🔍 Task isInterruption: ${this.currentInteractiveTask.isInterruption}`);
|
||||
|
|
@ -1751,6 +1769,10 @@ class InteractiveTaskManager {
|
|||
async createScenarioTask(task, container) {
|
||||
const scenario = task.interactiveData || {};
|
||||
|
||||
// Set this task as the current interactive task
|
||||
this.currentInteractiveTask = task;
|
||||
console.log('🎭 Set scenario task as current interactive task:', task.id);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="scenario-task">
|
||||
<div class="scenario-header">
|
||||
|
|
|
|||
|
|
@ -1452,31 +1452,35 @@ body.cinema-mode {
|
|||
}
|
||||
|
||||
.library-content {
|
||||
min-height: 120px;
|
||||
min-height: 120px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
max-height: 400px !important;
|
||||
}
|
||||
|
||||
.library-loading {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* ===== LIBRARY GRID VIEW ===== */
|
||||
.library-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
|
||||
gap: 15px !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.video-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-radius: 10px !important;
|
||||
overflow: hidden !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.3s ease !important;
|
||||
border: 2px solid transparent !important;
|
||||
position: relative !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
height: auto !important;
|
||||
min-height: 200px !important;
|
||||
}
|
||||
|
||||
.video-card:hover {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue