From b8c8c79f867e6bc39a48e876528ea1ca65955e8d Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Fri, 10 Oct 2025 02:39:28 +0100 Subject: [PATCH] Add Notes Minigame: Implement interactive note display and inventory integration Introduce a new Notes Minigame that allows players to view and interact with notes in a notepad-style interface. The minigame supports adding notes to the inventory, displaying observations, and includes navigation features such as previous/next buttons and search functionality. Update relevant files for integration with the existing game systems, including interaction and inventory management. Add test HTML for verifying minigame features and include necessary assets for the notepad background. --- NOTES_MINIGAME_USAGE.md | 113 ++++ assets/mini-games/notepad.png | Bin 0 -> 8088 bytes assets/rooms/room_closet2.json | 2 +- assets/rooms/room_closet2.tmj | 5 +- js/core/game.js | 5 + js/minigames/index.js | 11 +- js/minigames/notes/notes-minigame.js | 800 +++++++++++++++++++++++++++ js/systems/debug.js | 52 +- js/systems/interactions.js | 11 +- js/systems/inventory.js | 3 +- js/utils/helpers.js | 12 +- test-notes-features.html | 204 +++++++ 12 files changed, 1186 insertions(+), 32 deletions(-) create mode 100644 NOTES_MINIGAME_USAGE.md create mode 100644 assets/mini-games/notepad.png create mode 100644 js/minigames/notes/notes-minigame.js create mode 100644 test-notes-features.html diff --git a/NOTES_MINIGAME_USAGE.md b/NOTES_MINIGAME_USAGE.md new file mode 100644 index 0000000..21cc0c2 --- /dev/null +++ b/NOTES_MINIGAME_USAGE.md @@ -0,0 +1,113 @@ +# Notes Minigame Usage + +The Notes Minigame provides an interactive way to display note content with a notepad background and allows players to add notes to their inventory. + +## Features + +- Displays note content on a notepad background (`/assets/mini-games/notepad.png`) +- Shows observation text below the main content (if provided) +- Provides a "Add to Inventory" button with backpack icon (`/assets/mini-games/backpack.png`) +- Integrates with the existing inventory system +- Uses the minigame framework for consistent UI + +## Usage in Scenarios + +To use the notes minigame in your scenario files, add the following properties to note objects: + +```json +{ + "type": "notes", + "name": "Example Note", + "takeable": true, + "readable": true, + "text": "This is the main content of the note.\n\nIt can contain multiple lines and will be displayed on the notepad background.", + "observations": "The handwriting appears rushed and there are coffee stains on the paper." +} +``` + +### Required Properties + +- `type`: Must be "notes" +- `text`: The main content to display on the notepad +- `readable`: Must be true to trigger the minigame + +### Optional Properties + +- `observations`: Additional observation text displayed below the main content +- `takeable`: Whether the note can be added to inventory (default: true) + +## Programmatic Usage + +You can also start the notes minigame programmatically: + +```javascript +// Basic usage +window.startNotesMinigame(item, text, observations); + +// Example +const testItem = { + scene: null, + scenarioData: { + type: 'notes', + name: 'Test Note', + text: 'This is a test note content.', + observations: 'The note appears to be written in haste.' + } +}; + +window.startNotesMinigame(testItem, testItem.scenarioData.text, testItem.scenarioData.observations); + +// Show mission brief +window.showMissionBrief(); +``` + +## Features + +### Navigation +- **Previous/Next Buttons**: Navigate through collected notes +- **Search Functionality**: Search through note titles and content +- **Note Counter**: Shows current position (e.g., "2 / 5") + +### Mission Brief Integration +- The mission brief is automatically displayed via the notes minigame when starting a new scenario +- Uses the same notepad interface for consistency +- Automatically added to the notes system as an important note + +### Search +- Real-time search through note titles and content +- Case-insensitive matching +- Filters the note list to show only matching results +- Clear search to show all notes again + +### Player Notes +- **Edit Observations**: Click the edit button (✏️) to add or modify observations +- **Handwritten Style**: Player notes use the same handwritten font as original observations +- **Persistent Storage**: Player notes are saved to the notes system and persist between sessions +- **Visual Feedback**: Dashed border and background indicate editable areas + +### Visual Effects +- **Celotape Effect**: Realistic celotape strip overlapping the top of the text box +- **Binder Holes**: Small circular holes on the left side of the text box +- **Handwritten Fonts**: Uses Google Fonts 'Kalam' for authentic handwritten appearance + +## Automatic Collection + +- **Auto-Collection**: Notes are automatically added to the notes system when the minigame starts +- **Scene Removal**: Notes are automatically removed from the scene after being collected +- **No Manual Action**: Players don't need to click "Add to Inventory" - it happens automatically +- **Seamless Experience**: Notes are collected and removed from the world in one smooth interaction + +## Integration + +The notes minigame is automatically integrated into the interaction system. When a player interacts with a note object that has `text`, the minigame will be triggered instead of the default text display. The note is automatically collected and removed from the scene. + +## Testing + +A test file is available at `test-notes-minigame.html` to verify the implementation works correctly. + +## Files Modified + +- `js/minigames/notes/notes-minigame.js` - Main minigame implementation +- `js/minigames/index.js` - Registration and global export +- `js/systems/interactions.js` - Integration with interaction system +- `js/systems/inventory.js` - Made addToInventory function globally available diff --git a/assets/mini-games/notepad.png b/assets/mini-games/notepad.png new file mode 100644 index 0000000000000000000000000000000000000000..52b44f5e876f17ad9433d7abdcf55d76c6f4b31e GIT binary patch literal 8088 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 Notes +

Note automatically added to your collection

+ `; + + // Configure game container with notepad background - scaled to fill most of the screen + this.gameContainer.style.cssText = ` + width: 100%; + min-height: 100%; + max-width: 800px; + max-height: none; + background-image: url('assets/mini-games/notepad.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + position: relative; + margin: 20px auto; + padding: 10px 140px 50px 150px; + box-sizing: border-box; + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + `; + + // Create content area + const contentArea = document.createElement('div'); + contentArea.style.cssText = ` + width: 100%; + min-height: 100%; + font-family: 'Courier New', monospace; + font-size: 18px; + line-height: 1.5; + color: #333; + background: transparent; + padding: 0; + margin: 0; + `; + + // Create text box container to look like it's stuck in a binder + const textBox = document.createElement('div'); + textBox.style.cssText = ` + margin: 20px 50px 60px 80px; + padding: 40px; + background: #fefefe; + border: 2px solid #ddd; + border-radius: 3px; + box-shadow: + 0 2px 4px rgba(0,0,0,0.1), + inset 0 1px 0 rgba(255,255,255,0.8); + position: relative; + min-height: fit-content; + `; + + // Add celotape effect + const celotape = document.createElement('div'); + celotape.className = 'notes-minigame-celotape'; + celotape.style.cssText = ` + position: absolute; + top: -8px; + left: 80px; + right: 80px; + height: 16px; + background: linear-gradient(90deg, + rgba(255,255,255,0.9) 0%, + rgba(255,255,255,0.7) 20%, + rgba(255,255,255,0.9) 40%, + rgba(255,255,255,0.7) 60%, + rgba(255,255,255,0.9) 80%, + rgba(255,255,255,0.7) 100%); + border: 1px solid rgba(200,200,200,0.8); + border-radius: 2px; + box-shadow: + 0 1px 2px rgba(0,0,0,0.1), + inset 0 1px 0 rgba(255,255,255,0.9); + z-index: 1; + `; + textBox.appendChild(celotape); + + // Add binder holes effect + const binderHoles = document.createElement('div'); + binderHoles.style.cssText = ` + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 80px; + display: flex; + flex-direction: column; + justify-content: space-between; + `; + + + + // Add note title/name above the text box + const noteTitle = document.createElement('div'); + noteTitle.className = 'notes-minigame-title'; + noteTitle.style.cssText = ` + margin: 0 50px 20px 80px; + font-family: 'Kalam', 'Comic Sans MS', cursive; + font-size: 20px; + font-weight: bold; + color: #2c3e50; + text-decoration: underline; + text-decoration-color: #3498db; + text-underline-offset: 3px; + text-align: center; + `; + noteTitle.textContent = this.item?.scenarioData?.name || 'Note'; + contentArea.appendChild(noteTitle); + + // Add note content + const noteText = document.createElement('div'); + noteText.className = 'notes-minigame-text'; + noteText.style.cssText = ` + margin-left: 30px; + white-space: pre-wrap; + word-wrap: break-word; + color: #333; + `; + noteText.textContent = this.noteContent; + textBox.appendChild(noteText); + + contentArea.appendChild(textBox); + + // Add observation text if available - handwritten directly on the page + if (this.observationText) { + const observationContainer = document.createElement('div'); + observationContainer.className = 'notes-minigame-observation-container'; + observationContainer.style.cssText = ` + margin: 20px 50px 60px 80px; + position: relative; + `; + + const observationDiv = document.createElement('div'); + observationDiv.className = 'notes-minigame-observation'; + observationDiv.style.cssText = ` + font-family: 'Kalam', 'Comic Sans MS', cursive; + font-style: italic; + color: #666; + font-size: 18px; + line-height: 1.4; + text-align: left; + min-height: 30px; + padding: 10px; + border: 1px dashed #ccc; + border-radius: 3px; + background: rgba(255, 255, 255, 0.3); + `; + observationDiv.innerHTML = this.observationText; + + // Add edit button + const editBtn = document.createElement('button'); + editBtn.className = 'notes-minigame-edit-btn'; + editBtn.style.cssText = ` + position: absolute; + top: -8px; + right: -8px; + background: #3498db; + color: white; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + cursor: pointer; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0,0,0,0.3); + transition: background-color 0.3s ease; + `; + editBtn.innerHTML = '✏️'; + editBtn.title = 'Edit observations'; + editBtn.addEventListener('click', () => this.editObservations(observationDiv)); + + observationContainer.appendChild(observationDiv); + observationContainer.appendChild(editBtn); + contentArea.appendChild(observationContainer); + } else { + // Add empty observation area with edit button + const observationContainer = document.createElement('div'); + observationContainer.className = 'notes-minigame-observation-container'; + observationContainer.style.cssText = ` + margin: 20px 50px 60px 80px; + position: relative; + `; + + const observationDiv = document.createElement('div'); + observationDiv.className = 'notes-minigame-observation'; + observationDiv.style.cssText = ` + font-family: 'Kalam', 'Comic Sans MS', cursive; + font-style: italic; + color: #999; + font-size: 18px; + line-height: 1.4; + text-align: left; + min-height: 30px; + padding: 10px; + border: 1px dashed #ccc; + border-radius: 3px; + background: rgba(255, 255, 255, 0.3); + `; + observationDiv.innerHTML = 'Click edit to add your observations...'; + + // Add edit button + const editBtn = document.createElement('button'); + editBtn.className = 'notes-minigame-edit-btn'; + editBtn.style.cssText = ` + position: absolute; + top: -8px; + right: -8px; + background: #3498db; + color: white; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + cursor: pointer; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0,0,0,0.3); + transition: background-color 0.3s ease; + `; + editBtn.innerHTML = '✏️'; + editBtn.title = 'Add observations'; + editBtn.addEventListener('click', () => this.editObservations(observationDiv)); + + observationContainer.appendChild(observationDiv); + observationContainer.appendChild(editBtn); + contentArea.appendChild(observationContainer); + } + + this.gameContainer.appendChild(contentArea); + + // Create navigation buttons container + const navContainer = document.createElement('div'); + navContainer.style.cssText = ` + position: absolute; + bottom: 80px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 10; + `; + + // Add search input if there are multiple notes + if (this.collectedNotes.length > 1) { + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = 'Search notes...'; + searchInput.className = 'notes-minigame-search'; + searchInput.style.cssText = ` + padding: 8px 12px; + border: 1px solid #555; + border-radius: 5px; + background: rgba(0,0,0,0.7); + color: white; + font-size: 14px; + width: 200px; + margin-right: 10px; + `; + searchInput.addEventListener('input', (e) => this.searchNotes(e.target.value)); + navContainer.appendChild(searchInput); + + const prevBtn = document.createElement('button'); + prevBtn.className = 'minigame-button notes-nav-button'; + prevBtn.style.cssText = ` + background: #95a5a6; + color: white; + border: none; + padding: 8px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + font-weight: bold; + transition: background-color 0.3s ease; + `; + prevBtn.textContent = '← Previous'; + prevBtn.addEventListener('click', () => this.navigateToNote(-1)); + navContainer.appendChild(prevBtn); + + const nextBtn = document.createElement('button'); + nextBtn.className = 'minigame-button notes-nav-button'; + nextBtn.style.cssText = ` + background: #95a5a6; + color: white; + border: none; + padding: 8px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + font-weight: bold; + transition: background-color 0.3s ease; + `; + nextBtn.textContent = 'Next →'; + nextBtn.addEventListener('click', () => this.navigateToNote(1)); + navContainer.appendChild(nextBtn); + + // Add note counter + const noteCounter = document.createElement('div'); + noteCounter.className = 'notes-minigame-counter'; + noteCounter.style.cssText = ` + color: white; + font-size: 14px; + display: flex; + align-items: center; + padding: 8px 15px; + background: rgba(0,0,0,0.5); + border-radius: 5px; + `; + noteCounter.textContent = `${this.currentNoteIndex + 1} / ${this.collectedNotes.length}`; + navContainer.appendChild(noteCounter); + + this.container.appendChild(navContainer); + } + + // Create action buttons container + const buttonsContainer = document.createElement('div'); + buttonsContainer.style.cssText = ` + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 10; + `; + + + // Close button + const closeBtn = document.createElement('button'); + closeBtn.className = 'minigame-button notes-close-button'; + closeBtn.style.cssText = ` + background: #95a5a6; + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + font-weight: bold; + transition: background-color 0.3s ease; + `; + closeBtn.textContent = 'Close'; + + closeBtn.addEventListener('mouseenter', () => { + closeBtn.style.backgroundColor = '#7f8c8d'; + }); + closeBtn.addEventListener('mouseleave', () => { + closeBtn.style.backgroundColor = '#95a5a6'; + }); + + closeBtn.addEventListener('click', () => { + this.complete(false); + }); + + buttonsContainer.appendChild(closeBtn); + + this.container.appendChild(buttonsContainer); + } + + + removeNoteFromScene() { + // Remove the note from the scene if it has a sprite + if (this.item && this.item.sprite) { + console.log('Removing note from scene:', this.item.sprite); + + // Hide the sprite + if (this.item.sprite.setVisible) { + this.item.sprite.setVisible(false); + } + + // Remove from scene if it has a destroy method + if (this.item.sprite.destroy) { + this.item.sprite.destroy(); + } + + // Also try to remove from the scene's object list if available + if (this.item.scene && this.item.scene.objects) { + const objectIndex = this.item.scene.objects.findIndex(obj => obj.sprite === this.item.sprite); + if (objectIndex !== -1) { + this.item.scene.objects.splice(objectIndex, 1); + console.log('Removed note from scene objects list'); + } + } + + // Update the scene's interactive objects if available + if (this.item.scene && this.item.scene.interactiveObjects) { + const interactiveIndex = this.item.scene.interactiveObjects.findIndex(obj => obj.sprite === this.item.sprite); + if (interactiveIndex !== -1) { + this.item.scene.interactiveObjects.splice(interactiveIndex, 1); + console.log('Removed note from scene interactive objects list'); + } + } + } + } + + getCollectedNotes() { + // Get all notes from the notes system that are marked as important or have been collected + if (!window.gameState || !window.gameState.notes) { + return []; + } + + // Filter for important notes or notes that look like they were collected from objects + return window.gameState.notes.filter(note => + note.important || + note.title.includes('Log') || + note.title.includes('Note') || + note.title.includes('Security') || + note.title.includes('Report') + ); + } + + navigateToNote(direction) { + if (this.collectedNotes.length <= 1) return; + + this.currentNoteIndex += direction; + + // Wrap around + if (this.currentNoteIndex < 0) { + this.currentNoteIndex = this.collectedNotes.length - 1; + } else if (this.currentNoteIndex >= this.collectedNotes.length) { + this.currentNoteIndex = 0; + } + + // Update the displayed note + this.updateDisplayedNote(); + + // Update the counter + const noteCounter = this.container.querySelector('.notes-minigame-counter'); + if (noteCounter) { + noteCounter.textContent = `${this.currentNoteIndex + 1} / ${this.collectedNotes.length}`; + } + } + + updateDisplayedNote() { + const currentNote = this.collectedNotes[this.currentNoteIndex]; + if (!currentNote) return; + + // Parse the note text to extract observations + const noteParts = this.parseNoteText(currentNote.text); + this.noteContent = noteParts.mainText; + this.observationText = noteParts.observationText; + + // Update the displayed content + const noteTitle = this.container.querySelector('.notes-minigame-title'); + const noteText = this.container.querySelector('.notes-minigame-text'); + const observationDiv = this.container.querySelector('.notes-minigame-observation'); + + if (noteTitle) { + noteTitle.textContent = currentNote.title; + } + + if (noteText) { + noteText.textContent = this.noteContent; + } + + // Update observation container + const observationContainer = this.container.querySelector('.notes-minigame-observation-container'); + if (observationContainer) { + const observationDiv = observationContainer.querySelector('.notes-minigame-observation'); + const editBtn = observationContainer.querySelector('.notes-minigame-edit-btn'); + + if (this.observationText) { + observationDiv.innerHTML = this.observationText; + observationDiv.style.color = '#666'; + editBtn.title = 'Edit observations'; + } else { + observationDiv.innerHTML = 'Click edit to add your observations...'; + observationDiv.style.color = '#999'; + editBtn.title = 'Add observations'; + } + } + } + + parseNoteText(text) { + // Parse note text to separate main content from observations + const observationMatch = text.match(/\n\nObservation:\s*(.+)$/s); + if (observationMatch) { + return { + mainText: text.replace(/\n\nObservation:\s*.+$/s, '').trim(), + observationText: observationMatch[1].trim() + }; + } + return { + mainText: text, + observationText: '' + }; + } + + searchNotes(searchTerm) { + if (!searchTerm || searchTerm.trim() === '') { + // Reset to show all notes + this.collectedNotes = this.getCollectedNotes(); + this.currentNoteIndex = 0; + this.updateDisplayedNote(); + this.updateCounter(); + return; + } + + const searchLower = searchTerm.toLowerCase(); + const matchingNotes = this.collectedNotes.filter(note => + note.title.toLowerCase().includes(searchLower) || + note.text.toLowerCase().includes(searchLower) + ); + + if (matchingNotes.length > 0) { + this.collectedNotes = matchingNotes; + this.currentNoteIndex = 0; + this.updateDisplayedNote(); + this.updateCounter(); + } + } + + updateCounter() { + const noteCounter = this.container.querySelector('.notes-minigame-counter'); + if (noteCounter) { + noteCounter.textContent = `${this.currentNoteIndex + 1} / ${this.collectedNotes.length}`; + } + } + + editObservations(observationDiv) { + const currentText = observationDiv.textContent.trim(); + const isPlaceholder = currentText === 'Click edit to add your observations...'; + const originalText = isPlaceholder ? '' : currentText; + + // Create textarea for editing + const textarea = document.createElement('textarea'); + textarea.value = originalText; + textarea.style.cssText = ` + width: 100%; + min-height: 60px; + font-family: 'Kalam', 'Comic Sans MS', cursive; + font-size: 18px; + line-height: 1.4; + color: #666; + border: 2px solid #3498db; + border-radius: 3px; + padding: 10px; + background: rgba(255, 255, 255, 0.9); + resize: vertical; + outline: none; + `; + textarea.placeholder = 'Add your observations here...'; + + // Create button container + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = ` + margin-top: 10px; + display: flex; + gap: 10px; + justify-content: flex-end; + `; + + // Save button + const saveBtn = document.createElement('button'); + saveBtn.textContent = 'Save'; + saveBtn.style.cssText = ` + background: #2ecc71; + color: white; + border: none; + padding: 8px 16px; + border-radius: 3px; + cursor: pointer; + font-size: 14px; + `; + saveBtn.addEventListener('click', () => { + const newText = textarea.value.trim(); + observationDiv.innerHTML = newText || 'Click edit to add your observations...'; + observationDiv.style.color = newText ? '#666' : '#999'; + + // Update the stored observation text + this.observationText = newText; + + // Save to the current note in the notes system + this.saveObservationToNote(newText); + + // Remove editing elements + textarea.remove(); + buttonContainer.remove(); + }); + + // Cancel button + const cancelBtn = document.createElement('button'); + cancelBtn.textContent = 'Cancel'; + cancelBtn.style.cssText = ` + background: #95a5a6; + color: white; + border: none; + padding: 8px 16px; + border-radius: 3px; + cursor: pointer; + font-size: 14px; + `; + cancelBtn.addEventListener('click', () => { + // Restore original text + if (originalText) { + observationDiv.innerHTML = originalText; + observationDiv.style.color = '#666'; + } else { + observationDiv.innerHTML = 'Click edit to add your observations...'; + observationDiv.style.color = '#999'; + } + + // Remove editing elements + textarea.remove(); + buttonContainer.remove(); + }); + + buttonContainer.appendChild(saveBtn); + buttonContainer.appendChild(cancelBtn); + + // Replace content with editing interface + observationDiv.innerHTML = ''; + observationDiv.appendChild(textarea); + observationDiv.appendChild(buttonContainer); + + // Focus the textarea + textarea.focus(); + textarea.select(); + } + + saveObservationToNote(newObservationText) { + // Update the current note in the notes system + const currentNote = this.collectedNotes[this.currentNoteIndex]; + if (currentNote) { + // Parse the existing text to separate main content from observations + const noteParts = this.parseNoteText(currentNote.text); + + // Update the observation text + noteParts.observationText = newObservationText; + + // Reconstruct the full text + let fullText = noteParts.mainText; + if (newObservationText) { + fullText += `\n\nObservation: ${newObservationText}`; + } + + // Update the note in the notes system + currentNote.text = fullText; + + // Also update in the global notes system if it exists + if (window.gameState && window.gameState.notes) { + const globalNote = window.gameState.notes.find(note => + note.title === currentNote.title && note.timestamp === currentNote.timestamp + ); + if (globalNote) { + globalNote.text = fullText; + } + } + + console.log('Observation saved to note:', currentNote.title); + } + } + + start() { + super.start(); + console.log("Notes minigame started"); + + // Automatically add current note to notes system when starting + if (this.autoAddToNotes && window.addNote) { + const noteTitle = this.item?.scenarioData?.name || 'Note'; + const noteText = this.noteContent + (this.observationText ? `\n\nObservation: ${this.observationText}` : ''); + const isImportant = this.item?.scenarioData?.important || false; + + const addedNote = window.addNote(noteTitle, noteText, isImportant); + if (addedNote) { + console.log('Note automatically added to notes system on start:', addedNote); + // Refresh collected notes + this.collectedNotes = this.getCollectedNotes(); + + // Automatically remove the note from the scene + this.removeNoteFromScene(); + } + } + } + + complete(success) { + // Call parent complete with result + super.complete(success, this.gameResult); + } + + cleanup() { + super.cleanup(); + } +} + +// Export the minigame for the framework to register +// The registration is handled in the main minigames/index.js file + +// Function to show mission brief via notes minigame +export function showMissionBrief() { + if (!window.gameScenario || !window.gameScenario.scenario_brief) { + console.warn('No mission brief available'); + return; + } + + const missionBriefItem = { + scene: null, + scenarioData: { + type: 'notes', + name: 'Mission Brief', + text: window.gameScenario.scenario_brief, + important: true + } + }; + + startNotesMinigame(missionBriefItem, window.gameScenario.scenario_brief, ''); +} + +// Function to start the notes minigame +export function startNotesMinigame(item, noteContent, observationText) { + console.log('Starting notes minigame with:', { item, noteContent, observationText }); + + // Make sure the minigame is registered + if (window.MinigameFramework && !window.MinigameFramework.registeredScenes['notes']) { + window.MinigameFramework.registerScene('notes', NotesMinigame); + console.log('Notes minigame registered on demand'); + } + + // Initialize the framework if not already done + if (!window.MinigameFramework.mainGameScene && item && item.scene) { + window.MinigameFramework.init(item.scene); + } + + // Start the notes minigame with proper parameters + const params = { + title: item?.scenarioData?.name || 'Reading Notes', + item: item, + noteContent: noteContent, + observationText: observationText, + onComplete: (success, result) => { + if (success && result && result.addedToInventory) { + console.log('NOTES SUCCESS - Added to inventory', result); + + // Show notification + if (window.showNotification) { + window.showNotification('Note added to inventory', 'success'); + } else if (window.gameAlert) { + window.gameAlert('Note added to inventory', 'success', 'Item Collected', 3000); + } + } else { + console.log('NOTES COMPLETED - Not added to inventory'); + } + } + }; + + console.log('Starting minigame with params:', params); + window.MinigameFramework.startMinigame('notes', null, params); +} diff --git a/js/systems/debug.js b/js/systems/debug.js index 58f20c1..cf0b8bf 100644 --- a/js/systems/debug.js +++ b/js/systems/debug.js @@ -4,7 +4,7 @@ // Debug system variables let debugMode = false; let debugLevel = 1; // 1 = basic, 2 = detailed, 3 = verbose -let visualDebugMode = false; +let visualDebugMode = false; // Visual debug (collision boxes, movement vectors) - off by default // Initialize the debug system export function initializeDebugSystem() { @@ -13,18 +13,10 @@ export function initializeDebugSystem() { // Toggle debug mode with backtick if (event.key === '`') { if (event.shiftKey) { - // Toggle visual debug mode with Shift+backtick - visualDebugMode = !visualDebugMode; - console.log(`%c[DEBUG] === VISUAL DEBUG MODE ${visualDebugMode ? 'ENABLED' : 'DISABLED'} ===`, - `color: ${visualDebugMode ? '#00AA00' : '#DD0000'}; font-weight: bold;`); - - // Update physics debug display if game exists - if (window.game && window.game.scene && window.game.scene.scenes && window.game.scene.scenes[0]) { - const scene = window.game.scene.scenes[0]; - if (scene.physics && scene.physics.world) { - scene.physics.world.drawDebug = debugMode && visualDebugMode; - } - } + // Toggle console debug mode with Shift+backtick + debugMode = !debugMode; + console.log(`%c[DEBUG] === CONSOLE DEBUG MODE ${debugMode ? 'ENABLED' : 'DISABLED'} ===`, + `color: ${debugMode ? '#00AA00' : '#DD0000'}; font-weight: bold;`); } else if (event.ctrlKey) { // Cycle through debug levels with Ctrl+backtick if (debugMode) { @@ -33,18 +25,13 @@ export function initializeDebugSystem() { `color: #0077FF; font-weight: bold;`); } } else { - // Regular debug mode toggle - debugMode = !debugMode; - console.log(`%c[DEBUG] === DEBUG MODE ${debugMode ? 'ENABLED' : 'DISABLED'} ===`, - `color: ${debugMode ? '#00AA00' : '#DD0000'}; font-weight: bold;`); + // Regular backtick toggles visual debug mode (collision boxes, movement vectors) + visualDebugMode = !visualDebugMode; + console.log(`%c[DEBUG] === VISUAL DEBUG MODE ${visualDebugMode ? 'ENABLED' : 'DISABLED'} ===`, + `color: ${visualDebugMode ? '#00AA00' : '#DD0000'}; font-weight: bold;`); // Update physics debug display if game exists - if (window.game && window.game.scene && window.game.scene.scenes && window.game.scene.scenes[0]) { - const scene = window.game.scene.scenes[0]; - if (scene.physics && scene.physics.world) { - scene.physics.world.drawDebug = debugMode && visualDebugMode; - } - } + updatePhysicsDebugDisplay(); } } }); @@ -52,6 +39,17 @@ export function initializeDebugSystem() { console.log('Debug system initialized'); } +// Function to update physics debug display +function updatePhysicsDebugDisplay() { + if (window.game && window.game.scene && window.game.scene.scenes && window.game.scene.scenes[0]) { + const scene = window.game.scene.scenes[0]; + if (scene.physics && scene.physics.world) { + // Visual debug (collision boxes, movement vectors) is controlled by visualDebugMode only + scene.physics.world.drawDebug = visualDebugMode; + } + } +} + // Debug logging function that only logs when debug mode is active export function debugLog(message, data = null, level = 1) { if (!debugMode || debugLevel < level) return; @@ -101,5 +99,11 @@ export function debugLog(message, data = null, level = 1) { } } +// Function to initialize physics debug display (called when game starts) +export function initializePhysicsDebugDisplay() { + updatePhysicsDebugDisplay(); +} + // Export for global access -window.debugLog = debugLog; \ No newline at end of file +window.debugLog = debugLog; +window.initializePhysicsDebugDisplay = initializePhysicsDebugDisplay; \ No newline at end of file diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 8f7e438..95f9e4e 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -232,7 +232,16 @@ export function handleObjectInteraction(sprite) { if (data.readable && data.text) { message += `Text: ${data.text}\n`; - // Add readable text as a note + // For notes type objects, use the notes minigame + if (data.type === 'notes' && data.text) { + // Start the notes minigame + if (window.startNotesMinigame) { + window.startNotesMinigame(sprite, data.text, data.observations); + return; // Exit early since minigame handles the interaction + } + } + + // Add readable text as a note (fallback for other readable objects) if (data.text.trim().length > 0) { const addedNote = window.addNote(data.name, data.text, data.important || false); diff --git a/js/systems/inventory.js b/js/systems/inventory.js index aa74942..618b263 100644 --- a/js/systems/inventory.js +++ b/js/systems/inventory.js @@ -193,4 +193,5 @@ function addToInventory(sprite) { // Export for global access window.initializeInventory = initializeInventory; -window.processInitialInventoryItems = processInitialInventoryItems; \ No newline at end of file +window.processInitialInventoryItems = processInitialInventoryItems; +window.addToInventory = addToInventory; \ No newline at end of file diff --git a/js/utils/helpers.js b/js/utils/helpers.js index 3feaafa..be2fab6 100644 --- a/js/utils/helpers.js +++ b/js/utils/helpers.js @@ -12,8 +12,16 @@ export function introduceScenario() { // Add scenario brief as an important note addNote("Mission Brief", gameScenario.scenario_brief, true); - // Show notification - gameAlert(gameScenario.scenario_brief, 'info', 'Mission Brief', 0); + // Show mission brief via notes minigame if available, otherwise fallback to alert + if (window.showMissionBrief) { + // Delay slightly to ensure the game is fully loaded + setTimeout(() => { + window.showMissionBrief(); + }, 500); + } else { + // Fallback to old alert system + gameAlert(gameScenario.scenario_brief, 'info', 'Mission Brief', 0); + } } // Import crypto workstation functions diff --git a/test-notes-features.html b/test-notes-features.html new file mode 100644 index 0000000..8d38e14 --- /dev/null +++ b/test-notes-features.html @@ -0,0 +1,204 @@ + + + + + + Notes Minigame Features Test + + + +

Notes Minigame Features Test

+ +
+

Test Mission Brief

+

Test the mission brief functionality:

+ +
+ +
+

Test Notes with Observations

+

Test notes with observations, search functionality, and editing:

+ + +

Features to test: Edit observations, celotape effect, search, navigation

+
+ +
+

Current Notes

+
+
+ + + + +