diff --git a/js/main.js b/js/main.js index e2a5aa2..fb1c7fc 100644 --- a/js/main.js +++ b/js/main.js @@ -13,9 +13,9 @@ import './minigames/index.js'; // Import NPC systems import './systems/ink/ink-engine.js?v=1'; -import './systems/npc-events.js?v=1'; -import './systems/npc-manager.js?v=1'; -import './systems/npc-barks.js?v=1'; +import NPCEventDispatcher from './systems/npc-events.js?v=1'; +import NPCManager from './systems/npc-manager.js?v=1'; +import NPCBarkSystem from './systems/npc-barks.js?v=1'; // Global game variables window.game = null; @@ -74,6 +74,16 @@ function initializeGame() { // Bluetooth scanner and biometrics are now handled as minigames // Initialize NPC systems + console.log('🎭 Initializing NPC systems...'); + window.eventDispatcher = new NPCEventDispatcher(); + window.barkSystem = new NPCBarkSystem(); + window.npcManager = new NPCManager(window.eventDispatcher, window.barkSystem); + + // Start timed message system + window.npcManager.startTimedMessages(); + + console.log('✅ NPC systems initialized'); + if (window.npcBarkSystem) { window.npcBarkSystem.init(); } diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js index a9a57b9..bb8fd54 100644 --- a/js/minigames/container/container-minigame.js +++ b/js/minigames/container/container-minigame.js @@ -43,13 +43,14 @@ export class ContainerMinigame extends MinigameScene { `; } - // Add notebook button to minigame controls if postit note exists + // Add notebook button to minigame controls if postit note exists (before cancel button) if (this.controlsElement && this.containerItem.scenarioData.postitNote && this.containerItem.scenarioData.showPostit) { const notebookBtn = document.createElement('button'); notebookBtn.className = 'minigame-button'; notebookBtn.id = 'minigame-notebook-postit'; - notebookBtn.innerHTML = 'Notebook Add to Notebook'; - this.controlsElement.appendChild(notebookBtn); + notebookBtn.innerHTML = 'Notepad Add to Notepad'; + // Insert before the cancel button (first child in controls) + this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild); } // Create the container minigame UI @@ -248,7 +249,7 @@ export class ContainerMinigame extends MinigameScene { this.addEventListener(closeBtn, 'click', () => this.complete(false)); } - // Add to Notebook button + // Add to Notepad button const addToNotebookBtn = document.getElementById('minigame-notebook-postit'); if (addToNotebookBtn) { this.addEventListener(addToNotebookBtn, 'click', () => this.addPostitToNotebook()); @@ -424,10 +425,10 @@ export class ContainerMinigame extends MinigameScene { false ); - this.showMessage("Added postit note to notebook", 'success'); + this.showMessage("Added postit note to notepad", 'success'); } else { console.error('Notes minigame not available'); - this.showMessage('Notebook not available', 'error'); + this.showMessage('Notepad not available', 'error'); } } diff --git a/js/minigames/password/password-minigame.js b/js/minigames/password/password-minigame.js index 1108c4d..b5ada54 100644 --- a/js/minigames/password/password-minigame.js +++ b/js/minigames/password/password-minigame.js @@ -41,8 +41,9 @@ export class PasswordMinigame extends MinigameScene { const notebookBtn = document.createElement('button'); notebookBtn.className = 'minigame-button'; notebookBtn.id = 'minigame-notebook-postit'; - notebookBtn.innerHTML = 'Notebook Add to Notebook'; - this.controlsElement.appendChild(notebookBtn); + notebookBtn.innerHTML = 'Notepad Add to Notepad'; + // Insert before the cancel button (first child in controls) + this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild); } // Set up event listeners @@ -503,9 +504,9 @@ export class PasswordMinigame extends MinigameScene { false ); - this.showSuccess("Added postit note to notebook", false, 2000); + this.showSuccess("Added postit note to notepad", false, 2000); } else { - this.showFailure("Notebook not available", false, 2000); + this.showFailure("Notepad not available", false, 2000); } } diff --git a/js/minigames/phone-chat/phone-chat-minigame.js b/js/minigames/phone-chat/phone-chat-minigame.js index 74c5565..05bfbae 100644 --- a/js/minigames/phone-chat/phone-chat-minigame.js +++ b/js/minigames/phone-chat/phone-chat-minigame.js @@ -64,6 +64,11 @@ export class PhoneChatMinigame extends MinigameScene { * Initialize the minigame UI and components */ init() { + // Set cancelText to "Close" before calling parent init + if (!this.params.cancelText) { + this.params.cancelText = 'Close'; + } + // Call parent init to set up basic structure super.init(); @@ -80,10 +85,30 @@ export class PhoneChatMinigame extends MinigameScene { this.ui = new PhoneChatUI(this.gameContainer, safeParams, this.npcManager); this.ui.render(); + // Add notebook button to minigame controls (before close button) + if (this.controlsElement) { + const notebookBtn = document.createElement('button'); + notebookBtn.className = 'minigame-button'; + notebookBtn.id = 'minigame-notebook'; + notebookBtn.innerHTML = 'Notepad Add to Notepad'; + // Insert before the cancel/close button + const cancelBtn = this.controlsElement.querySelector('#minigame-cancel'); + if (cancelBtn) { + this.controlsElement.insertBefore(notebookBtn, cancelBtn); + } else { + this.controlsElement.appendChild(notebookBtn); + } + } + // Set up event listeners this.setupEventListeners(); console.log('✅ PhoneChatMinigame initialized'); + + // Call onInit callback if provided (used for returning from notes) + if (safeParams.onInit && typeof safeParams.onInit === 'function') { + safeParams.onInit(this); + } } /** @@ -104,6 +129,20 @@ export class PhoneChatMinigame extends MinigameScene { this.closeConversation(); }); + // Notepad button (context-aware: saves contact list or conversation) + const notebookBtn = document.getElementById('minigame-notebook'); + if (notebookBtn) { + this.addEventListener(notebookBtn, 'click', () => { + // Check which view is currently active + const currentView = this.ui.getCurrentView(); + if (currentView === 'conversation' && this.currentNPCId) { + this.saveConversationToNotepad(); + } else { + this.saveContactListToNotepad(); + } + }); + } + // Choice button clicks this.addEventListener(this.ui.elements.choicesContainer, 'click', (e) => { const choiceButton = e.target.closest('.choice-button'); @@ -478,6 +517,140 @@ export class PhoneChatMinigame extends MinigameScene { this.ui.showContactList(this.phoneId); } + /** + * Save contact list to notepad + */ + saveContactListToNotepad() { + console.log('📝 Saving contact list to notepad'); + + if (!this.npcManager || !window.startNotesMinigame) { + console.warn('Cannot save to notepad: missing dependencies'); + return; + } + + // Get all NPCs for this phone + const npcs = this.npcManager.getNPCsByPhone(this.phoneId); + + if (!npcs || npcs.length === 0) { + console.warn('No contacts to save'); + return; + } + + // Format contact list + let content = `CONTACTS\n`; + content += `${'='.repeat(30)}\n\n`; + + npcs.forEach(npc => { + const unreadCount = this.history ? this.history.getUnreadCount() : 0; + const statusText = unreadCount > 0 ? ` (${unreadCount} unread)` : ''; + content += `• ${npc.displayName || npc.id}${statusText}\n`; + }); + + content += `\n${'='.repeat(30)}\n`; + content += `Phone: ${this.params.title || 'Phone'}\n`; + content += `Date: ${new Date().toLocaleString()}`; + + // Store phone state for return + window.pendingPhoneReturn = { + phoneId: this.phoneId, + title: this.params.title, + params: this.params + }; + + // Create note item + const noteItem = { + scenarioData: { + type: 'note', + name: 'Contact List', + text: content, + observations: `Contact list from ${this.params.title || 'phone'}.` + } + }; + + // Start notes minigame + window.startNotesMinigame( + noteItem, + content, + `Contact list from ${this.params.title || 'phone'}.`, + null, + false, + true + ); + } + + /** + * Save current conversation to notepad + */ + saveConversationToNotepad() { + console.log('📝 Saving conversation to notepad'); + + if (!this.currentNPCId || !this.history || !window.startNotesMinigame) { + console.warn('Cannot save conversation: no active conversation or missing dependencies'); + return; + } + + const npc = this.npcManager.getNPC(this.currentNPCId); + if (!npc) { + console.warn('Cannot find NPC for conversation'); + return; + } + + // Get conversation history + const messages = this.history.loadHistory(); + + if (!messages || messages.length === 0) { + console.warn('No messages to save'); + return; + } + + // Format conversation + const npcName = npc.displayName || npc.id; + let content = `CONVERSATION WITH ${npcName.toUpperCase()}\n`; + content += `${'='.repeat(30)}\n\n`; + + messages.forEach(message => { + if (message.type === 'npc') { + content += `${npcName}: ${message.text}\n\n`; + } else if (message.type === 'player') { + content += `You: ${message.text}\n\n`; + } else if (message.type === 'choice') { + content += `> ${message.text}\n\n`; + } + }); + + content += `${'='.repeat(30)}\n`; + content += `Phone: ${this.params.title || 'Phone'}\n`; + content += `Date: ${new Date().toLocaleString()}`; + + // Store phone state for return + window.pendingPhoneReturn = { + phoneId: this.phoneId, + title: this.params.title, + params: this.params, + returnToNPC: this.currentNPCId // Remember which conversation to return to + }; + + // Create note item + const noteItem = { + scenarioData: { + type: 'note', + name: `Chat: ${npcName}`, + text: content, + observations: `Conversation history with ${npcName}.` + } + }; + + // Start notes minigame + window.startNotesMinigame( + noteItem, + content, + `Conversation history with ${npcName}.`, + null, + false, + true + ); + } + /** * Complete the minigame * @param {boolean} success - Whether minigame was successful @@ -527,10 +700,22 @@ export function returnToPhoneAfterNotes() { // Restart the phone-chat minigame with the saved state if (window.MinigameFramework) { - window.MinigameFramework.startMinigame('phone-chat', null, phoneState.params || { + const params = phoneState.params || { phoneId: phoneState.phoneId || 'default_phone', title: phoneState.title || 'Phone' - }); + }; + + // If we need to return to a specific conversation, add callback + if (phoneState.returnToNPC) { + params.onInit = (minigame) => { + // Wait a bit for UI to render, then open the conversation + setTimeout(() => { + minigame.openConversation(phoneState.returnToNPC); + }, 100); + }; + } + + window.MinigameFramework.startMinigame('phone-chat', null, params); } } } diff --git a/js/minigames/text-file/text-file-minigame.js b/js/minigames/text-file/text-file-minigame.js index 01a4c61..2bbd69e 100644 --- a/js/minigames/text-file/text-file-minigame.js +++ b/js/minigames/text-file/text-file-minigame.js @@ -32,7 +32,7 @@ export class TextFileMinigame extends MinigameScene { const notebookBtn = document.createElement('button'); notebookBtn.className = 'minigame-button'; notebookBtn.id = 'minigame-notebook'; - notebookBtn.innerHTML = 'Notebook Add to Notebook'; + notebookBtn.innerHTML = 'Notepad Add to Notepad'; this.controlsElement.appendChild(notebookBtn); // Change cancel button text to "Close" @@ -284,7 +284,7 @@ export class TextFileMinigame extends MinigameScene { addToNotebook() { // Check if there's content to add if (!this.textFileData.fileContent || this.textFileData.fileContent.trim() === '') { - this.showFailure("No content to add to notebook", false, 2000); + this.showFailure("No content to add to notepad", false, 2000); return; } @@ -331,7 +331,7 @@ export class TextFileMinigame extends MinigameScene { this.showSuccess("Added file content to notebook", false, 2000); } else { - this.showFailure("Notebook not available", false, 2000); + this.showFailure("Notepad not available", false, 2000); } } diff --git a/js/systems/interactions.js b/js/systems/interactions.js index a4b1f28..81eee33 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -527,16 +527,28 @@ export function handleObjectInteraction(sprite) { // For phone type objects, use phone-chat with runtime conversion if (data.type === 'phone' && (data.text || data.voice)) { - console.log('Phone object detected:', { type: data.type, text: data.text, voice: data.voice }); + console.log('Phone object detected:', { type: data.type, text: data.text, voice: data.voice, npcIds: data.npcIds }); // Check if phone-chat system is available if (window.MinigameFramework && window.npcManager) { - // Import the converter + const phoneId = data.phoneId || 'default_phone'; + + // Check if phone has already been converted (has npcIds) + if (data.npcIds && data.npcIds.length > 0) { + console.log('Phone already converted, opening phone-chat directly'); + // Phone already has NPCs, open directly + window.MinigameFramework.startMinigame('phone-chat', null, { + phoneId: phoneId, + title: data.name || 'Phone' + }); + return; + } + + // Need to convert - import the converter import('../utils/phone-message-converter.js').then(module => { const PhoneMessageConverter = module.default; // Convert simple message to virtual NPC - const phoneId = data.phoneId || 'default_phone'; const npcId = PhoneMessageConverter.convertAndRegister(data, window.npcManager); if (npcId) {