feat: Zombie logic

This commit is contained in:
2025-09-24 10:58:55 +02:00
parent 13db7972eb
commit bfafe8577a
7 changed files with 170 additions and 18 deletions

View File

@ -31,6 +31,7 @@ import lombok.Setter;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@ -96,6 +97,12 @@ public class Game {
return null; 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. * Moves the player one block to the right if possible.
* *

View File

@ -30,8 +30,6 @@ public class Generation {
game.getPlayer().setPlayerBlock1(steveBlock); game.getPlayer().setPlayerBlock1(steveBlock);
game.getPlayer().setPlayerBlock2(steveBlock2); game.getPlayer().setPlayerBlock2(steveBlock2);
game.getInventory().addItem(ItemBlockSupplier.getItem("sand"));
PopulateWorld.populateWorld(world, terrainHeight); PopulateWorld.populateWorld(world, terrainHeight);
Trees.plantTrees(world, terrainHeight); Trees.plantTrees(world, terrainHeight);

View File

@ -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);
}
}
}

View File

@ -7,6 +7,5 @@ import lombok.Setter;
@Setter @Setter
public class ZombieData { public class ZombieData {
private int lastDirection = 1; // 1 = right, -1 = left private int lastDirection = 1; // 1 = right, -1 = left
private int movementCooldown = 0;
private int jumpAttempts = 0; private int jumpAttempts = 0;
} }

View File

@ -1,13 +1,140 @@
package cz.jzitnik.game.mobs.services.zombie; package cz.jzitnik.game.mobs.services.zombie;
import cz.jzitnik.game.annotations.EntityLogic; import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.mobs.EntityLogicInterface; import cz.jzitnik.game.mobs.EntityLogicInterface;
import cz.jzitnik.game.mobs.EntityLogicProvider; 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") @EntityLogic("zombie")
public class ZombieMoveLogic implements EntityLogicInterface { public class ZombieMoveLogic implements EntityLogicInterface {
@Override @Override
public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) { 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<Block> 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<Block> blocksAboveAhead = world[zombieY - 1][zombieX + direction];
List<Block> 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<Block>[][] 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);
}
} }
} }

View File

@ -6,6 +6,7 @@ import lombok.AllArgsConstructor;
import org.jline.terminal.MouseEvent; import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -171,17 +172,14 @@ public class MouseHandler {
} }
if (game.isMineable(blockX, blockY, terminal)) { if (game.isMineable(blockX, blockY, terminal)) {
List<Integer> list = new ArrayList<>();
list.add(blockX);
list.add(blockY);
if (screenRenderer.getSelectedBlock().isPresent() if (screenRenderer.getSelectedBlock().isPresent()
&& screenRenderer.getSelectedBlock().get().get(0).equals(blockX) && screenRenderer.getSelectedBlock().get().x == blockX
&& screenRenderer.getSelectedBlock().get().get(1).equals(blockY)) { && screenRenderer.getSelectedBlock().get().y == blockY) {
return; return;
} }
screenRenderer.setSelectedBlock(Optional.of(list)); screenRenderer.setSelectedBlock(Optional.of(new Point(blockX, blockY)));
} else { } else {
if (screenRenderer.getSelectedBlock().isEmpty()) { if (screenRenderer.getSelectedBlock().isEmpty()) {
return; return;

View File

@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -45,7 +46,7 @@ public class ScreenRenderer {
*/ */
@Getter @Getter
@Setter @Setter
private Optional<List<Integer>> selectedBlock = Optional.empty(); private Optional<Point> selectedBlock = Optional.empty();
/** /**
* Finds the coordinates of the player's lower or upper body in the world array. * 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. * @param world The 2D world array of blocks.
* @return Integer array of coordinates [x, y], or null if player not found. * @return Integer array of coordinates [x, y], or null if player not found.
*/ */
private int[] getPlayerCords(List<Block>[][] world) { private Point getPlayerCords(List<Block>[][] world) {
for (int i = 0; i < world.length; i++) { for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) { for (int j = 0; j < world[i].length; j++) {
var steve = world[i][j].stream().filter(x -> x.getBlockId().equals("steve")).findFirst(); 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(); var steveData = (SteveData) steve.get().getData();
if (steveData.isTop()) { if (steveData.isTop()) {
return new int[] { j, i + 1 }; return new Point(j, i + 1);
} else { } else {
return new int[] { j, i }; return new Point(j, i);
} }
} }
} }
@ -126,12 +127,12 @@ public class ScreenRenderer {
case WORLD -> { case WORLD -> {
// World // World
int[] cords = getPlayerCords(world); Point cords = getPlayerCords(world);
if (cords == null) if (cords == null)
return; return;
int playerX = cords[0]; int playerX = cords.x;
int playerY = cords[1]; int playerY = cords.y;
int terminalWidth = terminal.getWidth(); int terminalWidth = terminal.getWidth();
int terminalHeight = terminal.getHeight(); int terminalHeight = terminal.getHeight();
@ -168,8 +169,8 @@ public class ScreenRenderer {
.map(block -> getTexture(block, spriteList, game)) .map(block -> getTexture(block, spriteList, game))
.toList()); .toList());
if (selectedBlock.isPresent() && selectedBlock.get().get(0) == x if (selectedBlock.isPresent() && selectedBlock.get().x == x
&& selectedBlock.get().get(1) == y) { && selectedBlock.get().y == y) {
StringBuilder stringBuilder = getStringBuilder(); StringBuilder stringBuilder = getStringBuilder();
sprites.add(stringBuilder.toString()); sprites.add(stringBuilder.toString());