forked from jzitnik/twodcraft
feat(saving): Use kryo for serialization
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.
This commit is contained in:
parent
1d29972087
commit
f09519773b
9
pom.xml
9
pom.xml
@ -128,11 +128,16 @@
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.18</version> <!-- latest at the time -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.trilarion</groupId>
|
||||
<artifactId>java-vorbis-support</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.esotericsoftware</groupId>
|
||||
<artifactId>kryo</artifactId>
|
||||
<version>5.6.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -31,7 +31,8 @@ public class Main {
|
||||
|
||||
var spriteList = SpriteLoader.load();
|
||||
var screenRenderer = new ScreenRenderer(spriteList, terminal);
|
||||
var game = GameSaver.load();
|
||||
var gameSaver = new GameSaver();
|
||||
var game = gameSaver.load();
|
||||
|
||||
final boolean[] isRunning = { true };
|
||||
|
||||
|
@ -11,7 +11,6 @@ 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;
|
||||
@ -20,9 +19,6 @@ 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;
|
||||
@ -39,19 +35,15 @@ import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@Getter
|
||||
public class Game extends AutoTransientSupport {
|
||||
public class Game {
|
||||
@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
|
||||
|
@ -1,46 +1,49 @@
|
||||
package cz.jzitnik.game;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class GameSaver {
|
||||
|
||||
private static final String SAVE_FILE = "world.ser";
|
||||
|
||||
private final Kryo kryo;
|
||||
|
||||
public GameSaver() {
|
||||
this.kryo = new Kryo();
|
||||
kryo.setRegistrationRequired(false);
|
||||
kryo.setReferences(true);
|
||||
}
|
||||
|
||||
public void save(Game game) {
|
||||
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("world.ser"))) {
|
||||
out.writeObject(game);
|
||||
try (Output output = new Output(new FileOutputStream(SAVE_FILE))) {
|
||||
kryo.writeClassAndObject(output, game);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error("Failed to save game", e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This will need rewrite
|
||||
public static Game load() {
|
||||
public Game load() {
|
||||
log.info("Loading game");
|
||||
File file = new File("world.ser");
|
||||
|
||||
File file = new File(SAVE_FILE);
|
||||
if (!file.isFile()) {
|
||||
log.info("No save file found, creating new game");
|
||||
return new Game();
|
||||
}
|
||||
|
||||
try {
|
||||
try (Input input = new Input(new FileInputStream(SAVE_FILE))) {
|
||||
log.info("Loading game from save file");
|
||||
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) {
|
||||
Object object = kryo.readClassAndObject(input);
|
||||
return (Game) object;
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load game", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package cz.jzitnik.game.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.jzitnik.game.core.autotransient.AutoTransientInitializer;
|
||||
import cz.jzitnik.game.core.autotransient.DefaultInitializer;
|
||||
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AutoTransient {
|
||||
Class<? extends AutoTransientInitializer<?>> initializer() default DefaultInitializer.class;
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
package cz.jzitnik.game.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Configuration implements Serializable {
|
||||
public class Configuration {
|
||||
private int soundVolume = 100; // 0-100
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package cz.jzitnik.game.core.autotransient;
|
||||
|
||||
public interface AutoTransientInitializer<T> {
|
||||
T initialize(Object parent);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package cz.jzitnik.game.core.autotransient;
|
||||
|
||||
import cz.jzitnik.game.annotations.AutoTransient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public abstract class AutoTransientSupport implements Serializable {
|
||||
|
||||
@Serial
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
reinitializeAutoTransients();
|
||||
}
|
||||
|
||||
protected void reinitializeAutoTransients() {
|
||||
for (Field field : this.getClass().getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(AutoTransient.class)) {
|
||||
field.setAccessible(true);
|
||||
AutoTransient annotation = field.getAnnotation(AutoTransient.class);
|
||||
try {
|
||||
Object value;
|
||||
|
||||
// Use initializer if provided
|
||||
Class<? extends AutoTransientInitializer<?>> initializerClass = annotation.initializer();
|
||||
if (!initializerClass.equals(DefaultInitializer.class)) {
|
||||
AutoTransientInitializer<?> initializer = initializerClass.getDeclaredConstructor().newInstance();
|
||||
value = initializer.initialize(this);
|
||||
} else {
|
||||
// Fallback to default instantiation
|
||||
value = instantiateField(field.getType());
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
field.set(this, value);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to reinitialize @AutoTransient field: " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Object instantiateField(Class<?> clazz) {
|
||||
try {
|
||||
// Try constructor with (this) reference first
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor(this.getClass());
|
||||
return constructor.newInstance(this);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Fallback to default constructor
|
||||
try {
|
||||
return clazz.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package cz.jzitnik.game.core.autotransient;
|
||||
|
||||
public class DefaultInitializer implements AutoTransientInitializer<Object> {
|
||||
@Override
|
||||
public Object initialize(Object parent) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package cz.jzitnik.game.core.autotransient.initilizers;
|
||||
|
||||
import cz.jzitnik.game.core.autotransient.AutoTransientInitializer;
|
||||
|
||||
public class GameMiningInitializer implements AutoTransientInitializer<Boolean> {
|
||||
@Override
|
||||
public Boolean initialize(Object parent) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package cz.jzitnik.game.core.autotransient.initilizers;
|
||||
|
||||
import cz.jzitnik.game.core.autotransient.AutoTransientInitializer;
|
||||
import cz.jzitnik.game.ui.Window;
|
||||
|
||||
public class GameWindowInitializer implements AutoTransientInitializer<Window> {
|
||||
@Override
|
||||
public Window initialize(Object parent) {
|
||||
return Window.WORLD;
|
||||
}
|
||||
}
|
@ -6,15 +6,16 @@ import cz.jzitnik.game.entities.items.ItemType;
|
||||
import cz.jzitnik.game.entities.items.ToolVariant;
|
||||
import cz.jzitnik.game.ui.Inventory;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Block implements Serializable {
|
||||
@NoArgsConstructor
|
||||
public class Block {
|
||||
private String blockId;
|
||||
private SpriteLoader.SPRITES sprite;
|
||||
private MyOptional<Enum> spriteState = MyOptional.empty();
|
||||
|
@ -1,10 +1,9 @@
|
||||
package cz.jzitnik.game.entities;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.*;
|
||||
|
||||
public class MyOptional<T> implements Serializable {
|
||||
public class MyOptional<T> {
|
||||
private T value;
|
||||
private boolean isPresent;
|
||||
|
||||
@ -64,22 +63,6 @@ public class MyOptional<T> implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Serial
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.writeBoolean(isPresent);
|
||||
if (isPresent) {
|
||||
out.writeObject(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Serial
|
||||
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";
|
||||
|
@ -3,7 +3,6 @@ package cz.jzitnik.game.entities;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
@ -15,7 +14,7 @@ import cz.jzitnik.tui.ScreenRenderer;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Player implements Serializable {
|
||||
public class Player {
|
||||
private int health = 10;
|
||||
private int hunger = 10;
|
||||
private int fallDistance = 0;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package cz.jzitnik.game.entities;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -9,6 +7,6 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class SteveData implements Serializable {
|
||||
public class SteveData {
|
||||
private boolean top = false;
|
||||
}
|
||||
|
@ -3,13 +3,12 @@ package cz.jzitnik.game.entities.items;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class InventoryItem implements Serializable {
|
||||
public class InventoryItem {
|
||||
private int amount;
|
||||
private final List<Item> item;
|
||||
|
||||
|
@ -7,12 +7,10 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class Item implements Serializable {
|
||||
public class Item {
|
||||
private String id;
|
||||
private String name;
|
||||
private ItemType type;
|
||||
|
@ -1,13 +1,11 @@
|
||||
package cz.jzitnik.game.logic.services.farmable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FarmableData implements Serializable {
|
||||
public class FarmableData {
|
||||
private int age = 0;
|
||||
private int state = 0;
|
||||
}
|
||||
|
@ -3,11 +3,9 @@ package cz.jzitnik.game.logic.services.farmland;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FarmlandData implements Serializable {
|
||||
public class FarmlandData {
|
||||
private int age = 0;
|
||||
private int dryAge = 0;
|
||||
private boolean watered = false;
|
||||
|
@ -3,10 +3,8 @@ package cz.jzitnik.game.logic.services.flowing;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FlowingData implements Serializable {
|
||||
public class FlowingData {
|
||||
protected boolean isSource = true;
|
||||
}
|
||||
|
@ -3,11 +3,9 @@ package cz.jzitnik.game.logic.services.grass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class GrassDirtData implements Serializable {
|
||||
public class GrassDirtData {
|
||||
private int age = 0;
|
||||
|
||||
public void increaseAge() {
|
||||
|
@ -3,12 +3,11 @@ 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 implements Serializable {
|
||||
public class SaplingData {
|
||||
private int growWait;
|
||||
|
||||
public SaplingData() {
|
||||
|
@ -3,8 +3,6 @@ package cz.jzitnik.game.mobs.services.cow;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cz.jzitnik.game.Game;
|
||||
import cz.jzitnik.game.annotations.RightClickLogic;
|
||||
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
|
||||
@ -14,7 +12,7 @@ import cz.jzitnik.tui.ScreenRenderer;
|
||||
@Getter
|
||||
@Setter
|
||||
@RightClickLogic
|
||||
public class CowData implements Serializable, RightClickHandler {
|
||||
public class CowData implements RightClickHandler {
|
||||
private int lastDirection = 1; // 1 = right, -1 = left
|
||||
private int movementCooldown = 0;
|
||||
private int jumpAttempts = 0;
|
||||
|
@ -3,11 +3,9 @@ package cz.jzitnik.game.mobs.services.pig;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PigData implements Serializable {
|
||||
public class PigData {
|
||||
private int lastDirection = 1; // 1 = right, -1 = left
|
||||
private int movementCooldown = 0;
|
||||
private int jumpAttempts = 0;
|
||||
|
@ -3,11 +3,9 @@ package cz.jzitnik.game.mobs.services.sheep;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class SheepData implements Serializable {
|
||||
public class SheepData {
|
||||
private int lastDirection = 1; // 1 = right, -1 = left
|
||||
private int movementCooldown = 0;
|
||||
private int jumpAttempts = 0;
|
||||
|
5
src/main/java/cz/jzitnik/game/ui/HomeScreen.java
Normal file
5
src/main/java/cz/jzitnik/game/ui/HomeScreen.java
Normal file
@ -0,0 +1,5 @@
|
||||
package cz.jzitnik.game.ui;
|
||||
|
||||
public class HomeScreen {
|
||||
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package cz.jzitnik.game.ui;
|
||||
|
||||
import cz.jzitnik.game.Game;
|
||||
import cz.jzitnik.game.annotations.AutoTransient;
|
||||
import cz.jzitnik.game.core.autotransient.AutoTransientSupport;
|
||||
import cz.jzitnik.game.entities.items.InventoryItem;
|
||||
import cz.jzitnik.game.entities.items.Item;
|
||||
import cz.jzitnik.tui.utils.SpriteCombiner;
|
||||
@ -17,7 +15,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Getter
|
||||
public class Inventory extends AutoTransientSupport {
|
||||
public class Inventory {
|
||||
public static final int INVENTORY_SIZE_PX = 470;
|
||||
public static final int COLUMN_AMOUNT = 5;
|
||||
public static final int ROW_AMOUNT = 4;
|
||||
@ -25,7 +23,6 @@ public class Inventory extends AutoTransientSupport {
|
||||
private final InventoryItem[] items = new InventoryItem[20];
|
||||
private final InventoryItem[] hotbar = new InventoryItem[9];
|
||||
|
||||
@AutoTransient
|
||||
private transient SmallCraftingTable smallCraftingTable = new SmallCraftingTable(this);
|
||||
|
||||
@Setter
|
||||
|
@ -18,7 +18,6 @@ public class Options {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
|
||||
public void render(StringBuilder buffer, Terminal terminal) {
|
||||
var buf = new StringBuilder();
|
||||
var font = game.getGameStates().dependencies.font;
|
||||
|
Loading…
x
Reference in New Issue
Block a user