diff --git a/Commands/Moderation/CmdHighlight.cs b/Commands/Moderation/CmdHighlight.cs index 74af538cc..ca292e5b5 100644 --- a/Commands/Moderation/CmdHighlight.cs +++ b/Commands/Moderation/CmdHighlight.cs @@ -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 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; } } diff --git a/Commands/building/CmdRedo.cs b/Commands/building/CmdRedo.cs index 16b96bf43..879f57c30 100644 --- a/Commands/building/CmdRedo.cs +++ b/Commands/building/CmdRedo.cs @@ -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 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."); diff --git a/Commands/building/CmdUndo.cs b/Commands/building/CmdUndo.cs index d82cba7af..61ec2a5a8 100644 --- a/Commands/building/CmdUndo.cs +++ b/Commands/building/CmdUndo.cs @@ -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 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) { diff --git a/Levels/Level.Blocks.cs b/Levels/Level.Blocks.cs index 54ff4b4aa..15cf10385 100644 --- a/Levels/Level.Blocks.cs +++ b/Levels/Level.Blocks.cs @@ -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++; diff --git a/MCGalaxy_.csproj b/MCGalaxy_.csproj index bc56de803..9ec34d5b6 100644 --- a/MCGalaxy_.csproj +++ b/MCGalaxy_.csproj @@ -443,6 +443,7 @@ + diff --git a/Player/Player.cs b/Player/Player.cs index d19c3991e..7290ef6c4 100644 --- a/Player/Player.cs +++ b/Player/Player.cs @@ -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 UndoBuffer = new List(); - public List RedoBuffer = new List(); + public UndoCache UndoBuffer = new UndoCache(); + public UndoCache RedoBuffer = new UndoCache(); public bool showPortals = false; diff --git a/Player/Undo/UndoCache.cs b/Player/Undo/UndoCache.cs new file mode 100644 index 000000000..095e2c957 --- /dev/null +++ b/Player/Undo/UndoCache.cs @@ -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 { + + /// The oldest/first node in the cache. + public UndoCacheNode Head; + + /// The newest/last node in the cache. + public UndoCacheNode Tail; + + /// Total number of items in the cache. + public volatile int Count; + + /// Appends an item to the cache. + 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++; + } + + /// Removes all items from the cache and resets the state to default. + 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 Items = new List(); + + 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; + } + } +} diff --git a/Player/Undo/UndoFile.cs b/Player/Undo/UndoFile.cs index 01a2b212f..b69b580a8 100644 --- a/Player/Undo/UndoFile.cs +++ b/Player/Undo/UndoFile.cs @@ -30,6 +30,8 @@ namespace MCGalaxy.Util { protected abstract void SaveUndoData(List buffer, string path); + protected abstract void SaveUndoData(UndoCache buffer, string path); + protected abstract void ReadUndoData(List 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) || diff --git a/Player/Undo/UndoFileBin.cs b/Player/Undo/UndoFileBin.cs index 6f49cc9ee..279280882 100644 --- a/Player/Undo/UndoFileBin.cs +++ b/Player/Undo/UndoFileBin.cs @@ -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 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; } } diff --git a/Player/Undo/UndoFileText.cs b/Player/Undo/UndoFileText.cs index fd4facec4..a96d2245f 100644 --- a/Player/Undo/UndoFileText.cs +++ b/Player/Undo/UndoFileText.cs @@ -30,7 +30,7 @@ namespace MCGalaxy.Util { protected override void SaveUndoData(List 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 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 { }