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

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

@ -28,22 +28,15 @@ public class World : Node
=> PlayerContainer.GetChildren().Cast<Player>(); => PlayerContainer.GetChildren().Cast<Player>();
public Player GetPlayer(int networkID) public Player GetPlayer(int networkID)
=> PlayerContainer.GetNode<Player>(networkID.ToString()); => 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) public Block GetBlockAt(BlockPos position)
=> BlockContainer.GetNodeOrNull<Block>(position.ToString()); => BlockContainer.GetNodeOrNull<Block>(position.ToString());
[PuppetSync] public void ClearBlocks()
{ foreach (var block in Blocks) block.RemoveFromParent(); }
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] [PuppetSync]

Loading…
Cancel
Save