// 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 for (let x = 0; x < CHUNK_SIZE; x++) { const height = heightMap[x]; for (let y = floorY - height; y < floorY; y++) { chunkData[y * CHUNK_SIZE + x] = SAND; } // 3. Add grass on top of the hills if (height > baseHeight + 3) { // Only on higher parts chunkData[(floorY - height) * CHUNK_SIZE + x] = GRASS; } } // 2. Add water in the lowest areas for (let x = 0; x < CHUNK_SIZE; x++) { const height = heightMap[x]; // Add water where the height is close to the minimum if (height <= minHeight + 2) { // Add a few layers of water const waterDepth = 3; for (let d = 0; d < waterDepth; d++) { const y = floorY - height - d - 1; if (y >= 0) { 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; }