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.entities.SteveData; 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.SteveState; import cz.jzitnik.game.annotations.WalkSound; import cz.jzitnik.game.annotations.BreaksByPlace; import cz.jzitnik.game.annotations.MineSound; import cz.jzitnik.game.annotations.MiningSound; import cz.jzitnik.game.annotations.PlaceSound; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.config.Configuration; import cz.jzitnik.game.core.sound.SoundKey; 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 { @SuppressWarnings("unchecked") 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(); private transient GameStates gameStates = new GameStates(this); @Setter private int daytime = 0; // 0-600 // private Configuration configuration = new Configuration(); 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")) { var steveData = (SteveData) block.getData(); if (steveData.isTop()) { return new int[] { j, i + 1 }; } else { 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); playMovePlayerSound(cords[0] + 1, cords[1]); 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); playMovePlayerSound(cords[0] - 1, cords[1]); 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(); gameStates.dependencies.sound.playSound(configuration, SoundKey.HIT, null); 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); var blocks = world[y][x]; this.mining = true; new Thread(() -> { for (Block block : blocks) { if (block.getClass().isAnnotationPresent(MiningSound.class)) { new Thread(() -> { var anot = block.getClass().getAnnotation(MiningSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); }).start(); } } try { Thread.sleep((long) (hardness * 166)); } catch (InterruptedException e) { throw new RuntimeException(e); } breakingBlock.setSpriteState(Breaking.BreakingState.SECOND); screenRenderer.render(this); for (Block block : blocks) { if (block.getClass().isAnnotationPresent(MiningSound.class)) { new Thread(() -> { var anot = block.getClass().getAnnotation(MiningSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); }).start(); } } try { Thread.sleep((long) (hardness * 166)); } catch (InterruptedException e) { throw new RuntimeException(e); } breakingBlock.setSpriteState(Breaking.BreakingState.THIRD); screenRenderer.render(this); for (Block block : blocks) { if (block.getClass().isAnnotationPresent(MiningSound.class)) { new Thread(() -> { var anot = block.getClass().getAnnotation(MiningSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); }).start(); } } 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); for (Block block : blocksCopy) { if (block.getClass().isAnnotationPresent(MineSound.class)) { new Thread(() -> { var anot = block.getClass().getAnnotation(MineSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); }).start(); } } 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]]); if (player.getFallDistance() != 0) { playMovePlayerSound(cords2[0], cords2[1]); } player.fell(combinedList, this, screenRenderer); 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); for (Block block : blocks) { if (block.getClass().isAnnotationPresent(PlaceSound.class)) { var anot = block.getClass().getAnnotation(PlaceSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); } } 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); } public void playerHit(ScreenRenderer screenRenderer) { player.getPlayerBlock1().setSpriteState(SteveState.FIRST_HURT); player.getPlayerBlock2().setSpriteState(SteveState.SECOND_HURT); new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } player.getPlayerBlock1().setSpriteState(SteveState.FIRST); player.getPlayerBlock2().setSpriteState(SteveState.SECOND); screenRenderer.render(this); }).start(); } private void playMovePlayerSound(int x, int y) { var blocks = world[y + 1][x]; for (Block block : blocks) { if (block.getClass().isAnnotationPresent(WalkSound.class)) { var anot = block.getClass().getAnnotation(WalkSound.class); gameStates.dependencies.sound.playSound(configuration, anot.value(), anot.annotationType()); } } } }