feat: Cobblestone generator

This commit is contained in:
Jakub Žitník 2025-03-08 09:03:32 +01:00
parent 6316833af7
commit eb899ac3a9
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
14 changed files with 235 additions and 271 deletions

View File

@ -51,7 +51,8 @@ public class Main {
}
try {
customLogicProvider.update(game);
} catch (Exception _) {
} catch (Exception e) {
e.printStackTrace();
}
if ( game.getWindow() == Window.WORLD) {

View File

@ -12,7 +12,7 @@ public class SpriteLoader {
// BLOCKS
// Blocks
AIR, WATER, LAVA, DIRT, GRASS, STONE, BEDROCK, COBBLESTONE, WOOL, OAK_LOG, OAK_LEAF, OAK_PLANKS, OAK_DOOR,
AIR, WATER, LAVA, DIRT, GRASS, STONE, BEDROCK, COBBLESTONE, WOOL, OAK_LOG, OAK_LEAF, OAK_PLANKS, OAK_DOOR, OBSIDIAN,
// Ores
COAL_ORE, IRON_ORE, GOLD_ORE, DIAMOND_ORE,
@ -81,6 +81,7 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.OAK_DOOR, new OakDoor());
SPRITES_MAP.put(SPRITES.WOOL, new Wool());
SPRITES_MAP.put(SPRITES.COBBLESTONE, new SimpleSprite("cobblestone.ans"));
SPRITES_MAP.put(SPRITES.OBSIDIAN, new SimpleSprite("obsidian.ans"));
// Ores
SPRITES_MAP.put(SPRITES.COAL_ORE, new SimpleSprite("coal_ore.ans"));

View File

@ -3,16 +3,17 @@ package cz.jzitnik.game.entities.items.registry.blocks;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.services.flowing.lava.LavaData;
import cz.jzitnik.game.logic.services.flowing.FlowingData;
import cz.jzitnik.game.sprites.Lava;
import cz.jzitnik.game.sprites.Water;
@BlockRegistry(value = "lava", drops = "lava_bucket")
public class LavaBlock extends Block {
public LavaBlock() {
super("lava", SpriteLoader.SPRITES.LAVA);
setMineable(false);
setSpriteState(Lava.LavaState.FIRST);
setData(new LavaData());
setSpriteState(Water.WaterState.FIRST);
setData(new FlowingData());
setFlowing(true);
setGhost(true);
}

View File

@ -0,0 +1,16 @@
package cz.jzitnik.game.entities.items.registry.blocks;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.entities.items.ToolVariant;
import java.util.List;
@BlockRegistry("obsidian")
public class ObsidianBlock extends Block {
public ObsidianBlock() {
super("obsidian", SpriteLoader.SPRITES.OBSIDIAN, 45, ItemType.PICKAXE, List.of(ToolVariant.DIAMOND));
}
}

View File

@ -3,7 +3,7 @@ package cz.jzitnik.game.entities.items.registry.blocks;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.services.flowing.water.WaterData;
import cz.jzitnik.game.logic.services.flowing.FlowingData;
import cz.jzitnik.game.sprites.Water;
@BlockRegistry(value = "water", drops = "water_bucket")
@ -12,7 +12,7 @@ public class WaterBlock extends Block {
super("water", SpriteLoader.SPRITES.WATER);
setMineable(false);
setSpriteState(Water.WaterState.FIRST);
setData(new WaterData());
setData(new FlowingData());
setFlowing(true);
setGhost(true);
}

View File

@ -36,6 +36,9 @@ public class Generation {
// 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("water_bucket"));
game.getInventory().addItem(ItemBlockSupplier.getItem("lava_bucket"));
}
private static void initializeWorld(List<Block>[][] world) {

View File

@ -0,0 +1,126 @@
package cz.jzitnik.game.logic.services.flowing;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.game.sprites.Water;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@CustomLogic
public class FlowingLogic implements CustomLogicInterface {
private static final int RADIUS = 20;
@AllArgsConstructor
@Getter
private static class LiquidBlock {
private Block block;
private int x;
private int y;
}
@Override
public void nextIteration(Game game) {
processFlow(game, "water");
processFlow(game, "lava");
}
private void processFlow(Game game, String liquidId) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];
int playerY = data[1];
int startX = Math.max(0, playerX - RADIUS);
int startY = Math.max(0, playerY - RADIUS);
int endX = Math.min(world[0].length - 1, playerX + RADIUS);
int endY = Math.min(world.length - 1, playerY + RADIUS);
List<LiquidBlock> sourceBlocks = new ArrayList<>();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
var blocks = world[y][x];
var sourceLiquidBlocks = blocks.stream()
.filter(block -> block.getBlockId().equals(liquidId) && ((FlowingData) block.getData()).isSource())
.toList();
if (!sourceLiquidBlocks.isEmpty()) {
sourceBlocks.add(new LiquidBlock(sourceLiquidBlocks.getFirst(), x, y));
}
world[y][x].removeAll(blocks.stream()
.filter(i -> i.getBlockId().equals(liquidId) && !((FlowingData) i.getData()).isSource()).toList());
}
}
for (LiquidBlock sourceBlock : sourceBlocks) {
flow(world, sourceBlock.getX(), sourceBlock.getY(), 5, new HashSet<>(), liquidId);
}
}
private void flow(List<Block>[][] world, int x, int y, int strength, Set<Point> visited, String liquidId) {
if (y + 1 < world.length && canFlowInto(world[y + 1][x])) {
Block newLiquid = ItemBlockSupplier.getBlock(liquidId);
newLiquid.setSpriteState(Water.WaterState.get(5));
((FlowingData) newLiquid.getData()).setSource(false);
world[y + 1][x].add(newLiquid);
flow(world, x, y + 1, 5, visited, liquidId);
return;
}
if (strength == 1 || visited.contains(new Point(x, y))) {
return;
}
visited.add(new Point(x, y));
if (x - 1 >= 0 && canFlowInto(world[y][x - 1])) {
try {
transformOrFlow(world, x - 1, y, strength, visited, liquidId);
} catch (Exception _) {}
}
if (x + 1 < world[y].length && canFlowInto(world[y][x + 1])) {
try {
transformOrFlow(world, x + 1, y, strength, visited, liquidId);
} catch (Exception _) {}
}
}
private void transformOrFlow(List<Block>[][] world, int newX, int newY, int strength, Set<Point> visited, String liquidId) {
var targetBlocks = world[newY][newX];
boolean hasWater = targetBlocks.stream().anyMatch(b -> b.getBlockId().equals("water"));
boolean hasLava = targetBlocks.stream().anyMatch(b -> b.getBlockId().equals("lava"));
boolean isLavaSource = hasLava && targetBlocks.stream().anyMatch(b -> ((FlowingData) b.getData()).isSource());
if (liquidId.equals("water") && hasLava) {
if (isLavaSource) {
world[newY][newX].add(ItemBlockSupplier.getBlock("obsidian"));
} else {
world[newY][newX].add(ItemBlockSupplier.getBlock("cobblestone"));
}
return;
}
if (liquidId.equals("lava") && hasWater) {
world[newY][newX].add(ItemBlockSupplier.getBlock("cobblestone"));
return;
}
Block newLiquid = ItemBlockSupplier.getBlock(liquidId);
newLiquid.setSpriteState(Water.WaterState.get(strength - 1));
((FlowingData) newLiquid.getData()).setSource(false);
world[newY][newX].add(newLiquid);
flow(world, newX, newY, strength - 1, visited, liquidId);
}
private boolean canFlowInto(List<Block> blocks) {
return blocks.stream().allMatch(
block -> block.getBlockId().equals("steve") || block.getBlockId().equals("air") || block.isMob() || block.isFlowing());
}
}

View File

@ -0,0 +1,47 @@
package cz.jzitnik.game.logic.services.flowing;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
@CustomLogic
public class LavaWaterLogic implements CustomLogicInterface {
private static final int RADIUS = 20;
@Override
public void nextIteration(Game game) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];
int playerY = data[1];
int startX = Math.max(0, playerX - RADIUS);
int startY = Math.max(0, playerY - RADIUS);
int endX = Math.min(world[0].length - 1, playerX + RADIUS);
int endY = Math.min(world.length - 1, playerY + RADIUS);
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
var blocks = world[y][x];
if (blocks.stream().anyMatch(block -> block.getBlockId().equals("water") && blocks.stream().anyMatch(block2 -> block2.getBlockId().equals("lava")))) {
// Generate
var water = blocks.stream().filter(block -> block.getBlockId().equals("water")).findFirst().get();
var lava = blocks.stream().filter(block -> block.getBlockId().equals("lava")).findFirst().get();
var waterData = (FlowingData) water.getData();
var lavaData = (FlowingData) lava.getData();
world[y][x].remove(water);
world[y][x].remove(lava);
if (lavaData.isSource()) {
world[y][x].add(ItemBlockSupplier.getBlock("obsidian"));
} else {
world[y][x].add(ItemBlockSupplier.getBlock("cobblestone"));
}
}
}
}
}
}

View File

@ -1,6 +0,0 @@
package cz.jzitnik.game.logic.services.flowing.lava;
import cz.jzitnik.game.logic.services.flowing.FlowingData;
public class LavaData extends FlowingData {
}

View File

@ -1,125 +0,0 @@
package cz.jzitnik.game.logic.services.flowing.lava;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.game.sprites.Lava;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@CustomLogic
public class LavaLogic implements CustomLogicInterface {
private static final int RADIUS = 20;
@AllArgsConstructor
@Getter
private static class WaterBlock {
private Block block;
private int x;
private int y;
}
@Override
public void nextIteration(Game game) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];
int playerY = data[1];
int startX = Math.max(0, playerX - RADIUS);
int startY = Math.max(0, playerY - RADIUS);
int endX = Math.min(world[0].length - 1, playerX + RADIUS);
int endY = Math.min(world.length - 1, playerY + RADIUS);
List<WaterBlock> sourceBlocks = new ArrayList<>();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
var blocks = world[y][x];
var waterSourceBlocks = blocks.stream()
.filter(block -> block.getBlockId().equals("lava") && ((LavaData) block.getData()).isSource())
.toList();
if (!waterSourceBlocks.isEmpty()) {
sourceBlocks.add(new WaterBlock(waterSourceBlocks.getFirst(), x, y));
}
world[y][x].removeAll(blocks.stream()
.filter(i -> i.getBlockId().equals("lava") && !((LavaData) i.getData()).isSource()).toList());
}
}
for (WaterBlock sourceBlock : sourceBlocks) {
int x = sourceBlock.getX();
int y = sourceBlock.getY();
flow(world, x, y, 5, new HashSet<>());
}
checkFire(game, startX, endX, startY, endY);
}
public void flow(List<Block>[][] world, int x, int y, int strength, Set<Point> visited) {
if (y + 1 < world.length && waterCanFlow(world[y + 1][x])) {
Block newWater = ItemBlockSupplier.getBlock("lava");
newWater.setSpriteState(Lava.LavaState.get(5));
((LavaData) newWater.getData()).setSource(false);
world[y + 1][x].add(newWater);
flow(world, x, y + 1, 5, visited);
return;
}
if (strength == 1 || visited.contains(new Point(x, y))) {
return;
}
visited.add(new Point(x, y));
if (x - 1 >= 0 && waterCanFlow(world[y][x - 1])) {
Block newWater = ItemBlockSupplier.getBlock("lava");
newWater.setSpriteState(Lava.LavaState.get(strength - 1));
((LavaData) newWater.getData()).setSource(false);
world[y][x - 1].add(newWater);
flow(world, x - 1, y, strength - 1, visited);
}
if (x + 1 < world[y].length && waterCanFlow(world[y][x + 1])) {
Block newWater = ItemBlockSupplier.getBlock("lava");
newWater.setSpriteState(Lava.LavaState.get(strength - 1));
((LavaData) newWater.getData()).setSource(false);
world[y][x + 1].add(newWater);
flow(world, x + 1, y, strength - 1, visited);
}
}
public void checkFire(Game game, int startX, int endX, int startY, int endY) {
var world = game.getWorld();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
var blocks = world[y][x];
if (blocks.stream().anyMatch(block -> block.getBlockId().equals("lava"))) {
var entities = blocks.stream().filter(Block::isMob).toList();
for (Block block : entities) {
if (block.getBlockId().equals("steve")) {
game.getPlayer().setHealth(game.getPlayer().getHealth() - 1);
continue;
}
block.decreaseHp(1);
}
}
}
}
}
private boolean waterCanFlow(List<Block> blocks) {
return blocks.stream().allMatch(
block -> block.getBlockId().equals("steve") || block.getBlockId().equals("air") || block.isMob());
}
}

View File

@ -1,6 +0,0 @@
package cz.jzitnik.game.logic.services.flowing.water;
import cz.jzitnik.game.logic.services.flowing.FlowingData;
public class WaterData extends FlowingData {
}

View File

@ -1,104 +0,0 @@
package cz.jzitnik.game.logic.services.flowing.water;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.game.sprites.Water;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@CustomLogic
public class WaterLogic implements CustomLogicInterface {
private static final int RADIUS = 20;
@AllArgsConstructor
@Getter
private static class WaterBlock {
private Block block;
private int x;
private int y;
}
@Override
public void nextIteration(Game game) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];
int playerY = data[1];
int startX = Math.max(0, playerX - RADIUS);
int startY = Math.max(0, playerY - RADIUS);
int endX = Math.min(world[0].length - 1, playerX + RADIUS);
int endY = Math.min(world.length - 1, playerY + RADIUS);
List<WaterBlock> sourceBlocks = new ArrayList<>();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
var blocks = world[y][x];
var waterSourceBlocks = blocks.stream()
.filter(block -> block.getBlockId().equals("water") && ((WaterData) block.getData()).isSource())
.toList();
if (!waterSourceBlocks.isEmpty()) {
sourceBlocks.add(new WaterBlock(waterSourceBlocks.getFirst(), x, y));
}
world[y][x].removeAll(blocks.stream()
.filter(i -> i.getBlockId().equals("water") && !((WaterData) i.getData()).isSource()).toList());
}
}
for (WaterBlock sourceBlock : sourceBlocks) {
int x = sourceBlock.getX();
int y = sourceBlock.getY();
flow(world, x, y, 5, new HashSet<>());
}
}
public void flow(List<Block>[][] world, int x, int y, int strength, Set<Point> visited) {
if (y + 1 < world.length && waterCanFlow(world[y + 1][x])) {
Block newWater = ItemBlockSupplier.getBlock("water");
newWater.setSpriteState(Water.WaterState.get(5));
((WaterData) newWater.getData()).setSource(false);
world[y + 1][x].add(newWater);
flow(world, x, y + 1, 5, visited);
return;
}
if (strength == 1 || visited.contains(new Point(x, y))) {
return;
}
visited.add(new Point(x, y));
if (x - 1 >= 0 && waterCanFlow(world[y][x - 1])) {
Block newWater = ItemBlockSupplier.getBlock("water");
newWater.setSpriteState(Water.WaterState.get(strength - 1));
((WaterData) newWater.getData()).setSource(false);
world[y][x - 1].add(newWater);
flow(world, x - 1, y, strength - 1, visited);
}
if (x + 1 < world[y].length && waterCanFlow(world[y][x + 1])) {
Block newWater = ItemBlockSupplier.getBlock("water");
newWater.setSpriteState(Water.WaterState.get(strength - 1));
((WaterData) newWater.getData()).setSource(false);
world[y][x + 1].add(newWater);
flow(world, x + 1, y, strength - 1, visited);
}
}
private boolean waterCanFlow(List<Block> blocks) {
return blocks.stream().allMatch(
block -> block.getBlockId().equals("steve") || block.getBlockId().equals("air") || block.isMob());
}
}

View File

@ -6,34 +6,19 @@ import cz.jzitnik.tui.Sprite;
import java.util.Optional;
public class Lava extends Sprite {
public enum LavaState {
FIRST, SECOND, THIRD, FOURTH, FIFTH;
public static LavaState get(int x) {
return switch (x) {
case 5 -> FIRST;
case 4 -> SECOND;
case 3 -> THIRD;
case 2 -> FOURTH;
case 1 -> FIFTH;
default -> throw new IllegalStateException("Unexpected value: " + x);
};
}
}
public String getSprite() {
return getSprite(LavaState.FIRST);
return getSprite(Water.WaterState.FIRST);
}
public String getSprite(Enum e) {
String[] resource = ResourceLoader.loadResource("lava.ans").split("\n");
int numberFormTop = switch (e) {
case LavaState.FIRST -> 0;
case LavaState.SECOND -> 5;
case LavaState.THIRD -> 10;
case LavaState.FOURTH -> 15;
case LavaState.FIFTH -> 20;
case Water.WaterState.FIRST -> 0;
case Water.WaterState.SECOND -> 5;
case Water.WaterState.THIRD -> 10;
case Water.WaterState.FOURTH -> 15;
case Water.WaterState.FIFTH -> 20;
default -> throw new IllegalStateException("Unexpected value: " + e);
};
@ -52,7 +37,7 @@ public class Lava extends Sprite {
}
@Override
public Optional<Class<LavaState>> getStates() {
return Optional.of(LavaState.class);
public Optional<Class<Water.WaterState>> getStates() {
return Optional.of(Water.WaterState.class);
}
}

View File

@ -0,0 +1,25 @@