feat: Caves and ores
This commit is contained in:
parent
a736795bd7
commit
b04e73dee3
@ -7,7 +7,6 @@ import cz.jzitnik.game.entities.items.ItemBlockSupplier;
|
||||
import cz.jzitnik.game.sprites.Steve;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class Generation {
|
||||
@ -23,19 +22,17 @@ public class Generation {
|
||||
steveBlock2.setGhost(true);
|
||||
steveBlock2.setMob(true);
|
||||
|
||||
int[] terrainHeight = generateTerrain();
|
||||
int[] terrainHeight = PopulateWorld.generateTerrain();
|
||||
|
||||
game.getPlayer().setPlayerBlock1(steveBlock);
|
||||
game.getPlayer().setPlayerBlock2(steveBlock2);
|
||||
|
||||
populateWorld(world, terrainHeight);
|
||||
plantTrees(world, terrainHeight);
|
||||
PopulateWorld.populateWorld(world, terrainHeight);
|
||||
Trees.plantTrees(world, terrainHeight);
|
||||
|
||||
// Spawn player at a valid starting point
|
||||
world[terrainHeight[256] - 1][256].add(steveBlock2);
|
||||
world[terrainHeight[256] - 2][256].add(steveBlock);
|
||||
|
||||
game.getInventory().addItem(ItemBlockSupplier.getItem("shears"));
|
||||
}
|
||||
|
||||
private static void initializeWorld(List<Block>[][] world) {
|
||||
@ -45,110 +42,4 @@ public class Generation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] generateTerrain() {
|
||||
Random random = new Random();
|
||||
int baseHeight = 120;
|
||||
int[] terrainHeight = new int[512];
|
||||
|
||||
terrainHeight[0] = baseHeight;
|
||||
for (int i = 1; i < 512; i++) {
|
||||
int heightChange = random.nextInt(3) - 1;
|
||||
terrainHeight[i] = Math.max(100, Math.min(140, terrainHeight[i - 1] + heightChange));
|
||||
}
|
||||
|
||||
for (int i = 2; i < 510; i++) {
|
||||
terrainHeight[i] = (terrainHeight[i - 1] + terrainHeight[i] + terrainHeight[i + 1]) / 3;
|
||||
}
|
||||
|
||||
return terrainHeight;
|
||||
}
|
||||
|
||||
private static void populateWorld(List<Block>[][] world, int[] terrainHeight) {
|
||||
Random random = new Random();
|
||||
|
||||
for (int i = 0; i < 512; i++) {
|
||||
int hillHeight = terrainHeight[i];
|
||||
|
||||
world[hillHeight][i].add(ItemBlockSupplier.getBlock("grass"));
|
||||
|
||||
if (random.nextDouble() < 0.1 && !isTreeNearby(world, i, hillHeight)) {
|
||||
world[hillHeight - 1][i].add(ItemBlockSupplier.getBlock("grass_bush"));
|
||||
}
|
||||
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
if (hillHeight + j < 256) {
|
||||
world[hillHeight + j][i].add(ItemBlockSupplier.getBlock("dirt"));
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = hillHeight + 5; j < 250; j++) {
|
||||
world[j][i].add(ItemBlockSupplier.getBlock("stone"));
|
||||
}
|
||||
|
||||
world[255][i].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK));
|
||||
}
|
||||
|
||||
// Fill air blocks
|
||||
for (List<Block>[] lists : world) {
|
||||
for (List<Block> list : lists) {
|
||||
list.addFirst(new Block("air", SpriteLoader.SPRITES.AIR, true, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTreeNearby(List<Block>[][] world, int x, int y) {
|
||||
int radius = 3; // Check within a 3-block radius for trees
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
for (int dy = -radius; dy <= radius; dy++) {
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
if (nx >= 0 && nx < 512 && ny >= 0 && ny < 256) {
|
||||
for (Block block : world[ny][nx]) {
|
||||
if (block.getBlockId().equals("oak_log") || block.getBlockId().equals("oak_leaves")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static void plantTrees(List<Block>[][] world, int[] terrainHeight) {
|
||||
Random random = new Random();
|
||||
for (int i = 10; i < 502; i += random.nextInt(20) + 20) {
|
||||
int treeBase = terrainHeight[i];
|
||||
|
||||
if (treeBase - 3 < 0)
|
||||
continue;
|
||||
|
||||
tree(world, i, treeBase);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tree(List<Block>[][] world, int i, int treeBase) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (treeBase - j >= 0) {
|
||||
world[treeBase - j - 1][i].add(ItemBlockSupplier.getBlock("oak_log"));
|
||||
}
|
||||
}
|
||||
|
||||
int leafY = treeBase - 4;
|
||||
|
||||
for (int layer = 0; layer < 3; layer++) {
|
||||
int size = 5 - (layer * 2);
|
||||
int offsetY = leafY - layer;
|
||||
|
||||
for (int dx = -size / 2; dx <= size / 2; dx++) {
|
||||
int x = i + dx;
|
||||
int y = offsetY;
|
||||
|
||||
if (x >= 0 && x < world[0].length && y >= 0) {
|
||||
world[y][x].add(ItemBlockSupplier.getBlock("oak_leaves"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
191
src/main/java/cz/jzitnik/game/generation/PopulateWorld.java
Normal file
191
src/main/java/cz/jzitnik/game/generation/PopulateWorld.java
Normal file
@ -0,0 +1,191 @@
|
||||
package cz.jzitnik.game.generation;
|
||||
|
||||
import cz.jzitnik.game.entities.Block;
|
||||
import cz.jzitnik.game.SpriteLoader;
|
||||
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class PopulateWorld {
|
||||
private static final int WORLD_WIDTH = 512;
|
||||
private static final int WORLD_HEIGHT = 256;
|
||||
|
||||
public static int[] generateTerrain() {
|
||||
Random random = new Random();
|
||||
int baseHeight = 120;
|
||||
int[] terrainHeight = new int[WORLD_WIDTH];
|
||||
|
||||
terrainHeight[0] = baseHeight;
|
||||
for (int i = 1; i < WORLD_WIDTH; i++) {
|
||||
int heightChange = random.nextInt(3) - 1;
|
||||
terrainHeight[i] = Math.max(100, Math.min(140, terrainHeight[i - 1] + heightChange));
|
||||
}
|
||||
|
||||
for (int i = 2; i < WORLD_WIDTH - 2; i++) {
|
||||
terrainHeight[i] = (terrainHeight[i - 1] + terrainHeight[i] + terrainHeight[i + 1]) / 3;
|
||||
}
|
||||
|
||||
return terrainHeight;
|
||||
}
|
||||
|
||||
public static void populateWorld(List<Block>[][] world, int[] terrainHeight) {
|
||||
boolean[][] isCave = generateCavesWithCellularAutomata(terrainHeight);
|
||||
Random random = new Random();
|
||||
|
||||
for (int x = 0; x < WORLD_WIDTH; x++) {
|
||||
int hillHeight = terrainHeight[x];
|
||||
|
||||
// Surface block
|
||||
world[hillHeight][x].add(ItemBlockSupplier.getBlock("grass"));
|
||||
|
||||
// Grass bush
|
||||
if (random.nextDouble() < 0.1 && !Trees.isTreeNearby(world, x, hillHeight)) {
|
||||
world[hillHeight - 1][x].add(ItemBlockSupplier.getBlock("grass_bush"));
|
||||
}
|
||||
|
||||
// Dirt layer
|
||||
for (int y = hillHeight + 1; y <= hillHeight + 4; y++) {
|
||||
if (!isCave[y][x]) {
|
||||
world[y][x].add(ItemBlockSupplier.getBlock("dirt"));
|
||||
}
|
||||
}
|
||||
|
||||
// Stone layer
|
||||
for (int y = hillHeight + 5; y < WORLD_HEIGHT - 1; y++) {
|
||||
if (!isCave[y][x]) {
|
||||
world[y][x].add(ItemBlockSupplier.getBlock("stone"));
|
||||
}
|
||||
}
|
||||
|
||||
// Bedrock
|
||||
world[WORLD_HEIGHT - 1][x].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK));
|
||||
}
|
||||
|
||||
// Generate ores
|
||||
generateOres(world, isCave);
|
||||
|
||||
// Fill air blocks
|
||||
for (List<Block>[] column : world) {
|
||||
for (List<Block> list : column) {
|
||||
list.addFirst(new Block("air", SpriteLoader.SPRITES.AIR, true, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateOres(List<Block>[][] world, boolean[][] isCave) {
|
||||
Random random = new Random();
|
||||
|
||||
// Ore configs: name, minY, maxY, minVeinSize, maxVeinSize, frequency
|
||||
String[][] ores = {
|
||||
{"coal_ore", "100", "220", "6", "15", "100"}, // More veins, bigger sizes
|
||||
{"iron_ore", "110", "220", "4", "10", "80"},
|
||||
{"gold_ore", "120", "230", "3", "7", "50"},
|
||||
{"diamond_ore", "130", "240", "2", "5", "30"},
|
||||
};
|
||||
|
||||
for (String[] ore : ores) {
|
||||
String oreName = ore[0];
|
||||
int minY = Integer.parseInt(ore[1]);
|
||||
int maxY = Integer.parseInt(ore[2]);
|
||||
int minVeinSize = Integer.parseInt(ore[3]);
|
||||
int maxVeinSize = Integer.parseInt(ore[4]);
|
||||
int frequency = Integer.parseInt(ore[5]);
|
||||
|
||||
for (int i = 0; i < frequency; i++) {
|
||||
int x = random.nextInt(WORLD_WIDTH);
|
||||
int y = minY + random.nextInt(maxY - minY + 1);
|
||||
int veinSize = minVeinSize + random.nextInt(maxVeinSize - minVeinSize + 1);
|
||||
|
||||
generateOreVein(world, isCave, x, y, oreName, veinSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void generateOreVein(List<Block>[][] world, boolean[][] isCave, int startX, int startY, String oreName, int veinSize) {
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < veinSize; i++) {
|
||||
int dx = startX + random.nextInt(3) - 1;
|
||||
int dy = startY + random.nextInt(3) - 1;
|
||||
|
||||
if (dx >= 0 && dx < WORLD_WIDTH && dy >= 0 && dy < WORLD_HEIGHT - 1) {
|
||||
System.out.println("Placed " + oreName + " at (" + dx + ", " + dy + ")");
|
||||
// Only place ore if it's inside stone and not cave
|
||||
List<Block> blockList = world[dy][dx];
|
||||
for (int j = 0; j < blockList.size(); j++) {
|
||||
Block b = blockList.get(j);
|
||||
if (b.getBlockId().equals("stone") && !isCave[dy][dx]) {
|
||||
blockList.set(j, ItemBlockSupplier.getBlock(oreName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean[][] generateCavesWithCellularAutomata(int[] terrainHeight) {
|
||||
Random random = new Random();
|
||||
boolean[][] caveMap = new boolean[WORLD_HEIGHT][WORLD_WIDTH];
|
||||
|
||||
// Dynamically define caveStartY per column to not overlap dirt layer
|
||||
for (int x = 0; x < WORLD_WIDTH; x++) {
|
||||
int dirtEndY = terrainHeight[x] + 4;
|
||||
int caveStartY = dirtEndY + 1;
|
||||
int caveEndY = Math.min(caveStartY + 40, WORLD_HEIGHT - 2); // limit depth to ~40 blocks
|
||||
|
||||
// Initial noise map for this column
|
||||
for (int y = caveStartY; y < caveEndY; y++) {
|
||||
caveMap[y][x] = random.nextDouble() < 0.45;
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth cave map with CA rules
|
||||
int iterations = 5;
|
||||
for (int iter = 0; iter < iterations; iter++) {
|
||||
boolean[][] newMap = new boolean[WORLD_HEIGHT][WORLD_WIDTH];
|
||||
|
||||
for (int x = 0; x < WORLD_WIDTH; x++) {
|
||||
int dirtEndY = terrainHeight[x] + 4;
|
||||
int caveStartY = dirtEndY + 1;
|
||||
int caveEndY = Math.min(caveStartY + 40, WORLD_HEIGHT - 2);
|
||||
|
||||
for (int y = caveStartY; y < caveEndY; y++) {
|
||||
int walls = countSurroundingWalls(caveMap, x, y);
|
||||
|
||||
if (walls > 4) newMap[y][x] = true; // wall
|
||||
else if (walls < 4) newMap[y][x] = false; // cave
|
||||
else newMap[y][x] = caveMap[y][x]; // keep
|
||||
}
|
||||
}
|
||||
|
||||
caveMap = newMap;
|
||||
}
|
||||
|
||||
return caveMap;
|
||||
}
|
||||
|
||||
|
||||
private static int countSurroundingWalls(boolean[][] map, int x, int y) {
|
||||
int count = 0;
|
||||
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
|
||||
if (nx < 0 || nx >= WORLD_WIDTH || ny < 0 || ny >= WORLD_HEIGHT) {
|
||||
count++; // out-of-bounds counts as wall
|
||||
} else if (map[ny][nx]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
64
src/main/java/cz/jzitnik/game/generation/Trees.java
Normal file
64
src/main/java/cz/jzitnik/game/generation/Trees.java
Normal file
@ -0,0 +1,64 @@
|
||||
package cz.jzitnik.game.generation;
|
||||
|
||||
import cz.jzitnik.game.entities.Block;
|
||||
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class Trees {
|
||||
public static boolean isTreeNearby(List<Block>[][] world, int x, int y) {
|
||||
int radius = 3; // Check within a 3-block radius for trees
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
for (int dy = -radius; dy <= radius; dy++) {
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
if (nx >= 0 && nx < 512 && ny >= 0 && ny < 256) {
|
||||
for (Block block : world[ny][nx]) {
|
||||
if (block.getBlockId().equals("oak_log") || block.getBlockId().equals("oak_leaves")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static void plantTrees(List<Block>[][] world, int[] terrainHeight) {
|
||||
Random random = new Random();
|
||||
for (int i = 10; i < 502; i += random.nextInt(20) + 20) {
|
||||
int treeBase = terrainHeight[i];
|
||||
|
||||
if (treeBase - 3 < 0)
|
||||
continue;
|
||||
|
||||
tree(world, i, treeBase);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tree(List<Block>[][] world, int i, int treeBase) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (treeBase - j >= 0) {
|
||||
world[treeBase - j - 1][i].add(ItemBlockSupplier.getBlock("oak_log"));
|
||||
}
|
||||
}
|
||||
|
||||
int leafY = treeBase - 4;
|
||||
|
||||
for (int layer = 0; layer < 3; layer++) {
|
||||
int size = 5 - (layer * 2);
|
||||
int offsetY = leafY - layer;
|
||||
|
||||
for (int dx = -size / 2; dx <= size / 2; dx++) {
|
||||
int x = i + dx;
|
||||
int y = offsetY;
|
||||
|
||||
if (x >= 0 && x < world[0].length && y >= 0) {
|
||||
world[y][x].add(ItemBlockSupplier.getBlock("oak_leaves"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import cz.jzitnik.game.annotations.BlockRegistry;
|
||||
import cz.jzitnik.game.annotations.CustomLogic;
|
||||
import cz.jzitnik.game.annotations.Sapling;
|
||||
import cz.jzitnik.game.entities.Block;
|
||||
import cz.jzitnik.game.generation.Generation;
|
||||
import cz.jzitnik.game.generation.Trees;
|
||||
import cz.jzitnik.game.logic.CustomLogicInterface;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
@ -85,7 +85,7 @@ public class SaplingLogic implements CustomLogicInterface {
|
||||
if (data.getGrowWait() == 1) {
|
||||
// Grow
|
||||
world[y][x].remove(sapling);
|
||||
Generation.tree(world, x, y + 1);
|
||||
Trees.tree(world, x, y + 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user