kill all the obsolete undo code

This commit is contained in:
UnknownShadow200 2017-01-02 12:23:45 +11:00
parent 6838c0964b
commit b306c81030
13 changed files with 39 additions and 482 deletions

View File

@ -54,12 +54,6 @@ namespace MCGalaxy.Commands.Building {
TimeSpan delta = GetDelta(p, parts[0], parts.Length > 1 ? parts[1] : "30m");
if (delta == TimeSpan.MinValue) return;
if (parts.Length > 1 && parts[1].CaselessEq("update")) {
UndoFormat.UpgradePlayerUndoFiles(parts[0]);
Player.Message(p, "Updated undo files for " + parts[0] + " to the new binary format.");
return;
}
Vec3S32[] marks = new Vec3S32[] { Vec3U16.MaxVal, Vec3U16.MaxVal };
if (undoPhysics)

View File

@ -148,13 +148,6 @@ namespace MCGalaxy {
BlockDB.Add(p, x, y, z, flags,
oldBlock, oldExtBlock, block, ext);
Player.UndoPos Pos;
Pos.x = x; Pos.y = y; Pos.z = z;
Pos.mapName = this.name;
Pos.type = oldBlock; Pos.extType = oldExtBlock;
Pos.newtype = block; Pos.newExtType = ext;
Pos.timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds;
p.UndoBuffer.Add(this, Pos);
}
bool CheckTNTWarsChange(Player p, ushort x, ushort y, ushort z, ref byte block) {
@ -277,15 +270,6 @@ namespace MCGalaxy {
if (old == Block.lava_sponge && physics > 0 && block != Block.lava_sponge)
OtherPhysics.DoSpongeRemoved(this, PosToInt(x, y, z), true);
errorLocation = "Undo buffer filling";
Player.UndoPos Pos;
Pos.x = x; Pos.y = y; Pos.z = z;
Pos.mapName = name;
Pos.type = old; Pos.extType = extOld;
Pos.newtype = block; Pos.newExtType = extBlock;
Pos.timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds;
p.UndoBuffer.Add(this, Pos);
errorLocation = "Setting tile";
p.IncrementBlockStats(block, drawn);
@ -304,17 +288,10 @@ namespace MCGalaxy {
bool diffBlock = old == Block.custom_block ? extOld != extBlock :
Block.Convert(old) != Block.Convert(block);
return diffBlock;
} catch (OutOfMemoryException) {
Player.Message(p, "Undo buffer too big! Cleared!");
p.UndoBuffer.Clear();
p.RemoveInvalidUndos();
goto retry;
} catch (Exception e) {
Server.ErrorLog(e);
Chat.MessageOps(p.name + " triggered a non-fatal error on " + ColoredName);
Chat.MessageOps("Error location: " + errorLocation);
Server.s.Log(p.name + " triggered a non-fatal error on " + ColoredName);
Server.s.Log("Error location: " + errorLocation);
Chat.MessageOps(p.name + " triggered a non-fatal error on " + ColoredName + ", %Sat location: " + errorLocation);
Server.s.Log(p.name + " triggered a non-fatal error on " + ColoredName + ", %Sat location: " + errorLocation);
return false;
}
}

View File

@ -566,10 +566,8 @@
<Compile Include="Player\PlayerInfo.cs" />
<Compile Include="Player\SpamChecker.cs" />
<Compile Include="Player\TabList.cs" />
<Compile Include="Player\Undo\UndoCache.cs" />
<Compile Include="Player\Undo\UndoFormat.Helpers.cs" />
<Compile Include="Player\Undo\UndoFormatCBin.cs" />
<Compile Include="Player\Undo\UndoFormatOnline.cs" />
<Compile Include="Player\Warp.cs" />
<Compile Include="Player\Player.Timers.cs" />
<Compile Include="Player\Undo\UndoFormat.cs" />

View File

@ -51,7 +51,6 @@ namespace MCGalaxy {
} catch ( ObjectDisposedException ) {
// Player is no longer connected, socket was closed
// Mark this as disconnected and remove them from active connection list
Player.SaveUndo(p);
connections.Remove(p);
p.RemoveFromPending();
p.disconnected = true;

View File

@ -211,8 +211,6 @@ namespace MCGalaxy {
public bool Mojangaccount { get { return truename.IndexOf('@') >= 0; } }
//Undo
public struct UndoPos { public ushort x, y, z; public byte type, extType, newtype, newExtType; public string mapName; public int timeDelta; }
public UndoCache UndoBuffer = new UndoCache();
internal VolatileArray<UndoDrawOpEntry> DrawOps = new VolatileArray<UndoDrawOpEntry>(false);
internal readonly object pendingDrawOpsLock = new object();
internal List<PendingDrawOp> PendingDrawOps = new List<PendingDrawOp>();

View File

@ -120,7 +120,6 @@ namespace MCGalaxy {
cuboided, totalKicked, time.ToDBTime(), name);
Server.zombie.SaveZombieStats(this);
SaveUndo(this);
}
#region == GLOBAL MESSAGES ==
@ -286,7 +285,6 @@ namespace MCGalaxy {
if (name == "") {
if (socket != null) CloseSocket();
connections.Remove(this);
SaveUndo(this);
disconnected = true;
return;
}
@ -368,13 +366,8 @@ namespace MCGalaxy {
}
}
public static void SaveUndo(Player p) {
try {
UndoFormat.SaveUndo(p);
} catch (Exception e) {
Server.s.Log("Error saving undo data for " + p.name + "!"); Server.ErrorLog(e);
}
}
[Obsolete]
public static void SaveUndo(Player p) { }
public void Dispose() {
connections.Remove(this);
@ -383,7 +376,6 @@ namespace MCGalaxy {
if (CopyBuffer != null)
CopyBuffer.Clear();
DrawOps.Clear();
UndoBuffer.Clear();
spamChecker.Clear();
spyChatRooms.Clear();
}
@ -523,14 +515,6 @@ namespace MCGalaxy {
}
return false;
}
internal void RemoveInvalidUndos() {
UndoDrawOpEntry[] items = DrawOps.Items;
for (int i = 0; i < items.Length; i++) {
if (items[i].End < UndoBuffer.LastClear)
DrawOps.Remove(items[i]);
}
}
internal static void AddNote(string target, Player who, string type) {
if (!Server.LogNotes) return;

View File

@ -1,166 +0,0 @@
/*
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.Runtime.InteropServices;
using System.Threading;
namespace MCGalaxy.Undo {
public sealed class UndoCache {
/// <summary> The olded/first node in the cache. </summary>
public UndoCacheNode Head;
/// <summary> The newest/last node in the cache. </summary>
public UndoCacheNode Tail;
/// <summary> Total number of items in the cache. </summary>
public volatile int Count;
/// <summary> Last time this undo buffer was cleared. </summary>
public DateTime LastClear;
/// <summary> Used to sychronise clearing an undo cache. </summary>
public ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
/// <summary> Used to sychronise adding to an undo cache. </summary>
public object AddLock = new object();
public const int TimeDeltaMax = (1 << 13) - 1;
/// <summary> Appends an item to the cache. </summary>
public void Add(Level lvl, Player.UndoPos item) {
lock (AddLock) {
DateTime time = Server.StartTime.AddTicks(item.timeDelta * TimeSpan.TicksPerSecond);
if (Tail == null) {
Tail = UndoCacheNode.Make(lvl, time); Head = Tail;
}
if (lvl.name != Tail.MapName || lvl.Width != Tail.Width || lvl.Height != Tail.Height ||
lvl.Length != Tail.Length || Math.Abs((time - Tail.BaseTime).TotalSeconds) > TimeDeltaMax) {
UndoCacheNode node = UndoCacheNode.Make(lvl, time);
Tail.Next = node; node.Prev = Tail;
Tail = node;
}
short timeDiff = (short)(time - Tail.BaseTime).TotalSeconds;
Tail.Items.Add(UndoCacheItem.Make(Tail, timeDiff, ref item));
Count++;
}
}
/// <summary> Removes all items from the cache and resets the state to default. </summary>
public void Clear() {
Count = 0;
if (Tail == null) return;
LastClear = DateTime.UtcNow;
UndoCacheNode node = Tail;
while (node != null) {
node.Items = null;
node = node.Prev;
}
Tail = null; Head = null;
}
}
public sealed class UndoCacheNode {
public string MapName;
public int Width, Height, Length;
public DateTime BaseTime;
public UndoCacheNode Prev, Next;
public List<UndoCacheItem> Items = new List<UndoCacheItem>();
public static UndoCacheNode Make(Level lvl, DateTime time) {
UndoCacheNode node = new UndoCacheNode();
node.MapName = lvl.name;
node.Width = lvl.Width; node.Height = lvl.Height; node.Length = lvl.Length;
node.BaseTime = time;
return node;
}
public void Unpack(int index, out ushort x, out ushort y, out ushort z) {
x = (ushort)(index % Width);
y = (ushort)(index / (Width * Length));
z = (ushort)((index / Width) % Length);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UndoCacheItem {
public int Index;
public byte Type, NewType;
public ushort Flags; // upper 2 bits for 'ext' or 'physics' type, lower 14 bits for time delta.
public short TimeDelta {
get {
int delta = Flags & 0x3FFF;
return delta >= 0x2000 ? (short)(delta - 16384) : (short)delta;
}
}
public void GetBlock(out byte type, out byte extType) {
if ((Flags & (1 << 14)) != 0) {
type = Block.custom_block; extType = Type;
} else {
type = Type; extType = 0;
}
}
public void GetNewBlock(out byte type, out byte extType) {
if ((Flags & (1 << 15)) != 0) {
type = Block.custom_block; extType = NewType;
} else {
type = NewType; extType = 0;
}
}
public static UndoCacheItem Make(UndoCacheNode node, short timeDelta, ref Player.UndoPos pos) {
UndoCacheItem item = default(UndoCacheItem);
item.Index = pos.x + node.Width * (pos.z + node.Length * pos.y);
item.Flags = (ushort)(timeDelta & 0x3FFF);
if (pos.type == Block.custom_block) {
item.Type = pos.extType;
item.Flags |= (ushort)(1 << 14);
} else {
item.Type = pos.type;
}
if (pos.newtype == Block.custom_block) {
item.NewType = pos.newExtType;
item.Flags |= (ushort)(1 << 15);
} else {
item.NewType = pos.newtype;
}
return item;
}
public static UndoCacheItem Make(UndoCacheNode node, short timeDelta, Player.UndoPos pos) {
return Make(node, timeDelta, ref pos);
}
}
public sealed class UndoDrawOpEntry {
public string DrawOpName;
public string LevelName;
public DateTime Start, End;
}
}

View File

@ -110,47 +110,6 @@ namespace MCGalaxy.Undo {
buffer.Send(true);
}
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<Player.UndoPos> buffer = new List<Player.UndoPos>();
UndoFormatArgs args = new UndoFormatArgs(null, DateTime.MinValue, DateTime.MaxValue, null);
for (int i = 0; i < files.Length; i++) {
path = files[i];
if (!path.EndsWith(BinFormat.Ext) && !path.EndsWith(TxtFormat.Ext)) continue;
IEnumerable<UndoFormatEntry> data = null;
Player.UndoPos pos;
using (FileStream s = File.OpenRead(path)) {
data = path.EndsWith(BinFormat.Ext)
? BinFormat.GetEntries(s, args) : TxtFormat.GetEntries(s, args);
foreach (UndoFormatEntry P in data) {
pos.x = P.X; pos.y = P.Y; pos.z = P.Z;
pos.type = P.Block; pos.extType = P.ExtBlock;
pos.newtype = P.NewBlock; pos.newExtType = P.NewExtBlock;
pos.timeDelta = (int)P.Time.Subtract(Server.StartTimeLocal).TotalSeconds;
pos.mapName = P.LevelName;
buffer.Add(pos);
}
buffer.Reverse();
string newPath = Path.ChangeExtension(path, NewFormat.Ext);
NewFormat.Save(buffer, newPath);
}
File.Delete(path);
}
}
static void UndoBlock(UndoFormatArgs args, Level lvl, UndoFormatEntry P) {
byte lvlBlock = lvl.GetTile(P.X, P.Y, P.Z);
if (lvlBlock == P.NewBlock || Block.Convert(lvlBlock) == Block.water

View File

@ -20,6 +20,12 @@ using System.Collections.Generic;
using System.IO;
namespace MCGalaxy.Undo {
public sealed class UndoDrawOpEntry {
public string DrawOpName;
public string LevelName;
public DateTime Start, End;
}
/// <summary> Retrieves and saves undo data in a particular format. </summary>
/// <remarks> Note most formats only support retrieving undo data. </remarks>
@ -30,43 +36,8 @@ namespace MCGalaxy.Undo {
public static UndoFormat BinFormat = new UndoFormatBin();
public static UndoFormat NewFormat = new UndoFormatCBin();
protected abstract void Save(List<Player.UndoPos> buffer, string path);
protected abstract void Save(UndoCache buffer, string path);
protected abstract IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args);
protected abstract string Ext { get; }
/// <summary> Saves the undo data for the given player to disc. </summary>
/// <remarks> Clears the player's in-memory undo buffer on success. </remarks>
public static void SaveUndo(Player p) {
if (p == 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.Ext);
UndoCache cache = p.UndoBuffer;
using (IDisposable locker = cache.ClearLock.AccquireReadLock()) {
NewFormat.Save(cache, path);
}
using (IDisposable locker = cache.ClearLock.AccquireWriteLock()) {
lock (cache.AddLock)
cache.Clear();
}
}
protected abstract IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args);
protected abstract string Ext { get; }
/// <summary> Gets a list of all undo file names for the given player. </summary>
/// <remarks> This list is sorted, such that the first element is the
@ -121,14 +92,6 @@ namespace MCGalaxy.Undo {
return a.CompareTo(b);
return aNum.CompareTo(bNum);
}
/// <summary> Creates the default base directories used to store undo data on disc. </summary>
public static void CreateDefaultDirectories() {
if (!Directory.Exists(undoDir))
Directory.CreateDirectory(undoDir);
if (!Directory.Exists(prevUndoDir))
Directory.CreateDirectory(prevUndoDir);
}
}
/// <summary> Arguments provided to an UndoFormat for retrieving undo data. </summary>

View File

@ -26,14 +26,6 @@ namespace MCGalaxy.Undo {
protected override string Ext { get { return ".unbin"; } }
const int entrySize = 12;
protected override void Save(List<Player.UndoPos> buffer, string path) {
throw new NotSupportedException("Non-optimised binary undo files have been deprecated");
}
protected override void Save(UndoCache buffer, string path) {
throw new NotSupportedException("Non-optimised binary undo files have been deprecated");
}
protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
List<ChunkHeader> list = new List<ChunkHeader>();

View File

@ -28,81 +28,12 @@ namespace MCGalaxy.Undo {
protected override string Ext { get { return ".uncbin"; } }
const int entrySize = 8;
protected override void Save(List<Player.UndoPos> buffer, string path) {
UndoCacheNode node = new UndoCacheNode();
string lastLoggedName = "";
using (FileStream fs = File.Create(path)) {
BinaryWriter w = new BinaryWriter(fs);
long entriesPos = 0;
ChunkHeader last = default(ChunkHeader);
foreach (Player.UndoPos uP in buffer) {
DateTime time = Server.StartTime.AddSeconds(uP.timeDelta);
int timeDiff = (int)(time - last.BaseTime).TotalSeconds;
if (last.LevelName != uP.mapName || timeDiff > (65535 >> 2) || last.Entries == ushort.MaxValue) {
if (!LevelInfo.ExistsOffline(uP.mapName)) {
if (uP.mapName != lastLoggedName) {
lastLoggedName = uP.mapName;
Server.s.Log("Missing map file \"" + lastLoggedName+ "\", skipping undo entries");
}
continue;
}
Vec3U16 dims = IMapImporter.Formats[0].ReadDimensions(LevelInfo.LevelPath(uP.mapName));
node.Width = dims.X; node.Height = dims.Y; node.Length = dims.Z;
WriteChunkEntries(w, last.Entries, entriesPos);
node.MapName = uP.mapName;
last = WriteEmptyChunk(w, node, time, ref entriesPos);
}
UndoCacheItem item = UndoCacheItem.Make(node, 0, uP);
int flags = (item.Flags & 0xC000) | timeDiff;
w.Write((ushort)flags); w.Write(item.Index);
w.Write(item.Type); w.Write(item.NewType);
last.Entries++;
}
if (last.Entries > 0)
WriteChunkEntries(w, last.Entries, entriesPos);
}
}
protected override void Save(UndoCache buffer, string path) {
using (FileStream fs = File.Create(path)) {
BinaryWriter w = new BinaryWriter(fs);
long entriesPos = 0;
ChunkHeader last = default(ChunkHeader);
UndoCacheNode node = buffer.Head;
while (node != null) {
List<UndoCacheItem> items = node.Items;
for (int i = 0; i < items.Count; i++) {
UndoCacheItem uP = items[i];
DateTime time = node.BaseTime.AddSeconds(uP.TimeDelta);
int timeDiff = (int)(time - last.BaseTime).TotalSeconds;
if (last.LevelName != node.MapName || timeDiff > (65535 >> 2) || last.Entries == ushort.MaxValue) {
WriteChunkEntries(w, last.Entries, entriesPos);
last = WriteEmptyChunk(w, node, time, ref entriesPos);
}
int flags = (uP.Flags & 0xC000) | timeDiff;
w.Write((ushort)flags); w.Write(uP.Index);
w.Write(uP.Type); w.Write(uP.NewType);
last.Entries++;
}
if (last.Entries > 0)
WriteChunkEntries(w, last.Entries, entriesPos);
node = node.Next;
}
}
}
protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
List<ChunkHeader> list = new List<ChunkHeader>();
UndoFormatEntry pos;
UndoCacheItem item = default(UndoCacheItem);
bool super = Player.IsSuper(args.Player);
DateTime start = args.Start;
DateTime start = args.Start;
ReadHeaders(list, s);
for (int i = list.Count - 1; i >= 0; i--) {
@ -158,7 +89,7 @@ namespace MCGalaxy.Undo {
static void ReadHeaders(List<ChunkHeader> list, Stream s) {
BinaryReader r = new BinaryReader(s);
long len = s.Length;
long len = s.Length;
while (s.Position < len) {
ChunkHeader header = ReadHeader(s, r);
s.Seek(header.Entries * entrySize, SeekOrigin.Current);
@ -180,28 +111,33 @@ namespace MCGalaxy.Undo {
return header;
}
static void WriteChunkEntries(BinaryWriter w, ushort entries, long entriesPos) {
long curPos = w.BaseStream.Position;
w.BaseStream.Seek(entriesPos, SeekOrigin.Begin);
public struct UndoCacheItem {
public int Index;
public byte Type, NewType;
public ushort Flags; // upper 2 bits for 'ext' or 'physics' type, lower 14 bits for time delta.
w.Write(entries);
w.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
static ChunkHeader WriteEmptyChunk(BinaryWriter w, UndoCacheNode node, DateTime time, ref long entriesPos) {
ChunkHeader header = default(ChunkHeader);
time = time.ToUniversalTime();
byte[] mapBytes = Encoding.UTF8.GetBytes(node.MapName);
w.Write((ushort)mapBytes.Length);
w.Write(mapBytes); header.LevelName = node.MapName;
w.Write(time.Ticks); header.BaseTime = time;
public short TimeDelta {
get {
int delta = Flags & 0x3FFF;
return delta >= 0x2000 ? (short)(delta - 16384) : (short)delta;
}
}
entriesPos = w.BaseStream.Position;
w.Write((ushort)0);
w.Write((ushort)node.Width); header.Width = (ushort)node.Width;
w.Write((ushort)node.Height); header.Height = (ushort)node.Height;
w.Write((ushort)node.Length); header.Length = (ushort)node.Length;
return header;
public void GetBlock(out byte type, out byte extType) {
if ((Flags & (1 << 14)) != 0) {
type = Block.custom_block; extType = Type;
} else {
type = Type; extType = 0;
}
}
public void GetNewBlock(out byte type, out byte extType) {
if ((Flags & (1 << 15)) != 0) {
type = Block.custom_block; extType = NewType;
} else {
type = NewType; extType = 0;
}
}
}
}
}

View File

@ -1,69 +0,0 @@
/*
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;
namespace MCGalaxy.Undo {
public sealed class UndoFormatOnline : UndoFormat {
protected override string Ext { get { return null; } }
readonly UndoCache cache;
public UndoFormatOnline(UndoCache cache) {
this.cache = cache;
}
protected override void Save(List<Player.UndoPos> buffer, string path) {
throw new NotSupportedException("UndoFileOnline is read only.");
}
protected override void Save(UndoCache buffer, string path) {
throw new NotSupportedException("UndoFileOnline is read only.");
}
protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
UndoCacheNode node = cache.Tail;
if (node == null) yield break;
UndoFormatEntry pos;
bool super = Player.IsSuper(args.Player);
DateTime start = args.Start;
while (node != null) {
Level lvl = LevelInfo.FindExact(node.MapName);
if (!super && !args.Player.level.name.CaselessEq(node.MapName)) { node = node.Prev; continue; }
List<UndoCacheItem> items = node.Items;
pos.LevelName = node.MapName;
for (int i = items.Count - 1; i >= 0; i--) {
UndoCacheItem item = items[i];
DateTime time = node.BaseTime.AddTicks(item.TimeDelta * TimeSpan.TicksPerSecond);
if (time < start) { args.Stop = true; yield break; }
pos.Time = time;
node.Unpack(item.Index, out pos.X, out pos.Y, out pos.Z);
item.GetBlock(out pos.Block, out pos.ExtBlock);
item.GetNewBlock(out pos.NewBlock, out pos.NewExtBlock);
yield return pos;
}
node = node.Prev;
}
}
}
}

View File

@ -26,14 +26,6 @@ namespace MCGalaxy.Undo {
protected override string Ext { get { return ".undo"; } }
protected override void Save(List<Player.UndoPos> buffer, string path) {
throw new NotSupportedException("Text undo files have been deprecated");
}
protected override void Save(UndoCache buffer, string path) {
throw new NotSupportedException("Text undo files have been deprecated");
}
protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
UndoFormatEntry pos;
pos.NewExtBlock = 0; pos.ExtBlock = 0;