Files
BreakEscape/js/systems/collision.js

624 lines
29 KiB
JavaScript

/**
* COLLISION MANAGEMENT SYSTEM
* ===========================
*
* Handles static collision geometry, tile-based collision, and wall management.
* Separated from rooms.js for better modularity and maintainability.
*/
import { TILE_SIZE } from '../utils/constants.js';
import { getOppositeDirection } from './doors.js';
let gameRef = null;
let rooms = null;
// Initialize collision system
export function initializeCollision(gameInstance, roomsRef) {
gameRef = gameInstance;
rooms = roomsRef;
}
// Function to create thin collision boxes for wall tiles
export function createWallCollisionBoxes(wallLayer, roomId, position) {
console.log(`Creating wall collision boxes for room ${roomId}`);
// Use window.rooms to ensure we see the latest state
const room = window.rooms ? window.rooms[roomId] : null;
if (!room) {
console.error(`Room ${roomId} not found in window.rooms, cannot create collision boxes`);
return;
}
// Ensure we have a valid game reference
const game = gameRef || window.game;
if (!game) {
console.error('No game reference available, cannot create collision boxes');
return;
}
// Get room dimensions from the map
const map = room.map;
const roomWidth = map.widthInPixels;
const roomHeight = map.heightInPixels;
console.log(`Room ${roomId} dimensions: ${roomWidth}x${roomHeight} at position (${position.x}, ${position.y})`);
const collisionBoxes = [];
// Get all wall tiles from the layer
const wallTiles = wallLayer.getTilesWithin(0, 0, map.width, map.height, { isNotEmpty: true });
wallTiles.forEach(tile => {
const tileX = tile.x;
const tileY = tile.y;
const worldX = position.x + (tileX * TILE_SIZE);
const worldY = position.y + (tileY * TILE_SIZE);
// Create collision boxes for all applicable edges (not just one)
const tileCollisionBoxes = [];
// North wall (top 2 rows) - collision on south edge
if (tileY < 2) {
const collisionBox = game.add.rectangle(
worldX + TILE_SIZE / 2,
worldY + TILE_SIZE - 4, // 4px from south edge
TILE_SIZE,
8, // Thicker collision box
0x000000,
0 // Invisible
);
tileCollisionBoxes.push(collisionBox);
}
// South wall (bottom row) - collision on south edge
if (tileY === map.height - 1) {
const collisionBox = game.add.rectangle(
worldX + TILE_SIZE / 2,
worldY + TILE_SIZE - 4, // 4px from south edge
TILE_SIZE,
8, // Thicker collision box
0x000000,
0 // Invisible
);
tileCollisionBoxes.push(collisionBox);
}
// West wall (left column) - collision on east edge
if (tileX === 0) {
const collisionBox = game.add.rectangle(
worldX + TILE_SIZE - 4, // 4px from east edge
worldY + TILE_SIZE / 2,
8, // Thicker collision box
TILE_SIZE,
0x000000,
0 // Invisible
);
tileCollisionBoxes.push(collisionBox);
}
// East wall (right column) - collision on west edge
if (tileX === map.width - 1) {
const collisionBox = game.add.rectangle(
worldX + 4, // 4px from west edge
worldY + TILE_SIZE / 2,
8, // Thicker collision box
TILE_SIZE,
0x000000,
0 // Invisible
);
tileCollisionBoxes.push(collisionBox);
}
// Set up all collision boxes for this tile
tileCollisionBoxes.forEach(collisionBox => {
collisionBox.setVisible(false);
game.physics.add.existing(collisionBox, true);
// Wait for the next frame to ensure body is fully initialized
game.time.delayedCall(0, () => {
if (collisionBox.body) {
// Use direct property assignment (fallback method)
collisionBox.body.immovable = true;
}
});
collisionBoxes.push(collisionBox);
});
});
console.log(`Created ${collisionBoxes.length} wall collision boxes for room ${roomId}`);
// Add collision with player for all collision boxes
const player = window.player;
if (player && player.body) {
collisionBoxes.forEach(collisionBox => {
game.physics.add.collider(player, collisionBox);
});
console.log(`Added ${collisionBoxes.length} wall collision boxes for room ${roomId} with player collision`);
} else {
console.warn(`Player not ready for room ${roomId}, storing ${collisionBoxes.length} collision boxes for later`);
if (!room.pendingWallCollisionBoxes) {
room.pendingWallCollisionBoxes = [];
}
room.pendingWallCollisionBoxes.push(...collisionBoxes);
}
// Store collision boxes in room for cleanup
if (!room.wallCollisionBoxes) {
room.wallCollisionBoxes = [];
}
room.wallCollisionBoxes.push(...collisionBoxes);
}
// Function to remove wall tiles under doors
export function removeTilesUnderDoor(wallLayer, roomId, position) {
console.log(`Removing wall tiles under doors in room ${roomId}`);
// Remove wall tiles under doors using the same positioning logic as door sprites
const gameScenario = window.gameScenario;
const roomData = gameScenario.rooms[roomId];
if (!roomData || !roomData.connections) {
console.log(`No connections found for room ${roomId}, skipping wall tile removal`);
return;
}
// Ensure we have a valid game reference
const game = gameRef || window.game;
if (!game) {
console.error('No game reference available, cannot remove tiles under door');
return;
}
// Get room dimensions for door positioning (same as door sprite creation)
const map = game.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;
}
}
const connections = roomData.connections;
// Process each connection direction
Object.entries(connections).forEach(([direction, connectedRooms]) => {
const roomList = Array.isArray(connectedRooms) ? connectedRooms : [connectedRooms];
roomList.forEach((connectedRoom, index) => {
// Calculate door position using the same logic as door sprite creation
let doorX, doorY;
let doorWidth = TILE_SIZE, doorHeight = TILE_SIZE * 2;
switch (direction) {
case 'north':
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;
} else {
// This room is on the right, so door should be on the left
doorX = position.x + TILE_SIZE * 1.5;
}
} else {
// Fallback to left positioning
doorX = position.x + TILE_SIZE * 1.5;
}
} else {
// Single door - use left positioning
doorX = position.x + TILE_SIZE * 1.5;
}
} 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;
break;
case 'south':
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;
} else {
// This room is on the right, so door should be on the left
doorX = position.x + TILE_SIZE * 1.5;
}
} else {
// Fallback to left positioning
doorX = position.x + TILE_SIZE * 1.5;
}
} else {
// Single door - use left positioning
doorX = position.x + TILE_SIZE * 1.5;
}
} 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;
break;
case 'east':
doorX = position.x + roomWidth - TILE_SIZE;
doorY = position.y + roomHeight / 2;
doorWidth = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
break;
case 'west':
doorX = position.x + TILE_SIZE;
doorY = position.y + roomHeight / 2;
doorWidth = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
break;
default:
return;
}
// Use Phaser's getTilesWithin to get tiles that overlap with the door area
const doorBounds = {
x: doorX - (doorWidth / 2), // Door sprite origin is center, so adjust bounds
y: doorY - (doorHeight / 2),
width: doorWidth,
height: doorHeight
};
// Convert door bounds to tilemap coordinates (relative to the layer)
const doorBoundsInTilemap = {
x: doorBounds.x - wallLayer.x,
y: doorBounds.y - wallLayer.y,
width: doorBounds.width,
height: doorBounds.height
};
console.log(`Removing wall tiles for ${roomId} -> ${connectedRoom} (${direction}): door at (${doorX}, ${doorY}), world bounds:`, doorBounds, `tilemap bounds:`, doorBoundsInTilemap);
console.log(`Wall layer info: x=${wallLayer.x}, y=${wallLayer.y}, width=${wallLayer.width}, height=${wallLayer.height}`);
// Try a different approach - convert to tile coordinates first
const doorTileX = Math.floor(doorBoundsInTilemap.x / TILE_SIZE);
const doorTileY = Math.floor(doorBoundsInTilemap.y / TILE_SIZE);
const doorTilesWide = Math.ceil(doorBoundsInTilemap.width / TILE_SIZE);
const doorTilesHigh = Math.ceil(doorBoundsInTilemap.height / TILE_SIZE);
console.log(`Door tile coordinates: (${doorTileX}, ${doorTileY}) covering ${doorTilesWide}x${doorTilesHigh} tiles`);
// Check what tiles exist in the door area manually
let foundTiles = [];
for (let x = 0; x < doorTilesWide; x++) {
for (let y = 0; y < doorTilesHigh; y++) {
const tileX = doorTileX + x;
const tileY = doorTileY + y;
const tile = wallLayer.getTileAt(tileX, tileY);
if (tile && tile.index !== -1) {
foundTiles.push({x: tileX, y: tileY, tile: tile});
console.log(`Found wall tile at (${tileX}, ${tileY}) with index ${tile.index}`);
}
}
}
console.log(`Manually found ${foundTiles.length} wall tiles in door area`);
// Get all tiles within the door bounds (using tilemap coordinates)
const overlappingTiles = wallLayer.getTilesWithin(
doorBoundsInTilemap.x,
doorBoundsInTilemap.y,
doorBoundsInTilemap.width,
doorBoundsInTilemap.height
);
console.log(`getTilesWithin found ${overlappingTiles.length} tiles overlapping with door area`);
// Use the manually found tiles if getTilesWithin didn't work
const tilesToRemove = foundTiles.length > 0 ? foundTiles : overlappingTiles;
// Remove wall tiles that overlap with the door
tilesToRemove.forEach(tileData => {
const tileX = tileData.x;
const tileY = tileData.y;
// Remove the wall tile
const removedTile = wallLayer.tilemap.removeTileAt(
tileX,
tileY,
true, // replaceWithNull
true, // recalculateFaces
wallLayer // layer
);
if (removedTile) {
console.log(`Removed wall tile at (${tileX}, ${tileY}) under door ${roomId} -> ${connectedRoom}`);
}
});
// Recalculate collision after removing tiles
if (tilesToRemove.length > 0) {
console.log(`Recalculating collision for wall layer in ${roomId} after removing ${tilesToRemove.length} tiles`);
wallLayer.setCollisionByExclusion([-1]);
}
});
});
}
// Function to remove wall tiles from a specific room for a door connection
export function removeWallTilesForDoorInRoom(roomId, fromRoomId, direction, doorWorldX, doorWorldY) {
console.log(`Removing wall tiles in room ${roomId} for door from ${fromRoomId} (${direction}) at world position (${doorWorldX}, ${doorWorldY})`);
// Use window.rooms to ensure we see the latest state
const room = window.rooms ? window.rooms[roomId] : null;
if (!room || !room.wallsLayers || room.wallsLayers.length === 0) {
console.log(`No wall layers found for room ${roomId}`);
return;
}
// Calculate the door position in the connected room
// The door should be on the opposite side of the connection
const oppositeDirection = getOppositeDirection(direction);
const roomPosition = window.roomPositions[roomId];
const roomData = window.gameScenario.rooms[roomId];
if (!roomPosition || !roomData) {
console.log(`Missing position or data for room ${roomId}`);
return;
}
// Get room dimensions
const roomWidth = roomData.width || 320;
const roomHeight = roomData.height || 288;
// Calculate door position in the connected room based on the opposite direction
let doorX, doorY, doorWidth, doorHeight;
// Calculate door position based on the room's door configuration
if (direction === 'north' || direction === 'south') {
// For north/south connections, calculate X position based on room configuration
const oppositeDirection = getOppositeDirection(direction);
const connections = roomData.connections?.[oppositeDirection];
if (Array.isArray(connections)) {
// Multiple doors - find the one that connects to fromRoomId
const doorIndex = connections.indexOf(fromRoomId);
if (doorIndex >= 0) {
const totalDoors = connections.length;
const availableWidth = roomWidth - (TILE_SIZE * 3); // 1.5 tiles from each edge
const doorSpacing = totalDoors > 1 ? availableWidth / (totalDoors - 1) : 0;
doorX = roomPosition.x + TILE_SIZE * 1.5 + (doorIndex * doorSpacing);
} else {
doorX = roomPosition.x + roomWidth / 2; // Default to center
}
} else {
// Single door - check if the connecting room has multiple doors
const connectingRoomConnections = window.gameScenario.rooms[fromRoomId]?.connections?.[direction];
if (Array.isArray(connectingRoomConnections) && connectingRoomConnections.length > 1) {
// The connecting room has multiple 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 = roomPosition.x + roomWidth - TILE_SIZE * 1.5;
console.log(`Wall tile removal door positioning for ${roomId}: left room (index 0), door on right (southeast), calculated doorX=${doorX}`);
} else {
// This room is on the right, so door should be on the left
doorX = roomPosition.x + TILE_SIZE * 1.5;
console.log(`Wall tile removal door positioning for ${roomId}: right room (index ${doorIndex}), door on left (southwest), calculated doorX=${doorX}`);
}
} else {
// Fallback to left positioning
doorX = roomPosition.x + TILE_SIZE * 1.5;
console.log(`Wall tile removal door positioning for ${roomId}: fallback to left, calculated doorX=${doorX}`);
}
} else {
// Single door - use left positioning
doorX = roomPosition.x + TILE_SIZE * 1.5;
console.log(`Wall tile removal door positioning for ${roomId}: single connection to ${fromRoomId}, calculated doorX=${doorX}`);
}
}
if (direction === 'north') {
// Original door is north, so new door should be south
doorY = roomPosition.y + roomHeight - TILE_SIZE;
} else {
// Original door is south, so new door should be north
doorY = roomPosition.y + TILE_SIZE;
}
doorWidth = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
} else if (direction === 'east' || direction === 'west') {
// For east/west connections, calculate Y position based on room configuration
doorY = roomPosition.y + roomHeight / 2; // Center of room
if (direction === 'east') {
// Original door is east, so new door should be west
doorX = roomPosition.x + TILE_SIZE;
} else {
// Original door is west, so new door should be east
doorX = roomPosition.x + roomWidth - TILE_SIZE;
}
doorWidth = TILE_SIZE;
doorHeight = TILE_SIZE * 2;
} else {
console.log(`Unknown direction: ${direction}`);
return;
}
// For debugging: Calculate what the door position should be based on room dimensions
const expectedSouthDoorY = roomPosition.y + roomHeight - TILE_SIZE;
const expectedNorthDoorY = roomPosition.y + TILE_SIZE;
console.log(`Expected door positions for ${roomId}: north=${expectedNorthDoorY}, south=${expectedSouthDoorY}`);
// Debug: Log the room position and calculated door position
console.log(`Room ${roomId} position: (${roomPosition.x}, ${roomPosition.y}), dimensions: ${roomWidth}x${roomHeight}`);
console.log(`Original door at (${doorWorldX}, ${doorWorldY}), calculated door at (${doorX}, ${doorY})`);
console.log(`Direction: ${direction}, oppositeDirection: ${getOppositeDirection(direction)}`);
console.log(`Room connections:`, roomData.connections);
console.log(`Calculated door position in ${roomId}: (${doorX}, ${doorY}) for ${oppositeDirection} connection`);
// Remove wall tiles from all wall layers in this room
room.wallsLayers.forEach(wallLayer => {
// Calculate door bounds
// For north/south doors, the door sprite origin is at the center, but we need to adjust for the actual door position
let doorBounds;
if (oppositeDirection === 'north' || oppositeDirection === 'south') {
// For north/south doors, the door should cover the full width and be positioned at the edge
doorBounds = {
x: doorX - (doorWidth / 2),
y: doorY, // Don't subtract half height - the door is positioned at the edge
width: doorWidth,
height: doorHeight
};
} else {
// For east/west doors, use center positioning
doorBounds = {
x: doorX - (doorWidth / 2),
y: doorY - (doorHeight / 2),
width: doorWidth,
height: doorHeight
};
}
// For debugging: Show the door sprite dimensions and bounds
console.log(`Door sprite at (${doorX}, ${doorY}) with dimensions ${doorWidth}x${doorHeight}`);
console.log(`Door bounds: x=${doorBounds.x}, y=${doorBounds.y}, width=${doorBounds.width}, height=${doorBounds.height}`);
// Convert door bounds to tilemap coordinates
const doorBoundsInTilemap = {
x: doorBounds.x - wallLayer.x,
y: doorBounds.y - wallLayer.y,
width: doorBounds.width,
height: doorBounds.height
};
console.log(`Removing wall tiles in ${roomId} for ${oppositeDirection} door: world bounds:`, doorBounds, `tilemap bounds:`, doorBoundsInTilemap);
console.log(`Wall layer position: (${wallLayer.x}, ${wallLayer.y}), size: ${wallLayer.width}x${wallLayer.height}`);
console.log(`Room position: (${roomPosition.x}, ${roomPosition.y}), door position: (${doorX}, ${doorY})`);
// Convert to tile coordinates
const doorTileX = Math.floor(doorBoundsInTilemap.x / TILE_SIZE);
const doorTileY = Math.floor(doorBoundsInTilemap.y / TILE_SIZE);
const doorTilesWide = Math.ceil(doorBoundsInTilemap.width / TILE_SIZE);
const doorTilesHigh = Math.ceil(doorBoundsInTilemap.height / TILE_SIZE);
console.log(`Expected tile Y: ${Math.floor((doorY - roomPosition.y) / TILE_SIZE)}, actual tile Y: ${doorTileY}`);
console.log(`Door tile coordinates in ${roomId}: (${doorTileX}, ${doorTileY}) covering ${doorTilesWide}x${doorTilesHigh} tiles`);
// Check what tiles exist in the door area manually
let foundTiles = [];
for (let x = 0; x < doorTilesWide; x++) {
for (let y = 0; y < doorTilesHigh; y++) {
const tileX = doorTileX + x;
const tileY = doorTileY + y;
const tile = wallLayer.getTileAt(tileX, tileY);
if (tile && tile.index !== -1) {
foundTiles.push({x: tileX, y: tileY, tile: tile});
console.log(`Found wall tile at (${tileX}, ${tileY}) with index ${tile.index} in ${roomId}`);
}
}
}
console.log(`Manually found ${foundTiles.length} wall tiles in door area in ${roomId}`);
// Remove wall tiles that overlap with the door
foundTiles.forEach(tileData => {
const tileX = tileData.x;
const tileY = tileData.y;
// Remove the wall tile
const removedTile = wallLayer.tilemap.removeTileAt(
tileX,
tileY,
true, // replaceWithNull
true, // recalculateFaces
wallLayer // layer
);
if (removedTile) {
console.log(`Removed wall tile at (${tileX}, ${tileY}) under door in ${roomId}`);
}
});
// Recalculate collision after removing tiles
if (foundTiles.length > 0) {
console.log(`Recalculating collision for wall layer in ${roomId} after removing ${foundTiles.length} tiles`);
wallLayer.setCollisionByExclusion([-1]);
}
});
}
// Function to remove wall tiles from all overlapping room layers at a world position
export function removeWallTilesAtWorldPosition(worldX, worldY, debugInfo = '') {
console.log(`Removing wall tiles at world position (${worldX}, ${worldY}) - ${debugInfo}`);
// Find all rooms and their wall layers that could contain this world position
Object.entries(rooms).forEach(([roomId, room]) => {
if (!room.wallsLayers || room.wallsLayers.length === 0) return;
room.wallsLayers.forEach(wallLayer => {
try {
// Convert world coordinates to tile coordinates for this layer
const tileX = Math.floor((worldX - room.position.x) / TILE_SIZE);
const tileY = Math.floor((worldY - room.position.y) / TILE_SIZE);
// Check if the tile coordinates are within the layer bounds
const wallTile = wallLayer.getTileAt(tileX, tileY);
if (wallTile && wallTile.index !== -1) {
// Remove the wall tile using the map's removeTileAt method
const removedTile = room.map.removeTileAt(
tileX,
tileY,
true, // replaceWithNull
true, // recalculateFaces
wallLayer // layer
);
if (removedTile) {
console.log(` Removed wall tile at (${tileX},${tileY}) from room ${roomId} layer ${wallLayer.name}`);
}
} else {
console.log(` No wall tile found at (${tileX},${tileY}) in room ${roomId} layer ${wallLayer.name || 'unnamed'}`);
}
} catch (error) {
console.warn(`Error removing wall tile from room ${roomId}:`, error);
}
});
});
}
// Export for global access
window.createWallCollisionBoxes = createWallCollisionBoxes;
window.removeTilesUnderDoor = removeTilesUnderDoor;
window.removeWallTilesForDoorInRoom = removeWallTilesForDoorInRoom;
window.removeWallTilesAtWorldPosition = removeWallTilesAtWorldPosition;