[Major WIP] More untested cleanup for undo stuff.

This commit is contained in:
UnknownShadow200 2016-08-20 17:05:29 +10:00
parent 25fadd9987
commit f0ea6ba7b1
11 changed files with 328 additions and 250 deletions

View File

@ -60,7 +60,7 @@ namespace MCGalaxy.Commands {
done = HighlightBlocks(p, start, cache); done = HighlightBlocks(p, start, cache);
} }
} }
if (!done) UndoFormat.HighlightPlayer(p, name.ToLower(), start, ref found); if (!done) UndoFormat.DoHighlight(p, name.ToLower(), start, ref found);
if (found) { if (found) {
Player.Message(p, "Now highlighting &b" + seconds + " %Sseconds for " + Server.FindColor(name) + name); Player.Message(p, "Now highlighting &b" + seconds + " %Sseconds for " + Server.FindColor(name) + name);
@ -71,9 +71,8 @@ namespace MCGalaxy.Commands {
} }
static bool HighlightBlocks(Player p, DateTime start, UndoCache cache) { static bool HighlightBlocks(Player p, DateTime start, UndoCache cache) {
UndoEntriesArgs args = new UndoEntriesArgs(p, start); UndoFormatArgs args = new UndoFormatArgs(p, start);
UndoFormatOnline format = new UndoFormatOnline(); UndoFormat format = new UndoFormatOnline(cache);
format.Cache = cache;
UndoFormat.DoHighlight(null, format, args); UndoFormat.DoHighlight(null, format, args);
return args.Stop; return args.Stop;
} }

View File

@ -60,11 +60,9 @@ namespace MCGalaxy.Commands {
op.who = who; op.who = who;
DrawOp.DoDrawOp(op, null, p, marks); DrawOp.DoDrawOp(op, null, p, marks);
Level saveLevel = op.saveLevel;
Player.SendChatFrom(who, who.ColoredName + Player.SendChatFrom(who, who.ColoredName +
"%S's actions for the past &b" + args.seconds + " seconds were undone.", false); "%S's actions for the past &b" + args.seconds + " seconds were undone.", false);
Server.s.Log(who.name + "'s actions for the past " + args.seconds + " seconds were undone."); Server.s.Log(who.name + "'s actions for the past " + args.seconds + " seconds were undone.");
if (saveLevel != null) saveLevel.Save(true);
} }
void UndoOfflinePlayer(Player p, string whoName, UndoArgs args, Vec3S32[] marks) { void UndoOfflinePlayer(Player p, string whoName, UndoArgs args, Vec3S32[] marks) {
@ -78,7 +76,7 @@ namespace MCGalaxy.Commands {
op.whoName = whoName; op.whoName = whoName;
DrawOp.DoDrawOp(op, null, p, marks); DrawOp.DoDrawOp(op, null, p, marks);
if (op.foundUser) { if (op.found) {
Chat.MessageAll("{0}{1}%S's actions for the past &b{2} %Sseconds were undone.", Chat.MessageAll("{0}{1}%S's actions for the past &b{2} %Sseconds were undone.",
group.color, whoName, args.seconds); group.color, whoName, args.seconds);
Server.s.Log(whoName + "'s actions for the past " + args.seconds + " seconds were undone."); Server.s.Log(whoName + "'s actions for the past " + args.seconds + " seconds were undone.");

View File

@ -118,14 +118,12 @@ namespace MCGalaxy.Commands.Building {
op.who = who; op.who = who;
DrawOp.DoDrawOp(op, null, p, new Vec3S32[] { Vec3U16.MaxVal, Vec3U16.MaxVal } ); DrawOp.DoDrawOp(op, null, p, new Vec3S32[] { Vec3U16.MaxVal, Vec3U16.MaxVal } );
Level saveLevel = op.saveLevel;
if (p == who) { if (p == who) {
Player.Message(p, "Undid your actions for the past &b" + seconds + " %Sseconds."); Player.Message(p, "Undid your actions for the past &b" + seconds + " %Sseconds.");
} else { } else {
Player.SendChatFrom(who, who.ColoredName + "%S's actions for the past &b" + seconds + " seconds were undone.", false); Player.SendChatFrom(who, who.ColoredName + "%S's actions for the past &b" + seconds + " seconds were undone.", false);
} }
Server.s.Log(who.name + "'s actions for the past " + seconds + " seconds were undone."); Server.s.Log(who.name + "'s actions for the past " + seconds + " seconds were undone.");
if (saveLevel != null) saveLevel.Save(true);
} }
void UndoOfflinePlayer(Player p, long seconds, string whoName) { void UndoOfflinePlayer(Player p, long seconds, string whoName) {
@ -136,7 +134,7 @@ namespace MCGalaxy.Commands.Building {
op.whoName = whoName; op.whoName = whoName;
DrawOp.DoDrawOp(op, null, p, new Vec3S32[] { Vec3U16.MaxVal, Vec3U16.MaxVal } ); DrawOp.DoDrawOp(op, null, p, new Vec3S32[] { Vec3U16.MaxVal, Vec3U16.MaxVal } );
if (op.foundUser) { if (op.found) {
Chat.MessageAll("{0}{1}%S's actions for the past &b{2} %Sseconds were undone.", Chat.MessageAll("{0}{1}%S's actions for the past &b{2} %Sseconds were undone.",
Server.FindColor(whoName), whoName, seconds); Server.FindColor(whoName), whoName, seconds);
Server.s.Log(whoName + "'s actions for the past " + seconds + " seconds were undone."); Server.s.Log(whoName + "'s actions for the past " + seconds + " seconds were undone.");

View File

@ -37,64 +37,33 @@ namespace MCGalaxy.Drawing.Ops {
public DateTime End = DateTime.MaxValue; public DateTime End = DateTime.MaxValue;
internal Player who; internal Player who;
internal Level saveLevel = null;
public override long GetBlocksAffected(Level lvl, Vec3S32[] marks) { return -1; } public override long GetBlocksAffected(Level lvl, Vec3S32[] marks) { return -1; }
public override IEnumerable<DrawOpBlock> Perform(Vec3S32[] marks, Player p, Level lvl, Brush brush) { public override IEnumerable<DrawOpBlock> Perform(Vec3S32[] marks, Player p, Level lvl, Brush brush) {
UndoCache cache = p.UndoBuffer; UndoCache cache = p.UndoBuffer;
using (IDisposable locker = cache.ClearLock.AccquireReadLock()) { using (IDisposable locker = cache.ClearLock.AccquireReadLock()) {
UndoBlocks(p, ref saveLevel); if (UndoBlocks(p)) yield break;
} }
bool found = false;
string target = who.name.ToLower();
bool foundUser = false; if (Min.X != ushort.MaxValue)
Vec3S32[] bounds = { Min, Max }; UndoFormat.DoUndoArea(p, target, Start, Min, Max, ref found);
UndoFormat.UndoPlayer(p, who.name.ToLower(), bounds, Start, ref foundUser); else
UndoFormat.DoUndo(p, target, Start, End, ref found);
yield break; yield break;
} }
void UndoBlocks(Player p, ref Level saveLvl) { bool UndoBlocks(Player p) {
UndoCache cache = who.UndoBuffer; UndoFormatArgs args = new UndoFormatArgs(p, Start);
UndoCacheNode node = cache.Tail; UndoFormat format = new UndoFormatOnline(p.UndoBuffer);
if (node == null) return;
Vec3U16 min = (Vec3U16)Min, max = (Vec3U16)Max; if (Min.X != ushort.MaxValue)
bool undoArea = Min.X != ushort.MaxValue; UndoFormat.DoUndoArea(null, Min, Max, format, args);
Player.UndoPos Pos = default(Player.UndoPos); else
int timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds; UndoFormat.DoUndo(null, End, format, args);
return args.Stop;
while (node != null) {
Level lvl = LevelInfo.FindExact(node.MapName);
if (lvl == null) { node = node.Prev; continue; }
bool super = p == null || p.ircNick != null;
if (!super && !p.level.name.CaselessEq(lvl.name)) { node = node.Prev; continue; }
Pos.mapName = lvl.name;
saveLvl = lvl;
List<UndoCacheItem> items = node.Items;
BufferedBlockSender buffer = new BufferedBlockSender(lvl);
if (!undoArea) {
min = new Vec3U16(0, 0, 0);
max = new Vec3U16((ushort)(lvl.Width - 1), (ushort)(lvl.Height - 1), (ushort)(lvl.Length - 1));
}
for (int i = items.Count - 1; i >= 0; i--) {
UndoCacheItem item = items[i];
node.Unpack(item.Index, out Pos.x, out Pos.y, out Pos.z);
if (Pos.x < min.X || Pos.y < min.Y || Pos.z < min.Z ||
Pos.x > max.X || Pos.y > max.Y || Pos.z > max.Z) continue;
DateTime time = node.BaseTime.AddTicks(item.TimeDelta * TimeSpan.TicksPerSecond);
if (time > End) continue;
if (time < Start) { buffer.CheckIfSend(true); return; }
item.GetNewBlock(out Pos.newtype, out Pos.newExtType);
item.GetBlock(out Pos.type, out Pos.extType);
UndoFormat.UndoBlock(p, lvl, Pos, buffer);
}
buffer.CheckIfSend(true);
node = node.Prev;
}
} }
} }
@ -106,13 +75,16 @@ namespace MCGalaxy.Drawing.Ops {
public DateTime Start = DateTime.MinValue; public DateTime Start = DateTime.MinValue;
internal string whoName; internal string whoName;
internal bool foundUser = false; internal bool found = false;
public override long GetBlocksAffected(Level lvl, Vec3S32[] marks) { return -1; } public override long GetBlocksAffected(Level lvl, Vec3S32[] marks) { return -1; }
public override IEnumerable<DrawOpBlock> Perform(Vec3S32[] marks, Player p, Level lvl, Brush brush) { public override IEnumerable<DrawOpBlock> Perform(Vec3S32[] marks, Player p, Level lvl, Brush brush) {
Vec3S32[] bounds = { Min, Max }; string target = whoName.ToLower();
UndoFormat.UndoPlayer(p, whoName.ToLower(), bounds, Start, ref foundUser); if (Min.X != ushort.MaxValue)
UndoFormat.DoUndoArea(p, target, Start, Min, Max, ref found);
else
UndoFormat.DoUndo(p, target, Start, DateTime.MaxValue, ref found);
yield break; yield break;
} }
} }

View File

@ -541,6 +541,7 @@
<Compile Include="Player\PlayerInfo.cs" /> <Compile Include="Player\PlayerInfo.cs" />
<Compile Include="Player\TabList.cs" /> <Compile Include="Player\TabList.cs" />
<Compile Include="Player\Undo\UndoCache.cs" /> <Compile Include="Player\Undo\UndoCache.cs" />
<Compile Include="Player\Undo\UndoFormat.Helpers.cs" />
<Compile Include="Player\Undo\UndoFormatCBin.cs" /> <Compile Include="Player\Undo\UndoFormatCBin.cs" />
<Compile Include="Player\Undo\UndoFormatOnline.cs" /> <Compile Include="Player\Undo\UndoFormatOnline.cs" />
<Compile Include="Player\Warp.cs" /> <Compile Include="Player\Warp.cs" />

View File

@ -0,0 +1,198 @@
/*
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 {
/// <summary> Retrieves and saves undo data in a particular format. </summary>
/// <remarks> Note most formats only support retrieving undo data. </remarks>
public abstract partial class UndoFormat {
public static void DoUndo(Player p, string target,
DateTime start, DateTime end, ref bool found) {
List<string> files = GetUndoFiles(target);
UndoFormatArgs args = new UndoFormatArgs(p, start);
foreach (string file in files) {
found = true;
using (Stream s = File.OpenRead(file)) {
DoUndo(s, end, GetFormat(file), args);
if (args.Stop) break;
}
}
}
public static void DoUndo(Stream s, DateTime end,
UndoFormat format, UndoFormatArgs args) {
Level lvl = args.Player == null ? null : args.Player.level;
BufferedBlockSender buffer = new BufferedBlockSender(lvl);
string lastMap = null;
foreach (UndoFormatEntry P in format.GetEntries(s, args)) {
if (P.LevelName != lastMap) {
lvl = LevelInfo.FindExact(P.LevelName);
buffer.CheckIfSend(true);
buffer.level = lvl;
}
if (lvl == null || P.Time > end) continue;
UndoBlock(args.Player, lvl, P, buffer);
}
buffer.CheckIfSend(true);
}
public static void DoUndoArea(Player p, string target,
DateTime start, Vec3S32 min, Vec3S32 max, ref bool found) {
List<string> files = GetUndoFiles(target);
UndoFormatArgs args = new UndoFormatArgs(p, start);
foreach (string file in files) {
found = true;
using (Stream s = File.OpenRead(file)) {
DoUndoArea(s, min, max, GetFormat(file), args);
if (args.Stop) break;
}
}
}
public static void DoUndoArea(Stream s, Vec3S32 min, Vec3S32 max,
UndoFormat format, UndoFormatArgs args) {
Level lvl = args.Player == null ? null : args.Player.level;
BufferedBlockSender buffer = new BufferedBlockSender(lvl);
string lastMap = null;
foreach (UndoFormatEntry P in format.GetEntries(s, args)) {
if (P.LevelName != lastMap) {
lvl = LevelInfo.FindExact(P.LevelName);
buffer.CheckIfSend(true);
buffer.level = lvl;
}
if (lvl == null) continue;
if (P.X < min.X || P.Y < min.Y || P.Z < min.Z) continue;
if (P.X > max.X || P.Y > max.Y || P.Z > max.Z) continue;
UndoBlock(args.Player, lvl, P, buffer);
}
buffer.CheckIfSend(true);
}
public static void DoHighlight(Player p, string target,
DateTime start, ref bool found) {
List<string> files = GetUndoFiles(target);
UndoFormatArgs args = new UndoFormatArgs(p, start);
foreach (string file in files) {
found = true;
using (Stream s = File.OpenRead(file)) {
DoHighlight(s, GetFormat(file), args);
if (args.Stop) break;
}
}
}
public static void DoHighlight(Stream s, UndoFormat format, UndoFormatArgs args) {
BufferedBlockSender buffer = new BufferedBlockSender(args.Player);
Level lvl = args.Player.level;
foreach (UndoFormatEntry P in format.GetEntries(s, args)) {
byte block = P.Block, newBlock = P.NewBlock;
byte highlight = (newBlock == Block.air
|| Block.Convert(block) == Block.water || block == Block.waterstill
|| Block.Convert(block) == Block.lava || block == Block.lavastill)
? Block.red : Block.green;
buffer.Add(lvl.PosToInt(P.X, P.Y, P.Z), highlight, 0);
buffer.CheckIfSend(false);
}
buffer.CheckIfSend(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);
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(Player pl, Level lvl, UndoFormatEntry P,
BufferedBlockSender buffer) {
byte lvlBlock = lvl.GetTile(P.X, P.Y, P.Z);
if (lvlBlock == P.NewBlock || Block.Convert(lvlBlock) == Block.water
|| Block.Convert(lvlBlock) == Block.lava || lvlBlock == Block.grass) {
lvl.changed = true;
if (pl != null) {
if (lvl.DoBlockchange(pl, P.X, P.Y, P.Z, P.Block, P.ExtBlock, true)) {
buffer.Add(lvl.PosToInt(P.X, P.Y, P.Z), P.Block, P.ExtBlock);
buffer.CheckIfSend(false);
}
} else {
bool diff = Block.Convert(lvlBlock) != Block.Convert(P.Block);
if (!diff && lvlBlock == Block.custom_block)
diff = lvl.GetExtTile(P.X, P.Y, P.Z) != P.ExtBlock;
if (diff) {
buffer.Add(lvl.PosToInt(P.X, P.Y, P.Z), P.Block, P.ExtBlock);
buffer.CheckIfSend(false);
}
lvl.SetTile(P.X, P.Y, P.Z, P.Block);
if (P.ExtBlock == Block.custom_block)
lvl.SetExtTile(P.X, P.Y, P.Z, P.ExtBlock);
}
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace MCGalaxy.Undo {
/// <summary> Retrieves and saves undo data in a particular format. </summary> /// <summary> Retrieves and saves undo data in a particular format. </summary>
/// <remarks> Note most formats only support retrieving undo data. </remarks> /// <remarks> Note most formats only support retrieving undo data. </remarks>
public abstract class UndoFormat { public abstract partial class UndoFormat {
protected const string undoDir = "extra/undo", prevUndoDir = "extra/undoPrevious"; protected const string undoDir = "extra/undo", prevUndoDir = "extra/undoPrevious";
public static UndoFormat TxtFormat = new UndoFormatText(); public static UndoFormat TxtFormat = new UndoFormatText();
@ -34,10 +34,12 @@ namespace MCGalaxy.Undo {
protected abstract void Save(UndoCache buffer, string path); protected abstract void Save(UndoCache buffer, string path);
protected abstract IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args); protected abstract IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args);
protected abstract string Ext { get; } 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) { public static void SaveUndo(Player p) {
if (p == null || p.UndoBuffer.Count < 1) return; if (p == null || p.UndoBuffer.Count < 1) return;
@ -66,44 +68,30 @@ namespace MCGalaxy.Undo {
} }
} }
public static void UndoPlayer(Player p, string target, Vec3S32[] marks, DateTime start, ref bool FoundUser) { /// <summary> Gets a list of all undo file names for the given player. </summary>
FilterEntries(p, undoDir, target, marks, start, false, ref FoundUser); /// <remarks> This list is sorted, such that the first element is the
FilterEntries(p, prevUndoDir, target, marks, start, false, ref FoundUser); /// most recent undo file, and the last element is the oldest.</remarks>
} public static List<string> GetUndoFiles(string name) {
List<string> entries = new List<string>();
string[] cur = GetFiles(undoDir, name);
string[] prev = GetFiles(prevUndoDir, name);
public static void HighlightPlayer(Player p, string target, DateTime start, ref bool FoundUser) { // Start from the last entry, because when undoing we want to start
FilterEntries(p, undoDir, target, null, start, true, ref FoundUser); // with the most recent file first
FilterEntries(p, prevUndoDir, target, null, start, true, ref FoundUser); for (int i = cur.Length - 1; i >= 0; i--) {
if (cur[i] == null) continue;
entries.Add(cur[i]);
} }
for (int i = prev.Length - 1; i >= 0; i--) {
static void FilterEntries(Player p, string dir, string name, Vec3S32[] marks, if (prev[i] == null) continue;
DateTime start, bool highlight, ref bool FoundUser) { entries.Add(prev[i]);
string[] files = GetFiles(dir, name);
if (files == null || files.Length == 0) return;
UndoEntriesArgs args = new UndoEntriesArgs(p, start);
for (int i = files.Length - 1; i >= 0; i--) {
string path = files[i];
if (path == null) continue;
UndoFormat format = GetFormat(path);
if (format == null) continue;
using (Stream s = File.OpenRead(path)) {
if (highlight) {
DoHighlight(s, format, args);
} else {
DoUndo(s, format, args);
} }
if (args.Stop) break; return entries;
}
}
FoundUser = true;
} }
static string[] GetFiles(string dir, string name) { static string[] GetFiles(string dir, string name) {
string path = Path.Combine(dir, name); string path = Path.Combine(dir, name);
if (!Directory.Exists(path)) return null; if (!Directory.Exists(path)) return new string[0];
string[] files = Directory.GetFiles(path); string[] files = Directory.GetFiles(path);
Array.Sort<string>(files, CompareFiles); Array.Sort<string>(files, CompareFiles);
@ -111,6 +99,8 @@ namespace MCGalaxy.Undo {
name = Path.GetFileName(files[i]); name = Path.GetFileName(files[i]);
if (name.Length == 0 || name[0] < '0' || name[0] > '9') if (name.Length == 0 || name[0] < '0' || name[0] > '9')
files[i] = null; files[i] = null;
if (files[i] != null && GetFormat(name) == null)
files[i] = null;
} }
return files; return files;
} }
@ -132,121 +122,43 @@ namespace MCGalaxy.Undo {
return aNum.CompareTo(bNum); return aNum.CompareTo(bNum);
} }
public static void DoHighlight(Stream s, UndoFormat format, UndoEntriesArgs args) { /// <summary> Creates the default base directories used to store undo data on disc. </summary>
BufferedBlockSender buffer = new BufferedBlockSender(args.Player);
Level lvl = args.Player.level;
foreach (Player.UndoPos P in format.GetEntries(s, args)) {
byte type = P.type, newType = P.newtype;
byte block = (newType == Block.air
|| Block.Convert(type) == Block.water || type == Block.waterstill
|| Block.Convert(type) == Block.lava || type == Block.lavastill)
? Block.red : Block.green;
buffer.Add(lvl.PosToInt(P.x, P.y, P.z), block, 0);
buffer.CheckIfSend(false);
}
buffer.CheckIfSend(true);
}
public static void DoUndo(Stream s, UndoFormat format, UndoEntriesArgs args) {
Level lvl = args.Player == null ? null : args.Player.level;
BufferedBlockSender buffer = new BufferedBlockSender(lvl);
string lastMap = null;
foreach (Player.UndoPos P in format.GetEntries(s, args)) {
if (P.mapName != lastMap) {
lvl = LevelInfo.FindExact(P.mapName);
buffer.CheckIfSend(true);
buffer.level = lvl;
}
if (lvl == null) continue;
UndoBlock(args.Player, lvl, P, buffer);
}
buffer.CheckIfSend(true);
}
protected internal static void UndoBlock(Player pl, Level lvl, Player.UndoPos P,
BufferedBlockSender buffer) {
byte lvlTile = lvl.GetTile(P.x, P.y, P.z);
if (lvlTile == P.newtype || Block.Convert(lvlTile) == Block.water
|| Block.Convert(lvlTile) == Block.lava || lvlTile == Block.grass) {
byte newExtType = P.newExtType;
P.newtype = P.type; P.newExtType = P.extType;
P.extType = newExtType; P.type = lvlTile;
if (pl != null) {
if (lvl.DoBlockchange(pl, P.x, P.y, P.z, P.newtype, P.newExtType, true)) {
buffer.Add(lvl.PosToInt(P.x, P.y, P.z), P.newtype, P.newExtType);
buffer.CheckIfSend(false);
}
} else {
bool diffBlock = Block.Convert(lvlTile) != Block.Convert(P.newtype);
if (!diffBlock && lvlTile == Block.custom_block)
diffBlock = lvl.GetExtTile(P.x, P.y, P.z) != P.newExtType;
if (diffBlock) {
buffer.Add(lvl.PosToInt(P.x, P.y, P.z), P.newtype, P.newExtType);
buffer.CheckIfSend(false);
}
lvl.SetTile(P.x, P.y, P.z, P.newtype);
if (P.newtype == Block.custom_block)
lvl.SetExtTile(P.x, P.y, P.z, P.newExtType);
}
}
}
public static void CreateDefaultDirectories() { public static void CreateDefaultDirectories() {
if (!Directory.Exists(undoDir)) if (!Directory.Exists(undoDir))
Directory.CreateDirectory(undoDir); Directory.CreateDirectory(undoDir);
if (!Directory.Exists(prevUndoDir)) if (!Directory.Exists(prevUndoDir))
Directory.CreateDirectory(prevUndoDir); Directory.CreateDirectory(prevUndoDir);
} }
public static void UpgradePlayerUndoFiles(string name) {
UpgradeFiles(undoDir, name);
UpgradeFiles(prevUndoDir, name);
} }
static void UpgradeFiles(string dir, string name) { /// <summary> Arguments provided to an UndoFormat for retrieving undo data. </summary>
string path = Path.Combine(dir, name); public class UndoFormatArgs {
if (!Directory.Exists(path)) return;
string[] files = Directory.GetFiles(path);
List<Player.UndoPos> buffer = new List<Player.UndoPos>();
UndoEntriesArgs args = new UndoEntriesArgs(null, DateTime.MinValue);
for (int i = 0; i < files.Length; i++) { /// <summary> Player associated with this undo, can be console or IRC. </summary>
path = files[i]; internal readonly Player Player;
if (!path.EndsWith(BinFormat.Ext) && !path.EndsWith(TxtFormat.Ext)) continue;
IEnumerable<Player.UndoPos> data = null; /// <summary> Small work buffer, used to avoid memory allocations. </summary>
using (FileStream s = File.OpenRead(path)) { internal byte[] Temp;
data = path.EndsWith(BinFormat.Ext)
? BinFormat.GetEntries(s, args) : TxtFormat.GetEntries(s, args);
foreach (Player.UndoPos pos in data) /// <summary> Whether the format has finished retrieving undo data,
buffer.Add(pos); /// due to finding an entry before the start range. </summary>
buffer.Reverse();
string newPath = Path.ChangeExtension(path, NewFormat.Ext);
NewFormat.Save(buffer, newPath);
}
File.Delete(path);
}
}
}
public class UndoEntriesArgs {
public Player Player;
public byte[] Temp;
public bool Stop; public bool Stop;
public DateTime StartRange;
public UndoEntriesArgs(Player p, DateTime start) { /// <summary> First instance in time that undo data should be retrieved back to. </summary>
internal readonly DateTime Start;
public UndoFormatArgs(Player p, DateTime start) {
Player = p; Player = p;
StartRange = start; Start = start;
} }
} }
public struct UndoFormatEntry {
public string LevelName;
public DateTime Time;
public ushort X, Y, Z;
public byte Block, ExtBlock;
public byte NewBlock, NewExtBlock;
}
} }

View File

@ -35,11 +35,11 @@ namespace MCGalaxy.Undo {
throw new NotSupportedException("Non-optimised binary undo files have been deprecated"); throw new NotSupportedException("Non-optimised binary undo files have been deprecated");
} }
protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) { protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
List<ChunkHeader> list = new List<ChunkHeader>(); List<ChunkHeader> list = new List<ChunkHeader>();
Player.UndoPos pos; UndoFormatEntry pos;
bool super = args.Player == null || args.Player.ircNick != null; bool super = args.Player == null || args.Player.ircNick != null;
DateTime start = args.StartRange; DateTime start = args.Start;
ReadHeaders(list, s); ReadHeaders(list, s);
for (int i = list.Count - 1; i >= 0; i--) { for (int i = list.Count - 1; i >= 0; i--) {
@ -48,7 +48,7 @@ namespace MCGalaxy.Undo {
bool inRange = chunk.BaseTime.AddTicks(65536 * TimeSpan.TicksPerSecond) >= start; bool inRange = chunk.BaseTime.AddTicks(65536 * TimeSpan.TicksPerSecond) >= start;
if (!inRange) { args.Stop = true; yield break; } if (!inRange) { args.Stop = true; yield break; }
if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue; if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue;
pos.mapName = chunk.LevelName; pos.LevelName = chunk.LevelName;
s.Seek(chunk.DataPosition, SeekOrigin.Begin); s.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (args.Temp == null) if (args.Temp == null)
@ -58,16 +58,15 @@ namespace MCGalaxy.Undo {
for (int j = chunk.Entries - 1; j >= 0; j-- ) { for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize; int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddTicks(U16(temp, offset + 0) * TimeSpan.TicksPerSecond); pos.Time = chunk.BaseTime.AddTicks(U16(temp, offset + 0) * TimeSpan.TicksPerSecond);
if (time < start) { args.Stop = true; yield break; } if (pos.Time < start) { args.Stop = true; yield break; }
pos.timeDelta = (int)time.Subtract(Server.StartTime).TotalSeconds;
pos.x = U16(temp, offset + 2); pos.X = U16(temp, offset + 2);
pos.y = U16(temp, offset + 4); pos.Y = U16(temp, offset + 4);
pos.z = U16(temp, offset + 6); pos.Z = U16(temp, offset + 6);
pos.type = temp[offset + 8]; pos.extType = temp[offset + 9]; pos.Block = temp[offset + 8]; pos.ExtBlock = temp[offset + 9];
pos.newtype = temp[offset + 10]; pos.newExtType = temp[offset + 11]; pos.NewBlock = temp[offset + 10]; pos.NewExtBlock = temp[offset + 11];
yield return pos; yield return pos;
} }
} }

View File

@ -98,12 +98,12 @@ namespace MCGalaxy.Undo {
} }
} }
protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) { protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
List<ChunkHeader> list = new List<ChunkHeader>(); List<ChunkHeader> list = new List<ChunkHeader>();
Player.UndoPos pos; UndoFormatEntry pos;
UndoCacheItem item = default(UndoCacheItem); UndoCacheItem item = default(UndoCacheItem);
bool super = args.Player == null || args.Player.ircNick != null; bool super = args.Player == null || args.Player.ircNick != null;
DateTime start = args.StartRange; DateTime start = args.Start;
ReadHeaders(list, s); ReadHeaders(list, s);
for (int i = list.Count - 1; i >= 0; i--) { for (int i = list.Count - 1; i >= 0; i--) {
@ -112,7 +112,7 @@ namespace MCGalaxy.Undo {
bool inRange = chunk.BaseTime.AddTicks((65536 >> 2) * TimeSpan.TicksPerSecond) >= start; bool inRange = chunk.BaseTime.AddTicks((65536 >> 2) * TimeSpan.TicksPerSecond) >= start;
if (!inRange) { args.Stop = true; yield break; } if (!inRange) { args.Stop = true; yield break; }
if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue; if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue;
pos.mapName = chunk.LevelName; pos.LevelName = chunk.LevelName;
s.Seek(chunk.DataPosition, SeekOrigin.Begin); s.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (args.Temp == null) if (args.Temp == null)
@ -123,19 +123,18 @@ namespace MCGalaxy.Undo {
for (int j = chunk.Entries - 1; j >= 0; j-- ) { for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize; int offset = j * entrySize;
item.Flags = U16(temp, offset + 0); item.Flags = U16(temp, offset + 0);
DateTime time = chunk.BaseTime.AddTicks((item.Flags & 0x3FFF) * TimeSpan.TicksPerSecond); pos.Time = chunk.BaseTime.AddTicks((item.Flags & 0x3FFF) * TimeSpan.TicksPerSecond);
if (time < start) { args.Stop = true; yield break; } if (pos.Time < start) { args.Stop = true; yield break; }
pos.timeDelta = (int)time.Subtract(Server.StartTime).TotalSeconds;
int index = I32(temp, offset + 2); int index = I32(temp, offset + 2);
pos.x = (ushort)(index % chunk.Width); pos.X = (ushort)(index % chunk.Width);
pos.y = (ushort)((index / chunk.Width) / chunk.Length); pos.Y = (ushort)((index / chunk.Width) / chunk.Length);
pos.z = (ushort)((index / chunk.Width) % chunk.Length); pos.Z = (ushort)((index / chunk.Width) % chunk.Length);
item.Type = temp[offset + 6]; item.Type = temp[offset + 6];
item.NewType = temp[offset + 7]; item.NewType = temp[offset + 7];
item.GetBlock(out pos.type, out pos.extType); item.GetBlock(out pos.Block, out pos.ExtBlock);
item.GetNewBlock(out pos.newtype, out pos.newExtType); item.GetNewBlock(out pos.NewBlock, out pos.NewExtBlock);
yield return pos; yield return pos;
} }
} }

View File

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

View File

@ -34,32 +34,31 @@ namespace MCGalaxy.Undo {
throw new NotSupportedException("Text undo files have been deprecated"); throw new NotSupportedException("Text undo files have been deprecated");
} }
protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) { protected override IEnumerable<UndoFormatEntry> GetEntries(Stream s, UndoFormatArgs args) {
Player.UndoPos pos; UndoFormatEntry pos;
pos.newExtType = 0; pos.extType = 0; pos.NewExtBlock = 0; pos.ExtBlock = 0;
string[] lines = new StreamReader(s).ReadToEnd().Split(' '); string[] lines = new StreamReader(s).ReadToEnd().Split(' ');
Player p = args.Player; Player p = args.Player;
bool super = p == null || p.ircNick != null; bool super = p == null || p.ircNick != null;
DateTime start = args.StartRange; DateTime start = args.Start;
// because we have space to end of each entry, need to subtract one otherwise we'll start at a "". // 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--) { for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
// line format: mapName x y z date oldblock newblock // line format: mapName x y z date oldblock newblock
string timeRaw = lines[(i * 7) - 3].Replace('&', ' '); string timeRaw = lines[(i * 7) - 3].Replace('&', ' ');
DateTime time = DateTime.Parse(timeRaw, CultureInfo.InvariantCulture); pos.Time = DateTime.Parse(timeRaw, CultureInfo.InvariantCulture);
if (time < start) { args.Stop = true; yield break; } if (pos.Time < start) { args.Stop = true; yield break; }
pos.timeDelta = (int)time.Subtract(Server.StartTimeLocal).TotalSeconds;
string map = lines[(i * 7) - 7]; string map = lines[(i * 7) - 7];
if (!super && !p.level.name.CaselessEq(map)) continue; if (!super && !p.level.name.CaselessEq(map)) continue;
pos.mapName = map; pos.LevelName = map;
pos.x = Convert.ToUInt16(lines[(i * 7) - 6]); pos.X = Convert.ToUInt16(lines[(i * 7) - 6]);
pos.y = Convert.ToUInt16(lines[(i * 7) - 5]); pos.Y = Convert.ToUInt16(lines[(i * 7) - 5]);
pos.z = Convert.ToUInt16(lines[(i * 7) - 4]); pos.Z = Convert.ToUInt16(lines[(i * 7) - 4]);
pos.newtype = Convert.ToByte(lines[(i * 7) - 1]); pos.NewBlock = Convert.ToByte(lines[(i * 7) - 1]);
pos.type = Convert.ToByte(lines[(i * 7) - 2]); pos.Block = Convert.ToByte(lines[(i * 7) - 2]);
yield return pos; yield return pos;
} }
} }