diff --git a/Commands/building/CmdUndo.cs b/Commands/building/CmdUndo.cs
index 4f29ec9ca..23cf70897 100644
--- a/Commands/building/CmdUndo.cs
+++ b/Commands/building/CmdUndo.cs
@@ -18,6 +18,8 @@
using System;
using System.Globalization;
using System.IO;
+using MCGalaxy.Util;
+
namespace MCGalaxy.Commands
{
public sealed class CmdUndo : Command
@@ -36,29 +38,21 @@ namespace MCGalaxy.Commands
byte b; long seconds = -2; Player who = null; Player.UndoPos Pos; int CurrentPos = 0; bool undoPhysics = false; string whoName = String.Empty;
if (p != null)
p.RedoBuffer.Clear();
-
- if (message == "")
- {
- if (p == null)
- {
- Player.SendMessage(null, "Console doesn't have an undo buffer.");
- return;
+ if (message == "") {
+ if (p == null) {
+ Player.SendMessage(null, "Console doesn't have an undo buffer."); return;
}
message = p.name.ToLower() + " 30";
}
+ string[] parts = message.Split(' ');
- try
- {
- if (message.Split(' ').Length > 1)
- {
- whoName = message.Split(' ')[0];
- who = message.Split(' ')[0].ToLower() == "physics" ? null : Player.Find(message.Split(' ')[0]);
- undoPhysics = message.Split(' ')[0].ToLower() == "physics";
- message = message.Split(' ')[1].ToLower();
-
- }
- else
- {
+ try {
+ if (parts.Length > 1) {
+ whoName = parts[0];
+ who = parts[0].ToLower() == "physics" ? null : Player.Find(parts[0]);
+ undoPhysics = parts[0].ToLower() == "physics";
+ message = parts[1].ToLower();
+ } else {
who = (p == null || message.ToLower() == "physics") ? null : p;
undoPhysics = message.ToLower() == "physics";
}
@@ -68,9 +62,12 @@ namespace MCGalaxy.Commands
seconds = ((message.ToLower() != "all") ? long.Parse(message) : int.MaxValue);
else
seconds = getAllowed(p, message.ToLower());
- }
- catch
- {
+ } catch {
+ if (parts.Length > 1 && parts[1].ToLower() == "update") {
+ UndoFile.UpgradePlayerUndoFiles(whoName);
+ Player.SendMessage(p, "Updated undo files for " + whoName + " to the new binary format.");
+ return;
+ }
Player.SendMessage(p, "Invalid seconds, or you're unable to use /xundo. Using 30 seconds."); //only run if seconds is an invalid number
seconds = 30;
}
@@ -202,10 +199,9 @@ namespace MCGalaxy.Commands
}
bool FoundUser = false;
-
try
{
- undoOfflineHelper(p, whoName, seconds, ref FoundUser);
+ UndoFile.UndoPlayer(p, whoName.ToLower(), seconds, ref FoundUser);
if (FoundUser)
{
@@ -241,85 +237,9 @@ namespace MCGalaxy.Commands
}
return secs;
}
-
- //Fixed by QuantumHive
- public bool undoOffline(string[] fileContent, long seconds, Player p)
- {
-
- Player.UndoPos Pos;
-
- //-1 because the last element in the array is an empty string "" go check Player.SaveUndo() if you wanna know why
- for (int i = (fileContent.Length - 1) / 7; i >= 0; i--)
- {
- try
- {
- string datetime = fileContent[(i * 7) - 3];
- datetime = datetime.Replace('&', ' ');
- DateTime time = DateTime.Parse(datetime, CultureInfo.InvariantCulture);
- time = time.AddSeconds(seconds);
- if (time < DateTime.Now)
- //if (Convert.ToDateTime(fileContent[(i * 7) - 3].Replace('&', ' ')).AddSeconds(seconds) < DateTime.Now)
- return false;
-
- Level foundLevel = Level.FindExact(fileContent[(i * 7) - 7]);
- if (foundLevel != null)
- {
- Pos.mapName = foundLevel.name;
- Pos.x = Convert.ToUInt16(fileContent[(i * 7) - 6]);
- Pos.y = Convert.ToUInt16(fileContent[(i * 7) - 5]);
- Pos.z = Convert.ToUInt16(fileContent[(i * 7) - 4]);
-
- Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
-
- if (Pos.type == Convert.ToByte(fileContent[(i * 7) - 1]) ||
- Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava ||
- Pos.type == Block.grass)
- {
- Pos.newtype = Convert.ToByte(fileContent[(i * 7) - 2]);
- Pos.timePlaced = DateTime.Now;
-
- foundLevel.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
- if (p != null)
- p.RedoBuffer.Add(Pos);
- }
- }
- }
- catch (Exception e) { }
- }
-
- return true;
- }
- private void undoOfflineHelper(Player p, string whoName, long seconds, ref bool FoundUser)
- {
- DirectoryInfo di;
- string[] fileContent;
-
- if (p != null)
- p.RedoBuffer.Clear();
-
- if (Directory.Exists("extra/undo/" + whoName.ToLower()))
- {
- di = new DirectoryInfo("extra/undo/" + whoName.ToLower());
-
- for (int i = di.GetFiles("*.undo").Length - 1; i >= 0; i--)
- {
- fileContent = File.ReadAllText("extra/undo/" + whoName.ToLower() + "/" + i + ".undo").Split();
- if (!undoOffline(fileContent, seconds, p)) break;
- }
- FoundUser = true;
- }
-
- if (Directory.Exists("extra/undoPrevious/" + whoName.ToLower()))
- {
- di = new DirectoryInfo("extra/undoPrevious/" + whoName.ToLower());
-
- for (int i = di.GetFiles("*.undo").Length - 1; i >= 0; i--)
- {
- fileContent = File.ReadAllText("extra/undoPrevious/" + whoName.ToLower() + "/" + i + ".undo").Split();
- if (!undoOffline(fileContent, seconds, p)) break;
- }
- FoundUser = true;
- }
+
+ private void undoOfflineHelper(Player p, string whoName, long seconds, ref bool FoundUser) {
+ UndoFile.UndoPlayer(p, whoName.ToLower(), seconds, ref FoundUser);
}
public override void Help(Player p)
diff --git a/MCGalaxy_.csproj b/MCGalaxy_.csproj
index 115328a66..127d45fe4 100644
--- a/MCGalaxy_.csproj
+++ b/MCGalaxy_.csproj
@@ -443,8 +443,11 @@
-
+
+
+
+
@@ -727,6 +730,7 @@
+
diff --git a/Player/Player.cs b/Player/Player.cs
index a75bea55e..935409342 100644
--- a/Player/Player.cs
+++ b/Player/Player.cs
@@ -26,6 +26,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using MCGalaxy.Drawing;
using MCGalaxy.SQL;
+using MCGalaxy.Util;
namespace MCGalaxy {
public sealed partial class Player : IDisposable {
@@ -2752,35 +2753,14 @@ level.Unload();
}
}
- public void SaveUndo() {
- SaveUndo(this);
- }
+ public void SaveUndo() { SaveUndo(this); }
+
public static void SaveUndo(Player p) {
- if ( p == null || p.UndoBuffer == null || p.UndoBuffer.Count < 1 ) return;
try {
- if ( !Directory.Exists("extra/undo") ) Directory.CreateDirectory("extra/undo");
- if ( !Directory.Exists("extra/undoPrevious") ) Directory.CreateDirectory("extra/undoPrevious");
- DirectoryInfo di = new DirectoryInfo("extra/undo");
- if ( di.GetDirectories("*").Length >= Server.totalUndo ) {
- Directory.Delete("extra/undoPrevious", true);
- Directory.Move("extra/undo", "extra/undoPrevious");
- Directory.CreateDirectory("extra/undo");
- }
-
- if ( !Directory.Exists("extra/undo/" + p.name.ToLower()) ) Directory.CreateDirectory("extra/undo/" + p.name.ToLower());
- di = new DirectoryInfo("extra/undo/" + p.name.ToLower());
- int number = di.GetFiles("*.undo").Length;
- File.Create("extra/undo/" + p.name.ToLower() + "/" + number + ".undo").Dispose();
- using ( StreamWriter w = File.CreateText("extra/undo/" + p.name.ToLower() + "/" + number + ".undo") ) {
- foreach ( UndoPos uP in p.UndoBuffer.ToList() ) {
- w.Write(uP.mapName + " " +
- uP.x + " " + uP.y + " " + uP.z + " " +
- uP.timePlaced.ToString(CultureInfo.InvariantCulture).Replace(' ', '&') + " " +
- uP.type + " " + uP.newtype + " ");
- }
- }
+ UndoFile.SaveUndo(p);
+ } catch (Exception e) {
+ Server.s.Log("Error saving undo data for " + p.name + "!"); Server.ErrorLog(e);
}
- catch ( Exception e ) { Server.s.Log("Error saving undo data for " + p.name + "!"); Server.ErrorLog(e); }
}
public void Dispose() {
diff --git a/Player/Undo/UndoFile.cs b/Player/Undo/UndoFile.cs
new file mode 100644
index 000000000..ea56c1324
--- /dev/null
+++ b/Player/Undo/UndoFile.cs
@@ -0,0 +1,142 @@
+/*
+ Copyright 2015 MCGalaxy
+
+ Dual-licensed under the Educational Community License, Version 2.0 and
+ the GNU General Public License, Version 3 (the "Licenses"); you may
+ not use this file except in compliance with the Licenses. You may
+ obtain a copy of the Licenses at
+
+ http://www.opensource.org/licenses/ecl2.php
+ http://www.gnu.org/licenses/gpl-3.0.html
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the Licenses are distributed on an "AS IS"
+ BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the Licenses for the specific language governing
+ permissions and limitations under the Licenses.
+ */
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MCGalaxy.Util {
+
+ public abstract class UndoFile {
+
+ protected const string undoDir = "extra/undo", prevUndoDir = "extra/undoPrevious";
+ public static UndoFile OldFormat = new UndoFileText();
+ public static UndoFile NewFormat = new UndoFileBin();
+
+ protected abstract void SaveUndoData(List buffer, string path);
+
+ protected abstract void ReadUndoData(List buffer, string path);
+
+ protected abstract bool UndoEntry(Player p, string path, long seconds);
+
+ protected abstract bool HighlightEntry(Player p, string path, long seconds);
+
+ protected abstract string Extension { get; }
+
+ public static void SaveUndo(Player p) {
+ if( p == null || p.UndoBuffer == null || p.UndoBuffer.Count < 1) return;
+
+ CreateDefaultDirectories();
+ if (Directory.GetDirectories(undoDir).Length >= Server.totalUndo) {
+ Directory.Delete(prevUndoDir, true);
+ Directory.Move(undoDir, prevUndoDir);
+ Directory.CreateDirectory(undoDir);
+ }
+
+ string playerDir = Path.Combine(undoDir, p.name.ToLower());
+ if (!Directory.Exists(playerDir))
+ Directory.CreateDirectory(playerDir);
+
+ int numFiles = Directory.GetFiles(playerDir).Length;
+ string path = Path.Combine(playerDir, numFiles + NewFormat.Extension);
+ NewFormat.SaveUndoData(p.UndoBuffer, path);
+ }
+
+ public static void UndoPlayer(Player p, string targetName, long seconds, ref bool FoundUser) {
+ if (p != null)
+ p.RedoBuffer.Clear();
+ FilterEntries(p, undoDir, targetName, seconds, false, ref FoundUser);
+ FilterEntries(p, prevUndoDir, targetName, seconds, false, ref FoundUser);
+ }
+
+ public static void HighlightPlayer(Player p, string targetName, long seconds, ref bool FoundUser) {
+ FilterEntries(p, undoDir, targetName, seconds, true, ref FoundUser);
+ FilterEntries(p, prevUndoDir, targetName, seconds, true, ref FoundUser);
+ }
+
+ static void FilterEntries(Player p, string dir, string name, long seconds, bool highlight, ref bool FoundUser) {
+ string path = Path.Combine(dir, name);
+ if (!Directory.Exists(path))
+ return;
+ string[] files = Directory.GetFiles(path);
+ Array.Sort(files, CompareFiles);
+
+ for (int i = files.Length - 1; i >= 0; i--) {
+ path = files[i];
+ string file = Path.GetFileName(path);
+ if (file.Length == 0 || file[0] < '0' || file[0] > '9')
+ continue;
+
+ UndoFile format = null;
+ if (path.EndsWith(OldFormat.Extension)) format = OldFormat;
+ if (path.EndsWith(NewFormat.Extension)) format = NewFormat;
+ if (format == null) continue;
+
+ if (highlight) {
+ if (!format.HighlightEntry(p, path, seconds)) break;
+ } else {
+ if (!format.UndoEntry(p, path, seconds)) break;
+ }
+ }
+ FoundUser = true;
+ }
+
+ static int CompareFiles(string a, string b) {
+ int aNumEnd = a.IndexOf('.'), bNumEnd = b.IndexOf('.');
+ if (aNumEnd < 0 || bNumEnd < 0) return a.CompareTo(b);
+
+ int aNum, bNum;
+ if (!int.TryParse(a.Substring(0, aNumEnd), out aNum) ||
+ !int.TryParse(b.Substring(0, bNumEnd), out bNum))
+ return a.CompareTo(b);
+ return aNum.CompareTo(bNum);
+ }
+
+ public static void CreateDefaultDirectories() {
+ if (!Directory.Exists(undoDir))
+ Directory.CreateDirectory(undoDir);
+ if (!Directory.Exists(prevUndoDir))
+ Directory.CreateDirectory(prevUndoDir);
+ }
+
+ public static void UpgradePlayerUndoFiles(string name) {
+ UpgradeFiles(undoDir, name);
+ UpgradeFiles(prevUndoDir, name);
+ }
+
+ static void UpgradeFiles(string dir, string name) {
+ string path = Path.Combine(dir, name);
+ if (!Directory.Exists(path))
+ return;
+ string[] files = Directory.GetFiles(path);
+ List buffer = new List();
+
+ for (int i = 0; i < files.Length; i++) {
+ path = files[i];
+ if (!path.EndsWith(OldFormat.Extension))
+ continue;
+ buffer.Clear();
+ OldFormat.ReadUndoData(buffer, path);
+
+ string newPath = Path.ChangeExtension(path, NewFormat.Extension);
+ NewFormat.SaveUndoData(buffer, newPath);
+ File.Delete(path);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Player/Undo/UndoFileBin.cs b/Player/Undo/UndoFileBin.cs
new file mode 100644
index 000000000..5d1b2278f
--- /dev/null
+++ b/Player/Undo/UndoFileBin.cs
@@ -0,0 +1,218 @@
+/*
+ Copyright 2015 MCGalaxy
+
+ Dual-licensed under the Educational Community License, Version 2.0 and
+ the GNU General Public License, Version 3 (the "Licenses"); you may
+ not use this file except in compliance with the Licenses. You may
+ obtain a copy of the Licenses at
+
+ http://www.opensource.org/licenses/ecl2.php
+ http://www.gnu.org/licenses/gpl-3.0.html
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the Licenses are distributed on an "AS IS"
+ BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the Licenses for the specific language governing
+ permissions and limitations under the Licenses.
+ */
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace MCGalaxy.Util {
+
+ public sealed class UndoFileBin : UndoFile {
+
+ protected override string Extension { get { return ".unbin"; } }
+
+ protected override void SaveUndoData(List buffer, string path) {
+ using (FileStream fs = File.Create(path)) {
+ BinaryWriter w = new BinaryWriter(fs);
+ long entriesPos = 0;
+ ChunkHeader lastChunk = default(ChunkHeader);
+
+ foreach (Player.UndoPos uP in buffer) {
+ int timeDiff = (int)(uP.timePlaced.ToUniversalTime() - lastChunk.BaseTime).TotalSeconds;
+ if (lastChunk.LevelName != uP.mapName || timeDiff > 65535 || lastChunk.Entries == ushort.MaxValue) {
+ WriteChunkEntries(w, lastChunk.Entries, entriesPos);
+ lastChunk = WriteEmptyChunk(w, uP, ref entriesPos);
+ }
+
+ w.Write((ushort)timeDiff);
+ w.Write(uP.x); w.Write(uP.y); w.Write(uP.z);
+ w.Write(uP.type);
+ w.Write((byte)0); // block definitions placeholder
+ w.Write(uP.newtype);
+ w.Write((byte)0); // block definitions placeholder
+ lastChunk.Entries++;
+ }
+ if (lastChunk.Entries > 0)
+ WriteChunkEntries(w, lastChunk.Entries, entriesPos);
+ }
+ }
+
+ protected override void ReadUndoData(List buffer, string path) {
+ DateTime now = DateTime.Now;
+ Player.UndoPos Pos;
+ using (Stream fs = File.OpenRead(path))
+ using (BinaryReader r = new BinaryReader(fs))
+ {
+ int approxEntries = (int)(fs.Length / 12);
+ if (buffer.Capacity < approxEntries)
+ buffer.Capacity = approxEntries;
+ while (fs.Position < fs.Length) {
+ ChunkHeader chunk = ReadHeader(fs, r);
+ Pos.mapName = chunk.LevelName;
+
+ for (int j = 0; j < chunk.Entries; j++ ) {
+ Pos.timePlaced = chunk.BaseTime.AddSeconds(r.ReadUInt16());
+ Pos.x = r.ReadUInt16(); Pos.y = r.ReadUInt16(); Pos.z = r.ReadUInt16();
+ Pos.type = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+ Pos.newtype = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+ buffer.Add(Pos);
+ }
+ }
+ }
+ }
+
+ protected override bool UndoEntry(Player p, string path, long seconds) {
+ List list = new List();
+ DateTime now = DateTime.Now;
+ Player.UndoPos Pos;
+
+ using (Stream fs = File.OpenRead(path))
+ using (BinaryReader r = new BinaryReader(fs))
+ {
+ ReadHeaders(list, r);
+ for (int i = list.Count - 1; i >= 0; i--) {
+ ChunkHeader chunk = list[i];
+ Level lvl;
+ if (!CheckChunk(chunk, now, seconds, p, out lvl))
+ return false;
+ if (lvl == null || lvl != p.level) continue;
+ Pos.mapName = chunk.LevelName;
+ fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
+
+ for (int j = 0; j < chunk.Entries; j++ ) {
+ DateTime time = chunk.BaseTime.AddSeconds(r.ReadUInt16());
+ if (time.AddSeconds(seconds) < now) return false;
+ Pos.x = r.ReadUInt16(); Pos.y = r.ReadUInt16(); Pos.z = r.ReadUInt16();
+
+ Pos.type = lvl.GetTile(Pos.x, Pos.y, Pos.z);
+ byte oldType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+ byte newType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+
+ if (Pos.type == newType || Block.Convert(Pos.type) == Block.water
+ || Block.Convert(Pos.type) == Block.lava || Pos.type == Block.grass) {
+
+ Pos.newtype = oldType;
+ Pos.timePlaced = now;
+ lvl.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
+ if (p != null)
+ p.RedoBuffer.Add(Pos);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ protected override bool HighlightEntry(Player p, string path, long seconds) {
+ List list = new List();
+ DateTime now = DateTime.Now;
+
+ using (Stream fs = File.OpenRead(path))
+ using (BinaryReader r = new BinaryReader(fs))
+ {
+ ReadHeaders(list, r);
+ for (int i = list.Count - 1; i >= 0; i--) {
+ ChunkHeader chunk = list[i];
+ Level lvl;
+ if (!CheckChunk(chunk, now, seconds, p, out lvl))
+ return false;
+ if (lvl == null || lvl != p.level) continue;
+ fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
+
+ for (int j = 0; j < chunk.Entries; j++ ) {
+ DateTime time = chunk.BaseTime.AddSeconds(r.ReadUInt16());
+ if (time.AddSeconds(seconds) < now) return false;
+ ushort x = r.ReadUInt16(), y = r.ReadUInt16(), z = r.ReadUInt16();
+
+ byte lvlTile = lvl.GetTile(x, y, z);
+ byte oldType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+ byte newType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
+
+ if (lvlTile == newType || Block.Convert(lvlTile) == Block.water || Block.Convert(lvlTile) == Block.lava) {
+
+ byte block = (lvlTile == Block.air || Block.Convert(lvlTile) == Block.water
+ || Block.Convert(lvlTile) == Block.lava) ? Block.red : Block.green;
+ p.SendBlockchange(x, y, z, block);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ static bool CheckChunk(ChunkHeader chunk, DateTime now, long seconds, Player p, out Level lvl) {
+ DateTime time = chunk.BaseTime;
+ lvl = null;
+ if (time.AddSeconds(65536).AddSeconds(seconds) < now)
+ return false; // we can safely discard the entire chunk
+
+ lvl = Level.FindExact(chunk.LevelName);
+ return true;
+ }
+
+ struct ChunkHeader {
+ public string LevelName;
+ public DateTime BaseTime;
+ public ushort Entries;
+ public long DataPosition;
+ }
+
+ static void ReadHeaders(List list, BinaryReader r) {
+ Stream s = r.BaseStream;
+ long len = s.Length;
+ while (s.Position < len) {
+ ChunkHeader header = ReadHeader(s, r);
+ s.Seek(header.Entries * 12, SeekOrigin.Current);
+ list.Add(header);
+ }
+ }
+
+ static ChunkHeader ReadHeader(Stream s, BinaryReader r) {
+ ChunkHeader header = default(ChunkHeader);
+ byte[] mapNameData = r.ReadBytes(r.ReadUInt16());
+ header.LevelName = Encoding.UTF8.GetString(mapNameData);
+
+ header.BaseTime = new DateTime(r.ReadInt64(), DateTimeKind.Local);
+ header.Entries = r.ReadUInt16();
+ header.DataPosition = s.Position;
+ return header;
+ }
+
+ static void WriteChunkEntries(BinaryWriter w, ushort entries, long entriesPos) {
+ long curPos = w.BaseStream.Position;
+ w.BaseStream.Seek(entriesPos, SeekOrigin.Begin);
+
+ w.Write(entries);
+ w.BaseStream.Seek(curPos, SeekOrigin.Begin);
+ }
+
+ static ChunkHeader WriteEmptyChunk(BinaryWriter w, Player.UndoPos uP, ref long entriesPos) {
+ byte[] mapBytes = Encoding.UTF8.GetBytes(uP.mapName);
+ w.Write((ushort)mapBytes.Length);
+ w.Write(mapBytes);
+ w.Write(uP.timePlaced.ToLocalTime().Ticks);
+
+ entriesPos = w.BaseStream.Position;
+ w.Write((ushort)0);
+ ChunkHeader header = default(ChunkHeader);
+ header.LevelName = uP.mapName; header.BaseTime = uP.timePlaced;
+ return header;
+ }
+ }
+}
diff --git a/Player/Undo/UndoFileText.cs b/Player/Undo/UndoFileText.cs
new file mode 100644
index 000000000..ef931598b
--- /dev/null
+++ b/Player/Undo/UndoFileText.cs
@@ -0,0 +1,134 @@
+/*
+ Copyright 2015 MCGalaxy
+
+ Dual-licensed under the Educational Community License, Version 2.0 and
+ the GNU General Public License, Version 3 (the "Licenses"); you may
+ not use this file except in compliance with the Licenses. You may
+ obtain a copy of the Licenses at
+
+ http://www.opensource.org/licenses/ecl2.php
+ http://www.gnu.org/licenses/gpl-3.0.html
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the Licenses are distributed on an "AS IS"
+ BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the Licenses for the specific language governing
+ permissions and limitations under the Licenses.
+ */
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+
+namespace MCGalaxy.Util {
+
+ public sealed class UndoFileText : UndoFile {
+
+ protected override string Extension { get { return ".undo"; } }
+
+ protected override void SaveUndoData(List buffer, string path) {
+ using (StreamWriter w = File.CreateText(path)) {
+ foreach (Player.UndoPos uP in buffer) {
+ w.Write(
+ uP.mapName + " " + uP.x + " " + uP.y + " " + uP.z + " " +
+ uP.timePlaced.ToString(CultureInfo.InvariantCulture).Replace(' ', '&') + " " +
+ uP.type + " " + uP.newtype + " ");
+ }
+ }
+ }
+
+ protected override void ReadUndoData(List buffer, string path) {
+ Player.UndoPos Pos;
+ string[] lines = File.ReadAllText(path).Split(' ');
+ int approxEntries = (int)(lines.Length / 7);
+ if (buffer.Capacity < approxEntries)
+ buffer.Capacity = approxEntries;
+
+ for (int i = 0; i < lines.Length; i += 7) {
+ if (lines[i].Length == 0) continue;
+ Pos.mapName = lines[i];
+ Pos.x = ushort.Parse(lines[i + 1]);
+ Pos.y = ushort.Parse(lines[i + 2]);
+ Pos.z = ushort.Parse(lines[i + 3]);
+
+ string time = lines[i + 4].Replace('&', ' ');
+ Pos.timePlaced = DateTime.Parse(time, CultureInfo.InvariantCulture);
+ Pos.type = byte.Parse(lines[i + 5]);
+ Pos.newtype = byte.Parse(lines[i + 6]);
+ buffer.Add(Pos);
+ }
+ }
+
+ protected override bool UndoEntry(Player p, string path, long seconds) {
+ Player.UndoPos Pos;
+ string[] lines = File.ReadAllText(path).Split(' ');
+ // because we have space to end of each entry, need to subtract one otherwise we'll start at a "".
+ for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
+ try {
+ // line format: mapName x y z date oldblock newblock
+ if (!InTime(lines[(i * 7) - 3], seconds)) return false;
+ Level foundLevel = Level.FindExact(lines[(i * 7) - 7]);
+ if (foundLevel == null) continue;
+
+ Pos.mapName = foundLevel.name;
+ Pos.x = Convert.ToUInt16(lines[(i * 7) - 6]);
+ Pos.y = Convert.ToUInt16(lines[(i * 7) - 5]);
+ Pos.z = Convert.ToUInt16(lines[(i * 7) - 4]);
+ Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
+
+ if (Pos.type == Convert.ToByte(lines[(i * 7) - 1]) ||
+ Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava ||
+ Pos.type == Block.grass) {
+
+ Pos.newtype = Convert.ToByte(lines[(i * 7) - 2]);
+ Pos.timePlaced = DateTime.Now;
+
+ foundLevel.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
+ if (p != null)
+ p.RedoBuffer.Add(Pos);
+ }
+ } catch {
+ }
+ }
+ return true;
+ }
+
+ protected override bool HighlightEntry(Player p, string path, long seconds) {
+ Player.UndoPos Pos;
+ string[] lines = File.ReadAllText(path).Split(' ');
+ // because we have space to end of each entry, need to subtract one otherwise we'll start at a "".
+ for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
+ try {
+ // line format: mapName x y z date oldblock newblock
+ if (!InTime(lines[(i * 7) - 3], seconds)) return false;
+ Level foundLevel = Level.FindExact(lines[(i * 7) - 7]);
+ if (foundLevel == null || foundLevel != p.level) continue;
+
+ Pos.mapName = foundLevel.name;
+ Pos.x = Convert.ToUInt16(lines[(i * 7) - 6]);
+ Pos.y = Convert.ToUInt16(lines[(i * 7) - 5]);
+ Pos.z = Convert.ToUInt16(lines[(i * 7) - 4]);
+ Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
+
+ if (Pos.type == Convert.ToByte(lines[(i * 7) - 1]) ||
+ Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava) {
+
+ if (Pos.type == Block.air || Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava)
+ p.SendBlockchange(Pos.x, Pos.y, Pos.z, Block.red);
+ else
+ p.SendBlockchange(Pos.x, Pos.y, Pos.z, Block.green);
+ }
+ } catch { }
+ }
+ return true;
+ }
+
+ static bool InTime(string line, long seconds) {
+ line = line.Replace('&', ' ');
+ DateTime time = DateTime.Parse(line, CultureInfo.InvariantCulture)
+ .AddSeconds(seconds);
+ return time >= DateTime.Now;
+ }
+ }
+}
diff --git a/Starter.csproj b/Starter.csproj
index 9400ca397..fb37cd206 100644
--- a/Starter.csproj
+++ b/Starter.csproj
@@ -1,74 +1,74 @@
-
-
-
- Debug
- x86
- 8.0.30703
- 2.0
- {63DCBB31-92CD-4464-A86C-A7E51A5FE9FE}
- Exe
- Properties
- Starter
- MCGalaxy
- v4.0
-
-
- 512
-
-
- x86
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- false
-
-
- x86
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- false
- false
-
-
- Starter.Program
-
-
- Galaxy.ico
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {12597DB0-7C34-4DE1-88EA-9250FF3372EB}
- MCGalaxy_
-
-
-
-
-
-
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {63DCBB31-92CD-4464-A86C-A7E51A5FE9FE}
+ Exe
+ Properties
+ Starter
+ MCGalaxy
+ v4.0
+
+
+ 512
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+ false
+
+
+ Starter.Program
+
+
+ Galaxy.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {12597DB0-7C34-4DE1-88EA-9250FF3372EB}
+ MCGalaxy_
+
+
+
+
+
+
+ -->
\ No newline at end of file