From cd20b987d41f75376a80a7b3ceea59db55e7bb2c Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 2 Feb 2015 15:52:25 -0700 Subject: [PATCH] Partially implement world persistence This does not account for entities or tile entities, and the seed is not saved because there is no level. TODO: Save levels --- TrueCraft.API/World/IWorld.cs | 1 + TrueCraft.Core/World/Chunk.cs | 29 +++++++++++++------ TrueCraft.Core/World/Region.cs | 51 ++-------------------------------- TrueCraft.Core/World/World.cs | 11 -------- TrueCraft/MultiplayerServer.cs | 12 -------- TrueCraft/Program.cs | 24 ++++++++++++++-- 6 files changed, 46 insertions(+), 82 deletions(-) diff --git a/TrueCraft.API/World/IWorld.cs b/TrueCraft.API/World/IWorld.cs index 23ff9cd..fd024c1 100644 --- a/TrueCraft.API/World/IWorld.cs +++ b/TrueCraft.API/World/IWorld.cs @@ -27,5 +27,6 @@ namespace TrueCraft.API.World void SetBlockLight(Coordinates3D coordinates, byte value); bool IsValidPosition(Coordinates3D position); void Save(); + void Save(string path); } } \ No newline at end of file diff --git a/TrueCraft.Core/World/Chunk.cs b/TrueCraft.Core/World/Chunk.cs index 59ea069..3da4322 100644 --- a/TrueCraft.Core/World/Chunk.cs +++ b/TrueCraft.Core/World/Chunk.cs @@ -209,23 +209,34 @@ namespace TrueCraft.Core.World var chunk = (NbtCompound)Serializer.Serialize(this, tagName, true); var entities = new NbtList("Entities", NbtTagType.Compound); chunk.Add(entities); - // TODO: Save block data + chunk.Add(new NbtByteArray("Blocks", Blocks)); + chunk.Add(new NbtByteArray("Data", Metadata.Data)); + chunk.Add(new NbtByteArray("SkyLight", SkyLight.Data)); + chunk.Add(new NbtByteArray("BlockLight", BlockLight.Data)); + // TODO: Tile entities, entities return chunk; } public void Deserialize(NbtTag value) { - IsModified = true; var chunk = (Chunk)Serializer.Deserialize(value, true); + var tag = (NbtCompound)value; - this.Biomes = chunk.Biomes; - this.HeightMap = chunk.HeightMap; - this.LastUpdate = chunk.LastUpdate; - this.TerrainPopulated = chunk.TerrainPopulated; - this.X = chunk.X; - this.Z = chunk.Z; + Biomes = chunk.Biomes; + HeightMap = chunk.HeightMap; + LastUpdate = chunk.LastUpdate; + TerrainPopulated = chunk.TerrainPopulated; + X = tag["xPos"].IntValue; + Z = tag["zPos"].IntValue; + Blocks = tag["Blocks"].ByteArrayValue; + Metadata = new NibbleArray(); + Metadata.Data = tag["Data"].ByteArrayValue; + BlockLight = new NibbleArray(); + BlockLight.Data = tag["BlockLight"].ByteArrayValue; + SkyLight = new NibbleArray(); + SkyLight.Data = tag["SkyLight"].ByteArrayValue; - // TODO: Load block data + // TODO: Tile entities, entities } } } diff --git a/TrueCraft.Core/World/Region.cs b/TrueCraft.Core/World/Region.cs index 0b43996..caa42c5 100644 --- a/TrueCraft.Core/World/Region.cs +++ b/TrueCraft.Core/World/Region.cs @@ -94,54 +94,8 @@ namespace TrueCraft.Core.World var nbt = new NbtFile(); nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null); var chunk = Chunk.FromNbt(nbt); - Chunks.Add(position, (IChunk)chunk); - break; - default: - throw new InvalidDataException("Invalid compression scheme provided by region file."); - } - } - } - else if (World.ChunkProvider == null) - throw new ArgumentException("The requested chunk is not loaded.", "position"); - else - GenerateChunk(position); - } - return Chunks[position]; - } - } - - /// - /// Retrieves the requested chunk from the region, without using the - /// world generator if it does not exist. - /// - /// The position of the requested local chunk coordinates. - public IChunk GetChunkWithoutGeneration(Coordinates2D position) - { - // TODO: This could use some refactoring - lock (Chunks) - { - if (!Chunks.ContainsKey(position)) - { - if (regionFile != null) - { - // Search the stream for that region - lock (regionFile) - { - var chunkData = GetChunkFromTable(position); - if (chunkData == null) - return null; - regionFile.Seek(chunkData.Item1, SeekOrigin.Begin); - /*int length = */new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32 - int compressionMode = regionFile.ReadByte(); - switch (compressionMode) - { - case 1: // gzip - break; - case 2: // zlib - var nbt = new NbtFile(); - nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null); - var chunk = Chunk.FromNbt(nbt); - Chunks.Add(position, (IChunk)chunk); + Chunks.Add(position, chunk); + Console.WriteLine("Loaded chunk at {0}", chunk.Coordinates); break; default: throw new InvalidDataException("Invalid compression scheme provided by region file."); @@ -209,6 +163,7 @@ namespace TrueCraft.Core.World var chunk = kvp.Value; if (chunk.IsModified) { + Console.WriteLine("Saving modified chunk at {0}", chunk.Coordinates); var data = ((Chunk)chunk).ToNbt(); byte[] raw = data.SaveToBuffer(NbtCompression.ZLib); diff --git a/TrueCraft.Core/World/World.cs b/TrueCraft.Core/World/World.cs index 3549ee5..3048061 100644 --- a/TrueCraft.Core/World/World.cs +++ b/TrueCraft.Core/World/World.cs @@ -81,17 +81,6 @@ namespace TrueCraft.Core.World region.GenerateChunk(new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32)); } - public Chunk GetChunkWithoutGeneration(Coordinates2D coordinates) - { - int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0); - int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0); - - var regionPosition = new Coordinates2D(regionX, regionZ); - if (!Regions.ContainsKey(regionPosition)) return null; - return (Chunk)((Region)Regions[regionPosition]).GetChunkWithoutGeneration( - new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32)); - } - public void SetChunk(Coordinates2D coordinates, Chunk chunk) { int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0); diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index b93bb6b..0e4646d 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -240,14 +240,8 @@ namespace TrueCraft RemoteClient client; lock (ClientLock) client = Clients[i] as RemoteClient; - var sendTimeout = DateTime.Now.AddMilliseconds(100); while (client.PacketQueue.Count != 0) { - if (DateTime.Now > sendTimeout) - { - Console.WriteLine("Send timeout" + DateTime.Now); - break; - } idle = false; try { @@ -284,14 +278,8 @@ namespace TrueCraft Clients.RemoveAt(i); break; } - var receiveTimeout = DateTime.Now.AddMilliseconds(100); while (client.DataAvailable) { - if (DateTime.Now > receiveTimeout) - { - Console.WriteLine("Receive timeout" + DateTime.Now); - break; - } idle = false; var packet = PacketReader.ReadPacket(client.MinecraftStream); LogPacket(packet, true); diff --git a/TrueCraft/Program.cs b/TrueCraft/Program.cs index 6eccf90..4eb248f 100644 --- a/TrueCraft/Program.cs +++ b/TrueCraft/Program.cs @@ -10,6 +10,7 @@ using TrueCraft.API; using TrueCraft.Core.Windows; using System.IO; using TrueCraft.Commands; +using TrueCraft.API.World; namespace TrueCraft { @@ -20,7 +21,19 @@ namespace TrueCraft { // TODO: Make this more flexible var server = new MultiplayerServer(); - server.AddWorld(new World("default", new StandardGenerator())); + IWorld world; + try + { + // TODO: Save and load levels, with seeds and everything + world = World.LoadWorld("world"); + world.ChunkProvider = new StandardGenerator(); + } + catch + { + world = new World("default", new StandardGenerator()); + world.Save("world"); + } + server.AddWorld(world); server.AddLogProvider(new ConsoleLogProvider(LogCategory.Notice | LogCategory.Warning | LogCategory.Error | LogCategory.Debug)); #if DEBUG server.AddLogProvider(new FileLogProvider(new StreamWriter("packets.log", false), LogCategory.Packets)); @@ -29,7 +42,14 @@ namespace TrueCraft server.ChatMessageReceived += HandleChatMessageReceived; server.Start(new IPEndPoint(IPAddress.Any, 25565)); while (true) - Thread.Sleep(1000); + { + Thread.Sleep(1000 * 30); // TODO: Allow users to customize world save interval + foreach (var w in server.Worlds) + { + server.Log(LogCategory.Debug, "Saved world '{0}'", w.Name); + w.Save(); + } + } } static void HandleChatMessageReceived(object sender, ChatMessageEventArgs e)