mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-09-21 11:29:42 -04:00
Merge with master.
This commit is contained in:
commit
e12ca9a116
@ -18,6 +18,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using MCGalaxy.Util;
|
||||
|
||||
namespace MCGalaxy.Commands
|
||||
{
|
||||
public sealed class CmdUndo : Command
|
||||
@ -36,29 +38,21 @@ namespace MCGalaxy.Commands
|
||||
byte b; long seconds = -2; Player who = null; Player.UndoPos Pos; int CurrentPos = 0; bool undoPhysics = false; string whoName = String.Empty;
|
||||
if (p != null)
|
||||
p.RedoBuffer.Clear();
|
||||
|
||||
if (message == "")
|
||||
{
|
||||
if (p == null)
|
||||
{
|
||||
Player.SendMessage(null, "Console doesn't have an undo buffer.");
|
||||
return;
|
||||
if (message == "") {
|
||||
if (p == null) {
|
||||
Player.SendMessage(null, "Console doesn't have an undo buffer."); return;
|
||||
}
|
||||
message = p.name.ToLower() + " 30";
|
||||
}
|
||||
string[] parts = message.Split(' ');
|
||||
|
||||
try
|
||||
{
|
||||
if (message.Split(' ').Length > 1)
|
||||
{
|
||||
whoName = message.Split(' ')[0];
|
||||
who = message.Split(' ')[0].ToLower() == "physics" ? null : Player.Find(message.Split(' ')[0]);
|
||||
undoPhysics = message.Split(' ')[0].ToLower() == "physics";
|
||||
message = message.Split(' ')[1].ToLower();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
try {
|
||||
if (parts.Length > 1) {
|
||||
whoName = parts[0];
|
||||
who = parts[0].ToLower() == "physics" ? null : Player.Find(parts[0]);
|
||||
undoPhysics = parts[0].ToLower() == "physics";
|
||||
message = parts[1].ToLower();
|
||||
} else {
|
||||
who = (p == null || message.ToLower() == "physics") ? null : p;
|
||||
undoPhysics = message.ToLower() == "physics";
|
||||
}
|
||||
@ -68,9 +62,12 @@ namespace MCGalaxy.Commands
|
||||
seconds = ((message.ToLower() != "all") ? long.Parse(message) : int.MaxValue);
|
||||
else
|
||||
seconds = getAllowed(p, message.ToLower());
|
||||
}
|
||||
catch
|
||||
{
|
||||
} catch {
|
||||
if (parts.Length > 1 && parts[1].ToLower() == "update") {
|
||||
UndoFile.UpgradePlayerUndoFiles(whoName);
|
||||
Player.SendMessage(p, "Updated undo files for " + whoName + " to the new binary format.");
|
||||
return;
|
||||
}
|
||||
Player.SendMessage(p, "Invalid seconds, or you're unable to use /xundo. Using 30 seconds."); //only run if seconds is an invalid number
|
||||
seconds = 30;
|
||||
}
|
||||
@ -202,10 +199,9 @@ namespace MCGalaxy.Commands
|
||||
}
|
||||
|
||||
bool FoundUser = false;
|
||||
|
||||
try
|
||||
{
|
||||
undoOfflineHelper(p, whoName, seconds, ref FoundUser);
|
||||
UndoFile.UndoPlayer(p, whoName.ToLower(), seconds, ref FoundUser);
|
||||
|
||||
if (FoundUser)
|
||||
{
|
||||
@ -241,85 +237,9 @@ namespace MCGalaxy.Commands
|
||||
}
|
||||
return secs;
|
||||
}
|
||||
|
||||
//Fixed by QuantumHive
|
||||
public bool undoOffline(string[] fileContent, long seconds, Player p)
|
||||
{
|
||||
|
||||
Player.UndoPos Pos;
|
||||
|
||||
//-1 because the last element in the array is an empty string "" go check Player.SaveUndo() if you wanna know why
|
||||
for (int i = (fileContent.Length - 1) / 7; i >= 0; i--)
|
||||
{
|
||||
try
|
||||
{
|
||||
string datetime = fileContent[(i * 7) - 3];
|
||||
datetime = datetime.Replace('&', ' ');
|
||||
DateTime time = DateTime.Parse(datetime, CultureInfo.InvariantCulture);
|
||||
time = time.AddSeconds(seconds);
|
||||
if (time < DateTime.Now)
|
||||
//if (Convert.ToDateTime(fileContent[(i * 7) - 3].Replace('&', ' ')).AddSeconds(seconds) < DateTime.Now)
|
||||
return false;
|
||||
|
||||
Level foundLevel = Level.FindExact(fileContent[(i * 7) - 7]);
|
||||
if (foundLevel != null)
|
||||
{
|
||||
Pos.mapName = foundLevel.name;
|
||||
Pos.x = Convert.ToUInt16(fileContent[(i * 7) - 6]);
|
||||
Pos.y = Convert.ToUInt16(fileContent[(i * 7) - 5]);
|
||||
Pos.z = Convert.ToUInt16(fileContent[(i * 7) - 4]);
|
||||
|
||||
Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
|
||||
|
||||
if (Pos.type == Convert.ToByte(fileContent[(i * 7) - 1]) ||
|
||||
Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava ||
|
||||
Pos.type == Block.grass)
|
||||
{
|
||||
Pos.newtype = Convert.ToByte(fileContent[(i * 7) - 2]);
|
||||
Pos.timePlaced = DateTime.Now;
|
||||
|
||||
foundLevel.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
|
||||
if (p != null)
|
||||
p.RedoBuffer.Add(Pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private void undoOfflineHelper(Player p, string whoName, long seconds, ref bool FoundUser)
|
||||
{
|
||||
DirectoryInfo di;
|
||||
string[] fileContent;
|
||||
|
||||
if (p != null)
|
||||
p.RedoBuffer.Clear();
|
||||
|
||||
if (Directory.Exists("extra/undo/" + whoName.ToLower()))
|
||||
{
|
||||
di = new DirectoryInfo("extra/undo/" + whoName.ToLower());
|
||||
|
||||
for (int i = di.GetFiles("*.undo").Length - 1; i >= 0; i--)
|
||||
{
|
||||
fileContent = File.ReadAllText("extra/undo/" + whoName.ToLower() + "/" + i + ".undo").Split();
|
||||
if (!undoOffline(fileContent, seconds, p)) break;
|
||||
}
|
||||
FoundUser = true;
|
||||
}
|
||||
|
||||
if (Directory.Exists("extra/undoPrevious/" + whoName.ToLower()))
|
||||
{
|
||||
di = new DirectoryInfo("extra/undoPrevious/" + whoName.ToLower());
|
||||
|
||||
for (int i = di.GetFiles("*.undo").Length - 1; i >= 0; i--)
|
||||
{
|
||||
fileContent = File.ReadAllText("extra/undoPrevious/" + whoName.ToLower() + "/" + i + ".undo").Split();
|
||||
if (!undoOffline(fileContent, seconds, p)) break;
|
||||
}
|
||||
FoundUser = true;
|
||||
}
|
||||
|
||||
private void undoOfflineHelper(Player p, string whoName, long seconds, ref bool FoundUser) {
|
||||
UndoFile.UndoPlayer(p, whoName.ToLower(), seconds, ref FoundUser);
|
||||
}
|
||||
|
||||
public override void Help(Player p)
|
||||
|
@ -443,8 +443,11 @@
|
||||
<Compile Include="Levels\Physics\TntPhysics.cs" />
|
||||
<Compile Include="Levels\Physics\ZombiePhysics.cs" />
|
||||
<Compile Include="Player\Chat.cs" />
|
||||
<Compile Include="Player\Player.Timers.cs" />
|
||||
<Compile Include="Player\Waypoint.cs" />
|
||||
<Compile Include="Player\Player.Timers.cs" />
|
||||
<Compile Include="Player\Undo\UndoFile.cs" />
|
||||
<Compile Include="Player\Undo\UndoFileBin.cs" />
|
||||
<Compile Include="Player\Undo\UndoFileText.cs" />
|
||||
<Compile Include="Plugins\Enums.cs" />
|
||||
<Compile Include="Plugins\Events\GroupEvents.cs" />
|
||||
<Compile Include="Plugins\Events\LevelEvents.cs" />
|
||||
@ -727,6 +730,7 @@
|
||||
<Folder Include="Drawing\DrawOps" />
|
||||
<Folder Include="Levels\IO" />
|
||||
<Folder Include="Levels\Physics" />
|
||||
<Folder Include="Player\Undo" />
|
||||
<Folder Include="Plugins" />
|
||||
<Folder Include="Plugins\Events" />
|
||||
<Folder Include="Plugins\Manager" />
|
||||
|
@ -26,6 +26,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using MCGalaxy.Drawing;
|
||||
using MCGalaxy.SQL;
|
||||
using MCGalaxy.Util;
|
||||
|
||||
namespace MCGalaxy {
|
||||
public sealed partial class Player : IDisposable {
|
||||
@ -2752,35 +2753,14 @@ level.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveUndo() {
|
||||
SaveUndo(this);
|
||||
}
|
||||
public void SaveUndo() { SaveUndo(this); }
|
||||
|
||||
public static void SaveUndo(Player p) {
|
||||
if ( p == null || p.UndoBuffer == null || p.UndoBuffer.Count < 1 ) return;
|
||||
try {
|
||||
if ( !Directory.Exists("extra/undo") ) Directory.CreateDirectory("extra/undo");
|
||||
if ( !Directory.Exists("extra/undoPrevious") ) Directory.CreateDirectory("extra/undoPrevious");
|
||||
DirectoryInfo di = new DirectoryInfo("extra/undo");
|
||||
if ( di.GetDirectories("*").Length >= Server.totalUndo ) {
|
||||
Directory.Delete("extra/undoPrevious", true);
|
||||
Directory.Move("extra/undo", "extra/undoPrevious");
|
||||
Directory.CreateDirectory("extra/undo");
|
||||
}
|
||||
|
||||
if ( !Directory.Exists("extra/undo/" + p.name.ToLower()) ) Directory.CreateDirectory("extra/undo/" + p.name.ToLower());
|
||||
di = new DirectoryInfo("extra/undo/" + p.name.ToLower());
|
||||
int number = di.GetFiles("*.undo").Length;
|
||||
File.Create("extra/undo/" + p.name.ToLower() + "/" + number + ".undo").Dispose();
|
||||
using ( StreamWriter w = File.CreateText("extra/undo/" + p.name.ToLower() + "/" + number + ".undo") ) {
|
||||
foreach ( UndoPos uP in p.UndoBuffer.ToList() ) {
|
||||
w.Write(uP.mapName + " " +
|
||||
uP.x + " " + uP.y + " " + uP.z + " " +
|
||||
uP.timePlaced.ToString(CultureInfo.InvariantCulture).Replace(' ', '&') + " " +
|
||||
uP.type + " " + uP.newtype + " ");
|
||||
}
|
||||
}
|
||||
UndoFile.SaveUndo(p);
|
||||
} catch (Exception e) {
|
||||
Server.s.Log("Error saving undo data for " + p.name + "!"); Server.ErrorLog(e);
|
||||
}
|
||||
catch ( Exception e ) { Server.s.Log("Error saving undo data for " + p.name + "!"); Server.ErrorLog(e); }
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
142
Player/Undo/UndoFile.cs
Normal file
142
Player/Undo/UndoFile.cs
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2015 MCGalaxy
|
||||
|
||||
Dual-licensed under the Educational Community License, Version 2.0 and
|
||||
the GNU General Public License, Version 3 (the "Licenses"); you may
|
||||
not use this file except in compliance with the Licenses. You may
|
||||
obtain a copy of the Licenses at
|
||||
|
||||
http://www.opensource.org/licenses/ecl2.php
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the Licenses are distributed on an "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the Licenses for the specific language governing
|
||||
permissions and limitations under the Licenses.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MCGalaxy.Util {
|
||||
|
||||
public abstract class UndoFile {
|
||||
|
||||
protected const string undoDir = "extra/undo", prevUndoDir = "extra/undoPrevious";
|
||||
public static UndoFile OldFormat = new UndoFileText();
|
||||
public static UndoFile NewFormat = new UndoFileBin();
|
||||
|
||||
protected abstract void SaveUndoData(List<Player.UndoPos> buffer, string path);
|
||||
|
||||
protected abstract void ReadUndoData(List<Player.UndoPos> buffer, string path);
|
||||
|
||||
protected abstract bool UndoEntry(Player p, string path, long seconds);
|
||||
|
||||
protected abstract bool HighlightEntry(Player p, string path, long seconds);
|
||||
|
||||
protected abstract string Extension { get; }
|
||||
|
||||
public static void SaveUndo(Player p) {
|
||||
if( p == null || p.UndoBuffer == null || p.UndoBuffer.Count < 1) return;
|
||||
|
||||
CreateDefaultDirectories();
|
||||
if (Directory.GetDirectories(undoDir).Length >= Server.totalUndo) {
|
||||
Directory.Delete(prevUndoDir, true);
|
||||
Directory.Move(undoDir, prevUndoDir);
|
||||
Directory.CreateDirectory(undoDir);
|
||||
}
|
||||
|
||||
string playerDir = Path.Combine(undoDir, p.name.ToLower());
|
||||
if (!Directory.Exists(playerDir))
|
||||
Directory.CreateDirectory(playerDir);
|
||||
|
||||
int numFiles = Directory.GetFiles(playerDir).Length;
|
||||
string path = Path.Combine(playerDir, numFiles + NewFormat.Extension);
|
||||
NewFormat.SaveUndoData(p.UndoBuffer, path);
|
||||
}
|
||||
|
||||
public static void UndoPlayer(Player p, string targetName, long seconds, ref bool FoundUser) {
|
||||
if (p != null)
|
||||
p.RedoBuffer.Clear();
|
||||
FilterEntries(p, undoDir, targetName, seconds, false, ref FoundUser);
|
||||
FilterEntries(p, prevUndoDir, targetName, seconds, false, ref FoundUser);
|
||||
}
|
||||
|
||||
public static void HighlightPlayer(Player p, string targetName, long seconds, ref bool FoundUser) {
|
||||
FilterEntries(p, undoDir, targetName, seconds, true, ref FoundUser);
|
||||
FilterEntries(p, prevUndoDir, targetName, seconds, true, ref FoundUser);
|
||||
}
|
||||
|
||||
static void FilterEntries(Player p, string dir, string name, long seconds, bool highlight, ref bool FoundUser) {
|
||||
string path = Path.Combine(dir, name);
|
||||
if (!Directory.Exists(path))
|
||||
return;
|
||||
string[] files = Directory.GetFiles(path);
|
||||
Array.Sort<string>(files, CompareFiles);
|
||||
|
||||
for (int i = files.Length - 1; i >= 0; i--) {
|
||||
path = files[i];
|
||||
string file = Path.GetFileName(path);
|
||||
if (file.Length == 0 || file[0] < '0' || file[0] > '9')
|
||||
continue;
|
||||
|
||||
UndoFile format = null;
|
||||
if (path.EndsWith(OldFormat.Extension)) format = OldFormat;
|
||||
if (path.EndsWith(NewFormat.Extension)) format = NewFormat;
|
||||
if (format == null) continue;
|
||||
|
||||
if (highlight) {
|
||||
if (!format.HighlightEntry(p, path, seconds)) break;
|
||||
} else {
|
||||
if (!format.UndoEntry(p, path, seconds)) break;
|
||||
}
|
||||
}
|
||||
FoundUser = true;
|
||||
}
|
||||
|
||||
static int CompareFiles(string a, string b) {
|
||||
int aNumEnd = a.IndexOf('.'), bNumEnd = b.IndexOf('.');
|
||||
if (aNumEnd < 0 || bNumEnd < 0) return a.CompareTo(b);
|
||||
|
||||
int aNum, bNum;
|
||||
if (!int.TryParse(a.Substring(0, aNumEnd), out aNum) ||
|
||||
!int.TryParse(b.Substring(0, bNumEnd), out bNum))
|
||||
return a.CompareTo(b);
|
||||
return aNum.CompareTo(bNum);
|
||||
}
|
||||
|
||||
public static void CreateDefaultDirectories() {
|
||||
if (!Directory.Exists(undoDir))
|
||||
Directory.CreateDirectory(undoDir);
|
||||
if (!Directory.Exists(prevUndoDir))
|
||||
Directory.CreateDirectory(prevUndoDir);
|
||||
}
|
||||
|
||||
public static void UpgradePlayerUndoFiles(string name) {
|
||||
UpgradeFiles(undoDir, name);
|
||||
UpgradeFiles(prevUndoDir, name);
|
||||
}
|
||||
|
||||
static void UpgradeFiles(string dir, string name) {
|
||||
string path = Path.Combine(dir, name);
|
||||
if (!Directory.Exists(path))
|
||||
return;
|
||||
string[] files = Directory.GetFiles(path);
|
||||
List<Player.UndoPos> buffer = new List<Player.UndoPos>();
|
||||
|
||||
for (int i = 0; i < files.Length; i++) {
|
||||
path = files[i];
|
||||
if (!path.EndsWith(OldFormat.Extension))
|
||||
continue;
|
||||
buffer.Clear();
|
||||
OldFormat.ReadUndoData(buffer, path);
|
||||
|
||||
string newPath = Path.ChangeExtension(path, NewFormat.Extension);
|
||||
NewFormat.SaveUndoData(buffer, newPath);
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
218
Player/Undo/UndoFileBin.cs
Normal file
218
Player/Undo/UndoFileBin.cs
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
Copyright 2015 MCGalaxy
|
||||
|
||||
Dual-licensed under the Educational Community License, Version 2.0 and
|
||||
the GNU General Public License, Version 3 (the "Licenses"); you may
|
||||
not use this file except in compliance with the Licenses. You may
|
||||
obtain a copy of the Licenses at
|
||||
|
||||
http://www.opensource.org/licenses/ecl2.php
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the Licenses are distributed on an "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the Licenses for the specific language governing
|
||||
permissions and limitations under the Licenses.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MCGalaxy.Util {
|
||||
|
||||
public sealed class UndoFileBin : UndoFile {
|
||||
|
||||
protected override string Extension { get { return ".unbin"; } }
|
||||
|
||||
protected override void SaveUndoData(List<Player.UndoPos> buffer, string path) {
|
||||
using (FileStream fs = File.Create(path)) {
|
||||
BinaryWriter w = new BinaryWriter(fs);
|
||||
long entriesPos = 0;
|
||||
ChunkHeader lastChunk = default(ChunkHeader);
|
||||
|
||||
foreach (Player.UndoPos uP in buffer) {
|
||||
int timeDiff = (int)(uP.timePlaced.ToUniversalTime() - lastChunk.BaseTime).TotalSeconds;
|
||||
if (lastChunk.LevelName != uP.mapName || timeDiff > 65535 || lastChunk.Entries == ushort.MaxValue) {
|
||||
WriteChunkEntries(w, lastChunk.Entries, entriesPos);
|
||||
lastChunk = WriteEmptyChunk(w, uP, ref entriesPos);
|
||||
}
|
||||
|
||||
w.Write((ushort)timeDiff);
|
||||
w.Write(uP.x); w.Write(uP.y); w.Write(uP.z);
|
||||
w.Write(uP.type);
|
||||
w.Write((byte)0); // block definitions placeholder
|
||||
w.Write(uP.newtype);
|
||||
w.Write((byte)0); // block definitions placeholder
|
||||
lastChunk.Entries++;
|
||||
}
|
||||
if (lastChunk.Entries > 0)
|
||||
WriteChunkEntries(w, lastChunk.Entries, entriesPos);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) {
|
||||
DateTime now = DateTime.Now;
|
||||
Player.UndoPos Pos;
|
||||
using (Stream fs = File.OpenRead(path))
|
||||
using (BinaryReader r = new BinaryReader(fs))
|
||||
{
|
||||
int approxEntries = (int)(fs.Length / 12);
|
||||
if (buffer.Capacity < approxEntries)
|
||||
buffer.Capacity = approxEntries;
|
||||
while (fs.Position < fs.Length) {
|
||||
ChunkHeader chunk = ReadHeader(fs, r);
|
||||
Pos.mapName = chunk.LevelName;
|
||||
|
||||
for (int j = 0; j < chunk.Entries; j++ ) {
|
||||
Pos.timePlaced = chunk.BaseTime.AddSeconds(r.ReadUInt16());
|
||||
Pos.x = r.ReadUInt16(); Pos.y = r.ReadUInt16(); Pos.z = r.ReadUInt16();
|
||||
Pos.type = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
Pos.newtype = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
buffer.Add(Pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool UndoEntry(Player p, string path, long seconds) {
|
||||
List<ChunkHeader> list = new List<ChunkHeader>();
|
||||
DateTime now = DateTime.Now;
|
||||
Player.UndoPos Pos;
|
||||
|
||||
using (Stream fs = File.OpenRead(path))
|
||||
using (BinaryReader r = new BinaryReader(fs))
|
||||
{
|
||||
ReadHeaders(list, r);
|
||||
for (int i = list.Count - 1; i >= 0; i--) {
|
||||
ChunkHeader chunk = list[i];
|
||||
Level lvl;
|
||||
if (!CheckChunk(chunk, now, seconds, p, out lvl))
|
||||
return false;
|
||||
if (lvl == null || lvl != p.level) continue;
|
||||
Pos.mapName = chunk.LevelName;
|
||||
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
|
||||
|
||||
for (int j = 0; j < chunk.Entries; j++ ) {
|
||||
DateTime time = chunk.BaseTime.AddSeconds(r.ReadUInt16());
|
||||
if (time.AddSeconds(seconds) < now) return false;
|
||||
Pos.x = r.ReadUInt16(); Pos.y = r.ReadUInt16(); Pos.z = r.ReadUInt16();
|
||||
|
||||
Pos.type = lvl.GetTile(Pos.x, Pos.y, Pos.z);
|
||||
byte oldType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
byte newType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
|
||||
if (Pos.type == newType || Block.Convert(Pos.type) == Block.water
|
||||
|| Block.Convert(Pos.type) == Block.lava || Pos.type == Block.grass) {
|
||||
|
||||
Pos.newtype = oldType;
|
||||
Pos.timePlaced = now;
|
||||
lvl.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
|
||||
if (p != null)
|
||||
p.RedoBuffer.Add(Pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool HighlightEntry(Player p, string path, long seconds) {
|
||||
List<ChunkHeader> list = new List<ChunkHeader>();
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
using (Stream fs = File.OpenRead(path))
|
||||
using (BinaryReader r = new BinaryReader(fs))
|
||||
{
|
||||
ReadHeaders(list, r);
|
||||
for (int i = list.Count - 1; i >= 0; i--) {
|
||||
ChunkHeader chunk = list[i];
|
||||
Level lvl;
|
||||
if (!CheckChunk(chunk, now, seconds, p, out lvl))
|
||||
return false;
|
||||
if (lvl == null || lvl != p.level) continue;
|
||||
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
|
||||
|
||||
for (int j = 0; j < chunk.Entries; j++ ) {
|
||||
DateTime time = chunk.BaseTime.AddSeconds(r.ReadUInt16());
|
||||
if (time.AddSeconds(seconds) < now) return false;
|
||||
ushort x = r.ReadUInt16(), y = r.ReadUInt16(), z = r.ReadUInt16();
|
||||
|
||||
byte lvlTile = lvl.GetTile(x, y, z);
|
||||
byte oldType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
byte newType = r.ReadByte(); r.ReadByte(); // block definitions placeholder
|
||||
|
||||
if (lvlTile == newType || Block.Convert(lvlTile) == Block.water || Block.Convert(lvlTile) == Block.lava) {
|
||||
|
||||
byte block = (lvlTile == Block.air || Block.Convert(lvlTile) == Block.water
|
||||
|| Block.Convert(lvlTile) == Block.lava) ? Block.red : Block.green;
|
||||
p.SendBlockchange(x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckChunk(ChunkHeader chunk, DateTime now, long seconds, Player p, out Level lvl) {
|
||||
DateTime time = chunk.BaseTime;
|
||||
lvl = null;
|
||||
if (time.AddSeconds(65536).AddSeconds(seconds) < now)
|
||||
return false; // we can safely discard the entire chunk
|
||||
|
||||
lvl = Level.FindExact(chunk.LevelName);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ChunkHeader {
|
||||
public string LevelName;
|
||||
public DateTime BaseTime;
|
||||
public ushort Entries;
|
||||
public long DataPosition;
|
||||
}
|
||||
|
||||
static void ReadHeaders(List<ChunkHeader> list, BinaryReader r) {
|
||||
Stream s = r.BaseStream;
|
||||
long len = s.Length;
|
||||
while (s.Position < len) {
|
||||
ChunkHeader header = ReadHeader(s, r);
|
||||
s.Seek(header.Entries * 12, SeekOrigin.Current);
|
||||
list.Add(header);
|
||||
}
|
||||
}
|
||||
|
||||
static ChunkHeader ReadHeader(Stream s, BinaryReader r) {
|
||||
ChunkHeader header = default(ChunkHeader);
|
||||
byte[] mapNameData = r.ReadBytes(r.ReadUInt16());
|
||||
header.LevelName = Encoding.UTF8.GetString(mapNameData);
|
||||
|
||||
header.BaseTime = new DateTime(r.ReadInt64(), DateTimeKind.Local);
|
||||
header.Entries = r.ReadUInt16();
|
||||
header.DataPosition = s.Position;
|
||||
return header;
|
||||
}
|
||||
|
||||
static void WriteChunkEntries(BinaryWriter w, ushort entries, long entriesPos) {
|
||||
long curPos = w.BaseStream.Position;
|
||||
w.BaseStream.Seek(entriesPos, SeekOrigin.Begin);
|
||||
|
||||
w.Write(entries);
|
||||
w.BaseStream.Seek(curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
static ChunkHeader WriteEmptyChunk(BinaryWriter w, Player.UndoPos uP, ref long entriesPos) {
|
||||
byte[] mapBytes = Encoding.UTF8.GetBytes(uP.mapName);
|
||||
w.Write((ushort)mapBytes.Length);
|
||||
w.Write(mapBytes);
|
||||
w.Write(uP.timePlaced.ToLocalTime().Ticks);
|
||||
|
||||
entriesPos = w.BaseStream.Position;
|
||||
w.Write((ushort)0);
|
||||
ChunkHeader header = default(ChunkHeader);
|
||||
header.LevelName = uP.mapName; header.BaseTime = uP.timePlaced;
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
134
Player/Undo/UndoFileText.cs
Normal file
134
Player/Undo/UndoFileText.cs
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright 2015 MCGalaxy
|
||||
|
||||
Dual-licensed under the Educational Community License, Version 2.0 and
|
||||
the GNU General Public License, Version 3 (the "Licenses"); you may
|
||||
not use this file except in compliance with the Licenses. You may
|
||||
obtain a copy of the Licenses at
|
||||
|
||||
http://www.opensource.org/licenses/ecl2.php
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the Licenses are distributed on an "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the Licenses for the specific language governing
|
||||
permissions and limitations under the Licenses.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MCGalaxy.Util {
|
||||
|
||||
public sealed class UndoFileText : UndoFile {
|
||||
|
||||
protected override string Extension { get { return ".undo"; } }
|
||||
|
||||
protected override void SaveUndoData(List<Player.UndoPos> buffer, string path) {
|
||||
using (StreamWriter w = File.CreateText(path)) {
|
||||
foreach (Player.UndoPos uP in buffer) {
|
||||
w.Write(
|
||||
uP.mapName + " " + uP.x + " " + uP.y + " " + uP.z + " " +
|
||||
uP.timePlaced.ToString(CultureInfo.InvariantCulture).Replace(' ', '&') + " " +
|
||||
uP.type + " " + uP.newtype + " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) {
|
||||
Player.UndoPos Pos;
|
||||
string[] lines = File.ReadAllText(path).Split(' ');
|
||||
int approxEntries = (int)(lines.Length / 7);
|
||||
if (buffer.Capacity < approxEntries)
|
||||
buffer.Capacity = approxEntries;
|
||||
|
||||
for (int i = 0; i < lines.Length; i += 7) {
|
||||
if (lines[i].Length == 0) continue;
|
||||
Pos.mapName = lines[i];
|
||||
Pos.x = ushort.Parse(lines[i + 1]);
|
||||
Pos.y = ushort.Parse(lines[i + 2]);
|
||||
Pos.z = ushort.Parse(lines[i + 3]);
|
||||
|
||||
string time = lines[i + 4].Replace('&', ' ');
|
||||
Pos.timePlaced = DateTime.Parse(time, CultureInfo.InvariantCulture);
|
||||
Pos.type = byte.Parse(lines[i + 5]);
|
||||
Pos.newtype = byte.Parse(lines[i + 6]);
|
||||
buffer.Add(Pos);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool UndoEntry(Player p, string path, long seconds) {
|
||||
Player.UndoPos Pos;
|
||||
string[] lines = File.ReadAllText(path).Split(' ');
|
||||
// because we have space to end of each entry, need to subtract one otherwise we'll start at a "".
|
||||
for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
|
||||
try {
|
||||
// line format: mapName x y z date oldblock newblock
|
||||
if (!InTime(lines[(i * 7) - 3], seconds)) return false;
|
||||
Level foundLevel = Level.FindExact(lines[(i * 7) - 7]);
|
||||
if (foundLevel == null) continue;
|
||||
|
||||
Pos.mapName = foundLevel.name;
|
||||
Pos.x = Convert.ToUInt16(lines[(i * 7) - 6]);
|
||||
Pos.y = Convert.ToUInt16(lines[(i * 7) - 5]);
|
||||
Pos.z = Convert.ToUInt16(lines[(i * 7) - 4]);
|
||||
Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
|
||||
|
||||
if (Pos.type == Convert.ToByte(lines[(i * 7) - 1]) ||
|
||||
Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava ||
|
||||
Pos.type == Block.grass) {
|
||||
|
||||
Pos.newtype = Convert.ToByte(lines[(i * 7) - 2]);
|
||||
Pos.timePlaced = DateTime.Now;
|
||||
|
||||
foundLevel.Blockchange(Pos.x, Pos.y, Pos.z, Pos.newtype, true);
|
||||
if (p != null)
|
||||
p.RedoBuffer.Add(Pos);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool HighlightEntry(Player p, string path, long seconds) {
|
||||
Player.UndoPos Pos;
|
||||
string[] lines = File.ReadAllText(path).Split(' ');
|
||||
// because we have space to end of each entry, need to subtract one otherwise we'll start at a "".
|
||||
for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
|
||||
try {
|
||||
// line format: mapName x y z date oldblock newblock
|
||||
if (!InTime(lines[(i * 7) - 3], seconds)) return false;
|
||||
Level foundLevel = Level.FindExact(lines[(i * 7) - 7]);
|
||||
if (foundLevel == null || foundLevel != p.level) continue;
|
||||
|
||||
Pos.mapName = foundLevel.name;
|
||||
Pos.x = Convert.ToUInt16(lines[(i * 7) - 6]);
|
||||
Pos.y = Convert.ToUInt16(lines[(i * 7) - 5]);
|
||||
Pos.z = Convert.ToUInt16(lines[(i * 7) - 4]);
|
||||
Pos.type = foundLevel.GetTile(Pos.x, Pos.y, Pos.z);
|
||||
|
||||
if (Pos.type == Convert.ToByte(lines[(i * 7) - 1]) ||
|
||||
Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava) {
|
||||
|
||||
if (Pos.type == Block.air || Block.Convert(Pos.type) == Block.water || Block.Convert(Pos.type) == Block.lava)
|
||||
p.SendBlockchange(Pos.x, Pos.y, Pos.z, Block.red);
|
||||
else
|
||||
p.SendBlockchange(Pos.x, Pos.y, Pos.z, Block.green);
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool InTime(string line, long seconds) {
|
||||
line = line.Replace('&', ' ');
|
||||
DateTime time = DateTime.Parse(line, CultureInfo.InvariantCulture)
|
||||
.AddSeconds(seconds);
|
||||
return time >= DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
134
Starter.csproj
134
Starter.csproj
@ -1,74 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{63DCBB31-92CD-4464-A86C-A7E51A5FE9FE}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Starter</RootNamespace>
|
||||
<AssemblyName>MCGalaxy</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Starter.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Galaxy.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="MCGalaxy_.csproj">
|
||||
<Project>{12597DB0-7C34-4DE1-88EA-9250FF3372EB}</Project>
|
||||
<Name>MCGalaxy_</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Galaxy.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{63DCBB31-92CD-4464-A86C-A7E51A5FE9FE}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Starter</RootNamespace>
|
||||
<AssemblyName>MCGalaxy</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Starter.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Galaxy.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="MCGalaxy_.csproj">
|
||||
<Project>{12597DB0-7C34-4DE1-88EA-9250FF3372EB}</Project>
|
||||
<Name>MCGalaxy_</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Galaxy.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
-->
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user