From 32fba4958702456e4460cff4c8781d818fc1150e Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Sat, 8 Mar 2025 17:36:59 +0100 Subject: [PATCH] feat: Implemented saving of world --- pom.xml | 6 + src/main/java/cz/jzitnik/Main.java | 4 +- src/main/java/cz/jzitnik/game/Game.java | 18 ++- src/main/java/cz/jzitnik/game/GameSaver.java | 41 +++++++ .../java/cz/jzitnik/game/entities/Block.java | 13 +- .../jzitnik/game/entities/Dependencies.java | 2 + .../cz/jzitnik/game/entities/MyOptional.java | 115 ++++++++++++++++++ .../java/cz/jzitnik/game/entities/Player.java | 4 +- .../game/entities/items/InventoryItem.java | 4 +- .../cz/jzitnik/game/entities/items/Item.java | 18 +-- .../entities/items/ItemBlockSupplier.java | 7 +- .../logic/services/flowing/FlowingData.java | 4 +- .../logic/services/saplings/SaplingData.java | 3 +- .../game/mobs/services/cow/CowData.java | 4 +- .../game/mobs/services/pig/PigData.java | 4 +- .../game/mobs/services/sheep/SheepData.java | 4 +- .../game/threads/InputHandlerThread.java | 2 + .../java/cz/jzitnik/game/ui/Inventory.java | 13 +- 18 files changed, 233 insertions(+), 33 deletions(-) create mode 100644 src/main/java/cz/jzitnik/game/GameSaver.java create mode 100644 src/main/java/cz/jzitnik/game/entities/MyOptional.java diff --git a/pom.xml b/pom.xml index 0882215..2a9bf26 100644 --- a/pom.xml +++ b/pom.xml @@ -128,6 +128,12 @@ slf4j-simple 2.0.17 + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.15.0 + diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java index 1478a16..2aa20ba 100644 --- a/src/main/java/cz/jzitnik/Main.java +++ b/src/main/java/cz/jzitnik/Main.java @@ -1,6 +1,6 @@ package cz.jzitnik; -import cz.jzitnik.game.Game; +import cz.jzitnik.game.GameSaver; import cz.jzitnik.game.logic.CustomLogicProvider; import cz.jzitnik.game.mobs.EntityLogicProvider; import cz.jzitnik.game.threads.HealthRegenerationThread; @@ -28,7 +28,7 @@ public class Main { var spriteList = SpriteLoader.load(); var screenRenderer = new ScreenRenderer(spriteList, terminal); - var game = new Game(); + var game = GameSaver.load(); final boolean[] isRunning = { true }; diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java index 3a2c290..dc3600c 100644 --- a/src/main/java/cz/jzitnik/game/Game.java +++ b/src/main/java/cz/jzitnik/game/Game.java @@ -22,12 +22,14 @@ 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 { +public class Game implements Serializable { @SuppressWarnings("unchecked") private final List[][] world = (List[][]) new CopyOnWriteArrayList[256][512]; private final Player player = new Player(); @@ -36,11 +38,17 @@ public class Game { private Window window = Window.WORLD; private final Inventory inventory = new Inventory(); - @JsonIgnore - private final EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); + private transient EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); - @JsonIgnore - private final GameStates gameStates = new GameStates(this); + 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); + } public Game() { Generation.generateWorld(this); diff --git a/src/main/java/cz/jzitnik/game/GameSaver.java b/src/main/java/cz/jzitnik/game/GameSaver.java new file mode 100644 index 0000000..80e6945 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/GameSaver.java @@ -0,0 +1,41 @@ +package cz.jzitnik.game; + +import java.io.*; + +public class GameSaver { + public void save(Game game) { + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("world.ser"))) { + out.writeObject(game); + System.out.println("Inner class saved!"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static Game load() { + File file = new File("world.ser"); + + if (!file.isFile()) { + return new Game(); + } + + try { + FileInputStream fileIn = new FileInputStream("world.ser"); + + ObjectInputStream in = new ObjectInputStream(fileIn); + + // Read the object from the file + Object object = in.readObject(); + + Game game = (Game) object; + + in.close(); + fileIn.close(); + + return game; + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/main/java/cz/jzitnik/game/entities/Block.java b/src/main/java/cz/jzitnik/game/entities/Block.java index f90b3b0..a5f9046 100644 --- a/src/main/java/cz/jzitnik/game/entities/Block.java +++ b/src/main/java/cz/jzitnik/game/entities/Block.java @@ -8,20 +8,21 @@ import cz.jzitnik.game.ui.Inventory; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Getter @Setter -public class Block { +public class Block implements Serializable { private String blockId; private SpriteLoader.SPRITES sprite; - private Optional spriteState = Optional.empty(); + private MyOptional spriteState = MyOptional.empty(); private boolean ghost = false; private boolean isMineable = true; private int hardness = 1; - private Optional tool = Optional.empty(); + private MyOptional tool = MyOptional.empty(); private List toolVariants = new ArrayList<>(); private List drops = new ArrayList<>(); private Object data = null; @@ -52,7 +53,7 @@ public class Block { this.blockId = blockId; this.sprite = sprite; this.hardness = hardness; - this.tool = Optional.of(tool); + this.tool = MyOptional.of(tool); this.toolVariants = toolVariants; } @@ -61,13 +62,13 @@ public class Block { this.blockId = blockId; this.sprite = sprite; this.hardness = hardness; - this.tool = Optional.of(tool); + this.tool = MyOptional.of(tool); this.toolVariants = toolVariants; this.data = data; } public void setSpriteState(Enum spriteState) { - this.spriteState = Optional.of(spriteState); + this.spriteState = MyOptional.of(spriteState); } public double calculateHardness(Inventory inventory) { diff --git a/src/main/java/cz/jzitnik/game/entities/Dependencies.java b/src/main/java/cz/jzitnik/game/entities/Dependencies.java index 896063c..8839f1a 100644 --- a/src/main/java/cz/jzitnik/game/entities/Dependencies.java +++ b/src/main/java/cz/jzitnik/game/entities/Dependencies.java @@ -1,5 +1,6 @@ package cz.jzitnik.game.entities; +import cz.jzitnik.game.GameSaver; import cz.jzitnik.game.handlers.pickup.PickupHandlerProvider; import cz.jzitnik.game.handlers.place.PlaceHandler; import cz.jzitnik.game.mobs.EntityHurtAnimation; @@ -10,4 +11,5 @@ public class Dependencies { public EntityHurtAnimation entityHurtAnimation = new EntityHurtAnimation(); public EntityKill entityKill = new EntityKill(); public PickupHandlerProvider pickupHandlerProvider = new PickupHandlerProvider(); + public GameSaver gameSaver = new GameSaver(); } diff --git a/src/main/java/cz/jzitnik/game/entities/MyOptional.java b/src/main/java/cz/jzitnik/game/entities/MyOptional.java new file mode 100644 index 0000000..3f74c8b --- /dev/null +++ b/src/main/java/cz/jzitnik/game/entities/MyOptional.java @@ -0,0 +1,115 @@ +package cz.jzitnik.game.entities; + +import java.io.*; +import java.util.NoSuchElementException; +import java.util.function.*; + +public class MyOptional implements Serializable { + private static final long serialVersionUID = 1L; + private T value; + private boolean isPresent; + + // Constructor + public MyOptional() { + this.isPresent = false; + } + + public MyOptional(T value) { + this.value = value; + this.isPresent = true; + } + + // Check if value is present + public boolean isPresent() { + return isPresent; + } + + // Get the value (or throw exception if not present) + public T get() { + if (!isPresent) { + throw new NoSuchElementException("No value present"); + } + return value; + } + + // Get the value or return default + public T orElse(T other) { + return isPresent ? value : other; + } + + // If value is present, apply function, else return another Optional + public MyOptional map(Function mapper) { + if (!isPresent) { + return new MyOptional<>(); + } + return new MyOptional<>(mapper.apply(value)); + } + + // If value is present, apply function and return a new Optional + public MyOptional flatMap(Function> mapper) { + if (!isPresent) { + return new MyOptional<>(); + } + return mapper.apply(value); + } + + // Perform an action if value is present + public void ifPresent(Consumer action) { + if (isPresent) { + action.accept(value); + } + } + + // Return the value inside the Optional or default if not present + public T orElseGet(Supplier other) { + return isPresent ? value : other.get(); + } + + // Filter the value based on a condition + public MyOptional filter(Predicate predicate) { + if (!isPresent || !predicate.test(value)) { + return new MyOptional<>(); + } + return this; + } + + // Serialize the value + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeBoolean(isPresent); + if (isPresent) { + out.writeObject(value); + } + } + + // Deserialize the value + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + isPresent = in.readBoolean(); + if (isPresent) { + value = (T) in.readObject(); + } + } + + @Override + public String toString() { + return isPresent ? "SerializableOptional[" + value + "]" : "SerializableOptional.empty"; + } + + // Static factory method for an empty SerializableOptional + public static MyOptional empty() { + return new MyOptional<>(); + } + + // Static factory method for a present value + public static MyOptional of(T value) { + return new MyOptional<>(value); + } + + // Static factory method for a present value or null + public static MyOptional ofNullable(T value) { + return value == null ? empty() : new MyOptional<>(value); + } + + public boolean isEmpty() { + return !isPresent; + } +} \ No newline at end of file diff --git a/src/main/java/cz/jzitnik/game/entities/Player.java b/src/main/java/cz/jzitnik/game/entities/Player.java index 5a61710..c24d5b9 100644 --- a/src/main/java/cz/jzitnik/game/entities/Player.java +++ b/src/main/java/cz/jzitnik/game/entities/Player.java @@ -3,9 +3,11 @@ package cz.jzitnik.game.entities; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class Player { +public class Player implements Serializable { private int health = 10; private int hunger = 10; private int fallDistance = 0; diff --git a/src/main/java/cz/jzitnik/game/entities/items/InventoryItem.java b/src/main/java/cz/jzitnik/game/entities/items/InventoryItem.java index 44d59fa..7198ecc 100644 --- a/src/main/java/cz/jzitnik/game/entities/items/InventoryItem.java +++ b/src/main/java/cz/jzitnik/game/entities/items/InventoryItem.java @@ -3,12 +3,14 @@ package cz.jzitnik.game.entities.items; import lombok.AllArgsConstructor; import lombok.Getter; +import java.io.Serializable; +import java.lang.foreign.SegmentAllocator; import java.util.ArrayList; import java.util.List; @Getter @AllArgsConstructor -public class InventoryItem { +public class InventoryItem implements Serializable { private int amount; private final List item; diff --git a/src/main/java/cz/jzitnik/game/entities/items/Item.java b/src/main/java/cz/jzitnik/game/entities/items/Item.java index 514067c..d8c9ec9 100644 --- a/src/main/java/cz/jzitnik/game/entities/items/Item.java +++ b/src/main/java/cz/jzitnik/game/entities/items/Item.java @@ -2,29 +2,31 @@ package cz.jzitnik.game.entities.items; import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.SpriteLoader; +import cz.jzitnik.game.entities.MyOptional; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.Optional; @Getter @Setter @AllArgsConstructor -public class Item { +public class Item implements Serializable { private String id; private String name; private ItemType type; - private Optional toolVariant = Optional.empty(); + private MyOptional toolVariant = MyOptional.empty(); private SpriteLoader.SPRITES sprite; - private Optional spriteState = Optional.empty(); + private MyOptional spriteState = MyOptional.empty(); private boolean stackable = true; private int durability; private double miningDecrease = 0; private int stackAmount = 64; private int addHunger = 0; private int dealDamage = 1; - private Optional block = Optional.empty(); + private MyOptional block = MyOptional.empty(); public Item(String id, String name, ItemType type, SpriteLoader.SPRITES sprite, ToolVariant toolVariant, int durability, boolean stackable, int dealDamage) { @@ -32,7 +34,7 @@ public class Item { this.name = name; this.type = type; this.sprite = sprite; - this.toolVariant = Optional.of(toolVariant); + this.toolVariant = MyOptional.of(toolVariant); this.durability = durability; this.stackable = stackable; this.dealDamage = dealDamage; @@ -44,7 +46,7 @@ public class Item { this.name = name; this.type = type; this.sprite = sprite; - this.toolVariant = Optional.of(toolVariant); + this.toolVariant = MyOptional.of(toolVariant); this.miningDecrease = miningDecrease; this.durability = durability; this.stackable = stackable; @@ -55,7 +57,7 @@ public class Item { this.name = name; this.type = type; this.sprite = sprite; - this.block = Optional.of(block); + this.block = MyOptional.of(block); } public Item(String id, String name, ItemType type, SpriteLoader.SPRITES sprite) { @@ -89,6 +91,6 @@ public class Item { } public void setSpriteState(Enum spriteState) { - this.spriteState = Optional.of(spriteState); + this.spriteState = MyOptional.of(spriteState); } } diff --git a/src/main/java/cz/jzitnik/game/entities/items/ItemBlockSupplier.java b/src/main/java/cz/jzitnik/game/entities/items/ItemBlockSupplier.java index 54214b7..b20fbe7 100644 --- a/src/main/java/cz/jzitnik/game/entities/items/ItemBlockSupplier.java +++ b/src/main/java/cz/jzitnik/game/entities/items/ItemBlockSupplier.java @@ -4,6 +4,7 @@ import cz.jzitnik.game.annotations.BlockRegistry; import cz.jzitnik.game.annotations.EntityRegistry; import cz.jzitnik.game.annotations.ItemRegistry; import cz.jzitnik.game.entities.Block; +import cz.jzitnik.game.entities.MyOptional; import org.reflections.Reflections; import java.lang.reflect.Constructor; @@ -107,7 +108,7 @@ public class ItemBlockSupplier { try { Item item = registeredItems.get(key).newInstance(); if (registeredBlocks.containsKey(blockList.get(key))) { - item.setBlock(Optional.of(getBlock(blockList.get(key), item))); + item.setBlock(MyOptional.of(getBlock(blockList.get(key), item))); } return item; @@ -121,9 +122,9 @@ public class ItemBlockSupplier { Item item = registeredItems.get(key).newInstance(); if (blockList.get(key).equals(block.getBlockId())) { - item.setBlock(Optional.of(block)); + item.setBlock(MyOptional.of(block)); } else { - item.setBlock(Optional.of(getBlock(blockList.get(key), item))); + item.setBlock(MyOptional.of(getBlock(blockList.get(key), item))); } return item; diff --git a/src/main/java/cz/jzitnik/game/logic/services/flowing/FlowingData.java b/src/main/java/cz/jzitnik/game/logic/services/flowing/FlowingData.java index 4056f4a..ef5af50 100644 --- a/src/main/java/cz/jzitnik/game/logic/services/flowing/FlowingData.java +++ b/src/main/java/cz/jzitnik/game/logic/services/flowing/FlowingData.java @@ -3,8 +3,10 @@ package cz.jzitnik.game.logic.services.flowing; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class FlowingData { +public class FlowingData implements Serializable { protected boolean isSource = true; } diff --git a/src/main/java/cz/jzitnik/game/logic/services/saplings/SaplingData.java b/src/main/java/cz/jzitnik/game/logic/services/saplings/SaplingData.java index 31ab442..871ad51 100644 --- a/src/main/java/cz/jzitnik/game/logic/services/saplings/SaplingData.java +++ b/src/main/java/cz/jzitnik/game/logic/services/saplings/SaplingData.java @@ -3,11 +3,12 @@ package cz.jzitnik.game.logic.services.saplings; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.Random; @Getter @Setter -public class SaplingData { +public class SaplingData implements Serializable { private int growWait; public SaplingData() { diff --git a/src/main/java/cz/jzitnik/game/mobs/services/cow/CowData.java b/src/main/java/cz/jzitnik/game/mobs/services/cow/CowData.java index 37db14f..4a41d8b 100644 --- a/src/main/java/cz/jzitnik/game/mobs/services/cow/CowData.java +++ b/src/main/java/cz/jzitnik/game/mobs/services/cow/CowData.java @@ -3,9 +3,11 @@ package cz.jzitnik.game.mobs.services.cow; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class CowData { +public class CowData implements Serializable { private int lastDirection = 1; // 1 = right, -1 = left private int movementCooldown = 0; private int jumpAttempts = 0; diff --git a/src/main/java/cz/jzitnik/game/mobs/services/pig/PigData.java b/src/main/java/cz/jzitnik/game/mobs/services/pig/PigData.java index 8f95ab7..e611fa6 100644 --- a/src/main/java/cz/jzitnik/game/mobs/services/pig/PigData.java +++ b/src/main/java/cz/jzitnik/game/mobs/services/pig/PigData.java @@ -3,9 +3,11 @@ package cz.jzitnik.game.mobs.services.pig; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class PigData { +public class PigData implements Serializable { private int lastDirection = 1; // 1 = right, -1 = left private int movementCooldown = 0; private int jumpAttempts = 0; diff --git a/src/main/java/cz/jzitnik/game/mobs/services/sheep/SheepData.java b/src/main/java/cz/jzitnik/game/mobs/services/sheep/SheepData.java index ab3e4bf..4caa046 100644 --- a/src/main/java/cz/jzitnik/game/mobs/services/sheep/SheepData.java +++ b/src/main/java/cz/jzitnik/game/mobs/services/sheep/SheepData.java @@ -3,9 +3,11 @@ package cz.jzitnik.game.mobs.services.sheep; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class SheepData { +public class SheepData implements Serializable { private int lastDirection = 1; // 1 = right, -1 = left private int movementCooldown = 0; private int jumpAttempts = 0; diff --git a/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java b/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java index d757012..6647246 100644 --- a/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java +++ b/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java @@ -84,6 +84,8 @@ public class InputHandlerThread extends Thread { case 'q' -> { System.out.println("Exiting game..."); isRunning[0] = false; + game.getGameStates().dependencies.gameSaver.save(game); + System.exit(0); } default -> { } diff --git a/src/main/java/cz/jzitnik/game/ui/Inventory.java b/src/main/java/cz/jzitnik/game/ui/Inventory.java index 7679c43..f59a599 100644 --- a/src/main/java/cz/jzitnik/game/ui/Inventory.java +++ b/src/main/java/cz/jzitnik/game/ui/Inventory.java @@ -9,19 +9,28 @@ 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.Optional; @Getter -public class Inventory { +public class Inventory implements Serializable { public static final int INVENTORY_SIZE_PX = 470; public static final int COLUMN_AMOUNT = 5; public static final int ROW_AMOUNT = 4; private final InventoryItem[] items = new InventoryItem[20]; private final InventoryItem[] hotbar = new InventoryItem[9]; - private final SmallCraftingTable smallCraftingTable = new SmallCraftingTable(this); + private transient SmallCraftingTable smallCraftingTable = new SmallCraftingTable(this); + + @Serial + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { + in.defaultReadObject(); + + smallCraftingTable = new SmallCraftingTable(this); + } @Setter private int itemInhHandIndex = 0;