Compare commits
No commits in common. "ebb96846ed3e430eaa8e0841eaa1f410d99556be" and "8ef18f52abb41d3e3a961ac94c4bde7c90a1db95" have entirely different histories.
ebb96846ed
...
8ef18f52ab
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
.aider*
|
|
@ -17,7 +17,6 @@ const FLOWER_COLORS = ['#FF0000', '#FFFF00', '#FF00FF', '#FFA500', '#FFFFFF', '#
|
|||||||
const LEAF_COLOR = '#228B22';
|
const LEAF_COLOR = '#228B22';
|
||||||
const FIRE_COLORS = ['#FF0000', '#FF3300', '#FF6600', '#FF9900', '#FFCC00', '#FFFF00'];
|
const FIRE_COLORS = ['#FF0000', '#FF3300', '#FF6600', '#FF9900', '#FFCC00', '#FFFF00'];
|
||||||
const LAVA_COLORS = ['#FF0000', '#FF3300', '#FF4500', '#FF6600', '#FF8C00'];
|
const LAVA_COLORS = ['#FF0000', '#FF3300', '#FF4500', '#FF6600', '#FF8C00'];
|
||||||
const RABBIT_COLORS = ['#FFFFFF', '#E0E0E0', '#D3C8B4']; // White, Light Gray, Light Brown
|
|
||||||
|
|
||||||
// Color variation functions
|
// Color variation functions
|
||||||
function getRandomColorVariation(baseColor, range) {
|
function getRandomColorVariation(baseColor, range) {
|
||||||
@ -59,7 +58,6 @@ const TREE_SEED = 11;
|
|||||||
const LEAF = 12;
|
const LEAF = 12;
|
||||||
const FIRE = 13;
|
const FIRE = 13;
|
||||||
const LAVA = 14;
|
const LAVA = 14;
|
||||||
const RABBIT = 15;
|
|
||||||
|
|
||||||
// Flammable materials
|
// Flammable materials
|
||||||
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];
|
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];
|
||||||
|
@ -4,37 +4,29 @@ function updateSand(x, y) {
|
|||||||
if (getPixel(x, y + 1) === EMPTY) {
|
if (getPixel(x, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, SAND);
|
setPixel(x, y + 1, SAND);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to move down-left or down-right
|
// Try to move down-left or down-right
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, SAND);
|
setPixel(x - 1, y + 1, SAND);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, SAND);
|
setPixel(x + 1, y + 1, SAND);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Sand can displace water
|
// Sand can displace water
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
else if (getPixel(x, y + 1) === WATER) {
|
||||||
setPixel(x, y, WATER);
|
setPixel(x, y, WATER);
|
||||||
setPixel(x, y + 1, SAND);
|
setPixel(x, y + 1, SAND);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWater(x, y) {
|
function updateWater(x, y) {
|
||||||
let modified = false;
|
|
||||||
|
|
||||||
// Update water color dynamically
|
// Update water color dynamically
|
||||||
const metadata = getMetadata(x, y);
|
const metadata = getMetadata(x, y);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
if (metadata.waterColorTimer === undefined) {
|
if (metadata.waterColorTimer === undefined) {
|
||||||
metadata.waterColorTimer = 0;
|
metadata.waterColorTimer = 0;
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.waterColorTimer++;
|
metadata.waterColorTimer++;
|
||||||
@ -43,7 +35,6 @@ function updateWater(x, y) {
|
|||||||
if (metadata.waterColorTimer > 20 && Math.random() < 0.1) {
|
if (metadata.waterColorTimer > 20 && Math.random() < 0.1) {
|
||||||
metadata.colorIndex = Math.floor(Math.random() * 10);
|
metadata.colorIndex = Math.floor(Math.random() * 10);
|
||||||
metadata.waterColorTimer = 0;
|
metadata.waterColorTimer = 0;
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setMetadata(x, y, metadata);
|
setMetadata(x, y, metadata);
|
||||||
@ -54,20 +45,17 @@ function updateWater(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, WATER);
|
setPixel(x, y + 1, WATER);
|
||||||
moveMetadata(x, y, x, y + 1);
|
moveMetadata(x, y, x, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to move down-left or down-right
|
// Try to move down-left or down-right
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, WATER);
|
setPixel(x - 1, y + 1, WATER);
|
||||||
moveMetadata(x, y, x - 1, y + 1);
|
moveMetadata(x, y, x - 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, WATER);
|
setPixel(x + 1, y + 1, WATER);
|
||||||
moveMetadata(x, y, x + 1, y + 1);
|
moveMetadata(x, y, x + 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to spread horizontally
|
// Try to spread horizontally
|
||||||
else {
|
else {
|
||||||
@ -79,25 +67,21 @@ function updateWater(x, y) {
|
|||||||
if (goLeft && getPixel(x - 1, y) === EMPTY) {
|
if (goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y, WATER);
|
setPixel(x - 1, y, WATER);
|
||||||
moveMetadata(x, y, x - 1, y);
|
moved = true;
|
||||||
return true;
|
|
||||||
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y, WATER);
|
setPixel(x + 1, y, WATER);
|
||||||
moveMetadata(x, y, x + 1, y);
|
moved = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try the other direction if first failed
|
// Try the other direction if first failed
|
||||||
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y, WATER);
|
setPixel(x - 1, y, WATER);
|
||||||
moveMetadata(x, y, x - 1, y);
|
moved = true;
|
||||||
return true;
|
|
||||||
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y, WATER);
|
setPixel(x + 1, y, WATER);
|
||||||
moveMetadata(x, y, x + 1, y);
|
moved = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,18 +97,15 @@ function updateWater(x, y) {
|
|||||||
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
|
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
|
||||||
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
||||||
removeMetadata(x + dir.dx, y + dir.dy);
|
removeMetadata(x + dir.dx, y + dir.dy);
|
||||||
return true;
|
|
||||||
} else if (getPixel(x + dir.dx, y + dir.dy) === LAVA) {
|
} else if (getPixel(x + dir.dx, y + dir.dy) === LAVA) {
|
||||||
// Water turns lava into stone
|
// Water turns lava into stone
|
||||||
setPixel(x + dir.dx, y + dir.dy, STONE);
|
setPixel(x + dir.dx, y + dir.dy, STONE);
|
||||||
removeMetadata(x + dir.dx, y + dir.dy);
|
removeMetadata(x + dir.dx, y + dir.dy);
|
||||||
// Water is consumed in the process
|
// Water is consumed in the process
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modified;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDirt(x, y) {
|
function updateDirt(x, y) {
|
||||||
@ -132,30 +113,25 @@ function updateDirt(x, y) {
|
|||||||
if (getPixel(x, y + 1) === EMPTY) {
|
if (getPixel(x, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, DIRT);
|
setPixel(x, y + 1, DIRT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to move down-left or down-right
|
// Try to move down-left or down-right
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, DIRT);
|
setPixel(x - 1, y + 1, DIRT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, DIRT);
|
setPixel(x + 1, y + 1, DIRT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Dirt can displace water
|
// Dirt can displace water
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
else if (getPixel(x, y + 1) === WATER) {
|
||||||
setPixel(x, y, WATER);
|
setPixel(x, y, WATER);
|
||||||
setPixel(x, y + 1, DIRT);
|
setPixel(x, y + 1, DIRT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dirt can turn into grass if exposed to air above
|
// Dirt can turn into grass if exposed to air above
|
||||||
if (getPixel(x, y - 1) === EMPTY && Math.random() < 0.001) {
|
if (getPixel(x, y - 1) === EMPTY && Math.random() < 0.001) {
|
||||||
setPixel(x, y, GRASS);
|
setPixel(x, y, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dirt can randomly spawn seeds if exposed to air above
|
// Dirt can randomly spawn seeds if exposed to air above
|
||||||
@ -164,17 +140,12 @@ function updateDirt(x, y) {
|
|||||||
const seedRoll = Math.random();
|
const seedRoll = Math.random();
|
||||||
if (seedRoll < 0.0002) { // Grass blade seed (most common)
|
if (seedRoll < 0.0002) { // Grass blade seed (most common)
|
||||||
setPixel(x, y - 1, SEED);
|
setPixel(x, y - 1, SEED);
|
||||||
return true;
|
|
||||||
} else if (seedRoll < 0.00025) { // Flower seed (less common)
|
} else if (seedRoll < 0.00025) { // Flower seed (less common)
|
||||||
setPixel(x, y - 1, SEED);
|
setPixel(x, y - 1, SEED);
|
||||||
// Mark this seed as a flower seed (will be handled in updateSeed)
|
// Mark this seed as a flower seed (will be handled in updateSeed)
|
||||||
setMetadata(x, y - 1, { type: 'flower' });
|
setMetadata(x, y - 1, { type: 'flower' });
|
||||||
return true;
|
|
||||||
} else if (seedRoll < 0.00026) { // Tree seed (rare)
|
} else if (seedRoll < 0.00026) { // Tree seed (rare)
|
||||||
setPixel(x, y - 1, TREE_SEED);
|
setPixel(x, y - 1, TREE_SEED);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
let fireUpdateCounter = 0;
|
let fireUpdateCounter = 0;
|
||||||
|
|
||||||
function updateFire(x, y) {
|
function updateFire(x, y) {
|
||||||
let modified = false;
|
|
||||||
const metadata = getMetadata(x, y);
|
const metadata = getMetadata(x, y);
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
@ -11,17 +10,15 @@ function updateFire(x, y) {
|
|||||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||||
});
|
});
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrease lifetime
|
// Decrease lifetime
|
||||||
metadata.lifetime--;
|
metadata.lifetime--;
|
||||||
modified = true;
|
|
||||||
|
|
||||||
// Randomly change color for flickering effect
|
// Randomly change color for flickering effect
|
||||||
if (Math.random() < 0.2) {
|
if (Math.random() < 0.2) {
|
||||||
metadata.colorIndex = Math.floor(Math.random() * FIRE_COLORS.length);
|
metadata.colorIndex = Math.floor(Math.random() * FIRE_COLORS.length);
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata
|
||||||
@ -32,7 +29,7 @@ function updateFire(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y - 1, FIRE);
|
setPixel(x, y - 1, FIRE);
|
||||||
moveMetadata(x, y, x, y - 1);
|
moveMetadata(x, y, x, y - 1);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire can also move slightly to the sides
|
// Fire can also move slightly to the sides
|
||||||
@ -42,7 +39,7 @@ function updateFire(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + direction, y - 1, FIRE);
|
setPixel(x + direction, y - 1, FIRE);
|
||||||
moveMetadata(x, y, x + direction, y - 1);
|
moveMetadata(x, y, x + direction, y - 1);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +61,6 @@ function updateFire(x, y) {
|
|||||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||||
});
|
});
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,14 +68,10 @@ function updateFire(x, y) {
|
|||||||
if (metadata.lifetime <= 0) {
|
if (metadata.lifetime <= 0) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
removeMetadata(x, y);
|
removeMetadata(x, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return modified;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLava(x, y) {
|
function updateLava(x, y) {
|
||||||
let modified = false;
|
|
||||||
const metadata = getMetadata(x, y);
|
const metadata = getMetadata(x, y);
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
@ -87,13 +79,11 @@ function updateLava(x, y) {
|
|||||||
setMetadata(x, y, {
|
setMetadata(x, y, {
|
||||||
colorIndex: Math.floor(Math.random() * LAVA_COLORS.length)
|
colorIndex: Math.floor(Math.random() * LAVA_COLORS.length)
|
||||||
});
|
});
|
||||||
modified = true;
|
|
||||||
} else {
|
} else {
|
||||||
// Randomly change color for flowing effect
|
// Randomly change color for flowing effect
|
||||||
if (Math.random() < 0.1) {
|
if (Math.random() < 0.1) {
|
||||||
metadata.colorIndex = Math.floor(Math.random() * LAVA_COLORS.length);
|
metadata.colorIndex = Math.floor(Math.random() * LAVA_COLORS.length);
|
||||||
setMetadata(x, y, metadata);
|
setMetadata(x, y, metadata);
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,20 +94,17 @@ function updateLava(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, LAVA);
|
setPixel(x, y + 1, LAVA);
|
||||||
moveMetadata(x, y, x, y + 1);
|
moveMetadata(x, y, x, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to move down-left or down-right
|
// Try to move down-left or down-right
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, LAVA);
|
setPixel(x - 1, y + 1, LAVA);
|
||||||
moveMetadata(x, y, x - 1, y + 1);
|
moveMetadata(x, y, x - 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, LAVA);
|
setPixel(x + 1, y + 1, LAVA);
|
||||||
moveMetadata(x, y, x + 1, y + 1);
|
moveMetadata(x, y, x + 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try to spread horizontally (slower than water)
|
// Try to spread horizontally (slower than water)
|
||||||
else if (Math.random() < 0.3) {
|
else if (Math.random() < 0.3) {
|
||||||
@ -128,24 +115,20 @@ function updateLava(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y, LAVA);
|
setPixel(x - 1, y, LAVA);
|
||||||
moveMetadata(x, y, x - 1, y);
|
moveMetadata(x, y, x - 1, y);
|
||||||
return true;
|
|
||||||
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y, LAVA);
|
setPixel(x + 1, y, LAVA);
|
||||||
moveMetadata(x, y, x + 1, y);
|
moveMetadata(x, y, x + 1, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Try the other direction if first failed
|
// Try the other direction if first failed
|
||||||
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y, LAVA);
|
setPixel(x - 1, y, LAVA);
|
||||||
moveMetadata(x, y, x - 1, y);
|
moveMetadata(x, y, x - 1, y);
|
||||||
return true;
|
|
||||||
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y, LAVA);
|
setPixel(x + 1, y, LAVA);
|
||||||
moveMetadata(x, y, x + 1, y);
|
moveMetadata(x, y, x + 1, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,21 +151,16 @@ function updateLava(x, y) {
|
|||||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||||
});
|
});
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lava can melt sand into glass (stone)
|
// Lava can melt sand into glass (stone)
|
||||||
else if (nearbyType === SAND && Math.random() < 0.05) {
|
else if (nearbyType === SAND && Math.random() < 0.05) {
|
||||||
setPixel(x + dir.dx, y + dir.dy, STONE);
|
setPixel(x + dir.dx, y + dir.dy, STONE);
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lava can burn dirt
|
// Lava can burn dirt
|
||||||
else if (nearbyType === DIRT && Math.random() < 0.02) {
|
else if (nearbyType === DIRT && Math.random() < 0.02) {
|
||||||
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
||||||
modified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modified;
|
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,18 @@ function updateGrass(x, y) {
|
|||||||
if (getPixel(x, y + 1) === EMPTY) {
|
if (getPixel(x, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, GRASS);
|
setPixel(x, y + 1, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, GRASS);
|
setPixel(x - 1, y + 1, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, GRASS);
|
setPixel(x + 1, y + 1, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
else if (getPixel(x, y + 1) === WATER) {
|
||||||
setPixel(x, y, WATER);
|
setPixel(x, y, WATER);
|
||||||
setPixel(x, y + 1, GRASS);
|
setPixel(x, y + 1, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grass can spread to nearby dirt
|
// Grass can spread to nearby dirt
|
||||||
@ -32,17 +28,13 @@ function updateGrass(x, y) {
|
|||||||
const dir = directions[Math.floor(Math.random() * directions.length)];
|
const dir = directions[Math.floor(Math.random() * directions.length)];
|
||||||
if (getPixel(x + dir.dx, y + dir.dy) === DIRT) {
|
if (getPixel(x + dir.dx, y + dir.dy) === DIRT) {
|
||||||
setPixel(x + dir.dx, y + dir.dy, GRASS);
|
setPixel(x + dir.dx, y + dir.dy, GRASS);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grass dies if covered (no air above)
|
// Grass dies if covered (no air above)
|
||||||
if (getPixel(x, y - 1) !== EMPTY && Math.random() < 0.05) {
|
if (getPixel(x, y - 1) !== EMPTY && Math.random() < 0.05) {
|
||||||
setPixel(x, y, DIRT);
|
setPixel(x, y, DIRT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSeed(x, y) {
|
function updateSeed(x, y) {
|
||||||
@ -51,24 +43,20 @@ function updateSeed(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, SEED);
|
setPixel(x, y + 1, SEED);
|
||||||
moveMetadata(x, y, x, y + 1);
|
moveMetadata(x, y, x, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, SEED);
|
setPixel(x - 1, y + 1, SEED);
|
||||||
moveMetadata(x, y, x - 1, y + 1);
|
moveMetadata(x, y, x - 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, SEED);
|
setPixel(x + 1, y + 1, SEED);
|
||||||
moveMetadata(x, y, x + 1, y + 1);
|
moveMetadata(x, y, x + 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Seeds can float on water
|
// Seeds can float on water
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
else if (getPixel(x, y + 1) === WATER) {
|
||||||
// Just float, don't do anything
|
// Just float, don't do anything
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// If seed is on dirt or grass, it can germinate
|
// If seed is on dirt or grass, it can germinate
|
||||||
else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
|
else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
|
||||||
@ -84,16 +72,12 @@ function updateSeed(x, y) {
|
|||||||
age: 0,
|
age: 0,
|
||||||
height: 1
|
height: 1
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
// Regular seed becomes a grass blade
|
// Regular seed becomes a grass blade
|
||||||
setPixel(x, y, GRASS_BLADE);
|
setPixel(x, y, GRASS_BLADE);
|
||||||
setMetadata(x, y, { age: 0, height: 1 });
|
setMetadata(x, y, { age: 0, height: 1 });
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGrassBlade(x, y) {
|
function updateGrassBlade(x, y) {
|
||||||
@ -102,7 +86,7 @@ function updateGrassBlade(x, y) {
|
|||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
setMetadata(x, y, { age: 0, height: 1 });
|
setMetadata(x, y, { age: 0, height: 1 });
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment age
|
// Increment age
|
||||||
@ -115,17 +99,13 @@ function updateGrassBlade(x, y) {
|
|||||||
setMetadata(x, y - 1, { age: 0, height: metadata.height + 1 });
|
setMetadata(x, y - 1, { age: 0, height: metadata.height + 1 });
|
||||||
metadata.isTop = false;
|
metadata.isTop = false;
|
||||||
setMetadata(x, y, metadata);
|
setMetadata(x, y, metadata);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grass blades die if covered by something other than another grass blade
|
// Grass blades die if covered by something other than another grass blade
|
||||||
if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== GRASS_BLADE && Math.random() < 0.01) {
|
if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== GRASS_BLADE && Math.random() < 0.01) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
removeMetadata(x, y);
|
removeMetadata(x, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFlower(x, y) {
|
function updateFlower(x, y) {
|
||||||
@ -139,7 +119,7 @@ function updateFlower(x, y) {
|
|||||||
age: 0,
|
age: 0,
|
||||||
height: 1
|
height: 1
|
||||||
});
|
});
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment age
|
// Increment age
|
||||||
@ -159,14 +139,12 @@ function updateFlower(x, y) {
|
|||||||
});
|
});
|
||||||
metadata.isTop = false;
|
metadata.isTop = false;
|
||||||
setMetadata(x, y, metadata);
|
setMetadata(x, y, metadata);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flowers die if covered
|
// Flowers die if covered
|
||||||
if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== FLOWER && Math.random() < 0.01) {
|
if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== FLOWER && Math.random() < 0.01) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
removeMetadata(x, y);
|
removeMetadata(x, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flowers can drop seeds occasionally
|
// Flowers can drop seeds occasionally
|
||||||
@ -176,9 +154,6 @@ function updateFlower(x, y) {
|
|||||||
if (getPixel(x + dir, y) === EMPTY) {
|
if (getPixel(x + dir, y) === EMPTY) {
|
||||||
setPixel(x + dir, y, SEED);
|
setPixel(x + dir, y, SEED);
|
||||||
setMetadata(x + dir, y, { type: 'flower' });
|
setMetadata(x + dir, y, { type: 'flower' });
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
// Rabbit element behaviors
|
|
||||||
function updateRabbit(x, y) {
|
|
||||||
const metadata = getMetadata(x, y) || {};
|
|
||||||
|
|
||||||
// Initialize rabbit metadata if it doesn't exist
|
|
||||||
if (!metadata.initialized) {
|
|
||||||
metadata.initialized = true;
|
|
||||||
metadata.jumpCooldown = 0;
|
|
||||||
metadata.direction = Math.random() > 0.5 ? 1 : -1; // 1 for right, -1 for left
|
|
||||||
metadata.jumpHeight = 0;
|
|
||||||
metadata.colorIndex = Math.floor(Math.random() * RABBIT_COLORS.length);
|
|
||||||
setMetadata(x, y, metadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update metadata
|
|
||||||
metadata.jumpCooldown = Math.max(0, metadata.jumpCooldown - 1);
|
|
||||||
|
|
||||||
// Check if rabbit is on solid ground
|
|
||||||
const onGround = getPixel(x, y + 1) !== EMPTY && getPixel(x, y + 1) !== WATER;
|
|
||||||
|
|
||||||
// Rabbit falls if there's nothing below
|
|
||||||
if (!onGround && metadata.jumpHeight <= 0) {
|
|
||||||
if (getPixel(x, y + 1) === EMPTY) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x, y + 1, RABBIT);
|
|
||||||
moveMetadata(x, y, x, y + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Rabbit can swim but prefers not to
|
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
|
||||||
// 50% chance to swim down or stay in place
|
|
||||||
if (Math.random() < 0.5) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x, y + 1, RABBIT);
|
|
||||||
moveMetadata(x, y, x, y + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// When in water, try to jump out
|
|
||||||
metadata.jumpCooldown = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rabbit jumps occasionally when on ground
|
|
||||||
if (onGround && metadata.jumpCooldown === 0) {
|
|
||||||
// Start a jump
|
|
||||||
metadata.jumpHeight = 3 + Math.floor(Math.random() * 2); // Jump 3-4 blocks high
|
|
||||||
metadata.jumpCooldown = 30 + Math.floor(Math.random() * 50); // Wait 30-80 frames before next jump
|
|
||||||
|
|
||||||
// Randomly change direction sometimes
|
|
||||||
if (Math.random() < 0.3) {
|
|
||||||
metadata.direction = -metadata.direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMetadata(x, y, metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute jump if jump height is positive
|
|
||||||
if (metadata.jumpHeight > 0) {
|
|
||||||
// Move up
|
|
||||||
if (getPixel(x, y - 1) === EMPTY) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x, y - 1, RABBIT);
|
|
||||||
moveMetadata(x, y, x, y - 1);
|
|
||||||
metadata.jumpHeight--;
|
|
||||||
setMetadata(x, y - 1, metadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If can't move up, try moving diagonally up in current direction
|
|
||||||
else if (getPixel(x + metadata.direction, y - 1) === EMPTY) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x + metadata.direction, y - 1, RABBIT);
|
|
||||||
moveMetadata(x, y, x + metadata.direction, y - 1);
|
|
||||||
metadata.jumpHeight--;
|
|
||||||
setMetadata(x + metadata.direction, y - 1, metadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If can't move diagonally up, try moving horizontally
|
|
||||||
else if (getPixel(x + metadata.direction, y) === EMPTY) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x + metadata.direction, y, RABBIT);
|
|
||||||
moveMetadata(x, y, x + metadata.direction, y);
|
|
||||||
metadata.jumpHeight = 0; // End jump if blocked
|
|
||||||
setMetadata(x + metadata.direction, y, metadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If completely blocked, end jump
|
|
||||||
else {
|
|
||||||
metadata.jumpHeight = 0;
|
|
||||||
setMetadata(x, y, metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move horizontally when not jumping
|
|
||||||
else if (metadata.jumpCooldown > 0 && metadata.jumpCooldown < 15 && onGround) {
|
|
||||||
// Hop horizontally between jumps
|
|
||||||
if (getPixel(x + metadata.direction, y) === EMPTY) {
|
|
||||||
setPixel(x, y, EMPTY);
|
|
||||||
setPixel(x + metadata.direction, y, RABBIT);
|
|
||||||
moveMetadata(x, y, x + metadata.direction, y);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If blocked, try to change direction
|
|
||||||
else {
|
|
||||||
metadata.direction = -metadata.direction;
|
|
||||||
setMetadata(x, y, metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
@ -5,33 +5,26 @@ function updateTreeSeed(x, y) {
|
|||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x, y + 1, TREE_SEED);
|
setPixel(x, y + 1, TREE_SEED);
|
||||||
moveMetadata(x, y, x, y + 1);
|
moveMetadata(x, y, x, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x - 1, y + 1, TREE_SEED);
|
setPixel(x - 1, y + 1, TREE_SEED);
|
||||||
moveMetadata(x, y, x - 1, y + 1);
|
moveMetadata(x, y, x - 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||||
setPixel(x, y, EMPTY);
|
setPixel(x, y, EMPTY);
|
||||||
setPixel(x + 1, y + 1, TREE_SEED);
|
setPixel(x + 1, y + 1, TREE_SEED);
|
||||||
moveMetadata(x, y, x + 1, y + 1);
|
moveMetadata(x, y, x + 1, y + 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Seeds can float on water
|
// Seeds can float on water
|
||||||
else if (getPixel(x, y + 1) === WATER) {
|
else if (getPixel(x, y + 1) === WATER) {
|
||||||
// Just float, don't do anything
|
// Just float, don't do anything
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// If seed is on dirt or grass, it can grow into a tree
|
// If seed is on dirt or grass, it can grow into a tree
|
||||||
else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
|
else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
|
||||||
// Start growing a tree
|
// Start growing a tree
|
||||||
growTree(x, y);
|
growTree(x, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function growTree(x, y) {
|
function growTree(x, y) {
|
||||||
|
49
js/main.js
49
js/main.js
@ -47,16 +47,6 @@ window.onload = function() {
|
|||||||
|
|
||||||
// Initialize the first chunk and generate terrain around it
|
// Initialize the first chunk and generate terrain around it
|
||||||
getOrCreateChunk(0, 0);
|
getOrCreateChunk(0, 0);
|
||||||
|
|
||||||
// Explicitly create and mark the stone layer as dirty
|
|
||||||
for (let dx = -5; dx <= 5; dx++) {
|
|
||||||
const chunkX = dx;
|
|
||||||
const chunkY = 1; // Stone layer
|
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
|
||||||
getOrCreateChunk(chunkX, chunkY);
|
|
||||||
dirtyChunks.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateChunksAroundPlayer();
|
generateChunksAroundPlayer();
|
||||||
|
|
||||||
// Start the simulation loop
|
// Start the simulation loop
|
||||||
@ -75,47 +65,12 @@ function simulationLoop(timestamp) {
|
|||||||
fps = Math.round(1000 / deltaTime);
|
fps = Math.round(1000 / deltaTime);
|
||||||
document.getElementById('fps').textContent = `FPS: ${fps}`;
|
document.getElementById('fps').textContent = `FPS: ${fps}`;
|
||||||
|
|
||||||
// Update physics with timestamp for rate limiting
|
// Update physics
|
||||||
updatePhysics(timestamp);
|
updatePhysics();
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
render();
|
render();
|
||||||
|
|
||||||
// Memory management: Clean up chunk cache for chunks that are far away
|
|
||||||
if (timestamp % 5000 < 16) { // Run every ~5 seconds
|
|
||||||
cleanupChunkCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue the loop
|
// Continue the loop
|
||||||
requestAnimationFrame(simulationLoop);
|
requestAnimationFrame(simulationLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up chunk cache to prevent memory leaks
|
|
||||||
function cleanupChunkCache() {
|
|
||||||
if (!chunkCanvasCache) return;
|
|
||||||
|
|
||||||
const visibleChunks = getVisibleChunks();
|
|
||||||
const visibleKeys = new Set();
|
|
||||||
|
|
||||||
// Get all visible chunk keys
|
|
||||||
for (const { chunkX, chunkY } of visibleChunks) {
|
|
||||||
visibleKeys.add(getChunkKey(chunkX, chunkY));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove cached canvases for chunks that are far from view
|
|
||||||
for (const key of chunkCanvasCache.keys()) {
|
|
||||||
if (!visibleKeys.has(key)) {
|
|
||||||
// Keep stone layer chunks in cache longer
|
|
||||||
if (key.split(',')[1] === '1') {
|
|
||||||
// Only remove if it's really far away
|
|
||||||
const [chunkX, chunkY] = key.split(',').map(Number);
|
|
||||||
const centerChunkX = Math.floor(worldOffsetX / CHUNK_SIZE);
|
|
||||||
if (Math.abs(chunkX - centerChunkX) > 10) {
|
|
||||||
chunkCanvasCache.delete(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chunkCanvasCache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
// Physics simulation functions
|
// Physics simulation functions
|
||||||
function updatePhysics(timestamp) {
|
function updatePhysics() {
|
||||||
// Check if we should update physics based on the update rate
|
|
||||||
if (timestamp && lastPhysicsTime && timestamp - lastPhysicsTime < physicsUpdateRate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPhysicsTime = timestamp || 0;
|
|
||||||
|
|
||||||
// Get visible chunks
|
// Get visible chunks
|
||||||
const visibleChunks = getVisibleChunks();
|
const visibleChunks = getVisibleChunks();
|
||||||
|
|
||||||
@ -14,12 +7,8 @@ function updatePhysics(timestamp) {
|
|||||||
fireUpdateCounter++;
|
fireUpdateCounter++;
|
||||||
|
|
||||||
// Process each visible chunk
|
// Process each visible chunk
|
||||||
for (const { chunkX, chunkY, isVisible } of visibleChunks) {
|
for (const { chunkX, chunkY } of visibleChunks) {
|
||||||
// Skip physics calculations for chunks that are not visible
|
|
||||||
if (!isVisible) continue;
|
|
||||||
|
|
||||||
const chunk = getOrCreateChunk(chunkX, chunkY);
|
const chunk = getOrCreateChunk(chunkX, chunkY);
|
||||||
let chunkModified = false;
|
|
||||||
|
|
||||||
// Process from bottom to top, right to left for correct gravity simulation
|
// Process from bottom to top, right to left for correct gravity simulation
|
||||||
for (let y = CHUNK_SIZE - 1; y >= 0; y--) {
|
for (let y = CHUNK_SIZE - 1; y >= 0; y--) {
|
||||||
@ -37,42 +26,28 @@ function updatePhysics(timestamp) {
|
|||||||
const worldX = chunkX * CHUNK_SIZE + x;
|
const worldX = chunkX * CHUNK_SIZE + x;
|
||||||
const worldY = chunkY * CHUNK_SIZE + y;
|
const worldY = chunkY * CHUNK_SIZE + y;
|
||||||
|
|
||||||
// Use a lookup table for faster element updates
|
if (type === SAND) {
|
||||||
const updateFunctions = {
|
updateSand(worldX, worldY);
|
||||||
[SAND]: updateSand,
|
} else if (type === WATER) {
|
||||||
[WATER]: updateWater,
|
updateWater(worldX, worldY);
|
||||||
[DIRT]: updateDirt,
|
} else if (type === DIRT) {
|
||||||
[GRASS]: updateGrass,
|
updateDirt(worldX, worldY);
|
||||||
[SEED]: updateSeed,
|
} else if (type === GRASS) {
|
||||||
[GRASS_BLADE]: updateGrassBlade,
|
updateGrass(worldX, worldY);
|
||||||
[FLOWER]: updateFlower,
|
} else if (type === SEED) {
|
||||||
[TREE_SEED]: updateTreeSeed,
|
updateSeed(worldX, worldY);
|
||||||
[FIRE]: updateFire,
|
} else if (type === GRASS_BLADE) {
|
||||||
[LAVA]: updateLava
|
updateGrassBlade(worldX, worldY);
|
||||||
};
|
} else if (type === FLOWER) {
|
||||||
|
updateFlower(worldX, worldY);
|
||||||
const updateFunction = updateFunctions[type];
|
} else if (type === TREE_SEED) {
|
||||||
if (updateFunction) {
|
updateTreeSeed(worldX, worldY);
|
||||||
const wasModified = updateFunction(worldX, worldY);
|
} else if (type === FIRE) {
|
||||||
if (wasModified) {
|
updateFire(worldX, worldY);
|
||||||
chunkModified = true;
|
} else if (type === LAVA) {
|
||||||
}
|
updateLava(worldX, worldY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark chunk as dirty if it was modified
|
|
||||||
if (chunkModified) {
|
|
||||||
dirtyChunks.add(getChunkKey(chunkX, chunkY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adaptive physics rate based on FPS
|
|
||||||
if (fps < 30 && physicsUpdateRate < 32) {
|
|
||||||
// If FPS is low, update physics less frequently
|
|
||||||
physicsUpdateRate = Math.min(32, physicsUpdateRate + 2);
|
|
||||||
} else if (fps > 50 && physicsUpdateRate > 16) {
|
|
||||||
// If FPS is high, update physics more frequently
|
|
||||||
physicsUpdateRate = Math.max(16, physicsUpdateRate - 2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
218
js/render.js
218
js/render.js
@ -1,7 +1,4 @@
|
|||||||
// Rendering functions
|
// Rendering functions
|
||||||
// Cache for rendered chunks
|
|
||||||
let chunkCanvasCache = new Map();
|
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
// Clear the canvas
|
// Clear the canvas
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
@ -10,33 +7,17 @@ function render() {
|
|||||||
const visibleChunks = getVisibleChunks();
|
const visibleChunks = getVisibleChunks();
|
||||||
|
|
||||||
// Render each visible chunk
|
// Render each visible chunk
|
||||||
for (const { chunkX, chunkY, isVisible } of visibleChunks) {
|
for (const { chunkX, chunkY } of visibleChunks) {
|
||||||
// Skip rendering for chunks that are not visible
|
|
||||||
if (!isVisible) continue;
|
|
||||||
|
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
const key = getChunkKey(chunkX, chunkY);
|
||||||
|
|
||||||
if (!chunks.has(key)) continue;
|
if (!chunks.has(key)) continue;
|
||||||
|
|
||||||
|
const chunk = chunks.get(key);
|
||||||
|
|
||||||
// Calculate screen position of chunk
|
// Calculate screen position of chunk
|
||||||
const screenX = (chunkX * CHUNK_SIZE - worldOffsetX) * PIXEL_SIZE;
|
const screenX = (chunkX * CHUNK_SIZE - worldOffsetX) * PIXEL_SIZE;
|
||||||
const screenY = (chunkY * CHUNK_SIZE - worldOffsetY) * PIXEL_SIZE;
|
const screenY = (chunkY * CHUNK_SIZE - worldOffsetY) * PIXEL_SIZE;
|
||||||
|
|
||||||
// Check if we need to render this chunk
|
|
||||||
const needsRender = dirtyChunks.has(key) || worldMoved || !chunkCanvasCache.has(key);
|
|
||||||
|
|
||||||
// Always render the chunk if it's in the stone layer (for visibility)
|
|
||||||
// or if it needs rendering
|
|
||||||
if (needsRender) {
|
|
||||||
renderChunkToCache(chunkX, chunkY, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the cached chunk to the main canvas
|
|
||||||
const cachedCanvas = chunkCanvasCache.get(key);
|
|
||||||
if (cachedCanvas) {
|
|
||||||
ctx.drawImage(cachedCanvas, screenX, screenY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw chunk border in debug mode
|
// Draw chunk border in debug mode
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
ctx.strokeStyle = '#ff0000';
|
ctx.strokeStyle = '#ff0000';
|
||||||
@ -54,15 +35,85 @@ function render() {
|
|||||||
ctx.fillText(`${chunkX},${chunkY}`, screenX + 5, screenY + 15);
|
ctx.fillText(`${chunkX},${chunkY}`, screenX + 5, screenY + 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove this chunk from the dirty list after rendering
|
// Render each pixel in the chunk
|
||||||
if (dirtyChunks.has(key)) {
|
for (let y = 0; y < CHUNK_SIZE; y++) {
|
||||||
dirtyChunks.delete(key);
|
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||||
|
const index = y * CHUNK_SIZE + x;
|
||||||
|
const type = chunk[index];
|
||||||
|
|
||||||
|
if (type === EMPTY) continue;
|
||||||
|
|
||||||
|
// Set color based on type
|
||||||
|
if (type === SAND) {
|
||||||
|
ctx.fillStyle = SAND_COLOR;
|
||||||
|
} else if (type === WATER) {
|
||||||
|
// Get water color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = WATER_COLORS[colorIndex];
|
||||||
|
} else if (type === WALL) {
|
||||||
|
ctx.fillStyle = WALL_COLOR;
|
||||||
|
} else if (type === DIRT) {
|
||||||
|
// Get dirt color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = DIRT_COLORS[colorIndex];
|
||||||
|
} else if (type === STONE) {
|
||||||
|
// Get stone color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = STONE_COLORS[colorIndex];
|
||||||
|
} else if (type === GRASS) {
|
||||||
|
// Get grass color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = GRASS_COLORS[colorIndex];
|
||||||
|
} else if (type === WOOD) {
|
||||||
|
// Get wood color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = WOOD_COLORS[colorIndex];
|
||||||
|
} else if (type === SEED) {
|
||||||
|
ctx.fillStyle = SEED_COLOR;
|
||||||
|
} else if (type === GRASS_BLADE) {
|
||||||
|
// Use the same color variation as grass
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = GRASS_COLORS[colorIndex];
|
||||||
|
} else if (type === FLOWER) {
|
||||||
|
// Get flower color from metadata or use a default
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
ctx.fillStyle = metadata && metadata.color ? metadata.color : FLOWER_COLORS[0];
|
||||||
|
} else if (type === TREE_SEED) {
|
||||||
|
ctx.fillStyle = SEED_COLOR;
|
||||||
|
} else if (type === LEAF) {
|
||||||
|
// Get leaf color from metadata with variation
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = LEAF_COLORS[colorIndex];
|
||||||
|
} else if (type === FIRE) {
|
||||||
|
// Get fire color from metadata
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = FIRE_COLORS[colorIndex];
|
||||||
|
} else if (type === LAVA) {
|
||||||
|
// Get lava color from metadata
|
||||||
|
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||||
|
const colorIndex = metadata ? metadata.colorIndex : 0;
|
||||||
|
ctx.fillStyle = LAVA_COLORS[colorIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the pixel
|
||||||
|
ctx.fillRect(
|
||||||
|
screenX + x * PIXEL_SIZE,
|
||||||
|
screenY + y * PIXEL_SIZE,
|
||||||
|
PIXEL_SIZE,
|
||||||
|
PIXEL_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset world moved flag after rendering
|
|
||||||
worldMoved = false;
|
|
||||||
|
|
||||||
// Draw cursor position and update debug info
|
// Draw cursor position and update debug info
|
||||||
if (currentMouseX !== undefined && currentMouseY !== undefined) {
|
if (currentMouseX !== undefined && currentMouseY !== undefined) {
|
||||||
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;
|
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;
|
||||||
@ -95,114 +146,3 @@ function render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render a chunk to an offscreen canvas and cache it
|
|
||||||
function renderChunkToCache(chunkX, chunkY, key) {
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
|
|
||||||
// Create a new canvas for this chunk if it doesn't exist
|
|
||||||
if (!chunkCanvasCache.has(key)) {
|
|
||||||
const chunkCanvas = document.createElement('canvas');
|
|
||||||
chunkCanvas.width = CHUNK_SIZE * PIXEL_SIZE;
|
|
||||||
chunkCanvas.height = CHUNK_SIZE * PIXEL_SIZE;
|
|
||||||
chunkCanvasCache.set(key, chunkCanvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkCanvas = chunkCanvasCache.get(key);
|
|
||||||
const chunkCtx = chunkCanvas.getContext('2d');
|
|
||||||
|
|
||||||
// Clear the chunk canvas
|
|
||||||
chunkCtx.clearRect(0, 0, chunkCanvas.width, chunkCanvas.height);
|
|
||||||
|
|
||||||
// Render each pixel in the chunk
|
|
||||||
for (let y = 0; y < CHUNK_SIZE; y++) {
|
|
||||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
|
||||||
const index = y * CHUNK_SIZE + x;
|
|
||||||
const type = chunk[index];
|
|
||||||
|
|
||||||
// Always render stone layer even if it's not directly visible
|
|
||||||
if (type === EMPTY && chunkY !== 1) continue;
|
|
||||||
|
|
||||||
// For the stone layer (chunkY = 1), render a faint background even for empty spaces
|
|
||||||
if (type === EMPTY && chunkY === 1) {
|
|
||||||
// Use a very faint gray for empty spaces in the stone layer
|
|
||||||
chunkCtx.fillStyle = 'rgba(100, 100, 100, 0.2)';
|
|
||||||
chunkCtx.fillRect(
|
|
||||||
x * PIXEL_SIZE,
|
|
||||||
y * PIXEL_SIZE,
|
|
||||||
PIXEL_SIZE,
|
|
||||||
PIXEL_SIZE
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set color based on type
|
|
||||||
if (type === SAND) {
|
|
||||||
chunkCtx.fillStyle = SAND_COLOR;
|
|
||||||
} else if (type === WATER) {
|
|
||||||
// Get water color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = WATER_COLORS[colorIndex];
|
|
||||||
} else if (type === WALL) {
|
|
||||||
chunkCtx.fillStyle = WALL_COLOR;
|
|
||||||
} else if (type === DIRT) {
|
|
||||||
// Get dirt color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = DIRT_COLORS[colorIndex];
|
|
||||||
} else if (type === STONE) {
|
|
||||||
// Get stone color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = STONE_COLORS[colorIndex];
|
|
||||||
} else if (type === GRASS) {
|
|
||||||
// Get grass color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = GRASS_COLORS[colorIndex];
|
|
||||||
} else if (type === WOOD) {
|
|
||||||
// Get wood color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = WOOD_COLORS[colorIndex];
|
|
||||||
} else if (type === SEED) {
|
|
||||||
chunkCtx.fillStyle = SEED_COLOR;
|
|
||||||
} else if (type === GRASS_BLADE) {
|
|
||||||
// Use the same color variation as grass
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = GRASS_COLORS[colorIndex];
|
|
||||||
} else if (type === FLOWER) {
|
|
||||||
// Get flower color from metadata or use a default
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
chunkCtx.fillStyle = metadata && metadata.color ? metadata.color : FLOWER_COLORS[0];
|
|
||||||
} else if (type === TREE_SEED) {
|
|
||||||
chunkCtx.fillStyle = SEED_COLOR;
|
|
||||||
} else if (type === LEAF) {
|
|
||||||
// Get leaf color from metadata with variation
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = LEAF_COLORS[colorIndex];
|
|
||||||
} else if (type === FIRE) {
|
|
||||||
// Get fire color from metadata
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = FIRE_COLORS[colorIndex];
|
|
||||||
} else if (type === LAVA) {
|
|
||||||
// Get lava color from metadata
|
|
||||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
|
||||||
const colorIndex = metadata ? metadata.colorIndex : 0;
|
|
||||||
chunkCtx.fillStyle = LAVA_COLORS[colorIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the pixel
|
|
||||||
chunkCtx.fillRect(
|
|
||||||
x * PIXEL_SIZE,
|
|
||||||
y * PIXEL_SIZE,
|
|
||||||
PIXEL_SIZE,
|
|
||||||
PIXEL_SIZE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
479
js/world.js
479
js/world.js
@ -6,19 +6,12 @@ let worldOffsetYBeforeDrag = 0;
|
|||||||
let chunks = new Map(); // Map to store chunks with key "x,y"
|
let chunks = new Map(); // Map to store chunks with key "x,y"
|
||||||
let metadata = new Map(); // Map to store metadata for pixels
|
let metadata = new Map(); // Map to store metadata for pixels
|
||||||
let generatedChunks = new Set(); // Set to track which chunks have been generated
|
let generatedChunks = new Set(); // Set to track which chunks have been generated
|
||||||
let dirtyChunks = new Set(); // Set to track which chunks need rendering
|
|
||||||
let lastPhysicsTime = 0; // Last time physics was updated
|
|
||||||
let physicsUpdateRate = 16; // Update physics every 16ms (approx 60fps)
|
|
||||||
let worldMoved = false; // Track if the world has moved for rendering
|
|
||||||
|
|
||||||
function moveWorld(dx, dy) {
|
function moveWorld(dx, dy) {
|
||||||
worldOffsetX += dx;
|
worldOffsetX += dx;
|
||||||
worldOffsetY += dy;
|
worldOffsetY += dy;
|
||||||
updateCoordinatesDisplay();
|
updateCoordinatesDisplay();
|
||||||
|
|
||||||
// Mark that the world has moved for rendering
|
|
||||||
worldMoved = true;
|
|
||||||
|
|
||||||
// Generate terrain for chunks around the current view
|
// Generate terrain for chunks around the current view
|
||||||
generateChunksAroundPlayer();
|
generateChunksAroundPlayer();
|
||||||
}
|
}
|
||||||
@ -40,41 +33,12 @@ function getOrCreateChunk(chunkX, chunkY) {
|
|||||||
// Create a new chunk with empty pixels
|
// Create a new chunk with empty pixels
|
||||||
const chunkData = new Array(CHUNK_SIZE * CHUNK_SIZE).fill(EMPTY);
|
const chunkData = new Array(CHUNK_SIZE * CHUNK_SIZE).fill(EMPTY);
|
||||||
|
|
||||||
// Fill chunk at y = 1 with stone, but create a noisy transition at the top
|
// Add floor at the bottom of the world (y = 0 and y = 1)
|
||||||
if (chunkY === 1) {
|
if (chunkY === 0 || chunkY === 1) {
|
||||||
// Use the chunk position as part of the seed for consistent generation
|
// Fill the bottom row with walls
|
||||||
const seed = chunkX * 10000;
|
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||||
const random = createSeededRandom(seed);
|
chunkData[(CHUNK_SIZE - 1) * CHUNK_SIZE + x] = WALL;
|
||||||
|
|
||||||
for (let y = 0; y < CHUNK_SIZE; y++) {
|
|
||||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
|
||||||
// Create a noisy transition at the top of the stone layer
|
|
||||||
if (y < 10) { // Only apply noise to the top 10 rows
|
|
||||||
// More noise at the top, less as we go down
|
|
||||||
const noiseThreshold = y / 10; // 0 at the top, 1 at row 10
|
|
||||||
|
|
||||||
if (random() > noiseThreshold) {
|
|
||||||
chunkData[y * CHUNK_SIZE + x] = SAND;
|
|
||||||
} else {
|
|
||||||
// Increase stone density to make it more visible
|
|
||||||
chunkData[y * CHUNK_SIZE + x] = random() < 0.9 ? STONE : EMPTY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Below the transition zone, it's all stone
|
|
||||||
chunkData[y * CHUNK_SIZE + x] = STONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this chunk as dirty to ensure it gets rendered
|
|
||||||
dirtyChunks.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floor has been removed as it's no longer needed
|
|
||||||
|
|
||||||
// Special generation for all chunks at y=0
|
|
||||||
if (chunkY === 0) {
|
|
||||||
generateSpecialChunk(chunkData, chunkX, chunkX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks.set(key, chunkData);
|
chunks.set(key, chunkData);
|
||||||
@ -83,302 +47,6 @@ function getOrCreateChunk(chunkX, chunkY) {
|
|||||||
return chunks.get(key);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Randomly spawn tree seeds on grass (reduced frequency)
|
|
||||||
if (random() < 0.03) { // Reduced from 8% to 3% chance for a tree seed on grass
|
|
||||||
const seedY = floorY - noiseHeight - 1; // Position above the grass
|
|
||||||
|
|
||||||
// Check if there are any existing tree seeds within 5 pixels
|
|
||||||
let hasSeedNearby = false;
|
|
||||||
for (let checkY = Math.max(0, seedY - 5); checkY <= Math.min(CHUNK_SIZE - 1, seedY + 5); checkY++) {
|
|
||||||
for (let checkX = Math.max(0, x - 5); checkX <= Math.min(CHUNK_SIZE - 1, x + 5); checkX++) {
|
|
||||||
if (chunkData[checkY * CHUNK_SIZE + checkX] === TREE_SEED) {
|
|
||||||
hasSeedNearby = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasSeedNearby) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's water below or nearby
|
|
||||||
let hasWaterBelow = false;
|
|
||||||
for (let checkY = floorY - noiseHeight + 1; checkY < Math.min(CHUNK_SIZE, floorY - noiseHeight + 5); checkY++) {
|
|
||||||
if (chunkData[checkY * CHUNK_SIZE + x] === WATER) {
|
|
||||||
hasWaterBelow = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only place the seed if there are no other seeds nearby and no water below
|
|
||||||
// Place seed 3 pixels above the surface instead of just 1
|
|
||||||
const elevatedSeedY = floorY - noiseHeight - 3; // 3 pixels above the grass
|
|
||||||
if (!hasSeedNearby && !hasWaterBelow && elevatedSeedY >= 0 && chunkData[(floorY - noiseHeight) * CHUNK_SIZE + x] === GRASS) {
|
|
||||||
chunkData[elevatedSeedY * CHUNK_SIZE + x] = TREE_SEED;
|
|
||||||
// Add metadata for the tree seed
|
|
||||||
const seedMetadata = {
|
|
||||||
age: Math.floor(random() * 50), // Random initial age
|
|
||||||
growthStage: 0,
|
|
||||||
type: 'oak' // Default tree type
|
|
||||||
};
|
|
||||||
// We'll set the metadata when the chunk is actually created
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add additional tree seeds scattered throughout the terrain with minimum spacing
|
|
||||||
const treePositions = []; // Track positions of placed tree seeds
|
|
||||||
|
|
||||||
for (let x = 0; x < CHUNK_SIZE; x += 15 + Math.floor(random() * 20)) { // Increased spacing from 5-15 to 15-35
|
|
||||||
const height = heightMap[x];
|
|
||||||
const surfaceY = floorY - height;
|
|
||||||
|
|
||||||
// Only place seeds on grass
|
|
||||||
if (chunkData[surfaceY * CHUNK_SIZE + x] === GRASS) {
|
|
||||||
// Reduced from 25% to 10% chance for a tree seed at each valid position
|
|
||||||
if (random() < 0.1) {
|
|
||||||
const seedY = surfaceY - 3; // Position 3 pixels above the grass instead of just 1
|
|
||||||
|
|
||||||
// Check if this position is at least 5 pixels away from any existing tree seed
|
|
||||||
let tooClose = false;
|
|
||||||
for (const pos of treePositions) {
|
|
||||||
const distance = Math.abs(x - pos.x) + Math.abs(seedY - pos.y); // Manhattan distance
|
|
||||||
if (distance < 5) {
|
|
||||||
tooClose = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's water below or nearby
|
|
||||||
let hasWaterBelow = false;
|
|
||||||
for (let checkY = surfaceY + 1; checkY < Math.min(CHUNK_SIZE, surfaceY + 5); checkY++) {
|
|
||||||
if (chunkData[checkY * CHUNK_SIZE + x] === WATER) {
|
|
||||||
hasWaterBelow = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only place the seed if it's not too close to another seed and no water below
|
|
||||||
if (!tooClose && !hasWaterBelow && seedY >= 0) {
|
|
||||||
chunkData[seedY * CHUNK_SIZE + x] = TREE_SEED;
|
|
||||||
treePositions.push({ x, y: seedY });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add some flower seeds in clusters near grass
|
|
||||||
for (let i = 0; i < 3; i++) { // Create a few flower clusters
|
|
||||||
const clusterX = Math.floor(random() * CHUNK_SIZE);
|
|
||||||
const clusterY = floorY - heightMap[clusterX];
|
|
||||||
|
|
||||||
if (chunkData[clusterY * CHUNK_SIZE + clusterX] === GRASS) {
|
|
||||||
// Create a small cluster of flower seeds
|
|
||||||
for (let dy = -1; dy <= 0; dy++) {
|
|
||||||
for (let dx = -2; dx <= 2; dx++) {
|
|
||||||
const seedX = clusterX + dx;
|
|
||||||
const seedY = clusterY + dy - 1; // Above the grass
|
|
||||||
|
|
||||||
if (seedX >= 0 && seedX < CHUNK_SIZE && seedY >= 0 &&
|
|
||||||
random() < 0.6 && // 60% chance for each position in the cluster
|
|
||||||
chunkData[(seedY+1) * CHUNK_SIZE + seedX] === GRASS) {
|
|
||||||
chunkData[seedY * CHUNK_SIZE + seedX] = SEED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChunkCoordinates(worldX, worldY) {
|
function getChunkCoordinates(worldX, worldY) {
|
||||||
const chunkX = Math.floor(worldX / CHUNK_SIZE);
|
const chunkX = Math.floor(worldX / CHUNK_SIZE);
|
||||||
const chunkY = Math.floor(worldY / CHUNK_SIZE);
|
const chunkY = Math.floor(worldY / CHUNK_SIZE);
|
||||||
@ -393,42 +61,26 @@ function setPixel(worldX, worldY, type) {
|
|||||||
const chunk = getOrCreateChunk(chunkX, chunkY);
|
const chunk = getOrCreateChunk(chunkX, chunkY);
|
||||||
const index = localY * CHUNK_SIZE + localX;
|
const index = localY * CHUNK_SIZE + localX;
|
||||||
|
|
||||||
// Only update if the pixel type is changing
|
chunk[index] = type;
|
||||||
if (chunk[index] !== type) {
|
|
||||||
chunk[index] = type;
|
|
||||||
|
|
||||||
// Mark chunk as dirty for rendering
|
// Assign random color index for natural elements
|
||||||
dirtyChunks.add(getChunkKey(chunkX, chunkY));
|
if (type === DIRT || type === GRASS || type === STONE || type === WOOD || type === LEAF) {
|
||||||
|
const colorIndex = Math.floor(Math.random() * 10);
|
||||||
// Assign random color index for natural elements
|
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex });
|
||||||
if (type === DIRT || type === GRASS || type === STONE || type === WOOD || type === LEAF) {
|
}
|
||||||
const colorIndex = Math.floor(Math.random() * 10);
|
else if (type === WATER) {
|
||||||
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex });
|
const colorIndex = Math.floor(Math.random() * 10);
|
||||||
}
|
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex, waterColorTimer: 0 });
|
||||||
else if (type === WATER) {
|
|
||||||
const colorIndex = Math.floor(Math.random() * 10);
|
|
||||||
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex, waterColorTimer: 0 });
|
|
||||||
}
|
|
||||||
else if (type === TREE_SEED) {
|
|
||||||
// Initialize tree seed metadata
|
|
||||||
setMetadata(worldX, worldY, {
|
|
||||||
age: Math.floor(Math.random() * 50), // Random initial age
|
|
||||||
growthStage: 0,
|
|
||||||
type: Math.random() < 0.8 ? 'oak' : 'pine' // 80% oak, 20% pine
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (type === SEED) {
|
|
||||||
// Initialize flower seed metadata
|
|
||||||
setMetadata(worldX, worldY, {
|
|
||||||
age: Math.floor(Math.random() * 30),
|
|
||||||
growthStage: 0,
|
|
||||||
flowerType: Math.floor(Math.random() * 5) // Different flower types
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPixel(worldX, worldY) {
|
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 { chunkX, chunkY, localX, localY } = getChunkCoordinates(worldX, worldY);
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
const key = getChunkKey(chunkX, chunkY);
|
||||||
|
|
||||||
@ -464,110 +116,23 @@ function moveMetadata(fromX, fromY, toX, toY) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
setMetadata(toX, toY, data);
|
setMetadata(toX, toY, data);
|
||||||
removeMetadata(fromX, fromY);
|
removeMetadata(fromX, fromY);
|
||||||
|
|
||||||
// Mark chunks as dirty for rendering
|
|
||||||
const { chunkX: fromChunkX, chunkY: fromChunkY } = getChunkCoordinates(fromX, fromY);
|
|
||||||
const { chunkX: toChunkX, chunkY: toChunkY } = getChunkCoordinates(toX, toY);
|
|
||||||
|
|
||||||
dirtyChunks.add(getChunkKey(fromChunkX, fromChunkY));
|
|
||||||
dirtyChunks.add(getChunkKey(toChunkX, toChunkY));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVisibleChunks() {
|
function getVisibleChunks() {
|
||||||
const visibleChunks = [];
|
const visibleChunks = [];
|
||||||
|
|
||||||
// Calculate visible chunk range (chunks that might be visible on screen)
|
// Calculate visible chunk range
|
||||||
const startChunkX = Math.floor(worldOffsetX / CHUNK_SIZE) - 1;
|
const startChunkX = Math.floor(worldOffsetX / CHUNK_SIZE) - 1;
|
||||||
const endChunkX = Math.ceil((worldOffsetX + canvas.width / PIXEL_SIZE) / CHUNK_SIZE) + 1;
|
const endChunkX = Math.ceil((worldOffsetX + canvas.width / PIXEL_SIZE) / CHUNK_SIZE) + 1;
|
||||||
const startChunkY = Math.floor(worldOffsetY / CHUNK_SIZE) - 1;
|
const startChunkY = Math.floor(worldOffsetY / CHUNK_SIZE) - 1;
|
||||||
const endChunkY = Math.ceil((worldOffsetY + canvas.height / PIXEL_SIZE) / CHUNK_SIZE) + 1;
|
const endChunkY = Math.ceil((worldOffsetY + canvas.height / PIXEL_SIZE) / CHUNK_SIZE) + 1;
|
||||||
|
|
||||||
// Calculate the exact visible area in world coordinates
|
|
||||||
const visibleStartX = worldOffsetX;
|
|
||||||
const visibleEndX = worldOffsetX + canvas.width / PIXEL_SIZE;
|
|
||||||
const visibleStartY = worldOffsetY;
|
|
||||||
const visibleEndY = worldOffsetY + canvas.height / PIXEL_SIZE;
|
|
||||||
|
|
||||||
for (let chunkY = startChunkY; chunkY < endChunkY; chunkY++) {
|
for (let chunkY = startChunkY; chunkY < endChunkY; chunkY++) {
|
||||||
for (let chunkX = startChunkX; chunkX < endChunkX; chunkX++) {
|
for (let chunkX = startChunkX; chunkX < endChunkX; chunkX++) {
|
||||||
// Calculate chunk boundaries in world coordinates
|
visibleChunks.push({ chunkX, chunkY });
|
||||||
const chunkWorldStartX = chunkX * CHUNK_SIZE;
|
|
||||||
const chunkWorldEndX = (chunkX + 1) * CHUNK_SIZE;
|
|
||||||
const chunkWorldStartY = chunkY * CHUNK_SIZE;
|
|
||||||
const chunkWorldEndY = (chunkY + 1) * CHUNK_SIZE;
|
|
||||||
|
|
||||||
// Check if this chunk is actually visible in the viewport
|
|
||||||
const isVisible = !(
|
|
||||||
chunkWorldEndX < visibleStartX ||
|
|
||||||
chunkWorldStartX > visibleEndX ||
|
|
||||||
chunkWorldEndY < visibleStartY ||
|
|
||||||
chunkWorldStartY > visibleEndY
|
|
||||||
);
|
|
||||||
|
|
||||||
visibleChunks.push({ chunkX, chunkY, isVisible });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return visibleChunks;
|
return visibleChunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateChunksAroundPlayer() {
|
|
||||||
const centerChunkX = Math.floor(worldOffsetX / CHUNK_SIZE);
|
|
||||||
const centerChunkY = Math.floor(worldOffsetY / CHUNK_SIZE);
|
|
||||||
const radius = 3; // Generate chunks within 3 chunks of the player
|
|
||||||
|
|
||||||
// Get visible chunks to prioritize their generation
|
|
||||||
const visibleChunks = getVisibleChunks();
|
|
||||||
const visibleChunkKeys = new Set(visibleChunks.map(chunk => getChunkKey(chunk.chunkX, chunk.chunkY)));
|
|
||||||
|
|
||||||
// Always generate the stone layer at y = 1 for visible chunks first
|
|
||||||
for (let dx = -radius; dx <= radius; dx++) {
|
|
||||||
const chunkX = centerChunkX + dx;
|
|
||||||
const chunkY = 1; // The chunk at y = 1 (moved from y = -1)
|
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
|
||||||
|
|
||||||
// Always generate stone layer chunks
|
|
||||||
const isNewChunk = !chunks.has(key);
|
|
||||||
getOrCreateChunk(chunkX, chunkY);
|
|
||||||
|
|
||||||
// Mark as dirty only if it's a new chunk
|
|
||||||
if (isNewChunk) {
|
|
||||||
dirtyChunks.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate visible chunks first
|
|
||||||
for (const { chunkX, chunkY, isVisible } of visibleChunks) {
|
|
||||||
if (isVisible) {
|
|
||||||
getOrCreateChunk(chunkX, chunkY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then generate non-visible chunks within the radius (with lower priority)
|
|
||||||
// Always generate the stone layer at y = 1 for remaining chunks
|
|
||||||
for (let dx = -radius; dx <= radius; dx++) {
|
|
||||||
const chunkX = centerChunkX + dx;
|
|
||||||
const chunkY = 1;
|
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
|
||||||
|
|
||||||
// Skip if already generated
|
|
||||||
if (!visibleChunkKeys.has(key)) {
|
|
||||||
getOrCreateChunk(chunkX, chunkY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate remaining chunks in a square around the player
|
|
||||||
for (let dy = -radius; dy <= radius; dy++) {
|
|
||||||
for (let dx = -radius; dx <= radius; dx++) {
|
|
||||||
const chunkX = centerChunkX + dx;
|
|
||||||
const chunkY = centerChunkY + dy;
|
|
||||||
const key = getChunkKey(chunkX, chunkY);
|
|
||||||
|
|
||||||
// Skip if already generated
|
|
||||||
if (!visibleChunkKeys.has(key)) {
|
|
||||||
getOrCreateChunk(chunkX, chunkY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user