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", };