Merge branch 'UndoCleanup'

This commit is contained in:
UnknownShadow200 2016-02-25 22:45:13 +11:00
commit f8e83786c2
10 changed files with 268 additions and 86 deletions

View File

@ -16,6 +16,7 @@
permissions and limitations under the Licenses.
*/
using System;
using System.Collections.Generic;
using System.IO;
using MCGalaxy.Util;
@ -52,7 +53,7 @@ namespace MCGalaxy.Commands {
Player who = PlayerInfo.Find(name);
if (who != null) {
FoundUser = true;
HighlightOnline(p, seconds, who);
PerformHighlight(p, seconds, who.UndoBuffer);
}
UndoFile.HighlightPlayer(p, name.ToLower(), seconds, ref FoundUser);
@ -63,25 +64,32 @@ namespace MCGalaxy.Commands {
Player.SendMessage(p, "Could not find player specified.");
}
}
static void HighlightOnline(Player p, long seconds, Player who) {
for (int i = who.UndoBuffer.Count - 1; i >= 0; --i) {
try {
Player.UndoPos undo = who.UndoBuffer[i];
Level foundLevel = LevelInfo.FindExact(undo.mapName);
if (foundLevel != p.level) continue;
static void PerformHighlight(Player p, long seconds, UndoCache cache) {
UndoCacheNode node = cache.Tail;
if (node == null) return;
while (node != null) {
Level lvl = LevelInfo.FindExact(node.MapName);
if (lvl != p.level) continue;
List<UndoCacheItem> items = node.Items;
for (int i = items.Count - 1; i >= 0; i--) {
UndoCacheItem item = items[i];
ushort x, y, z;
node.Unpack(item.Index, out x, out y, out z);
DateTime time = node.BaseTime.AddSeconds(item.TimeDelta + seconds);
if (time < DateTime.UtcNow) return;
byte b = foundLevel.GetTile(undo.x, undo.y, undo.z);
DateTime time = Server.StartTime.AddSeconds(undo.timeDelta + seconds);
if (time < DateTime.UtcNow) break;
if (b == undo.newtype || Block.Convert(b) == Block.water || Block.Convert(b) == Block.lava) {
byte b = lvl.GetTile(x, y, z);
if (b == item.NewType || Block.Convert(b) == Block.water || Block.Convert(b) == Block.lava) {
if (b == Block.air || Block.Convert(b) == Block.water || Block.Convert(b) == Block.lava)
p.SendBlockchange(undo.x, undo.y, undo.z, Block.red);
p.SendBlockchange(x, y, z, Block.red);
else
p.SendBlockchange(undo.x, undo.y, undo.z, Block.green);
p.SendBlockchange(x, y, z, Block.green);
}
} catch { }
}
node = node.Prev;
}
}

View File

@ -16,6 +16,8 @@
permissions and limitations under the Licenses.
*/
using System;
using System.Collections.Generic;
using MCGalaxy.Util;
namespace MCGalaxy.Commands {
@ -30,21 +32,32 @@ namespace MCGalaxy.Commands {
public override void Use(Player p, string message) {
if (message != "") { Help(p); return; }
for (int i = p.RedoBuffer.Count - 1; i >= 0; i--) {
Player.UndoPos Pos = p.RedoBuffer[i];
Level lvl = LevelInfo.FindExact(Pos.mapName);
if (lvl == null)
continue;
byte type = lvl.GetTile(Pos.x, Pos.y, Pos.z), extType = 0;
if (type == Block.custom_block)
extType = lvl.GetExtTile(Pos.x, Pos.y, Pos.z);
lvl.Blockchange(p, Pos.x, Pos.y, Pos.z, Pos.type, Pos.extType);
}
PerformRedo(p, p.RedoBuffer);
Player.SendMessage(p, "Redo performed.");
}
static void PerformRedo(Player p, UndoCache cache) {
UndoCacheNode node = cache.Tail;
if (node == null) return;
while (node != null) {
Level lvl = LevelInfo.FindExact(node.MapName);
if (lvl == null) continue;
List<UndoCacheItem> items = node.Items;
for (int i = items.Count - 1; i >= 0; i--) {
UndoCacheItem item = items[i];
ushort x, y, z;
node.Unpack(item.Index, out x, out y, out z);
byte type = lvl.GetTile(x, y, z), extType = 0;
if (type == Block.custom_block)
extType = lvl.GetExtTile(x, y, z);
lvl.Blockchange(p, x, y, z, item.Type, item.ExtType);
}
node = node.Prev;
}
}
public override void Help(Player p) {
Player.SendMessage(p, "/redo - Redoes the Undo you just performed.");

View File

@ -1,7 +1,7 @@
/*
Copyright 2011 MCGalaxy
Dual-licensed under the Educational Community License, Version 2.0 and
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
@ -16,8 +16,7 @@
permissions and limitations under the Licenses.
*/
using System;
using System.Globalization;
using System.IO;
using System.Collections.Generic;
using MCGalaxy.Util;
namespace MCGalaxy.Commands
@ -89,12 +88,7 @@ namespace MCGalaxy.Commands
}
Level saveLevel = null;
for (int i = who.UndoBuffer.Count - 1; i >= 0; --i) {
try {
Player.UndoPos Pos = who.UndoBuffer[i];
if (!CheckBlockPlayer(p, seconds, Pos, ref saveLevel)) break;
} catch { }
}
PerformUndo(p, seconds, who.UndoBuffer, ref saveLevel);
bool foundUser = false;
UndoFile.UndoPlayer(p, whoName.ToLower(), seconds, ref foundUser);
@ -159,24 +153,41 @@ namespace MCGalaxy.Commands
p.level.Save(true);
}
bool CheckBlockPlayer(Player p, long seconds, Player.UndoPos undo, ref Level saveLevel) {
Level lvl = LevelInfo.FindExact(undo.mapName);
saveLevel = lvl;
byte b = lvl.GetTile(undo.x, undo.y, undo.z);
DateTime time = Server.StartTime.AddSeconds(undo.timeDelta + seconds);
if (time < DateTime.UtcNow) return false;
static void PerformUndo(Player p, long seconds, UndoCache cache, ref Level saveLvl) {
UndoCacheNode node = cache.Tail;
if (node == null) return;
if (b == undo.newtype || Block.Convert(b) == Block.water || Block.Convert(b) == Block.lava) {
undo.newtype = undo.type; undo.newExtType = undo.extType;
byte extType = 0;
if (b == Block.custom_block)
extType = lvl.GetExtTile(undo.x, undo.y, undo.z);
while (node != null) {
Level lvl = LevelInfo.FindExact(node.MapName);
if (lvl == null) continue;
saveLvl = lvl;
List<UndoCacheItem> items = node.Items;
lvl.Blockchange(p, undo.x, undo.y, undo.z, undo.type, undo.extType);
undo.type = b; undo.extType = extType;
if (p != null) p.RedoBuffer.Add(undo);
for (int i = items.Count - 1; i >= 0; i--) {
UndoCacheItem item = items[i];
ushort x, y, z;
node.Unpack(item.Index, out x, out y, out z);
DateTime time = node.BaseTime.AddSeconds(item.TimeDelta + seconds);
if (time < DateTime.UtcNow) return;
byte b = lvl.GetTile(x, y, z);
if (b == item.NewType || Block.Convert(b) == Block.water || Block.Convert(b) == Block.lava) {
Player.UndoPos uP = default(Player.UndoPos);
uP.newtype = item.Type; uP.newExtType = item.ExtType;
byte extType = 0;
if (b == Block.custom_block) extType = lvl.GetExtTile(x, y, z);
lvl.Blockchange(p, x, y, z, item.Type, item.ExtType);
uP.type = b; uP.extType = extType;
uP.x = x; uP.y = y; uP.z = z;
uP.mapName = node.MapName;
time = node.BaseTime.AddSeconds(item.TimeDelta);
uP.timeDelta = (int)time.Subtract(Server.StartTime).TotalSeconds;
if (p != null) p.RedoBuffer.Add(lvl, uP);
}
}
node = node.Prev;
}
return true;
}
bool CheckBlockPhysics(Player p, long seconds, int i, Level.UndoPos undo) {

View File

@ -160,7 +160,7 @@ namespace MCGalaxy {
Pos.type = oldType; Pos.extType = oldExtType;
Pos.newtype = type; Pos.newExtType = extType;
Pos.timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds;
p.UndoBuffer.Add(Pos);
p.UndoBuffer.Add(this, Pos);
}
bool CheckTNTWarsChange(Player p, ushort x, ushort y, ushort z, ref byte type) {
@ -328,7 +328,7 @@ namespace MCGalaxy {
Pos.type = b; Pos.extType = extB;
Pos.newtype = type; Pos.newExtType = extType;
Pos.timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds;
p.UndoBuffer.Add(Pos);
p.UndoBuffer.Add(this, Pos);
errorLocation = "Setting tile";
p.loginBlocks++;

View File

@ -443,6 +443,7 @@
<Compile Include="Player\Group\GroupProperties.cs" />
<Compile Include="Player\Player.Handlers.cs" />
<Compile Include="Player\PlayerInfo.cs" />
<Compile Include="Player\Undo\UndoCache.cs" />
<Compile Include="Player\Waypoint.cs" />
<Compile Include="Player\Player.Timers.cs" />
<Compile Include="Player\Undo\UndoFile.cs" />

View File

@ -235,8 +235,8 @@ namespace MCGalaxy {
//Undo
public struct UndoPos { public ushort x, y, z; public byte type, extType, newtype, newExtType; public string mapName; public int timeDelta; }
public List<UndoPos> UndoBuffer = new List<UndoPos>();
public List<UndoPos> RedoBuffer = new List<UndoPos>();
public UndoCache UndoBuffer = new UndoCache();
public UndoCache RedoBuffer = new UndoCache();
public bool showPortals = false;

111
Player/Undo/UndoCache.cs Normal file
View File

@ -0,0 +1,111 @@
/*
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.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace MCGalaxy.Util {
public sealed class UndoCache {
/// <summary> The oldest/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> Appends an item to the cache. </summary>
public void Add(Level lvl, Player.UndoPos item) {
DateTime time = Server.StartTime.AddSeconds(item.timeDelta);
if (Head == null) {
Head = UndoCacheNode.Make(lvl, time);
Tail = Head;
}
if (lvl.name != Tail.MapName || lvl.Width != Tail.Width || lvl.Height != Tail.Height ||
lvl.Length != Tail.Length || Math.Abs((time - Tail.BaseTime).TotalSeconds) > 32767) {
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( Head == null ) return;
UndoCacheNode node = Head;
while( node != null ) {
node.Items.Clear();
node = node.Next;
}
Head = null; Tail = 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, ExtType;
public byte NewType, NewExtType;
public short TimeDelta;
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.Type = pos.type; item.ExtType = pos.extType;
item.NewType = pos.newtype; item.NewExtType = pos.newExtType;
item.TimeDelta = timeDelta;
return item;
}
}
}

View File

@ -30,6 +30,8 @@ namespace MCGalaxy.Util {
protected abstract void SaveUndoData(List<Player.UndoPos> buffer, string path);
protected abstract void SaveUndoData(UndoCache buffer, string path);
protected abstract void ReadUndoData(List<Player.UndoPos> buffer, string path);
protected abstract bool UndoEntry(Player p, string path, ref byte[] temp, long seconds);
@ -39,7 +41,7 @@ namespace MCGalaxy.Util {
protected abstract string Extension { get; }
public static void SaveUndo(Player p) {
if( p == null || p.UndoBuffer == null || p.UndoBuffer.Count < 1) return;
if( p == null || p.UndoBuffer.Count < 1) return;
CreateDefaultDirectories();
if (Directory.GetDirectories(undoDir).Length >= Server.totalUndo) {
@ -54,7 +56,7 @@ namespace MCGalaxy.Util {
int numFiles = Directory.GetFiles(playerDir).Length;
string path = Path.Combine(playerDir, numFiles + NewFormat.Extension);
NewFormat.SaveUndoData(p.UndoBuffer.ToList(), path);
NewFormat.SaveUndoData(p.UndoBuffer, path);
}
public static void UndoPlayer(Player p, string targetName, long seconds, ref bool FoundUser) {
@ -96,11 +98,11 @@ namespace MCGalaxy.Util {
}
static int CompareFiles(string a, string b) {
int aNumStart = a.LastIndexOf('\\'), bNumStart = b.LastIndexOf('\\');
int aNumStart = a.LastIndexOf('\\'), bNumStart = b.LastIndexOf('\\');
int aNumEnd = a.LastIndexOf('.'), bNumEnd = b.LastIndexOf('.');
if (aNumStart < 0 || bNumStart < 0 || aNumEnd < 0 ||
bNumEnd < 0 || aNumStart >= aNumEnd || bNumStart >= bNumEnd)
return a.CompareTo(b);
return a.CompareTo(b);
int aNum, bNum;
if (!int.TryParse(a.Substring(aNumStart + 1, aNumEnd - aNumStart - 1), out aNum) ||

View File

@ -32,24 +32,57 @@ namespace MCGalaxy.Util {
using (FileStream fs = File.Create(path)) {
BinaryWriter w = new BinaryWriter(fs);
long entriesPos = 0;
ChunkHeader lastChunk = default(ChunkHeader);
ChunkHeader last = default(ChunkHeader);
foreach (Player.UndoPos uP in buffer) {
DateTime time = Server.StartTime.AddSeconds(uP.timeDelta);
int timeDiff = (int)(time - 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);
int timeDiff = (int)(time - last.BaseTime).TotalSeconds;
if (last.LevelName != uP.mapName || timeDiff > 65535 || last.Entries == ushort.MaxValue) {
WriteChunkEntries(w, last.Entries, entriesPos);
last = WriteEmptyChunk(w, uP.mapName, time, ref entriesPos);
}
w.Write((ushort)timeDiff);
w.Write(uP.x); w.Write(uP.y); w.Write(uP.z);
w.Write(uP.type); w.Write(uP.extType);
w.Write(uP.newtype); w.Write(uP.newExtType);
lastChunk.Entries++;
last.Entries++;
}
if (last.Entries > 0)
WriteChunkEntries(w, last.Entries, entriesPos);
}
}
protected override void SaveUndoData(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.Tail;
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 || last.Entries == ushort.MaxValue) {
WriteChunkEntries(w, last.Entries, entriesPos);
last = WriteEmptyChunk(w, node.MapName, time, ref entriesPos);
}
ushort x, y, z;
node.Unpack(uP.Index, out x, out y, out z);
w.Write((ushort)timeDiff);
w.Write(x); w.Write(y); w.Write(z);
w.Write(uP.Type); w.Write(uP.ExtType);
w.Write(uP.NewType); w.Write(uP.NewExtType);
last.Entries++;
}
if (last.Entries > 0)
WriteChunkEntries(w, last.Entries, entriesPos);
node = node.Prev;
}
if (lastChunk.Entries > 0)
WriteChunkEntries(w, lastChunk.Entries, entriesPos);
}
}
@ -102,8 +135,8 @@ namespace MCGalaxy.Util {
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddSeconds(U16(temp, offset + 0));
int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddSeconds(U16(temp, offset + 0));
if (time.AddSeconds(seconds) < now) return false;
Pos.x = U16(temp, offset + 2); Pos.y = U16(temp, offset + 4); Pos.z = U16(temp, offset + 6);
@ -117,7 +150,7 @@ namespace MCGalaxy.Util {
Pos.newtype = oldType; Pos.newExtType = oldExtType;
Pos.extType = newExtType; Pos.timeDelta = timeDelta;
lvl.Blockchange(p, Pos.x, Pos.y, Pos.z, Pos.newtype, Pos.newExtType);
if (p != null) p.RedoBuffer.Add(Pos);
if (p != null) p.RedoBuffer.Add(lvl, Pos);
}
}
}
@ -145,8 +178,8 @@ namespace MCGalaxy.Util {
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddSeconds(U16(temp, offset + 0));
int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddSeconds(U16(temp, offset + 0));
if (time.AddSeconds(seconds) < now) return false;
ushort x = U16(temp, offset + 2), y = U16(temp, offset + 4), z = U16(temp, offset + 6);
@ -167,7 +200,7 @@ namespace MCGalaxy.Util {
}
static ushort U16(byte[] buffer, int offset) {
return (ushort)(buffer[offset + 0] | buffer[offset + 1] << 8);
return (ushort)(buffer[offset + 0] | buffer[offset + 1] << 8);
}
static bool CheckChunk(ChunkHeader chunk, DateTime now, long seconds, Player p, out Level lvl) {
@ -216,9 +249,8 @@ namespace MCGalaxy.Util {
w.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
static ChunkHeader WriteEmptyChunk(BinaryWriter w, Player.UndoPos uP, ref long entriesPos) {
DateTime time = Server.StartTime.AddSeconds(uP.timeDelta);
byte[] mapBytes = Encoding.UTF8.GetBytes(uP.mapName);
static ChunkHeader WriteEmptyChunk(BinaryWriter w, string mapName, DateTime time, ref long entriesPos) {
byte[] mapBytes = Encoding.UTF8.GetBytes(mapName);
w.Write((ushort)mapBytes.Length);
w.Write(mapBytes);
w.Write(time.ToLocalTime().Ticks);
@ -226,7 +258,7 @@ namespace MCGalaxy.Util {
entriesPos = w.BaseStream.Position;
w.Write((ushort)0);
ChunkHeader header = default(ChunkHeader);
header.LevelName = uP.mapName; header.BaseTime = time;
header.LevelName = mapName; header.BaseTime = time;
return header;
}
}

View File

@ -30,7 +30,7 @@ namespace MCGalaxy.Util {
protected override void SaveUndoData(List<Player.UndoPos> buffer, string path) {
using (StreamWriter w = File.CreateText(path)) {
foreach (Player.UndoPos uP in buffer) {
DateTime time = Server.StartTimeLocal.AddSeconds(uP.timeDelta);
DateTime time = Server.StartTimeLocal.AddSeconds(uP.timeDelta);
w.Write(
uP.mapName + " " + uP.x + " " + uP.y + " " + uP.z + " " +
time.ToString(CultureInfo.InvariantCulture).Replace(' ', '&') + " " +
@ -39,6 +39,10 @@ namespace MCGalaxy.Util {
}
}
protected override void SaveUndoData(UndoCache buffer, string path) {
throw new NotImplementedException("The .txt based undo files are deprecated.");
}
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) {
Player.UndoPos Pos;
Pos.extType = 0; Pos.newExtType = 0;
@ -74,14 +78,14 @@ namespace MCGalaxy.Util {
try {
// line format: mapName x y z date oldblock newblock
if (!InTime(lines[(i * 7) - 3], seconds)) return false;
Level foundLevel = LevelInfo.FindExact(lines[(i * 7) - 7]);
if (foundLevel == null) continue;
Level lvl = LevelInfo.FindExact(lines[(i * 7) - 7]);
if (lvl == null) continue;
Pos.mapName = foundLevel.name;
Pos.mapName = lvl.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);
Pos.type = lvl.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) {
@ -89,8 +93,8 @@ namespace MCGalaxy.Util {
Pos.newtype = Convert.ToByte(lines[(i * 7) - 2]);
Pos.timeDelta = timeDelta;
foundLevel.Blockchange(p, Pos.x, Pos.y, Pos.z, Pos.newtype, 0);
if (p != null) p.RedoBuffer.Add(Pos);
lvl.Blockchange(p, Pos.x, Pos.y, Pos.z, Pos.newtype, 0);
if (p != null) p.RedoBuffer.Add(lvl, Pos);
}
} catch {
}