diff --git a/.gitignore b/.gitignore index 321903a..4492218 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # BlueJ files *.ctxt +# run +saves/ + # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/src/main/java/com/github/halotroop/litecraft/Litecraft.java b/src/main/java/com/github/halotroop/litecraft/Litecraft.java index 709bc56..3bcaf66 100644 --- a/src/main/java/com/github/halotroop/litecraft/Litecraft.java +++ b/src/main/java/com/github/halotroop/litecraft/Litecraft.java @@ -4,9 +4,11 @@ import java.util.Random; import org.lwjgl.glfw.GLFW; +import com.github.halotroop.litecraft.save.LitecraftSave; import com.github.halotroop.litecraft.screens.TitleScreen; +import com.github.halotroop.litecraft.types.block.*; import com.github.halotroop.litecraft.world.World; -import com.github.halotroop.litecraft.world.gen.Dimension; +import com.github.halotroop.litecraft.world.gen.*; import com.github.hydos.ginger.engine.api.*; import com.github.hydos.ginger.engine.api.game.*; import com.github.hydos.ginger.engine.cameras.*; @@ -26,6 +28,7 @@ import tk.valoeghese.gateways.client.io.*; public class Litecraft extends Game { private World world; + private LitecraftSave save; private Ginger ginger3D; private static Litecraft INSTANCE; @@ -51,6 +54,8 @@ public class Litecraft extends Game setupKeybinds(); + Block b = Blocks.AIR; // make sure blocks are initialised + GingerUtils.init(); Window.setBackgroundColour(0.2f, 0.2f, 0.6f); TexturedModel dirtModel = ModelLoader.loadGenericCube("block/cubes/stone/brick/stonebrick.png"); @@ -89,6 +94,7 @@ public class Litecraft extends Game @Override public void exit() { + this.world.unloadAllChunks(); ginger3D.cleanup(); System.exit(0); } @@ -146,7 +152,8 @@ public class Litecraft extends Game public void onPlayButtonClick() { if (world == null) { - world = new World(new Random().nextLong(), 2, Dimension.OVERWORLD); + this.save = new LitecraftSave("test", false); + this.world = this.save.getWorldOrCreate(Dimensions.OVERWORLD); } } } \ No newline at end of file diff --git a/src/main/java/com/github/halotroop/litecraft/save/LitecraftSave.java b/src/main/java/com/github/halotroop/litecraft/save/LitecraftSave.java index 81cd186..22a69f0 100644 --- a/src/main/java/com/github/halotroop/litecraft/save/LitecraftSave.java +++ b/src/main/java/com/github/halotroop/litecraft/save/LitecraftSave.java @@ -1,59 +1,67 @@ package com.github.halotroop.litecraft.save; import java.io.*; +import java.util.Random; -import com.github.halotroop.litecraft.world.Chunk; +import com.github.halotroop.litecraft.world.*; +import com.github.halotroop.litecraft.world.gen.*; -import tk.valoeghese.sod.BinaryData; +import tk.valoeghese.sod.*; public final class LitecraftSave { public LitecraftSave(String name, boolean mustCreateNew) { StringBuilder sb = new StringBuilder(SAVE_DIR).append(name); - File saveFile = new File(sb.toString()); - + File saveDir = new File(sb.toString()); + if (mustCreateNew) { - while (saveFile.exists()) + while (saveDir.exists()) { - sb.append('_'); - saveFile = new File(sb.toString()); + sb.append('_'); // append "_" to the save name until we get a unique save, if we must create a new save + saveDir = new File(sb.toString()); } } - this.file = saveFile; + this.file = saveDir; this.file.mkdirs(); } private final File file; - public void saveChunk(Chunk chunk) + public boolean saveChunk(Chunk chunk) { StringBuilder fileLocBuilder = new StringBuilder(this.file.getPath()) + .append('/').append(chunk.dimension) .append('/').append(chunk.chunkX) .append('/').append(chunk.chunkZ); File chunkDir = new File(fileLocBuilder.toString()); - chunkDir.mkdirs(); + chunkDir.mkdirs(); // create directory for file if it does not exist + + // format: ////.sod File chunkFile = new File(fileLocBuilder.append('/').append(chunk.chunkY).append(".sod").toString()); try { chunkFile.createNewFile(); - BinaryData data = new BinaryData(); - chunk.write(data); - data.write(chunkFile); + BinaryData data = new BinaryData(); // create new empty binary data + chunk.write(data); // write the chunk info to the binary data + return data.write(chunkFile); // write the data to the file, return whether an io exception occurred } catch (IOException e) { e.printStackTrace(); + return false; // io exception = chunk writing failed at some point } } - public Chunk readChunk(int chunkX, int chunkY, int chunkZ) + public Chunk readChunk(int chunkX, int chunkY, int chunkZ, int dimension) { + // format: ////.sod File chunkFile = new File(new StringBuilder(this.file.getPath()) + .append('/').append(dimension) .append('/').append(chunkX) .append('/').append(chunkZ) .append('/').append(chunkY).append(".sod").toString()); @@ -62,12 +70,60 @@ public final class LitecraftSave { BinaryData data = BinaryData.read(chunkFile); - Chunk result = new Chunk(chunkX, chunkY, chunkZ); - result.read(data); + Chunk result = new Chunk(chunkX, chunkY, chunkZ, dimension); // create chunk + result.read(data); // load the chunk data we have just read into the chunk return result; } else return null; } + public World getWorldOrCreate(Dimension dim) + { + File worldFile = new File(this.file.getPath() + "/global_data.sod"); + + if (worldFile.isFile()) // load world + { + BinaryData data = BinaryData.read(worldFile); // read data from the world file + DataSection properties = data.get("properties"); // get the properties data section from the data that we have just read + long seed = 0; // default seed if we cannot read it is 0 + + try // try read the seed from the file + { + seed = properties.readLong(0); // seed is at index 0 + } + catch (Throwable e) + { + e.printStackTrace(); + } + + World world = new World(seed, 2, dim, this); // create new world with seed read from file or 0, if it could not be read + world.spawnPlayer(); // spawn player in world + return world; + } + else // create world + { + long seed = new Random().nextLong(); + + try + { + worldFile.createNewFile(); // create world file + BinaryData data = new BinaryData(); // create empty binary data + DataSection properties = new DataSection(); // create empty data section for properties + properties.writeLong(seed); // write seed at index 0 + data.put("properties", properties); // add properties section + data.write(worldFile); // write to file + } + catch (IOException e) + { + // If this fails the world seed will not be consistent across saves + e.printStackTrace(); + } + + World world = new World(seed, 2, dim, this); // create new world with generated seed + world.spawnPlayer(); // spawn player in world + return world; + } + } + private static final String SAVE_DIR = "./saves/"; } diff --git a/src/main/java/com/github/halotroop/litecraft/types/block/Block.java b/src/main/java/com/github/halotroop/litecraft/types/block/Block.java index 076863b..7c4ebb6 100644 --- a/src/main/java/com/github/halotroop/litecraft/types/block/Block.java +++ b/src/main/java/com/github/halotroop/litecraft/types/block/Block.java @@ -7,10 +7,6 @@ import com.github.hydos.ginger.engine.render.models.TexturedModel; public class Block { - public static final Block AIR = new Block(new Properties("air").visible(false)); - public static final Block DIRT = new Block("block/cubes/soil/dirt.png", new Properties("dirt")); - public static final Block STONE = new Block("block/cubes/stone/basic/gneiss.png", new Properties("stone")); - public static class Properties { // add properties to this builder! private boolean visible = true; diff --git a/src/main/java/com/github/halotroop/litecraft/types/block/Blocks.java b/src/main/java/com/github/halotroop/litecraft/types/block/Blocks.java new file mode 100644 index 0000000..3636fc1 --- /dev/null +++ b/src/main/java/com/github/halotroop/litecraft/types/block/Blocks.java @@ -0,0 +1,10 @@ +package com.github.halotroop.litecraft.types.block; + +import com.github.halotroop.litecraft.types.block.Block.Properties; + +public final class Blocks +{ + public static final Block AIR = new Block(new Properties("air").visible(false)); + public static final Block DIRT = new Block("block/cubes/soil/dirt.png", new Properties("dirt")); + public static final Block STONE = new Block("block/cubes/stone/basic/gneiss.png", new Properties("stone")); +} diff --git a/src/main/java/com/github/halotroop/litecraft/world/Chunk.java b/src/main/java/com/github/halotroop/litecraft/world/Chunk.java index 8207264..113876f 100644 --- a/src/main/java/com/github/halotroop/litecraft/world/Chunk.java +++ b/src/main/java/com/github/halotroop/litecraft/world/Chunk.java @@ -30,8 +30,9 @@ public class Chunk implements BlockAccess, WorldGenConstants, DataStorage public final int chunkX, chunkY, chunkZ; public final int chunkStartX, chunkStartY, chunkStartZ; private boolean fullyGenerated = false; + public final int dimension; - public Chunk(int chunkX, int chunkY, int chunkZ) + public Chunk(int chunkX, int chunkY, int chunkZ, int dimension) { renderList = new ArrayList(); this.chunkX = chunkX; @@ -40,6 +41,7 @@ public class Chunk implements BlockAccess, WorldGenConstants, DataStorage this.chunkStartX = chunkX << POS_SHIFT; this.chunkStartY = chunkY << POS_SHIFT; this.chunkStartZ = chunkZ << POS_SHIFT; + this.dimension = dimension; } public boolean doRender() diff --git a/src/main/java/com/github/halotroop/litecraft/world/World.java b/src/main/java/com/github/halotroop/litecraft/world/World.java index 88adfdc..5545b74 100644 --- a/src/main/java/com/github/halotroop/litecraft/world/World.java +++ b/src/main/java/com/github/halotroop/litecraft/world/World.java @@ -1,7 +1,9 @@ package com.github.halotroop.litecraft.world; import java.util.Random; +import java.util.function.LongConsumer; +import com.github.halotroop.litecraft.save.LitecraftSave; import com.github.halotroop.litecraft.types.block.Block; import com.github.halotroop.litecraft.world.block.BlockRenderer; import com.github.halotroop.litecraft.world.gen.*; @@ -18,31 +20,48 @@ public class World implements BlockAccess, WorldGenConstants private final WorldModifier[] worldModifiers; private final ChunkGenerator chunkGenerator; private final BlockAccess genBlockAccess; + private final LitecraftSave save; private final long seed; + private final int dimension; public Player player; - public World(long seed, int size, Dimension dim) + // This will likely become the main public constructor after we add dynamic chunkloading + private World(long seed, Dimension dim, LitecraftSave save) { this.chunks = new Long2ObjectArrayMap<>(); this.seed = seed; this.chunkGenerator = dim.createChunkGenerator(seed); this.worldModifiers = dim.getWorldModifierArray(); this.genBlockAccess = new GenWorld(this); + this.save = save; + this.dimension = dim.id; + } + + public void spawnPlayer() + { + TexturedModel dirtModel = ModelLoader.loadGenericCube("block/cubes/soil/dirt.png"); + this.player = new Player(dirtModel, new Vector3f(0, 0, -3), 0, 180f, 0, new Vector3f(0.2f, 0.2f, 0.2f)); + } + + // this constructor will likely not be neccesary when we have dynamic chunkloading + public World(long seed, int size, Dimension dim, LitecraftSave save) + { + this(seed, dim, save); for (int i = (0 - (size/2)); i < (size/2); i++) for (int k = (0 - (size/2)); k < (size/2); k++) for (int y = -2; y < 0; ++y) this.getChunk(i, y, k).setRender(true); - - TexturedModel dirtModel = ModelLoader.loadGenericCube("block/cubes/soil/dirt.png"); - this.player = new Player(dirtModel, new Vector3f(0, 0, -3), 0, 180f, 0, new Vector3f(0.2f, 0.2f, 0.2f)); } public Chunk getChunk(int chunkX, int chunkY, int chunkZ) { - Chunk chunk = this.chunks.computeIfAbsent(posHash(chunkX, chunkY, chunkZ), pos -> this.chunkGenerator.generateChunk(chunkX, chunkY, chunkZ)); + Chunk chunk = this.chunks.computeIfAbsent(posHash(chunkX, chunkY, chunkZ), pos -> { + Chunk readChunk = save.readChunk(chunkX, chunkY, chunkZ, this.dimension); + return readChunk == null ? this.chunkGenerator.generateChunk(chunkX, chunkY, chunkZ) : readChunk; + }); if (chunk.isFullyGenerated()) return chunk; @@ -51,6 +70,23 @@ public class World implements BlockAccess, WorldGenConstants return chunk; } + /** + * @return whether the chunk was unloaded without errors. Will often, but not always, be equal to whether the chunk was already in memory. + */ + public boolean unloadChunk(int chunkX, int chunkY, int chunkZ) + { + long posHash = posHash(chunkX, chunkY, chunkZ); + Chunk chunk = this.chunks.get(posHash); + + // If the chunk is not in memory, it does not need to be unloaded + if (chunk == null) return false; + + // Otherwise save the chunk + boolean result = this.save.saveChunk(chunk); + this.chunks.remove(posHash); + return result; + } + private void populateChunk(int chunkX, int chunkY, int chunkZ, int chunkStartX, int chunkStartY, int chunkStartZ) { Random rand = new Random(this.seed + 5828671L * (long) chunkX + -47245139L * (long) chunkY + 8972357 * (long) chunkZ); @@ -90,4 +126,16 @@ public class World implements BlockAccess, WorldGenConstants public void render(BlockRenderer blockRenderer) { this.chunks.forEach((pos, chunk) -> chunk.render(blockRenderer)); } + + public void unloadAllChunks() + { + LongList chunkPositions = new LongArrayList(); + + this.chunks.forEach((pos, chunk) -> { // for every chunk in memory + chunkPositions.add((long) pos); // add pos to chunk positions list for removal later + this.save.saveChunk(chunk); // save chunk + }); + + chunkPositions.forEach((LongConsumer) (pos -> this.chunks.remove(pos))); // remove all chunks + } } diff --git a/src/main/java/com/github/halotroop/litecraft/world/gen/Dimension.java b/src/main/java/com/github/halotroop/litecraft/world/gen/Dimension.java index c6e6b08..501bcbe 100644 --- a/src/main/java/com/github/halotroop/litecraft/world/gen/Dimension.java +++ b/src/main/java/com/github/halotroop/litecraft/world/gen/Dimension.java @@ -2,13 +2,20 @@ package com.github.halotroop.litecraft.world.gen; import java.util.*; -import com.github.halotroop.litecraft.world.Chunk; +import it.unimi.dsi.fastutil.ints.*; public abstract class Dimension { public List worldModifiers = new ArrayList<>(); + public final int id; - public Dimension addWorldModifier(WorldModifier modifier) + public Dimension(int id) + { + this.id = id; + ID_TO_DIMENSION.put(id, this); + } + + public Dimension addWorldModifier(WorldModifier modifier) { this.worldModifiers.add(modifier); return this; @@ -20,5 +27,5 @@ public abstract class Dimension public abstract T createChunkGenerator(long seed); - public static final Dimension OVERWORLD = new OverworldDimension(); + private static final Int2ObjectMap> ID_TO_DIMENSION = new Int2ObjectArrayMap<>(); } diff --git a/src/main/java/com/github/halotroop/litecraft/world/gen/Dimensions.java b/src/main/java/com/github/halotroop/litecraft/world/gen/Dimensions.java new file mode 100644 index 0000000..1490943 --- /dev/null +++ b/src/main/java/com/github/halotroop/litecraft/world/gen/Dimensions.java @@ -0,0 +1,6 @@ +package com.github.halotroop.litecraft.world.gen; + +public final class Dimensions +{ + public static final Dimension OVERWORLD = new OverworldDimension(0); +} diff --git a/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldChunkGenerator.java b/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldChunkGenerator.java index 5abb14d..c5527b3 100644 --- a/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldChunkGenerator.java +++ b/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldChunkGenerator.java @@ -2,23 +2,25 @@ package com.github.halotroop.litecraft.world.gen; import java.util.Random; -import com.github.halotroop.litecraft.types.block.Block; +import com.github.halotroop.litecraft.types.block.*; import com.github.halotroop.litecraft.util.noise.OctaveSimplexNoise; import com.github.halotroop.litecraft.world.Chunk; public class OverworldChunkGenerator implements ChunkGenerator, WorldGenConstants { - public OverworldChunkGenerator(long seed) + public OverworldChunkGenerator(long seed, int dimension) { this.noise = new OctaveSimplexNoise(new Random(seed), 3, 250.0, 35.0, 10.0); + this.dimension = dimension; } private final OctaveSimplexNoise noise; + private final int dimension; @Override public Chunk generateChunk(int chunkX, int chunkY, int chunkZ) { - Chunk chunk = new Chunk(chunkX, chunkY, chunkZ); + Chunk chunk = new Chunk(chunkX, chunkY, chunkZ, this.dimension); for (int x = 0; x < CHUNK_SIZE; ++x) { double totalX = x + chunk.chunkStartX; @@ -28,15 +30,15 @@ public class OverworldChunkGenerator implements ChunkGenerator, WorldGenConstant for (int y = 0; y < CHUNK_SIZE; ++y) { int totalY = chunk.chunkStartY + y; - Block block = Block.AIR; + Block block = Blocks.AIR; if (totalY < height - 3) { - block = Block.DIRT; + block = Blocks.DIRT; } else if (totalY < height) { - block = Block.STONE; + block = Blocks.STONE; } chunk.setBlock(x, y, z, block); diff --git a/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldDimension.java b/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldDimension.java index cec8673..3e5c242 100644 --- a/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldDimension.java +++ b/src/main/java/com/github/halotroop/litecraft/world/gen/OverworldDimension.java @@ -2,9 +2,14 @@ package com.github.halotroop.litecraft.world.gen; public class OverworldDimension extends Dimension { + public OverworldDimension(int id) + { + super(id); + } + @Override public OverworldChunkGenerator createChunkGenerator(long seed) { - return new OverworldChunkGenerator(seed); + return new OverworldChunkGenerator(seed, this.id); } }