forked from jzitnik/twodcraft
This will probably be more expanded in future. But for now this approach works without some major issues. But ofc things like data migration etc doesn't work.
520 lines
19 KiB
Java
520 lines
19 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.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<Block>[][] world = (List<Block>[][]) 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<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());
|
|
}
|
|
}
|
|
}
|
|
}
|