feat: Implemented pig spawning and pig walking

This commit is contained in:
Jakub Žitník 2025-03-01 17:29:30 +01:00
parent c50184983b
commit 940c3f3fe5
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
43 changed files with 601 additions and 173 deletions

View File

@ -40,6 +40,12 @@
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
</dependencies>
</project>

View File

@ -1,6 +1,7 @@
package cz.jzitnik;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.mobs.EntityLogicProvider;
import cz.jzitnik.game.threads.HealthRegenerationThread;
import cz.jzitnik.game.threads.HungerDrainThread;
import cz.jzitnik.game.threads.InputHandlerThread;
@ -33,16 +34,18 @@ public class Main {
Thread inputHandlerThread = new InputHandlerThread(game, terminal, screenRenderer, isRunning);
Thread healingThread = new HealthRegenerationThread(game.getPlayer());
Thread hungerDrainThread = new HungerDrainThread(game.getPlayer());
EntityLogicProvider entityLogicProvider = new EntityLogicProvider();
// Start all threads
healingThread.start();
hungerDrainThread.start();
inputHandlerThread.start();
while (isRunning[0]) {
if (game.getWindow() == Window.WORLD) {
screenRenderer.render(game);
}
entityLogicProvider.update(game);
Thread.sleep(1000);
}

View File

@ -1,16 +1,21 @@
package cz.jzitnik.game;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.GameStates;
import cz.jzitnik.game.entities.Player;
import cz.jzitnik.game.generation.Generation;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.handlers.place.CustomPlaceHandler;
import cz.jzitnik.game.mobs.EntitySpawnProvider;
import cz.jzitnik.game.sprites.Breaking;
import cz.jzitnik.game.sprites.Steve;
import cz.jzitnik.game.ui.Chest;
import cz.jzitnik.game.ui.Furnace;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.Window;
import cz.jzitnik.game.ui.Inventory;
import cz.jzitnik.game.handlers.rightclick.RightClickHandler;
import cz.jzitnik.game.handlers.rightclick.RightClickHandlerProvider;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.Getter;
@ -28,12 +33,14 @@ public class Game {
private boolean mining = false;
@Setter
private Window window = Window.WORLD;
//jsonignore
private final GameStates gameStates = new GameStates(this);
private final Inventory inventory = new Inventory();
@JsonIgnore
private final EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider();
@JsonIgnore
private final GameStates gameStates = new GameStates(this);
public Game() {
Generation.generateWorld(this);
}
@ -51,13 +58,13 @@ public class Game {
return null;
}
public void movePlayerRight(ScreenRenderer screenRenderer) {
public void movePlayerRight(ScreenRenderer screenRenderer, Terminal terminal) {
if (window != Window.WORLD) {
return;
}
int[] cords = getPlayerCords();
if (world[cords[1]][cords[0] + 1].stream().anyMatch(block -> !block.isGhost()) || world[cords[1] - 1][cords[0] + 1].stream().anyMatch(block -> !block.isGhost())) {
if (isSolid(world[cords[1]][cords[0] + 1]) || isSolid(world[cords[1] - 1][cords[0] + 1])) {
return;
}
@ -67,16 +74,18 @@ public class Game {
world[cords[1]-1][cords[0]].remove(player.getPlayerBlock1());
screenRenderer.render(this);
entitySpawnProvider.update(this, terminal);
update(screenRenderer);
}
public void movePlayerLeft(ScreenRenderer screenRenderer) {
public void movePlayerLeft(ScreenRenderer screenRenderer, Terminal terminal) {
if (window != Window.WORLD) {
return;
}
int[] cords = getPlayerCords();
if (world[cords[1]][cords[0] - 1].stream().anyMatch(block -> !block.isGhost()) || world[cords[1] - 1][cords[0] - 1].stream().anyMatch(block -> !block.isGhost())) {
if (isSolid(world[cords[1]][cords[0] - 1]) || isSolid(world[cords[1] - 1][cords[0] - 1])) {
return;
}
@ -86,6 +95,8 @@ public class Game {
world[cords[1]-1][cords[0]].remove(player.getPlayerBlock1());
screenRenderer.render(this);
entitySpawnProvider.update(this, terminal);
update(screenRenderer);
}
@ -95,7 +106,7 @@ public class Game {
}
int[] cords = getPlayerCords();
if (world[cords[1] - 2][cords[0]].stream().anyMatch(block -> !block.isGhost()) || world[cords[1] + 1][cords[0]].stream().allMatch(Block::isGhost)) {
if (isSolid(world[cords[1] - 2][cords[0]]) || !isSolid(world[cords[1] + 1][cords[0]])) {
return;
}
@ -106,20 +117,12 @@ public class Game {
new Thread(() -> {
try {
Thread.sleep(500);
Thread.sleep(400);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int[] cords2 = getPlayerCords();
if (world[cords2[1] + 1][cords2[0]].stream().allMatch(Block::isGhost)) {
world[cords2[1] - 1][cords2[0]].remove(player.getPlayerBlock1());
world[cords2[1]][cords2[0]].add(player.getPlayerBlock1());
world[cords2[1] + 1][cords2[0]].add(player.getPlayerBlock2());
world[cords2[1]][cords2[0]].remove(player.getPlayerBlock2());
screenRenderer.render(this);
}
update(screenRenderer);
}).start();
}
@ -233,7 +236,7 @@ public class Game {
}
int[] cords2 = getPlayerCords();
if (world[cords2[1] + 1][cords2[0]].stream().allMatch(Block::isGhost)) {
if (!isSolid(world[cords2[1] + 1][cords2[0]])) {
world[cords2[1] - 1][cords2[0]].remove(player.getPlayerBlock1());
world[cords2[1]][cords2[0]].add(player.getPlayerBlock1());
world[cords2[1] + 1][cords2[0]].add(player.getPlayerBlock2());
@ -268,7 +271,7 @@ public class Game {
}
if (!blocks.stream().allMatch(block -> block.getBlockId().equals("air"))) {
RightClickHandler.handle(x, y, this, screenRenderer);
RightClickHandlerProvider.handle(x, y, this, screenRenderer);
screenRenderer.render(this);
return;
}
@ -293,4 +296,8 @@ public class Game {
inventory.setItemInhHandIndex(slot);
screenRenderer.render(this);
}
public boolean isSolid(List<Block> blocks) {
return !blocks.stream().allMatch(Block::isGhost);
}
}

View File

@ -5,8 +5,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RegisterPlaceHandler {
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityLogic {
String value();
}

View File

@ -0,0 +1,11 @@
package cz.jzitnik.game.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntitySpawn {
}

View File

@ -0,0 +1,13 @@
package cz.jzitnik.game.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PlaceHandler {
String value();
}

View File

@ -0,0 +1,11 @@
package cz.jzitnik.game.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RightClickLogic {
}

View File

@ -1,7 +1,11 @@
package cz.jzitnik.game.ui;
package cz.jzitnik.game.blocks;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.annotations.RightClickLogic;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.handlers.rightclick.RightClickHandler;
import cz.jzitnik.game.ui.InventoryClickHandler;
import cz.jzitnik.game.ui.Window;
import cz.jzitnik.tui.ScreenRenderer;
import cz.jzitnik.tui.SpriteList;
import org.jline.terminal.MouseEvent;
@ -10,7 +14,8 @@ import org.jline.terminal.Terminal;
import java.util.List;
import java.util.Optional;
public class Chest {
@RightClickLogic
public class Chest implements RightClickHandler {
private static final int ROW_AMOUNT = 4;
private static final int COLUMN_AMOUNT = 6;
private static final int CELL_WIDTH = 50;
@ -91,4 +96,9 @@ public class Chest {
items[i] = null;
}
}
@Override
public void onBlockRightClick(int ignored, int ignored2, Game game, ScreenRenderer ignored4) {
game.setWindow(Window.CHEST);
}
}

View File

@ -1,10 +1,14 @@
package cz.jzitnik.game.ui;
package cz.jzitnik.game.blocks;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.annotations.RightClickLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.handlers.rightclick.RightClickHandler;
import cz.jzitnik.game.smelting.Smelting;
import cz.jzitnik.game.ui.InventoryClickHandler;
import cz.jzitnik.game.ui.Window;
import cz.jzitnik.tui.ScreenRenderer;
import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.tui.utils.Numbers;
@ -15,7 +19,8 @@ import org.jline.terminal.Terminal;
import java.util.List;
import java.util.Optional;
public class Furnace {
@RightClickLogic
public class Furnace implements RightClickHandler {
private final Block block;
private final InventoryItem[] items = new InventoryItem[2];
private InventoryItem outputItem;
@ -254,4 +259,9 @@ public class Furnace {
setSmelting(false);
}
@Override
public void onBlockRightClick(int ignored, int ignored2, Game game, ScreenRenderer ignored3) {
game.setWindow(Window.FURNACE);
}
}

View File

@ -0,0 +1,51 @@
package cz.jzitnik.game.blocks;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.RightClickLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.handlers.rightclick.RightClickHandler;
import cz.jzitnik.game.sprites.OakDoor;
import cz.jzitnik.tui.ScreenRenderer;
@RightClickLogic
public class OakDoorData implements RightClickHandler {
private void change(Block door) {
door.setSpriteState(switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP -> OakDoor.OakDoorState.TOPCLOSED;
case OakDoor.OakDoorState.BOTTOM -> OakDoor.OakDoorState.BOTTOMCLOSED;
case OakDoor.OakDoorState.TOPCLOSED -> OakDoor.OakDoorState.TOP;
case OakDoor.OakDoorState.BOTTOMCLOSED -> OakDoor.OakDoorState.BOTTOM;
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
});
door.setGhost(switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP, OakDoor.OakDoorState.BOTTOM -> true;
case OakDoor.OakDoorState.TOPCLOSED, OakDoor.OakDoorState.BOTTOMCLOSED -> false;
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
});
}
public void onBlockRightClick(int x, int y, Game game, ScreenRenderer screenRenderer) {
var blocks = game.getWorld()[y][x];
var door = blocks.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP, OakDoor.OakDoorState.TOPCLOSED -> {
var blocks2 = game.getWorld()[y+1][x];
var door2 = blocks2.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
change(door2);
}
case OakDoor.OakDoorState.BOTTOM, OakDoor.OakDoorState.BOTTOMCLOSED -> {
var blocks2 = game.getWorld()[y-1][x];
var door2 = blocks2.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
change(door2);
}
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
}
change(door);
game.update(screenRenderer);
}
}

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.crafting;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.entities.items.InventoryItem;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -1,7 +1,7 @@
package cz.jzitnik.game.crafting;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.ItemBlockSupplier;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import java.util.*;

View File

@ -1,8 +1,9 @@
package cz.jzitnik.game;
package cz.jzitnik.game.entities;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.items.ToolVariant;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.entities.items.ToolVariant;
import cz.jzitnik.game.ui.Inventory;
import lombok.Getter;
import lombok.Setter;
@ -24,6 +25,7 @@ public class Block {
private List<ToolVariant> toolVariants = new ArrayList<>();
private List<Item> drops = new ArrayList<>();
private Object data = null;
private boolean isMob = false;
public Block(String blockId, SpriteLoader.SPRITES sprite) {
this.blockId = blockId;

View File

@ -1,4 +1,4 @@
package cz.jzitnik.game;
package cz.jzitnik.game.entities;
import cz.jzitnik.game.handlers.place.PlaceHandler;

View File

@ -1,5 +1,6 @@
package cz.jzitnik.game;
package cz.jzitnik.game.entities;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.ui.CraftingTable;
public class GameStates {

View File

@ -1,4 +1,4 @@
package cz.jzitnik.game;
package cz.jzitnik.game.entities;
import lombok.Getter;
import lombok.Setter;

View File

@ -1,4 +1,4 @@
package cz.jzitnik.game.items;
package cz.jzitnik.game.entities.items;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.items;
package cz.jzitnik.game.entities.items;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.SpriteLoader;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -1,9 +1,11 @@
package cz.jzitnik.game.items;
package cz.jzitnik.game.entities.items;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.ui.Chest;
import cz.jzitnik.game.ui.Furnace;
import cz.jzitnik.game.blocks.OakDoorData;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.mobs.services.pig.PigData;
import java.util.ArrayList;
import java.util.Arrays;
@ -93,6 +95,7 @@ public class ItemBlockSupplier {
}
public static Block oakDoor() {
var block = new Block("oak_door", SpriteLoader.SPRITES.OAK_DOOR, 3, ItemType.AXE, new ArrayList<>());
block.setData(new OakDoorData());
block.setDrops(List.of(Helper.oakDoor(block)));
return block;
}
@ -146,4 +149,16 @@ public class ItemBlockSupplier {
return Helper.oakDoor(Blocks.oakDoor());
}
}
public static class Mobs {
public static Block pig() {
// Yes, pig is a block. Cry about it.
var block = new Block("pig", SpriteLoader.SPRITES.PIG);
block.setMob(true);
block.setGhost(true);
block.setMineable(false);
block.setData(new PigData());
return block;
}
}
}

View File

@ -1,4 +1,4 @@
package cz.jzitnik.game.items;
package cz.jzitnik.game.entities.items;
public enum ItemType {
PICKAXE,

View File

@ -1,4 +1,4 @@
package cz.jzitnik.game.items;
package cz.jzitnik.game.entities.items;
public enum ToolVariant {
WOODEN,

View File

@ -1,12 +0,0 @@
package cz.jzitnik.game.generation;
import cz.jzitnik.game.Block;
import java.util.List;
public class CaveGenerator {
private static final int WIDTH = 512;
private static final int HEIGHT = 256;
public static void generateCaves(List<Block>[][] world, int[] terrainHeight) {
}
}

View File

@ -1,10 +1,10 @@
package cz.jzitnik.game.generation;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.items.ItemBlockSupplier;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.sprites.Steve;
import java.util.ArrayList;
@ -18,13 +18,16 @@ public class Generation {
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
steveBlock.setSpriteState(Steve.SteveState.FIRST);
steveBlock.setGhost(true);
Block steveBlock2 = new Block("steve", SpriteLoader.SPRITES.STEVE);
steveBlock2.setSpriteState(Steve.SteveState.SECOND);
steveBlock2.setGhost(true);
int[] terrainHeight = generateTerrain();
game.getPlayer().setPlayerBlock1(steveBlock);
game.getPlayer().setPlayerBlock2(steveBlock2);
int[] terrainHeight = generateTerrain();
populateWorld(world, terrainHeight);
plantTrees(world, terrainHeight);

View File

@ -3,7 +3,6 @@ package cz.jzitnik.game.handlers.place;
import java.util.HashMap;
import java.util.Set;
import cz.jzitnik.game.annotations.RegisterPlaceHandler;
import org.reflections.Reflections;
public class PlaceHandler {
@ -28,13 +27,13 @@ public class PlaceHandler {
private void registerHandlers() {
Reflections reflections = new Reflections("cz.jzitnik.game.handlers.place.handlers");
Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(RegisterPlaceHandler.class);
Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(cz.jzitnik.game.annotations.PlaceHandler.class);
for (Class<?> clazz : handlerClasses) {
if (CustomPlaceHandler.class.isAssignableFrom(clazz)) {
try {
CustomPlaceHandler handlerInstance = (CustomPlaceHandler) clazz.getDeclaredConstructor().newInstance();
RegisterPlaceHandler annotation = clazz.getAnnotation(RegisterPlaceHandler.class);
cz.jzitnik.game.annotations.PlaceHandler annotation = clazz.getAnnotation(cz.jzitnik.game.annotations.PlaceHandler.class);
placeHandlerList.put(annotation.value(), handlerInstance);
} catch (Exception e) {
e.printStackTrace();

View File

@ -1,54 +1,14 @@
package cz.jzitnik.game.handlers.place.handlers;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.annotations.PlaceHandler;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.RegisterPlaceHandler;
import cz.jzitnik.game.handlers.place.CustomPlaceHandler;
import cz.jzitnik.game.items.ItemBlockSupplier;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.sprites.OakDoor;
import cz.jzitnik.tui.ScreenRenderer;
@RegisterPlaceHandler("oak_door")
@PlaceHandler("oak_door")
public class DoorPlaceHandler implements CustomPlaceHandler {
public static void rightClick(Game game, int x, int y, ScreenRenderer screenRenderer) {
var blocks = game.getWorld()[y][x];
var door = blocks.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP, OakDoor.OakDoorState.TOPCLOSED -> {
var blocks2 = game.getWorld()[y+1][x];
var door2 = blocks2.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
change(door2);
}
case OakDoor.OakDoorState.BOTTOM, OakDoor.OakDoorState.BOTTOMCLOSED -> {
var blocks2 = game.getWorld()[y-1][x];
var door2 = blocks2.stream().filter(block -> block.getBlockId().equals("oak_door")).toList().getFirst();
change(door2);
}
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
}
change(door);
game.update(screenRenderer);
}
private static void change(Block door) {
door.setSpriteState(switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP -> OakDoor.OakDoorState.TOPCLOSED;
case OakDoor.OakDoorState.BOTTOM -> OakDoor.OakDoorState.BOTTOMCLOSED;
case OakDoor.OakDoorState.TOPCLOSED -> OakDoor.OakDoorState.TOP;
case OakDoor.OakDoorState.BOTTOMCLOSED -> OakDoor.OakDoorState.BOTTOM;
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
});
door.setGhost(switch (door.getSpriteState().get()) {
case OakDoor.OakDoorState.TOP, OakDoor.OakDoorState.BOTTOM -> true;
case OakDoor.OakDoorState.TOPCLOSED, OakDoor.OakDoorState.BOTTOMCLOSED -> false;
default -> throw new IllegalStateException("Unexpected value: " + door.getSpriteState().get());
});
}
@Override
public boolean place(Game game, int x, int y) {
var blocks = game.getWorld()[y][x];

View File

@ -1,40 +1,8 @@
package cz.jzitnik.game.handlers.rightclick;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.handlers.place.handlers.DoorPlaceHandler;
import cz.jzitnik.game.ui.Window;
import cz.jzitnik.tui.ScreenRenderer;
import java.util.HashMap;
public class RightClickHandler {
@FunctionalInterface
public interface Function3<T, U> {
void apply(T t, U u);
}
public static void handle(int x, int y, Game game, ScreenRenderer screenRenderer) {
if (game.isMining()) {
return;
}
HashMap<String, Function3<Integer, Integer>> functionMap = new HashMap<>();
functionMap.put("crafting_table", game.getGameStates().craftingTable::render);
functionMap.put("chest", (Integer ignored, Integer ignored2) -> game.setWindow(Window.CHEST));
functionMap.put("furnace", (Integer ignored, Integer ignored2) -> game.setWindow(Window.FURNACE));
functionMap.put("oak_door", (Integer xx, Integer xy) -> DoorPlaceHandler.rightClick(game, xx, xy, screenRenderer));
game.getGameStates().clickX = x;
game.getGameStates().clickY = y;
var blocks = game.getWorld()[y][x];
for (Block block : blocks) {
if (functionMap.containsKey(block.getBlockId())) {
functionMap.get(block.getBlockId()).apply(x, y);
return;
}
}
}
public interface RightClickHandler {
void onBlockRightClick(int x, int y, Game game, ScreenRenderer screenRenderer);
}

View File

@ -0,0 +1,29 @@
package cz.jzitnik.game.handlers.rightclick;
import cz.jzitnik.game.annotations.RightClickLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.tui.ScreenRenderer;
public class RightClickHandlerProvider {
public static void handle(int x, int y, Game game, ScreenRenderer screenRenderer) {
if (game.isMining()) {
return;
}
game.getGameStates().clickX = x;
game.getGameStates().clickY = y;
var blocks = game.getWorld()[y][x];
for (Block block : blocks) {
if (block.getBlockId().equalsIgnoreCase("crafting_table")) {
game.getGameStates().craftingTable.render(x, y);
continue;
}
if (block.getData() != null && block.getData().getClass().isAnnotationPresent(RightClickLogic.class) && block.getData() instanceof RightClickHandler handlerClass) {
handlerClass.onBlockRightClick(x, y, game, screenRenderer);
}
}
}
}

View File

@ -0,0 +1,5 @@
package cz.jzitnik.game.mobs;
public interface EntityLogicInterface {
void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO);
}

View File

@ -0,0 +1,82 @@
package cz.jzitnik.game.mobs;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.entities.Block;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.reflections.Reflections;
public class EntityLogicProvider {
private static final int CHUNK_SIZE = 20;
private final HashMap<String, EntityLogicInterface> logicList = new HashMap<>();
@AllArgsConstructor
@Getter
public static class EntityLogicMobDTO {
private Game game;
private Block mob;
private int x;
private int y;
}
public void update(Game game) {
int[] playerLocation = game.getPlayerCords();
int playerX = playerLocation[0];
int playerY = playerLocation[1];
int startX = Math.max(0, playerX - CHUNK_SIZE);
int startY = Math.max(0, playerY - CHUNK_SIZE);
int endX = Math.min(game.getWorld()[0].length, playerX + CHUNK_SIZE);
int endY = Math.min(game.getWorld().length, playerY + CHUNK_SIZE);
List<EntityLogicMobDTO> mobs = new ArrayList<>();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
int finalX = x;
int finalY = y;
List<EntityLogicMobDTO> blockMobs = game.getWorld()[y][x].stream().filter(Block::isMob).map(mob -> new EntityLogicMobDTO(game, mob, finalX, finalY)).toList();
mobs.addAll(blockMobs);
}
}
for (EntityLogicMobDTO entityLogicMobDTO : mobs) {
run(entityLogicMobDTO);
}
}
private void run(EntityLogicMobDTO entityLogicMobDTO) {
if (!logicList.containsKey(entityLogicMobDTO.getMob().getBlockId())) {
return;
}
logicList.get(entityLogicMobDTO.getMob().getBlockId()).nextIteration(entityLogicMobDTO);
}
public EntityLogicProvider() {
registerHandlers();
}
private void registerHandlers() {
Reflections reflections = new Reflections("cz.jzitnik.game.mobs.services");
Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(EntityLogic.class);
for (Class<?> clazz : handlerClasses) {
if (EntityLogicInterface.class.isAssignableFrom(clazz)) {
try {
EntityLogicInterface instance = (EntityLogicInterface) clazz.getDeclaredConstructor().newInstance();
EntityLogic annotation = clazz.getAnnotation(EntityLogic.class);
logicList.put(annotation.value(), instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,8 @@
package cz.jzitnik.game.mobs;
import cz.jzitnik.game.Game;
import org.jline.terminal.Terminal;
public interface EntitySpawnInterface {
void spawn(int playerX, int playerY, Game game, Terminal terminal);
}

View File

@ -0,0 +1,57 @@
package cz.jzitnik.game.mobs;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntitySpawn;
import cz.jzitnik.game.entities.Block;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jline.terminal.Terminal;
import org.reflections.Reflections;
public class EntitySpawnProvider {
private final List<EntitySpawnInterface> spawnList = new ArrayList<>();
@AllArgsConstructor
@Getter
public static class EntityLogicMobDTO {
private Game game;
private Block mob;
private int x;
private int y;
}
public void update(Game game, Terminal terminal) {
int[] playerLocation = game.getPlayerCords();
int playerX = playerLocation[0];
int playerY = playerLocation[1];
for (EntitySpawnInterface entitySpawnInterface: spawnList) {
entitySpawnInterface.spawn(playerX, playerY, game, terminal);
}
}
public EntitySpawnProvider() {
registerHandlers();
}
private void registerHandlers() {
Reflections reflections = new Reflections("cz.jzitnik.game.mobs.services");
Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(EntitySpawn.class);
for (Class<?> clazz : handlerClasses) {
if (EntitySpawnInterface.class.isAssignableFrom(clazz)) {
try {
EntitySpawnInterface instance = (EntitySpawnInterface) clazz.getDeclaredConstructor().newInstance();
spawnList.add(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,12 @@
package cz.jzitnik.game.mobs.services.pig;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PigData {
private int lastDirection = 1; // 1 = right, -1 = left
private int movementCooldown = 0;
private int jumpAttempts = 0;
}

View File

@ -0,0 +1,177 @@
package cz.jzitnik.game.mobs.services.pig;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.annotations.EntitySpawn;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.mobs.EntityLogicInterface;
import cz.jzitnik.game.mobs.EntityLogicProvider;
import cz.jzitnik.game.mobs.EntitySpawnInterface;
import cz.jzitnik.game.sprites.Pig;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import org.jline.terminal.Terminal;
import java.util.*;
@EntitySpawn
@EntityLogic("pig")
public class PigLogic implements EntityLogicInterface, EntitySpawnInterface {
private final Random random = new Random();
@Override
public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
int pigX = entityLogicMobDTO.getX();
int pigY = entityLogicMobDTO.getY();
var game = entityLogicMobDTO.getGame();
var pig = entityLogicMobDTO.getMob();
var world = game.getWorld();
var pigData = (PigData) pig.getData();
boolean updated = false;
int newPigX = pigX;
int newPigY = pigY;
// Reduce movement cooldown
if (pigData.getMovementCooldown() > 0) {
pigData.setMovementCooldown(pigData.getMovementCooldown() - 1);
return; // Skip movement this iteration
}
// Determine movement direction
int direction = pigData.getLastDirection();
if (random.nextInt(10) < 3) { // 30% chance to change direction
direction = -direction;
}
pigData.setLastDirection(direction);
// Update sprite direction
if (direction == 1) {
pig.setSpriteState(Pig.PigState.RIGHT);
} else {
pig.setSpriteState(Pig.PigState.LEFT);
}
List<Block> blocksAhead = world[pigY][pigX + direction];
if (!game.isSolid(blocksAhead)) {
world[pigY][pigX].remove(pig);
world[pigY][pigX + direction].add(pig);
newPigX = pigX + direction;
updated = true;
pigData.setJumpAttempts(0); // Reset jump attempts when moving forward
} else {
List<Block> blocksAboveAhead = world[pigY - 1][pigX + direction];
List<Block> blocksTwoAboveAhead = world[pigY - 2][pigX + direction];
// Jump if there is only one block height obstacle and limit jump attempts
if (!game.isSolid(blocksAboveAhead) && game.isSolid(blocksAhead) && !game.isSolid(blocksTwoAboveAhead)) {
if (pigData.getJumpAttempts() < 2) { // Limit jumping attempts to prevent infinite jumping
world[pigY][pigX].remove(pig);
world[pigY - 1][pigX + direction].add(pig);
newPigX = pigX + direction;
newPigY = pigY - 1;
updated = true;
pigData.setJumpAttempts(pigData.getJumpAttempts() + 1);
}
}
}
// Falling logic (avoid long falls)
while (updated) {
if (!game.isSolid(world[newPigY + 1][newPigX])) {
if (newPigY - pigY < 3) { // Only fall if it's at most 2 blocks drop
world[newPigY][newPigX].remove(pig);
world[newPigY + 1][newPigX].add(pig);
newPigY++;
} else {
updated = false;
}
} else {
updated = false;
}
}
// Apply movement cooldown to slow down movement
pigData.setMovementCooldown(random.nextInt(3) + 1); // 1-3 iterations cooldown
}
@Override
public void spawn(int playerX, int playerY, Game game, Terminal terminal) {
// Cordinates where player can see
int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);
var world = game.getWorld();
int startX = data[0];
int endX = data[1];
// Left side
int lstartX = startX - 20;
int lendX = startX - 5;
int lstartY = playerY - 15;
int lendY = playerY + 15;
if (countPrasata(lstartX, lendX, lstartY, lendY, game) < 3 && random.nextInt(100) < 2) {
var spawnLocations = pigCanSpawn(lstartX, lendX, playerY, game);
if (!spawnLocations.isEmpty()) {
System.out.println(spawnLocations.size());
for (int i = 0; i < Math.min(4, spawnLocations.size()); i++) {
var randomLocation = getRandomEntry(spawnLocations);
int x = randomLocation.getKey();
int y = randomLocation.getValue();
world[y][x].add(ItemBlockSupplier.Mobs.pig());
}
}
}
// Right side
int rstartX = endX + 5;
int rendX = endX + 20;
int rstartY = playerY - 15;
int rendY = playerY + 15;
if (countPrasata(rstartX, rendX, rstartY, rendY, game) < 3 && random.nextInt(100) < 2) {
var spawnLocations = pigCanSpawn(rstartX, rendX, playerY, game);
if (!spawnLocations.isEmpty()) {
System.out.println(spawnLocations.size());
for (int i = 0; i < Math.min(4, spawnLocations.size()); i++) {
var randomLocation = getRandomEntry(spawnLocations);
int x = randomLocation.getKey();
int y = randomLocation.getValue();
world[y][x].add(ItemBlockSupplier.Mobs.pig());
}
}
}
}
public static <K, V> Map.Entry<K, V> getRandomEntry(HashMap<K, V> map) {
List<Map.Entry<K, V>> entryList = new ArrayList<>(map.entrySet());
Random random = new Random();
return entryList.get(random.nextInt(entryList.size()));
}
private HashMap<Integer, Integer> pigCanSpawn(int startX, int endX, int playerY, Game game) {
var map = new HashMap<Integer, Integer>();
var world = game.getWorld();
for (int x = startX; x <= endX; x++) {
for (int y = Math.max(0, playerY - 30); y < Math.min(world.length, playerY + 30); y++) {
if (world[y][x].stream().anyMatch(i -> i.getBlockId().equals("grass"))) {
map.put(x, y - 1);
}
}
}
return map;
}
private long countPrasata(int startX, int endX, int startY, int endY, Game game) {
long pigAmount = 0;
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
pigAmount += game.getWorld()[y][x].stream().filter(i -> i.getBlockId().equals("pig")).count();
}
}
return pigAmount;
}
}

View File

@ -1,7 +1,7 @@
package cz.jzitnik.game.smelting;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemBlockSupplier;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import java.util.HashMap;
import java.util.function.Supplier;

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Player;
import cz.jzitnik.game.entities.Player;
import lombok.AllArgsConstructor;
@AllArgsConstructor

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Player;
import cz.jzitnik.game.entities.Player;
import lombok.AllArgsConstructor;
@AllArgsConstructor

View File

@ -1,6 +1,8 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.*;
import cz.jzitnik.tui.MouseHandler;
import cz.jzitnik.tui.ScreenRenderer;
@ -54,11 +56,11 @@ public class InputHandlerThread extends Thread {
switch (key) {
case '1', '2', '3', '4', '5', '6', '7', '8', '9' -> game.changeSlot(key - 49, screenRenderer);
case 'a' -> {
game.movePlayerLeft(screenRenderer);
game.movePlayerLeft(screenRenderer, terminal);
screenRenderer.render(game);
}
case 'd' -> {
game.movePlayerRight(screenRenderer);
game.movePlayerRight(screenRenderer, terminal);
screenRenderer.render(game);
}
case ' ' -> {

View File

@ -3,7 +3,7 @@ package cz.jzitnik.game.ui;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.crafting.CraftingRecipe;
import cz.jzitnik.game.crafting.CraftingRecipeList;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.tui.ScreenRenderer;
import cz.jzitnik.tui.utils.SpriteCombiner;
import cz.jzitnik.tui.SpriteList;

View File

@ -1,7 +1,7 @@
package cz.jzitnik.game.ui;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.tui.utils.SpriteCombiner;
import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.tui.utils.Numbers;

View File

@ -1,7 +1,7 @@
package cz.jzitnik.game.ui;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.tui.ScreenRenderer;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.ui;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.entities.items.InventoryItem;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -2,7 +2,7 @@ package cz.jzitnik.game.ui;
import cz.jzitnik.game.crafting.CraftingRecipe;
import cz.jzitnik.game.crafting.CraftingRecipeList;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.tui.utils.SpriteCombiner;
import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.tui.utils.Numbers;

View File

@ -1,14 +1,13 @@
package cz.jzitnik.tui;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.sprites.Steve;
import cz.jzitnik.game.ui.Chest;
import cz.jzitnik.game.ui.Furnace;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.Healthbar;
import cz.jzitnik.tui.utils.SpriteCombiner;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jline.terminal.Terminal;
@ -16,10 +15,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@RequiredArgsConstructor
public class ScreenRenderer {
private SpriteList spriteList;
private Terminal terminal;
private final SpriteList spriteList;
private final Terminal terminal;
public ScreenRenderer(SpriteList spriteList, Terminal terminal) {
this.spriteList = spriteList;
@ -41,7 +39,7 @@ public class ScreenRenderer {
return null;
}
public void render(Game game) {
public synchronized void render(Game game) {
var world = game.getWorld();
StringBuilder main = new StringBuilder();
main.append("\033[H\033[2J");