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 = '
Add to Notebook';
- this.controlsElement.appendChild(notebookBtn);
+ notebookBtn.innerHTML = '
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 = '
Add to Notebook';
- this.controlsElement.appendChild(notebookBtn);
+ notebookBtn.innerHTML = '
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 = '
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 = '
Add to Notebook';
+ notebookBtn.innerHTML = '
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) {