package cz.jzitnik.game; import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.entities.GameStates; import cz.jzitnik.game.entities.Player; import cz.jzitnik.game.generation.Generation; import cz.jzitnik.game.entities.items.Item; import cz.jzitnik.game.entities.items.ItemType; import cz.jzitnik.game.handlers.place.CustomPlaceHandler; import cz.jzitnik.game.mobs.EntitySpawnProvider; import cz.jzitnik.game.sprites.Breaking; import cz.jzitnik.game.sprites.Steve; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.ui.Window; import cz.jzitnik.game.ui.Inventory; import cz.jzitnik.game.handlers.rightclick.RightClickHandlerProvider; import cz.jzitnik.tui.ScreenMovingCalculationProvider; import cz.jzitnik.tui.ScreenRenderer; import lombok.Getter; import lombok.Setter; import org.jline.terminal.Terminal; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @Getter public class Game implements Serializable { @SuppressWarnings("unchecked") private final List[][] world = (List[][]) new CopyOnWriteArrayList[256][512]; private final Player player = new Player(); private transient boolean mining = false; @Setter private Window window = Window.WORLD; private final Inventory inventory = new Inventory(); private transient EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); private transient GameStates gameStates = new GameStates(this); @Serial private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { in.defaultReadObject(); entitySpawnProvider = new EntitySpawnProvider(); gameStates = new GameStates(this); mining = false; } public Game() { Generation.generateWorld(this); } public int[] getPlayerCords() { for (int i = 0; i < world.length; i++) { for (int j = 0; j < world[i].length; j++) { for (Block block : world[i][j]) { if (block.getBlockId().equals("steve") && block.getSpriteState().isPresent() && block.getSpriteState().get() == Steve.SteveState.SECOND) { return new int[] { j, i }; } } } } return null; } public void movePlayerRight(ScreenRenderer screenRenderer, Terminal terminal) { if (window != Window.WORLD) { return; } int[] cords = getPlayerCords(); if (isSolid(world[cords[1]][cords[0] + 1]) || isSolid(world[cords[1] - 1][cords[0] + 1])) { return; } world[cords[1]][cords[0] + 1].add(player.getPlayerBlock2()); world[cords[1]][cords[0]].remove(player.getPlayerBlock2()); world[cords[1] - 1][cords[0] + 1].add(player.getPlayerBlock1()); world[cords[1] - 1][cords[0]].remove(player.getPlayerBlock1()); screenRenderer.render(this); entitySpawnProvider.update(this, terminal); update(screenRenderer); } public void movePlayerLeft(ScreenRenderer screenRenderer, Terminal terminal) { if (window != Window.WORLD) { return; } int[] cords = getPlayerCords(); if (isSolid(world[cords[1]][cords[0] - 1]) || isSolid(world[cords[1] - 1][cords[0] - 1])) { return; } world[cords[1]][cords[0] - 1].add(player.getPlayerBlock2()); world[cords[1]][cords[0]].remove(player.getPlayerBlock2()); world[cords[1] - 1][cords[0] - 1].add(player.getPlayerBlock1()); world[cords[1] - 1][cords[0]].remove(player.getPlayerBlock1()); screenRenderer.render(this); entitySpawnProvider.update(this, terminal); update(screenRenderer); } public void movePlayerUp(ScreenRenderer screenRenderer) { if (window != Window.WORLD) { return; } int[] cords = getPlayerCords(); if (isSolid(world[cords[1] - 2][cords[0]]) || !isSolid(world[cords[1] + 1][cords[0]])) { return; } world[cords[1] - 1][cords[0]].remove(player.getPlayerBlock1()); world[cords[1] - 1][cords[0]].add(player.getPlayerBlock2()); world[cords[1] - 2][cords[0]].add(player.getPlayerBlock1()); world[cords[1]][cords[0]].remove(player.getPlayerBlock2()); new Thread(() -> { try { Thread.sleep(400); } catch (InterruptedException e) { throw new RuntimeException(e); } update(screenRenderer); }).start(); } public void hit(ScreenRenderer screenRenderer, int x, int y) { if (mining || window != Window.WORLD) { return; } List mobs = world[y][x].stream().filter(Block::isMob).toList(); for (Block mob : mobs) { int dealDamage = inventory.getItemInHand().map(Item::getDealDamage).orElse(1); if (mob.getHp() - dealDamage <= 0) { // Mob is killed gameStates.dependencies.entityKill.get(mob.getBlockId()).killed(this, mob); world[y][x].remove(mob); } else { mob.decreaseHp(dealDamage); mob.setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId()) .setHurtAnimation(true, mob.getSpriteState().get())); } } screenRenderer.render(this); new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } for (Block mob : mobs) { mob.setSpriteState(gameStates.dependencies.entityHurtAnimation.get(mob.getBlockId()) .setHurtAnimation(false, mob.getSpriteState().get())); } screenRenderer.render(this); }).start(); } public void mine(ScreenRenderer screenRenderer, int x, int y) { if (mining || window != Window.WORLD) { return; } Block breakingBlock = new Block("breaking", SpriteLoader.SPRITES.BREAKING); breakingBlock.setGhost(true); world[y][x].add(breakingBlock); screenRenderer.render(this); double hardness = world[y][x].stream().filter(block -> !block.getBlockId().equals("air")).toList().getFirst() .calculateHardness(inventory); this.mining = true; new Thread(() -> { try { Thread.sleep((long) (hardness * 166)); } catch (InterruptedException e) { throw new RuntimeException(e); } breakingBlock.setSpriteState(Breaking.BreakingState.SECOND); screenRenderer.render(this); try { Thread.sleep((long) (hardness * 166)); } catch (InterruptedException e) { throw new RuntimeException(e); } breakingBlock.setSpriteState(Breaking.BreakingState.THIRD); screenRenderer.render(this); try { Thread.sleep((long) (hardness * 166)); } catch (InterruptedException e) { throw new RuntimeException(e); } mining = false; mineInstant(screenRenderer, x, y, true); }).start(); } public void mineInstant(ScreenRenderer screenRenderer, int x, int y, boolean minedDirectly) { var blocks = world[y][x]; var blocksCopy = new ArrayList<>(blocks); CustomPlaceHandler customPlaceHandler = gameStates.dependencies.placeHandler .get(blocks.stream().filter(Block::isMineable).toList().getFirst().getBlockId()); boolean giveItem = customPlaceHandler.mine(this, x, y); if (giveItem) { for (Block block : blocksCopy) { if (!block.isMineable()) { continue; } if (block.getToolVariants().isEmpty()) { // Add to inv block.getDrops().forEach(inventory::addItem); continue; } var toolVariants = block.getToolVariants(); if (inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getToolVariant().isPresent() && block.getTool().isPresent() && block.getTool().get().equals(inventory.getItemInHand().get().getType()) && toolVariants.contains(inventory.getItemInHand().get().getToolVariant().get()) && minedDirectly) { block.getDrops().forEach(inventory::addItem); } } } for (Block block : blocksCopy) { if (block.getBlockId().equals("chest")) { ((Chest) block.getData()).breakBlock(this); } else if (block.getBlockId().equals("furnace")) { ((Furnace) block.getData()).breakBlock(this); } } inventory.getItemInHand().ifPresent(Item::use); gameStates.dependencies.eventHandlerProvider.handleMine(screenRenderer, this, x, y); screenRenderer.render(this); update(screenRenderer); } public boolean isMineable(int x, int y, Terminal terminal) { List blocks = world[y][x]; int[] cords = getPlayerCords(); int playerX = cords[0]; int playerY = cords[1]; int distanceX = Math.abs(playerX - x); int distanceY = Math.abs(playerY - y); int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), world[0].length, world.length); int startX = data[0]; int endX = data[1]; int startY = data[2]; int endY = data[3]; return y >= startY && y < endY - 1 && x >= startX && x < endX - 1 && distanceX <= 5 && distanceY <= 5 && !(playerX == x && playerY == y) && !(playerX == x && playerY - 1 == y) && blocks.stream().anyMatch(Block::isMineable); } public boolean isHitable(int x, int y, Terminal terminal) { List blocks = world[y][x]; int[] cords = getPlayerCords(); int playerX = cords[0]; int playerY = cords[1]; int distanceX = Math.abs(playerX - x); int distanceY = Math.abs(playerY - y); int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), world[0].length, world.length); int startX = data[0]; int endX = data[1]; int startY = data[2]; int endY = data[3]; return y >= startY && y < endY - 1 && x >= startX && x < endX - 1 && distanceX <= 5 && distanceY <= 5 && !(playerX == x && playerY == y) && !(playerX == x && playerY - 1 == y) && blocks.stream().anyMatch(Block::isMob); } public void update(ScreenRenderer screenRenderer) { while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } int[] cords2 = getPlayerCords(); if (!isSolid(world[cords2[1] + 1][cords2[0]])) { world[cords2[1] - 1][cords2[0]].remove(player.getPlayerBlock1()); world[cords2[1]][cords2[0]].add(player.getPlayerBlock1()); world[cords2[1] + 1][cords2[0]].add(player.getPlayerBlock2()); world[cords2[1]][cords2[0]].remove(player.getPlayerBlock2()); player.addFalling(); screenRenderer.render(this); } else { player.fell(); screenRenderer.render(this); break; } } } public void build(int x, int y, ScreenRenderer screenRenderer) { if (window != Window.WORLD) { return; } if (inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getType() == ItemType.FOOD) { if (player.getHunger() >= 10) { return; } player.setHunger(Math.min(10, player.getHunger() + inventory.getItemInHand().get().getAddHunger())); inventory.decreaseItemInHand(); screenRenderer.render(this); return; } var blocks = world[y][x]; int[] cords = getPlayerCords(); int playerX = cords[0]; int playerY = cords[1]; int distanceX = Math.abs(playerX - x); int distanceY = Math.abs(playerY - y); if (distanceX > 5 || distanceY > 5) { return; } if (inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getType() == ItemType.PICKUPER) { if (gameStates.dependencies.pickupHandlerProvider.get(inventory.getItemInHand().get().getId()).handle(this, x, y)) { screenRenderer.render(this); } ; return; } if (!blocks.stream().allMatch(block -> block.getBlockId().equals("air") || block.isFlowing())) { RightClickHandlerProvider.handle(x, y, this, screenRenderer); screenRenderer.render(this); return; } if (!(inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getType() == ItemType.BLOCK)) { return; } Item item = inventory.getItemInHand().get(); CustomPlaceHandler placeHandler = gameStates.dependencies.placeHandler.get(item.getId()); if (placeHandler.place(this, x, y)) { gameStates.dependencies.eventHandlerProvider.handlePlace(screenRenderer, this, x, y); screenRenderer.render(this); } } public void changeSlot(int slot, ScreenRenderer screenRenderer) { if (window != Window.WORLD) { return; } inventory.setItemInhHandIndex(slot); screenRenderer.render(this); } public boolean isSolid(List blocks) { return !blocks.stream().allMatch(Block::isGhost); } }