feat: Food, Health system

This commit is contained in:
2026-01-03 14:38:02 +01:00
parent 00be3b066e
commit 9992c753ba
26 changed files with 214 additions and 66 deletions

View File

@@ -44,20 +44,24 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
for (int y = startYNormalized; y <= endYNormalized; y += 2) {
for (int x = start.getColumn(); x <= end.getColumn(); x++) {
Pixel topPixel = buffer[y][x];
Pixel bottomPixel = (y + 1 <= end.getRow())
? buffer[y + 1][x]
: new Empty();
try {
Pixel topPixel = buffer[y][x];
Pixel bottomPixel = (y + 1 <= end.getRow())
? buffer[y + 1][x]
: new Empty();
TextColor topColor = topPixel instanceof Empty
? Constants.BACKGROUND_COLOR
: topPixel.getColor();
TextColor topColor = topPixel instanceof Empty
? Constants.BACKGROUND_COLOR
: topPixel.getColor();
TextColor bottomColor = bottomPixel instanceof Empty
? Constants.BACKGROUND_COLOR
: bottomPixel.getColor();
TextColor bottomColor = bottomPixel instanceof Empty
? Constants.BACKGROUND_COLOR
: bottomPixel.getColor();
drawHalfPixel(tg, x, y / 2, topColor, bottomColor);
drawHalfPixel(tg, x, y / 2, topColor, bottomColor);
} catch (ArrayIndexOutOfBoundsException ignored) {
// Random error, ignore
}
}
}
}

View File

@@ -40,12 +40,27 @@ public class Player {
stamina--;
}
public void addHealth(int amount) {
health = Math.min(MAX_HEALTH, health + amount);
}
public boolean dealDamage(int amount) {
if (health - amount <= 0) {
health = 0;
return true;
}
health -= amount;
return false;
}
public int getDamageDeal() {
int damage = 1;
// Probably in the future, there will be more logic like potions, etc.
log.debug("Selected item: {}", selectedItem);
if (selectedItem.getType() instanceof WeaponInterface item) {
if (selectedItem != null && selectedItem.getType() instanceof WeaponInterface item) {
damage = item.getDamageDeal();
}

View File

@@ -34,6 +34,8 @@ public class ResourceManager {
WOODEN_SWORD("tools/wooden_sword.png"),
APPLE("food/apple.png"),
DOORS("rooms/doors.png");
private final String path;

View File

@@ -1,4 +0,0 @@
package cz.jzitnik.game.items.strategy;
public interface Strategy {
}

View File

@@ -1,9 +0,0 @@
package cz.jzitnik.game.items.strategy;
import cz.jzitnik.game.items.types.Sword;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SwordStrategy implements Strategy {
private final Sword sword;
}

View File

@@ -0,0 +1,12 @@
package cz.jzitnik.game.items.types;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.StateManager;
public interface InteractableItem {
InteractableItemResponse interact(DependencyManager dependencyManager, StateManager stateManager);
enum InteractableItemResponse {
CLEAR_ITEM,
}
}

View File

@@ -1,8 +1,5 @@
package cz.jzitnik.game.items.types;
import cz.jzitnik.game.items.strategy.Strategy;
public interface ItemType<T> {
Class<T> getItemType();
Strategy getStrategy();
}

View File

@@ -1,15 +0,0 @@
package cz.jzitnik.game.items.types;
import cz.jzitnik.game.items.strategy.Strategy;
import cz.jzitnik.game.items.strategy.SwordStrategy;
public class Sword extends Weapon {
public Sword(int damageDeal) {
super(damageDeal);
}
@Override
public Strategy getStrategy() {
return new SwordStrategy(this);
}
}

View File

@@ -0,0 +1,31 @@
package cz.jzitnik.game.items.types.food;
import cz.jzitnik.events.RenderStats;
import cz.jzitnik.game.GameState;
import cz.jzitnik.game.items.types.InteractableItem;
import cz.jzitnik.game.items.types.ItemType;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.StateManager;
import cz.jzitnik.utils.events.EventManager;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Food implements InteractableItem, ItemType<Food> {
private final int addHealth;
@Override
public InteractableItemResponse interact(DependencyManager dependencyManager, StateManager stateManager) {
GameState gameState = stateManager.getOrThrow(GameState.class);
EventManager eventManager = dependencyManager.getDependencyOrThrow(EventManager.class);
gameState.getPlayer().addHealth(addHealth);
eventManager.emitEvent(new RenderStats());
return InteractableItemResponse.CLEAR_ITEM;
}
@Override
public Class<Food> getItemType() {
return Food.class;
}
}

View File

@@ -0,0 +1,7 @@
package cz.jzitnik.game.items.types.weapons;
public class Sword extends Weapon {
public Sword(int damageDeal) {
super(damageDeal);
}
}

View File

@@ -1,6 +1,6 @@
package cz.jzitnik.game.items.types;
package cz.jzitnik.game.items.types.weapons;
import cz.jzitnik.game.items.strategy.Strategy;
import cz.jzitnik.game.items.types.ItemType;
import cz.jzitnik.game.items.types.interfaces.WeaponInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -15,5 +15,4 @@ public abstract class Weapon implements ItemType<Weapon>, WeaponInterface {
return Weapon.class;
}
public abstract Strategy getStrategy();
}

View File

@@ -67,7 +67,7 @@ public abstract class HittableMob extends Mob {
if (health <= 0) {
onKilled();
if (task != null) {
for (RoomTask task : tasks) {
roomTaskScheduler.stopTask(task);
}
gameState.getCurrentRoom().getMobs().remove(this);

View File

@@ -14,12 +14,18 @@ public abstract class Mob implements Renderable, Selectable {
protected final BufferedImage texture;
@Setter
protected RoomTask task;
protected RoomTask[] tasks;
protected final RoomCords cords;
public Mob(BufferedImage texture, RoomTask task, RoomCords cords) {
this.texture = texture;
this.task = task;
this.tasks = new RoomTask[] {task};
this.cords = cords;
}
public Mob(BufferedImage texture, RoomTask[] tasks, RoomCords cords) {
this.texture = texture;
this.tasks = tasks;
this.cords = cords;
}

View File

@@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor;
import java.util.concurrent.TimeUnit;
public class BlindMobFollowingPlayerTask extends RoomTask {
public BlindMobFollowingPlayerTask(Mob mob, int speed, int updateRate) {
super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS);
public BlindMobFollowingPlayerTask(Mob mob, int speed, int updateRateMs) {
super(new Task(mob, speed), updateRateMs, TimeUnit.MILLISECONDS);
}
@RequiredArgsConstructor

View File

@@ -0,0 +1,54 @@
package cz.jzitnik.game.mobs.tasks;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.RenderStats;
import cz.jzitnik.game.GameState;
import cz.jzitnik.game.mobs.Mob;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.utils.events.EventManager;
import cz.jzitnik.utils.roomtasks.RoomTask;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@Slf4j
public class EnemyPlayerHittingTask extends RoomTask {
public EnemyPlayerHittingTask(Mob mob, long updateRateMs, double reach, Supplier<Integer> damageSupplier) {
super(new Task(reach, damageSupplier, mob), updateRateMs, TimeUnit.MILLISECONDS);
}
@RequiredArgsConstructor
private static class Task implements Runnable {
private final double reach;
private final Supplier<Integer> damageSupplier;
private final Mob mob;
@InjectState
private GameState gameState;
@InjectDependency
private EventManager eventManager;
@Override
public void run() {
RoomCords playerCords = gameState.getPlayer().getPlayerCords();
RoomCords mobCords = mob.getCords();
double distance = playerCords.calculateDistance(mobCords);
if (distance > reach) {
return;
}
int damage = damageSupplier.get();
boolean isDead = gameState.getPlayer().dealDamage(damage);
eventManager.emitEvent(new RenderStats());
log.debug("Is dead: {}", isDead);
// TODO: Death screen
}
}
}

View File

@@ -11,6 +11,7 @@ import cz.jzitnik.game.GameState;
import cz.jzitnik.game.Player;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.mobs.Mob;
import cz.jzitnik.game.mobs.tasks.utils.AStarAlg;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState;
@@ -26,8 +27,8 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class MobFollowingPlayerTask extends RoomTask {
public MobFollowingPlayerTask(Mob mob, int speed, int updateRate) {
super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS);
public MobFollowingPlayerTask(Mob mob, int speed, int updateRateMs) {
super(new Task(mob, speed), updateRateMs, TimeUnit.MILLISECONDS);
}
@RequiredArgsConstructor

View File

@@ -1,4 +1,4 @@
package cz.jzitnik.game.mobs.tasks;
package cz.jzitnik.game.mobs.tasks.utils;
import cz.jzitnik.game.GameRoomPart;
import cz.jzitnik.game.utils.RoomCords;

View File

@@ -1,10 +1,11 @@
package cz.jzitnik.game.setup.mobs;
package cz.jzitnik.game.setup.enemies;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.mobs.HittableMob;
import cz.jzitnik.game.mobs.tasks.BlindMobFollowingPlayerTask;
import cz.jzitnik.game.mobs.tasks.EnemyPlayerHittingTask;
import cz.jzitnik.game.mobs.tasks.MobFollowingPlayerTask;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.utils.roomtasks.RoomTask;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -12,8 +13,10 @@ public class Zombie extends HittableMob {
public Zombie(ResourceManager resourceManager, RoomCords cords) {
super(resourceManager.getResource(ResourceManager.Resource.PLAYER_FRONT), null, cords, 10);
setTask(new BlindMobFollowingPlayerTask(this, 1, 100));
//setTask(new MobFollowingPlayerTask(this, 1, 100));
setTasks(new RoomTask[]{
new MobFollowingPlayerTask(this, 1, 100),
new EnemyPlayerHittingTask(this, 500, 15, () -> 5)
});
}
@Override

View File

@@ -0,0 +1,15 @@
package cz.jzitnik.game.setup.items;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.types.food.Food;
public class Apple extends GameItem {
public Apple(ResourceManager resourceManager) {
super(
"Apple",
new Food(1),
resourceManager.getResource(ResourceManager.Resource.APPLE)
);
}
}

View File

@@ -2,7 +2,7 @@ package cz.jzitnik.game.setup.items;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.types.Sword;
import cz.jzitnik.game.items.types.weapons.Sword;
public class WoodenSword extends GameItem {
public WoodenSword(ResourceManager resourceManager) {

View File

@@ -4,9 +4,10 @@ import cz.jzitnik.game.GameRoom;
import cz.jzitnik.game.GameRoomPart;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.setup.items.Apple;
import cz.jzitnik.game.setup.items.WoodenSword;
import cz.jzitnik.game.objects.Chest;
import cz.jzitnik.game.setup.mobs.Zombie;
import cz.jzitnik.game.setup.enemies.Zombie;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.utils.DependencyManager;
import lombok.extern.slf4j.Slf4j;
@@ -18,8 +19,7 @@ public class MainRoom extends GameRoom {
Chest chest = new Chest(dependencyManager, resourceManager, new RoomCords(100, 45), new GameItem[]{
new WoodenSword(resourceManager),
new WoodenSword(resourceManager),
new WoodenSword(resourceManager),
new Apple(resourceManager)
});
addCollider(new GameRoomPart(
new RoomCords(60, 10),
@@ -28,6 +28,6 @@ public class MainRoom extends GameRoom {
addObject(chest);
Zombie zombie = new Zombie(resourceManager, new RoomCords(100, 100));
//addMob(zombie);
addMob(zombie);
}
}

View File

@@ -38,4 +38,16 @@ public class RoomCords implements Cloneable {
throw new AssertionError();
}
}
/**
* Calculates the Euclidean distance between this coordinate and another.
* @param other The other RoomCords instance
* @return The distance as a double
*/
public double calculateDistance(RoomCords other) {
if (other == null) {
throw new IllegalArgumentException("Cannot calculate distance to null");
}
return Math.hypot(this.x - other.x, this.y - other.y);
}
}

View File

@@ -11,13 +11,16 @@ import cz.jzitnik.events.MouseAction;
import cz.jzitnik.game.GameState;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.types.InteractableItem;
import cz.jzitnik.game.objects.GlobalUIClickHandler;
import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState;
import cz.jzitnik.ui.pixels.ColoredPixel;
import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.ui.utils.Grid;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.RerenderUtils;
import cz.jzitnik.utils.StateManager;
import cz.jzitnik.utils.events.EventManager;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -82,6 +85,10 @@ public class Inventory implements GlobalUIClickHandler {
private ScreenBuffer screenBuffer;
@InjectDependency
private EventManager eventManager;
@InjectDependency
private DependencyManager dependencyManager;
@InjectDependency
private StateManager stateManager;
@Getter
private int offsetX;
@Getter
@@ -137,6 +144,17 @@ public class Inventory implements GlobalUIClickHandler {
if (inventoryState.isGonnaDoubleClick) {
inventoryState.isGonnaDoubleClick = false;
inventoryState.selectedItem = -1;
if (inventory[itemClickedOnIndex].getType() instanceof InteractableItem item) {
switch (item.interact(dependencyManager, stateManager)) {
case CLEAR_ITEM -> {
inventory[itemClickedOnIndex] = null;
eventManager.emitEvent(new InventoryRerender());
}
}
return true;
}
gameState.getPlayer().setSelectedItem(
gameState.getPlayer().getSelectedItem() == inventory[itemClickedOnIndex]
? null

View File

@@ -7,7 +7,7 @@ import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
@Getter
public class RoomTask {
public abstract class RoomTask {
private final Runnable task;
private final long rate;
private final TimeUnit rateUnit;

View File

@@ -77,9 +77,9 @@ public class RoomTaskScheduler {
}
for (Mob mob : currentRoom.getMobs()) {
RoomTask task = mob.getTask();
RoomTask[] mobTasks = mob.getTasks();
if (task != null) {
for (RoomTask task : mobTasks) {
dependencyManager.inject(task.getTask());
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(task.getTask(), 0, task.getRate(), task.getRateUnit());
tasks.put(task, future);

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B