feat: Hurt animation and killing of zombie

This commit is contained in:
2025-09-22 15:29:58 +02:00
parent 32cb96dd49
commit 5d57d8e069
9 changed files with 99 additions and 63 deletions

View File

@ -57,7 +57,9 @@ public class Game {
private transient GameStates gameStates = new GameStates(this);
private Stats stats = new Stats();
/** Current time of day in the game (0600 range). */
/**
* Current time of day in the game (0600 range).
*/
@Setter
private int daytime = 0;
//
@ -83,9 +85,9 @@ public class Game {
var steveData = (SteveData) block.getData();
if (steveData.isTop()) {
return new int[] { j, i + 1 };
return new int[]{j, i + 1};
} else {
return new int[] { j, i };
return new int[]{j, i};
}
}
}
@ -208,12 +210,16 @@ public class Game {
int dealDamage = inventory.getItemInHand().map(Item::getDealDamage).orElse(1);
if (mob.getHp() - dealDamage <= 0) {
// Mob is killed
gameStates.dependencies.entityKill.get(mob.getBlockId()).killed(this, mob);
gameStates.dependencies.entityKill.get(mob.getBlockId()).killed(this, mob, x, y);
world[y][x].remove(mob);
} else {
mob.decreaseHp(dealDamage);
mob.setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId())
.setHurtAnimation(true, mob.getSpriteState().get()));
if (mob.getLinkedMobTexture() != null) {
mob.getLinkedMobTexture().setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId())
.setHurtAnimation(true, mob.getLinkedMobTexture().getSpriteState().get()));
}
}
}
screenRenderer.render(this);
@ -228,6 +234,11 @@ public class Game {
for (Block mob : mobs) {
mob.setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId())
.setHurtAnimation(false, mob.getSpriteState().get()));
if (mob.getLinkedMobTexture() != null) {
mob.getLinkedMobTexture().setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId())
.setHurtAnimation(false, mob.getLinkedMobTexture().getSpriteState().get()));
}
}
screenRenderer.render(this);
}).start();
@ -387,9 +398,9 @@ public class Game {
/**
* Checks whether a block is valid for mining based on proximity and visibility.
*
* @param x x-coordinate.
* @param y y-coordinate.
* @param terminal terminal context used to determine screen bounds.
* @param x x-coordinate.
* @param y y-coordinate.
* @param terminal terminal context used to determine screen bounds.
* @return true if mineable, false otherwise.
*/
public boolean isMineable(int x, int y, Terminal terminal) {
@ -420,9 +431,9 @@ public class Game {
/**
* Checks whether a block is valid for hitting (attacking).
*
* @param x x-coordinate.
* @param y y-coordinate.
* @param terminal terminal context used to determine screen bounds.
* @param x x-coordinate.
* @param y y-coordinate.
* @param terminal terminal context used to determine screen bounds.
* @return true if a mob can be hit at the given location.
*/
public boolean isHitable(int x, int y, Terminal terminal) {

View File

@ -32,6 +32,7 @@ public class Block {
private boolean onFire = false;
private int burningTime = 0;
private int burningTime2 = 0;
private Block linkedMobTexture;
public Block(String blockId, SpriteLoader.SPRITES sprite) {
this.blockId = blockId;

View File

@ -4,5 +4,5 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.entities.Block;
public interface EntityKillInterface {
void killed(Game game, Block mob);
void killed(Game game, Block mob, int x, int y);
}

View File

@ -199,7 +199,7 @@ public class CowLogic
}
@Override
public void killed(Game game, Block mob) {
public void killed(Game game, Block mob, int x, int y) {
int leatherAmount = random.nextInt(3);
InventoryItem inventoryItem = new InventoryItem(leatherAmount, ItemBlockSupplier.getItem("leather"));
int beefAmount = random.nextInt(3) + 1;

View File

@ -199,7 +199,7 @@ public class PigLogic
}
@Override
public void killed(Game game, Block mob) {
public void killed(Game game, Block mob, int x, int y) {
int amount = random.nextInt(2) + 1;
InventoryItem inventoryItem = new InventoryItem(amount, ItemBlockSupplier.getItem("porkchop"));
game.getInventory().addItem(inventoryItem);

View File

@ -242,7 +242,7 @@ public class SheepLogic
}
@Override
public void killed(Game game, Block mob) {
public void killed(Game game, Block mob, int x, int y) {
int amount = random.nextInt(3) + 1;
var sheepData = (SheepData) mob.getData();

View File

@ -0,0 +1,49 @@
package cz.jzitnik.game.mobs.services.zombie;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntityHurtAnimationHandler;
import cz.jzitnik.game.annotations.EntityKillHandler;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.mobs.*;
import cz.jzitnik.game.sprites.Zombie;
import java.util.*;
import static cz.jzitnik.game.sprites.Zombie.ZombieState.*;
@EntityHurtAnimationHandler("zombie")
@EntityKillHandler("zombie")
public class ZombieHurtKillLogic implements EntityHurtAnimationChanger, EntityKillInterface {
private final Random random = new Random();
public Zombie.ZombieState setHurtAnimation(boolean hurt, Enum current) {
if (hurt) {
return switch (current) {
case TOP, TOP_HURT -> TOP_HURT;
case BOTTOM, BOTTOM_HURT -> BOTTOM_HURT;
default -> throw new IllegalStateException("Unexpected state: " + current);
};
}
return switch (current) {
case TOP, TOP_HURT -> TOP;
case BOTTOM, BOTTOM_HURT -> BOTTOM;
default -> throw new IllegalStateException("Unexpected state: " + current);
};
}
@Override
public void killed(Game game, Block mob, int x, int y) {
int rottenFlesh = random.nextInt(3) + 1;
boolean isTop = mob.getSpriteState().isPresent() && (mob.getSpriteState().get().equals(TOP_HURT) || mob.getSpriteState().get().equals(TOP));
int newY = isTop ? y + 1 : y - 1;
game.getWorld()[newY][x].remove(mob.getLinkedMobTexture());
// Rotten flesh is currently not an item. So this is commented out
// TODO: Add rotten flesh to the game
//InventoryItem drop = new InventoryItem(rottenFlesh, ItemBlockSupplier.getItem("rotten_flesh"));
//game.getInventory().addItem(drop);
}
}

View File

@ -0,0 +1,13 @@
package cz.jzitnik.game.mobs.services.zombie;
import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.mobs.EntityLogicInterface;
import cz.jzitnik.game.mobs.EntityLogicProvider;
@EntityLogic("zombie")
public class ZombieMoveLogic implements EntityLogicInterface {
@Override
public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
}
}

View File

@ -1,49 +1,30 @@
package cz.jzitnik.game.mobs.services.zombie;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntityHurtAnimationHandler;
import cz.jzitnik.game.annotations.EntityKillHandler;
import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.annotations.EntitySpawn;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.mobs.*;
import cz.jzitnik.game.mobs.EntitySpawnInterface;
import cz.jzitnik.game.sprites.Zombie;
import cz.jzitnik.game.sprites.Zombie.ZombieState;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import lombok.extern.slf4j.Slf4j;
import org.jline.terminal.Terminal;
import java.util.*;
import static cz.jzitnik.game.sprites.Zombie.ZombieState.*;
@Slf4j
@EntitySpawn
@EntityLogic("zombie")
@EntityHurtAnimationHandler("zombie")
@EntityKillHandler("zombie")
public class ZombieLogic
implements EntityLogicInterface, EntitySpawnInterface, EntityHurtAnimationChanger, EntityKillInterface {
public class ZombieSpawnLogic implements EntitySpawnInterface {
private final Random random = new Random();
@Override
public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
log.debug("Running zombie logic");
}
@Override
public void spawn(int playerX, int playerY, Game game, Terminal terminal) {
int[] view = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);
int startX = view[0];
int endX = view[1];
//attemptZombieSpawn(startX - 20, startX - 5, playerY, game);
//attemptZombieSpawn(endX + 5, endX + 20, playerY, game);
attemptZombieSpawn(startX - 20, startX - 5, playerY, game);
attemptZombieSpawn(endX + 5, endX + 20, playerY, game);
}
private void attemptZombieSpawn(int startX, int endX, int centerY, Game game) {
if (countZombies(startX, endX, centerY - 15, centerY + 15, game) < 3 && random.nextInt(100) < 2) {
var spawnLocations = zombieCanSpawn(startX, endX, centerY, game);
@ -54,10 +35,13 @@ public class ZombieLogic
int y = loc.getValue();
var top = ItemBlockSupplier.getEntity("zombie");
top.setSpriteState(ZombieState.TOP);
top.setSpriteState(Zombie.ZombieState.TOP);
var bottom = ItemBlockSupplier.getEntity("zombie");
bottom.setSpriteState(ZombieState.BOTTOM);
bottom.setSpriteState(Zombie.ZombieState.BOTTOM);
top.setLinkedMobTexture(bottom);
bottom.setLinkedMobTexture(top);
game.getWorld()[y - 1][x].add(top);
game.getWorld()[y][x].add(bottom);
@ -66,6 +50,7 @@ public class ZombieLogic
}
}
private HashMap<Integer, Integer> zombieCanSpawn(int startX, int endX, int playerY, Game game) {
var map = new HashMap<Integer, Integer>();
var world = game.getWorld();
@ -89,29 +74,6 @@ public class ZombieLogic
return count / 2; // Each zombie has two parts
}
public Zombie.ZombieState setHurtAnimation(boolean hurt, Enum current) {
if (hurt) {
return switch (current) {
case TOP, TOP_HURT -> TOP_HURT;
case BOTTOM, BOTTOM_HURT -> BOTTOM_HURT;
default -> throw new IllegalStateException("Unexpected state: " + current);
};
}
return switch (current) {
case TOP, TOP_HURT -> TOP;
case BOTTOM, BOTTOM_HURT -> BOTTOM;
default -> throw new IllegalStateException("Unexpected state: " + current);
};
}
@Override
public void killed(Game game, Block mob) {
int rottenFlesh = random.nextInt(3) + 1;
//InventoryItem drop = new InventoryItem(rottenFlesh, ItemBlockSupplier.getItem("rotten_flesh"));
//game.getInventory().addItem(drop);
}
public static <K, V> Map.Entry<K, V> getRandomEntry(HashMap<K, V> map) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
return list.get(new Random().nextInt(list.size()));