From 5ce514187f671a7ba917e7fd1f5ef2dcec736af4 Mon Sep 17 00:00:00 2001 From: Goodlyay Date: Wed, 20 Aug 2025 06:02:01 -0700 Subject: [PATCH] Add /os list, sort lists of displayed levels a bit more nicely --- MCGalaxy/Commands/Information/CmdLevels.cs | 51 ++------------------- MCGalaxy/Commands/Information/CmdSearch.cs | 1 + MCGalaxy/Commands/Overseer.cs | 40 ++++++++++++++--- MCGalaxy/Levels/LevelInfo.cs | 52 ++++++++++++++++++++++ MCGalaxy/util/Formatting/Formatter.cs | 42 +++++++++++++++++ 5 files changed, 133 insertions(+), 53 deletions(-) diff --git a/MCGalaxy/Commands/Information/CmdLevels.cs b/MCGalaxy/Commands/Information/CmdLevels.cs index 4ee444208..0cdc72532 100644 --- a/MCGalaxy/Commands/Information/CmdLevels.cs +++ b/MCGalaxy/Commands/Information/CmdLevels.cs @@ -16,7 +16,6 @@ permissions and limitations under the Licenses. */ using System; -using System.IO; namespace MCGalaxy.Commands.Info { @@ -31,55 +30,11 @@ namespace MCGalaxy.Commands.Info } public override void Use(Player p, string message, CommandData data) { - string[] files = LevelInfo.AllMapFiles(); + string[] files = LevelInfo.AllMapNames(); // Files list is not guaranteed to be in alphabetical order - Array.Sort(files); - - p.Message("Levels (&c[no] &Sif not visitable):"); - Paginator.Output(p, files, (file) => FormatMap(p, file), - "Levels", "levels", message); + Array.Sort(files, new AlphanumComparator()); + LevelInfo.ListMaps(p, files, "Levels", "Levels", "levels", message); } - - static string FormatMap(Player p, string file) { - LevelPermission visitP, buildP; - bool loadOnGoto; - string map = Path.GetFileNameWithoutExtension(file); - RetrieveProps(map, out visitP, out buildP, out loadOnGoto); - - LevelPermission maxPerm = visitP; - if (maxPerm < buildP) maxPerm = buildP; - - string visit = loadOnGoto && p.Rank >= visitP ? "" : " &c[no]"; - return Group.GetColor(maxPerm) + map + visit; - } - - static void RetrieveProps(string level, out LevelPermission visit, - out LevelPermission build, out bool loadOnGoto) { - visit = LevelPermission.Guest; - build = LevelPermission.Guest; - loadOnGoto = true; - - string propsPath = LevelInfo.PropsPath(level); - SearchArgs args = new SearchArgs(); - if (!PropertiesFile.Read(propsPath, ref args, ProcessLine)) return; - - visit = Group.ParsePermOrName(args.Visit, visit); - build = Group.ParsePermOrName(args.Build, build); - if (!bool.TryParse(args.LoadOnGoto, out loadOnGoto)) - loadOnGoto = true; - } - - static void ProcessLine(string key, string value, ref SearchArgs args) { - if (key.CaselessEq("pervisit")) { - args.Visit = value; - } else if (key.CaselessEq("perbuild")) { - args.Build = value; - } else if (key.CaselessEq("loadongoto")) { - args.LoadOnGoto = value; - } - } - - struct SearchArgs { public string Visit, Build, LoadOnGoto; } public override void Help(Player p) { p.Message("&T/Levels"); diff --git a/MCGalaxy/Commands/Information/CmdSearch.cs b/MCGalaxy/Commands/Information/CmdSearch.cs index 621cb3897..b0682e993 100644 --- a/MCGalaxy/Commands/Information/CmdSearch.cs +++ b/MCGalaxy/Commands/Information/CmdSearch.cs @@ -108,6 +108,7 @@ namespace MCGalaxy.Commands.Info static void SearchMaps(Player p, string keyword, string modifier) { string[] allMaps = LevelInfo.AllMapNames(); List maps = Wildcard.Filter(allMaps, keyword, map => map); + maps.Sort(new AlphanumComparator()); OutputList(p, keyword, "search levels", "maps", modifier, maps); } diff --git a/MCGalaxy/Commands/Overseer.cs b/MCGalaxy/Commands/Overseer.cs index c8520a98f..988da8b83 100644 --- a/MCGalaxy/Commands/Overseer.cs +++ b/MCGalaxy/Commands/Overseer.cs @@ -56,11 +56,8 @@ namespace MCGalaxy.Commands.World { string[] allMaps = LevelInfo.AllMapNames(); int realmsOwned = 0; - foreach (string lvlName in allMaps) - { - if (!lvlName.CaselessStarts(p.name)) continue; - - if (LevelInfo.IsRealmOwner(p.name, lvlName)) { + foreach (string lvlName in allMaps) { + if (IsOwnedBy(p.name, lvlName)) { realmsOwned += 1; if (realmsOwned >= p.group.OverseerMaps) { break; @@ -81,6 +78,22 @@ namespace MCGalaxy.Commands.World { p.Message("You have reached the limit for your overseer maps."); return null; } + /// + /// Returns all the os maps owned by p, sorted alphabetically. + /// + static List AllOwnedBy(string playerName) { + string[] allMaps = LevelInfo.AllMapNames(); + List owned = new List(); + foreach (string lvlName in allMaps) { + if (IsOwnedBy(playerName, lvlName)) owned.Add(lvlName); + } + owned.Sort(new AlphanumComparator()); + return owned; + } + + static bool IsOwnedBy(string playerName, string levelName) { + return levelName.CaselessStarts(playerName) && LevelInfo.IsRealmOwner(playerName, levelName); + } static string[] addHelp = new string[] { "&T/os add &H- Creates a flat map (128x128x128).", @@ -479,6 +492,22 @@ namespace MCGalaxy.Commands.World { static void HandleRestore(Player p, string args) { UseCommand(p, "Restore", args); } + + static string[] listHelp = new string[] { + "&T/os list ", + "&H Lists all the os realms for ", + "&H By default, lists your own realms.", + }; + static void HandleList(Player p, string args) { + string[] words = args.SplitSpaces(2); + string word0 = words[0]; + string word1 = words.Length > 1 ? words[1] : ""; //How many times have I typed a variant of this + + string playerName = word0.Length == 0 ? p.name : PlayerInfo.FindMatchesPreferOnline(p, word0); + if (playerName == null) return; + string page = word1; + LevelInfo.ListMaps(p, AllOwnedBy(playerName), "OS realms", "os list "+playerName, "OS realms", page, playerName != p.name); + } //Placed at the end so that the help arrays aren't null internal static SubCommandGroup subCommandGroup = new SubCommandGroup(commandShortcut, @@ -510,6 +539,7 @@ namespace MCGalaxy.Commands.World { new SubCommand("Delete", HandleDelete, deleteHelp, true, new string[] { "del", "remove" } ), new SubCommand("Rename", HandleRename, renameHelp), new SubCommand("Restore", HandleRestore, restoreHelp), + new SubCommand("List", HandleList, listHelp, false), } ); diff --git a/MCGalaxy/Levels/LevelInfo.cs b/MCGalaxy/Levels/LevelInfo.cs index fd2c5aefe..8df83efd2 100644 --- a/MCGalaxy/Levels/LevelInfo.cs +++ b/MCGalaxy/Levels/LevelInfo.cs @@ -16,6 +16,7 @@ permissions and limitations under the Licenses. */ using System; +using System.Collections.Generic; using System.IO; using System.Text; using MCGalaxy.DB; @@ -240,5 +241,56 @@ namespace MCGalaxy { // Match the backwards compatibilty case of IsRealmOwner return PlayerDB.FindName(map); } + + public static void ListMaps(Player p, IList maps, string levelsTitle, string listCmd, string itemName, string page, bool showVisitable = true) { + p.Message("{0} (&c[no] &Sif not visitable):", levelsTitle); + Paginator.Output(p, maps, (file) => FormatMap(p, file, showVisitable), + listCmd, itemName, page); + } + static string FormatMap(Player p, string map, bool showVisitable) { + LevelPermission visitP, buildP; + bool loadOnGoto; + RetrieveProps(map, out visitP, out buildP, out loadOnGoto); + + LevelPermission maxPerm = visitP; + if (maxPerm < buildP) maxPerm = buildP; + + string visit; + if (showVisitable) { + visit = loadOnGoto && p.Rank >= visitP ? "" : " &c[no]"; + } else { + visit = ""; + } + return Group.GetColor(maxPerm) + map + visit; + } + + static void RetrieveProps(string level, out LevelPermission visit, + out LevelPermission build, out bool loadOnGoto) { + visit = LevelPermission.Guest; + build = LevelPermission.Guest; + loadOnGoto = true; + + string propsPath = LevelInfo.PropsPath(level); + SearchArgs args = new SearchArgs(); + if (!PropertiesFile.Read(propsPath, ref args, ProcessLine)) return; + + visit = Group.ParsePermOrName(args.Visit, visit); + build = Group.ParsePermOrName(args.Build, build); + if (!bool.TryParse(args.LoadOnGoto, out loadOnGoto)) + loadOnGoto = true; + } + + static void ProcessLine(string key, string value, ref SearchArgs args) { + if (key.CaselessEq("pervisit")) { + args.Visit = value; + } else if (key.CaselessEq("perbuild")) { + args.Build = value; + } else if (key.CaselessEq("loadongoto")) { + args.LoadOnGoto = value; + } + } + + struct SearchArgs { public string Visit, Build, LoadOnGoto; } + } } \ No newline at end of file diff --git a/MCGalaxy/util/Formatting/Formatter.cs b/MCGalaxy/util/Formatting/Formatter.cs index 015650eaf..0148592be 100644 --- a/MCGalaxy/util/Formatting/Formatter.cs +++ b/MCGalaxy/util/Formatting/Formatter.cs @@ -130,4 +130,46 @@ namespace MCGalaxy return true; } } + + /// + /// Sorts a list of strings such that a1 a2 a3 comes before a10 a11 a12 and so on. + /// + public class AlphanumComparator : IComparer { + + // Simplified but based off of https://www.dotnetperls.com/alphanumeric-sorting + public int Compare(string a, string b) { + int result; + int aLen, bLen; + int aDigit = GetDigits(a, out aLen); + int bDigit = GetDigits(b, out bLen); + if (aDigit != -1 && bDigit != -1) { + result = aDigit.CompareTo(bDigit); + } else { + result = a.Substring(0, aLen).CompareTo(b.Substring(0, bLen)); + } + if (result != 0) return result; + return a.Length - b.Length; + } + + /// + /// Returns the digits on the end of the string. -1 if no integer found. + /// + static int GetDigits(string name, out int nameLength) { + nameLength = name.Length; + if (!Char.IsDigit(name[name.Length - 1])) return -1; + + int decimalShift = 1; + int number = 0; + for (int i = name.Length - 1; i >= 0; i--) { + if (!Char.IsDigit(name[i])) return number; + + nameLength--; + int digit = name[i] - '0'; //Paige Ruten: here's the most insane way to convert a digit char to an integer + number += digit * decimalShift; + decimalShift *= 10; + } + + return number; + } + } }