diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java index ba80071..932ff14 100644 --- a/src/main/java/cz/jzitnik/Main.java +++ b/src/main/java/cz/jzitnik/Main.java @@ -1,6 +1,7 @@ package cz.jzitnik; import cz.jzitnik.game.GameSaver; +import cz.jzitnik.game.context.list.GameLoopContext; import cz.jzitnik.game.logic.CustomLogicProvider; import cz.jzitnik.game.mobs.EntityLogicProvider; import cz.jzitnik.game.threads.ThreadProvider; @@ -38,15 +39,21 @@ public class Main { var gameSaver = new GameSaver(); var game = gameSaver.load(); - final boolean[] isRunning = { true }; + game.registerContext(Terminal.class, terminal); + GameLoopContext gameLoopContext = game.getContext(GameLoopContext.class); EntityLogicProvider entityLogicProvider = new EntityLogicProvider(); CustomLogicProvider customLogicProvider = new CustomLogicProvider(); - ThreadProvider threadProvider = new ThreadProvider(game, screenRenderer, terminal, isRunning); + ThreadProvider threadProvider = new ThreadProvider(game, screenRenderer, terminal); threadProvider.start(); - while (isRunning[0]) { + while (true) { + if (!gameLoopContext.isRunning()) { + Thread.sleep(1000); + continue; + } + try { entityLogicProvider.update(game); } catch (Exception ignored) { @@ -64,10 +71,6 @@ public class Main { Thread.sleep(1000); } - - log.info("Closing terminal"); - terminal.trackMouse(Terminal.MouseTracking.Off); - terminal.close(); } catch (IOException | InterruptedException ignored) { } } diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java index ec98b38..ba8c924 100644 --- a/src/main/java/cz/jzitnik/game/Game.java +++ b/src/main/java/cz/jzitnik/game/Game.java @@ -51,7 +51,6 @@ public class Game { private final List[][] world = (List[][]) new CopyOnWriteArrayList[256][512]; private final Player player = new Player(); private transient boolean mining = false; - @Setter private transient Window window = Window.WORLD; private final Inventory inventory = new Inventory(); private transient EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); @@ -73,10 +72,15 @@ public class Game { Generation.generateWorld(this); } - public boolean isNight(){ + public boolean isNight() { return daytime > 200 && daytime < 400; } + public void setWindow(Window window) { + this.window = window; + gameStates.dependencies.windowSwitchHandlerProvider.handle(this); + } + /** * Returns the current coordinates of the player (bottom half). * @@ -667,7 +671,11 @@ public class Game { } } - public Object getContext(Class clazz) { + public T getContext(Class clazz) { return gameStates.globalContextProvider.getMap(clazz); } + + public void registerContext(Class clazz, Object instance) { + gameStates.globalContextProvider.registerContext(clazz, instance); + } } diff --git a/src/main/java/cz/jzitnik/game/annotations/WindowSwitchHandler.java b/src/main/java/cz/jzitnik/game/annotations/WindowSwitchHandler.java new file mode 100644 index 0000000..0a799e9 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/annotations/WindowSwitchHandler.java @@ -0,0 +1,11 @@ +package cz.jzitnik.game.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface WindowSwitchHandler { +} diff --git a/src/main/java/cz/jzitnik/game/blocks/Bed.java b/src/main/java/cz/jzitnik/game/blocks/Bed.java index 0778cff..28e2a2d 100644 --- a/src/main/java/cz/jzitnik/game/blocks/Bed.java +++ b/src/main/java/cz/jzitnik/game/blocks/Bed.java @@ -17,7 +17,7 @@ public class Bed implements RightClickHandler { @Override public void onBlockRightClick(int x, int y, Game game, ScreenRenderer screenRenderer) { BedSleepGlobalContext bedSleepGlobalContext = - (BedSleepGlobalContext) game.getContext(BedSleepGlobalContext.class); + game.getContext(BedSleepGlobalContext.class); if (bedSleepGlobalContext.isSleeping() || game.getDaytime() < 200 || game.getDaytime() > 500) { return; // already in progress diff --git a/src/main/java/cz/jzitnik/game/context/GlobalContextProvider.java b/src/main/java/cz/jzitnik/game/context/GlobalContextProvider.java index 8c37962..bed3f90 100644 --- a/src/main/java/cz/jzitnik/game/context/GlobalContextProvider.java +++ b/src/main/java/cz/jzitnik/game/context/GlobalContextProvider.java @@ -11,7 +11,8 @@ public class GlobalContextProvider { private final Map data = new HashMap<>(); private boolean loaded = false; - public Object getMap(Class clazz) { + @SuppressWarnings("unchecked") + public T getMap(Class clazz) { if (!loaded) { Reflections reflections = new Reflections("cz.jzitnik.game.context"); Set> handlerClasses = reflections.getTypesAnnotatedWith(GlobalContext.class); @@ -28,6 +29,10 @@ public class GlobalContextProvider { loaded = true; } - return data.get(clazz); + return clazz.cast(data.get(clazz)); + } + + public void registerContext(Class clazz, Object instance) { + data.put(clazz, instance); } } diff --git a/src/main/java/cz/jzitnik/game/context/list/GameLoopContext.java b/src/main/java/cz/jzitnik/game/context/list/GameLoopContext.java new file mode 100644 index 0000000..736e4b7 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/context/list/GameLoopContext.java @@ -0,0 +1,10 @@ +package cz.jzitnik.game.context.list; + +import cz.jzitnik.game.annotations.GlobalContext; +import lombok.Data; + +@Data +@GlobalContext +public class GameLoopContext { + private boolean isRunning = true; +} diff --git a/src/main/java/cz/jzitnik/game/entities/Dependencies.java b/src/main/java/cz/jzitnik/game/entities/Dependencies.java index c3d596a..2403f65 100644 --- a/src/main/java/cz/jzitnik/game/entities/Dependencies.java +++ b/src/main/java/cz/jzitnik/game/entities/Dependencies.java @@ -7,6 +7,7 @@ import cz.jzitnik.game.handlers.events.EventHandlerProvider; import cz.jzitnik.game.handlers.pickup.PickupHandlerProvider; import cz.jzitnik.game.handlers.place.PlaceHandler; import cz.jzitnik.game.handlers.tooluse.ToolUseProvider; +import cz.jzitnik.game.handlers.windowswitch.WindowSwitchHandlerProvider; import cz.jzitnik.game.mobs.EntityHurtAnimation; import cz.jzitnik.game.mobs.EntityKill; import cz.jzitnik.game.smelting.Smelting; @@ -20,6 +21,7 @@ public class Dependencies { public EntityHurtAnimation entityHurtAnimation = new EntityHurtAnimation(); public EntityKill entityKill = new EntityKill(); public PickupHandlerProvider pickupHandlerProvider = new PickupHandlerProvider(); + public WindowSwitchHandlerProvider windowSwitchHandlerProvider = new WindowSwitchHandlerProvider(); public GameSaver gameSaver = new GameSaver(); public EventHandlerProvider eventHandlerProvider = new EventHandlerProvider(); public Smelting smelting = new Smelting(); diff --git a/src/main/java/cz/jzitnik/game/entities/Player.java b/src/main/java/cz/jzitnik/game/entities/Player.java index 9881e40..a0604bb 100644 --- a/src/main/java/cz/jzitnik/game/entities/Player.java +++ b/src/main/java/cz/jzitnik/game/entities/Player.java @@ -1,5 +1,6 @@ package cz.jzitnik.game.entities; +import cz.jzitnik.game.context.list.GameLoopContext; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerInterface.java b/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerInterface.java new file mode 100644 index 0000000..3d91055 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerInterface.java @@ -0,0 +1,7 @@ +package cz.jzitnik.game.handlers.windowswitch; + +import cz.jzitnik.game.Game; + +public interface WindowSwitchHandlerInterface { + void handle(Game game); +} diff --git a/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerProvider.java b/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerProvider.java new file mode 100644 index 0000000..7bc0d2f --- /dev/null +++ b/src/main/java/cz/jzitnik/game/handlers/windowswitch/WindowSwitchHandlerProvider.java @@ -0,0 +1,39 @@ +package cz.jzitnik.game.handlers.windowswitch; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import cz.jzitnik.game.Game; + +import cz.jzitnik.game.annotations.WindowSwitchHandler; +import org.reflections.Reflections; + +public class WindowSwitchHandlerProvider { + public final List handler = new ArrayList<>(); + + public WindowSwitchHandlerProvider() { + registerHandlers(); + } + + public void handle(Game game) { + handler.forEach(handler -> handler.handle(game)); + } + + private void registerHandlers() { + Reflections reflections = new Reflections("cz.jzitnik.game.handlers.windowswitch.handlers"); + Set> handlerClasses = reflections.getTypesAnnotatedWith(WindowSwitchHandler.class); + + for (Class clazz : handlerClasses) { + if (WindowSwitchHandlerInterface.class.isAssignableFrom(clazz)) { + try { + WindowSwitchHandlerInterface handlerInstance = (WindowSwitchHandlerInterface) clazz.getDeclaredConstructor() + .newInstance(); + handler.add(handlerInstance); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/cz/jzitnik/game/handlers/windowswitch/handlers/GamePauseHandler.java b/src/main/java/cz/jzitnik/game/handlers/windowswitch/handlers/GamePauseHandler.java new file mode 100644 index 0000000..2300b15 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/handlers/windowswitch/handlers/GamePauseHandler.java @@ -0,0 +1,20 @@ +package cz.jzitnik.game.handlers.windowswitch.handlers; + +import cz.jzitnik.game.Game; +import cz.jzitnik.game.annotations.WindowSwitchHandler; +import cz.jzitnik.game.context.list.GameLoopContext; +import cz.jzitnik.game.handlers.windowswitch.WindowSwitchHandlerInterface; +import cz.jzitnik.game.ui.Window; + +import java.util.Arrays; + +@WindowSwitchHandler +public class GamePauseHandler implements WindowSwitchHandlerInterface { + private final Window[] pausedWindows = {Window.ESC, Window.DEATH_SCREEN, Window.OPTIONS, Window.SAVE_EXIT, Window.SAVED}; + + @Override + public void handle(Game game) { + GameLoopContext gameLoopContext = game.getContext(GameLoopContext.class); + gameLoopContext.setRunning(!Arrays.asList(pausedWindows).contains(game.getWindow())); + } +} diff --git a/src/main/java/cz/jzitnik/game/logic/services/flowing/LavaFireLogic.java b/src/main/java/cz/jzitnik/game/logic/services/flowing/LavaFireLogic.java index 67ac0f1..3014232 100644 --- a/src/main/java/cz/jzitnik/game/logic/services/flowing/LavaFireLogic.java +++ b/src/main/java/cz/jzitnik/game/logic/services/flowing/LavaFireLogic.java @@ -9,6 +9,7 @@ import cz.jzitnik.game.annotations.Flamable; import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.entities.items.Item; import cz.jzitnik.game.logic.CustomLogicInterface; +import cz.jzitnik.game.sprites.Steve; import cz.jzitnik.tui.ScreenRenderer; @CustomLogic @@ -59,7 +60,7 @@ public class LavaFireLogic implements CustomLogicInterface { block.setOnFire(true); block.setBurningTime(0); } - } else if (!block.getClass().isAnnotationPresent(FireImmune.class)) { + } else if (!block.getClass().isAnnotationPresent(FireImmune.class) && !block.getBlockId().equals("steve")) { int dealDamage = game.getInventory().getItemInHand().map(Item::getDealDamage).orElse(1); if (block.getHp() - dealDamage <= 0) { // Mob is killed diff --git a/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java b/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java index 663ffb5..b83aa64 100644 --- a/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java +++ b/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java @@ -22,7 +22,6 @@ public class ThreadProvider { private final Game game; private final ScreenRenderer screenRenderer; private final Terminal terminal; - private final boolean[] isRunning; private final List list = new ArrayList<>(); /** @@ -43,11 +42,10 @@ public class ThreadProvider { * @param terminal The terminal. * @param isRunning A shared boolean array for controlling thread execution. */ - public ThreadProvider(Game game, ScreenRenderer screenRenderer, Terminal terminal, boolean[] isRunning) { + public ThreadProvider(Game game, ScreenRenderer screenRenderer, Terminal terminal) { this.game = game; this.screenRenderer = screenRenderer; this.terminal = terminal; - this.isRunning = isRunning; registerHandlers(); } @@ -77,8 +75,6 @@ public class ThreadProvider { params[i] = screenRenderer; else if (type == Terminal.class) params[i] = terminal; - else if (type == boolean[].class) - params[i] = isRunning; else if (type == Player.class) params[i] = game.getPlayer(); else { diff --git a/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java index dab003c..0218ee5 100644 --- a/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java +++ b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java @@ -102,11 +102,12 @@ public class InputHandlerThread extends Thread { case 'q' -> { if (game.getWindow() != Window.WORLD) { game.setWindow(Window.WORLD); + screenRenderer.render(game); } else { game.setWindow(Window.ESC); game.getGameStates().dependencies.escape.reset(); + screenRenderer.render(game); } - screenRenderer.render(game); } default -> { } diff --git a/src/main/java/cz/jzitnik/game/ui/Escape.java b/src/main/java/cz/jzitnik/game/ui/Escape.java index 5f6db3d..d8b373a 100644 --- a/src/main/java/cz/jzitnik/game/ui/Escape.java +++ b/src/main/java/cz/jzitnik/game/ui/Escape.java @@ -7,6 +7,8 @@ import cz.jzitnik.game.sprites.ui.Font.*; import cz.jzitnik.tui.ScreenRenderer; import cz.jzitnik.tui.utils.Menu; +import java.io.IOException; + /** * Handles the in-game escape menu functionality, including rendering options, * handling mouse input, and managing save and exit logic. @@ -22,7 +24,7 @@ public class Escape { */ public Escape(Game game) { this.game = game; - this.menu = new Menu(game, "2DCraft", new String[] { "Continue", "Options", "Save and exit" }, + this.menu = new Menu(game, "2DCraft", new String[]{"Continue", "Options", "Save and exit"}, this::onButtonClick); } @@ -74,6 +76,15 @@ public class Escape { game.getGameStates().dependencies.gameSaver.save(game); game.setWindow(Window.SAVED); screenRenderer.render(game); + + Terminal terminal = game.getContext(Terminal.class); + terminal.trackMouse(Terminal.MouseTracking.Off); + try { + terminal.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.exit(0); break; } @@ -99,8 +110,8 @@ public class Escape { /** * Callback for when a button in the escape menu is clicked. * - * @param index The index of the clicked button (0 = Continue, 1 = Options, 2 = Save and exit). - * @param screenRenderer The screen renderer used to refresh the display. + * @param index The index of the clicked button (0 = Continue, 1 = Options, 2 = Save and exit). + * @param screenRenderer The screen renderer used to refresh the display. */ public void renderSaved(StringBuilder buffer, Terminal terminal) { var font = game.getGameStates().dependencies.font; diff --git a/src/main/java/cz/jzitnik/tui/ScreenRenderer.java b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java index 818dd24..5b8d300 100644 --- a/src/main/java/cz/jzitnik/tui/ScreenRenderer.java +++ b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java @@ -8,6 +8,7 @@ import cz.jzitnik.game.sprites.SimpleSprite; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.ui.Healthbar; +import cz.jzitnik.game.ui.Window; import cz.jzitnik.tui.utils.SpriteCombiner; import lombok.Getter; import lombok.Setter; @@ -29,6 +30,7 @@ public class ScreenRenderer { private final SpriteList spriteList; private final Terminal terminal; private boolean rendering = false; + private Window currentlyRenderingWindow; /** * Constructs a {@code ScreenRenderer} with the given sprite list and terminal. @@ -100,10 +102,11 @@ public class ScreenRenderer { * @param game Current game state to render. */ public void render(Game game) { - if (rendering) { + if (rendering && game.getWindow() == currentlyRenderingWindow) { return; } rendering = true; + currentlyRenderingWindow = game.getWindow(); log.debug("Rendering frame"); var world = game.getWorld(); StringBuilder main = new StringBuilder();