jzitnik-dev 8c161180e6
feat(sounds): Added sounds
This currently doesn't work idk why. Will fix later.
2025-03-28 18:43:08 +01:00

527 lines
20 KiB
Java

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.AutoTransient;
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.autotransient.AutoTransientSupport;
import cz.jzitnik.game.core.autotransient.initilizers.GameMiningInitializer;
import cz.jzitnik.game.core.autotransient.initilizers.GameWindowInitializer;
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 extends AutoTransientSupport {
@SuppressWarnings("unchecked")
private final List<Block>[][] world = (List<Block>[][]) 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);
@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<Block> 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<Block> 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<Block> 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<Block> 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<Block> 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());
}
}
}
}