diff --git a/src/main/java/cz/jzitnik/Game.java b/src/main/java/cz/jzitnik/Game.java index 9848c14..b0cf666 100644 --- a/src/main/java/cz/jzitnik/Game.java +++ b/src/main/java/cz/jzitnik/Game.java @@ -2,6 +2,7 @@ package cz.jzitnik; import cz.jzitnik.game.setup.GameSetup; import cz.jzitnik.utils.DependencyManager; +import cz.jzitnik.utils.ScheduledTaskManager; import cz.jzitnik.utils.ThreadManager; import org.reflections.Reflections; @@ -13,9 +14,11 @@ public class Game { GameSetup gameSetup = dependencyManager.getDependencyOrThrow(GameSetup.class); ThreadManager threadManager = dependencyManager.getDependencyOrThrow(ThreadManager.class); + ScheduledTaskManager scheduledTaskManager = dependencyManager.getDependencyOrThrow(ScheduledTaskManager.class); gameSetup.setup(); threadManager.startAll(); + scheduledTaskManager.startAll(); cli.run(); } diff --git a/src/main/java/cz/jzitnik/annotations/ScheduledTask.java b/src/main/java/cz/jzitnik/annotations/ScheduledTask.java new file mode 100644 index 0000000..f60c113 --- /dev/null +++ b/src/main/java/cz/jzitnik/annotations/ScheduledTask.java @@ -0,0 +1,14 @@ +package cz.jzitnik.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.util.concurrent.TimeUnit; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ScheduledTask { + long rate(); + TimeUnit rateUnit(); +} diff --git a/src/main/java/cz/jzitnik/config/PlayerConfig.java b/src/main/java/cz/jzitnik/config/PlayerConfig.java index 50e07b9..19744b3 100644 --- a/src/main/java/cz/jzitnik/config/PlayerConfig.java +++ b/src/main/java/cz/jzitnik/config/PlayerConfig.java @@ -1,6 +1,7 @@ package cz.jzitnik.config; import cz.jzitnik.annotations.Config; +import cz.jzitnik.events.handlers.PlayerMoveEventHandler; import lombok.Getter; @Getter @@ -8,5 +9,10 @@ import lombok.Getter; public class PlayerConfig { private final double playerReach = 20; private final int playerMoveDistance = 3; + private final int playerMoveDistanceSprinting = 6; + private final PlayerMoveEventHandler.SprintKey sprintKey = PlayerMoveEventHandler.SprintKey.CTRL; private final int swingTimeMs = 500; + + private final int staminaIncreaseRateMs = 500; + private final int staminaDelayMs = 1000; } diff --git a/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java index 2111e34..7a35cbe 100644 --- a/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java @@ -6,6 +6,7 @@ import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.events.ExitEvent; import cz.jzitnik.states.RunningState; import cz.jzitnik.utils.DependencyManager; +import cz.jzitnik.utils.ScheduledTaskManager; import cz.jzitnik.utils.StateManager; import cz.jzitnik.utils.ThreadManager; import cz.jzitnik.utils.events.AbstractEventHandler; @@ -22,6 +23,9 @@ public class ExitEventHandler extends AbstractEventHandler { @InjectDependency private RoomTaskScheduler roomTaskScheduler; + @InjectDependency + private ScheduledTaskManager scheduledTaskManager; + public ExitEventHandler(DependencyManager dm) { super(dm); } @@ -29,6 +33,7 @@ public class ExitEventHandler extends AbstractEventHandler { @Override public void handle(ExitEvent event) { threadManager.shutdownAll(); + scheduledTaskManager.shutdown(); roomTaskScheduler.finalShutdown(); runningState.setRunning(false); System.exit(0); // Pls don't blame me diff --git a/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java index 37d20f9..52040ed 100644 --- a/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java @@ -19,6 +19,7 @@ import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.states.RenderState; import cz.jzitnik.states.ScreenBuffer; +import cz.jzitnik.states.PlayerMovementState; import cz.jzitnik.states.TerminalState; import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.RerenderUtils; @@ -31,37 +32,31 @@ import java.awt.image.BufferedImage; @Slf4j @EventHandler(PlayerMoveEvent.class) public class PlayerMoveEventHandler extends AbstractEventHandler { + @InjectState + private GameState gameState; + @InjectDependency + private EventManager eventManager; + @InjectState + private TerminalState terminalState; + @InjectState + private ScreenBuffer screenBuffer; + @InjectDependency + private ResourceManager resourceManager; + @InjectConfig + private Debugging debugging; + @InjectConfig + private PlayerConfig playerConfig; + @InjectState + private RenderState renderState; + @InjectConfig + private Logging logging; + @InjectState + private PlayerMovementState playerMovementState; + public PlayerMoveEventHandler(DependencyManager dm) { super(dm); } - @InjectState - private GameState gameState; - - @InjectDependency - private EventManager eventManager; - - @InjectState - private TerminalState terminalState; - - @InjectState - private ScreenBuffer screenBuffer; - - @InjectDependency - private ResourceManager resourceManager; - - @InjectConfig - private Debugging debugging; - - @InjectConfig - private PlayerConfig playerConfig; - - @InjectState - private RenderState renderState; - - @InjectConfig - private Logging logging; - @Override public void handle(PlayerMoveEvent event) { if (renderState.isTerminalTooSmall()) { @@ -73,7 +68,13 @@ public class PlayerMoveEventHandler extends AbstractEventHandler 0 && switch (playerConfig.getSprintKey()) { + case CTRL -> event.getKeyStroke().isCtrlDown(); + case SHIFT -> event.getKeyStroke().isShiftDown(); + case ALT -> event.getKeyStroke().isAltDown(); + }; + + int moveStep = isSprinting ? playerConfig.getPlayerMoveDistanceSprinting() : playerConfig.getPlayerMoveDistance(); int originalPlayerX = playerCords.getX(); int originalPlayerY = playerCords.getY(); @@ -123,6 +124,11 @@ public class PlayerMoveEventHandler extends AbstractEventHandler permits Sword { +public interface ItemType { Class getItemType(); Strategy getStrategy(); } diff --git a/src/main/java/cz/jzitnik/game/items/types/Sword.java b/src/main/java/cz/jzitnik/game/items/types/Sword.java index 71e53e1..daeb1e0 100644 --- a/src/main/java/cz/jzitnik/game/items/types/Sword.java +++ b/src/main/java/cz/jzitnik/game/items/types/Sword.java @@ -1,19 +1,15 @@ package cz.jzitnik.game.items.types; +import cz.jzitnik.game.items.strategy.Strategy; import cz.jzitnik.game.items.strategy.SwordStrategy; -import lombok.AllArgsConstructor; -import lombok.Getter; -@AllArgsConstructor -@Getter -public non-sealed class Sword implements ItemType { - protected int damageDeal; - - public final Class getItemType() { - return Sword.class; +public class Sword extends Weapon { + public Sword(int damageDeal) { + super(damageDeal); } - public SwordStrategy getStrategy() { + @Override + public Strategy getStrategy() { return new SwordStrategy(this); } } diff --git a/src/main/java/cz/jzitnik/game/items/types/Weapon.java b/src/main/java/cz/jzitnik/game/items/types/Weapon.java new file mode 100644 index 0000000..aab73ea --- /dev/null +++ b/src/main/java/cz/jzitnik/game/items/types/Weapon.java @@ -0,0 +1,19 @@ +package cz.jzitnik.game.items.types; + +import cz.jzitnik.game.items.strategy.Strategy; +import cz.jzitnik.game.items.types.interfaces.WeaponInterface; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public abstract class Weapon implements ItemType, WeaponInterface { + protected int damageDeal; + + @Override + public final Class getItemType() { + return Weapon.class; + } + + public abstract Strategy getStrategy(); +} diff --git a/src/main/java/cz/jzitnik/game/items/types/interfaces/WeaponInterface.java b/src/main/java/cz/jzitnik/game/items/types/interfaces/WeaponInterface.java new file mode 100644 index 0000000..3e7d70c --- /dev/null +++ b/src/main/java/cz/jzitnik/game/items/types/interfaces/WeaponInterface.java @@ -0,0 +1,5 @@ +package cz.jzitnik.game.items.types.interfaces; + +public interface WeaponInterface { + int getDamageDeal(); +} diff --git a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java index 448ecd8..a086790 100644 --- a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java +++ b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java @@ -61,8 +61,7 @@ public abstract class HittableMob extends Mob { public void interact(DependencyManager dm) { dm.inject(this); - // TODO: Swords in hand will deal more damage, for now deal always one - health--; + health -= gameState.getPlayer().getDamageDeal(); log.debug("Health: {}", health); diff --git a/src/main/java/cz/jzitnik/game/items/WoodenSword.java b/src/main/java/cz/jzitnik/game/setup/items/WoodenSword.java similarity index 75% rename from src/main/java/cz/jzitnik/game/items/WoodenSword.java rename to src/main/java/cz/jzitnik/game/setup/items/WoodenSword.java index 948ca1b..243814f 100644 --- a/src/main/java/cz/jzitnik/game/items/WoodenSword.java +++ b/src/main/java/cz/jzitnik/game/setup/items/WoodenSword.java @@ -1,13 +1,14 @@ -package cz.jzitnik.game.items; +package cz.jzitnik.game.setup.items; import cz.jzitnik.game.ResourceManager; +import cz.jzitnik.game.items.GameItem; import cz.jzitnik.game.items.types.Sword; public class WoodenSword extends GameItem { public WoodenSword(ResourceManager resourceManager) { super( "Wooden sword", - new Sword(5), + new Sword(2), resourceManager.getResource(ResourceManager.Resource.WOODEN_SWORD) ); } 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 3b336d9..1da5c57 100644 --- a/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java +++ b/src/main/java/cz/jzitnik/game/setup/rooms/MainRoom.java @@ -4,7 +4,7 @@ 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.items.WoodenSword; +import cz.jzitnik.game.setup.items.WoodenSword; import cz.jzitnik.game.objects.Chest; import cz.jzitnik.game.setup.mobs.Zombie; import cz.jzitnik.game.utils.RoomCords; diff --git a/src/main/java/cz/jzitnik/states/PlayerMovementState.java b/src/main/java/cz/jzitnik/states/PlayerMovementState.java new file mode 100644 index 0000000..eaa4e1d --- /dev/null +++ b/src/main/java/cz/jzitnik/states/PlayerMovementState.java @@ -0,0 +1,13 @@ +package cz.jzitnik.states; + +import cz.jzitnik.annotations.State; +import lombok.Data; + +import java.util.concurrent.ScheduledFuture; + +@Data +@State +public class PlayerMovementState { + long lastMovement; + ScheduledFuture staminaIncreaseSchedule; +} diff --git a/src/main/java/cz/jzitnik/tasks/StaminaIncreaseTask.java b/src/main/java/cz/jzitnik/tasks/StaminaIncreaseTask.java new file mode 100644 index 0000000..74b85f1 --- /dev/null +++ b/src/main/java/cz/jzitnik/tasks/StaminaIncreaseTask.java @@ -0,0 +1,73 @@ +package cz.jzitnik.tasks; + +import cz.jzitnik.annotations.ScheduledTask; +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.game.GameState; +import cz.jzitnik.game.Player; +import cz.jzitnik.states.PlayerMovementState; +import cz.jzitnik.utils.DependencyManager; +import cz.jzitnik.utils.ScheduledTaskManager; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@ScheduledTask(rate = 1, rateUnit = TimeUnit.SECONDS) +public class StaminaIncreaseTask implements Runnable { + @InjectState + private PlayerMovementState playerMovementState; + + @InjectState + private GameState gameState; + + @InjectDependency + private ScheduledTaskManager scheduledTaskManager; + + @InjectDependency + private DependencyManager dependencyManager; + + @InjectConfig + private PlayerConfig playerConfig; + + @Override + public void run() { + if (playerMovementState.getStaminaIncreaseSchedule() != null) { + return; + } + + long nowTime = System.currentTimeMillis(); + if (nowTime - playerMovementState.getLastMovement() >= playerConfig.getStaminaDelayMs() && gameState.getPlayer().getStamina() < Player.MAX_STAMINA) { + IncreaseStamina instance = new IncreaseStamina(); + dependencyManager.inject(instance); + playerMovementState.setStaminaIncreaseSchedule(scheduledTaskManager.tempScheduleFixedRate(instance, playerConfig.getStaminaIncreaseRateMs(), TimeUnit.MILLISECONDS)); + } + } + + public static class IncreaseStamina implements Runnable { + @InjectState + private PlayerMovementState playerMovementState; + + @InjectState + private GameState gameState; + + @InjectConfig + private PlayerConfig playerConfig; + + @Override + public void run() { + long nowTime = System.currentTimeMillis(); + + Player player = gameState.getPlayer(); + if (player.getStamina() >= Player.MAX_STAMINA || nowTime - playerMovementState.getLastMovement() < playerConfig.getStaminaDelayMs()) { + ScheduledFuture future = playerMovementState.getStaminaIncreaseSchedule(); + playerMovementState.setStaminaIncreaseSchedule(null); + future.cancel(false); + return; + } + + player.increaseStamina(); + } + } +} diff --git a/src/main/java/cz/jzitnik/utils/ScheduledTaskManager.java b/src/main/java/cz/jzitnik/utils/ScheduledTaskManager.java new file mode 100644 index 0000000..801c418 --- /dev/null +++ b/src/main/java/cz/jzitnik/utils/ScheduledTaskManager.java @@ -0,0 +1,73 @@ +package cz.jzitnik.utils; + +import cz.jzitnik.annotations.Dependency; +import cz.jzitnik.annotations.ScheduledTask; +import cz.jzitnik.config.ThreadPoolConfig; +import lombok.extern.slf4j.Slf4j; +import org.reflections.Reflections; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Dependency +public class ScheduledTaskManager { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(new ThreadPoolConfig().getTaskThreadCount()); + private final HashSet instances = new HashSet<>(); + private final DependencyManager dependencyManager; + + public ScheduledTaskManager(Reflections reflections, DependencyManager dependencyManager) { + this.dependencyManager = dependencyManager; + var classes = reflections.getTypesAnnotatedWith(ScheduledTask.class); + for (Class clazz : classes) { + if (!Runnable.class.isAssignableFrom(clazz)) { + continue; + } + try { + var instance = (Runnable) clazz.getDeclaredConstructor().newInstance(); + var annotation = clazz.getAnnotation(ScheduledTask.class); + + assert annotation != null; + + instances.add(new Registry(instance, annotation)); + + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + + public void startAll() { + for (Registry instance : instances) { + dependencyManager.inject(instance.runnable); + scheduler.scheduleAtFixedRate(instance.runnable, 0, instance.scheduledTask.rate(), instance.scheduledTask.rateUnit()); + } + } + + public ScheduledFuture tempScheduleFixedRate(Runnable runnable, int rate, TimeUnit rateUnit) { + return scheduler.scheduleAtFixedRate(runnable, 0, rate, rateUnit); + } + + public void shutdown() { + try { + scheduler.shutdown(); + if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) { + log.error("Pool did not terminate"); + } + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + private record Registry(Runnable runnable, ScheduledTask scheduledTask) { + } +}