package cz.jzitnik.tui; import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.Game; import cz.jzitnik.game.sprites.Air; import cz.jzitnik.game.sprites.SimpleSprite; import cz.jzitnik.game.sprites.Steve; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.ui.Escape; import cz.jzitnik.game.ui.Healthbar; import cz.jzitnik.tui.utils.SpriteCombiner; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jline.terminal.Terminal; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Slf4j public class ScreenRenderer { private final SpriteList spriteList; private final Terminal terminal; public ScreenRenderer(SpriteList spriteList, Terminal terminal) { this.spriteList = spriteList; this.terminal = terminal; } @Getter @Setter private Optional> selectedBlock = Optional.empty(); private int[] getPlayerCords(List[][] world) { for (int i = 0; i < world.length; i++) { for (int j = 0; j < world[i].length; j++) { if (world[i][j].stream().anyMatch(x -> x.getBlockId().equals("steve") && x.getSpriteState().isPresent() && x.getSpriteState().get() == Steve.SteveState.SECOND)) { return new int[] { j, i }; } } } return null; } public String getTexture(Block block, SpriteList spriteList, Game game) { if (Air.class.isAssignableFrom(spriteList.getSprite(block.getSprite()).getClass())) { var air = (Air) spriteList.getSprite(block.getSprite()); return air.getSprite(game.getDaytime()); } if (block.getSpriteState().isEmpty()) { return spriteList.getSprite(block.getSprite()).getSprite(); } return spriteList.getSprite(block.getSprite()).getSprite(block.getSpriteState().get()); } public synchronized void render(Game game) { log.debug("Rendering frame"); var world = game.getWorld(); StringBuilder main = new StringBuilder(); main.append("\033[H\033[2J"); switch (game.getWindow()) { case INVENTORY -> game.getInventory().renderFull(main, terminal, spriteList, true, Optional.empty()); case CRAFTING_TABLE -> game.getGameStates().craftingTable.render(main, terminal, spriteList); case CHEST -> ((Chest) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX].stream() .filter(i -> i.getBlockId().equals("chest")).toList().getFirst().getData()).render(game, main, terminal, spriteList); case FURNACE -> ((Furnace) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX] .stream().filter(i -> i.getBlockId().equals("furnace")).toList().getFirst().getData()).render(game, main, terminal, spriteList); case ESC -> Escape.render(main, terminal); case WORLD -> { // World int[] cords = getPlayerCords(world); if (cords == null) return; int playerX = cords[0]; int playerY = cords[1]; int terminalWidth = terminal.getWidth(); int terminalHeight = terminal.getHeight(); int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminalHeight, terminalWidth, world[0].length, world.length); // Calculate visible area boundaries int startX = data[0]; int endX = data[1]; int startY = data[2]; int endY = data[3]; int visibleWidth = endX - startX; int visibleHeight = endY - startY; if (visibleWidth % 2 != 0) { endX = Math.max(startX, endX - 1); } if (visibleHeight % 2 != 0) { endY = Math.max(startY, endY - 1); } StringBuilder[] lines = new StringBuilder[(endY - startY) * 25 + 1]; for (int i = 0; i < lines.length; i++) { lines[i] = new StringBuilder(); } int counter = 0; for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { List blocks = world[y][x]; List sprites = new ArrayList<>(blocks.stream() .map(block -> getTexture(block, spriteList, game)) .toList()); if (selectedBlock.isPresent() && selectedBlock.get().get(0) == x && selectedBlock.get().get(1) == y) { StringBuilder stringBuilder = getStringBuilder(); sprites.add(stringBuilder.toString()); } if (blocks.stream() .anyMatch(block -> block.getBlockId().equals("steve") && block.getSpriteState().get() == Steve.SteveState.SECOND) && game.getPlayer().isBurningState()) { SimpleSprite fire = new SimpleSprite("fire.ans"); sprites.add(fire.getSprite()); } var burningBlocks = blocks.stream().filter(Block::isOnFire).toList(); if (!burningBlocks.isEmpty()) { SimpleSprite fire = new SimpleSprite("fire.ans"); sprites.add(blocks.indexOf(burningBlocks.getLast()) + 1, fire.getSprite()); } String sprite = SpriteCombiner.combineSprites(sprites.toArray(String[]::new)); String[] spriteLines = sprite.split("\n"); for (int i = 0; i < spriteLines.length; i++) { lines[counter * 25 + i].append(spriteLines[i]).append("\033[0m"); } } counter++; } // World for (int i = 0; i < lines.length; i++) { main.append(lines[i]); if (i < lines.length - 1) { main.append("\n"); } } // Empty space between world and hotbar main.append("\n\n\n"); Healthbar.render(main, spriteList, terminal, game); game.getInventory().renderHotbar(main, spriteList, terminal, false); } } log.debug("Frame rendered"); System.out.println(main); } private static StringBuilder getStringBuilder() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50)); stringBuilder.append("\n"); for (int i = 0; i < 23; i++) { stringBuilder.append("\033[38;5;231;48;5;231m▓"); stringBuilder.append("\033[38;5;231;48;5;231m▓"); stringBuilder.append("\033[0m ".repeat(46)); stringBuilder.append("\033[38;5;231;48;5;231m▓"); stringBuilder.append("\033[38;5;231;48;5;231m▓"); stringBuilder.append("\n"); } stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50)); stringBuilder.append("\n"); return stringBuilder; } }