diff --git a/CmdPruneDB/CmdPruneDB.csproj b/CmdPruneDB/CmdPruneDB.csproj
new file mode 100644
index 000000000..29ef58d8d
--- /dev/null
+++ b/CmdPruneDB/CmdPruneDB.csproj
@@ -0,0 +1,61 @@
+
+
+
+ {6AAEC8C6-6A45-4490-8A60-937F287E3D78}
+ Debug
+ AnyCPU
+ Library
+ CmdPruneDB
+ CmdPruneDB
+ v4.0
+ Client
+ Properties
+ False
+ True
+ False
+ False
+ obj\$(Configuration)\
+ 4
+
+
+ AnyCPU
+ 4194304
+ False
+ Auto
+ 4096
+
+
+ bin\Debug\
+ True
+ Full
+ False
+ True
+ DEBUG;TRACE
+
+
+ bin\Release\
+ False
+ None
+ True
+ False
+ TRACE
+ obj\
+
+
+
+
+ 3.5
+
+
+
+
+
+
+
+
+ {12597DB0-7C34-4DE1-88EA-9250FF3372EB}
+ MCGalaxy_
+
+
+
+
\ No newline at end of file
diff --git a/CmdPruneDB/MyClass.cs b/CmdPruneDB/MyClass.cs
new file mode 100644
index 000000000..75bbcf773
--- /dev/null
+++ b/CmdPruneDB/MyClass.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using MCGalaxy.DB;
+using MCGalaxy.Maths;
+using MCGalaxy.Util;
+
+namespace MCGalaxy.Commands.Moderation {
+ public class CmdPruneDB : Command2 {
+ public override string name { get { return "PruneDB"; } }
+ public override string type { get { return CommandTypes.Moderation; } }
+ public override LevelPermission defaultRank { get { return LevelPermission.Admin; } }
+ public override bool SuperUseable { get { return false; } }
+
+ public unsafe override void Use(Player p, string message, CommandData data) {
+ if (message.Length == 0) { Player.Message(p, "You need to provide a player name."); return; }
+
+ string[] parts = message.SplitSpaces(), names = null;
+ int[] ids = GetIds(p, data, parts, out names);
+ if (ids == null) return;
+
+ TimeSpan delta = GetDelta(p, parts[0], parts, 1);
+ if (delta == TimeSpan.MinValue) return;
+
+ BlockDB db = p.level.BlockDB;
+ DateTime startTime = DateTime.UtcNow - delta;
+
+ Vec3U16 dims;
+ FastList entries = new FastList(4096);
+ byte[] bulk = new byte[BlockDBFile.BulkEntries * BlockDBFile.EntrySize];
+ int start = (int)((startTime - BlockDB.Epoch).TotalSeconds);
+ long total;
+ int changed = 0;
+
+ using (IDisposable locker = db.Locker.AccquireWrite()) {
+ if (!File.Exists(db.FilePath)) {
+ Player.Message(p, "BlockDB file for this map doesn't exist.");
+ return;
+ }
+
+ using (Stream src = OpenRead(db.FilePath), dst = OpenWrite(db.FilePath + ".tmp")) {
+ BlockDBFile format = BlockDBFile.ReadHeader(src, out dims);
+ BlockDBFile.WriteHeader(dst, dims);
+ total = format.CountEntries(src);
+
+ src.Position = src.Length;
+ fixed (byte* ptr = bulk) {
+ while (true) {
+ BlockDBEntry* entry = (BlockDBEntry*)ptr;
+ int count = format.ReadBackward(src, bulk, entry);
+ if (count == 0) break;
+ entry += (count - 1);
+
+ for (int i = count - 1; i >= 0; i--, entry--) {
+ if (entry->TimeDelta < start) goto finished;
+ for (int j = 0; j < ids.Length; j++) {
+
+ if (entry->PlayerID != ids[j]) continue;
+ changed++;
+ entries.Add(*entry);
+
+ if (entries.Count == 4096) {
+ format.WriteEntries(dst, entries);
+ entries.Count = 0;
+ }
+ }
+ }
+ }
+ }
+
+ finished:
+ // flush remaining few entries
+ if (entries.Count > 0) format.WriteEntries(dst, entries);
+ }
+
+ string namesStr = names.Join(name => PlayerInfo.GetColoredName(p, name));
+ if (changed > 0) {
+ File.Delete(db.FilePath);
+ File.Move(db.FilePath + ".tmp", db.FilePath);
+ p.Message("Pruned {2} changes by {1}%S's in the past &b{0} %S({3} entries left)",
+ delta.Shorten(true), namesStr, changed, total - changed);
+ } else {
+ File.Delete(db.FilePath + ".tmp");
+ p.Message("No changes found by {1} %Sin the past &b{0}",
+ delta.Shorten(true), namesStr);
+ }
+ }
+ }
+
+ // all this copy paste makes me sad
+
+ static FileStream OpenWrite(string path) {
+ return new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
+ }
+
+ static FileStream OpenRead(string path) {
+ return new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ static int[] GetIds(Player p, CommandData data, string[] parts, out string[] names) {
+ int count = Math.Max(1, parts.Length - 1);
+ List ids = new List();
+ names = new string[count];
+
+ for (int i = 0; i < names.Length; i++) {
+ p.Message("Searching PlayerDB for \"{0}\"..", parts[i]);
+ names[i] = PlayerDB.MatchNames(p, parts[i]);
+ if (names[i] == null) return null;
+
+ if (!p.name.CaselessEq(names[i])) {
+ Group grp = Group.GroupIn(names[i]);
+ if (!CheckRank(p, data, grp.Permission, "undo", false)) return null;
+ }
+ ids.AddRange(NameConverter.FindIds(names[i]));
+ }
+ return ids.ToArray();
+ }
+
+ static TimeSpan GetDelta(Player p, string name, string[] parts, int offset) {
+ TimeSpan delta = TimeSpan.Zero;
+ string timespan = parts.Length > offset ? parts[parts.Length - 1] : "30m";
+ bool self = p.name.CaselessEq(name);
+
+ if (timespan.CaselessEq("all")) {
+ return self ? TimeSpan.FromSeconds(int.MaxValue) : p.group.MaxUndo;
+ } else if (!CommandParser.GetTimespan(p, timespan, ref delta, "undo the past", "s")) {
+ return TimeSpan.MinValue;
+ }
+
+ if (delta.TotalSeconds == 0)
+ delta = TimeSpan.FromMinutes(90);
+ if (!self && delta > p.group.MaxUndo) {
+ p.Message("{0}%Ss may only undo up to {1}",
+ p.group.ColoredName, p.group.MaxUndo.Shorten(true, true));
+ return p.group.MaxUndo;
+ }
+ return delta;
+ }
+
+
+ public override void Help(Player p) {
+ p.Message("%T/PruneDB [player1] ");
+ p.Message("%HDeletes the block changes of [players] in the past from BlockDB.");
+ p.Message("&cSlow and dangerous. Use with care.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/CmdPruneDB/Properties/AssemblyInfo.cs b/CmdPruneDB/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..171b0bf26
--- /dev/null
+++ b/CmdPruneDB/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+#region Using directives
+
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+#endregion
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("CmdPruneDB")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("CmdPruneDB")]
+[assembly: AssemblyCopyright("Copyright 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// This sets the default COM visibility of types in the assembly to invisible.
+// If you need to expose a type to COM, use [ComVisible(true)] on that type.
+[assembly: ComVisible(false)]
+
+// The assembly version has following format :
+//
+// Major.Minor.Build.Revision
+//
+// You can specify all the values or you can use the default the Revision and
+// Build Numbers by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.*")]
diff --git a/MCGalaxy/Commands/Information/CmdCommands.cs b/MCGalaxy/Commands/Information/CmdCommands.cs
index d65051f31..355e20381 100644
--- a/MCGalaxy/Commands/Information/CmdCommands.cs
+++ b/MCGalaxy/Commands/Information/CmdCommands.cs
@@ -43,47 +43,21 @@ namespace MCGalaxy.Commands.Info {
}
}
- switch (args[0].ToLower()) {
- case "build":
- case "building":
- PrintHelpForGroup(p, sort, modifier, "build", "Building"); break;
- case "chat":
- PrintHelpForGroup(p, sort, modifier, "chat", "Chat"); break;
- case "eco":
- case "economy":
- PrintHelpForGroup(p, sort, modifier, "eco", "Economy"); break;
- case "mod":
- case "moderation":
- PrintHelpForGroup(p, sort, modifier, "mod", "Moderation"); break;
- case "info":
- case "information":
- PrintHelpForGroup(p, sort, modifier, "info", "Information"); break;
- case "game":
- case "games":
- PrintHelpForGroup(p, sort, modifier, "game", "Game"); break;
- case "other":
- case "others":
- PrintHelpForGroup(p, sort, modifier, "other", "Other"); break;
- case "maps":
- case "world":
- PrintHelpForGroup(p, sort, modifier, "world", "World"); break;
- case "short":
- case "shortcut":
- case "shortcuts":
- PrintShortcuts(p, sort); break;
- case "old":
- case "oldmenu":
- case "command":
- case "":
- PrintRankCommands(p, sort, modifier, p.group, true); break;
- case "commandsall":
- case "commandall":
- case "all":
- PrintAllCommands(p, sort, modifier); break;
- default:
- Group grp = Group.Find(args[0]);
- if (grp == null) return false;
- PrintRankCommands(p, sort, modifier, grp, false); break;
+ string type = args[0].ToLower();
+ if (type == "short" || type == "shortcut" || type == "shortcuts") {
+ PrintShortcuts(p, sort);
+ } else if (type == "old" || type == "oldmenu" || type == "" || type == "command") {
+ PrintRankCommands(p, sort, modifier, p.group, true);
+ } else if (type == "all" || type == "commandall" || type == "commandsall") {
+ PrintAllCommands(p, sort, modifier);
+ } else {
+ bool any = PrintCategoryCommands(p, sort, modifier, type);
+ if (any) return true;
+
+ // list commands a rank can use
+ Group grp = Group.Find(type);
+ if (grp == null) return false;
+ PrintRankCommands(p, sort, modifier, grp, false);
}
return true;
}
@@ -105,9 +79,7 @@ namespace MCGalaxy.Commands.Info {
List cmds = new List();
foreach (Command c in Command.allCmds) {
string disabled = Command.GetDisabledReason(c.Enabled);
- if (disabled != null || c.name == null) continue;
- if (!group.Commands.Contains(c)) continue;
- cmds.Add(c);
+ if (disabled == null && group.Commands.Contains(c)) cmds.Add(c);
}
if (cmds.Count == 0) {
@@ -140,29 +112,44 @@ namespace MCGalaxy.Commands.Info {
p.Message("Type %T/Help %Sfor more help on a command.");
}
- static void PrintHelpForGroup(Player p, string sort, string modifier,
- string type, string category) {
+ static string GetCategory(string type) {
+ if (type.CaselessEq("building")) return CommandTypes.Building;
+ if (type.CaselessEq("eco")) return CommandTypes.Economy;
+ if (type.CaselessEq("games")) return CommandTypes.Games;
+ if (type.CaselessEq("info")) return CommandTypes.Information;
+ if (type.CaselessEq("moderation")) return CommandTypes.Moderation;
+ if (type.CaselessEq("others")) return CommandTypes.Other;
+ if (type.CaselessEq("maps")) return CommandTypes.World;
+ return type;
+ }
+
+ static bool PrintCategoryCommands(Player p, string sort, string modifier, string type) {
List cmds = new List();
+ string category = GetCategory(type);
+ bool any = false;
+
foreach (Command c in Command.allCmds) {
string disabled = Command.GetDisabledReason(c.Enabled);
- if (p.CanUse(c) && disabled == null) {
- if (!c.type.CaselessContains(type) || c.name == null) continue;
- cmds.Add(c);
- }
+ if (!c.type.CaselessContains(category)) continue;
+
+ if (disabled == null && p.CanUse(c)) cmds.Add(c);
+ any = true;
}
if (cmds.Count == 0) {
- p.Message("You cannot use any of the " + category + " commands."); return;
+ p.Message("You cannot use any of the " + category + " commands."); return any;
}
SortCommands(cmds, sort);
p.Message(category + " commands you may use:");
- type = "Commands " + category;
+ type = "Commands " + type;
if (sort.Length > 0) type += " " + sort;
MultiPageOutput.Output(p, cmds,
(cmd) => CmdHelp.GetColor(cmd) + cmd.name,
type, "commands", modifier, false);
+
p.Message("Type %T/Help %Sfor more help on a command.");
+ return any;
}
static void SortCommands(List cmds, string sort) {
diff --git a/MCGalaxy/Commands/World/CmdOverseer.cs b/MCGalaxy/Commands/World/CmdOverseer.cs
index 542739932..176a890ee 100644
--- a/MCGalaxy/Commands/World/CmdOverseer.cs
+++ b/MCGalaxy/Commands/World/CmdOverseer.cs
@@ -133,7 +133,7 @@ namespace MCGalaxy.Commands.World {
#region Help messages
static string[] blockPropsHelp = new string[] {
- "%T/os blockprops [id] [action] %H- Manages properties for custom blocks on your map.",
+ "%T/os blockprops [id] [action] %H- Changes properties of blocks in your map.",
"%H See %T/Help blockprops %Hfor a list of actions",
};