mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
1204 lines
45 KiB
JavaScript
1204 lines
45 KiB
JavaScript
// Player System
|
|
// Handles player creation, movement, and animation
|
|
|
|
// Player management system
|
|
import {
|
|
MOVEMENT_SPEED,
|
|
RUN_SPEED_MULTIPLIER,
|
|
RUN_ANIMATION_MULTIPLIER,
|
|
ARRIVAL_THRESHOLD,
|
|
PLAYER_FEET_OFFSET_Y,
|
|
ROOM_CHECK_THRESHOLD,
|
|
CLICK_INDICATOR_SIZE,
|
|
CLICK_INDICATOR_DURATION,
|
|
SPRITE_PADDING_BOTTOM_ATLAS,
|
|
SPRITE_PADDING_BOTTOM_LEGACY
|
|
} from '../utils/constants.js?v=8';
|
|
|
|
export let player = null;
|
|
export let targetPoint = null;
|
|
export let isMoving = false;
|
|
export let lastPlayerPosition = { x: 0, y: 0 };
|
|
let gameRef = null;
|
|
|
|
// Keyboard input state
|
|
const keyboardInput = {
|
|
up: false,
|
|
down: false,
|
|
left: false,
|
|
right: false,
|
|
space: false,
|
|
shift: false
|
|
};
|
|
let isKeyboardMoving = false;
|
|
|
|
// Keyboard pause state (for when minigames need keyboard input)
|
|
let keyboardPaused = false;
|
|
|
|
// Export functions to pause/resume keyboard interception
|
|
export function pauseKeyboardInput() {
|
|
keyboardPaused = true;
|
|
console.log('🔒 Keyboard input PAUSED for minigame (keyboardPaused = true)');
|
|
}
|
|
|
|
export function resumeKeyboardInput() {
|
|
keyboardPaused = false;
|
|
// Clear all keyboard state when resuming
|
|
keyboardInput.up = false;
|
|
keyboardInput.down = false;
|
|
keyboardInput.left = false;
|
|
keyboardInput.right = false;
|
|
keyboardInput.space = false;
|
|
keyboardInput.shift = false;
|
|
isKeyboardMoving = false;
|
|
console.log('🔓 Keyboard input RESUMED (keyboardPaused = false)');
|
|
}
|
|
|
|
/**
|
|
* Update the player sprite to use a different character
|
|
* This allows changing the player's appearance mid-game
|
|
* @param {string} newSpriteKey - The texture key for the new sprite
|
|
*/
|
|
export async function updatePlayerSprite(newSpriteKey) {
|
|
if (!player || !gameRef) {
|
|
console.error('❌ Cannot update player sprite - player or game not initialized');
|
|
return false;
|
|
}
|
|
|
|
console.log('🔄 Updating player sprite from', player.texture.key, 'to', newSpriteKey);
|
|
|
|
// Check if the new sprite is already loaded
|
|
const newTexture = gameRef.textures.get(newSpriteKey);
|
|
if (!newTexture || newTexture.key === '__MISSING') {
|
|
console.log('📦 Loading new sprite:', newSpriteKey);
|
|
|
|
// Load the new sprite
|
|
const assetsPath = window.breakEscapeConfig?.assetsPath || '/break_escape/assets';
|
|
const atlasPath = `${assetsPath}/characters/${newSpriteKey}.png`;
|
|
const jsonPath = `${assetsPath}/characters/${newSpriteKey}.json`;
|
|
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
gameRef.load.atlas(newSpriteKey, atlasPath, jsonPath);
|
|
gameRef.load.once('complete', resolve);
|
|
gameRef.load.once('loaderror', reject);
|
|
gameRef.load.start();
|
|
});
|
|
console.log('✅ New sprite loaded:', newSpriteKey);
|
|
} catch (error) {
|
|
console.error('❌ Failed to load new sprite:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Store current state
|
|
const currentDirection = player.direction || 'down';
|
|
const wasMoving = player.isMoving;
|
|
|
|
// Detect if new sprite is atlas or legacy
|
|
const frames = gameRef.textures.get(newSpriteKey).getFrameNames();
|
|
const isAtlas = frames.length > 0 && typeof frames[0] === 'string' &&
|
|
(frames[0].includes('breathing-idle') || frames[0].includes('walk_') || frames[0].includes('_frame_'));
|
|
|
|
// Update collision box for new sprite type
|
|
if (isAtlas) {
|
|
player.body.setSize(18, 10);
|
|
player.body.setOffset(31, 66);
|
|
console.log('🎮 Updated collision box for atlas sprite (80x80)');
|
|
} else {
|
|
player.body.setSize(15, 10);
|
|
player.body.setOffset(25, 50);
|
|
console.log('🎮 Updated collision box for legacy sprite (64x64)');
|
|
}
|
|
|
|
// Store the atlas flag on player
|
|
player.isAtlas = isAtlas;
|
|
|
|
// Update scenario reference BEFORE recreating animations so createPlayerAnimations() uses the new sprite
|
|
if (window.gameScenario?.player) {
|
|
window.gameScenario.player.spriteSheet = newSpriteKey;
|
|
}
|
|
|
|
// Destroy old animations before creating new ones (they reference the old sprite texture)
|
|
const animKeysToDestroy = [
|
|
'idle-down', 'idle-up', 'idle-left', 'idle-right',
|
|
'idle-down-left', 'idle-down-right', 'idle-up-left', 'idle-up-right',
|
|
'walk-down', 'walk-up', 'walk-left', 'walk-right',
|
|
'walk-down-left', 'walk-down-right', 'walk-up-left', 'walk-up-right',
|
|
'punch-down', 'punch-up', 'punch-left', 'punch-right'
|
|
];
|
|
|
|
// Also destroy punch animations with compass directions
|
|
const punchDirections = ['north', 'south', 'east', 'west', 'north-east', 'north-west', 'south-east', 'south-west'];
|
|
punchDirections.forEach(dir => {
|
|
animKeysToDestroy.push(`cross-punch_${dir}`);
|
|
animKeysToDestroy.push(`lead-jab_${dir}`);
|
|
});
|
|
|
|
animKeysToDestroy.forEach(key => {
|
|
if (gameRef.anims.exists(key)) {
|
|
gameRef.anims.remove(key);
|
|
}
|
|
});
|
|
|
|
console.log('🗑️ Removed old animations');
|
|
|
|
// Change the texture of the existing sprite
|
|
let initialFrame;
|
|
if (isAtlas) {
|
|
const breathingIdleFrames = frames.filter(f => f.startsWith('breathing-idle_south_frame_'));
|
|
initialFrame = breathingIdleFrames.length > 0 ? breathingIdleFrames[0] : frames[0];
|
|
} else {
|
|
initialFrame = 20;
|
|
}
|
|
|
|
player.setTexture(newSpriteKey, initialFrame);
|
|
|
|
// Recreate animations for the new sprite (now reads updated scenario)
|
|
createPlayerAnimations();
|
|
|
|
// Play appropriate animation
|
|
const animKey = wasMoving ? `walk-${currentDirection}` : `idle-${currentDirection}`;
|
|
if (player.anims.exists(animKey)) {
|
|
player.anims.play(animKey, true);
|
|
}
|
|
|
|
console.log('✅ Player sprite updated successfully to', newSpriteKey);
|
|
return true;
|
|
}
|
|
|
|
// Create player sprite
|
|
export function createPlayer(gameInstance) {
|
|
gameRef = gameInstance;
|
|
console.log('Creating player');
|
|
|
|
// Get starting room position and calculate center
|
|
const scenario = window.gameScenario;
|
|
const startRoomId = scenario ? scenario.startRoom : 'reception';
|
|
const startRoomPosition = getStartingRoomCenter(startRoomId);
|
|
|
|
// Get player sprite - prioritize saved preference over scenario default
|
|
const playerSprite = window.breakEscapeConfig?.playerSprite || window.gameScenario?.player?.spriteSheet || 'male_hacker';
|
|
const source = window.breakEscapeConfig?.playerSprite ? 'saved preference' : (window.gameScenario?.player ? 'scenario' : 'default');
|
|
console.log(`🎮 Loading player sprite: ${playerSprite} (from ${source})`);
|
|
|
|
// Update scenario to match saved preference
|
|
if (window.gameScenario?.player && window.breakEscapeConfig?.playerSprite) {
|
|
window.gameScenario.player.spriteSheet = window.breakEscapeConfig.playerSprite;
|
|
}
|
|
|
|
// Check if this is an atlas sprite (has named frames) or legacy (numbered frames)
|
|
const texture = gameInstance.textures.get(playerSprite);
|
|
const frames = texture ? texture.getFrameNames() : [];
|
|
|
|
// More robust atlas detection
|
|
let isAtlas = false;
|
|
if (frames.length > 0) {
|
|
const firstFrame = frames[0];
|
|
isAtlas = typeof firstFrame === 'string' &&
|
|
(firstFrame.includes('breathing-idle') ||
|
|
firstFrame.includes('walk_') ||
|
|
firstFrame.includes('_frame_'));
|
|
}
|
|
|
|
console.log(`🔍 Player sprite ${playerSprite}: ${frames.length} frames, first: "${frames[0]}", isAtlas: ${isAtlas}`);
|
|
|
|
// Create player sprite with appropriate initial frame
|
|
let initialFrame;
|
|
if (isAtlas) {
|
|
// Find first breathing-idle_south frame
|
|
const breathingIdleFrames = frames.filter(f => f.startsWith('breathing-idle_south_frame_'));
|
|
initialFrame = breathingIdleFrames.length > 0 ? breathingIdleFrames[0] : frames[0];
|
|
} else {
|
|
initialFrame = 20; // Legacy default
|
|
}
|
|
player = gameInstance.add.sprite(startRoomPosition.x, startRoomPosition.y, playerSprite, initialFrame);
|
|
gameInstance.physics.add.existing(player);
|
|
|
|
// Store atlas detection flag for depth calculations
|
|
player.isAtlas = isAtlas;
|
|
|
|
// Keep the character at original size
|
|
player.setScale(1);
|
|
|
|
// Set smaller collision box at the feet
|
|
// Atlas sprites (80x80) vs Legacy sprites (64x64) have different offsets
|
|
if (isAtlas) {
|
|
// 80x80 sprite - collision box at feet
|
|
player.body.setSize(18, 10);
|
|
player.body.setOffset(31, 66); // Center horizontally (80-18)/2=31, feet at bottom 80-14=66
|
|
console.log('🎮 Player using atlas sprite (80x80) with adjusted collision box');
|
|
} else {
|
|
// 64x64 sprite - legacy collision box
|
|
player.body.setSize(15, 10);
|
|
player.body.setOffset(25, 50); // Legacy offset for 64px sprite
|
|
console.log('🎮 Player using legacy sprite (64x64) with standard collision box');
|
|
}
|
|
|
|
player.body.setCollideWorldBounds(true);
|
|
player.body.setBounce(0);
|
|
player.body.setDrag(0);
|
|
player.body.setFriction(0);
|
|
|
|
// Set initial player depth (will be updated dynamically during movement)
|
|
updatePlayerDepth(startRoomPosition.x, startRoomPosition.y);
|
|
|
|
// Track player direction and movement state
|
|
player.direction = 'down'; // Initial direction
|
|
player.isMoving = false;
|
|
player.lastDirection = 'down';
|
|
|
|
// Create animations
|
|
createPlayerAnimations();
|
|
|
|
// Set initial animation
|
|
player.anims.play('idle-down', true);
|
|
|
|
// Initialize last position
|
|
lastPlayerPosition = { x: player.x, y: player.y };
|
|
|
|
// Store player globally immediately for safety
|
|
window.player = player;
|
|
|
|
// Setup keyboard input listeners
|
|
setupKeyboardInput();
|
|
|
|
return player;
|
|
}
|
|
|
|
function setupKeyboardInput() {
|
|
// Handle keydown events
|
|
document.addEventListener('keydown', (event) => {
|
|
// Skip if keyboard input is paused (for minigames that need keyboard input)
|
|
if (keyboardPaused) {
|
|
console.log('⏸️ Keydown blocked (paused):', event.key);
|
|
return;
|
|
}
|
|
|
|
const key = event.key.toLowerCase();
|
|
|
|
// Shift key for running
|
|
if (key === 'shift') {
|
|
keyboardInput.shift = true;
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// Spacebar for jump
|
|
if (key === ' ') {
|
|
keyboardInput.space = true;
|
|
if (window.createPlayerJump) {
|
|
window.createPlayerJump();
|
|
}
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// E key for interaction
|
|
if (key === 'e') {
|
|
// Check interaction mode - if in punch mode, just punch in current direction
|
|
if (window.playerCombat) {
|
|
const currentMode = window.playerCombat.getInteractionMode();
|
|
if (currentMode === 'jab' || currentMode === 'cross') {
|
|
// Punch in current facing direction (don't interact)
|
|
window.playerCombat.punch();
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Normal interaction mode - interact with nearest object
|
|
if (window.tryInteractWithNearest) {
|
|
window.tryInteractWithNearest();
|
|
}
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// Arrow keys
|
|
if (key === 'arrowup') {
|
|
keyboardInput.up = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowdown') {
|
|
keyboardInput.down = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowleft') {
|
|
keyboardInput.left = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowright') {
|
|
keyboardInput.right = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
}
|
|
|
|
// WASD keys
|
|
if (key === 'w') {
|
|
keyboardInput.up = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 's') {
|
|
keyboardInput.down = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 'a') {
|
|
keyboardInput.left = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
} else if (key === 'd') {
|
|
keyboardInput.right = true;
|
|
isKeyboardMoving = true;
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
// Handle keyup events
|
|
document.addEventListener('keyup', (event) => {
|
|
// Skip if keyboard input is paused (for minigames that need keyboard input)
|
|
if (keyboardPaused) {
|
|
return;
|
|
}
|
|
|
|
const key = event.key.toLowerCase();
|
|
|
|
// Shift key
|
|
if (key === 'shift') {
|
|
keyboardInput.shift = false;
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// Spacebar
|
|
if (key === ' ') {
|
|
keyboardInput.space = false;
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// Arrow keys
|
|
if (key === 'arrowup') {
|
|
keyboardInput.up = false;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowdown') {
|
|
keyboardInput.down = false;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowleft') {
|
|
keyboardInput.left = false;
|
|
event.preventDefault();
|
|
} else if (key === 'arrowright') {
|
|
keyboardInput.right = false;
|
|
event.preventDefault();
|
|
}
|
|
|
|
// WASD keys
|
|
if (key === 'w') {
|
|
keyboardInput.up = false;
|
|
event.preventDefault();
|
|
} else if (key === 's') {
|
|
keyboardInput.down = false;
|
|
event.preventDefault();
|
|
} else if (key === 'a') {
|
|
keyboardInput.left = false;
|
|
event.preventDefault();
|
|
} else if (key === 'd') {
|
|
keyboardInput.right = false;
|
|
event.preventDefault();
|
|
}
|
|
|
|
// Check if any keys are still pressed
|
|
isKeyboardMoving = keyboardInput.up || keyboardInput.down || keyboardInput.left || keyboardInput.right;
|
|
});
|
|
}
|
|
|
|
function getAnimationKey(direction) {
|
|
// Check if player uses atlas-based animations (has native left directions)
|
|
// For atlas sprites, all 8 directions exist natively
|
|
const hasNativeLeft = gameRef?.anims?.exists(`idle-left`) || gameRef?.anims?.exists(`walk-left`);
|
|
|
|
if (hasNativeLeft) {
|
|
// Atlas sprite - use native directions
|
|
return direction;
|
|
}
|
|
|
|
// Legacy sprite - map left directions to their right counterparts (sprite is flipped)
|
|
switch(direction) {
|
|
case 'left':
|
|
return 'right';
|
|
case 'down-left':
|
|
return 'down-right';
|
|
case 'up-left':
|
|
return 'up-right';
|
|
default:
|
|
return direction;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map player directions to compass directions for punch animations
|
|
* @param {string} direction - Player direction (down, up, left, right, down-left, etc.)
|
|
* @returns {string} - Compass direction (south, north, west, east, south-west, etc.)
|
|
*/
|
|
function mapPlayerDirectionToCompass(direction) {
|
|
const directionMap = {
|
|
'right': 'east',
|
|
'left': 'west',
|
|
'up': 'north',
|
|
'down': 'south',
|
|
'up-right': 'north-east',
|
|
'up-left': 'north-west',
|
|
'down-right': 'south-east',
|
|
'down-left': 'south-west'
|
|
};
|
|
return directionMap[direction] || 'south';
|
|
}
|
|
|
|
function updateAnimationSpeed(isRunning) {
|
|
// Update animation speed based on whether player is running
|
|
if (!player || !player.anims) {
|
|
return;
|
|
}
|
|
|
|
const frameRate = isRunning ? 8 * RUN_ANIMATION_MULTIPLIER : 8;
|
|
|
|
// If there's a current animation playing, update its frameRate
|
|
if (player.anims.currentAnim) {
|
|
player.anims.currentAnim.frameRate = frameRate;
|
|
}
|
|
}
|
|
|
|
function createPlayerAnimations() {
|
|
const playerSprite = window.gameScenario?.player?.spriteSheet || 'hacker';
|
|
|
|
// Check if this is an atlas sprite (has named frames) or legacy (numbered frames)
|
|
const texture = gameRef.textures.get(playerSprite);
|
|
const frames = texture ? texture.getFrameNames() : [];
|
|
|
|
// More robust atlas detection
|
|
let isAtlas = false;
|
|
if (frames.length > 0) {
|
|
const firstFrame = frames[0];
|
|
isAtlas = typeof firstFrame === 'string' &&
|
|
(firstFrame.includes('breathing-idle') ||
|
|
firstFrame.includes('walk_') ||
|
|
firstFrame.includes('_frame_'));
|
|
}
|
|
|
|
console.log(`🔍 Player sprite ${playerSprite}: ${frames.length} frames, first: "${frames[0]}", isAtlas: ${isAtlas}`);
|
|
|
|
if (isAtlas) {
|
|
console.log(`🎮 Player using atlas sprite: ${playerSprite}`);
|
|
createAtlasPlayerAnimations(playerSprite);
|
|
} else {
|
|
console.log(`🎮 Player using legacy sprite: ${playerSprite}`);
|
|
createLegacyPlayerAnimations(playerSprite);
|
|
}
|
|
}
|
|
|
|
function createAtlasPlayerAnimations(spriteSheet) {
|
|
// Get texture and build animation data from frame names
|
|
const texture = gameRef.textures.get(spriteSheet);
|
|
const frameNames = texture.getFrameNames();
|
|
|
|
// Build animations object from frame names
|
|
const animations = {};
|
|
frameNames.forEach(frameName => {
|
|
// Parse frame name: "breathing-idle_south_frame_000" -> animation: "breathing-idle_south"
|
|
const match = frameName.match(/^(.+)_frame_\d+$/);
|
|
if (match) {
|
|
const animKey = match[1];
|
|
if (!animations[animKey]) {
|
|
animations[animKey] = [];
|
|
}
|
|
animations[animKey].push(frameName);
|
|
}
|
|
});
|
|
|
|
// Sort frames within each animation
|
|
Object.keys(animations).forEach(key => {
|
|
animations[key].sort();
|
|
});
|
|
|
|
if (Object.keys(animations).length === 0) {
|
|
console.warn(`⚠️ No animation data found in atlas: ${spriteSheet}`);
|
|
return;
|
|
}
|
|
|
|
// Get frame rates from player config
|
|
const playerConfig = window.gameScenario?.player?.spriteConfig || {};
|
|
const idleFrameRate = playerConfig.idleFrameRate || 6; // Slower for breathing effect
|
|
const walkFrameRate = playerConfig.walkFrameRate || 10;
|
|
const punchFrameRate = playerConfig.punchFrameRate || 12; // Faster for action animations
|
|
|
|
// Direction mapping: atlas directions → player directions
|
|
const directionMap = {
|
|
'east': 'right',
|
|
'west': 'left',
|
|
'north': 'up',
|
|
'south': 'down',
|
|
'north-east': 'up-right',
|
|
'north-west': 'up-left',
|
|
'south-east': 'down-right',
|
|
'south-west': 'down-left'
|
|
};
|
|
|
|
// Animation type mapping: atlas animations → player animations
|
|
const animTypeMap = {
|
|
'breathing-idle': 'idle',
|
|
'walk': 'walk',
|
|
'taking-punch': 'hit'
|
|
};
|
|
|
|
// Animation type framework (for grouping and frame rate)
|
|
const animationFramework = {
|
|
'idle': { frameRate: idleFrameRate, repeat: -1, name: 'idle' },
|
|
'walk': { frameRate: walkFrameRate, repeat: -1, name: 'walk' },
|
|
'cross-punch': { frameRate: punchFrameRate, repeat: 0, name: 'attack' },
|
|
'lead-jab': { frameRate: punchFrameRate, repeat: 0, name: 'attack' },
|
|
'taking-punch': { frameRate: 12, repeat: 0, name: 'hit' },
|
|
'falling-back-death': { frameRate: 10, repeat: 0, name: 'death' }
|
|
};
|
|
|
|
// Create animations from atlas metadata
|
|
for (const [atlasAnimKey, frames] of Object.entries(animations)) {
|
|
// Parse animation key: "breathing-idle_east" → type: "breathing-idle", direction: "east"
|
|
const parts = atlasAnimKey.split('_');
|
|
const atlasDirection = parts[parts.length - 1];
|
|
const atlasType = parts.slice(0, -1).join('_');
|
|
|
|
// Map to player direction and type
|
|
const playerDirection = directionMap[atlasDirection] || atlasDirection;
|
|
const playerType = animTypeMap[atlasType] || atlasType;
|
|
|
|
// Create animation key: "walk-right", "idle-down", "cross-punch_east", "lead-jab_south", etc.
|
|
const animKey = playerType === 'idle' || playerType === 'walk'
|
|
? `${playerType}-${playerDirection}`
|
|
: `${playerType}_${atlasDirection}`; // Keep atlas direction for punch animations
|
|
|
|
// Debug for punch animations
|
|
if (playerType === 'cross-punch' || playerType === 'lead-jab') {
|
|
console.log(` - Punch anim: ${atlasAnimKey} → type: ${playerType}, direction: ${atlasDirection}, key: ${animKey}, frames: ${frames.length}`);
|
|
}
|
|
|
|
// For idle animations, create a custom sequence: hold rotation frame for 2s, then loop breathing animation
|
|
if (playerType === 'idle') {
|
|
// Use the first frame of the rotation image (e.g., breathing-idle_{direction}_frame_000)
|
|
const rotationFrame = frames[0];
|
|
// Remaining frames are the breathing animation
|
|
const breathFrames = frames.slice(1);
|
|
// Build custom animation sequence
|
|
const idleAnimFrames = [
|
|
{ key: spriteSheet, frame: rotationFrame, duration: 2000 }, // Hold for 2s
|
|
...breathFrames.map(frameName => ({ key: spriteSheet, frame: frameName, duration: 200 }))
|
|
];
|
|
if (!gameRef.anims.exists(animKey)) {
|
|
gameRef.anims.create({
|
|
key: animKey,
|
|
frames: idleAnimFrames,
|
|
frameRate: idleFrameRate,
|
|
repeat: -1
|
|
});
|
|
console.log(` ✓ Created custom idle animation: ${animKey} (rotation + breath, ${idleAnimFrames.length} frames)`);
|
|
}
|
|
} else {
|
|
// Standard animation (walk, cross-punch, lead-jab, etc.)
|
|
const frameConfig = animationFramework[playerType] || { frameRate: walkFrameRate, repeat: -1 };
|
|
if (!gameRef.anims.exists(animKey)) {
|
|
const frameArray = frames.map(frameName => ({ key: spriteSheet, frame: frameName }));
|
|
console.log(` - Creating ${animKey} with ${frameArray.length} frames, frameRate: ${frameConfig.frameRate}, repeat: ${frameConfig.repeat}`);
|
|
if (frameArray.length === 0) {
|
|
console.warn(` ⚠️ Warning: Animation has 0 frames!`);
|
|
}
|
|
gameRef.anims.create({
|
|
key: animKey,
|
|
frames: frameArray,
|
|
frameRate: frameConfig.frameRate,
|
|
repeat: frameConfig.repeat
|
|
});
|
|
console.log(` ✓ Created ${frameConfig.name} animation: ${animKey} (${frames.length} frames @ ${frameConfig.frameRate} fps, repeat: ${frameConfig.repeat})`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Player atlas animations created for ${spriteSheet} (idle: ${idleFrameRate} fps, walk: ${walkFrameRate} fps, punch: ${punchFrameRate} fps)`);
|
|
|
|
// Log all punch animations created
|
|
const punchAnims = Object.keys(animations).filter(key => key.includes('cross-punch') || key.includes('lead-jab'));
|
|
if (punchAnims.length > 0) {
|
|
console.log(`🥊 Punch animations available (${punchAnims.length} total):`);
|
|
punchAnims.forEach(animName => {
|
|
const frameCount = animations[animName].length;
|
|
console.log(` - ${animName}: ${frameCount} frames`);
|
|
});
|
|
} else {
|
|
console.warn('⚠️ No punch animations found in atlas!');
|
|
}
|
|
}
|
|
|
|
function createLegacyPlayerAnimations(spriteSheet) {
|
|
// Create walking animations with correct frame numbers from original
|
|
gameRef.anims.create({
|
|
key: 'walk-right',
|
|
frames: gameRef.anims.generateFrameNumbers(spriteSheet, { start: 1, end: 4 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'walk-down',
|
|
frames: gameRef.anims.generateFrameNumbers(spriteSheet, { start: 6, end: 9 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'walk-up',
|
|
frames: gameRef.anims.generateFrameNumbers(spriteSheet, { start: 11, end: 14 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'walk-up-right',
|
|
frames: gameRef.anims.generateFrameNumbers(spriteSheet, { start: 16, end: 19 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'walk-down-right',
|
|
frames: gameRef.anims.generateFrameNumbers(spriteSheet, { start: 21, end: 24 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// Create idle frames (first frame of each row) with correct frame numbers
|
|
gameRef.anims.create({
|
|
key: 'idle-right',
|
|
frames: [{ key: spriteSheet, frame: 0 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-down',
|
|
frames: [{ key: spriteSheet, frame: 5 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-up',
|
|
frames: [{ key: spriteSheet, frame: 10 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-up-right',
|
|
frames: [{ key: spriteSheet, frame: 15 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-down-right',
|
|
frames: [{ key: spriteSheet, frame: 20 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
// Create left-facing idle animations (same frames as right, but sprite will be flipped)
|
|
gameRef.anims.create({
|
|
key: 'idle-left',
|
|
frames: [{ key: spriteSheet, frame: 0 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-down-left',
|
|
frames: [{ key: spriteSheet, frame: 20 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
gameRef.anims.create({
|
|
key: 'idle-up-left',
|
|
frames: [{ key: spriteSheet, frame: 15 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
console.log(`✅ Player legacy animations created for ${spriteSheet}`);
|
|
}
|
|
|
|
export function movePlayerToPoint(x, y) {
|
|
const worldBounds = gameRef.physics.world.bounds;
|
|
|
|
// Ensure coordinates are within bounds
|
|
x = Phaser.Math.Clamp(x, worldBounds.x, worldBounds.x + worldBounds.width);
|
|
y = Phaser.Math.Clamp(y, worldBounds.y, worldBounds.y + worldBounds.height);
|
|
|
|
// Create click indicator
|
|
createClickIndicator(x, y);
|
|
|
|
targetPoint = { x, y };
|
|
isMoving = true;
|
|
|
|
// Notify tutorial of movement
|
|
if (window.getTutorialManager) {
|
|
const tutorialManager = window.getTutorialManager();
|
|
tutorialManager.notifyPlayerMoved();
|
|
tutorialManager.notifyPlayerClickedToMove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Turn the player to face a world position, updating direction and idle animation.
|
|
* Call this before triggering a click-based interaction so the player visually
|
|
* faces the object/NPC they are acting on.
|
|
*/
|
|
export function facePlayerToward(targetX, targetY) {
|
|
if (!player) return;
|
|
|
|
const dx = targetX - player.x;
|
|
const dy = targetY - player.y;
|
|
const absX = Math.abs(dx);
|
|
const absY = Math.abs(dy);
|
|
|
|
let direction;
|
|
if (absX > absY * 2) {
|
|
direction = dx > 0 ? 'right' : 'left';
|
|
} else if (absY > absX * 2) {
|
|
direction = dy > 0 ? 'down' : 'up';
|
|
} else {
|
|
direction = dy > 0 ? (dx > 0 ? 'down-right' : 'down-left')
|
|
: (dx > 0 ? 'up-right' : 'up-left');
|
|
}
|
|
|
|
player.direction = direction;
|
|
player.lastDirection = direction;
|
|
|
|
// Play idle animation for the new direction (handles atlas vs legacy sprite mapping)
|
|
const animDir = getAnimationKey(direction);
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (!currentAnim.includes('punch') && !currentAnim.includes('jab') &&
|
|
!currentAnim.includes('death') && !currentAnim.includes('taking-punch')) {
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
}
|
|
|
|
function updatePlayerDepth(x, y) {
|
|
// Get the bottom of the player sprite, accounting for padding
|
|
// Atlas sprites (80x80) have 16px padding at bottom, legacy sprites (64x64) have minimal padding
|
|
// Use actual y parameter so depth follows visual position (including during death animations)
|
|
const spriteCenterToBottom = (player.height * player.scaleY) / 2;
|
|
const paddingOffset = player.isAtlas ? SPRITE_PADDING_BOTTOM_ATLAS : SPRITE_PADDING_BOTTOM_LEGACY;
|
|
const playerBottomY = y + spriteCenterToBottom - paddingOffset;
|
|
|
|
// Simple depth calculation: world Y position + layer offset
|
|
const playerDepth = playerBottomY + 0.5; // World Y + sprite layer offset
|
|
|
|
// Set the player depth (always update, no threshold)
|
|
if (player) {
|
|
player.setDepth(playerDepth);
|
|
|
|
// Debug logging - only show when depth actually changes significantly
|
|
const lastDepth = player.lastDepth || 0;
|
|
if (Math.abs(playerDepth - lastDepth) > 25) { // Reduced threshold for finer granularity
|
|
console.log(`Player depth: ${playerDepth} (Feet Y: ${playerBottomY}, Padding: ${paddingOffset}px)`);
|
|
console.log(` Player layers: feetY(${playerBottomY}) + 0.5`);
|
|
player.lastDepth = playerDepth;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createClickIndicator(x, y) {
|
|
// Create a circle at the click position
|
|
const indicator = gameRef.add.circle(x, y, CLICK_INDICATOR_SIZE, 0xffffff, 0.7);
|
|
indicator.setDepth(1000); // Above ground but below player
|
|
|
|
// Add a pulsing animation
|
|
gameRef.tweens.add({
|
|
targets: indicator,
|
|
scale: { from: 0.5, to: 1.5 },
|
|
alpha: { from: 0.7, to: 0 },
|
|
duration: CLICK_INDICATOR_DURATION,
|
|
ease: 'Sine.easeOut',
|
|
onComplete: () => {
|
|
indicator.destroy();
|
|
}
|
|
});
|
|
}
|
|
|
|
export function updatePlayerMovement() {
|
|
// Safety check: ensure player exists
|
|
if (!player || !player.body) {
|
|
return;
|
|
}
|
|
|
|
// Check if player is KO (knocked out) - disable movement
|
|
if (window.playerHealth && window.playerHealth.isKO()) {
|
|
player.body.setVelocity(0, 0);
|
|
return;
|
|
}
|
|
|
|
// Check if movement is explicitly disabled
|
|
if (player.disableMovement) {
|
|
player.body.setVelocity(0, 0);
|
|
return;
|
|
}
|
|
|
|
// Handle keyboard movement (takes priority over mouse movement)
|
|
if (isKeyboardMoving) {
|
|
updatePlayerKeyboardMovement();
|
|
} else {
|
|
// Handle mouse-based movement (original behavior)
|
|
updatePlayerMouseMovement();
|
|
}
|
|
|
|
// Final check: if velocity is 0 and player is marked as moving, switch to idle
|
|
if (player.body.velocity.x === 0 && player.body.velocity.y === 0 && player.isMoving) {
|
|
player.isMoving = false;
|
|
const animDir = getAnimationKey(player.direction);
|
|
// Don't interrupt special animations: punch, death, hit, etc.
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (!currentAnim.includes('punch') && !currentAnim.includes('jab') &&
|
|
!currentAnim.includes('death') && !currentAnim.includes('taking-punch')) {
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updatePlayerKeyboardMovement() {
|
|
// Cancel click-to-move when keyboard input is detected
|
|
if (isMoving || targetPoint) {
|
|
isMoving = false;
|
|
targetPoint = null;
|
|
}
|
|
|
|
// Calculate movement direction based on keyboard input
|
|
let dirX = 0;
|
|
let dirY = 0;
|
|
|
|
if (keyboardInput.right) dirX += 1;
|
|
if (keyboardInput.left) dirX -= 1;
|
|
if (keyboardInput.down) dirY += 1;
|
|
if (keyboardInput.up) dirY -= 1;
|
|
|
|
// Normalize diagonal movement to maintain consistent speed
|
|
let velocityX = 0;
|
|
let velocityY = 0;
|
|
|
|
if (dirX !== 0 || dirY !== 0) {
|
|
const magnitude = Math.sqrt(dirX * dirX + dirY * dirY);
|
|
// Apply run speed multiplier if shift is held
|
|
const speed = keyboardInput.shift ? MOVEMENT_SPEED * RUN_SPEED_MULTIPLIER : MOVEMENT_SPEED;
|
|
velocityX = (dirX / magnitude) * speed;
|
|
velocityY = (dirY / magnitude) * speed;
|
|
|
|
// Notify tutorial of movement and running
|
|
if (window.getTutorialManager) {
|
|
const tutorialManager = window.getTutorialManager();
|
|
tutorialManager.notifyPlayerMoved();
|
|
if (keyboardInput.shift) {
|
|
tutorialManager.notifyPlayerRan();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update animation speed every frame while moving
|
|
if (player.isMoving) {
|
|
updateAnimationSpeed(keyboardInput.shift);
|
|
}
|
|
|
|
// Check if movement is being blocked by collisions
|
|
let isBlocked = false;
|
|
if (velocityX !== 0 || velocityY !== 0) {
|
|
// Check if blocked in the direction we want to move
|
|
if (velocityX > 0 && player.body.blocked.right) isBlocked = true;
|
|
if (velocityX < 0 && player.body.blocked.left) isBlocked = true;
|
|
if (velocityY > 0 && player.body.blocked.down) isBlocked = true;
|
|
if (velocityY < 0 && player.body.blocked.up) isBlocked = true;
|
|
}
|
|
|
|
// Apply velocity
|
|
player.body.setVelocity(velocityX, velocityY);
|
|
|
|
// Update player depth based on actual player position
|
|
updatePlayerDepth(player.x, player.y);
|
|
|
|
// Update last player position for depth calculations
|
|
lastPlayerPosition.x = player.x;
|
|
lastPlayerPosition.y = player.y;
|
|
|
|
// Determine direction based on velocity
|
|
const absVX = Math.abs(velocityX);
|
|
const absVY = Math.abs(velocityY);
|
|
|
|
// Set player direction and animation
|
|
if (velocityX === 0 && velocityY === 0) {
|
|
// No movement - stop
|
|
if (player.isMoving) {
|
|
player.isMoving = false;
|
|
const animDir = getAnimationKey(player.direction);
|
|
player.anims.stop(); // Stop current animation
|
|
// Don't interrupt special animations: punch, death, hit, etc.
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (!currentAnim.includes('punch') && !currentAnim.includes('jab') &&
|
|
!currentAnim.includes('death') && !currentAnim.includes('taking-punch')) {
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
}
|
|
} else if (isBlocked) {
|
|
// Blocked by collision - preserve special animations but switch walk to idle
|
|
player.isMoving = false;
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
|
|
// Only change animation if it's a walk animation
|
|
// Preserve punch, jab, death, taking-punch, etc.
|
|
if (currentAnim.includes('walk')) {
|
|
const animDir = getAnimationKey(player.direction);
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
} else if (absVX > absVY * 2) {
|
|
// Mostly horizontal movement
|
|
player.direction = velocityX > 0 ? 'right' : 'left';
|
|
|
|
// Check if we have native left animations (atlas sprite)
|
|
const hasNativeLeft = gameRef.anims.exists('walk-left');
|
|
const animDir = hasNativeLeft ? player.direction : (velocityX > 0 ? 'right' : 'right');
|
|
const shouldFlip = !hasNativeLeft && velocityX < 0;
|
|
|
|
player.setFlipX(shouldFlip);
|
|
|
|
if (!player.isMoving || player.lastDirection !== player.direction) {
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
|
|
// If punching, restart punch animation in new direction
|
|
if (currentAnim.includes('punch') || currentAnim.includes('jab')) {
|
|
const animType = currentAnim.includes('cross-punch') ? 'cross-punch' : 'lead-jab';
|
|
const compassDir = mapPlayerDirectionToCompass(player.direction);
|
|
const newPunchKey = `${animType}_${compassDir}`;
|
|
|
|
if (gameRef.anims.exists(newPunchKey)) {
|
|
player.anims.play(newPunchKey, true);
|
|
console.log(`🥊 Direction changed during punch, restarting: ${newPunchKey}`);
|
|
}
|
|
} else {
|
|
// Normal walk animation
|
|
player.anims.play(`walk-${animDir}`, true);
|
|
}
|
|
player.isMoving = true;
|
|
player.lastDirection = player.direction;
|
|
}
|
|
} else if (absVY > absVX * 2) {
|
|
// Mostly vertical movement
|
|
player.direction = velocityY > 0 ? 'down' : 'up';
|
|
player.setFlipX(false);
|
|
|
|
if (!player.isMoving || player.lastDirection !== player.direction) {
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
|
|
// If punching, restart punch animation in new direction
|
|
if (currentAnim.includes('punch') || currentAnim.includes('jab')) {
|
|
const animType = currentAnim.includes('cross-punch') ? 'cross-punch' : 'lead-jab';
|
|
const compassDir = mapPlayerDirectionToCompass(player.direction);
|
|
const newPunchKey = `${animType}_${compassDir}`;
|
|
|
|
if (gameRef.anims.exists(newPunchKey)) {
|
|
player.anims.play(newPunchKey, true);
|
|
console.log(`🥊 Direction changed during punch, restarting: ${newPunchKey}`);
|
|
}
|
|
} else {
|
|
// Normal walk animation
|
|
player.anims.play(`walk-${player.direction}`, true);
|
|
}
|
|
player.isMoving = true;
|
|
player.lastDirection = player.direction;
|
|
}
|
|
} else {
|
|
// Diagonal movement
|
|
if (velocityY > 0) {
|
|
player.direction = velocityX > 0 ? 'down-right' : 'down-left';
|
|
} else {
|
|
player.direction = velocityX > 0 ? 'up-right' : 'up-left';
|
|
}
|
|
|
|
// Check if we have native left animations (atlas sprite)
|
|
const hasNativeLeft = gameRef.anims.exists('walk-down-left') || gameRef.anims.exists('walk-up-left');
|
|
const baseDir = hasNativeLeft ? player.direction : (velocityY > 0 ? 'down-right' : 'up-right');
|
|
const shouldFlip = !hasNativeLeft && velocityX < 0;
|
|
|
|
player.setFlipX(shouldFlip);
|
|
|
|
if (!player.isMoving || player.lastDirection !== player.direction) {
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
|
|
// If punching, restart punch animation in new direction
|
|
if (currentAnim.includes('punch') || currentAnim.includes('jab')) {
|
|
const animType = currentAnim.includes('cross-punch') ? 'cross-punch' : 'lead-jab';
|
|
const compassDir = mapPlayerDirectionToCompass(player.direction);
|
|
const newPunchKey = `${animType}_${compassDir}`;
|
|
|
|
if (gameRef.anims.exists(newPunchKey)) {
|
|
player.anims.play(newPunchKey, true);
|
|
console.log(`🥊 Direction changed during punch, restarting: ${newPunchKey}`);
|
|
}
|
|
} else {
|
|
// Normal walk animation
|
|
player.anims.play(`walk-${baseDir}`, true);
|
|
}
|
|
player.isMoving = true;
|
|
player.lastDirection = player.direction;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updatePlayerMouseMovement() {
|
|
if (!isMoving || !targetPoint) {
|
|
if (player.body.velocity.x !== 0 || player.body.velocity.y !== 0) {
|
|
player.body.setVelocity(0, 0);
|
|
player.isMoving = false;
|
|
|
|
// Play idle animation based on last direction
|
|
// Don't interrupt special animations: punch, death, hit, etc.
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (!currentAnim.includes('punch') && !currentAnim.includes('jab') &&
|
|
!currentAnim.includes('death') && !currentAnim.includes('taking-punch')) {
|
|
player.anims.play(`idle-${player.direction}`, true);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Cache player position - adjust for feet position
|
|
const px = player.x;
|
|
const py = player.y + PLAYER_FEET_OFFSET_Y; // Add offset to target the feet
|
|
|
|
// Update player depth based on actual player position (not feet-adjusted)
|
|
updatePlayerDepth(px, player.y);
|
|
|
|
// Use squared distance for performance
|
|
const dx = targetPoint.x - px;
|
|
const dy = targetPoint.y - py; // Compare with feet position
|
|
const distanceSq = dx * dx + dy * dy;
|
|
|
|
// Reached target point
|
|
if (distanceSq < ARRIVAL_THRESHOLD * ARRIVAL_THRESHOLD) {
|
|
isMoving = false;
|
|
player.body.setVelocity(0, 0);
|
|
if (player.isMoving) {
|
|
player.isMoving = false;
|
|
const animDir = getAnimationKey(player.direction);
|
|
player.anims.stop(); // Stop current animation
|
|
// Don't interrupt special animations: punch, death, hit, etc.
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (!currentAnim.includes('punch') && !currentAnim.includes('jab') &&
|
|
!currentAnim.includes('death') && !currentAnim.includes('taking-punch')) {
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Update last player position for depth calculations
|
|
lastPlayerPosition.x = px;
|
|
lastPlayerPosition.y = py - PLAYER_FEET_OFFSET_Y; // Store actual player position
|
|
|
|
// Normalize movement vector for consistent speed
|
|
const distance = Math.sqrt(distanceSq);
|
|
const velocityX = (dx / distance) * MOVEMENT_SPEED;
|
|
const velocityY = (dy / distance) * MOVEMENT_SPEED;
|
|
|
|
// Set velocity directly without checking for changes
|
|
player.body.setVelocity(velocityX, velocityY);
|
|
|
|
// Determine direction based on velocity
|
|
const absVX = Math.abs(velocityX);
|
|
const absVY = Math.abs(velocityY);
|
|
|
|
// Check if we have native left animations (atlas sprite)
|
|
const hasNativeLeft = gameRef.anims.exists('walk-left') || gameRef.anims.exists('walk-down-left');
|
|
|
|
// Set player direction and animation
|
|
if (absVX > absVY * 2) {
|
|
// Mostly horizontal movement
|
|
player.direction = velocityX > 0 ? 'right' : (hasNativeLeft ? 'left' : 'right');
|
|
player.setFlipX(!hasNativeLeft && velocityX < 0);
|
|
} else if (absVY > absVX * 2) {
|
|
// Mostly vertical movement
|
|
player.direction = velocityY > 0 ? 'down' : 'up';
|
|
player.setFlipX(false);
|
|
} else {
|
|
// Diagonal movement
|
|
if (velocityY > 0) {
|
|
player.direction = velocityX > 0 ? 'down-right' : (hasNativeLeft ? 'down-left' : 'down-right');
|
|
} else {
|
|
player.direction = velocityX > 0 ? 'up-right' : (hasNativeLeft ? 'up-left' : 'up-right');
|
|
}
|
|
player.setFlipX(!hasNativeLeft && velocityX < 0);
|
|
}
|
|
|
|
// Play appropriate animation if not already playing
|
|
if (!player.isMoving || player.lastDirection !== player.direction) {
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
|
|
// If punching, restart punch animation in new direction
|
|
if (currentAnim.includes('punch') || currentAnim.includes('jab')) {
|
|
const animType = currentAnim.includes('cross-punch') ? 'cross-punch' : 'lead-jab';
|
|
const compassDir = mapPlayerDirectionToCompass(player.direction);
|
|
const newPunchKey = `${animType}_${compassDir}`;
|
|
|
|
if (gameRef.anims.exists(newPunchKey)) {
|
|
player.anims.play(newPunchKey, true);
|
|
console.log(`🥊 Mouse movement: direction changed during punch, restarting: ${newPunchKey}`);
|
|
}
|
|
} else {
|
|
// Normal walk animation
|
|
player.anims.play(`walk-${player.direction}`, true);
|
|
}
|
|
player.isMoving = true;
|
|
player.lastDirection = player.direction;
|
|
}
|
|
|
|
// Stop if collision detected
|
|
if (player.body.blocked.none === false) {
|
|
isMoving = false;
|
|
player.body.setVelocity(0, 0);
|
|
player.isMoving = false;
|
|
|
|
// Switch walk animations to idle, but preserve special animations
|
|
const currentAnim = player.anims.currentAnim?.key || '';
|
|
if (currentAnim.includes('walk')) {
|
|
const animDir = getAnimationKey(player.direction);
|
|
player.anims.play(`idle-${animDir}`, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getStartingRoomCenter(startRoomId) {
|
|
// Default position if rooms not initialized yet
|
|
const defaultPos = { x: 160, y: 144 };
|
|
|
|
// If rooms are available, get the actual room position
|
|
if (window.rooms && window.rooms[startRoomId]) {
|
|
const roomPos = window.rooms[startRoomId].position;
|
|
// Center of 320x288 room
|
|
return {
|
|
x: roomPos.x + 160,
|
|
y: roomPos.y + 144
|
|
};
|
|
}
|
|
|
|
// Fallback to reasonable center position for reception room
|
|
// Reception is typically at (0,0) so center would be (160, 144)
|
|
return defaultPos;
|
|
}
|
|
|
|
// Export for global access
|
|
window.createPlayer = createPlayer;
|
|
window.pauseKeyboardInput = pauseKeyboardInput;
|
|
window.resumeKeyboardInput = resumeKeyboardInput;
|
|
window.updatePlayerSprite = updatePlayerSprite;
|
|
|
|
console.log('✅ Player module loaded - keyboard control functions exported to window:', {
|
|
createPlayer: typeof window.createPlayer,
|
|
pauseKeyboardInput: typeof window.pauseKeyboardInput,
|
|
resumeKeyboardInput: typeof window.resumeKeyboardInput,
|
|
updatePlayerSprite: typeof window.updatePlayerSprite
|
|
});
|