Reintroduce saving / loading

main
copygirl 4 years ago
parent 94bd99a478
commit f9339ead77
  1. 7
      src/EscapeMenuMultiplayer.cs
  2. 67
      src/EscapeMenuWorld.cs
  3. 82
      src/Network/WorldSave.cs
  4. 6
      src/Utility/Extensions.cs
  5. 19
      src/World.cs

@ -141,7 +141,8 @@ public class EscapeMenuMultiplayer : Container
IntegratedServer = null;
client.Disconnect();
this.GetWorld().Clear();
this.GetWorld().ClearPlayers();
this.GetWorld().ClearBlocks();
}
if (client.Status == ConnectionStatus.Disconnected) {
@ -155,8 +156,8 @@ public class EscapeMenuMultiplayer : Container
}
client.Connect(address, port);
} else {
client.Disconnect();
this.GetWorld().Clear();
this.GetWorld().ClearPlayers();
this.GetWorld().ClearBlocks();
}
}
}

@ -1,10 +1,6 @@
using System;
using System.Text;
using Godot;
using Path = System.IO.Path;
using File = System.IO.File;
using Directory = System.IO.Directory;
using System.Linq;
using static Godot.NetworkedMultiplayerPeer;
public class EscapeMenuWorld : CenterContainer
@ -43,10 +39,9 @@ public class EscapeMenuWorld : CenterContainer
SaveAsButton.Text = "Save World As...";
SaveFileDialog.GetOk().Text = "Save";
var worldsFolder = OS.GetUserDataDir() + "/worlds/";
Directory.CreateDirectory(worldsFolder);
SaveFileDialog.CurrentPath = worldsFolder;
LoadFileDialog.CurrentPath = worldsFolder;
new Directory().MakeDirRecursive(WorldSave.WORLDS_DIR);
SaveFileDialog.CurrentPath = WorldSave.WORLDS_DIR;
LoadFileDialog.CurrentPath = WorldSave.WORLDS_DIR;
this.GetClient().StatusChanged += OnStatusChanged;
}
@ -85,17 +80,16 @@ public class EscapeMenuWorld : CenterContainer
private void _on_SaveFileDialog_file_selected(string path)
{
// var server = this.GetClient().GetNode<IntegratedServer>(nameof(IntegratedServer)).Server;
// var save = Save.CreateFromWorld(server, _playtime);
// save.WriteToFile(path + ".tmp");
// File.Delete(path); // TODO: In later .NET, there is a File.Move(source, dest, overwrite).
// File.Move(path + ".tmp", path);
// _currentWorld = path;
// FilenameLabel.Text = Path.GetFileName(path);
// LastSavedLabel.Text = save.LastSaved.ToString("yyyy-MM-dd HH:mm");
// QuickSaveButton.Visible = true;
// SaveAsButton.Text = "Save As...";
var server = this.GetClient().GetNode<IntegratedServer>(nameof(IntegratedServer)).Server;
var save = new WorldSave { Playtime = _playtime };
save.WriteDataFromWorld(server.GetWorld());
save.WriteToFile(path);
_currentWorld = path;
FilenameLabel.Text = System.IO.Path.GetFileName(path);
LastSavedLabel.Text = save.LastSaved.ToString("yyyy-MM-dd HH:mm");
QuickSaveButton.Visible = true;
SaveAsButton.Text = "Save As...";
}
private void _on_LoadFrom_pressed()
@ -106,25 +100,20 @@ public class EscapeMenuWorld : CenterContainer
private void _on_LoadFileDialog_file_selected(string path)
{
// var server = this.GetClient().GetNode<IntegratedServer>(nameof(IntegratedServer)).Server;
// var save = Save.ReadFromFile(path);
// // Clear out all objects that have a SaveAttribute.
// var objectsToRemove = server.Objects.Select(x => x.Item2)
// .Where(x => SaveRegistry.GetOrNull(x.GetType()) != null).ToArray();
// foreach (var obj in objectsToRemove) obj.RemoveFromParent();
// // Reset players' positions.
// foreach (var (id, player) in server.Players)
// player.RPC(new []{ id }, player.ResetPosition, Vector2.Zero);
// save.AddToWorld(server);
// _playtime = save.Playtime;
// _currentWorld = path;
// FilenameLabel.Text = Path.GetFileName(path);
// LastSavedLabel.Text = save.LastSaved.ToString("yyyy-MM-dd HH:mm");
// QuickSaveButton.Visible = true;
// SaveAsButton.Text = "Save As...";
var server = this.GetClient().GetNode<IntegratedServer>(nameof(IntegratedServer)).Server;
var save = WorldSave.ReadFromFile(path);
// Reset players' positions.
foreach (var player in server.GetWorld().Players)
player.RpcId(player.NetworkID, nameof(LocalPlayer.ResetPosition), Vector2.Zero);
save.ReadDataIntoWorld(server.GetWorld());
_playtime = save.Playtime;
_currentWorld = path;
FilenameLabel.Text = System.IO.Path.GetFileName(path);
LastSavedLabel.Text = save.LastSaved.ToString("yyyy-MM-dd HH:mm");
QuickSaveButton.Visible = true;
SaveAsButton.Text = "Save As...";
}
}

@ -0,0 +1,82 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Godot;
using File = System.IO.File;
public class WorldSave
{
public const string FILE_EXT = ".yf5";
public const int MAGIC_NUMBER = 0x59463573; // "YF5s"
public const int LATEST_VERSION = 0;
public static readonly string WORLDS_DIR = OS.GetUserDataDir() + "/worlds/";
public DateTime LastSaved { get; private set; }
public int Version { get; private set; } = LATEST_VERSION;
public TimeSpan Playtime { get; set; } = TimeSpan.Zero;
public List<(BlockPos, Color, bool)> Blocks { get; private set; }
public static WorldSave ReadFromFile(string path)
{
var save = new WorldSave { LastSaved = File.GetLastAccessTime(path) };
using (var stream = File.OpenRead(path)) {
using (var reader = new BinaryReader(stream)) {
var magic = reader.ReadInt32();
if (magic != MAGIC_NUMBER) throw new IOException(
$"Magic number does not match ({magic:X8} != {MAGIC_NUMBER:X8})");
// TODO: See how to support multiple versions.
save.Version = reader.ReadUInt16();
if (save.Version != LATEST_VERSION) throw new IOException(
$"Version does not match ({save.Version} != {LATEST_VERSION})");
save.Playtime = TimeSpan.FromSeconds(reader.ReadUInt32());
var numBlocks = reader.ReadInt32();
save.Blocks = new List<(BlockPos, Color, bool)>();
for (var i = 0; i < numBlocks; i++)
save.Blocks.Add((new BlockPos(reader.ReadInt32(), reader.ReadInt32()),
new Color(reader.ReadInt32()),
reader.ReadBoolean()));
}
}
return save;
}
public void WriteToFile(string path)
{
using (var stream = File.OpenWrite(path + ".tmp")) {
using (var writer = new BinaryWriter(stream)) {
writer.Write(MAGIC_NUMBER);
writer.Write((ushort)LATEST_VERSION);
writer.Write((uint)Playtime.TotalSeconds);
writer.Write(Blocks.Count);
foreach (var (position, color, unbreakable) in Blocks) {
writer.Write(position.X);
writer.Write(position.Y);
writer.Write(color.ToRgba32());
writer.Write(unbreakable);
}
}
}
new Godot.Directory().Rename(path + ".tmp", path);
LastSaved = File.GetLastWriteTime(path);
}
public void WriteDataFromWorld(World world)
=> Blocks = world.Blocks.Select(block => (block.Position, block.Color, block.Unbreakable)).ToList();
public void ReadDataIntoWorld(World world)
{
world.Rpc(nameof(World.ClearBlocks));
foreach (var (position, color, unbreakable) in Blocks)
world.Rpc(nameof(World.SpawnBlock), position.X, position.Y, color, unbreakable);
}
}

@ -18,6 +18,12 @@ public static class Extensions
=> node.GetGame() as Server;
public static World GetWorld(this Node node)
=> node.GetGame().GetNode<World>("World");
public static void RemoveFromParent(this Node node)
{
node.GetParent().RemoveChild(node);
node.QueueFree();
}
}
public interface IInitializable

@ -28,22 +28,15 @@ public class World : Node
=> PlayerContainer.GetChildren().Cast<Player>();
public Player GetPlayer(int networkID)
=> PlayerContainer.GetNode<Player>(networkID.ToString());
public void ClearPlayers()
{ foreach (var player in Players) player.RemoveFromParent(); }
public IEnumerable<Block> Blocks
=> BlockContainer.GetChildren().Cast<Block>();
public Block GetBlockAt(BlockPos position)
=> BlockContainer.GetNodeOrNull<Block>(position.ToString());
public void Clear()
{
foreach (var player in Players) {
BlockContainer.RemoveChild(player);
player.QueueFree();
}
foreach (var node in BlockContainer.GetChildren().Cast<Node>()) {
BlockContainer.RemoveChild(node);
node.QueueFree();
}
}
[PuppetSync] public void ClearBlocks()
{ foreach (var block in Blocks) block.RemoveFromParent(); }
[PuppetSync]

Loading…
Cancel
Save