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,6 +44,7 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
for (int y = startYNormalized; y <= endYNormalized; y += 2) { for (int y = startYNormalized; y <= endYNormalized; y += 2) {
for (int x = start.getColumn(); x <= end.getColumn(); x++) { for (int x = start.getColumn(); x <= end.getColumn(); x++) {
try {
Pixel topPixel = buffer[y][x]; Pixel topPixel = buffer[y][x];
Pixel bottomPixel = (y + 1 <= end.getRow()) Pixel bottomPixel = (y + 1 <= end.getRow())
? buffer[y + 1][x] ? buffer[y + 1][x]
@@ -58,6 +59,9 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
: bottomPixel.getColor(); : 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--; 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() { public int getDamageDeal() {
int damage = 1; int damage = 1;
// Probably in the future, there will be more logic like potions, etc. // Probably in the future, there will be more logic like potions, etc.
log.debug("Selected item: {}", selectedItem); log.debug("Selected item: {}", selectedItem);
if (selectedItem.getType() instanceof WeaponInterface item) { if (selectedItem != null && selectedItem.getType() instanceof WeaponInterface item) {
damage = item.getDamageDeal(); damage = item.getDamageDeal();
} }

View File

@@ -34,6 +34,8 @@ public class ResourceManager {
WOODEN_SWORD("tools/wooden_sword.png"), WOODEN_SWORD("tools/wooden_sword.png"),
APPLE("food/apple.png"),
DOORS("rooms/doors.png"); DOORS("rooms/doors.png");
private final String path; 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; package cz.jzitnik.game.items.types;
import cz.jzitnik.game.items.strategy.Strategy;
public interface ItemType<T> { public interface ItemType<T> {
Class<T> getItemType(); 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 cz.jzitnik.game.items.types.interfaces.WeaponInterface;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -15,5 +15,4 @@ public abstract class Weapon implements ItemType<Weapon>, WeaponInterface {
return Weapon.class; return Weapon.class;
} }
public abstract Strategy getStrategy();
} }

View File

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

View File

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

View File

@@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class BlindMobFollowingPlayerTask extends RoomTask { public class BlindMobFollowingPlayerTask extends RoomTask {
public BlindMobFollowingPlayerTask(Mob mob, int speed, int updateRate) { public BlindMobFollowingPlayerTask(Mob mob, int speed, int updateRateMs) {
super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS); super(new Task(mob, speed), updateRateMs, TimeUnit.MILLISECONDS);
} }
@RequiredArgsConstructor @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.Player;
import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.mobs.Mob; import cz.jzitnik.game.mobs.Mob;
import cz.jzitnik.game.mobs.tasks.utils.AStarAlg;
import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState; import cz.jzitnik.states.TerminalState;
@@ -26,8 +27,8 @@ import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public class MobFollowingPlayerTask extends RoomTask { public class MobFollowingPlayerTask extends RoomTask {
public MobFollowingPlayerTask(Mob mob, int speed, int updateRate) { public MobFollowingPlayerTask(Mob mob, int speed, int updateRateMs) {
super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS); super(new Task(mob, speed), updateRateMs, TimeUnit.MILLISECONDS);
} }
@RequiredArgsConstructor @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.GameRoomPart;
import cz.jzitnik.game.utils.RoomCords; 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.ResourceManager;
import cz.jzitnik.game.mobs.HittableMob; 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.mobs.tasks.MobFollowingPlayerTask;
import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.utils.roomtasks.RoomTask;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@@ -12,8 +13,10 @@ public class Zombie extends HittableMob {
public Zombie(ResourceManager resourceManager, RoomCords cords) { public Zombie(ResourceManager resourceManager, RoomCords cords) {
super(resourceManager.getResource(ResourceManager.Resource.PLAYER_FRONT), null, cords, 10); super(resourceManager.getResource(ResourceManager.Resource.PLAYER_FRONT), null, cords, 10);
setTask(new BlindMobFollowingPlayerTask(this, 1, 100)); setTasks(new RoomTask[]{
//setTask(new MobFollowingPlayerTask(this, 1, 100)); new MobFollowingPlayerTask(this, 1, 100),
new EnemyPlayerHittingTask(this, 500, 15, () -> 5)
});
} }
@Override @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.ResourceManager;
import cz.jzitnik.game.items.GameItem; 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 class WoodenSword extends GameItem {
public WoodenSword(ResourceManager resourceManager) { public WoodenSword(ResourceManager resourceManager) {

View File

@@ -4,9 +4,10 @@ import cz.jzitnik.game.GameRoom;
import cz.jzitnik.game.GameRoomPart; import cz.jzitnik.game.GameRoomPart;
import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem; import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.setup.items.Apple;
import cz.jzitnik.game.setup.items.WoodenSword; import cz.jzitnik.game.setup.items.WoodenSword;
import cz.jzitnik.game.objects.Chest; 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.game.utils.RoomCords;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
import lombok.extern.slf4j.Slf4j; 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[]{ Chest chest = new Chest(dependencyManager, resourceManager, new RoomCords(100, 45), new GameItem[]{
new WoodenSword(resourceManager), new WoodenSword(resourceManager),
new WoodenSword(resourceManager), new Apple(resourceManager)
new WoodenSword(resourceManager),
}); });
addCollider(new GameRoomPart( addCollider(new GameRoomPart(
new RoomCords(60, 10), new RoomCords(60, 10),
@@ -28,6 +28,6 @@ public class MainRoom extends GameRoom {
addObject(chest); addObject(chest);
Zombie zombie = new Zombie(resourceManager, new RoomCords(100, 100)); 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(); 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.GameState;
import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem; import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.types.InteractableItem;
import cz.jzitnik.game.objects.GlobalUIClickHandler; import cz.jzitnik.game.objects.GlobalUIClickHandler;
import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState; import cz.jzitnik.states.TerminalState;
import cz.jzitnik.ui.pixels.ColoredPixel; import cz.jzitnik.ui.pixels.ColoredPixel;
import cz.jzitnik.ui.pixels.Pixel; import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.ui.utils.Grid; import cz.jzitnik.ui.utils.Grid;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.RerenderUtils; import cz.jzitnik.utils.RerenderUtils;
import cz.jzitnik.utils.StateManager;
import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.events.EventManager;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -82,6 +85,10 @@ public class Inventory implements GlobalUIClickHandler {
private ScreenBuffer screenBuffer; private ScreenBuffer screenBuffer;
@InjectDependency @InjectDependency
private EventManager eventManager; private EventManager eventManager;
@InjectDependency
private DependencyManager dependencyManager;
@InjectDependency
private StateManager stateManager;
@Getter @Getter
private int offsetX; private int offsetX;
@Getter @Getter
@@ -137,6 +144,17 @@ public class Inventory implements GlobalUIClickHandler {
if (inventoryState.isGonnaDoubleClick) { if (inventoryState.isGonnaDoubleClick) {
inventoryState.isGonnaDoubleClick = false; inventoryState.isGonnaDoubleClick = false;
inventoryState.selectedItem = -1; 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().setSelectedItem(
gameState.getPlayer().getSelectedItem() == inventory[itemClickedOnIndex] gameState.getPlayer().getSelectedItem() == inventory[itemClickedOnIndex]
? null ? null

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B