Add /os list, sort lists of displayed levels a bit more nicely

This commit is contained in:
Goodlyay 2025-08-20 06:02:01 -07:00
parent aeda35c708
commit 5ce514187f
5 changed files with 133 additions and 53 deletions

View File

@ -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");

View File

@ -108,6 +108,7 @@ namespace MCGalaxy.Commands.Info
static void SearchMaps(Player p, string keyword, string modifier) {
string[] allMaps = LevelInfo.AllMapNames();
List<string> maps = Wildcard.Filter(allMaps, keyword, map => map);
maps.Sort(new AlphanumComparator());
OutputList(p, keyword, "search levels", "maps", modifier, maps);
}

View File

@ -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;
}
/// <summary>
/// Returns all the os maps owned by p, sorted alphabetically.
/// </summary>
static List<string> AllOwnedBy(string playerName) {
string[] allMaps = LevelInfo.AllMapNames();
List<string> owned = new List<string>();
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 <player>",
"&H Lists all the os realms for <player>",
"&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),
}
);

View File

@ -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<string> 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; }
}
}

View File

@ -130,4 +130,46 @@ namespace MCGalaxy
return true;
}
}
/// <summary>
/// Sorts a list of strings such that a1 a2 a3 comes before a10 a11 a12 and so on.
/// </summary>
public class AlphanumComparator : IComparer<string> {
// 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;
}
/// <summary>
/// Returns the digits on the end of the string. -1 if no integer found.
/// </summary>
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;
}
}
}