mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 19:28:03 +00:00
feat(room-layout): Implement new grid-based room layout system with critical bug fixes
- Add grid unit constants (5x4 tiles) to constants.js - Create comprehensive grid conversion helper functions - Implement 4-direction room positioning (N/S/E/W) with breadth-first algorithm - Add door placement functions with CRITICAL fixes: * Negative modulo fix: ((sum % 2) + 2) % 2 for deterministic placement * Asymmetric alignment fix: single-door rooms align with multi-door rooms * Consistent grid alignment using Math.floor() for negative coordinates - Rewrite calculateRoomPositions to support variable room sizes - Update createDoorSpritesForRoom to use new placement system - Store room dimensions globally for cross-system access This implements the comprehensive room layout redesign as specified in planning_notes/new_room_layout with all critical fixes from review2. Addresses: Variable room sizes, proper door alignment, and grid-based positioning
This commit is contained in:
557
js/core/rooms.js
557
js/core/rooms.js
@@ -42,7 +42,18 @@
|
||||
*/
|
||||
|
||||
// Room management system
|
||||
import { TILE_SIZE, DOOR_ALIGN_OVERLAP, GRID_SIZE, INTERACTION_RANGE_SQ, INTERACTION_CHECK_INTERVAL } from '../utils/constants.js?v=8';
|
||||
import {
|
||||
TILE_SIZE,
|
||||
DOOR_ALIGN_OVERLAP,
|
||||
GRID_SIZE,
|
||||
INTERACTION_RANGE_SQ,
|
||||
INTERACTION_CHECK_INTERVAL,
|
||||
GRID_UNIT_WIDTH_TILES,
|
||||
GRID_UNIT_HEIGHT_TILES,
|
||||
VISUAL_TOP_TILES,
|
||||
GRID_UNIT_WIDTH_PX,
|
||||
GRID_UNIT_HEIGHT_PX
|
||||
} from '../utils/constants.js?v=8';
|
||||
|
||||
// Import the new system modules
|
||||
import { initializeDoors, createDoorSpritesForRoom, checkDoorTransitions, updateDoorSpritesVisibility } from '../systems/doors.js';
|
||||
@@ -641,147 +652,433 @@ export function calculateWorldBounds(gameInstance) {
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateRoomPositions(gameInstance) {
|
||||
const OVERLAP = 64;
|
||||
// ============================================================================
|
||||
// GRID UNIT CONVERSION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Convert tile dimensions to grid units
|
||||
*
|
||||
* Grid units are the base stacking size: 5 tiles wide × 4 tiles tall
|
||||
* (excluding top 2 visual wall tiles)
|
||||
*
|
||||
* @param {number} widthTiles - Room width in tiles
|
||||
* @param {number} heightTiles - Room height in tiles (including visual wall)
|
||||
* @returns {{gridWidth: number, gridHeight: number}}
|
||||
*/
|
||||
function tilesToGridUnits(widthTiles, heightTiles) {
|
||||
const gridWidth = Math.floor(widthTiles / GRID_UNIT_WIDTH_TILES);
|
||||
|
||||
// Subtract visual top wall tiles before calculating grid height
|
||||
const stackingHeightTiles = heightTiles - VISUAL_TOP_TILES;
|
||||
const gridHeight = Math.floor(stackingHeightTiles / GRID_UNIT_HEIGHT_TILES);
|
||||
|
||||
return { gridWidth, gridHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert grid coordinates to world position
|
||||
*
|
||||
* Grid coordinates are positions in grid unit space.
|
||||
* This converts them to pixel world coordinates.
|
||||
*
|
||||
* @param {number} gridX - Grid X coordinate
|
||||
* @param {number} gridY - Grid Y coordinate
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
function gridToWorld(gridX, gridY) {
|
||||
return {
|
||||
x: gridX * GRID_UNIT_WIDTH_PX,
|
||||
y: gridY * GRID_UNIT_HEIGHT_PX
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert world position to grid coordinates
|
||||
*
|
||||
* @param {number} worldX - World X position in pixels
|
||||
* @param {number} worldY - World Y position in pixels
|
||||
* @returns {{gridX: number, gridY: number}}
|
||||
*/
|
||||
function worldToGrid(worldX, worldY) {
|
||||
return {
|
||||
gridX: Math.floor(worldX / GRID_UNIT_WIDTH_PX),
|
||||
gridY: Math.floor(worldY / GRID_UNIT_HEIGHT_PX)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Align a world position to the nearest grid boundary
|
||||
*
|
||||
* Uses Math.floor for consistent rounding of negative numbers
|
||||
* (always rounds toward negative infinity)
|
||||
*
|
||||
* @param {number} worldX - World X position
|
||||
* @param {number} worldY - World Y position
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
function alignToGrid(worldX, worldY) {
|
||||
// Use floor for consistent rounding of negative numbers
|
||||
const gridX = Math.floor(worldX / GRID_UNIT_WIDTH_PX);
|
||||
const gridY = Math.floor(worldY / GRID_UNIT_HEIGHT_PX);
|
||||
|
||||
return {
|
||||
x: gridX * GRID_UNIT_WIDTH_PX,
|
||||
y: gridY * GRID_UNIT_HEIGHT_PX
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract room dimensions from Tiled JSON data
|
||||
*
|
||||
* Reads the tilemap to get room size and calculates:
|
||||
* - Tile dimensions
|
||||
* - Pixel dimensions
|
||||
* - Grid units
|
||||
* - Stacking height (for positioning calculations)
|
||||
*
|
||||
* @param {string} roomId - Room identifier
|
||||
* @param {Object} roomData - Room data from scenario
|
||||
* @param {Phaser.Game} gameInstance - Game instance for accessing tilemaps
|
||||
* @returns {Object} Dimension data
|
||||
*/
|
||||
function getRoomDimensions(roomId, roomData, gameInstance) {
|
||||
const map = gameInstance.cache.tilemap.get(roomData.type);
|
||||
|
||||
let widthTiles, heightTiles;
|
||||
|
||||
// Try different ways to access tilemap data
|
||||
if (map && map.json) {
|
||||
widthTiles = map.json.width;
|
||||
heightTiles = map.json.height;
|
||||
} else if (map && map.data) {
|
||||
widthTiles = map.data.width;
|
||||
heightTiles = map.data.height;
|
||||
} else {
|
||||
// Fallback to standard room size
|
||||
console.warn(`Could not read dimensions for ${roomId}, using default 10×10`);
|
||||
widthTiles = 10;
|
||||
heightTiles = 10;
|
||||
}
|
||||
|
||||
// Calculate grid units
|
||||
const { gridWidth, gridHeight } = tilesToGridUnits(widthTiles, heightTiles);
|
||||
|
||||
// Calculate pixel dimensions
|
||||
const widthPx = widthTiles * TILE_SIZE;
|
||||
const heightPx = heightTiles * TILE_SIZE;
|
||||
const stackingHeightPx = (heightTiles - VISUAL_TOP_TILES) * TILE_SIZE;
|
||||
|
||||
return {
|
||||
widthTiles,
|
||||
heightTiles,
|
||||
widthPx,
|
||||
heightPx,
|
||||
stackingHeightPx,
|
||||
gridWidth,
|
||||
gridHeight
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ROOM POSITIONING FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Position a single room to the north of current room
|
||||
*/
|
||||
function positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const connectedDim = dimensions[connectedRoom];
|
||||
|
||||
// Center the connected room above current room
|
||||
const x = currentPos.x + (currentDim.widthPx - connectedDim.widthPx) / 2;
|
||||
const y = currentPos.y - connectedDim.stackingHeightPx;
|
||||
|
||||
// Align to grid using floor (consistent rounding for negatives)
|
||||
return alignToGrid(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position multiple rooms to the north of current room
|
||||
*/
|
||||
function positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const positions = {};
|
||||
const gameScenario = window.gameScenario;
|
||||
|
||||
console.log('=== Starting Room Position Calculations ===');
|
||||
|
||||
// Get room dimensions from tilemaps
|
||||
const roomDimensions = {};
|
||||
Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => {
|
||||
const map = gameInstance.cache.tilemap.get(roomData.type);
|
||||
console.log(`Debug - Room ${roomId}:`, {
|
||||
mapData: map,
|
||||
fullData: map?.data,
|
||||
json: map?.json
|
||||
});
|
||||
|
||||
// Try different ways to access the data
|
||||
if (map) {
|
||||
let width, height;
|
||||
if (map.json) {
|
||||
width = map.json.width;
|
||||
height = map.json.height;
|
||||
} else if (map.data) {
|
||||
width = map.data.width;
|
||||
height = map.data.height;
|
||||
} else {
|
||||
width = map.width;
|
||||
height = map.height;
|
||||
}
|
||||
|
||||
roomDimensions[roomId] = {
|
||||
width: width * TILE_SIZE, // tile width is TILE_SIZE
|
||||
height: height * TILE_SIZE // tile height is TILE_SIZE
|
||||
};
|
||||
} else {
|
||||
console.error(`Could not find tilemap data for room ${roomId}`);
|
||||
// Fallback to default dimensions if needed
|
||||
roomDimensions[roomId] = {
|
||||
width: 320, // default width (10 tiles at 32px)
|
||||
height: 288 // default height (9 tiles at 32px)
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate total width of all connected rooms
|
||||
const totalWidth = connectedRooms.reduce((sum, roomId) => {
|
||||
return sum + dimensions[roomId].widthPx;
|
||||
}, 0);
|
||||
|
||||
// Determine starting X position (center the group)
|
||||
const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2;
|
||||
|
||||
// Position each room left to right
|
||||
let currentX = startX;
|
||||
connectedRooms.forEach(roomId => {
|
||||
const connectedDim = dimensions[roomId];
|
||||
|
||||
// Y position is based on stacking height
|
||||
const y = currentPos.y - connectedDim.stackingHeightPx;
|
||||
|
||||
// Align to grid
|
||||
positions[roomId] = alignToGrid(currentX, y);
|
||||
|
||||
currentX += connectedDim.widthPx;
|
||||
});
|
||||
|
||||
// Start with reception room at origin
|
||||
positions[gameScenario.startRoom] = { x: 0, y: 0 };
|
||||
console.log(`Starting room ${gameScenario.startRoom} position:`, positions[gameScenario.startRoom]);
|
||||
|
||||
// Process rooms level by level, starting from reception
|
||||
const processed = new Set([gameScenario.startRoom]);
|
||||
const queue = [gameScenario.startRoom];
|
||||
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position a single room to the south of current room
|
||||
*/
|
||||
function positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const connectedDim = dimensions[connectedRoom];
|
||||
|
||||
// Center the connected room below current room
|
||||
const x = currentPos.x + (currentDim.widthPx - connectedDim.widthPx) / 2;
|
||||
const y = currentPos.y + currentDim.stackingHeightPx;
|
||||
|
||||
// Align to grid
|
||||
return alignToGrid(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position multiple rooms to the south of current room
|
||||
*/
|
||||
function positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const positions = {};
|
||||
|
||||
// Calculate total width
|
||||
const totalWidth = connectedRooms.reduce((sum, roomId) => {
|
||||
return sum + dimensions[roomId].widthPx;
|
||||
}, 0);
|
||||
|
||||
// Determine starting X position (center the group)
|
||||
const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2;
|
||||
|
||||
// Position each room left to right
|
||||
let currentX = startX;
|
||||
connectedRooms.forEach(roomId => {
|
||||
const connectedDim = dimensions[roomId];
|
||||
|
||||
// Y position below current room
|
||||
const y = currentPos.y + currentDim.stackingHeightPx;
|
||||
|
||||
// Align to grid
|
||||
positions[roomId] = alignToGrid(currentX, y);
|
||||
|
||||
currentX += connectedDim.widthPx;
|
||||
});
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position a single room to the east of current room
|
||||
*/
|
||||
function positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const connectedDim = dimensions[connectedRoom];
|
||||
|
||||
// Position to the right, aligned at north edge
|
||||
const x = currentPos.x + currentDim.widthPx;
|
||||
const y = currentPos.y;
|
||||
|
||||
// Align to grid
|
||||
return alignToGrid(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position multiple rooms to the east of current room
|
||||
*/
|
||||
function positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions) {
|
||||
const currentDim = dimensions[currentRoom];
|
||||
const positions = {};
|
||||
|
||||
// Position to the right
|
||||
const x = currentPos.x + currentDim.widthPx;
|
||||
|
||||
// Stack vertically starting at current Y
|
||||
let currentY = currentPos.y;
|
||||
connectedRooms.forEach(roomId => {
|
||||
const connectedDim = dimensions[roomId];
|
||||
|
||||
// Align to grid
|
||||
positions[roomId] = alignToGrid(x, currentY);
|
||||
|
||||
currentY += connectedDim.stackingHeightPx;
|
||||
});
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position a single room to the west of current room
|
||||
*/
|
||||
function positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions) {
|
||||
const connectedDim = dimensions[connectedRoom];
|
||||
|
||||
// Position to the left, aligned at north edge
|
||||
const x = currentPos.x - connectedDim.widthPx;
|
||||
const y = currentPos.y;
|
||||
|
||||
// Align to grid
|
||||
return alignToGrid(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position multiple rooms to the west of current room
|
||||
*/
|
||||
function positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions) {
|
||||
const positions = {};
|
||||
|
||||
// Stack vertically starting at current Y
|
||||
let currentY = currentPos.y;
|
||||
connectedRooms.forEach(roomId => {
|
||||
const connectedDim = dimensions[roomId];
|
||||
|
||||
// Position to the left
|
||||
const x = currentPos.x - connectedDim.widthPx;
|
||||
|
||||
// Align to grid
|
||||
positions[roomId] = alignToGrid(x, currentY);
|
||||
|
||||
currentY += connectedDim.stackingHeightPx;
|
||||
});
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route single room positioning to appropriate direction function
|
||||
*/
|
||||
function positionSingleRoom(direction, currentRoom, connectedRoom, currentPos, dimensions) {
|
||||
switch (direction) {
|
||||
case 'north': return positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions);
|
||||
case 'south': return positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions);
|
||||
case 'east': return positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions);
|
||||
case 'west': return positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions);
|
||||
default:
|
||||
console.error(`Unknown direction: ${direction}`);
|
||||
return currentPos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route multiple room positioning to appropriate direction function
|
||||
*/
|
||||
function positionMultipleRooms(direction, currentRoom, connectedRooms, currentPos, dimensions) {
|
||||
switch (direction) {
|
||||
case 'north': return positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions);
|
||||
case 'south': return positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions);
|
||||
case 'east': return positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions);
|
||||
case 'west': return positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions);
|
||||
default:
|
||||
console.error(`Unknown direction: ${direction}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateRoomPositions(gameInstance) {
|
||||
const positions = {};
|
||||
const dimensions = {};
|
||||
const processed = new Set();
|
||||
const queue = [];
|
||||
const gameScenario = window.gameScenario;
|
||||
|
||||
console.log('=== NEW ROOM LAYOUT SYSTEM: Starting Room Position Calculations ===');
|
||||
|
||||
// Phase 1: Extract all room dimensions
|
||||
console.log('\n--- Phase 1: Extracting Room Dimensions ---');
|
||||
Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => {
|
||||
dimensions[roomId] = getRoomDimensions(roomId, roomData, gameInstance);
|
||||
console.log(`Room ${roomId} (${roomData.type}): ${dimensions[roomId].widthTiles}×${dimensions[roomId].heightTiles} tiles = ${dimensions[roomId].gridWidth}×${dimensions[roomId].gridHeight} grid units`);
|
||||
});
|
||||
|
||||
// Phase 2: Place starting room at origin
|
||||
console.log('\n--- Phase 2: Placing Starting Room ---');
|
||||
const startRoomId = gameScenario.startRoom;
|
||||
positions[startRoomId] = { x: 0, y: 0 };
|
||||
processed.add(startRoomId);
|
||||
queue.push(startRoomId);
|
||||
console.log(`Starting room "${startRoomId}" positioned at (0, 0)`);
|
||||
|
||||
// Phase 3: Process rooms breadth-first
|
||||
console.log('\n--- Phase 3: Processing Rooms Breadth-First ---');
|
||||
while (queue.length > 0) {
|
||||
const currentRoomId = queue.shift();
|
||||
const currentRoom = gameScenario.rooms[currentRoomId];
|
||||
const currentPos = positions[currentRoomId];
|
||||
const currentDimensions = roomDimensions[currentRoomId];
|
||||
|
||||
console.log(`\nProcessing room ${currentRoomId}`);
|
||||
console.log('Current position:', currentPos);
|
||||
console.log('Connections:', currentRoom.connections);
|
||||
|
||||
Object.entries(currentRoom.connections).forEach(([direction, connected]) => {
|
||||
console.log(`\nProcessing ${direction} connection:`, connected);
|
||||
|
||||
if (Array.isArray(connected)) {
|
||||
const roomsToProcess = connected.filter(r => !processed.has(r));
|
||||
console.log('Unprocessed connected rooms:', roomsToProcess);
|
||||
if (roomsToProcess.length === 0) return;
|
||||
|
||||
if (direction === 'north' || direction === 'south') {
|
||||
const firstRoom = roomsToProcess[0];
|
||||
const firstRoomWidth = roomDimensions[firstRoom].width;
|
||||
const firstRoomHeight = roomDimensions[firstRoom].height;
|
||||
|
||||
const secondRoom = roomsToProcess[1];
|
||||
const secondRoomWidth = roomDimensions[secondRoom].width;
|
||||
const secondRoomHeight = roomDimensions[secondRoom].height;
|
||||
|
||||
if (direction === 'north') {
|
||||
// First room - right edge aligns with current room's left edge
|
||||
positions[firstRoom] = {
|
||||
x: currentPos.x - firstRoomWidth + DOOR_ALIGN_OVERLAP,
|
||||
y: currentPos.y - firstRoomHeight + OVERLAP
|
||||
};
|
||||
|
||||
// Second room - left edge aligns with current room's right edge
|
||||
positions[secondRoom] = {
|
||||
x: currentPos.x + currentDimensions.width - DOOR_ALIGN_OVERLAP,
|
||||
y: currentPos.y - secondRoomHeight + OVERLAP
|
||||
};
|
||||
} else if (direction === 'south') {
|
||||
// First room - left edge aligns with current room's right edge
|
||||
positions[firstRoom] = {
|
||||
x: currentPos.x - firstRoomWidth + DOOR_ALIGN_OVERLAP,
|
||||
y: currentPos.y + currentDimensions.height - OVERLAP
|
||||
};
|
||||
|
||||
// Second room - right edge aligns with current room's left edge
|
||||
positions[secondRoom] = {
|
||||
x: currentPos.x + currentDimensions.width - DOOR_ALIGN_OVERLAP,
|
||||
y: currentPos.y + currentDimensions.height - secondRoomHeight - OVERLAP
|
||||
};
|
||||
}
|
||||
|
||||
roomsToProcess.forEach(roomId => {
|
||||
processed.add(roomId);
|
||||
queue.push(roomId);
|
||||
console.log(`Positioned room ${roomId} at:`, positions[roomId]);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`\nProcessing room: ${currentRoomId}`);
|
||||
console.log(` Position: (${currentPos.x}, ${currentPos.y})`);
|
||||
|
||||
// Skip rooms without connections
|
||||
if (!currentRoom.connections) {
|
||||
console.log(` No connections for ${currentRoomId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process each direction
|
||||
['north', 'south', 'east', 'west'].forEach(direction => {
|
||||
const connected = currentRoom.connections[direction];
|
||||
if (!connected) return;
|
||||
|
||||
// Convert to array if single connection
|
||||
const connectedRooms = Array.isArray(connected) ? connected : [connected];
|
||||
|
||||
// Filter out already processed rooms
|
||||
const unprocessed = connectedRooms.filter(roomId => !processed.has(roomId));
|
||||
if (unprocessed.length === 0) {
|
||||
console.log(` ${direction}: all rooms already processed`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` ${direction}: positioning ${unprocessed.length} room(s) - ${unprocessed.join(', ')}`);
|
||||
|
||||
// Position rooms based on count
|
||||
if (unprocessed.length === 1) {
|
||||
// Single room connection
|
||||
const roomId = unprocessed[0];
|
||||
const position = positionSingleRoom(direction, currentRoomId, roomId, currentPos, dimensions);
|
||||
positions[roomId] = position;
|
||||
processed.add(roomId);
|
||||
queue.push(roomId);
|
||||
|
||||
const gridCoords = worldToGrid(position.x, position.y);
|
||||
console.log(` ${roomId}: positioned at world(${position.x}, ${position.y}) = grid(${gridCoords.gridX}, ${gridCoords.gridY})`);
|
||||
} else {
|
||||
if (processed.has(connected)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectedDimensions = roomDimensions[connected];
|
||||
|
||||
// Center the connected room
|
||||
const x = currentPos.x +
|
||||
(currentDimensions.width - connectedDimensions.width) / 2;
|
||||
const y = direction === 'north'
|
||||
? currentPos.y - connectedDimensions.height + OVERLAP
|
||||
: currentPos.y + currentDimensions.height - OVERLAP;
|
||||
|
||||
|
||||
positions[connected] = { x, y };
|
||||
processed.add(connected);
|
||||
queue.push(connected);
|
||||
|
||||
console.log(`Positioned single room ${connected} at:`, positions[connected]);
|
||||
// Multiple room connections
|
||||
const newPositions = positionMultipleRooms(direction, currentRoomId, unprocessed, currentPos, dimensions);
|
||||
|
||||
unprocessed.forEach(roomId => {
|
||||
positions[roomId] = newPositions[roomId];
|
||||
processed.add(roomId);
|
||||
queue.push(roomId);
|
||||
|
||||
const gridCoords = worldToGrid(newPositions[roomId].x, newPositions[roomId].y);
|
||||
console.log(` ${roomId}: positioned at world(${newPositions[roomId].x}, ${newPositions[roomId].y}) = grid(${gridCoords.gridX}, ${gridCoords.gridY})`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n=== Final Room Positions ===');
|
||||
|
||||
// Phase 4: Log final positions
|
||||
console.log('\n--- Phase 4: Final Room Positions ---');
|
||||
Object.entries(positions).forEach(([roomId, pos]) => {
|
||||
console.log(`${roomId}:`, pos);
|
||||
const gridCoords = worldToGrid(pos.x, pos.y);
|
||||
console.log(`${roomId}: world(${pos.x}, ${pos.y}) = grid(${gridCoords.gridX}, ${gridCoords.gridY})`);
|
||||
});
|
||||
|
||||
|
||||
// Store dimensions globally for use by door placement
|
||||
window.roomDimensions = dimensions;
|
||||
|
||||
console.log('\n=== Room Position Calculations Complete ===\n');
|
||||
return positions;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
/**
|
||||
* DOOR SYSTEM
|
||||
* ===========
|
||||
*
|
||||
*
|
||||
* Handles door sprites, interactions, transitions, and visibility management.
|
||||
* Separated from rooms.js for better modularity and maintainability.
|
||||
*
|
||||
* NEW: Includes comprehensive door placement with asymmetric alignment fix
|
||||
* for variable room sizes using the grid unit system.
|
||||
*/
|
||||
|
||||
import { TILE_SIZE } from '../utils/constants.js';
|
||||
import {
|
||||
TILE_SIZE,
|
||||
GRID_UNIT_WIDTH_PX,
|
||||
GRID_UNIT_HEIGHT_PX
|
||||
} from '../utils/constants.js';
|
||||
import { handleUnlock } from './unlock-system.js';
|
||||
|
||||
let gameRef = null;
|
||||
@@ -37,6 +44,316 @@ let lastDoorTransitionTime = 0;
|
||||
const DOOR_TRANSITION_COOLDOWN = 1000; // 1 second cooldown between transitions
|
||||
let lastDoorTransition = null; // Track the last door transition to prevent repeats
|
||||
|
||||
// ============================================================================
|
||||
// DOOR PLACEMENT FUNCTIONS WITH ASYMMETRIC ALIGNMENT FIX
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Helper to convert world position to grid coordinates
|
||||
*/
|
||||
function worldToGrid(worldX, worldY) {
|
||||
return {
|
||||
gridX: Math.floor(worldX / GRID_UNIT_WIDTH_PX),
|
||||
gridY: Math.floor(worldY / GRID_UNIT_HEIGHT_PX)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a single north door with asymmetric alignment fix
|
||||
* CRITICAL: Handles negative modulo correctly and aligns with multi-door rooms
|
||||
*/
|
||||
function placeNorthDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom, gameScenario, allPositions, allDimensions) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
|
||||
// CRITICAL: Check if connected room has multiple connections in opposite direction
|
||||
const connectedRoomData = gameScenario.rooms[connectedRoom];
|
||||
const connectedSouthConnections = connectedRoomData?.connections?.south;
|
||||
|
||||
if (Array.isArray(connectedSouthConnections) && connectedSouthConnections.length > 1) {
|
||||
// Connected room has multiple south doors - align with the correct one
|
||||
const indexInArray = connectedSouthConnections.indexOf(roomId);
|
||||
|
||||
if (indexInArray >= 0) {
|
||||
// Calculate where the connected room's door is positioned
|
||||
const connectedPos = allPositions[connectedRoom];
|
||||
const connectedDim = allDimensions[connectedRoom];
|
||||
|
||||
// Use same spacing logic as placeNorthDoorsMultiple
|
||||
const edgeInset = TILE_SIZE * 1.5;
|
||||
const availableWidth = connectedDim.widthPx - (edgeInset * 2);
|
||||
const doorCount = connectedSouthConnections.length;
|
||||
const spacing = availableWidth / (doorCount - 1);
|
||||
|
||||
const alignedDoorX = connectedPos.x + edgeInset + (spacing * indexInArray);
|
||||
const doorY = roomPosition.y + TILE_SIZE;
|
||||
|
||||
return { x: alignedDoorX, y: doorY, connectedRoom };
|
||||
}
|
||||
}
|
||||
|
||||
// Default: Deterministic left/right placement based on grid position
|
||||
// CRITICAL FIX: Handle negative grid coordinates correctly
|
||||
// JavaScript modulo with negatives: -5 % 2 = -1 (not 1)
|
||||
const gridCoords = worldToGrid(roomPosition.x, roomPosition.y);
|
||||
const sum = gridCoords.gridX + gridCoords.gridY;
|
||||
const useRightSide = ((sum % 2) + 2) % 2 === 1;
|
||||
|
||||
let doorX;
|
||||
if (useRightSide) {
|
||||
// Northeast corner
|
||||
doorX = roomPosition.x + roomWidthPx - (TILE_SIZE * 1.5);
|
||||
} else {
|
||||
// Northwest corner
|
||||
doorX = roomPosition.x + (TILE_SIZE * 1.5);
|
||||
}
|
||||
|
||||
const doorY = roomPosition.y + TILE_SIZE;
|
||||
return { x: doorX, y: doorY, connectedRoom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Place multiple north doors with even spacing
|
||||
*/
|
||||
function placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
const doorPositions = [];
|
||||
|
||||
// Available width after edge insets
|
||||
const edgeInset = TILE_SIZE * 1.5;
|
||||
const availableWidth = roomWidthPx - (edgeInset * 2);
|
||||
|
||||
// Space between doors
|
||||
const doorCount = connectedRooms.length;
|
||||
const doorSpacing = availableWidth / (doorCount - 1);
|
||||
|
||||
connectedRooms.forEach((connectedRoom, index) => {
|
||||
const doorX = roomPosition.x + edgeInset + (doorSpacing * index);
|
||||
const doorY = roomPosition.y + TILE_SIZE;
|
||||
|
||||
doorPositions.push({
|
||||
x: doorX,
|
||||
y: doorY,
|
||||
connectedRoom
|
||||
});
|
||||
});
|
||||
|
||||
return doorPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a single south door with asymmetric alignment fix
|
||||
*/
|
||||
function placeSouthDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom, gameScenario, allPositions, allDimensions) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
const roomHeightPx = roomDimensions.heightPx;
|
||||
|
||||
// CRITICAL: Check if connected room has multiple north connections
|
||||
const connectedRoomData = gameScenario.rooms[connectedRoom];
|
||||
const connectedNorthConnections = connectedRoomData?.connections?.north;
|
||||
|
||||
if (Array.isArray(connectedNorthConnections) && connectedNorthConnections.length > 1) {
|
||||
// Connected room has multiple north doors - align with the correct one
|
||||
const indexInArray = connectedNorthConnections.indexOf(roomId);
|
||||
|
||||
if (indexInArray >= 0) {
|
||||
const connectedPos = allPositions[connectedRoom];
|
||||
const connectedDim = allDimensions[connectedRoom];
|
||||
|
||||
const edgeInset = TILE_SIZE * 1.5;
|
||||
const availableWidth = connectedDim.widthPx - (edgeInset * 2);
|
||||
const doorCount = connectedNorthConnections.length;
|
||||
const spacing = availableWidth / (doorCount - 1);
|
||||
|
||||
const alignedDoorX = connectedPos.x + edgeInset + (spacing * indexInArray);
|
||||
const doorY = roomPosition.y + roomHeightPx - TILE_SIZE;
|
||||
|
||||
return { x: alignedDoorX, y: doorY, connectedRoom };
|
||||
}
|
||||
}
|
||||
|
||||
// Default: Deterministic placement
|
||||
const gridCoords = worldToGrid(roomPosition.x, roomPosition.y);
|
||||
const sum = gridCoords.gridX + gridCoords.gridY;
|
||||
const useRightSide = ((sum % 2) + 2) % 2 === 1;
|
||||
|
||||
let doorX;
|
||||
if (useRightSide) {
|
||||
doorX = roomPosition.x + roomWidthPx - (TILE_SIZE * 1.5);
|
||||
} else {
|
||||
doorX = roomPosition.x + (TILE_SIZE * 1.5);
|
||||
}
|
||||
|
||||
const doorY = roomPosition.y + roomHeightPx - TILE_SIZE;
|
||||
return { x: doorX, y: doorY, connectedRoom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Place multiple south doors with even spacing
|
||||
*/
|
||||
function placeSouthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
const roomHeightPx = roomDimensions.heightPx;
|
||||
const doorPositions = [];
|
||||
|
||||
const edgeInset = TILE_SIZE * 1.5;
|
||||
const availableWidth = roomWidthPx - (edgeInset * 2);
|
||||
const doorCount = connectedRooms.length;
|
||||
const doorSpacing = availableWidth / (doorCount - 1);
|
||||
|
||||
connectedRooms.forEach((connectedRoom, index) => {
|
||||
const doorX = roomPosition.x + edgeInset + (doorSpacing * index);
|
||||
const doorY = roomPosition.y + roomHeightPx - TILE_SIZE;
|
||||
|
||||
doorPositions.push({
|
||||
x: doorX,
|
||||
y: doorY,
|
||||
connectedRoom
|
||||
});
|
||||
});
|
||||
|
||||
return doorPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a single east door
|
||||
*/
|
||||
function placeEastDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
|
||||
const doorX = roomPosition.x + roomWidthPx - TILE_SIZE;
|
||||
const doorY = roomPosition.y + (TILE_SIZE * 2); // Below visual wall
|
||||
|
||||
return { x: doorX, y: doorY, connectedRoom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Place multiple east doors with vertical spacing
|
||||
*/
|
||||
function placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) {
|
||||
const roomWidthPx = roomDimensions.widthPx;
|
||||
const roomHeightPx = roomDimensions.heightPx;
|
||||
const doorPositions = [];
|
||||
|
||||
const doorX = roomPosition.x + roomWidthPx - TILE_SIZE;
|
||||
|
||||
if (connectedRooms.length === 1) {
|
||||
const doorY = roomPosition.y + (TILE_SIZE * 2);
|
||||
doorPositions.push({ x: doorX, y: doorY, connectedRoom: connectedRooms[0] });
|
||||
} else {
|
||||
// Multiple doors - space vertically
|
||||
const topY = roomPosition.y + (TILE_SIZE * 2);
|
||||
const bottomY = roomPosition.y + roomHeightPx - (TILE_SIZE * 3);
|
||||
const spacing = (bottomY - topY) / (connectedRooms.length - 1);
|
||||
|
||||
connectedRooms.forEach((connectedRoom, index) => {
|
||||
const doorY = topY + (spacing * index);
|
||||
doorPositions.push({ x: doorX, y: doorY, connectedRoom });
|
||||
});
|
||||
}
|
||||
|
||||
return doorPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a single west door
|
||||
*/
|
||||
function placeWestDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom) {
|
||||
const doorX = roomPosition.x + TILE_SIZE;
|
||||
const doorY = roomPosition.y + (TILE_SIZE * 2);
|
||||
|
||||
return { x: doorX, y: doorY, connectedRoom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Place multiple west doors with vertical spacing
|
||||
*/
|
||||
function placeWestDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) {
|
||||
const roomHeightPx = roomDimensions.heightPx;
|
||||
const doorPositions = [];
|
||||
|
||||
const doorX = roomPosition.x + TILE_SIZE;
|
||||
|
||||
if (connectedRooms.length === 1) {
|
||||
const doorY = roomPosition.y + (TILE_SIZE * 2);
|
||||
doorPositions.push({ x: doorX, y: doorY, connectedRoom: connectedRooms[0] });
|
||||
} else {
|
||||
// Multiple doors - space vertically
|
||||
const topY = roomPosition.y + (TILE_SIZE * 2);
|
||||
const bottomY = roomPosition.y + roomHeightPx - (TILE_SIZE * 3);
|
||||
const spacing = (bottomY - topY) / (connectedRooms.length - 1);
|
||||
|
||||
connectedRooms.forEach((connectedRoom, index) => {
|
||||
const doorY = topY + (spacing * index);
|
||||
doorPositions.push({ x: doorX, y: doorY, connectedRoom });
|
||||
});
|
||||
}
|
||||
|
||||
return doorPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate all door positions for a room
|
||||
* This is the main entry point for door placement
|
||||
*/
|
||||
function calculateDoorPositionsForRoom(roomId, roomPosition, roomDimensions, connections, allPositions, allDimensions, gameScenario) {
|
||||
const doorPositions = [];
|
||||
|
||||
['north', 'south', 'east', 'west'].forEach(direction => {
|
||||
const connected = connections[direction];
|
||||
if (!connected) return;
|
||||
|
||||
const connectedRooms = Array.isArray(connected) ? connected : [connected];
|
||||
|
||||
let positions;
|
||||
if (connectedRooms.length === 1) {
|
||||
// Single connection
|
||||
const connectedRoom = connectedRooms[0];
|
||||
let doorPos;
|
||||
|
||||
switch (direction) {
|
||||
case 'north':
|
||||
doorPos = placeNorthDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom, gameScenario, allPositions, allDimensions);
|
||||
break;
|
||||
case 'south':
|
||||
doorPos = placeSouthDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom, gameScenario, allPositions, allDimensions);
|
||||
break;
|
||||
case 'east':
|
||||
doorPos = placeEastDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom);
|
||||
break;
|
||||
case 'west':
|
||||
doorPos = placeWestDoorSingle(roomId, roomPosition, roomDimensions, connectedRoom);
|
||||
break;
|
||||
}
|
||||
|
||||
if (doorPos) {
|
||||
doorPositions.push({ ...doorPos, direction });
|
||||
}
|
||||
} else {
|
||||
// Multiple connections
|
||||
switch (direction) {
|
||||
case 'north':
|
||||
positions = placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms);
|
||||
break;
|
||||
case 'south':
|
||||
positions = placeSouthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms);
|
||||
break;
|
||||
case 'east':
|
||||
positions = placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms);
|
||||
break;
|
||||
case 'west':
|
||||
positions = placeWestDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms);
|
||||
break;
|
||||
}
|
||||
|
||||
if (positions) {
|
||||
positions.forEach(pos => doorPositions.push({ ...pos, direction }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return doorPositions;
|
||||
}
|
||||
|
||||
// Initialize door system
|
||||
export function initializeDoors(gameInstance, roomsRef) {
|
||||
gameRef = gameInstance;
|
||||
@@ -51,267 +368,148 @@ export function createDoorSpritesForRoom(roomId, position) {
|
||||
console.log(`No connections found for room ${roomId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// console.log(`Creating door sprites for room ${roomId}:`, roomData.connections);
|
||||
|
||||
|
||||
const doorSprites = [];
|
||||
const connections = roomData.connections;
|
||||
|
||||
// Get room dimensions for door positioning
|
||||
const map = gameRef.cache.tilemap.get(roomData.type);
|
||||
let roomWidth = 800, roomHeight = 600; // fallback
|
||||
|
||||
if (map) {
|
||||
if (map.json) {
|
||||
roomWidth = map.json.width * TILE_SIZE;
|
||||
roomHeight = map.json.height * TILE_SIZE;
|
||||
} else if (map.data) {
|
||||
roomWidth = map.data.width * TILE_SIZE;
|
||||
roomHeight = map.data.height * TILE_SIZE;
|
||||
|
||||
// Get room dimensions from global cache (set by calculateRoomPositions)
|
||||
const roomDimensions = window.roomDimensions?.[roomId];
|
||||
if (!roomDimensions) {
|
||||
console.error(`Room dimensions not found for ${roomId}. Did calculateRoomPositions run?`);
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`Creating doors for ${roomId} at (${position.x}, ${position.y}), dimensions: ${roomDimensions.widthTiles}×${roomDimensions.heightTiles} tiles`);
|
||||
|
||||
// Get all positions and dimensions for door alignment
|
||||
const allPositions = window.roomPositions || {};
|
||||
const allDimensions = window.roomDimensions || {};
|
||||
|
||||
// Calculate door positions using the new system
|
||||
const doorPositions = calculateDoorPositionsForRoom(
|
||||
roomId,
|
||||
position,
|
||||
roomDimensions,
|
||||
roomData.connections,
|
||||
allPositions,
|
||||
allDimensions,
|
||||
gameScenario
|
||||
);
|
||||
|
||||
console.log(`Calculated ${doorPositions.length} door positions for ${roomId}`);
|
||||
|
||||
// Create door sprites for each calculated position
|
||||
doorPositions.forEach(doorInfo => {
|
||||
const { x: doorX, y: doorY, direction, connectedRoom } = doorInfo;
|
||||
|
||||
// Set door size based on direction
|
||||
let doorWidth = TILE_SIZE;
|
||||
let doorHeight = TILE_SIZE * 2;
|
||||
|
||||
if (direction === 'east' || direction === 'west') {
|
||||
doorWidth = TILE_SIZE * 2;
|
||||
doorHeight = TILE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Room ${roomId} dimensions: ${roomWidth}x${roomHeight}, position: (${position.x}, ${position.y})`);
|
||||
|
||||
// Create door sprites for each connection direction
|
||||
Object.entries(connections).forEach(([direction, connectedRooms]) => {
|
||||
const roomList = Array.isArray(connectedRooms) ? connectedRooms : [connectedRooms];
|
||||
|
||||
roomList.forEach((connectedRoom, index) => {
|
||||
// Calculate door position based on direction
|
||||
let doorX, doorY;
|
||||
let doorWidth = TILE_SIZE, doorHeight = TILE_SIZE * 2;
|
||||
|
||||
switch (direction) {
|
||||
case 'north':
|
||||
// Door at top of room, 1.5 tiles in from sides
|
||||
if (roomList.length === 1) {
|
||||
// Single connection - check the connecting room's connections to determine position
|
||||
const connectingRoom = roomList[0];
|
||||
const connectingRoomConnections = window.gameScenario.rooms[connectingRoom]?.connections?.south;
|
||||
|
||||
if (Array.isArray(connectingRoomConnections) && connectingRoomConnections.length > 1) {
|
||||
// The connecting room has multiple south doors, find which one connects to this room
|
||||
const doorIndex = connectingRoomConnections.indexOf(roomId);
|
||||
if (doorIndex >= 0) {
|
||||
// When the connecting room has multiple doors, position this door to match
|
||||
// If this room is at index 0 (left), position door on the right (southeast)
|
||||
// If this room is at index 1 (right), position door on the left (southwest)
|
||||
if (doorIndex === 0) {
|
||||
// This room is on the left, so door should be on the right
|
||||
doorX = position.x + roomWidth - TILE_SIZE * 1.5;
|
||||
// console.log(`North door positioning for ${roomId}: left room (index 0), door on right (southeast), doorX=${doorX}`);
|
||||
} else {
|
||||
// This room is on the right, so door should be on the left
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`North door positioning for ${roomId}: right room (index ${doorIndex}), door on left (southwest), doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Fallback to left positioning
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`North door positioning for ${roomId}: fallback to left, doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Single door - use left positioning
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`North door positioning for ${roomId}: single connection to ${connectingRoom}, doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Multiple connections - use 1.5 tile spacing from edges
|
||||
const availableWidth = roomWidth - (TILE_SIZE * 1.5 * 2); // Subtract edge spacing
|
||||
const doorSpacing = availableWidth / (roomList.length - 1); // Space between doors
|
||||
doorX = position.x + TILE_SIZE * 1.5 + (doorSpacing * index); // Start at 1.5 tiles from edge
|
||||
}
|
||||
doorY = position.y + TILE_SIZE; // 1 tile from top
|
||||
// console.log(`North door Y position: ${doorY} (position.y=${position.y}, TILE_SIZE=${TILE_SIZE})`);
|
||||
break;
|
||||
case 'south':
|
||||
// Door at bottom of room, 1.5 tiles in from sides
|
||||
if (roomList.length === 1) {
|
||||
// Single connection - check if the connecting room has multiple doors
|
||||
const connectingRoom = roomList[0];
|
||||
const connectingRoomConnections = window.gameScenario.rooms[connectingRoom]?.connections?.north;
|
||||
if (Array.isArray(connectingRoomConnections) && connectingRoomConnections.length > 1) {
|
||||
// The connecting room has multiple north doors, find which one connects to this room
|
||||
const doorIndex = connectingRoomConnections.indexOf(roomId);
|
||||
if (doorIndex >= 0) {
|
||||
// When the connecting room has multiple doors, position this door to match
|
||||
// If this room is at index 0 (left), position door on the right (southeast)
|
||||
// If this room is at index 1 (right), position door on the left (southwest)
|
||||
if (doorIndex === 0) {
|
||||
// This room is on the left, so door should be on the right
|
||||
doorX = position.x + roomWidth - TILE_SIZE * 1.5;
|
||||
// console.log(`South door positioning for ${roomId}: left room (index 0), door on right (southeast), doorX=${doorX}`);
|
||||
} else {
|
||||
// This room is on the right, so door should be on the left
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`South door positioning for ${roomId}: right room (index ${doorIndex}), door on left (southwest), doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Fallback to left positioning
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`South door positioning for ${roomId}: fallback to left, doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Single door - use left positioning
|
||||
doorX = position.x + TILE_SIZE * 1.5;
|
||||
// console.log(`South door positioning for ${roomId}: single connection to ${connectingRoom}, doorX=${doorX}`);
|
||||
}
|
||||
} else {
|
||||
// Multiple connections - use 1.5 tile spacing from edges
|
||||
const availableWidth = roomWidth - (TILE_SIZE * 1.5 * 2); // Subtract edge spacing
|
||||
const doorSpacing = availableWidth / (roomList.length - 1); // Space between doors
|
||||
doorX = position.x + TILE_SIZE * 1.5 + (doorSpacing * index); // Start at 1.5 tiles from edge
|
||||
}
|
||||
doorY = position.y + roomHeight - TILE_SIZE; // 1 tile from bottom
|
||||
// replace the bottom most tile with a copy of the tile above
|
||||
|
||||
|
||||
break;
|
||||
case 'east':
|
||||
// Door at right side of room, 1 tile in from top/bottom
|
||||
doorX = position.x + roomWidth - TILE_SIZE; // 1 tile from right
|
||||
doorY = position.y + roomHeight / 2; // Center of room
|
||||
doorWidth = TILE_SIZE * 2;
|
||||
doorHeight = TILE_SIZE;
|
||||
break;
|
||||
case 'west':
|
||||
// Door at left side of room, 1 tile in from top/bottom
|
||||
doorX = position.x + TILE_SIZE; // 1 tile from left
|
||||
doorY = position.y + roomHeight / 2; // Center of room
|
||||
doorWidth = TILE_SIZE * 2;
|
||||
doorHeight = TILE_SIZE;
|
||||
break;
|
||||
default:
|
||||
return; // Skip unknown directions
|
||||
}
|
||||
|
||||
// Create door sprite
|
||||
// console.log(`Creating door sprite at (${doorX}, ${doorY}) for ${roomId} -> ${connectedRoom}`);
|
||||
|
||||
// Create a colored rectangle as a fallback if door texture fails
|
||||
let doorSprite;
|
||||
try {
|
||||
doorSprite = gameRef.add.sprite(doorX, doorY, 'door_32');
|
||||
} catch (error) {
|
||||
console.warn(`Failed to create door sprite with 'door_32' texture, creating colored rectangle instead:`, error);
|
||||
// Create a colored rectangle as fallback
|
||||
const graphics = gameRef.add.graphics();
|
||||
graphics.fillStyle(0xff0000, 1); // Red color
|
||||
graphics.fillRect(-TILE_SIZE/2, -TILE_SIZE, TILE_SIZE, TILE_SIZE * 2);
|
||||
graphics.setPosition(doorX, doorY);
|
||||
doorSprite = graphics;
|
||||
}
|
||||
doorSprite.setOrigin(0.5, 0.5);
|
||||
doorSprite.setDepth(doorY + 0.45); // World Y + door layer offset
|
||||
doorSprite.setAlpha(1); // Visible by default
|
||||
doorSprite.setVisible(true); // Ensure visibility
|
||||
|
||||
// console.log(`Door sprite created:`, {
|
||||
// x: doorSprite.x,
|
||||
// y: doorSprite.y,
|
||||
// visible: doorSprite.visible,
|
||||
// alpha: doorSprite.alpha,
|
||||
// depth: doorSprite.depth,
|
||||
// texture: doorSprite.texture?.key,
|
||||
// width: doorSprite.width,
|
||||
// height: doorSprite.height,
|
||||
// displayWidth: doorSprite.displayWidth,
|
||||
// displayHeight: doorSprite.displayHeight
|
||||
// });
|
||||
// console.log(`Door depth: ${doorSprite.depth} (roomDepth: ${doorY}, between tiles and sprites)`);
|
||||
|
||||
// Get lock properties from either the door object or the destination room
|
||||
// First check if this door has explicit lock properties in the scenario
|
||||
const doorDefinition = roomData.doors?.find(d =>
|
||||
d.connectedRoom === connectedRoom && d.direction === direction
|
||||
);
|
||||
|
||||
// Lock properties can come from the door definition or the connected room
|
||||
const lockProps = doorDefinition || {};
|
||||
const connectedRoomData = gameScenario.rooms[connectedRoom];
|
||||
|
||||
// Check for both keyPins (camelCase) and key_pins (snake_case) in the room data
|
||||
const keyPinsArray = lockProps.keyPins || lockProps.key_pins ||
|
||||
connectedRoomData?.keyPins || connectedRoomData?.key_pins;
|
||||
|
||||
// DEBUG: Log what we're finding
|
||||
if (connectedRoomData?.locked) {
|
||||
console.log(`🔍 Door keyPins lookup for ${connectedRoom}:`, {
|
||||
connectedRoomData_keyPins: connectedRoomData?.keyPins,
|
||||
connectedRoomData_key_pins: connectedRoomData?.key_pins,
|
||||
finalKeyPinsArray: keyPinsArray,
|
||||
locked: connectedRoomData?.locked,
|
||||
lockType: connectedRoomData?.lockType,
|
||||
requires: connectedRoomData?.requires
|
||||
});
|
||||
}
|
||||
|
||||
// Set up door properties
|
||||
doorSprite.doorProperties = {
|
||||
roomId: roomId,
|
||||
connectedRoom: connectedRoom,
|
||||
direction: direction,
|
||||
worldX: doorX,
|
||||
worldY: doorY,
|
||||
open: false,
|
||||
locked: lockProps.locked !== undefined ? lockProps.locked : (connectedRoomData?.locked || false),
|
||||
lockType: lockProps.lockType || connectedRoomData?.lockType || null,
|
||||
requires: lockProps.requires || connectedRoomData?.requires || null,
|
||||
keyPins: keyPinsArray, // Include keyPins from scenario (supports both cases)
|
||||
difficulty: lockProps.difficulty || connectedRoomData?.difficulty // Include difficulty from scenario
|
||||
};
|
||||
|
||||
// Debug door properties
|
||||
console.log(`🚪 Door properties set for ${roomId} -> ${connectedRoom}:`, {
|
||||
locked: doorSprite.doorProperties.locked,
|
||||
lockType: doorSprite.doorProperties.lockType,
|
||||
requires: doorSprite.doorProperties.requires,
|
||||
keyPins: doorSprite.doorProperties.keyPins,
|
||||
difficulty: doorSprite.doorProperties.difficulty
|
||||
|
||||
console.log(`Creating door sprite at (${doorX}, ${doorY}) for ${roomId} -> ${connectedRoom} (${direction})`);
|
||||
|
||||
// Create a colored rectangle as a fallback if door texture fails
|
||||
let doorSprite;
|
||||
try {
|
||||
doorSprite = gameRef.add.sprite(doorX, doorY, 'door_32');
|
||||
} catch (error) {
|
||||
console.warn(`Failed to create door sprite with 'door_32' texture, creating colored rectangle instead:`, error);
|
||||
// Create a colored rectangle as fallback
|
||||
const graphics = gameRef.add.graphics();
|
||||
graphics.fillStyle(0xff0000, 1); // Red color
|
||||
graphics.fillRect(-TILE_SIZE/2, -TILE_SIZE, TILE_SIZE, TILE_SIZE * 2);
|
||||
graphics.setPosition(doorX, doorY);
|
||||
doorSprite = graphics;
|
||||
}
|
||||
doorSprite.setOrigin(0.5, 0.5);
|
||||
doorSprite.setDepth(doorY + 0.45); // World Y + door layer offset
|
||||
doorSprite.setAlpha(1); // Visible by default
|
||||
doorSprite.setVisible(true); // Ensure visibility
|
||||
|
||||
// Get lock properties from either the door object or the destination room
|
||||
// First check if this door has explicit lock properties in the scenario
|
||||
const doorDefinition = roomData.doors?.find(d =>
|
||||
d.connectedRoom === connectedRoom && d.direction === direction
|
||||
);
|
||||
|
||||
// Lock properties can come from the door definition or the connected room
|
||||
const lockProps = doorDefinition || {};
|
||||
const connectedRoomData = gameScenario.rooms[connectedRoom];
|
||||
|
||||
// Check for both keyPins (camelCase) and key_pins (snake_case) in the room data
|
||||
const keyPinsArray = lockProps.keyPins || lockProps.key_pins ||
|
||||
connectedRoomData?.keyPins || connectedRoomData?.key_pins;
|
||||
|
||||
// DEBUG: Log what we're finding
|
||||
if (connectedRoomData?.locked) {
|
||||
console.log(`🔍 Door keyPins lookup for ${connectedRoom}:`, {
|
||||
connectedRoomData_keyPins: connectedRoomData?.keyPins,
|
||||
connectedRoomData_key_pins: connectedRoomData?.key_pins,
|
||||
finalKeyPinsArray: keyPinsArray,
|
||||
locked: connectedRoomData?.locked,
|
||||
lockType: connectedRoomData?.lockType,
|
||||
requires: connectedRoomData?.requires
|
||||
});
|
||||
|
||||
// Set up door info for transition detection
|
||||
doorSprite.doorInfo = {
|
||||
roomId: roomId,
|
||||
connectedRoom: connectedRoom,
|
||||
direction: direction
|
||||
};
|
||||
|
||||
// Set up collision
|
||||
gameRef.physics.add.existing(doorSprite);
|
||||
doorSprite.body.setSize(doorWidth, doorHeight);
|
||||
doorSprite.body.setImmovable(true);
|
||||
|
||||
// Add collision with player
|
||||
if (window.player && window.player.body) {
|
||||
gameRef.physics.add.collider(window.player, doorSprite);
|
||||
}
|
||||
|
||||
// Set up interaction zone
|
||||
const zone = gameRef.add.zone(doorX, doorY, doorWidth, doorHeight);
|
||||
zone.setInteractive({ useHandCursor: true });
|
||||
zone.on('pointerdown', () => handleDoorInteraction(doorSprite));
|
||||
|
||||
doorSprite.interactionZone = zone;
|
||||
doorSprites.push(doorSprite);
|
||||
|
||||
// console.log(`Created door sprite for ${roomId} -> ${connectedRoom} (${direction}) at (${doorX}, ${doorY})`);
|
||||
}
|
||||
|
||||
// Set up door properties
|
||||
doorSprite.doorProperties = {
|
||||
roomId: roomId,
|
||||
connectedRoom: connectedRoom,
|
||||
direction: direction,
|
||||
worldX: doorX,
|
||||
worldY: doorY,
|
||||
open: false,
|
||||
locked: lockProps.locked !== undefined ? lockProps.locked : (connectedRoomData?.locked || false),
|
||||
lockType: lockProps.lockType || connectedRoomData?.lockType || null,
|
||||
requires: lockProps.requires || connectedRoomData?.requires || null,
|
||||
keyPins: keyPinsArray, // Include keyPins from scenario (supports both cases)
|
||||
difficulty: lockProps.difficulty || connectedRoomData?.difficulty // Include difficulty from scenario
|
||||
};
|
||||
|
||||
// Debug door properties
|
||||
console.log(`🚪 Door properties set for ${roomId} -> ${connectedRoom}:`, {
|
||||
locked: doorSprite.doorProperties.locked,
|
||||
lockType: doorSprite.doorProperties.lockType,
|
||||
requires: doorSprite.doorProperties.requires,
|
||||
keyPins: doorSprite.doorProperties.keyPins,
|
||||
difficulty: doorSprite.doorProperties.difficulty
|
||||
});
|
||||
|
||||
// Set up door info for transition detection
|
||||
doorSprite.doorInfo = {
|
||||
roomId: roomId,
|
||||
connectedRoom: connectedRoom,
|
||||
direction: direction
|
||||
};
|
||||
|
||||
// Set up collision
|
||||
gameRef.physics.add.existing(doorSprite);
|
||||
doorSprite.body.setSize(doorWidth, doorHeight);
|
||||
doorSprite.body.setImmovable(true);
|
||||
|
||||
// Add collision with player
|
||||
if (window.player && window.player.body) {
|
||||
gameRef.physics.add.collider(window.player, doorSprite);
|
||||
}
|
||||
|
||||
// Set up interaction zone
|
||||
const zone = gameRef.add.zone(doorX, doorY, doorWidth, doorHeight);
|
||||
zone.setInteractive({ useHandCursor: true });
|
||||
zone.on('pointerdown', () => handleDoorInteraction(doorSprite));
|
||||
|
||||
doorSprite.interactionZone = zone;
|
||||
doorSprites.push(doorSprite);
|
||||
|
||||
console.log(`Created door sprite for ${roomId} -> ${connectedRoom} (${direction}) at (${doorX}, ${doorY})`);
|
||||
});
|
||||
|
||||
// console.log(`Created ${doorSprites.length} door sprites for room ${roomId}`);
|
||||
|
||||
// Log camera position for debugging
|
||||
if (gameRef.cameras && gameRef.cameras.main) {
|
||||
console.log(`Camera position:`, {
|
||||
x: gameRef.cameras.main.scrollX,
|
||||
y: gameRef.cameras.main.scrollY,
|
||||
width: gameRef.cameras.main.width,
|
||||
height: gameRef.cameras.main.height
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log(`Created ${doorSprites.length} door sprites for room ${roomId}`);
|
||||
|
||||
return doorSprites;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
export const TILE_SIZE = 32;
|
||||
export const DOOR_ALIGN_OVERLAP = 32 * 3;
|
||||
export const GRID_SIZE = 32;
|
||||
|
||||
// Grid unit system constants for room layout
|
||||
// Grid units are the base stacking size for rooms
|
||||
export const GRID_UNIT_WIDTH_TILES = 5; // 5 tiles wide
|
||||
export const GRID_UNIT_HEIGHT_TILES = 4; // 4 tiles tall (stacking area)
|
||||
export const VISUAL_TOP_TILES = 2; // Top 2 rows are visual wall overlay
|
||||
|
||||
// Calculated grid unit sizes in pixels
|
||||
export const GRID_UNIT_WIDTH_PX = GRID_UNIT_WIDTH_TILES * TILE_SIZE; // 160px
|
||||
export const GRID_UNIT_HEIGHT_PX = GRID_UNIT_HEIGHT_TILES * TILE_SIZE; // 128px
|
||||
|
||||
export const MOVEMENT_SPEED = 150;
|
||||
export const RUN_SPEED_MULTIPLIER = 1.5; // Speed multiplier when holding shift
|
||||
export const RUN_ANIMATION_MULTIPLIER = 1.5; // Animation speed multiplier when holding shift
|
||||
|
||||
Reference in New Issue
Block a user