feat: Update minigame controls to use "Notepad" terminology and enhance NPC system initialization

This commit is contained in:
Z. Cliffe Schreuders
2025-10-30 13:40:34 +00:00
parent 01010e7e20
commit b8bed005aa
6 changed files with 230 additions and 21 deletions

View File

@@ -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();
}

View File

@@ -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 = '<img src="assets/icons/notes-sm.png" alt="Notebook" class="icon-small"> Add to Notebook';
this.controlsElement.appendChild(notebookBtn);
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> 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');
}
}

View File

@@ -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 = '<img src="assets/icons/notes-sm.png" alt="Notebook" class="icon-small"> Add to Notebook';
this.controlsElement.appendChild(notebookBtn);
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> 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);
}
}

View File

@@ -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 = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> 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);
}
}
}

View File

@@ -32,7 +32,7 @@ export class TextFileMinigame extends MinigameScene {
const notebookBtn = document.createElement('button');
notebookBtn.className = 'minigame-button';
notebookBtn.id = 'minigame-notebook';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notebook" class="icon-small"> Add to Notebook';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> 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);
}
}

View File

@@ -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) {