// World management functions let worldOffsetX = 0; let worldOffsetY = 0; let worldOffsetXBeforeDrag = 0; let worldOffsetYBeforeDrag = 0; let chunks = new Map(); // Map to store chunks with key "x,y" let metadata = new Map(); // Map to store metadata for pixels let generatedChunks = new Set(); // Set to track which chunks have been generated function moveWorld(dx, dy) { worldOffsetX += dx; worldOffsetY += dy; updateCoordinatesDisplay(); // Generate terrain for chunks around the current view generateChunksAroundPlayer(); } function updateCoordinatesDisplay() { const chunkX = Math.floor(worldOffsetX / CHUNK_SIZE); const chunkY = Math.floor(worldOffsetY / CHUNK_SIZE); document.getElementById('coords').textContent = `Chunk: ${chunkX},${chunkY} | Offset: ${Math.floor(worldOffsetX)},${Math.floor(worldOffsetY)}`; } function getChunkKey(chunkX, chunkY) { return `${chunkX},${chunkY}`; } function getOrCreateChunk(chunkX, chunkY) { const key = getChunkKey(chunkX, chunkY); if (!chunks.has(key)) { // Create a new chunk with empty pixels const chunkData = new Array(CHUNK_SIZE * CHUNK_SIZE).fill(EMPTY); // Add floor at the bottom of the world (y = 0 and y = 1) if (chunkY === 0 || chunkY === 1) { // Fill the bottom row with walls for (let x = 0; x < CHUNK_SIZE; x++) { chunkData[(CHUNK_SIZE - 1) * CHUNK_SIZE + x] = WALL; } } // Get the current player chunk position const playerChunkX = Math.floor(worldOffsetX / CHUNK_SIZE); // Special generation for chunks within 3 chunks of the player's position if (chunkY === 0 && Math.abs(chunkX - playerChunkX) <= 3) { generateSpecialChunk(chunkData, chunkX, playerChunkX); } chunks.set(key, chunkData); } return chunks.get(key); } // Generate special terrain for chunks near the player function generateSpecialChunk(chunkData, chunkX, playerChunkX) { // 1. Create a base layer of sand above the floor const floorY = CHUNK_SIZE - 1; const baseHeight = 10; // Base height of sand // Use the chunk position as part of the seed for consistent generation const seed = chunkX * 10000; const random = createSeededRandom(seed); // Create two random hill points const hill1X = Math.floor(CHUNK_SIZE * (0.2 + random() * 0.2)); const hill2X = Math.floor(CHUNK_SIZE * (0.6 + random() * 0.2)); const hill1Height = baseHeight + Math.floor(random() * 10) + 5; // 5-15 blocks higher const hill2Height = baseHeight + Math.floor(random() * 10) + 5; // Generate height map for sand const heightMap = new Array(CHUNK_SIZE).fill(0); // Calculate heights based on distance from the two hills for (let x = 0; x < CHUNK_SIZE; x++) { // Distance from each hill (using a simple distance function) const dist1 = Math.abs(x - hill1X); const dist2 = Math.abs(x - hill2X); // Height contribution from each hill (inverse to distance) const h1 = hill1Height * Math.max(0, 1 - dist1 / (CHUNK_SIZE * 0.3)); const h2 = hill2Height * Math.max(0, 1 - dist2 / (CHUNK_SIZE * 0.3)); // Take the maximum height contribution heightMap[x] = Math.floor(baseHeight + Math.max(h1, h2)); // Add some variation based on distance from player's chunk const distanceFromPlayer = Math.abs(chunkX - playerChunkX); if (distanceFromPlayer > 0) { // Make terrain more extreme as we move away from player const factor = 1 + (distanceFromPlayer * 0.2); heightMap[x] = Math.floor(heightMap[x] * factor); } } // Find the lowest points for water let minHeight = Math.min(...heightMap); // Place sand according to the height map with noise for (let x = 0; x < CHUNK_SIZE; x++) { const height = heightMap[x]; // Add more noise to the height const noiseHeight = height + Math.floor(random() * 5) - 2; for (let y = floorY - noiseHeight; y < floorY; y++) { chunkData[y * CHUNK_SIZE + x] = SAND; } // 3. Add grass with significantly more coverage and noise // Increase grass probability for more coverage - now almost guaranteed const grassProbability = (height - baseHeight) / (hill1Height - baseHeight); if (random() < grassProbability * 0.3 + 0.7) { // Minimum 70% chance, up to 100% // Add grass on top chunkData[(floorY - noiseHeight) * CHUNK_SIZE + x] = GRASS; // Much more frequently add patches of grass on the sides if (random() < 0.8) { // Increased from 0.5 // Add grass to the left if possible if (x > 0 && chunkData[(floorY - noiseHeight) * CHUNK_SIZE + (x-1)] === SAND) { chunkData[(floorY - noiseHeight) * CHUNK_SIZE + (x-1)] = GRASS; } } if (random() < 0.8) { // Increased from 0.5 // Add grass to the right if possible if (x < CHUNK_SIZE-1 && chunkData[(floorY - noiseHeight) * CHUNK_SIZE + (x+1)] === SAND) { chunkData[(floorY - noiseHeight) * CHUNK_SIZE + (x+1)] = GRASS; } } // More frequently add grass patches below the top if (random() < 0.6 && noiseHeight > 2) { // Increased from 0.3 const patchDepth = Math.floor(random() * 5) + 2; // Increased max depth and minimum for (let d = 1; d <= patchDepth; d++) { if (floorY - noiseHeight + d < floorY) { chunkData[(floorY - noiseHeight + d) * CHUNK_SIZE + x] = GRASS; } } } // More frequently add grass clusters if (random() < 0.5) { // Increased from 0.2 // Add a larger cluster of grass for (let dy = -2; dy <= 1; dy++) { // Increased vertical range for (let dx = -2; dx <= 2; dx++) { // Increased horizontal range const nx = x + dx; const ny = floorY - noiseHeight + dy; if (nx >= 0 && nx < CHUNK_SIZE && ny >= 0 && ny < CHUNK_SIZE && (chunkData[ny * CHUNK_SIZE + nx] === SAND || chunkData[ny * CHUNK_SIZE + nx] === EMPTY)) { // Higher chance to place grass closer to center if (Math.abs(dx) + Math.abs(dy) <= 2 || random() < 0.7) { chunkData[ny * CHUNK_SIZE + nx] = GRASS; } } } } } // Sometimes add grass "islands" on top of sand if (random() < 0.15 && noiseHeight > 4) { // Add a small patch of grass above the surface const islandHeight = Math.floor(random() * 2) + 1; for (let d = 1; d <= islandHeight; d++) { const ny = floorY - noiseHeight - d; if (ny >= 0) { chunkData[ny * CHUNK_SIZE + x] = GRASS; } } } } } // 2. Add water in more areas with greater depth for (let x = 0; x < CHUNK_SIZE; x++) { const height = heightMap[x]; // Add water where the height is close to the minimum (increased threshold) if (height <= minHeight + 4) { // Increased from +2 to +4 // Add more layers of water const waterDepth = 5; // Increased from 3 to 5 for (let d = 0; d < waterDepth; d++) { const y = floorY - height - d - 1; if (y >= 0) { chunkData[y * CHUNK_SIZE + x] = WATER; } } } // Sometimes add small water pools in random depressions if (random() < 0.1 && height <= minHeight + 8 && height > minHeight + 4) { // Add a small pool of water const poolDepth = Math.floor(random() * 2) + 1; for (let d = 0; d < poolDepth; d++) { const y = floorY - height - d - 1; if (y >= 0) { chunkData[y * CHUNK_SIZE + x] = WATER; } } } } // Add some connected water channels between pools for (let x = 1; x < CHUNK_SIZE - 1; x++) { // Check if there's water to the left and right but not at this position const y = floorY - heightMap[x] - 1; const leftHasWater = x > 0 && chunkData[y * CHUNK_SIZE + (x-1)] === WATER; const rightHasWater = x < CHUNK_SIZE-1 && chunkData[y * CHUNK_SIZE + (x+1)] === WATER; if (leftHasWater && rightHasWater && chunkData[y * CHUNK_SIZE + x] !== WATER) { if (random() < 0.7) { // 70% chance to connect water bodies chunkData[y * CHUNK_SIZE + x] = WATER; } } } // Add some random elements based on the chunk position if (random() < 0.3) { // Add a small tree or plant cluster const plantX = Math.floor(random() * CHUNK_SIZE); const plantY = floorY - heightMap[plantX] - 1; if (plantY > 0 && chunkData[plantY * CHUNK_SIZE + plantX] === GRASS) { // Add a small tree for (let i = 0; i < 3; i++) { if (plantY - i > 0) { chunkData[(plantY - i) * CHUNK_SIZE + plantX] = WOOD; } } // Add some leaves for (let dy = -2; dy <= 0; dy++) { for (let dx = -2; dx <= 2; dx++) { const leafX = plantX + dx; const leafY = plantY - 3 + dy; if (leafX >= 0 && leafX < CHUNK_SIZE && leafY >= 0 && Math.abs(dx) + Math.abs(dy) < 3) { chunkData[leafY * CHUNK_SIZE + leafX] = LEAF; } } } } } } function getChunkCoordinates(worldX, worldY) { const chunkX = Math.floor(worldX / CHUNK_SIZE); const chunkY = Math.floor(worldY / CHUNK_SIZE); const localX = ((worldX % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; const localY = ((worldY % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; return { chunkX, chunkY, localX, localY }; } function setPixel(worldX, worldY, type) { const { chunkX, chunkY, localX, localY } = getChunkCoordinates(worldX, worldY); const chunk = getOrCreateChunk(chunkX, chunkY); const index = localY * CHUNK_SIZE + localX; chunk[index] = type; // Assign random color index for natural elements if (type === DIRT || type === GRASS || type === STONE || type === WOOD || type === LEAF) { const colorIndex = Math.floor(Math.random() * 10); setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex }); } else if (type === WATER) { const colorIndex = Math.floor(Math.random() * 10); setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex, waterColorTimer: 0 }); } } function getPixel(worldX, worldY) { // Special case: floor at the bottom of the world (first two chunks) const floorChunkY = Math.floor(worldY / CHUNK_SIZE); if (worldY % CHUNK_SIZE === CHUNK_SIZE - 1 && (floorChunkY === 0 || floorChunkY === 1)) { return WALL; } const { chunkX, chunkY, localX, localY } = getChunkCoordinates(worldX, worldY); const key = getChunkKey(chunkX, chunkY); if (!chunks.has(key)) { return EMPTY; } const chunk = chunks.get(key); const index = localY * CHUNK_SIZE + localX; return chunk[index]; } // Metadata functions to store additional information about pixels function setMetadata(worldX, worldY, data) { const key = `${worldX},${worldY}`; metadata.set(key, data); } function getMetadata(worldX, worldY) { const key = `${worldX},${worldY}`; return metadata.get(key); } function removeMetadata(worldX, worldY) { const key = `${worldX},${worldY}`; metadata.delete(key); } // Move metadata when a pixel moves function moveMetadata(fromX, fromY, toX, toY) { const data = getMetadata(fromX, fromY); if (data) { setMetadata(toX, toY, data); removeMetadata(fromX, fromY); } } function getVisibleChunks() { const visibleChunks = []; // Calculate visible chunk range const startChunkX = Math.floor(worldOffsetX / CHUNK_SIZE) - 1; const endChunkX = Math.ceil((worldOffsetX + canvas.width / PIXEL_SIZE) / CHUNK_SIZE) + 1; const startChunkY = Math.floor(worldOffsetY / CHUNK_SIZE) - 1; const endChunkY = Math.ceil((worldOffsetY + canvas.height / PIXEL_SIZE) / CHUNK_SIZE) + 1; for (let chunkY = startChunkY; chunkY < endChunkY; chunkY++) { for (let chunkX = startChunkX; chunkX < endChunkX; chunkX++) { visibleChunks.push({ chunkX, chunkY }); } } return visibleChunks; }