From bfafe8577aa119fbc46efe46e98ee19b2425c078 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Wed, 24 Sep 2025 10:58:55 +0200 Subject: [PATCH] feat: Zombie logic --- src/main/java/cz/jzitnik/game/Game.java | 7 + .../jzitnik/game/generation/Generation.java | 2 - .../logic/services/mobs/ZombieHurtLogic.java | 22 +++ .../game/mobs/services/zombie/ZombieData.java | 1 - .../mobs/services/zombie/ZombieMoveLogic.java | 127 ++++++++++++++++++ .../java/cz/jzitnik/tui/MouseHandler.java | 10 +- .../java/cz/jzitnik/tui/ScreenRenderer.java | 19 +-- 7 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 src/main/java/cz/jzitnik/game/logic/services/mobs/ZombieHurtLogic.java diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java index 4c6a0d3..bcec83c 100644 --- a/src/main/java/cz/jzitnik/game/Game.java +++ b/src/main/java/cz/jzitnik/game/Game.java @@ -31,6 +31,7 @@ import lombok.Setter; import org.jline.terminal.Terminal; +import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -96,6 +97,12 @@ public class Game { return null; } + public Point getPlayerCordsPoint() { + int[] cords = getPlayerCords(); + + return new Point(cords[0], cords[1]); + } + /** * Moves the player one block to the right if possible. * diff --git a/src/main/java/cz/jzitnik/game/generation/Generation.java b/src/main/java/cz/jzitnik/game/generation/Generation.java index 9d26c6c..b3d7ad2 100644 --- a/src/main/java/cz/jzitnik/game/generation/Generation.java +++ b/src/main/java/cz/jzitnik/game/generation/Generation.java @@ -30,8 +30,6 @@ public class Generation { game.getPlayer().setPlayerBlock1(steveBlock); game.getPlayer().setPlayerBlock2(steveBlock2); - game.getInventory().addItem(ItemBlockSupplier.getItem("sand")); - PopulateWorld.populateWorld(world, terrainHeight); Trees.plantTrees(world, terrainHeight); diff --git a/src/main/java/cz/jzitnik/game/logic/services/mobs/ZombieHurtLogic.java b/src/main/java/cz/jzitnik/game/logic/services/mobs/ZombieHurtLogic.java new file mode 100644 index 0000000..7ab4840 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/logic/services/mobs/ZombieHurtLogic.java @@ -0,0 +1,22 @@ +package cz.jzitnik.game.logic.services.mobs; + +import cz.jzitnik.game.Game; +import cz.jzitnik.game.annotations.CustomLogic; +import cz.jzitnik.game.entities.items.registry.mobs.Zombie; +import cz.jzitnik.game.logic.CustomLogicInterface; +import cz.jzitnik.tui.ScreenRenderer; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@CustomLogic +public class ZombieHurtLogic implements CustomLogicInterface { + @Override + public void nextIteration(Game game, ScreenRenderer screenRenderer) { + var world = game.getWorld(); + var playerCords = game.getPlayerCordsPoint(); + + if (world[playerCords.y][playerCords.x].stream().anyMatch(block -> block.getClass().equals(Zombie.class))) { + game.getPlayer().dealDamage(game, screenRenderer); + } + } +} diff --git a/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieData.java b/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieData.java index a2fc72d..1443f4a 100644 --- a/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieData.java +++ b/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieData.java @@ -7,6 +7,5 @@ import lombok.Setter; @Setter public class ZombieData { private int lastDirection = 1; // 1 = right, -1 = left - private int movementCooldown = 0; private int jumpAttempts = 0; } diff --git a/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieMoveLogic.java b/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieMoveLogic.java index 8112e1e..f2d172e 100644 --- a/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieMoveLogic.java +++ b/src/main/java/cz/jzitnik/game/mobs/services/zombie/ZombieMoveLogic.java @@ -1,13 +1,140 @@ package cz.jzitnik.game.mobs.services.zombie; import cz.jzitnik.game.annotations.EntityLogic; +import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.mobs.EntityLogicInterface; import cz.jzitnik.game.mobs.EntityLogicProvider; +import cz.jzitnik.game.sprites.Zombie; +import lombok.extern.slf4j.Slf4j; +import java.awt.*; +import java.util.List; + +@Slf4j @EntityLogic("zombie") public class ZombieMoveLogic implements EntityLogicInterface { @Override public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) { + int zombieX = entityLogicMobDTO.getX(); + int zombieY = entityLogicMobDTO.getY(); + var game = entityLogicMobDTO.getGame(); + var zombie = entityLogicMobDTO.getMob(); + // Skip updates for top half + if (zombie.getSpriteState().get() == Zombie.ZombieState.TOP || + zombie.getSpriteState().get() == Zombie.ZombieState.TOP_HURT) { + log.debug("Skipping top half of zombie at ({},{})", zombieX, zombieY); + return; + } + + log.debug("Zombie tick @ ({},{})", zombieX, zombieY); + + var world = game.getWorld(); + var zombieData = (ZombieData) zombie.getData(); + + // Player coordinates + Point playerCords = game.getPlayerCordsPoint(); + int playerX = playerCords.x; + int playerY = playerCords.y; + log.debug("Player at ({},{})", playerX, playerY); + + // Decide direction + int direction = Integer.compare(playerX, zombieX); + zombieData.setLastDirection(direction); + log.debug("Zombie direction: {}", direction); + + if (direction == 0) { + return; + } + + boolean updated = false; + int newZombieX = zombieX; + int newZombieY = zombieY; + + // Check bounds + if (zombieX + direction < 0 || zombieX + direction >= world[0].length) { + log.debug("Zombie cannot move, out of bounds at X: {}", zombieX + direction); + } else { + // Blocks ahead + List blocksAhead = world[zombieY][zombieX + direction]; + log.debug("Blocks ahead solid? {}", game.isSolid(blocksAhead)); + + if (!game.isSolid(blocksAhead)) { + log.debug("Path clear, moving to ({},{})", zombieX + direction, zombieY); + moveZombie(zombie, zombieX, zombieY, zombieX + direction, zombieY, world); + newZombieX = zombieX + direction; + updated = true; + zombieData.setJumpAttempts(0); + } else { + log.debug("Path blocked, checking jump options"); + List blocksAboveAhead = world[zombieY - 1][zombieX + direction]; + List blocksTwoAboveAhead = world[zombieY - 2][zombieX + direction]; + + log.debug("Blocks above ahead solid? {}", game.isSolid(blocksAboveAhead)); + log.debug("Blocks two above ahead solid? {}", game.isSolid(blocksTwoAboveAhead)); + + if (!game.isSolid(blocksAboveAhead) && game.isSolid(blocksAhead) && !game.isSolid(blocksTwoAboveAhead)) { + if (zombieData.getJumpAttempts() < 2) { + log.debug("Jumping to ({},{})", zombieX + direction, zombieY - 1); + moveZombie(zombie, zombieX, zombieY, zombieX + direction, zombieY - 1, world); + newZombieX = zombieX + direction; + newZombieY = zombieY - 1; + updated = true; + zombieData.setJumpAttempts(zombieData.getJumpAttempts() + 1); + } else { + log.debug("Max jump attempts reached: {}", zombieData.getJumpAttempts()); + } + } else { + log.debug("Cannot jump over obstacle"); + } + } + } + + // Apply gravity + while (updated) { + if (newZombieY + 1 >= world.length) { + log.debug("Gravity blocked, bottom of world reached"); + updated = false; + break; + } + + if (!game.isSolid(world[newZombieY + 1][newZombieX])) { + if (newZombieY - zombieY < 3) { + log.debug("Gravity moving zombie down from ({},{}) to ({},{})", newZombieX, newZombieY, newZombieX, newZombieY + 1); + moveZombie(zombie, newZombieX, newZombieY, newZombieX, newZombieY + 1, world); + newZombieY++; + } else { + log.debug("Gravity limit reached, stopping"); + updated = false; + } + } else { + log.debug("Block below solid, stopping gravity"); + updated = false; + } + } + } + + private void moveZombie(Block zombie, int oldX, int oldY, int newX, int newY, List[][] world) { + Block linked = zombie.getLinkedMobTexture(); + + boolean isTop = zombie.getSpriteState().isPresent() && + (zombie.getSpriteState().get().equals(Zombie.ZombieState.TOP) || + zombie.getSpriteState().get().equals(Zombie.ZombieState.TOP_HURT)); + + if (isTop) { + log.debug("Moving TOP block from ({},{}) to ({},{})", oldX, oldY, newX, newY); + world[oldY][oldX].remove(zombie); + world[oldY + 1][oldX].remove(linked); + + world[newY][newX].add(zombie); + world[newY + 1][newX].add(linked); + } else { + log.debug("Moving BOTTOM block from ({},{}) to ({},{})", oldX, oldY, newX, newY); + world[oldY][oldX].remove(zombie); + world[oldY - 1][oldX].remove(linked); + + world[newY][newX].add(zombie); + world[newY - 1][newX].add(linked); + } } } diff --git a/src/main/java/cz/jzitnik/tui/MouseHandler.java b/src/main/java/cz/jzitnik/tui/MouseHandler.java index 3bf6aef..febb8dd 100644 --- a/src/main/java/cz/jzitnik/tui/MouseHandler.java +++ b/src/main/java/cz/jzitnik/tui/MouseHandler.java @@ -6,6 +6,7 @@ import lombok.AllArgsConstructor; import org.jline.terminal.MouseEvent; import org.jline.terminal.Terminal; +import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -171,17 +172,14 @@ public class MouseHandler { } if (game.isMineable(blockX, blockY, terminal)) { - List list = new ArrayList<>(); - list.add(blockX); - list.add(blockY); if (screenRenderer.getSelectedBlock().isPresent() - && screenRenderer.getSelectedBlock().get().get(0).equals(blockX) - && screenRenderer.getSelectedBlock().get().get(1).equals(blockY)) { + && screenRenderer.getSelectedBlock().get().x == blockX + && screenRenderer.getSelectedBlock().get().y == blockY) { return; } - screenRenderer.setSelectedBlock(Optional.of(list)); + screenRenderer.setSelectedBlock(Optional.of(new Point(blockX, blockY))); } else { if (screenRenderer.getSelectedBlock().isEmpty()) { return; diff --git a/src/main/java/cz/jzitnik/tui/ScreenRenderer.java b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java index d6ff76f..e161da3 100644 --- a/src/main/java/cz/jzitnik/tui/ScreenRenderer.java +++ b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java @@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j; import org.jline.terminal.Terminal; +import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -45,7 +46,7 @@ public class ScreenRenderer { */ @Getter @Setter - private Optional> selectedBlock = Optional.empty(); + private Optional selectedBlock = Optional.empty(); /** * Finds the coordinates of the player's lower or upper body in the world array. @@ -53,7 +54,7 @@ public class ScreenRenderer { * @param world The 2D world array of blocks. * @return Integer array of coordinates [x, y], or null if player not found. */ - private int[] getPlayerCords(List[][] world) { + private Point getPlayerCords(List[][] world) { for (int i = 0; i < world.length; i++) { for (int j = 0; j < world[i].length; j++) { var steve = world[i][j].stream().filter(x -> x.getBlockId().equals("steve")).findFirst(); @@ -61,9 +62,9 @@ public class ScreenRenderer { var steveData = (SteveData) steve.get().getData(); if (steveData.isTop()) { - return new int[] { j, i + 1 }; + return new Point(j, i + 1); } else { - return new int[] { j, i }; + return new Point(j, i); } } } @@ -126,12 +127,12 @@ public class ScreenRenderer { case WORLD -> { // World - int[] cords = getPlayerCords(world); + Point cords = getPlayerCords(world); if (cords == null) return; - int playerX = cords[0]; - int playerY = cords[1]; + int playerX = cords.x; + int playerY = cords.y; int terminalWidth = terminal.getWidth(); int terminalHeight = terminal.getHeight(); @@ -168,8 +169,8 @@ public class ScreenRenderer { .map(block -> getTexture(block, spriteList, game)) .toList()); - if (selectedBlock.isPresent() && selectedBlock.get().get(0) == x - && selectedBlock.get().get(1) == y) { + if (selectedBlock.isPresent() && selectedBlock.get().x == x + && selectedBlock.get().y == y) { StringBuilder stringBuilder = getStringBuilder(); sprites.add(stringBuilder.toString());