world saving and loading integrated into the game
parent
bd18284c40
commit
3d37dec619
|
@ -7,6 +7,9 @@
|
|||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# run
|
||||
saves/
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: <save dir>/<dim>/<chunkX>/<chunkZ>/<chunkY>.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: <save dir>/<dim>/<chunkX>/<chunkZ>/<chunkY>.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/";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"));
|
||||
}
|
|
@ -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<BlockEntity>();
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T extends ChunkGenerator>
|
||||
{
|
||||
public List<WorldModifier> 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<T> addWorldModifier(WorldModifier modifier)
|
||||
{
|
||||
this.worldModifiers.add(modifier);
|
||||
return this;
|
||||
|
@ -20,5 +27,5 @@ public abstract class Dimension<T extends ChunkGenerator>
|
|||
|
||||
public abstract T createChunkGenerator(long seed);
|
||||
|
||||
public static final Dimension OVERWORLD = new OverworldDimension();
|
||||
private static final Int2ObjectMap<Dimension<?>> ID_TO_DIMENSION = new Int2ObjectArrayMap<>();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.github.halotroop.litecraft.world.gen;
|
||||
|
||||
public final class Dimensions
|
||||
{
|
||||
public static final Dimension<OverworldChunkGenerator> OVERWORLD = new OverworldDimension(0);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -2,9 +2,14 @@ package com.github.halotroop.litecraft.world.gen;
|
|||
|
||||
public class OverworldDimension extends Dimension<OverworldChunkGenerator>
|
||||
{
|
||||
public OverworldDimension(int id)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OverworldChunkGenerator createChunkGenerator(long seed)
|
||||
{
|
||||
return new OverworldChunkGenerator(seed);
|
||||
return new OverworldChunkGenerator(seed, this.id);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue