From ba26b633af38d0326978030f67a9f5ad6989d3dd Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Thu, 1 Jan 2026 14:23:21 +0100 Subject: [PATCH] feat: Swinging mechanic --- .../java/cz/jzitnik/config/PlayerConfig.java | 1 + .../handlers/MouseActionEventHandler.java | 23 ++++++++++-- .../handlers/MouseMoveEventHandler.java | 14 +++++-- src/main/java/cz/jzitnik/game/GameRoom.java | 2 +- src/main/java/cz/jzitnik/game/Player.java | 30 +++++++++++++-- .../cz/jzitnik/game/mobs/HittableMob.java | 33 +++++++++++++++++ src/main/java/cz/jzitnik/game/mobs/Mob.java | 22 +++++++++++ .../java/cz/jzitnik/game/objects/Chest.java | 2 +- .../cz/jzitnik/game/objects/GameObject.java | 3 +- .../java/cz/jzitnik/game/objects/Mob.java | 11 ------ .../cz/jzitnik/game/setup/mobs/Zombie.java | 12 ++++++ .../cz/jzitnik/game/setup/rooms/MainRoom.java | 17 ++------- .../cz/jzitnik/game/utils/Selectable.java | 16 ++++++++ .../java/cz/jzitnik/utils/RerenderUtils.java | 37 +++++++++++++++++-- .../utils/UIClickHandlerRepository.java | 8 ++-- .../utils/roomtasks/RoomTaskScheduler.java | 6 ++- 16 files changed, 189 insertions(+), 48 deletions(-) create mode 100644 src/main/java/cz/jzitnik/game/mobs/HittableMob.java create mode 100644 src/main/java/cz/jzitnik/game/mobs/Mob.java delete mode 100644 src/main/java/cz/jzitnik/game/objects/Mob.java create mode 100644 src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java create mode 100644 src/main/java/cz/jzitnik/game/utils/Selectable.java diff --git a/src/main/java/cz/jzitnik/config/PlayerConfig.java b/src/main/java/cz/jzitnik/config/PlayerConfig.java index 0356345..50e07b9 100644 --- a/src/main/java/cz/jzitnik/config/PlayerConfig.java +++ b/src/main/java/cz/jzitnik/config/PlayerConfig.java @@ -8,4 +8,5 @@ import lombok.Getter; public class PlayerConfig { private final double playerReach = 20; private final int playerMoveDistance = 3; + private final int swingTimeMs = 500; } diff --git a/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java index 09e082e..2087437 100644 --- a/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java @@ -1,13 +1,16 @@ package cz.jzitnik.events.handlers; import cz.jzitnik.annotations.EventHandler; +import cz.jzitnik.annotations.injectors.InjectConfig; import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectState; +import cz.jzitnik.config.PlayerConfig; import cz.jzitnik.events.MouseAction; import cz.jzitnik.events.MouseMoveEvent; import cz.jzitnik.game.GameState; import cz.jzitnik.game.objects.GameObject; import cz.jzitnik.game.objects.Interactable; +import cz.jzitnik.game.utils.Selectable; import cz.jzitnik.states.RenderState; import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.UIClickHandlerRepository; @@ -15,6 +18,7 @@ import cz.jzitnik.utils.events.AbstractEventHandler; import cz.jzitnik.utils.events.EventManager; import java.util.Optional; +import java.util.stream.Stream; @EventHandler(MouseAction.class) public class MouseActionEventHandler extends AbstractEventHandler { @@ -34,6 +38,9 @@ public class MouseActionEventHandler extends AbstractEventHandler { @InjectState private RenderState renderState; + @InjectConfig + private PlayerConfig playerConfig; + @Override public void handle(MouseAction event) { if (gameState.getScreen() != null) { @@ -48,14 +55,22 @@ public class MouseActionEventHandler extends AbstractEventHandler { switch (event.getActionType()) { case MOVE -> eventManager.emitEvent(new MouseMoveEvent(event)); case CLICK_RELEASE -> { - Optional object = gameState.getCurrentRoom().getObjects().stream().filter(GameObject::isSelected).findFirst(); + boolean clicked = uiClickHandlerRepository.handleClick(event); - if (object.isPresent()) { - ((Interactable) object.get()).interact(dm); + if (clicked || gameState.getPlayer().isSwinging()) { return; } - uiClickHandlerRepository.handleClick(event); + Stream combined = Stream.concat( + gameState.getCurrentRoom().getMobs().stream(), + gameState.getCurrentRoom().getObjects().stream() + ); + + Optional object = combined.filter(Selectable::isSelected).findFirst(); + + gameState.getPlayer().swing(playerConfig.getSwingTimeMs()); + + object.ifPresent(selectable -> selectable.interact(dm)); } } } diff --git a/src/main/java/cz/jzitnik/events/handlers/MouseMoveEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/MouseMoveEventHandler.java index 26583e4..41617ea 100644 --- a/src/main/java/cz/jzitnik/events/handlers/MouseMoveEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/MouseMoveEventHandler.java @@ -15,7 +15,9 @@ import cz.jzitnik.game.GameState; import cz.jzitnik.game.Player; import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.objects.GameObject; +import cz.jzitnik.game.utils.Renderable; import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.game.utils.Selectable; import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.TerminalState; import cz.jzitnik.utils.DependencyManager; @@ -30,6 +32,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @EventHandler(MouseMoveEvent.class) @@ -90,7 +93,10 @@ public class MouseMoveEventHandler extends AbstractEventHandler int startX = start.getX(); int startY = start.getY(); - Set selectedObjects = currentRoom.getObjects().stream().filter(gameObject -> { + List combinedObjects = Stream + .concat(currentRoom.getObjects().stream(), currentRoom.getMobs().stream()).toList(); + + Set selectedObjects = combinedObjects.stream().filter(gameObject -> { if (!gameObject.isSelectable()) return false; BufferedImage texture = gameObject.getTexture(); RoomCords cords = gameObject.getCords(); @@ -122,9 +128,9 @@ public class MouseMoveEventHandler extends AbstractEventHandler relativeMouseY < cords.getY() + texture.getHeight(); }).collect(Collectors.toSet()); - Set changedObjects = new HashSet<>(); + Set changedObjects = new HashSet<>(); - for (GameObject object : currentRoom.getObjects()) { + for (Selectable object : combinedObjects) { boolean newValue = selectedObjects.contains(object); boolean changed = object.isSelected() != newValue; @@ -136,7 +142,7 @@ public class MouseMoveEventHandler extends AbstractEventHandler List parts = new ArrayList<>(); - for (GameObject object : changedObjects) { + for (Selectable object : changedObjects) { RoomCords cords = object.getCords(); BufferedImage objectTexture = object.getTexture(); diff --git a/src/main/java/cz/jzitnik/game/GameRoom.java b/src/main/java/cz/jzitnik/game/GameRoom.java index b49e50c..0d32395 100644 --- a/src/main/java/cz/jzitnik/game/GameRoom.java +++ b/src/main/java/cz/jzitnik/game/GameRoom.java @@ -1,7 +1,7 @@ package cz.jzitnik.game; import cz.jzitnik.game.objects.GameObject; -import cz.jzitnik.game.objects.Mob; +import cz.jzitnik.game.mobs.Mob; import cz.jzitnik.ui.pixels.Empty; import cz.jzitnik.ui.pixels.Pixel; import lombok.Getter; diff --git a/src/main/java/cz/jzitnik/game/Player.java b/src/main/java/cz/jzitnik/game/Player.java index 3aee2cf..5f5a310 100644 --- a/src/main/java/cz/jzitnik/game/Player.java +++ b/src/main/java/cz/jzitnik/game/Player.java @@ -4,16 +4,38 @@ import cz.jzitnik.game.utils.RoomCords; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @RequiredArgsConstructor @Getter +@Slf4j public class Player { - public enum PlayerRotation { - FRONT, BACK, LEFT, RIGHT - } - private final RoomCords playerCords; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private boolean swinging = false; @Setter private PlayerRotation playerRotation = PlayerRotation.FRONT; + + public void swing(int delayMs) { + if (swinging) { + return; + } + + log.debug("Started swinging"); + swinging = true; + + scheduler.schedule(() -> { + swinging = false; + log.debug("Swinging done"); + }, delayMs, TimeUnit.MILLISECONDS); + } + + public enum PlayerRotation { + FRONT, BACK, LEFT, RIGHT + } } diff --git a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java new file mode 100644 index 0000000..9ce6226 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java @@ -0,0 +1,33 @@ +package cz.jzitnik.game.mobs; + +import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.utils.DependencyManager; +import cz.jzitnik.utils.roomtasks.RoomTask; +import lombok.extern.slf4j.Slf4j; + +import java.awt.image.BufferedImage; + +@Slf4j +public abstract class HittableMob extends Mob { + protected int health; + + public HittableMob(BufferedImage texture, RoomTask task, RoomCords cords, int initialHealth) { + super(texture, task, cords); + health = initialHealth; + } + + @Override + public void interact(DependencyManager dm) { + // TODO: Swords in hand will deal more damage, for now deal always one + health--; + + log.debug("Health: {}", health); + + if (health <= 0) { + return; + } + + // play animation + + } +} diff --git a/src/main/java/cz/jzitnik/game/mobs/Mob.java b/src/main/java/cz/jzitnik/game/mobs/Mob.java new file mode 100644 index 0000000..64445ab --- /dev/null +++ b/src/main/java/cz/jzitnik/game/mobs/Mob.java @@ -0,0 +1,22 @@ +package cz.jzitnik.game.mobs; + +import cz.jzitnik.game.utils.Renderable; +import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.game.utils.Selectable; +import cz.jzitnik.utils.roomtasks.RoomTask; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.awt.image.BufferedImage; + +@Getter +@RequiredArgsConstructor +public abstract class Mob implements Renderable, Selectable { + private final BufferedImage texture; + private final RoomTask task; + private final RoomCords cords; + + @Setter + private boolean selected = false; +} diff --git a/src/main/java/cz/jzitnik/game/objects/Chest.java b/src/main/java/cz/jzitnik/game/objects/Chest.java index 8ba2148..da679db 100644 --- a/src/main/java/cz/jzitnik/game/objects/Chest.java +++ b/src/main/java/cz/jzitnik/game/objects/Chest.java @@ -32,7 +32,7 @@ import java.util.HashSet; import java.util.List; @Slf4j -public final class Chest extends GameObject implements Interactable, UIClickHandler { +public final class Chest extends GameObject implements UIClickHandler { private static final TextColor BORDER_COLOR = new TextColor.RGB(0, 0, 0); private static final TextColor TRANSPARENT_COLOR = new TextColor.RGB(255, 255, 255); diff --git a/src/main/java/cz/jzitnik/game/objects/GameObject.java b/src/main/java/cz/jzitnik/game/objects/GameObject.java index 5488f78..b34989c 100644 --- a/src/main/java/cz/jzitnik/game/objects/GameObject.java +++ b/src/main/java/cz/jzitnik/game/objects/GameObject.java @@ -2,6 +2,7 @@ package cz.jzitnik.game.objects; import cz.jzitnik.game.utils.Renderable; import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.game.utils.Selectable; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -10,7 +11,7 @@ import java.awt.image.BufferedImage; @Getter @RequiredArgsConstructor -public sealed abstract class GameObject implements Renderable permits Chest { +public sealed abstract class GameObject implements Renderable, Selectable permits Chest { private final BufferedImage texture; private final RoomCords cords; private final boolean selectable; diff --git a/src/main/java/cz/jzitnik/game/objects/Mob.java b/src/main/java/cz/jzitnik/game/objects/Mob.java deleted file mode 100644 index 7163583..0000000 --- a/src/main/java/cz/jzitnik/game/objects/Mob.java +++ /dev/null @@ -1,11 +0,0 @@ -package cz.jzitnik.game.objects; - -import cz.jzitnik.utils.roomtasks.RoomTask; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public abstract class Mob { - private final RoomTask task; -} diff --git a/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java new file mode 100644 index 0000000..3bace8f --- /dev/null +++ b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java @@ -0,0 +1,12 @@ +package cz.jzitnik.game.setup.mobs; + +import cz.jzitnik.game.ResourceManager; +import cz.jzitnik.game.mobs.HittableMob; +import cz.jzitnik.game.utils.RoomCords; + +public class Zombie extends HittableMob { + + public Zombie(ResourceManager resourceManager, RoomCords cords) { + super(resourceManager.getResource(ResourceManager.Resource.CHEST), null, cords, 10); + } +} diff --git a/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java b/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java index b2cf294..c45d006 100644 --- a/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java +++ b/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java @@ -6,16 +6,11 @@ import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.items.GameItem; import cz.jzitnik.game.items.WoodenSword; import cz.jzitnik.game.objects.Chest; -import cz.jzitnik.game.objects.Mob; +import cz.jzitnik.game.setup.mobs.Zombie; import cz.jzitnik.game.utils.RoomCords; -import cz.jzitnik.states.SoundState; import cz.jzitnik.utils.DependencyManager; -import cz.jzitnik.utils.StateManager; -import cz.jzitnik.utils.roomtasks.RoomTask; import lombok.extern.slf4j.Slf4j; -import java.util.concurrent.TimeUnit; - @Slf4j public class MainRoom extends GameRoom { public MainRoom(DependencyManager dependencyManager, ResourceManager resourceManager) { @@ -32,13 +27,7 @@ public class MainRoom extends GameRoom { )); addObject(chest); - SoundState soundState = dependencyManager.getDependencyOrThrow(StateManager.class).getOrThrow(SoundState.class); - Mob testMob = new Mob(new RoomTask( - () -> log.debug("Sound: {}", soundState.getSoundVolume()), - 1, - TimeUnit.SECONDS - )) {}; - - addMob(testMob); + Zombie zombie = new Zombie(resourceManager, new RoomCords(100, 100)); + addMob(zombie); } } diff --git a/src/main/java/cz/jzitnik/game/utils/Selectable.java b/src/main/java/cz/jzitnik/game/utils/Selectable.java new file mode 100644 index 0000000..1052413 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/utils/Selectable.java @@ -0,0 +1,16 @@ +package cz.jzitnik.game.utils; + +import cz.jzitnik.game.objects.Interactable; + +import java.awt.image.BufferedImage; + +public interface Selectable extends Interactable { + boolean isSelected(); + void setSelected(boolean selected); + BufferedImage getTexture(); + RoomCords getCords(); + + default boolean isSelectable() { + return true; + } +} diff --git a/src/main/java/cz/jzitnik/utils/RerenderUtils.java b/src/main/java/cz/jzitnik/utils/RerenderUtils.java index 54a22e0..7d9ca04 100644 --- a/src/main/java/cz/jzitnik/utils/RerenderUtils.java +++ b/src/main/java/cz/jzitnik/utils/RerenderUtils.java @@ -7,12 +7,14 @@ import cz.jzitnik.events.handlers.FullRoomDrawHandler; import cz.jzitnik.game.GameRoom; import cz.jzitnik.game.Player; import cz.jzitnik.game.ResourceManager; +import cz.jzitnik.game.mobs.Mob; import cz.jzitnik.game.objects.GameObject; import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.ui.pixels.ColoredPixel; import cz.jzitnik.ui.pixels.Empty; import cz.jzitnik.ui.pixels.Pixel; +import lombok.extern.slf4j.Slf4j; import java.awt.image.BufferedImage; import java.util.HashSet; @@ -80,11 +82,13 @@ public class RerenderUtils { } } - for (GameObject object : currentRoom.getObjects()) { + // TODO: Remove duplicates + for (Mob object: currentRoom.getMobs()) { RoomCords startObjectCords = object.getCords(); BufferedImage texture = object.getTexture(); - RoomCords endObjectCords = new RoomCords(startObjectCords.getX() + texture.getWidth() - 1, startObjectCords.getY() + texture.getHeight() - 1); + boolean isSelected = object.isSelected(); + RoomCords endObjectCords = new RoomCords(startObjectCords.getX() + texture.getWidth() - 1, startObjectCords.getY() + texture.getHeight() - 1); if (x >= startObjectCords.getX() && x <= endObjectCords.getX() && y >= startObjectCords.getY() && y <= endObjectCords.getY()) { int pixel = texture.getRGB(x - startObjectCords.getX(), y - startObjectCords.getY()); int alpha = (pixel >> 24) & 0xff; @@ -94,7 +98,34 @@ public class RerenderUtils { float factor = 1.5f; // brightness multiplier if (alpha != 0) { - if (object.isSelected()) { + if (isSelected) { + r = Math.min(255, (int)(r * factor)); + g = Math.min(255, (int)(g * factor)); + b = Math.min(255, (int)(b * factor)); + pixel = (alpha << 24) | (r << 16) | (g << 8) | b; + } + + return new PixelResult(pixel, false); + } + } + } + + for (GameObject object : currentRoom.getObjects()) { + RoomCords startObjectCords = object.getCords(); + BufferedImage texture = object.getTexture(); + boolean isSelected = object.isSelected(); + + RoomCords endObjectCords = new RoomCords(startObjectCords.getX() + texture.getWidth() - 1, startObjectCords.getY() + texture.getHeight() - 1); + if (x >= startObjectCords.getX() && x <= endObjectCords.getX() && y >= startObjectCords.getY() && y <= endObjectCords.getY()) { + int pixel = texture.getRGB(x - startObjectCords.getX(), y - startObjectCords.getY()); + int alpha = (pixel >> 24) & 0xff; + int r = (pixel >> 16) & 0xff; + int g = (pixel >> 8) & 0xff; + int b = pixel & 0xff; + float factor = 1.5f; // brightness multiplier + + if (alpha != 0) { + if (isSelected) { r = Math.min(255, (int)(r * factor)); g = Math.min(255, (int)(g * factor)); b = Math.min(255, (int)(b * factor)); diff --git a/src/main/java/cz/jzitnik/utils/UIClickHandlerRepository.java b/src/main/java/cz/jzitnik/utils/UIClickHandlerRepository.java index 47c8f76..20451d5 100644 --- a/src/main/java/cz/jzitnik/utils/UIClickHandlerRepository.java +++ b/src/main/java/cz/jzitnik/utils/UIClickHandlerRepository.java @@ -34,9 +34,9 @@ public class UIClickHandlerRepository { return screenPart.hashCode(); } - public void handleClick(MouseAction mouseAction) { + public boolean handleClick(MouseAction mouseAction) { if (!roomSpecificHandlers.containsKey(gameState.getCurrentRoom())) { - return; + return false; } Map handlers = roomSpecificHandlers.get(gameState.getCurrentRoom()); @@ -48,9 +48,11 @@ public class UIClickHandlerRepository { if (part.isWithin(position)) { uiClickHandler.handleClick(mouseAction); - return; + return true; } } + + return false; } public void removeHandlerForCurrentRoom(int screenPartHashCode, UIClickHandler uiClickHandler) { diff --git a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java index 99588b4..fff033c 100644 --- a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java +++ b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java @@ -5,7 +5,7 @@ import cz.jzitnik.annotations.PostInit; import cz.jzitnik.annotations.injectors.InjectConfig; import cz.jzitnik.config.ThreadPoolConfig; import cz.jzitnik.game.GameRoom; -import cz.jzitnik.game.objects.Mob; +import cz.jzitnik.game.mobs.Mob; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; @@ -54,7 +54,9 @@ public class RoomTaskScheduler { for (Mob mob : currentRoom.getMobs()) { RoomTask task = mob.getTask(); - scheduler.scheduleAtFixedRate(task.task(), 0, task.rate(), task.rateUnit()); + if (task != null) { + scheduler.scheduleAtFixedRate(task.task(), 0, task.rate(), task.rateUnit()); + } } firstRun = false;