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.annotations.AutoTransient; import cz.jzitnik.game.annotations.BreaksByPlace; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.core.autotransient.AutoTransientSupport; import cz.jzitnik.game.core.autotransient.initilizers.GameMiningInitializer; import cz.jzitnik.game.core.autotransient.initilizers.GameWindowInitializer; 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.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @Getter public class Game extends AutoTransientSupport { @SuppressWarnings("unchecked") private final List[][] world = (List[][]) new CopyOnWriteArrayList[256][512]; private final Player player = new Player(); @AutoTransient(initializer = GameMiningInitializer.class) private transient boolean mining = false; @Setter @AutoTransient(initializer = GameWindowInitializer.class) private transient Window window = Window.WORLD; private final Inventory inventory = new Inventory(); @AutoTransient private transient EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); @AutoTransient private transient GameStates gameStates = new GameStates(this); 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); blocksCopy.forEach((e) -> { e.setOnFire(false); e.setBurningTime(0); }); 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); } } if (inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getMaxDurability() != 0) { boolean broken = inventory.getItemInHand().get().use(); if (broken) { inventory.decreaseItemInHand(); } } 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 { ArrayList combinedList = new ArrayList<>(); combinedList.addAll(world[cords2[1]][cords2[0]]); combinedList.addAll(world[cords2[1] + 1][cords2[0]]); player.fell(combinedList); 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() || block.getClass().isAnnotationPresent(BreaksByPlace.class))) { boolean toolUsed = false; if (inventory.getItemInHand().isPresent()) { var item = inventory.getItemInHand().get(); toolUsed = gameStates.dependencies.toolUseProvider.handle(item.getType(), this, x, y); if (toolUsed) { boolean broken = item.use(); if (broken) { inventory.decreaseItemInHand(); } } } if (!toolUsed) { 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.getBlock().get().getBlockId()); var blocksRemove = blocks.stream().filter(block -> block.getClass().isAnnotationPresent(BreaksByPlace.class)) .toList(); if (placeHandler.place(this, x, y)) { blocks.removeAll(blocksRemove); 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); } }