feat: New scenes

This commit is contained in:
2025-12-21 14:40:36 +01:00
parent 88403993fe
commit fdb6c3075d
12 changed files with 245 additions and 73 deletions

View File

@@ -23,6 +23,8 @@
<version>1.18.38</version> <version>1.18.38</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
<source>25</source>
<target>25</target>
</configuration> </configuration>
</plugin> </plugin>

View File

@@ -1,11 +0,0 @@
package cz.jzitnik.events;
import cz.jzitnik.utils.events.Event;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class PlayVideo implements Event {
private String fileName;
}

View File

@@ -1,48 +0,0 @@
package cz.jzitnik.events.handlers;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.screen.TerminalScreen;
import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.PlayVideo;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.states.TerminalState;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.AbstractEventHandler;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@EventHandler(PlayVideo.class)
public class PlayVideoHandler extends AbstractEventHandler<PlayVideo> {
public PlayVideoHandler(DependencyManager dm) {
super(dm);
}
@InjectDependency
private ResourceManager resourceManager;
@InjectState
private TerminalState terminalState;
@Override
public void handle(PlayVideo event) {
}
private static TextColor.RGB toColor(int rgb) {
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
return new TextColor.RGB(r, g, b);
}
}

View File

@@ -5,7 +5,6 @@ import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.FullRoomDraw; import cz.jzitnik.events.FullRoomDraw;
import cz.jzitnik.events.PlayVideo;
import cz.jzitnik.events.TerminalResizeEvent; import cz.jzitnik.events.TerminalResizeEvent;
import cz.jzitnik.game.GameState; import cz.jzitnik.game.GameState;
import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.ScreenBuffer;
@@ -32,6 +31,8 @@ public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalRes
@InjectState @InjectState
private GameState gameState; private GameState gameState;
private boolean screenRerendering = false;
@Override @Override
public void handle(TerminalResizeEvent event) { public void handle(TerminalResizeEvent event) {
TerminalSize size = event.getNewSize(); TerminalSize size = event.getNewSize();
@@ -47,7 +48,13 @@ public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalRes
screenBuffer.setRenderedBuffer(buffer); screenBuffer.setRenderedBuffer(buffer);
if (gameState.getScreen() != null) { if (gameState.getScreen() != null) {
if (screenRerendering) {
return;
} else {
screenRerendering = true;
gameState.getScreen().fullRender(); gameState.getScreen().fullRender();
screenRerendering = false;
}
} else { } else {
eventManager.emitEvent(new FullRoomDraw(true)); eventManager.emitEvent(new FullRoomDraw(true));
} }

View File

@@ -60,6 +60,19 @@ public class ResourceManager {
} }
} }
public BufferedImage getResource(String path) {
InputStream is = classLoader.getResourceAsStream(path);
if (is == null) {
throw new RuntimeException("Image not found in resources!");
}
try {
return ImageIO.read(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public InputStream getResourceAsStream(String path) { public InputStream getResourceAsStream(String path) {
return classLoader.getResourceAsStream(path); return classLoader.getResourceAsStream(path);
} }

View File

@@ -0,0 +1,77 @@
package cz.jzitnik.game.setup;
import com.googlecode.lanterna.input.KeyType;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.FullRoomDraw;
import cz.jzitnik.events.KeyboardPressEvent;
import cz.jzitnik.events.MouseAction;
import cz.jzitnik.game.GameState;
import cz.jzitnik.screens.Screen;
import cz.jzitnik.screens.scenes.BasicImageScene;
import cz.jzitnik.screens.scenes.Scene;
import cz.jzitnik.sound.SoundPlayer;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.EventManager;
public class GameMenuScene extends Scene {
private static class GameMenuScreen extends Screen {
protected final SoundPlayer soundPlayer = new SoundPlayer();
protected boolean play = true;
@Override
public void fullRender() {
// No render here just basic audio playback
new Thread(() -> {
while (play) {
soundPlayer.playSound("audio/menu.ogg", 30, 100);
}
}).start();
}
@Override
public void handleMouseAction(MouseAction event) {
}
@Override
public void handleKeyboardAction(KeyboardPressEvent event) {
}
}
private static final class ImageScene extends BasicImageScene {
@InjectState
private GameState gameState;
@InjectDependency
private EventManager eventManager;
private final GameMenuScreen gameMenuScreen;
public ImageScene(String filePath, GameMenuScreen gameMenuScreen) {
super(filePath);
this.gameMenuScreen = gameMenuScreen;
}
@Override
public void handleKeyboardAction(KeyboardPressEvent event) {
if (event.getKeyStroke().getKeyType() == KeyType.Enter) {
gameMenuScreen.play = false;
gameMenuScreen.soundPlayer.stopCurrentSound();
gameState.setScreen(null);
eventManager.emitEvent(new FullRoomDraw(true));
}
}
}
public GameMenuScene(DependencyManager dependencyManager) {
GameMenuScreen gameMenuScreen = new GameMenuScreen();
ImageScene basicImageScene = new ImageScene("menu.png", gameMenuScreen);
super(new Screen[]{gameMenuScreen, basicImageScene}, new OnEndAction.Repeat());
dependencyManager.inject(this);
dependencyManager.inject(basicImageScene);
}
}

View File

@@ -11,7 +11,6 @@ import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.WoodenSword; import cz.jzitnik.game.items.WoodenSword;
import cz.jzitnik.game.objects.Chest; import cz.jzitnik.game.objects.Chest;
import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.screens.scenes.IntroScene;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
@Dependency @Dependency
@@ -31,9 +30,13 @@ public class GameSetup {
GameRoom mainRoom = new GameRoom(ResourceManager.Resource.ROOM1); GameRoom mainRoom = new GameRoom(ResourceManager.Resource.ROOM1);
GameRoom rightRoom = new GameRoom(ResourceManager.Resource.ROOM2); GameRoom rightRoom = new GameRoom(ResourceManager.Resource.ROOM2);
GameRoom topRightRoom = new GameRoom(ResourceManager.Resource.ROOM3); GameRoom topRightRoom = new GameRoom(ResourceManager.Resource.ROOM3);
GameRoom topRightTop = new GameRoom(ResourceManager.Resource.ROOM4);
GameRoom topRightTopLeft = new GameRoom(ResourceManager.Resource.ROOM_FROZEN);
mainRoom.setRight(rightRoom); mainRoom.setRight(rightRoom);
rightRoom.setUp(topRightRoom); rightRoom.setUp(topRightRoom);
topRightRoom.setUp(topRightTop);
topRightTop.setLeft(topRightTopLeft);
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),

View File

@@ -1,6 +1,8 @@
package cz.jzitnik.screens.scenes; package cz.jzitnik.game.setup;
import cz.jzitnik.screens.Screen; import cz.jzitnik.screens.Screen;
import cz.jzitnik.screens.scenes.Scene;
import cz.jzitnik.screens.scenes.VideoSceneWithAudio;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
public class IntroScene extends Scene { public class IntroScene extends Scene {
@@ -9,7 +11,7 @@ public class IntroScene extends Scene {
new Screen[]{ new Screen[]{
new VideoSceneWithAudio("video.mp4", "audio.ogg") new VideoSceneWithAudio("video.mp4", "audio.ogg")
}, },
OnEndAction.SWITCH_TO_GAME new OnEndAction.SwitchToScreen(new GameMenuScene(dependencyManager))
); );
dependencyManager.inject(this); dependencyManager.inject(this);
} }

View File

@@ -0,0 +1,105 @@
package cz.jzitnik.screens.scenes;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.screen.TerminalScreen;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.KeyboardPressEvent;
import cz.jzitnik.events.MouseAction;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.screens.Screen;
import cz.jzitnik.states.TerminalState;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class BasicImageScene extends Screen {
@InjectDependency
private ResourceManager resourceManager;
@InjectState
private TerminalState terminalState;
private final String imagePath;
public BasicImageScene(String filePath) {
imagePath = filePath;
}
@Override
public void fullRender() {
BufferedImage image = resourceManager.getResource(imagePath);
TerminalScreen screen = terminalState.getTerminalScreen();
screen.clear();
var tg = terminalState.getTextGraphics();
int termWidth = screen.getTerminalSize().getColumns();
int termHeight = screen.getTerminalSize().getRows();
int imgWidth = image.getWidth();
int imgHeight = image.getHeight();
// Terminal pixel space (because 1 cell = 2 vertical pixels)
int termPixelWidth = termWidth;
int termPixelHeight = termHeight * 2;
// Scale while preserving aspect ratio
double scale = Math.min(
(double) termPixelWidth / imgWidth,
(double) termPixelHeight / imgHeight
);
int scaledWidth = (int) (imgWidth * scale);
int scaledHeight = (int) (imgHeight * scale);
// Centering offsets (in pixel space)
int xOffset = (termPixelWidth - scaledWidth) / 2;
int yOffset = (termPixelHeight - scaledHeight) / 2;
for (int y = 0; y < scaledHeight / 2; y++) {
int imgYTop = (int) ((y * 2) / scale);
int imgYBottom = (int) ((y * 2 + 1) / scale);
int termY = y + (yOffset / 2);
if (termY < 0 || termY >= termHeight) continue;
for (int x = 0; x < scaledWidth; x++) {
int termX = x + xOffset;
if (termX < 0 || termX >= termWidth) continue;
int imgX = (int) (x / scale);
int topPixel = image.getRGB(imgX, imgYTop);
int bottomPixel = image.getRGB(imgX, imgYBottom);
tg.setBackgroundColor(toColor(topPixel));
tg.setForegroundColor(toColor(bottomPixel));
tg.setCharacter(termX, termY, '▄');
}
}
try {
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private TextColor.RGB toColor(int rgb) {
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
return new TextColor.RGB(r, g, b);
}
@Override
public void handleMouseAction(MouseAction event) {
}
@Override
public void handleKeyboardAction(KeyboardPressEvent event) {
}
}

View File

@@ -9,6 +9,8 @@ import cz.jzitnik.game.GameState;
import cz.jzitnik.screens.Screen; import cz.jzitnik.screens.Screen;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.events.EventManager;
import lombok.AllArgsConstructor;
import lombok.Getter;
public abstract class Scene extends Screen { public abstract class Scene extends Screen {
private final Screen[] parts; private final Screen[] parts;
@@ -26,8 +28,16 @@ public abstract class Scene extends Screen {
@InjectDependency @InjectDependency
private EventManager eventManager; private EventManager eventManager;
public enum OnEndAction { public static class OnEndAction {
SWITCH_TO_GAME public static class SwitchToGame extends OnEndAction {}
public static class Repeat extends OnEndAction {}
public static class None extends OnEndAction {}
@Getter
@AllArgsConstructor
public static class SwitchToScreen extends OnEndAction {
private final Screen screen;
}
} }
public Scene(Screen[] parts, OnEndAction onEndAction) { public Scene(Screen[] parts, OnEndAction onEndAction) {
@@ -41,6 +51,8 @@ public abstract class Scene extends Screen {
if (!isRenderedAlready) { if (!isRenderedAlready) {
isRenderedAlready = true; isRenderedAlready = true;
render(); render();
} else if (currentPart != null && !onEndAction.getClass().equals(OnEndAction.Repeat.class)) {
currentPart.fullRender();
} }
} }
@@ -52,16 +64,26 @@ public abstract class Scene extends Screen {
try { try {
currentPart = parts[++currentIndex]; currentPart = parts[++currentIndex];
} catch (ArrayIndexOutOfBoundsException e) { } catch (ArrayIndexOutOfBoundsException e) {
if (!onEndAction.getClass().equals(OnEndAction.Repeat.class)) {
currentPart = null; currentPart = null;
} else {
break;
}
} }
} }
switch (onEndAction) { if (onEndAction.getClass().equals(OnEndAction.SwitchToGame.class)) {
case SWITCH_TO_GAME -> { switchToGame();
gameState.setScreen(null); } else if (onEndAction.getClass().equals(OnEndAction.SwitchToScreen.class)) {
OnEndAction.SwitchToScreen switchToScreen = (OnEndAction.SwitchToScreen) onEndAction;
gameState.setScreen(switchToScreen.getScreen());
eventManager.emitEvent(new FullRoomDraw(true)); eventManager.emitEvent(new FullRoomDraw(true));
} }
} }
protected void switchToGame() {
gameState.setScreen(null);
eventManager.emitEvent(new FullRoomDraw(true));
} }
@Override @Override

Binary file not shown.

BIN
src/main/resources/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB