diff --git a/css/person-chat-minigame.css b/css/person-chat-minigame.css
index 39037d1..bc4e75a 100644
--- a/css/person-chat-minigame.css
+++ b/css/person-chat-minigame.css
@@ -77,7 +77,7 @@
display: block;
width: 100%;
height: 100%;
- object-fit: cover;
+ object-fit: contain; /* Changed from cover to contain to maintain aspect ratio and match main game scaling */
border: none;
background-color: #000;
}
diff --git a/js/minigames/person-chat/person-chat-minigame-old.js b/js/minigames/person-chat/person-chat-minigame-old.js
deleted file mode 100644
index 82ca755..0000000
--- a/js/minigames/person-chat/person-chat-minigame-old.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/**
- * PersonChatMinigame - Main Person-Chat Minigame Controller
- *
- * Extends MinigameScene to provide cinematic in-person conversation interface.
- * Orchestrates:
- * - Portrait rendering (NPC and player)
- * - Dialogue display
- * - Choice selection
- * - Ink story progression
- *
- * @module person-chat-minigame
- */
-
-import { MinigameScene } from '../framework/base-minigame.js';
-import PersonChatUI from './person-chat-ui.js';
-import PhoneChatConversation from '../phone-chat/phone-chat-conversation.js'; // Reuse phone-chat conversation logic
-import InkEngine from '../../systems/ink/ink-engine.js?v=1';
-
-export class PersonChatMinigame extends MinigameScene {
- /**
- * Create a PersonChatMinigame instance
- * @param {HTMLElement} container - Container element
- * @param {Object} params - Configuration parameters
- */
- constructor(container, params) {
- super(container, params);
-
- // Get required globals
- if (!window.game || !window.npcManager) {
- throw new Error('PersonChatMinigame requires window.game and window.npcManager');
- }
-
- this.game = window.game;
- this.npcManager = window.npcManager;
- this.player = window.player;
-
- // Create InkEngine instance for this conversation
- this.inkEngine = new InkEngine(`person-chat-${params.npcId}`);
-
- // Parameters
- this.npcId = params.npcId;
- this.title = params.title || 'Conversation';
-
- // Verify NPC exists
- const npc = this.npcManager.getNPC(this.npcId);
- if (!npc) {
- throw new Error(`NPC not found: ${this.npcId}`);
- }
- this.npc = npc;
-
- // Modules
- this.ui = null;
- this.conversation = null;
-
- // State
- this.isConversationActive = false;
-
- console.log(`🎭 PersonChatMinigame created for NPC: ${this.npcId}`);
- }
-
- /**
- * Initialize the minigame UI and components
- */
- init() {
- // Set up basic minigame structure (header, container, etc.)
- if (!this.params.cancelText) {
- this.params.cancelText = 'End Conversation';
- }
- super.init();
-
- // Customize header
- this.headerElement.innerHTML = `
-
🎭 ${this.title}
- Speaking with ${this.npc.displayName}
- `;
-
- // Create UI
- this.ui = new PersonChatUI(this.gameContainer, {
- game: this.game,
- npc: this.npc,
- playerSprite: this.player
- }, this.npcManager);
-
- this.ui.render();
-
- // Set up event listeners
- this.setupEventListeners();
-
- console.log('✅ PersonChatMinigame initialized');
- }
-
- /**
- * Set up event listeners for UI interactions
- */
- setupEventListeners() {
- // Choice button clicks
- this.addEventListener(this.ui.elements.choicesContainer, 'click', (e) => {
- const choiceButton = e.target.closest('.person-chat-choice-button');
- if (choiceButton) {
- const choiceIndex = parseInt(choiceButton.dataset.index);
- this.handleChoice(choiceIndex);
- }
- });
- }
-
- /**
- * Start the minigame
- * Initializes conversation flow
- */
- start() {
- super.start();
-
- console.log('🎭 PersonChatMinigame started');
-
- // Start conversation with Ink
- this.startConversation();
- }
-
- /**
- * Start conversation with NPC
- * Loads Ink story and shows initial dialogue
- */
- async startConversation() {
- try {
- // Create conversation manager using PhoneChatConversation (reused logic)
- this.conversation = new PhoneChatConversation(this.npcId, this.npcManager, this.inkEngine);
-
- // Load story from NPC's storyPath or storyJSON
- const storySource = this.npc.storyJSON || this.npc.storyPath;
- const loaded = await this.conversation.loadStory(storySource);
-
- if (!loaded) {
- console.error('❌ Failed to load conversation story');
- this.showError('Failed to load conversation');
- return;
- }
-
- // Navigate to start knot
- const startKnot = this.npc.currentKnot || 'start';
- this.conversation.goToKnot(startKnot);
-
- this.isConversationActive = true;
-
- // Show initial dialogue
- this.showCurrentDialogue();
-
- console.log('✅ Conversation started');
- } catch (error) {
- console.error('❌ Error starting conversation:', error);
- this.showError('An error occurred during conversation');
- }
- }
-
- /**
- * Display current dialogue and choices
- */
- showCurrentDialogue() {
- if (!this.conversation) return;
-
- try {
- // Continue the story to get next content
- const result = this.conversation.continue();
-
- // Check if story has ended
- if (result.hasEnded) {
- this.endConversation();
- return;
- }
-
- // Display dialogue text
- if (result.text && result.text.trim()) {
- this.ui.showDialogue(result.text, this.npcId);
- }
-
- // Display choices
- if (result.choices && result.choices.length > 0) {
- this.ui.showChoices(result.choices);
- } else if (!result.canContinue) {
- // No more content and no choices - conversation ended
- this.endConversation();
- }
- } catch (error) {
- console.error('❌ Error showing dialogue:', error);
- this.showError('An error occurred during conversation');
- }
- }
-
- /**
- * Handle choice selection
- * @param {number} choiceIndex - Index of selected choice
- */
- handleChoice(choiceIndex) {
- if (!this.conversation) return;
-
- try {
- console.log(`📝 Choice selected: ${choiceIndex}`);
-
- // Make choice in conversation (this also continues the story)
- const result = this.conversation.makeChoice(choiceIndex);
-
- // Clear old choices
- this.ui.hideChoices();
-
- // Show new dialogue after a small delay for visual feedback
- setTimeout(() => {
- // Display the result
- if (result.hasEnded) {
- this.endConversation();
- } else {
- // Display new text and choices
- if (result.text && result.text.trim()) {
- this.ui.showDialogue(result.text, this.npcId);
- }
- if (result.choices && result.choices.length > 0) {
- this.ui.showChoices(result.choices);
- }
- }
- }, 200);
- } catch (error) {
- console.error('❌ Error handling choice:', error);
- this.showError('Failed to process choice');
- }
- }
-
- /**
- * Show error message
- * @param {string} message - Error message
- */
- showError(message) {
- if (this.messageContainer) {
- this.messageContainer.innerHTML = `${message}
`;
- }
- console.error(`⚠️ Error: ${message}`);
- }
-
- /**
- * End conversation and close minigame
- */
- endConversation() {
- try {
- console.log('🎭 Ending conversation');
-
- // Cleanup conversation
- this.conversation = null;
- this.isConversationActive = false;
-
- // Close minigame
- this.complete(true);
-
- } catch (error) {
- console.error('❌ Error ending conversation:', error);
- this.complete(false);
- }
- }
-
- /**
- * Cleanup and destroy minigame
- */
- destroy() {
- try {
- // Stop conversation
- if (this.conversation) {
- this.conversation.end();
- this.conversation = null;
- }
-
- // Destroy UI
- if (this.ui) {
- this.ui.destroy();
- this.ui = null;
- }
-
- console.log('✅ PersonChatMinigame destroyed');
- } catch (error) {
- console.error('❌ Error destroying minigame:', error);
- }
- }
-
- /**
- * Complete minigame with success/failure
- * @param {boolean} success - Whether minigame succeeded
- */
- complete(success) {
- this.destroy();
- super.complete(success);
- }
-}
diff --git a/js/minigames/person-chat/person-chat-portraits.js b/js/minigames/person-chat/person-chat-portraits.js
index 897f04e..cbf5dba 100644
--- a/js/minigames/person-chat/person-chat-portraits.js
+++ b/js/minigames/person-chat/person-chat-portraits.js
@@ -55,9 +55,6 @@ export default class PersonChatPortraits {
// Create canvas
this.canvas = document.createElement('canvas');
- // Set canvas to use full available screen size
- this.updateCanvasSize();
-
this.canvas.className = 'person-chat-portrait';
this.canvas.id = `portrait-${this.npc.id}`;
this.ctx = this.canvas.getContext('2d');
@@ -70,14 +67,22 @@ export default class PersonChatPortraits {
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
- // Add to container
+ // Add to container first so it has dimensions
this.portraitContainer.innerHTML = '';
this.portraitContainer.appendChild(this.canvas);
// Get sprite sheet and frame
this.setupSpriteInfo();
- // Render portrait
+ // Set canvas size after it's in the DOM (container now has dimensions)
+ // Use a small delay to ensure container is fully laid out
+ setTimeout(() => {
+ this.updateCanvasSize();
+ this.render();
+ }, 0);
+
+ // Also set initial size immediately (in case container is already sized)
+ this.updateCanvasSize();
this.render();
// Handle window resize
@@ -92,17 +97,30 @@ export default class PersonChatPortraits {
}
/**
- * Calculate optimal integer scale factor for current browser window
+ * Calculate optimal integer scale factor for current container
* Matches base resolution (640x480) with pixel-perfect scaling
+ * Uses the same logic as the main game
* @returns {number} Optimal integer scale factor
*/
calculateOptimalScale() {
+ // Try to get the game-container (same as main game uses)
+ const gameContainer = document.getElementById('game-container');
+ const container = gameContainer || this.portraitContainer;
+
+ if (!container) {
+ return 2; // Default fallback
+ }
+
+ const containerWidth = container.clientWidth;
+ const containerHeight = container.clientHeight;
+
+ // Base resolution (same as main game)
const baseWidth = 640;
const baseHeight = 480;
// Calculate scale factors for both dimensions
- const scaleX = window.innerWidth / baseWidth;
- const scaleY = window.innerHeight / baseHeight;
+ const scaleX = containerWidth / baseWidth;
+ const scaleY = containerHeight / baseHeight;
// Use the smaller scale to maintain aspect ratio
const maxScale = Math.min(scaleX, scaleY);
@@ -116,7 +134,7 @@ export default class PersonChatPortraits {
const scaledHeight = baseHeight * scale;
// If this scale fits within the container, use it
- if (scaledWidth <= window.innerWidth && scaledHeight <= window.innerHeight) {
+ if (scaledWidth <= containerWidth && scaledHeight <= containerHeight) {
bestScale = scale;
} else {
break; // Stop at the largest scale that fits
@@ -127,25 +145,26 @@ export default class PersonChatPortraits {
}
/**
- * Update canvas size to match available screen space with pixel-perfect scaling
+ * Update canvas size to match available container space with pixel-perfect scaling
+ * Uses the same approach as the main game
*/
updateCanvasSize() {
if (!this.canvas) return;
- // Calculate optimal scale for pixel-perfect rendering
+ // Calculate optimal scale for pixel-perfect rendering (same as main game)
const optimalScale = this.calculateOptimalScale();
const baseWidth = 640;
const baseHeight = 480;
- // Set canvas internal resolution based on optimal scale
+ // Set canvas internal resolution to scaled resolution for pixel-perfect rendering
+ // This matches how the main game uses Phaser's scale system
this.canvas.width = baseWidth * optimalScale;
this.canvas.height = baseHeight * optimalScale;
- // Apply CSS scale to maintain proper viewport size
- this.canvas.style.width = '100%';
- this.canvas.style.height = '100%';
+ // CSS handles the display sizing (width/height 100% with object-fit: contain)
+ // The canvas internal resolution is set above for pixel-perfect rendering
- console.log(`🎨 Canvas scaled to ${optimalScale}x (${this.canvas.width}x${this.canvas.height}px)`);
+ console.log(`🎨 Canvas scaled to ${optimalScale}x (${this.canvas.width}x${this.canvas.height}px internal, fits container)`);
}
/**
@@ -239,7 +258,8 @@ export default class PersonChatPortraits {
// Get the source image
const source = frame.source.image;
- // Calculate scaling to fill canvas while maintaining aspect ratio
+ // Calculate scaling to fit sprite within canvas while maintaining aspect ratio
+ // Use Math.min to ensure full sprite is visible (contain style, not cover)
const spriteWidth = frame.cutWidth;
const spriteHeight = frame.cutHeight;
const canvasWidth = this.canvas.width;
@@ -247,7 +267,7 @@ export default class PersonChatPortraits {
let scaleX = canvasWidth / spriteWidth;
let scaleY = canvasHeight / spriteHeight;
- let scale = Math.max(scaleX, scaleY); // Fit cover style
+ let scale = Math.min(scaleX, scaleY); // Fit contain style - ensures full sprite visible
// Calculate position to center the sprite
const scaledWidth = spriteWidth * scale;
@@ -337,10 +357,11 @@ export default class PersonChatPortraits {
const imgWidth = img.width;
const imgHeight = img.height;
- // Calculate scaling to fill canvas while maintaining aspect ratio
+ // Calculate scaling to fit image within canvas while maintaining aspect ratio
+ // Use Math.min to ensure full sprite is visible (contain style, not cover)
let scaleX = canvasWidth / imgWidth;
let scaleY = canvasHeight / imgHeight;
- let scale = Math.max(scaleX, scaleY); // Fit cover style
+ let scale = Math.min(scaleX, scaleY); // Fit contain style - ensures full sprite visible
// Calculate position to center the image
const scaledWidth = imgWidth * scale;