User prompt
Has que la generación de los pasillos sea menos frecuente y si se generan sean más cortos
User prompt
Please fix the bug: 'self.createNarrowCorridorAreas is not a function. (In 'self.createNarrowCorridorAreas(offsetX, offsetY, rooms)', 'self.createNarrowCorridorAreas' is undefined)' in or related to this line: 'self.createNarrowCorridorAreas(offsetX, offsetY, rooms);' Line Number: 802
User prompt
Aparte de las habitaciones has que el sistema procedural pueda crear áreas de pasillos estrechos lo suficientemente grandes para que el personaje pueda pasar
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Has que el sistema procedural cree las habitaciones simpre con múltiples salidas y con múltiples callejones sin salida
User prompt
Has un sistema de reconocimiento de pasajes si una habitación no tiene salida crea una que se haga antes que el jugador pase por ese lugar
User prompt
Corrige el sistema procedural para que siempre esté conectada por lo menos con 1 habitación y que la siguiente tenga lo mismo
User prompt
Crea un sistema de reconocimiento de áreas sin salida si una habitación no tiene salida se considera pared
User prompt
Has que el jugador aparezca en un lugar donde se pueda mover si esta colisionando con una pared redirigelo a un lugar donde se pueda mover
User prompt
Cada habitación tiene que tener por lo menos 1 salida a otra habitación
User prompt
Has que los pasillos tengan la probabilidad del 30% de generarse
User prompt
Has que los pasillos sean más cortos
User prompt
Haz que las habitaciones sean variadas y que no sean tan rectas o pasillos rectos
User prompt
Has que las habitaciones tengan probabilidades las pequeñas de 50% mediana 35% y las grandes de 15%
User prompt
Las habitaciones mucho más más pequeña
User prompt
Has que las habitaciones sean más pequeñas
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'worldGrid.walls[midX + i - Math.floor(self.hallwayWidth / 2)][offsetY + self.chunkSize] = false')' in or related to this line: 'worldGrid.walls[midX + i - Math.floor(self.hallwayWidth / 2)][offsetY + self.chunkSize] = false;' Line Number: 631
User prompt
Corrige la generación procedural o has uno nuevo en este sistema por lo menos tiene que a ver 1 salida o entrada a una habitación nueva
User prompt
Crea el escenario proseduralmente
User prompt
El piso está mal posicionado
User prompt
Has que el piso tenga sombreado si el personaje está serca se ilumina pero si esta lejos se oscurece pero hazlo en el área de piso no lo pongas en el área de pared ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Ahora ponle sombreado como lo tienen los demás objetos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Error el piso lo esas considerando pared y estas dejando sin textura el área de piso
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var CeilingTileRenderer = Container.expand(function () {
var self = Container.call(this);
self.tiles = [];
// Generate ceiling tiles in safe positions (away from corners and walls)
self.generateTiles = function () {
for (var x = 2; x < worldGrid.width - 2; x++) {
for (var y = 2; y < worldGrid.height - 2; y++) {
// Only place tiles in open areas (not near walls or corners)
if (!worldGrid.hasWallAt(x * worldGrid.cellSize, y * worldGrid.cellSize) && !self.isNearCorner(x, y) && self.isSafePosition(x, y)) {
var tile = {
worldX: x * worldGrid.cellSize + worldGrid.cellSize / 2,
worldY: y * worldGrid.cellSize + worldGrid.cellSize / 2,
sprite: null
};
self.tiles.push(tile);
}
}
}
};
// Check if position is near a corner
self.isNearCorner = function (gridX, gridY) {
// Check 3x3 area around position for wall density
var wallCount = 0;
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var checkX = gridX + dx;
var checkY = gridY + dy;
if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) {
if (worldGrid.walls && worldGrid.walls[checkX] && worldGrid.walls[checkX][checkY]) {
wallCount++;
}
}
}
}
return wallCount >= 3; // Near corner if 3+ walls nearby
};
// Check if position is safe (center of open areas)
self.isSafePosition = function (gridX, gridY) {
// Ensure there's open space in all 4 cardinal directions
var directions = [{
x: 0,
y: -1
}, {
x: 1,
y: 0
}, {
x: 0,
y: 1
}, {
x: -1,
y: 0
}];
for (var i = 0; i < directions.length; i++) {
var checkX = gridX + directions[i].x;
var checkY = gridY + directions[i].y;
if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) {
if (worldGrid.walls && worldGrid.walls[checkX] && worldGrid.walls[checkX][checkY]) {
return false;
}
}
}
return true;
};
self.render = function (player) {
// Clear existing sprites
for (var i = 0; i < self.tiles.length; i++) {
if (self.tiles[i].sprite) {
self.tiles[i].sprite.visible = false;
}
}
var visibleTiles = [];
// Calculate which tiles are visible and their screen positions
for (var i = 0; i < self.tiles.length; i++) {
var tile = self.tiles[i];
var dx = tile.worldX - player.x;
var dy = tile.worldY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only render tiles within reasonable distance
if (distance < 800) {
// Calculate angle relative to player's view direction
var tileAngle = Math.atan2(dy, dx);
var angleDiff = tileAngle - player.angle;
// Normalize angle difference
while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
// Check if tile is within field of view
var fov = Math.PI / 3;
if (Math.abs(angleDiff) < fov / 2) {
// Calculate screen X position
var screenX = 1366 + angleDiff / (fov / 2) * 1366;
// Only add tiles that are within horizontal screen bounds with margin
if (screenX >= -50 && screenX <= 2782) {
// Apply pitch offset to ceiling tiles
var pitchOffset = player.pitch * 400;
visibleTiles.push({
tile: tile,
distance: distance,
screenX: screenX,
screenY: 400 - 200 * (1000 / (distance + 100)) + pitchOffset // Project to ceiling with pitch
});
}
}
}
}
// Sort by distance (farthest first)
visibleTiles.sort(function (a, b) {
return b.distance - a.distance;
});
// Render visible tiles
for (var i = 0; i < visibleTiles.length; i++) {
var visibleTile = visibleTiles[i];
var tile = visibleTile.tile;
if (!tile.sprite) {
tile.sprite = self.addChild(LK.getAsset('ceilingTile', {
anchorX: 0.5,
anchorY: 0.5
}));
}
tile.sprite.x = visibleTile.screenX;
tile.sprite.y = visibleTile.screenY;
tile.sprite.visible = true;
// Scale based on distance
var scale = Math.max(0.1, 20 / (visibleTile.distance + 20));
tile.sprite.scaleX = scale;
tile.sprite.scaleY = scale;
}
};
return self;
});
var GeometricWallRenderer = Container.expand(function () {
var self = Container.call(this);
// Simplified wall rendering with geometric shapes
self.wallStrips = [];
self.numStrips = 64; // Further reduced for better performance
self.maxWalls = 32; // Maximum number of wall strips to render
// Initialize wall strips pool
self.initWallStrips = function () {
for (var i = 0; i < self.maxWalls; i++) {
var wallStrip = self.addChild(LK.getAsset('wallSegment', {
anchorX: 0.5,
anchorY: 0.5
}));
wallStrip.visible = false;
self.wallStrips.push(wallStrip);
}
};
// Get available wall strip from pool
self.getWallStrip = function () {
for (var i = 0; i < self.wallStrips.length; i++) {
if (!self.wallStrips[i].visible) {
return self.wallStrips[i];
}
}
return null;
};
// Render walls using simplified column-based approach
self.render = function (player) {
// Initialize strips if not done
if (self.wallStrips.length === 0) {
self.initWallStrips();
}
// Hide all wall strips
for (var j = 0; j < self.wallStrips.length; j++) {
self.wallStrips[j].visible = false;
}
var fov = Math.PI / 3; // 60 degrees field of view
var halfFov = fov / 2;
var screenCenter = 1024; // Y center of screen
var pitchOffset = player.pitch * 300; // Apply pitch for vertical look
var stripWidth = 2732 / self.numStrips;
var wallsRendered = 0;
// Cast rays and render wall strips
for (var i = 0; i < self.numStrips && wallsRendered < self.maxWalls; i++) {
var rayAngle = player.angle - halfFov + i / self.numStrips * fov;
var rayData = self.castSimpleRay(player.x, player.y, rayAngle);
if (rayData.hit) {
var distance = rayData.distance;
// Apply fish-eye correction
var correctedDistance = distance * Math.cos(rayAngle - player.angle);
// Calculate screen X position for horizontal bounds checking
var screenX = i * stripWidth + stripWidth / 2;
// Only render if within horizontal screen bounds with stricter checking
if (screenX >= -stripWidth && screenX <= 2732 + stripWidth) {
// Get wall strip from pool
var wallStrip = self.getWallStrip();
if (wallStrip) {
// Calculate wall height based on distance
var baseWallSize = worldGrid.cellSize;
var wallHeight = Math.max(60, baseWallSize * (500 / (correctedDistance + 50)));
// Position wall strip
wallStrip.width = stripWidth + 2; // Add small overlap
wallStrip.height = wallHeight;
wallStrip.x = Math.max(-stripWidth, Math.min(2732 + stripWidth, screenX)); // Clamp position
wallStrip.y = screenCenter + pitchOffset;
wallStrip.visible = true;
// Apply distance-based shading
var shadingFactor = Math.max(0.2, 1.0 - correctedDistance / 600);
var tintValue = Math.floor(shadingFactor * 255);
wallStrip.tint = tintValue << 16 | tintValue << 8 | tintValue;
// Add slight variation based on position for texture effect
var positionVariation = (rayData.hitX + rayData.hitY) % 40;
if (positionVariation < 20) {
tintValue = Math.floor(tintValue * 0.9); // Slightly darker
wallStrip.tint = tintValue << 16 | tintValue << 8 | tintValue;
}
wallsRendered++;
}
}
}
}
};
// Simplified raycasting for geometric walls
self.castSimpleRay = function (startX, startY, angle) {
var rayX = startX;
var rayY = startY;
var deltaX = Math.cos(angle) * 8; // Larger steps for performance
var deltaY = Math.sin(angle) * 8;
var distance = 0;
var maxDistance = 600;
var stepSize = 8;
// Raycast until wall hit or max distance
while (distance < maxDistance) {
rayX += deltaX;
rayY += deltaY;
distance += stepSize;
// Check for wall collision
if (worldGrid.hasWallAt(rayX, rayY)) {
return {
hit: true,
distance: distance,
hitX: rayX,
hitY: rayY
};
}
}
return {
hit: false,
distance: maxDistance,
hitX: rayX,
hitY: rayY
};
};
return self;
});
var MovementCrosshair = Container.expand(function () {
var self = Container.call(this);
self.isActive = false;
self.activeButton = null;
// Create base circle
var base = self.attachAsset('crosshairBase', {
anchorX: 0.5,
anchorY: 0.5
});
base.alpha = 0.6;
// Create directional buttons
var upButton = self.attachAsset('crosshairUp', {
anchorX: 0.5,
anchorY: 0.5
});
upButton.x = 0;
upButton.y = -70;
upButton.alpha = 0.7;
var downButton = self.attachAsset('crosshairDown', {
anchorX: 0.5,
anchorY: 0.5
});
downButton.x = 0;
downButton.y = 70;
downButton.alpha = 0.7;
var leftButton = self.attachAsset('crosshairLeft', {
anchorX: 0.5,
anchorY: 0.5
});
leftButton.x = -70;
leftButton.y = 0;
leftButton.alpha = 0.7;
var rightButton = self.attachAsset('crosshairRight', {
anchorX: 0.5,
anchorY: 0.5
});
rightButton.x = 70;
rightButton.y = 0;
rightButton.alpha = 0.7;
var centerButton = self.attachAsset('crosshairCenter', {
anchorX: 0.5,
anchorY: 0.5
});
centerButton.alpha = 0.8;
// Movement state tracking
self.movementState = {
forward: false,
backward: false,
left: false,
right: false
};
// Button press handlers
upButton.down = function (x, y, obj) {
upButton.alpha = 1.0;
self.movementState.forward = true;
self.activeButton = 'up';
};
upButton.up = function (x, y, obj) {
upButton.alpha = 0.7;
self.movementState.forward = false;
if (self.activeButton === 'up') {
self.activeButton = null;
}
};
downButton.down = function (x, y, obj) {
downButton.alpha = 1.0;
self.movementState.backward = true;
self.activeButton = 'down';
};
downButton.up = function (x, y, obj) {
downButton.alpha = 0.7;
self.movementState.backward = false;
if (self.activeButton === 'down') {
self.activeButton = null;
}
};
leftButton.down = function (x, y, obj) {
leftButton.alpha = 1.0;
self.movementState.left = true;
self.activeButton = 'left';
};
leftButton.up = function (x, y, obj) {
leftButton.alpha = 0.7;
self.movementState.left = false;
if (self.activeButton === 'left') {
self.activeButton = null;
}
};
rightButton.down = function (x, y, obj) {
rightButton.alpha = 1.0;
self.movementState.right = true;
self.activeButton = 'right';
};
rightButton.up = function (x, y, obj) {
rightButton.alpha = 0.7;
self.movementState.right = false;
if (self.activeButton === 'right') {
self.activeButton = null;
}
};
// Get current movement state
self.getMovementState = function () {
return self.movementState;
};
// Reset all movement states
self.resetMovement = function () {
self.movementState.forward = false;
self.movementState.backward = false;
self.movementState.left = false;
self.movementState.right = false;
self.activeButton = null;
upButton.alpha = 0.7;
downButton.alpha = 0.7;
leftButton.alpha = 0.7;
rightButton.alpha = 0.7;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.x = 1366;
self.y = 1024;
self.angle = 0;
self.pitch = 0; // Vertical look angle (up/down)
self.speed = 3;
self.rotSpeed = 0.1;
// Smooth interpolation properties
self.targetX = 1366;
self.targetY = 1024;
self.targetAngle = 0;
self.targetPitch = 0;
self.smoothingFactor = 0.15;
// Player visual for debugging (will be hidden in first person)
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
playerGraphics.visible = false; // Hide for first person view
self.moveForward = function () {
var newX = self.targetX + Math.cos(self.targetAngle) * self.speed;
var newY = self.targetY + Math.sin(self.targetAngle) * self.speed;
// Constrain Y coordinate to not go below 0
if (newY < 0) {
newY = 0;
}
// Check collision with world grid using improved collision detection
if (!worldGrid.checkCollision(newX, newY)) {
self.targetX = newX;
self.targetY = newY;
}
};
self.moveBackward = function () {
var newX = self.targetX - Math.cos(self.targetAngle) * self.speed;
var newY = self.targetY - Math.sin(self.targetAngle) * self.speed;
// Constrain Y coordinate to not go below 0
if (newY < 0) {
newY = 0;
}
// Check collision with world grid using improved collision detection
if (!worldGrid.checkCollision(newX, newY)) {
self.targetX = newX;
self.targetY = newY;
}
};
self.turnLeft = function () {
self.targetAngle -= self.rotSpeed;
};
self.turnRight = function () {
self.targetAngle += self.rotSpeed;
};
self.lookUp = function () {
self.targetPitch = Math.max(-Math.PI / 3, self.targetPitch - self.rotSpeed); // Limit to -60 degrees
};
self.lookDown = function () {
self.targetPitch = Math.min(Math.PI / 3, self.targetPitch + self.rotSpeed); // Limit to +60 degrees
};
self.updateSmooth = function () {
// Smooth interpolation for position
self.x += (self.targetX - self.x) * self.smoothingFactor;
self.y += (self.targetY - self.y) * self.smoothingFactor;
// Smooth interpolation for rotation with angle wrapping
var angleDiff = self.targetAngle - self.angle;
// Handle angle wrapping (ensure shortest rotation path)
if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
self.angle += angleDiff * self.smoothingFactor;
// Smooth interpolation for pitch
var pitchDiff = self.targetPitch - self.pitch;
self.pitch += pitchDiff * self.smoothingFactor;
};
return self;
});
var ProcGen = Container.expand(function () {
var self = Container.call(this);
self.generatedChunks = {};
self.chunkSize = 10; // Smaller chunks for more compact generation
self.roomMinSize = 1;
self.roomMaxSize = 2;
self.hallwayWidth = 1; // Much narrower connecting hallways - reduced for shorter corridors
// Generate a procedural chunk at given chunk coordinates
self.generateChunk = function (chunkX, chunkY) {
var chunkKey = chunkX + ',' + chunkY;
if (self.generatedChunks[chunkKey]) {
return; // Already generated
}
self.generatedChunks[chunkKey] = true;
// Calculate world grid offset for this chunk
var offsetX = chunkX * self.chunkSize;
var offsetY = chunkY * self.chunkSize;
// Generate Backrooms-style layout for this chunk
self.generateBackroomsChunk(offsetX, offsetY);
};
// Generate Backrooms-style layout with guaranteed connectivity and multiple exits
self.generateBackroomsChunk = function (offsetX, offsetY) {
// First, fill entire chunk with walls
for (var x = offsetX; x < offsetX + self.chunkSize; x++) {
for (var y = offsetY; y < offsetY + self.chunkSize; y++) {
if (x >= 0 && x < worldGrid.width && y >= 0 && y < worldGrid.height) {
worldGrid.walls[x][y] = true;
}
}
}
// Create main room in center of chunk with irregular shape
var mainRoomSize = 3; // Increased size for better connectivity
var mainRoomX = offsetX + Math.floor((self.chunkSize - mainRoomSize) / 2);
var mainRoomY = offsetY + Math.floor((self.chunkSize - mainRoomSize) / 2);
self.carveIrregularRoom(mainRoomX, mainRoomY, mainRoomSize, mainRoomSize);
// Create 2-4 additional rooms for multiple connections
var numRooms = Math.floor(Math.random() * 3) + 2; // 2-4 rooms
var rooms = [{
x: mainRoomX,
y: mainRoomY,
width: mainRoomSize,
height: mainRoomSize,
centerX: mainRoomX + Math.floor(mainRoomSize / 2),
centerY: mainRoomY + Math.floor(mainRoomSize / 2)
}];
for (var i = 0; i < numRooms; i++) {
var attempts = 0;
while (attempts < 30) {
// Determine room size based on probabilities
var roomSizes = self.getRoomSizeByProbability();
var roomW = roomSizes.width;
var roomH = roomSizes.height;
var roomX = offsetX + Math.floor(Math.random() * (self.chunkSize - roomW - 2)) + 1;
var roomY = offsetY + Math.floor(Math.random() * (self.chunkSize - roomH - 2)) + 1;
var newRoom = {
x: roomX,
y: roomY,
width: roomW,
height: roomH,
centerX: roomX + Math.floor(roomW / 2),
centerY: roomY + Math.floor(roomH / 2)
};
if (!self.roomOverlaps(newRoom, rooms)) {
self.carveIrregularRoom(roomX, roomY, roomW, roomH);
rooms.push(newRoom);
// Connect this room to multiple existing rooms for guaranteed connectivity
self.connectToMultipleRooms(newRoom, rooms);
break;
}
attempts++;
}
}
// Create guaranteed multiple exits to adjacent chunks
self.createMultipleChunkExits(offsetX, offsetY, rooms);
// Create dead-end corridors for exploration variety
self.createDeadEndCorridors(offsetX, offsetY, rooms);
// Add some random pillars for atmosphere
self.addRandomPillars(offsetX, offsetY);
// Validate and ensure all rooms are connected
self.validateRoomConnectivity(rooms, offsetX, offsetY);
};
// Carve out a room (remove walls)
self.carveRoom = function (x, y, width, height) {
for (var roomX = x; roomX < x + width; roomX++) {
for (var roomY = y; roomY < y + height; roomY++) {
if (roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) {
worldGrid.walls[roomX][roomY] = false;
}
}
}
};
// Carve out an irregular room shape with random variations
self.carveIrregularRoom = function (x, y, width, height) {
var roomType = Math.random();
var centerX = x + Math.floor(width / 2);
var centerY = y + Math.floor(height / 2);
if (roomType < 0.4) {
// Circular/oval room
var radiusX = Math.floor(width / 2);
var radiusY = Math.floor(height / 2);
for (var roomX = x; roomX < x + width; roomX++) {
for (var roomY = y; roomY < y + height; roomY++) {
if (roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) {
var dx = roomX - centerX;
var dy = roomY - centerY;
// Create elliptical shape with some randomness
var distanceSquared = dx * dx / (radiusX * radiusX) + dy * dy / (radiusY * radiusY);
if (distanceSquared <= 1.0 + Math.random() * 0.3 - 0.15) {
worldGrid.walls[roomX][roomY] = false;
}
}
}
}
} else if (roomType < 0.7) {
// L-shaped room
self.carveRoom(x, y, width, Math.floor(height / 2) + 1);
self.carveRoom(x + Math.floor(width / 2), y + Math.floor(height / 2), Math.floor(width / 2) + 1, Math.floor(height / 2) + 1);
} else if (roomType < 0.9) {
// Cross-shaped room
var halfW = Math.floor(width / 2);
var halfH = Math.floor(height / 2);
// Vertical bar
self.carveRoom(centerX - 1, y, 2, height);
// Horizontal bar
self.carveRoom(x, centerY - 1, width, 2);
} else {
// Regular rectangular room with random indentations
self.carveRoom(x, y, width, height);
// Add random indentations
var indentations = Math.floor(Math.random() * 3) + 1;
for (var i = 0; i < indentations; i++) {
var indentX = x + Math.floor(Math.random() * width);
var indentY = y + Math.floor(Math.random() * height);
var indentSize = Math.floor(Math.random() * 2) + 1;
for (var ix = 0; ix < indentSize; ix++) {
for (var iy = 0; iy < indentSize; iy++) {
var wallX = indentX + ix;
var wallY = indentY + iy;
if (wallX >= 0 && wallX < worldGrid.width && wallY >= 0 && wallY < worldGrid.height) {
worldGrid.walls[wallX][wallY] = true;
}
}
}
}
}
};
// Check if room overlaps with existing rooms
self.roomOverlaps = function (room, existingRooms) {
for (var i = 0; i < existingRooms.length; i++) {
var existing = existingRooms[i];
// Add padding between rooms
if (room.x < existing.x + existing.width + 2 && room.x + room.width + 2 > existing.x && room.y < existing.y + existing.height + 2 && room.y + room.height + 2 > existing.y) {
return true;
}
}
return false;
};
// Connect room to nearest existing room
self.connectToNearestRoom = function (newRoom, existingRooms) {
var nearestRoom = existingRooms[0];
var minDistance = Infinity;
// Find nearest room
for (var i = 0; i < existingRooms.length; i++) {
if (existingRooms[i] !== newRoom) {
var dx = newRoom.centerX - existingRooms[i].centerX;
var dy = newRoom.centerY - existingRooms[i].centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
nearestRoom = existingRooms[i];
}
}
}
// Create L-shaped corridor
self.createCorridor(newRoom.centerX, newRoom.centerY, nearestRoom.centerX, nearestRoom.centerY);
};
// Connect room to multiple existing rooms for guaranteed connectivity
self.connectToMultipleRooms = function (newRoom, existingRooms) {
// Sort rooms by distance to find nearest connections
var roomDistances = [];
for (var i = 0; i < existingRooms.length; i++) {
if (existingRooms[i] !== newRoom) {
var dx = newRoom.centerX - existingRooms[i].centerX;
var dy = newRoom.centerY - existingRooms[i].centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
roomDistances.push({
room: existingRooms[i],
distance: distance
});
}
}
// Sort by distance
roomDistances.sort(function (a, b) {
return a.distance - b.distance;
});
// Always connect to at least 2 rooms for guaranteed multiple exits
var minConnections = Math.min(2, roomDistances.length);
var maxConnections = Math.min(3, roomDistances.length); // Up to 3 connections
var connectionsToMake = Math.floor(Math.random() * (maxConnections - minConnections + 1)) + minConnections;
for (var i = 0; i < connectionsToMake; i++) {
self.createCurvedCorridor(newRoom.centerX, newRoom.centerY, roomDistances[i].room.centerX, roomDistances[i].room.centerY);
}
// Add one more connection with 40% probability for extra connectivity
if (Math.random() < 0.4 && roomDistances.length > connectionsToMake) {
self.createCurvedCorridor(newRoom.centerX, newRoom.centerY, roomDistances[connectionsToMake].room.centerX, roomDistances[connectionsToMake].room.centerY);
}
};
// Connect room to nearest existing room with curved corridor (legacy method for compatibility)
self.connectToNearestRoomCurved = function (newRoom, existingRooms) {
// Use the new multiple connections method
self.connectToMultipleRooms(newRoom, existingRooms);
};
// Create multiple guaranteed exits to adjacent chunks for better connectivity
self.createMultipleChunkExits = function (offsetX, offsetY, rooms) {
var midX = offsetX + Math.floor(self.chunkSize / 2);
var midY = offsetY + Math.floor(self.chunkSize / 2);
// Find main room (usually the first/largest)
var mainRoom = rooms[0];
for (var i = 1; i < rooms.length; i++) {
if (rooms[i].width * rooms[i].height > mainRoom.width * mainRoom.height) {
mainRoom = rooms[i];
}
}
// Ensure at least 2-3 exits for multiple pathways
var possibleExits = [];
// Check which exits are possible
if (offsetX > 0) possibleExits.push('left');
if (offsetX + self.chunkSize < worldGrid.width) possibleExits.push('right');
if (offsetY > 0) possibleExits.push('top');
if (offsetY + self.chunkSize < worldGrid.height) possibleExits.push('bottom');
// Guarantee at least 2 exits if possible, 3 if we have 4 sides available
var minExits = Math.min(2, possibleExits.length);
var maxExits = Math.min(3, possibleExits.length);
var exitsToCreate = Math.floor(Math.random() * (maxExits - minExits + 1)) + minExits;
// Shuffle possible exits for randomness
for (var i = possibleExits.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = possibleExits[i];
possibleExits[i] = possibleExits[j];
possibleExits[j] = temp;
}
// Create the guaranteed exits
for (var i = 0; i < exitsToCreate; i++) {
var exitDirection = possibleExits[i];
// Connect from different rooms for variety
var sourceRoom = rooms[i % rooms.length];
if (exitDirection === 'left') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, offsetX, midY);
} else if (exitDirection === 'right') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, offsetX + self.chunkSize - 1, midY);
} else if (exitDirection === 'top') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, midX, offsetY);
} else if (exitDirection === 'bottom') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, midX, offsetY + self.chunkSize - 1);
}
}
// Create additional exits with 50% probability for extra connectivity
for (var i = exitsToCreate; i < possibleExits.length; i++) {
if (Math.random() < 0.5) {
var exitDirection = possibleExits[i];
var sourceRoom = rooms[Math.floor(Math.random() * rooms.length)];
if (exitDirection === 'left') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, offsetX, midY);
} else if (exitDirection === 'right') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, offsetX + self.chunkSize - 1, midY);
} else if (exitDirection === 'top') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, midX, offsetY);
} else if (exitDirection === 'bottom') {
self.createCorridor(sourceRoom.centerX, sourceRoom.centerY, midX, offsetY + self.chunkSize - 1);
}
}
}
};
// Create dead-end corridors for exploration variety
self.createDeadEndCorridors = function (offsetX, offsetY, rooms) {
// Create 2-4 dead-end corridors per chunk
var numDeadEnds = Math.floor(Math.random() * 3) + 2; // 2-4 dead ends
for (var i = 0; i < numDeadEnds; i++) {
// Choose a random room as starting point
var sourceRoom = rooms[Math.floor(Math.random() * rooms.length)];
// Choose a random direction for the dead-end
var directions = [{
dx: 1,
dy: 0
},
// East
{
dx: -1,
dy: 0
},
// West
{
dx: 0,
dy: 1
},
// South
{
dx: 0,
dy: -1
} // North
];
var direction = directions[Math.floor(Math.random() * directions.length)];
// Create dead-end corridor of random length (2-5 cells)
var corridorLength = Math.floor(Math.random() * 4) + 2;
var startX = sourceRoom.centerX;
var startY = sourceRoom.centerY;
// Find a good starting point on the room edge
var edgeStartX = startX;
var edgeStartY = startY;
// Move to room edge
if (direction.dx !== 0) {
edgeStartX = direction.dx > 0 ? sourceRoom.x + sourceRoom.width : sourceRoom.x - 1;
} else {
edgeStartY = direction.dy > 0 ? sourceRoom.y + sourceRoom.height : sourceRoom.y - 1;
}
// Create the dead-end corridor
self.createDeadEndPath(edgeStartX, edgeStartY, direction.dx, direction.dy, corridorLength, offsetX, offsetY);
}
};
// Create a single dead-end corridor path
self.createDeadEndPath = function (startX, startY, dirX, dirY, length, chunkOffsetX, chunkOffsetY) {
var currentX = startX;
var currentY = startY;
// Create corridor cells
for (var i = 0; i < length; i++) {
currentX += dirX;
currentY += dirY;
// Check bounds - stop if we're going outside the chunk or world
if (currentX < chunkOffsetX + 1 || currentX >= chunkOffsetX + self.chunkSize - 1 || currentY < chunkOffsetY + 1 || currentY >= chunkOffsetY + self.chunkSize - 1 || currentX < 0 || currentX >= worldGrid.width || currentY < 0 || currentY >= worldGrid.height) {
break;
}
// Carve out the corridor cell
worldGrid.walls[currentX][currentY] = false;
// Add some width to the corridor (occasionally)
if (Math.random() < 0.3) {
// Add perpendicular width
var perpDirX = dirY; // Perpendicular direction
var perpDirY = -dirX;
var widthX = currentX + perpDirX;
var widthY = currentY + perpDirY;
if (widthX >= chunkOffsetX && widthX < chunkOffsetX + self.chunkSize && widthY >= chunkOffsetY && widthY < chunkOffsetY + self.chunkSize && widthX >= 0 && widthX < worldGrid.width && widthY >= 0 && widthY < worldGrid.height) {
worldGrid.walls[widthX][widthY] = false;
}
}
// Occasionally branch the dead-end
if (i > 1 && Math.random() < 0.2) {
// Create a short branch (1-2 cells)
var branchLength = Math.floor(Math.random() * 2) + 1;
var branchDirX = Math.random() < 0.5 ? dirY : -dirY; // Perpendicular directions
var branchDirY = Math.random() < 0.5 ? -dirX : dirX;
self.createDeadEndPath(currentX, currentY, branchDirX, branchDirY, branchLength, chunkOffsetX, chunkOffsetY);
}
}
// Create a small room at the end of some dead-ends (30% chance)
if (Math.random() < 0.3) {
self.createDeadEndRoom(currentX, currentY, chunkOffsetX, chunkOffsetY);
}
};
// Create a small room at the end of a dead-end corridor
self.createDeadEndRoom = function (centerX, centerY, chunkOffsetX, chunkOffsetY) {
// Create a small 2x2 or 3x3 room
var roomSize = Math.floor(Math.random() * 2) + 2; // 2x2 or 3x3
var halfSize = Math.floor(roomSize / 2);
for (var dx = -halfSize; dx <= halfSize; dx++) {
for (var dy = -halfSize; dy <= halfSize; dy++) {
var roomX = centerX + dx;
var roomY = centerY + dy;
// Check bounds
if (roomX >= chunkOffsetX && roomX < chunkOffsetX + self.chunkSize && roomY >= chunkOffsetY && roomY < chunkOffsetY + self.chunkSize && roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) {
worldGrid.walls[roomX][roomY] = false;
}
}
}
};
// Create guaranteed exits to adjacent chunks (legacy method for compatibility)
self.createChunkExits = function (offsetX, offsetY, mainRoom) {
// Use the new multiple exits method
self.createMultipleChunkExits(offsetX, offsetY, [mainRoom]);
};
// Create L-shaped corridor between two points with much shorter segments
self.createCorridor = function (x1, y1, x2, y2) {
// Create narrower corridors for shorter navigation paths
var halfWidth = Math.floor(self.hallwayWidth / 2);
// Calculate distance and limit corridor length to make them much shorter
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var maxCorridorLength = 3; // Maximum corridor segment length
// If distance is too long, create intermediate points for shorter segments
if (dx > maxCorridorLength || dy > maxCorridorLength) {
var midX = x1 + Math.sign(x2 - x1) * Math.min(maxCorridorLength, dx);
var midY = y1 + Math.sign(y2 - y1) * Math.min(maxCorridorLength, dy);
// Create first short segment
self.createShortSegment(x1, y1, midX, y1, halfWidth);
// Create second short segment
self.createShortSegment(midX, y1, midX, midY, halfWidth);
// If we haven't reached the destination, create final segment
if (midX !== x2 || midY !== y2) {
self.createShortSegment(midX, midY, x2, y2, halfWidth);
}
} else {
// Choose random direction for short L-shaped corridor
if (Math.random() < 0.5) {
// Horizontal first, then vertical - but keep it short
self.createShortSegment(x1, y1, x2, y1, halfWidth);
self.createShortSegment(x2, y1, x2, y2, halfWidth);
} else {
// Vertical first, then horizontal - but keep it short
self.createShortSegment(x1, y1, x1, y2, halfWidth);
self.createShortSegment(x1, y2, x2, y2, halfWidth);
}
}
};
// Create a short corridor segment with limited length
self.createShortSegment = function (x1, y1, x2, y2, halfWidth) {
var startX = Math.min(x1, x2);
var endX = Math.max(x1, x2);
var startY = Math.min(y1, y2);
var endY = Math.max(y1, y2);
// Limit segment length to make corridors much shorter
var maxSegmentLength = 2;
endX = Math.min(endX, startX + maxSegmentLength);
endY = Math.min(endY, startY + maxSegmentLength);
for (var x = startX; x <= endX; x++) {
for (var y = startY; y <= endY; y++) {
for (var w = -halfWidth; w <= halfWidth; w++) {
var corridorX = x + (x1 === x2 ? w : 0);
var corridorY = y + (y1 === y2 ? w : 0);
if (corridorX >= 0 && corridorX < worldGrid.width && corridorY >= 0 && corridorY < worldGrid.height) {
worldGrid.walls[corridorX][corridorY] = false;
}
}
}
}
};
// Create curved and winding corridor between two points
self.createCurvedCorridor = function (x1, y1, x2, y2) {
var corridorType = Math.random();
var halfWidth = Math.floor(self.hallwayWidth / 2);
if (corridorType < 0.3) {
// S-shaped curve
var midX = Math.floor((x1 + x2) / 2) + Math.floor(Math.random() * 4) - 2;
var midY = Math.floor((y1 + y2) / 2) + Math.floor(Math.random() * 4) - 2;
self.createSmoothPath(x1, y1, midX, midY);
self.createSmoothPath(midX, midY, x2, y2);
} else if (corridorType < 0.6) {
// Zigzag corridor
var steps = Math.floor(Math.abs(x2 - x1) + Math.abs(y2 - y1)) / 3;
var currentX = x1;
var currentY = y1;
var stepX = (x2 - x1) / steps;
var stepY = (y2 - y1) / steps;
for (var i = 0; i < steps; i++) {
var nextX = Math.floor(currentX + stepX + Math.random() * 2 - 1);
var nextY = Math.floor(currentY + stepY + Math.random() * 2 - 1);
self.createSmoothPath(Math.floor(currentX), Math.floor(currentY), nextX, nextY);
currentX = nextX;
currentY = nextY;
}
self.createSmoothPath(Math.floor(currentX), Math.floor(currentY), x2, y2);
} else {
// Wide curved path with multiple control points
var controlPoints = [];
controlPoints.push({
x: x1,
y: y1
});
// Add 1-2 random control points
var numControls = Math.floor(Math.random() * 2) + 1;
for (var i = 0; i < numControls; i++) {
var t = (i + 1) / (numControls + 1);
var controlX = Math.floor(x1 + (x2 - x1) * t + Math.random() * 6 - 3);
var controlY = Math.floor(y1 + (y2 - y1) * t + Math.random() * 6 - 3);
controlPoints.push({
x: controlX,
y: controlY
});
}
controlPoints.push({
x: x2,
y: y2
});
// Connect all control points
for (var i = 0; i < controlPoints.length - 1; i++) {
self.createSmoothPath(controlPoints[i].x, controlPoints[i].y, controlPoints[i + 1].x, controlPoints[i + 1].y);
}
}
};
// Create smooth path between two points with width - much shorter segments
self.createSmoothPath = function (x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var distance = Math.sqrt(dx * dx + dy * dy);
// Limit path length to make corridors much shorter
var maxPathLength = 4;
if (distance > maxPathLength) {
// Create shorter intermediate path
var ratio = maxPathLength / distance;
x2 = Math.floor(x1 + dx * ratio);
y2 = Math.floor(y1 + dy * ratio);
dx = x2 - x1;
dy = y2 - y1;
distance = maxPathLength;
}
var steps = Math.min(Math.floor(distance) + 1, 3); // Limit steps for shorter paths
var halfWidth = Math.floor(self.hallwayWidth / 2);
for (var i = 0; i <= steps; i++) {
var t = i / steps;
var x = Math.floor(x1 + dx * t);
var y = Math.floor(y1 + dy * t);
// Carve corridor with width
for (var w = -halfWidth; w <= halfWidth; w++) {
for (var h = -halfWidth; h <= halfWidth; h++) {
var corridorX = x + w;
var corridorY = y + h;
if (corridorX >= 0 && corridorX < worldGrid.width && corridorY >= 0 && corridorY < worldGrid.height) {
worldGrid.walls[corridorX][corridorY] = false;
}
}
}
}
};
// Add random pillars for atmosphere
self.addRandomPillars = function (offsetX, offsetY) {
for (var x = offsetX + 2; x < offsetX + self.chunkSize - 2; x++) {
for (var y = offsetY + 2; y < offsetY + self.chunkSize - 2; y++) {
if (x >= 0 && x < worldGrid.width && y >= 0 && y < worldGrid.height) {
// Only add pillars in open areas with low probability
if (!worldGrid.walls[x][y] && Math.random() < 0.008) {
// Check if surrounded by enough open space (3x3 area)
var canPlace = true;
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var checkX = x + dx;
var checkY = y + dy;
if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) {
if (worldGrid.walls[checkX][checkY]) {
canPlace = false;
break;
}
}
}
if (!canPlace) break;
}
if (canPlace) {
worldGrid.walls[x][y] = true;
}
}
}
}
}
};
// Validate that all rooms are connected and fix any isolated rooms
self.validateRoomConnectivity = function (rooms, offsetX, offsetY) {
if (rooms.length <= 1) return; // Single room doesn't need validation
// Use flood fill to check connectivity from main room
var visited = [];
for (var i = 0; i < rooms.length; i++) {
visited[i] = false;
}
// Start flood fill from main room (first room)
var connected = [0]; // Start with main room
visited[0] = true;
var changed = true;
// Keep checking until no new connections are found
while (changed) {
changed = false;
for (var i = 0; i < connected.length; i++) {
var currentRoom = rooms[connected[i]];
// Check if any unvisited room is connected to this room
for (var j = 0; j < rooms.length; j++) {
if (!visited[j] && self.areRoomsConnected(currentRoom, rooms[j])) {
visited[j] = true;
connected.push(j);
changed = true;
}
}
}
}
// Connect any isolated rooms
for (var i = 0; i < rooms.length; i++) {
if (!visited[i]) {
// This room is isolated, connect it to the nearest connected room
var nearestConnectedRoom = rooms[connected[0]];
var minDistance = Infinity;
for (var j = 0; j < connected.length; j++) {
var connectedRoom = rooms[connected[j]];
var dx = rooms[i].centerX - connectedRoom.centerX;
var dy = rooms[i].centerY - connectedRoom.centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
nearestConnectedRoom = connectedRoom;
}
}
// Force connection to prevent isolation
self.createCurvedCorridor(rooms[i].centerX, rooms[i].centerY, nearestConnectedRoom.centerX, nearestConnectedRoom.centerY);
}
}
};
// Check if two rooms are connected via corridors
self.areRoomsConnected = function (room1, room2) {
// Use simple pathfinding to check if rooms are connected
var visited = [];
for (var x = 0; x < worldGrid.width; x++) {
visited[x] = [];
for (var y = 0; y < worldGrid.height; y++) {
visited[x][y] = false;
}
}
var stack = [{
x: room1.centerX,
y: room1.centerY
}];
while (stack.length > 0) {
var current = stack.pop();
var gridX = Math.floor(current.x / worldGrid.cellSize) * worldGrid.cellSize;
var gridY = Math.floor(current.y / worldGrid.cellSize) * worldGrid.cellSize;
var arrayX = Math.floor(current.x / worldGrid.cellSize);
var arrayY = Math.floor(current.y / worldGrid.cellSize);
if (arrayX < 0 || arrayX >= worldGrid.width || arrayY < 0 || arrayY >= worldGrid.height) continue;
if (visited[arrayX][arrayY] || worldGrid.walls[arrayX][arrayY]) continue;
visited[arrayX][arrayY] = true;
// Check if we reached room2
if (Math.abs(current.x - room2.centerX) < worldGrid.cellSize && Math.abs(current.y - room2.centerY) < worldGrid.cellSize) {
return true; // Rooms are connected
}
// Add adjacent cells
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
stack.push({
x: current.x + directions[d].dx * worldGrid.cellSize,
y: current.y + directions[d].dy * worldGrid.cellSize
});
}
}
return false; // No connection found
};
// Get room size based on probability distribution
// Small rooms: 50%, Medium rooms: 35%, Large rooms: 15%
self.getRoomSizeByProbability = function () {
var random = Math.random() * 100;
if (random < 50) {
// Small rooms (50% probability) - 1x1 to 2x2
var size = Math.floor(Math.random() * 2) + 1;
return {
width: size,
height: size
};
} else if (random < 85) {
// Medium rooms (35% probability) - 3x3 to 4x4
var size = Math.floor(Math.random() * 2) + 3;
return {
width: size,
height: size
};
} else {
// Large rooms (15% probability) - 5x5 to 6x6
var size = Math.floor(Math.random() * 2) + 5;
return {
width: size,
height: size
};
}
};
// Generate chunks around player position
self.generateAroundPlayer = function (playerX, playerY) {
var playerChunkX = Math.floor(playerX / (worldGrid.cellSize * self.chunkSize));
var playerChunkY = Math.floor(playerY / (worldGrid.cellSize * self.chunkSize));
// Generate chunks in a 3x3 area around player
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var chunkX = playerChunkX + dx;
var chunkY = playerChunkY + dy;
self.generateChunk(chunkX, chunkY);
}
}
};
return self;
});
var RaycastRenderer = Container.expand(function () {
var self = Container.call(this);
self.screenWidth = 2732;
self.screenHeight = 2048;
self.numRays = 128; // Number of rays to cast
self.wallColumns = [];
self.floorColumns = [];
self.ceilingColumns = [];
// Initialize rendering columns
for (var i = 0; i < self.numRays; i++) {
var stripWidth = self.screenWidth / self.numRays;
// Wall column
var wallCol = self.addChild(LK.getAsset('wallSegment', {
anchorX: 0.5,
anchorY: 0.5
}));
wallCol.x = i * stripWidth + stripWidth / 2;
wallCol.y = self.screenHeight / 2;
wallCol.width = stripWidth + 1; // Small overlap to prevent gaps
wallCol.visible = false;
self.wallColumns.push(wallCol);
// Floor column
var floorCol = self.addChild(LK.getAsset('floorStrip', {
anchorX: 0.5,
anchorY: 0
}));
floorCol.x = i * stripWidth + stripWidth / 2;
floorCol.width = stripWidth + 1;
floorCol.visible = false;
self.floorColumns.push(floorCol);
// Ceiling column
var ceilCol = self.addChild(LK.getAsset('ceilingStrip', {
anchorX: 0.5,
anchorY: 1
}));
ceilCol.x = i * stripWidth + stripWidth / 2;
ceilCol.width = stripWidth + 1;
ceilCol.visible = false;
self.ceilingColumns.push(ceilCol);
}
self.render = function (player) {
var fov = Math.PI / 2; // 90 degrees field of view for classic raycasting
var halfFov = fov / 2;
var stripWidth = self.screenWidth / self.numRays;
var screenCenter = self.screenHeight / 2;
var pitchOffset = player.pitch * 300;
// Cast rays across the field of view
for (var i = 0; i < self.numRays; i++) {
var rayAngle = player.angle - halfFov + i / self.numRays * fov;
var rayData = self.castRay(player.x, player.y, rayAngle);
var distance = rayData.distance;
var wallHeight = 0;
var wallCol = self.wallColumns[i];
var floorCol = self.floorColumns[i];
var ceilCol = self.ceilingColumns[i];
// Check if column is within horizontal bounds with tighter constraints
var columnX = wallCol.x;
var withinBounds = columnX >= -stripWidth / 2 && columnX <= self.screenWidth + stripWidth / 2;
if (rayData.hit && withinBounds) {
// Fish-eye correction
var correctedDistance = distance * Math.cos(rayAngle - player.angle);
// Calculate wall height based on distance
wallHeight = Math.max(50, worldGrid.cellSize * 800 / (correctedDistance + 1));
// Wall rendering
wallCol.height = wallHeight;
wallCol.x = Math.max(0, Math.min(self.screenWidth, wallCol.x)); // Clamp X position to screen bounds
wallCol.y = screenCenter + pitchOffset;
wallCol.visible = true;
// Distance-based shading
var shadingFactor = Math.max(0.15, 1.0 - correctedDistance / 800);
var tintValue = Math.floor(shadingFactor * 255);
wallCol.tint = tintValue << 16 | tintValue << 8 | tintValue;
// Floor rendering with distance-based shading
var wallBottom = screenCenter + wallHeight / 2 + pitchOffset;
var floorHeight = self.screenHeight - wallBottom;
floorCol.y = wallBottom;
floorCol.height = Math.max(1, floorHeight);
floorCol.visible = true;
// Calculate proper floor distance - distance from player to the floor point being rendered
// The floor distance increases as we look further down from the wall base
var floorCenter = wallBottom + floorHeight / 2; // Center point of floor strip
var floorDistanceFromWall = Math.abs(floorCenter - (screenCenter + pitchOffset)) / 10; // Normalize distance
var actualFloorDistance = correctedDistance + floorDistanceFromWall;
// Calculate floor shading based on actual floor distance - closer is lighter, farther is darker
var floorShadingFactor = Math.max(0.1, Math.min(1.0, 1.2 - actualFloorDistance / 500));
var floorTintValue = Math.floor(floorShadingFactor * 180); // Base floor brightness
floorCol.tint = floorTintValue << 16 | floorTintValue << 8 | Math.floor(floorTintValue * 0.7); // Slight yellow tint for floor
// Ceiling rendering
var ceilHeight = screenCenter - wallHeight / 2 + pitchOffset;
ceilCol.y = ceilHeight;
ceilCol.height = Math.max(1, ceilHeight);
ceilCol.visible = true;
ceilCol.tint = 0x333333;
} else {
// No wall hit or outside bounds - hide columns
wallCol.visible = false;
floorCol.visible = false;
ceilCol.visible = false;
}
}
};
// DDA (Digital Differential Analyzer) raycasting algorithm
self.castRay = function (startX, startY, angle) {
var rayX = startX;
var rayY = startY;
var rayDirX = Math.cos(angle);
var rayDirY = Math.sin(angle);
// Which grid cell we're in
var mapX = Math.floor(rayX / worldGrid.cellSize);
var mapY = Math.floor(rayY / worldGrid.cellSize);
// Length of ray from current position to x or y side
var deltaDistX = Math.abs(1 / rayDirX);
var deltaDistY = Math.abs(1 / rayDirY);
// Calculate step and initial sideDist
var stepX, sideDistX;
var stepY, sideDistY;
if (rayDirX < 0) {
stepX = -1;
sideDistX = (rayX / worldGrid.cellSize - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - rayX / worldGrid.cellSize) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (rayY / worldGrid.cellSize - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - rayY / worldGrid.cellSize) * deltaDistY;
}
// Perform DDA
var hit = false;
var side = 0; // 0 if x-side, 1 if y-side
var maxSteps = 100;
var steps = 0;
while (!hit && steps < maxSteps) {
steps++;
// Jump to next map square, either in x-direction, or in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
// Check if ray has hit a wall
if (worldGrid.hasWallAt(mapX * worldGrid.cellSize, mapY * worldGrid.cellSize)) {
hit = true;
}
}
var distance = 0;
if (hit) {
// Calculate distance
if (side === 0) {
distance = (mapX - rayX / worldGrid.cellSize + (1 - stepX) / 2) / rayDirX;
} else {
distance = (mapY - rayY / worldGrid.cellSize + (1 - stepY) / 2) / rayDirY;
}
distance = Math.abs(distance * worldGrid.cellSize);
}
return {
hit: hit,
distance: distance,
side: side,
mapX: mapX,
mapY: mapY
};
};
return self;
});
var SensitivityConfig = Container.expand(function () {
var self = Container.call(this);
// Load saved sensitivity or default to 50
self.sensitivity = storage.sensitivity || 50;
self.isVisible = false;
// Create background panel
var background = self.addChild(LK.getAsset('untexturedArea', {
anchorX: 0,
anchorY: 0,
width: 300,
height: 200,
alpha: 0.8
}));
background.tint = 0x222222;
// Create title text
var titleText = new Text2('Sensitivity', {
size: 40,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 150;
titleText.y = 20;
self.addChild(titleText);
// Create sensitivity value text
var valueText = new Text2(self.sensitivity.toString(), {
size: 35,
fill: 0xFFFFFF
});
valueText.anchor.set(0.5, 0);
valueText.x = 150;
valueText.y = 70;
self.addChild(valueText);
// Create decrease button (larger for mobile)
var decreaseBtn = self.addChild(LK.getAsset('untexturedArea', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 60
}));
decreaseBtn.x = 80;
decreaseBtn.y = 130;
decreaseBtn.tint = 0x666666;
var decreaseText = new Text2('-', {
size: 40,
fill: 0xFFFFFF
});
decreaseText.anchor.set(0.5, 0.5);
decreaseText.x = 80;
decreaseText.y = 130;
self.addChild(decreaseText);
// Create increase button (larger for mobile)
var increaseBtn = self.addChild(LK.getAsset('untexturedArea', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 60
}));
increaseBtn.x = 220;
increaseBtn.y = 130;
increaseBtn.tint = 0x666666;
var increaseText = new Text2('+', {
size: 40,
fill: 0xFFFFFF
});
increaseText.anchor.set(0.5, 0.5);
increaseText.x = 220;
increaseText.y = 130;
self.addChild(increaseText);
// Update sensitivity display
self.updateDisplay = function () {
valueText.setText(self.sensitivity.toString());
// Save to storage
storage.sensitivity = self.sensitivity;
};
// Toggle visibility
self.toggle = function () {
self.isVisible = !self.isVisible;
self.visible = self.isVisible;
};
// Handle decrease button with visual feedback
decreaseBtn.down = function (x, y, obj) {
decreaseBtn.tint = 0x888888; // Lighten on press
if (self.sensitivity > 0) {
self.sensitivity = Math.max(0, self.sensitivity - 5);
self.updateDisplay();
}
};
decreaseBtn.up = function (x, y, obj) {
decreaseBtn.tint = 0x666666; // Reset color on release
};
// Handle increase button with visual feedback
increaseBtn.down = function (x, y, obj) {
increaseBtn.tint = 0x888888; // Lighten on press
if (self.sensitivity < 100) {
self.sensitivity = Math.min(100, self.sensitivity + 5);
self.updateDisplay();
}
};
increaseBtn.up = function (x, y, obj) {
increaseBtn.tint = 0x666666; // Reset color on release
};
// Add background click handler to prevent game interactions
background.down = function (x, y, obj) {
// Prevent event from bubbling to game
return true;
};
background.up = function (x, y, obj) {
// Prevent event from bubbling to game
return true;
};
background.move = function (x, y, obj) {
// Prevent event from bubbling to game
return true;
};
// Initially hidden
self.visible = false;
return self;
});
/****
* Initialize Game
****/
// Create player
var game = new LK.Game({
backgroundColor: 0x000000,
orientation: 'landscape'
});
/****
* Game Code
****/
// World coordinate system - grid-based layout with procedural generation
var worldGrid = {
cellSize: 200,
width: 100,
// Expanded world size for infinite generation
height: 100,
walls: [],
// Will store wall positions
// Initialize world grid with walls
initializeGrid: function initializeGrid() {
// Initialize walls array first - fill entire world with walls initially
this.walls = [];
for (var x = 0; x < this.width; x++) {
this.walls[x] = [];
for (var y = 0; y < this.height; y++) {
// Start with all walls - procedural generation will carve out spaces
this.walls[x][y] = true;
}
}
// Create a starting room around spawn point (3x3 room)
var spawnX = Math.floor(this.width / 2);
var spawnY = Math.floor(this.height / 2);
for (var x = spawnX - 1; x <= spawnX + 1; x++) {
for (var y = spawnY - 1; y <= spawnY + 1; y++) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
this.walls[x][y] = false;
}
}
}
},
// Check if a world position has a wall
hasWallAt: function hasWallAt(worldX, worldY) {
var gridX = Math.floor(worldX / this.cellSize);
var gridY = Math.floor(worldY / this.cellSize);
if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) {
return true; // Outside bounds = wall
}
return this.walls[gridX][gridY];
},
// Check collision with wall boundaries (with player radius) - optimized
checkCollision: function checkCollision(worldX, worldY, radius) {
radius = radius || 20; // Default player radius
var gridX = Math.floor(worldX / this.cellSize);
var gridY = Math.floor(worldY / this.cellSize);
// Quick bounds check first
if (gridX < 1 || gridX >= this.width - 1 || gridY < 1 || gridY >= this.height - 1) {
return true; // Near or outside bounds = collision
}
// Optimized collision check - only check center and edges that matter for movement
var checkPoints = [
// Center point
{
x: worldX,
y: worldY
},
// Movement-relevant edges
{
x: worldX - radius,
y: worldY
},
// Left edge
{
x: worldX + radius,
y: worldY
},
// Right edge
{
x: worldX,
y: worldY - radius
},
// Top edge
{
x: worldX,
y: worldY + radius
} // Bottom edge
];
for (var i = 0; i < checkPoints.length; i++) {
var point = checkPoints[i];
var pointGridX = Math.floor(point.x / this.cellSize);
var pointGridY = Math.floor(point.y / this.cellSize);
// Quick bounds check
if (pointGridX < 0 || pointGridX >= this.width || pointGridY < 0 || pointGridY >= this.height) {
return true;
}
// Check wall collision with early exit
if (this.walls[pointGridX][pointGridY]) {
return true;
}
}
return false;
},
// Convert screen coordinates to world coordinates
screenToWorld: function screenToWorld(screenX, screenY) {
return {
x: screenX,
y: screenY
};
},
// Convert world coordinates to screen coordinates
worldToScreen: function worldToScreen(worldX, worldY) {
return {
x: worldX,
y: worldY
};
}
};
// Initialize the world grid
worldGrid.initializeGrid();
// Create procedural generator
var procGen = new ProcGen();
// Generate initial chunks around spawn point
procGen.generateAroundPlayer(worldGrid.width * worldGrid.cellSize / 2, worldGrid.height * worldGrid.cellSize / 2);
// Add wall line completion system
worldGrid.completeWallLines = function () {
// Trace horizontal lines and complete them
for (var y = 0; y < this.height; y++) {
var wallStart = -1;
var wallEnd = -1;
// Find wall segments in this row
for (var x = 0; x < this.width; x++) {
if (this.walls[x][y]) {
if (wallStart === -1) {
wallStart = x; // Start of wall segment
}
wallEnd = x; // Update end of wall segment
} else {
// If we found a wall segment, complete the line between start and end
if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) {
for (var fillX = wallStart; fillX <= wallEnd; fillX++) {
this.walls[fillX][y] = true; // Fill the gap
}
}
wallStart = -1; // Reset for next segment
wallEnd = -1;
}
}
// Complete any remaining segment at end of row
if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) {
for (var fillX = wallStart; fillX <= wallEnd; fillX++) {
this.walls[fillX][y] = true;
}
}
}
// Trace vertical lines and complete them
for (var x = 0; x < this.width; x++) {
var wallStart = -1;
var wallEnd = -1;
// Find wall segments in this column
for (var y = 0; y < this.height; y++) {
if (this.walls[x][y]) {
if (wallStart === -1) {
wallStart = y; // Start of wall segment
}
wallEnd = y; // Update end of wall segment
} else {
// If we found a wall segment, complete the line between start and end
if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) {
for (var fillY = wallStart; fillY <= wallEnd; fillY++) {
this.walls[x][fillY] = true; // Fill the gap
}
}
wallStart = -1; // Reset for next segment
wallEnd = -1;
}
}
// Complete any remaining segment at end of column
if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) {
for (var fillY = wallStart; fillY <= wallEnd; fillY++) {
this.walls[x][fillY] = true;
}
}
}
};
// Apply wall line completion after initial generation
worldGrid.completeWallLines();
// Add dead-end detection system that preserves room connectivity
worldGrid.detectAndFillDeadEnds = function () {
// Find small isolated areas (not connected to main network) and mark them as walls
var visited = [];
// Initialize visited array
for (var x = 0; x < this.width; x++) {
visited[x] = [];
for (var y = 0; y < this.height; y++) {
visited[x][y] = false;
}
}
// Function to check if an area is a meaningful connected space
var isSignificantArea = function isSignificantArea(startX, startY) {
if (worldGrid.walls[startX][startY]) return true; // Wall positions are fine
if (visited[startX][startY]) return true; // Already processed
var localVisited = [];
for (var x = 0; x < worldGrid.width; x++) {
localVisited[x] = [];
for (var y = 0; y < worldGrid.height; y++) {
localVisited[x][y] = false;
}
}
var area = [];
var stack = [{
x: startX,
y: startY
}];
var hasChunkExit = false;
// Flood fill to find connected area
while (stack.length > 0) {
var current = stack.pop();
var x = current.x;
var y = current.y;
if (x < 0 || x >= worldGrid.width || y < 0 || y >= worldGrid.height) continue;
if (localVisited[x][y] || worldGrid.walls[x][y]) continue;
localVisited[x][y] = true;
visited[x][y] = true; // Mark as processed in main visited array
area.push({
x: x,
y: y
});
// Check if this area connects to chunk boundaries (significant exit)
if (x <= 1 || x >= worldGrid.width - 2 || y <= 1 || y >= worldGrid.height - 2) {
hasChunkExit = true;
}
// Add adjacent cells
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
stack.push({
x: x + directions[d].dx,
y: y + directions[d].dy
});
}
}
// Only fill small areas (less than 8 cells) that don't connect to chunk boundaries
if (area.length < 8 && !hasChunkExit) {
for (var i = 0; i < area.length; i++) {
worldGrid.walls[area[i].x][area[i].y] = true;
}
return false; // Area was filled
}
return true; // Area is significant and kept
};
// Check all open areas for significance
for (var x = 0; x < this.width; x++) {
for (var y = 0; y < this.height; y++) {
if (!visited[x][y] && !this.walls[x][y]) {
isSignificantArea(x, y);
}
}
}
};
// Apply dead-end detection after wall completion
worldGrid.detectAndFillDeadEnds();
// Add passage recognition system
worldGrid.passageRecognition = function () {
// Find all isolated rooms (areas without proper exits)
var isolatedRooms = this.findIsolatedRooms();
// Create passages for isolated rooms
for (var i = 0; i < isolatedRooms.length; i++) {
this.createPassageForRoom(isolatedRooms[i]);
}
};
// Find rooms that don't have adequate exits
worldGrid.findIsolatedRooms = function () {
var visited = [];
var isolatedRooms = [];
// Initialize visited array
for (var x = 0; x < this.width; x++) {
visited[x] = [];
for (var y = 0; y < this.height; y++) {
visited[x][y] = false;
}
}
// Check each open area for connectivity
for (var x = 1; x < this.width - 1; x++) {
for (var y = 1; y < this.height - 1; y++) {
if (!this.walls[x][y] && !visited[x][y]) {
var room = this.analyzeRoom(x, y, visited);
if (room && room.area.length >= 4) {
// Only consider rooms with at least 4 cells
var exitCount = this.countRoomExits(room);
if (exitCount === 0) {
isolatedRooms.push(room);
}
}
}
}
}
return isolatedRooms;
};
// Analyze a room starting from given coordinates
worldGrid.analyzeRoom = function (startX, startY, visited) {
var room = {
area: [],
bounds: {
minX: startX,
maxX: startX,
minY: startY,
maxY: startY
},
center: {
x: 0,
y: 0
}
};
var stack = [{
x: startX,
y: startY
}];
while (stack.length > 0) {
var current = stack.pop();
var x = current.x;
var y = current.y;
if (x < 0 || x >= this.width || y < 0 || y >= this.height) continue;
if (visited[x][y] || this.walls[x][y]) continue;
visited[x][y] = true;
room.area.push({
x: x,
y: y
});
// Update bounds
room.bounds.minX = Math.min(room.bounds.minX, x);
room.bounds.maxX = Math.max(room.bounds.maxX, x);
room.bounds.minY = Math.min(room.bounds.minY, y);
room.bounds.maxY = Math.max(room.bounds.maxY, y);
// Add adjacent cells
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
stack.push({
x: x + directions[d].dx,
y: y + directions[d].dy
});
}
}
// Calculate center
if (room.area.length > 0) {
var centerX = Math.floor((room.bounds.minX + room.bounds.maxX) / 2);
var centerY = Math.floor((room.bounds.minY + room.bounds.maxY) / 2);
room.center = {
x: centerX,
y: centerY
};
}
return room.area.length > 0 ? room : null;
};
// Count the number of exits a room has
worldGrid.countRoomExits = function (room) {
var exits = 0;
var checkedPositions = [];
// Check room perimeter for connections to other areas
for (var i = 0; i < room.area.length; i++) {
var cell = room.area[i];
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
var checkX = cell.x + directions[d].dx;
var checkY = cell.y + directions[d].dy;
// Skip if out of bounds
if (checkX < 0 || checkX >= this.width || checkY < 0 || checkY >= this.height) continue;
// If we find an open area that's not part of this room, it's a potential exit
if (!this.walls[checkX][checkY]) {
var isPartOfRoom = false;
for (var j = 0; j < room.area.length; j++) {
if (room.area[j].x === checkX && room.area[j].y === checkY) {
isPartOfRoom = true;
break;
}
}
if (!isPartOfRoom) {
// Check if this exit position was already counted
var posKey = checkX + ',' + checkY;
var alreadyCounted = false;
for (var k = 0; k < checkedPositions.length; k++) {
if (checkedPositions[k] === posKey) {
alreadyCounted = true;
break;
}
}
if (!alreadyCounted) {
exits++;
checkedPositions.push(posKey);
}
}
}
}
}
return exits;
};
// Create a passage for an isolated room
worldGrid.createPassageForRoom = function (room) {
if (!room || room.area.length === 0) return;
// Find the best direction to create a passage
var directions = [{
dx: 1,
dy: 0,
name: 'east'
}, {
dx: -1,
dy: 0,
name: 'west'
}, {
dx: 0,
dy: 1,
name: 'south'
}, {
dx: 0,
dy: -1,
name: 'north'
}];
var bestDirection = null;
var shortestDistance = Infinity;
// For each direction, find the shortest path to open space
for (var d = 0; d < directions.length; d++) {
var dir = directions[d];
var distance = this.findDistanceToOpenSpace(room.center.x, room.center.y, dir.dx, dir.dy);
if (distance < shortestDistance && distance > 0) {
shortestDistance = distance;
bestDirection = dir;
}
}
// Create passage in the best direction
if (bestDirection && shortestDistance <= 5) {
// Limit passage length
this.createPassageInDirection(room.center.x, room.center.y, bestDirection.dx, bestDirection.dy, shortestDistance);
} else {
// If no good direction found, create a passage to the nearest chunk boundary
this.createPassageToChunkBoundary(room);
}
};
// Find distance to open space in a given direction
worldGrid.findDistanceToOpenSpace = function (startX, startY, dirX, dirY) {
var distance = 0;
var maxDistance = 6; // Limit search distance
for (var i = 1; i <= maxDistance; i++) {
var checkX = startX + dirX * i;
var checkY = startY + dirY * i;
// Check bounds
if (checkX < 1 || checkX >= this.width - 1 || checkY < 1 || checkY >= this.height - 1) {
return maxDistance + 1; // Out of bounds
}
// If we find open space, return distance
if (!this.walls[checkX][checkY]) {
return i;
}
distance = i;
}
return distance;
};
// Create a passage in the specified direction
worldGrid.createPassageInDirection = function (startX, startY, dirX, dirY, length) {
for (var i = 0; i <= length; i++) {
var passageX = startX + dirX * i;
var passageY = startY + dirY * i;
// Ensure passage coordinates are valid
if (passageX >= 0 && passageX < this.width && passageY >= 0 && passageY < this.height) {
this.walls[passageX][passageY] = false;
// Create wider passage (2 cells wide) for better navigation
if (dirX !== 0) {
// Horizontal passage
if (passageY + 1 < this.height) this.walls[passageX][passageY + 1] = false;
if (passageY - 1 >= 0) this.walls[passageX][passageY - 1] = false;
} else {
// Vertical passage
if (passageX + 1 < this.width) this.walls[passageX + 1][passageY] = false;
if (passageX - 1 >= 0) this.walls[passageX - 1][passageY] = false;
}
}
}
};
// Create passage to chunk boundary if no nearby open space
worldGrid.createPassageToChunkBoundary = function (room) {
var centerX = room.center.x;
var centerY = room.center.y;
// Find closest chunk boundary
var distanceToLeft = centerX;
var distanceToRight = this.width - 1 - centerX;
var distanceToTop = centerY;
var distanceToBottom = this.height - 1 - centerY;
var minDistance = Math.min(distanceToLeft, distanceToRight, distanceToTop, distanceToBottom);
if (minDistance === distanceToLeft) {
// Create passage to left boundary
this.createPassageInDirection(centerX, centerY, -1, 0, distanceToLeft);
} else if (minDistance === distanceToRight) {
// Create passage to right boundary
this.createPassageInDirection(centerX, centerY, 1, 0, distanceToRight);
} else if (minDistance === distanceToTop) {
// Create passage to top boundary
this.createPassageInDirection(centerX, centerY, 0, -1, distanceToTop);
} else {
// Create passage to bottom boundary
this.createPassageInDirection(centerX, centerY, 0, 1, distanceToBottom);
}
};
// Apply passage recognition system after dead-end detection
worldGrid.passageRecognition();
// Add method to check for isolated areas near player and create passages
worldGrid.checkPlayerProximityForPassages = function (playerX, playerY) {
var playerGridX = Math.floor(playerX / this.cellSize);
var playerGridY = Math.floor(playerY / this.cellSize);
var checkRadius = 8; // Check 8 grid cells around player
// Check areas around player for potential isolation
for (var dx = -checkRadius; dx <= checkRadius; dx++) {
for (var dy = -checkRadius; dy <= checkRadius; dy++) {
var checkX = playerGridX + dx;
var checkY = playerGridY + dy;
// Skip if out of bounds
if (checkX < 1 || checkX >= this.width - 1 || checkY < 1 || checkY >= this.height - 1) continue;
// If this is an open area, check if it needs a passage
if (!this.walls[checkX][checkY]) {
var needsPassage = this.checkIfAreaNeedsPassage(checkX, checkY, playerGridX, playerGridY);
if (needsPassage) {
this.createEmergencyPassage(checkX, checkY, playerGridX, playerGridY);
}
}
}
}
};
// Check if an area needs an emergency passage
worldGrid.checkIfAreaNeedsPassage = function (areaX, areaY, playerX, playerY) {
// Quick flood fill to check if this area has limited connectivity
var visited = [];
for (var x = 0; x < this.width; x++) {
visited[x] = [];
for (var y = 0; y < this.height; y++) {
visited[x][y] = false;
}
}
var reachableCells = [];
var stack = [{
x: areaX,
y: areaY
}];
var hasChunkExit = false;
while (stack.length > 0 && reachableCells.length < 50) {
// Limit search for performance
var current = stack.pop();
var x = current.x;
var y = current.y;
if (x < 0 || x >= this.width || y < 0 || y >= this.height) continue;
if (visited[x][y] || this.walls[x][y]) continue;
visited[x][y] = true;
reachableCells.push({
x: x,
y: y
});
// Check if area connects to chunk boundaries
if (x <= 2 || x >= this.width - 3 || y <= 2 || y >= this.height - 3) {
hasChunkExit = true;
}
// Add adjacent cells
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
stack.push({
x: x + directions[d].dx,
y: y + directions[d].dy
});
}
}
// If area is small and doesn't connect to chunk boundaries, it needs a passage
return reachableCells.length < 20 && !hasChunkExit;
};
// Create an emergency passage from isolated area toward player or main areas
worldGrid.createEmergencyPassage = function (areaX, areaY, playerX, playerY) {
// Calculate direction toward player
var dirToPlayerX = playerX - areaX;
var dirToPlayerY = playerY - areaY;
// Normalize direction
var dirX = dirToPlayerX > 0 ? 1 : dirToPlayerX < 0 ? -1 : 0;
var dirY = dirToPlayerY > 0 ? 1 : dirToPlayerY < 0 ? -1 : 0;
// Create passage toward player or toward center
var targetX = dirX !== 0 ? areaX + dirX * 3 : areaX;
var targetY = dirY !== 0 ? areaY + dirY * 3 : areaY;
// Ensure target is within bounds
targetX = Math.max(1, Math.min(this.width - 2, targetX));
targetY = Math.max(1, Math.min(this.height - 2, targetY));
// Create L-shaped passage to target
this.createSimplePassage(areaX, areaY, targetX, targetY);
};
// Create a simple passage between two points
worldGrid.createSimplePassage = function (x1, y1, x2, y2) {
// Create horizontal segment first
var minX = Math.min(x1, x2);
var maxX = Math.max(x1, x2);
for (var x = minX; x <= maxX; x++) {
if (x >= 0 && x < this.width && y1 >= 0 && y1 < this.height) {
this.walls[x][y1] = false;
}
}
// Create vertical segment
var minY = Math.min(y1, y2);
var maxY = Math.max(y1, y2);
for (var y = minY; y <= maxY; y++) {
if (x2 >= 0 && x2 < this.width && y >= 0 && y < this.height) {
this.walls[x2][y] = false;
}
}
};
// Create geometric wall renderer
var wallRenderer = new GeometricWallRenderer();
game.addChild(wallRenderer);
// Create ceiling tile renderer
var ceilingTileRenderer = new CeilingTileRenderer();
game.addChild(ceilingTileRenderer);
ceilingTileRenderer.generateTiles();
// Function to find a safe spawn position
function findSafeSpawnPosition(startX, startY, searchRadius) {
searchRadius = searchRadius || 5;
// First check if starting position is safe
if (!worldGrid.checkCollision(startX, startY)) {
return {
x: startX,
y: startY
};
}
// Search in expanding circles for a safe position
for (var radius = 1; radius <= searchRadius; radius++) {
for (var angle = 0; angle < Math.PI * 2; angle += Math.PI / 8) {
var testX = startX + Math.cos(angle) * radius * worldGrid.cellSize;
var testY = startY + Math.sin(angle) * radius * worldGrid.cellSize;
// Check bounds
if (testX >= worldGrid.cellSize && testX < (worldGrid.width - 1) * worldGrid.cellSize && testY >= worldGrid.cellSize && testY < (worldGrid.height - 1) * worldGrid.cellSize) {
if (!worldGrid.checkCollision(testX, testY)) {
return {
x: testX,
y: testY
};
}
}
}
}
// If no safe position found in search radius, force create one
var fallbackX = Math.floor(worldGrid.width / 2) * worldGrid.cellSize;
var fallbackY = Math.floor(worldGrid.height / 2) * worldGrid.cellSize;
// Clear a 3x3 area around fallback position
var fallbackGridX = Math.floor(fallbackX / worldGrid.cellSize);
var fallbackGridY = Math.floor(fallbackY / worldGrid.cellSize);
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var clearX = fallbackGridX + dx;
var clearY = fallbackGridY + dy;
if (clearX >= 0 && clearX < worldGrid.width && clearY >= 0 && clearY < worldGrid.height) {
worldGrid.walls[clearX][clearY] = false;
}
}
}
return {
x: fallbackX,
y: fallbackY
};
}
// Create player
var player = new Player();
// Find safe spawn position
var spawnCenter = {
x: worldGrid.width * worldGrid.cellSize / 2,
y: worldGrid.height * worldGrid.cellSize / 2
};
var safeSpawn = findSafeSpawnPosition(spawnCenter.x, spawnCenter.y, 10);
// Position player at safe spawn location
player.x = safeSpawn.x;
player.y = safeSpawn.y;
player.targetX = safeSpawn.x;
player.targetY = safeSpawn.y;
game.addChild(player);
// Create raycasting renderer
var raycastRenderer = new RaycastRenderer();
game.addChild(raycastRenderer);
// FPS counter variables
var fpsCounter = 0;
var fpsDisplay = 0;
var lastFpsTime = Date.now();
// Create coordinate display text
var coordXText = new Text2('X: 0', {
size: 60,
fill: 0xFFFFFF
});
coordXText.anchor.set(0, 0);
coordXText.x = 120; // Avoid top-left 100x100 area
coordXText.y = 120;
LK.gui.addChild(coordXText);
var coordZText = new Text2('Z: 0', {
size: 60,
fill: 0xFFFFFF
});
coordZText.anchor.set(0, 0);
coordZText.x = 120; // Avoid top-left 100x100 area
coordZText.y = 200;
LK.gui.addChild(coordZText);
// Create FPS display text
var fpsText = new Text2('FPS: 60', {
size: 60,
fill: 0x00FF00
});
fpsText.anchor.set(0, 0);
fpsText.x = 120; // Avoid top-left 100x100 area
fpsText.y = 280;
LK.gui.addChild(fpsText);
// Create settings button in top-right corner (larger for mobile)
var settingsButton = LK.getAsset('untexturedArea', {
anchorX: 1,
anchorY: 0,
width: 120,
height: 120
});
settingsButton.tint = 0x444444;
settingsButton.alpha = 0.7;
LK.gui.topRight.addChild(settingsButton);
var settingsText = new Text2('⚙', {
size: 60,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsText.x = -60;
settingsText.y = 60;
LK.gui.topRight.addChild(settingsText);
// Create sensitivity configuration panel
var sensitivityConfig = new SensitivityConfig();
sensitivityConfig.x = 2732 - 320;
sensitivityConfig.y = 100;
LK.gui.addChild(sensitivityConfig);
// Create movement crosshair for better mobile controls
var movementCrosshair = new MovementCrosshair();
movementCrosshair.x = 200; // Position on left side
movementCrosshair.y = 2048 - 200; // Bottom left area
LK.gui.addChild(movementCrosshair);
// Movement flags
var moveForward = false;
var moveBackward = false;
var turnLeft = false;
var turnRight = false;
var lookUp = false;
var lookDown = false;
// Touch controls for movement
var touchStartX = 0;
var touchStartY = 0;
var touchActive = false;
// Settings button click handler
settingsButton.down = function (x, y, obj) {
sensitivityConfig.toggle();
};
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
touchActive = true;
// Forward movement on touch
moveForward = true;
};
game.up = function (x, y, obj) {
touchActive = false;
moveForward = false;
moveBackward = false;
turnLeft = false;
turnRight = false;
lookUp = false;
lookDown = false;
// Reset crosshair if not actively being used
if (!movementCrosshair.activeButton) {
movementCrosshair.resetMovement();
}
};
game.move = function (x, y, obj) {
if (!touchActive) return;
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
// Horizontal movement for turning
if (Math.abs(deltaX) > 50) {
if (deltaX > 0) {
turnRight = true;
turnLeft = false;
} else {
turnLeft = true;
turnRight = false;
}
} else {
turnLeft = false;
turnRight = false;
}
// Vertical movement - split between forward/backward and look up/down
if (Math.abs(deltaY) > 50) {
// If touch is in upper part of screen, use for looking up/down
if (y < 1024) {
// Upper half of screen for vertical look
if (deltaY < 0) {
lookUp = true;
lookDown = false;
} else {
lookDown = true;
lookUp = false;
}
moveForward = false;
moveBackward = false;
} else {
// Lower half of screen for movement
if (deltaY < 0) {
moveForward = true;
moveBackward = false;
} else {
moveBackward = true;
moveForward = false;
}
lookUp = false;
lookDown = false;
}
} else {
lookUp = false;
lookDown = false;
}
};
game.update = function () {
// Update player rotation speed based on sensitivity (0-100 maps to 0.05-0.2)
var sensitivityValue = sensitivityConfig.sensitivity;
player.rotSpeed = 0.05 + sensitivityValue / 100 * 0.15;
// Get movement state from crosshair
var crosshairState = movementCrosshair.getMovementState();
// Handle movement (combine touch controls and crosshair)
if (moveForward || crosshairState.forward) {
player.moveForward();
}
if (moveBackward || crosshairState.backward) {
player.moveBackward();
}
if (turnLeft || crosshairState.left) {
player.turnLeft();
}
if (turnRight || crosshairState.right) {
player.turnRight();
}
if (lookUp) {
player.lookUp();
}
if (lookDown) {
player.lookDown();
}
// Apply smooth interpolation
player.updateSmooth();
// Generate new chunks as player moves
if (LK.ticks % 30 === 0) {
// Check every 30 frames for performance
procGen.generateAroundPlayer(player.x, player.y);
// Check for isolated areas near player and create passages proactively
worldGrid.checkPlayerProximityForPassages(player.x, player.y);
}
// Render the raycasted view
raycastRenderer.render(player);
// Render walls
wallRenderer.render(player);
// Render ceiling tiles
ceilingTileRenderer.render(player);
// Update FPS counter
fpsCounter++;
var currentTime = Date.now();
if (currentTime - lastFpsTime >= 1000) {
// Update every second
fpsDisplay = fpsCounter;
fpsCounter = 0;
lastFpsTime = currentTime;
// Color code FPS display based on performance
var fpsColor = 0x00FF00; // Green for good FPS (60+)
if (fpsDisplay < 30) {
fpsColor = 0xFF0000; // Red for poor FPS
} else if (fpsDisplay < 50) {
fpsColor = 0xFFFF00; // Yellow for moderate FPS
}
fpsText.fill = fpsColor;
fpsText.setText('FPS: ' + fpsDisplay);
}
// Update coordinate display
var gridX = Math.floor(player.x / worldGrid.cellSize);
var gridZ = Math.floor(player.y / worldGrid.cellSize);
coordXText.setText('X: ' + gridX);
coordZText.setText('Z: ' + gridZ);
}; ===================================================================
--- original.js
+++ change.js
@@ -715,114 +715,114 @@
}
}
}
};
- // Create guaranteed exits to adjacent chunks (legacy method for compatibility)
- self.createChunkExits = function (offsetX, offsetY, mainRoom) {
- // Use the new multiple exits method
- self.createMultipleChunkExits(offsetX, offsetY, [mainRoom]);
- // Create dead-end corridors for exploration variety
- self.createDeadEndCorridors = function (offsetX, offsetY, rooms) {
- // Create 2-4 dead-end corridors per chunk
- var numDeadEnds = Math.floor(Math.random() * 3) + 2; // 2-4 dead ends
- for (var i = 0; i < numDeadEnds; i++) {
- // Choose a random room as starting point
- var sourceRoom = rooms[Math.floor(Math.random() * rooms.length)];
- // Choose a random direction for the dead-end
- var directions = [{
- dx: 1,
- dy: 0
- },
- // East
- {
- dx: -1,
- dy: 0
- },
- // West
- {
- dx: 0,
- dy: 1
- },
- // South
- {
- dx: 0,
- dy: -1
- } // North
- ];
- var direction = directions[Math.floor(Math.random() * directions.length)];
- // Create dead-end corridor of random length (2-5 cells)
- var corridorLength = Math.floor(Math.random() * 4) + 2;
- var startX = sourceRoom.centerX;
- var startY = sourceRoom.centerY;
- // Find a good starting point on the room edge
- var edgeStartX = startX;
- var edgeStartY = startY;
- // Move to room edge
- if (direction.dx !== 0) {
- edgeStartX = direction.dx > 0 ? sourceRoom.x + sourceRoom.width : sourceRoom.x - 1;
- } else {
- edgeStartY = direction.dy > 0 ? sourceRoom.y + sourceRoom.height : sourceRoom.y - 1;
- }
- // Create the dead-end corridor
- self.createDeadEndPath(edgeStartX, edgeStartY, direction.dx, direction.dy, corridorLength, offsetX, offsetY);
+ // Create dead-end corridors for exploration variety
+ self.createDeadEndCorridors = function (offsetX, offsetY, rooms) {
+ // Create 2-4 dead-end corridors per chunk
+ var numDeadEnds = Math.floor(Math.random() * 3) + 2; // 2-4 dead ends
+ for (var i = 0; i < numDeadEnds; i++) {
+ // Choose a random room as starting point
+ var sourceRoom = rooms[Math.floor(Math.random() * rooms.length)];
+ // Choose a random direction for the dead-end
+ var directions = [{
+ dx: 1,
+ dy: 0
+ },
+ // East
+ {
+ dx: -1,
+ dy: 0
+ },
+ // West
+ {
+ dx: 0,
+ dy: 1
+ },
+ // South
+ {
+ dx: 0,
+ dy: -1
+ } // North
+ ];
+ var direction = directions[Math.floor(Math.random() * directions.length)];
+ // Create dead-end corridor of random length (2-5 cells)
+ var corridorLength = Math.floor(Math.random() * 4) + 2;
+ var startX = sourceRoom.centerX;
+ var startY = sourceRoom.centerY;
+ // Find a good starting point on the room edge
+ var edgeStartX = startX;
+ var edgeStartY = startY;
+ // Move to room edge
+ if (direction.dx !== 0) {
+ edgeStartX = direction.dx > 0 ? sourceRoom.x + sourceRoom.width : sourceRoom.x - 1;
+ } else {
+ edgeStartY = direction.dy > 0 ? sourceRoom.y + sourceRoom.height : sourceRoom.y - 1;
}
- };
- // Create a single dead-end corridor path
- self.createDeadEndPath = function (startX, startY, dirX, dirY, length, chunkOffsetX, chunkOffsetY) {
- var currentX = startX;
- var currentY = startY;
- // Create corridor cells
- for (var i = 0; i < length; i++) {
- currentX += dirX;
- currentY += dirY;
- // Check bounds - stop if we're going outside the chunk or world
- if (currentX < chunkOffsetX + 1 || currentX >= chunkOffsetX + self.chunkSize - 1 || currentY < chunkOffsetY + 1 || currentY >= chunkOffsetY + self.chunkSize - 1 || currentX < 0 || currentX >= worldGrid.width || currentY < 0 || currentY >= worldGrid.height) {
- break;
- }
- // Carve out the corridor cell
- worldGrid.walls[currentX][currentY] = false;
- // Add some width to the corridor (occasionally)
- if (Math.random() < 0.3) {
- // Add perpendicular width
- var perpDirX = dirY; // Perpendicular direction
- var perpDirY = -dirX;
- var widthX = currentX + perpDirX;
- var widthY = currentY + perpDirY;
- if (widthX >= chunkOffsetX && widthX < chunkOffsetX + self.chunkSize && widthY >= chunkOffsetY && widthY < chunkOffsetY + self.chunkSize && widthX >= 0 && widthX < worldGrid.width && widthY >= 0 && widthY < worldGrid.height) {
- worldGrid.walls[widthX][widthY] = false;
- }
- }
- // Occasionally branch the dead-end
- if (i > 1 && Math.random() < 0.2) {
- // Create a short branch (1-2 cells)
- var branchLength = Math.floor(Math.random() * 2) + 1;
- var branchDirX = Math.random() < 0.5 ? dirY : -dirY; // Perpendicular directions
- var branchDirY = Math.random() < 0.5 ? -dirX : dirX;
- self.createDeadEndPath(currentX, currentY, branchDirX, branchDirY, branchLength, chunkOffsetX, chunkOffsetY);
- }
+ // Create the dead-end corridor
+ self.createDeadEndPath(edgeStartX, edgeStartY, direction.dx, direction.dy, corridorLength, offsetX, offsetY);
+ }
+ };
+ // Create a single dead-end corridor path
+ self.createDeadEndPath = function (startX, startY, dirX, dirY, length, chunkOffsetX, chunkOffsetY) {
+ var currentX = startX;
+ var currentY = startY;
+ // Create corridor cells
+ for (var i = 0; i < length; i++) {
+ currentX += dirX;
+ currentY += dirY;
+ // Check bounds - stop if we're going outside the chunk or world
+ if (currentX < chunkOffsetX + 1 || currentX >= chunkOffsetX + self.chunkSize - 1 || currentY < chunkOffsetY + 1 || currentY >= chunkOffsetY + self.chunkSize - 1 || currentX < 0 || currentX >= worldGrid.width || currentY < 0 || currentY >= worldGrid.height) {
+ break;
}
- // Create a small room at the end of some dead-ends (30% chance)
+ // Carve out the corridor cell
+ worldGrid.walls[currentX][currentY] = false;
+ // Add some width to the corridor (occasionally)
if (Math.random() < 0.3) {
- self.createDeadEndRoom(currentX, currentY, chunkOffsetX, chunkOffsetY);
+ // Add perpendicular width
+ var perpDirX = dirY; // Perpendicular direction
+ var perpDirY = -dirX;
+ var widthX = currentX + perpDirX;
+ var widthY = currentY + perpDirY;
+ if (widthX >= chunkOffsetX && widthX < chunkOffsetX + self.chunkSize && widthY >= chunkOffsetY && widthY < chunkOffsetY + self.chunkSize && widthX >= 0 && widthX < worldGrid.width && widthY >= 0 && widthY < worldGrid.height) {
+ worldGrid.walls[widthX][widthY] = false;
+ }
}
- };
- // Create a small room at the end of a dead-end corridor
- self.createDeadEndRoom = function (centerX, centerY, chunkOffsetX, chunkOffsetY) {
- // Create a small 2x2 or 3x3 room
- var roomSize = Math.floor(Math.random() * 2) + 2; // 2x2 or 3x3
- var halfSize = Math.floor(roomSize / 2);
- for (var dx = -halfSize; dx <= halfSize; dx++) {
- for (var dy = -halfSize; dy <= halfSize; dy++) {
- var roomX = centerX + dx;
- var roomY = centerY + dy;
- // Check bounds
- if (roomX >= chunkOffsetX && roomX < chunkOffsetX + self.chunkSize && roomY >= chunkOffsetY && roomY < chunkOffsetY + self.chunkSize && roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) {
- worldGrid.walls[roomX][roomY] = false;
- }
+ // Occasionally branch the dead-end
+ if (i > 1 && Math.random() < 0.2) {
+ // Create a short branch (1-2 cells)
+ var branchLength = Math.floor(Math.random() * 2) + 1;
+ var branchDirX = Math.random() < 0.5 ? dirY : -dirY; // Perpendicular directions
+ var branchDirY = Math.random() < 0.5 ? -dirX : dirX;
+ self.createDeadEndPath(currentX, currentY, branchDirX, branchDirY, branchLength, chunkOffsetX, chunkOffsetY);
+ }
+ }
+ // Create a small room at the end of some dead-ends (30% chance)
+ if (Math.random() < 0.3) {
+ self.createDeadEndRoom(currentX, currentY, chunkOffsetX, chunkOffsetY);
+ }
+ };
+ // Create a small room at the end of a dead-end corridor
+ self.createDeadEndRoom = function (centerX, centerY, chunkOffsetX, chunkOffsetY) {
+ // Create a small 2x2 or 3x3 room
+ var roomSize = Math.floor(Math.random() * 2) + 2; // 2x2 or 3x3
+ var halfSize = Math.floor(roomSize / 2);
+ for (var dx = -halfSize; dx <= halfSize; dx++) {
+ for (var dy = -halfSize; dy <= halfSize; dy++) {
+ var roomX = centerX + dx;
+ var roomY = centerY + dy;
+ // Check bounds
+ if (roomX >= chunkOffsetX && roomX < chunkOffsetX + self.chunkSize && roomY >= chunkOffsetY && roomY < chunkOffsetY + self.chunkSize && roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) {
+ worldGrid.walls[roomX][roomY] = false;
}
}
- };
+ }
};
+ // Create guaranteed exits to adjacent chunks (legacy method for compatibility)
+ self.createChunkExits = function (offsetX, offsetY, mainRoom) {
+ // Use the new multiple exits method
+ self.createMultipleChunkExits(offsetX, offsetY, [mainRoom]);
+ };
// Create L-shaped corridor between two points with much shorter segments
self.createCorridor = function (x1, y1, x2, y2) {
// Create narrower corridors for shorter navigation paths
var halfWidth = Math.floor(self.hallwayWidth / 2);