mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat(person-chat): Implement click-through mode for dialogue advancement and enhance UI with continue button
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
<div class="popup-overlay"></div>
|
||||
|
||||
<!-- Main Game JavaScript Module -->
|
||||
<script type="module" src="js/main.js?v=40"></script>
|
||||
<script type="module" src="js/main.js?v=41"></script>
|
||||
|
||||
<!-- Mobile touch handling -->
|
||||
<script>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { initializeRooms, calculateWorldBounds, calculateRoomPositions, createRo
|
||||
import { createPlayer, updatePlayerMovement, movePlayerToPoint, player } from './player.js?v=7';
|
||||
import { initializePathfinder } from './pathfinding.js?v=7';
|
||||
import { initializeInventory, processInitialInventoryItems } from '../systems/inventory.js?v=8';
|
||||
import { checkObjectInteractions, setGameInstance } from '../systems/interactions.js?v=25';
|
||||
import { checkObjectInteractions, setGameInstance } from '../systems/interactions.js?v=26';
|
||||
import { introduceScenario } from '../utils/helpers.js?v=19';
|
||||
import '../minigames/index.js?v=2';
|
||||
import SoundManager from '../systems/sound-manager.js?v=1';
|
||||
@@ -602,29 +602,15 @@ export function create() {
|
||||
if (npcAtPosition) {
|
||||
// Try to interact with the NPC
|
||||
if (window.tryInteractWithNPC) {
|
||||
const player = window.player;
|
||||
if (player) {
|
||||
window.preventPlayerMovement = true;
|
||||
const previousX = player.x;
|
||||
const previousY = player.y;
|
||||
|
||||
// Try the interaction
|
||||
window.tryInteractWithNPC(npcAtPosition);
|
||||
|
||||
// If the interaction didn't move the player (NPC was out of range),
|
||||
// treat this as a movement request to that NPC instead
|
||||
if (player.x === previousX && player.y === previousY) {
|
||||
// Reset the flag and move toward the NPC
|
||||
window.preventPlayerMovement = false;
|
||||
movePlayerToPoint(npcAtPosition.x, npcAtPosition.y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Interaction was successful
|
||||
setTimeout(() => {
|
||||
window.preventPlayerMovement = false;
|
||||
}, 100);
|
||||
const interactionSuccessful = window.tryInteractWithNPC(npcAtPosition);
|
||||
|
||||
if (interactionSuccessful) {
|
||||
// Interaction was successful (NPC was in range)
|
||||
return; // Exit early after handling the interaction
|
||||
} else {
|
||||
// NPC was out of range - treat click as a movement request
|
||||
movePlayerToPoint(npcAtPosition.x, npcAtPosition.y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GAME_CONFIG } from './utils/constants.js?v=8';
|
||||
import { preload, create, update } from './core/game.js?v=39';
|
||||
import { preload, create, update } from './core/game.js?v=40';
|
||||
import { initializeNotifications } from './systems/notifications.js?v=7';
|
||||
// Bluetooth scanner is now handled as a minigame
|
||||
// Biometrics is now handled as a minigame
|
||||
|
||||
@@ -10,7 +10,7 @@ export { BluetoothScannerMinigame, startBluetoothScannerMinigame } from './bluet
|
||||
export { BiometricsMinigame, startBiometricsMinigame } from './biometrics/biometrics-minigame.js';
|
||||
export { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes } from './container/container-minigame.js';
|
||||
export { PhoneChatMinigame, returnToPhoneAfterNotes } from './phone-chat/phone-chat-minigame.js';
|
||||
export { PersonChatMinigame } from './person-chat/person-chat-minigame.js?v=7';
|
||||
export { PersonChatMinigame } from './person-chat/person-chat-minigame.js?v=8';
|
||||
export { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
|
||||
export { PasswordMinigame } from './password/password-minigame.js';
|
||||
export { TextFileMinigame, returnToTextFileAfterNotes } from './text-file/text-file-minigame.js';
|
||||
@@ -58,7 +58,7 @@ import { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes
|
||||
import { PhoneChatMinigame, returnToPhoneAfterNotes } from './phone-chat/phone-chat-minigame.js';
|
||||
|
||||
// Import the person chat minigame (In-person NPC conversations)
|
||||
import { PersonChatMinigame } from './person-chat/person-chat-minigame.js?v=2';
|
||||
import { PersonChatMinigame } from './person-chat/person-chat-minigame.js?v=8';
|
||||
|
||||
// Import the PIN minigame
|
||||
import { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
|
||||
|
||||
@@ -72,6 +72,8 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.isConversationActive = false;
|
||||
this.currentSpeaker = null; // Track current speaker ID ('player' or NPC id)
|
||||
this.lastResult = null; // Store last continue() result for choice handling
|
||||
this.isClickThroughMode = false; // If true, player must click to advance between dialogue lines
|
||||
this.pendingContinueCallback = null; // Callback waiting for player click in click-through mode
|
||||
|
||||
console.log(`🎭 PersonChatMinigame created for NPC: ${this.npcId}`);
|
||||
}
|
||||
@@ -167,6 +169,58 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.handleChoice(choiceIndex);
|
||||
}
|
||||
});
|
||||
|
||||
// Continue button to toggle click-through mode
|
||||
if (this.ui.elements.continueButton) {
|
||||
this.addEventListener(this.ui.elements.continueButton, 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleClickThroughMode();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between automatic timing and click-through mode
|
||||
*/
|
||||
toggleClickThroughMode() {
|
||||
this.isClickThroughMode = !this.isClickThroughMode;
|
||||
|
||||
if (this.isClickThroughMode) {
|
||||
console.log('📋 Switched to CLICK-THROUGH mode');
|
||||
this.ui.elements.continueButton.textContent = '■ Auto';
|
||||
// Cancel any pending automatic advances
|
||||
if (this.pendingContinueCallback) {
|
||||
clearTimeout(this.pendingContinueCallback);
|
||||
this.pendingContinueCallback = null;
|
||||
}
|
||||
} else {
|
||||
console.log('📋 Switched to AUTOMATIC mode');
|
||||
this.ui.elements.continueButton.textContent = '▼ Continue';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the next dialogue advancement, respecting click-through mode
|
||||
* @param {Function} callback - Function to call to advance dialogue
|
||||
* @param {number} delay - Delay in milliseconds (ignored in click-through mode)
|
||||
*/
|
||||
scheduleDialogueAdvance(callback, delay = 2000) {
|
||||
if (this.isClickThroughMode) {
|
||||
// In click-through mode, show continue button and wait for user click
|
||||
this.pendingContinueCallback = callback;
|
||||
this.ui.showContinueButton(() => {
|
||||
if (this.pendingContinueCallback) {
|
||||
const pending = this.pendingContinueCallback;
|
||||
this.pendingContinueCallback = null;
|
||||
pending();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// In automatic mode, use the delay
|
||||
this.ui.hideContinueButton();
|
||||
this.pendingContinueCallback = setTimeout(callback, delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,13 +331,12 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.ui.showDialogue(result.text, speaker);
|
||||
|
||||
if (result.canContinue) {
|
||||
// Can continue - auto-advance after delay
|
||||
console.log('⏳ Auto-continuing in 2 seconds...');
|
||||
setTimeout(() => this.showCurrentDialogue(), 2000);
|
||||
// Can continue - schedule next advance
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
|
||||
} else {
|
||||
// Can't continue but have text - story will end
|
||||
console.log('✓ Waiting for story to end...');
|
||||
setTimeout(() => this.endConversation(), 1000);
|
||||
this.scheduleDialogueAdvance(() => this.endConversation(), 1000);
|
||||
}
|
||||
} else {
|
||||
// No text and no choices - story has ended
|
||||
@@ -369,7 +422,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
}
|
||||
|
||||
// Then display the result (dialogue blocks) after a small delay
|
||||
setTimeout(() => {
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
// Process accumulated dialogue by splitting into individual speaker blocks
|
||||
this.displayAccumulatedDialogue(result);
|
||||
}, 1500);
|
||||
@@ -479,11 +532,11 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
if (blockIndex >= blocks.length) {
|
||||
// All blocks displayed, check if story has ended
|
||||
if (originalResult.hasEnded) {
|
||||
setTimeout(() => this.endConversation(), 1000);
|
||||
this.scheduleDialogueAdvance(() => this.endConversation(), 1000);
|
||||
} else {
|
||||
// Try to continue for more dialogue
|
||||
console.log('⏸️ Blocks finished, checking for more dialogue...');
|
||||
setTimeout(() => {
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
const nextLine = this.conversation.continue();
|
||||
|
||||
// Store for choice handling
|
||||
@@ -510,7 +563,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.ui.showDialogue(block.text, block.speaker);
|
||||
|
||||
// Display next block after delay
|
||||
setTimeout(() => {
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
this.displayDialogueBlocksSequentially(blocks, originalResult, blockIndex + 1);
|
||||
}, 2000);
|
||||
}
|
||||
@@ -556,12 +609,12 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
} else if (result.canContinue) {
|
||||
// No choices but can continue - auto-advance after delay
|
||||
console.log('⏳ Auto-continuing in 2 seconds...');
|
||||
setTimeout(() => this.showCurrentDialogue(), 2000);
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
|
||||
} else {
|
||||
// No choices and can't continue - check if there's more content
|
||||
// Try to continue anyway (for linear scripted conversations)
|
||||
console.log('⏸️ No more choices, attempting to continue for next line...');
|
||||
setTimeout(() => {
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
const nextLine = this.conversation.continue();
|
||||
if (nextLine.text && nextLine.text.trim()) {
|
||||
// There's more dialogue to show
|
||||
@@ -572,7 +625,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
} else {
|
||||
// No text but story isn't ended - wait a bit and end
|
||||
console.log('✓ No more dialogue - ending conversation');
|
||||
setTimeout(() => this.endConversation(), 1000);
|
||||
this.scheduleDialogueAdvance(() => this.endConversation(), 1000);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
@@ -604,7 +657,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.ui.reset();
|
||||
|
||||
// Close minigame after a delay
|
||||
setTimeout(() => {
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
this.complete(true);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,15 @@ export default class PersonChatUI {
|
||||
|
||||
dialogueBox.appendChild(dialogueText);
|
||||
|
||||
// Continue button - placed next to dialogue
|
||||
const continueButton = document.createElement('button');
|
||||
continueButton.className = 'person-chat-continue-button';
|
||||
continueButton.textContent = '▼ Continue';
|
||||
continueButton.id = 'continue-button';
|
||||
continueButton.style.display = 'none'; // Hidden by default, shown when there's more dialogue
|
||||
|
||||
dialogueBox.appendChild(continueButton);
|
||||
|
||||
// Choices container (in caption area, below dialogue)
|
||||
const choicesContainer = document.createElement('div');
|
||||
choicesContainer.className = 'person-chat-choices-container';
|
||||
@@ -142,6 +151,7 @@ export default class PersonChatUI {
|
||||
this.elements.speakerName = speakerName;
|
||||
this.elements.dialogueBox = dialogueBox;
|
||||
this.elements.dialogueText = dialogueText;
|
||||
this.elements.continueButton = continueButton;
|
||||
this.elements.choicesContainer = choicesContainer;
|
||||
|
||||
this.elements.root.appendChild(mainContent);
|
||||
@@ -304,6 +314,37 @@ export default class PersonChatUI {
|
||||
/**
|
||||
* Clear dialogue and reset UI
|
||||
*/
|
||||
/**
|
||||
* Show the continue button to indicate player can advance
|
||||
* @param {Function} onContinueClick - Callback when continue button is clicked
|
||||
*/
|
||||
showContinueButton(onContinueClick) {
|
||||
if (!this.elements.continueButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.elements.continueButton.style.display = 'inline-block';
|
||||
|
||||
// Remove any existing listeners
|
||||
const newButton = this.elements.continueButton.cloneNode(true);
|
||||
this.elements.continueButton.parentNode.replaceChild(newButton, this.elements.continueButton);
|
||||
this.elements.continueButton = newButton;
|
||||
|
||||
// Add click listener
|
||||
if (onContinueClick) {
|
||||
this.elements.continueButton.addEventListener('click', onContinueClick);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the continue button
|
||||
*/
|
||||
hideContinueButton() {
|
||||
if (this.elements.continueButton) {
|
||||
this.elements.continueButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.currentSpeaker = null;
|
||||
this.hasContinued = false;
|
||||
|
||||
@@ -962,12 +962,12 @@ export function tryInteractWithNearest() {
|
||||
// Handle NPC interaction by sprite reference
|
||||
export function tryInteractWithNPC(npcSprite) {
|
||||
if (!npcSprite || !npcSprite._isNPC) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const player = window.player;
|
||||
if (!player) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if NPC is within interaction range of the player
|
||||
@@ -977,8 +977,11 @@ export function tryInteractWithNPC(npcSprite) {
|
||||
// Only interact if within range
|
||||
if (distance <= INTERACTION_RANGE) {
|
||||
handleObjectInteraction(npcSprite);
|
||||
return true; // Interaction successful
|
||||
}
|
||||
// If out of range, the click handler will treat it as a movement request instead
|
||||
|
||||
// Out of range - caller should handle movement
|
||||
return false;
|
||||
}
|
||||
|
||||
// Export for global access
|
||||
|
||||
Reference in New Issue
Block a user