diff --git a/MCGalaxy/Commands/Information/CmdSearch.cs b/MCGalaxy/Commands/Information/CmdSearch.cs index 56305bc04..11c20fc88 100644 --- a/MCGalaxy/Commands/Information/CmdSearch.cs +++ b/MCGalaxy/Commands/Information/CmdSearch.cs @@ -16,9 +16,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.RegularExpressions; +using MCGalaxy.Blocks; namespace MCGalaxy.Commands.Info { - public class CmdSearch : Command { + public class CmdSearch : Command { public override string name { get { return "Search"; } } public override string type { get { return CommandTypes.Information; } } public override LevelPermission defaultRank { get { return LevelPermission.Builder; } } @@ -34,111 +36,125 @@ namespace MCGalaxy.Commands.Info { SearchBlocks(p, keyword, modifier); } else if (args[0] == "rank" || args[0] == "ranks") { SearchRanks(p, keyword, modifier); - } else if (args[0] == "command" || args[0] == "commands" || args[0] == "cmd" || args[0] == "cmds") { + } else if (args[0] == "command" || args[0] == "commands") { SearchCommands(p, keyword, modifier); - } else if (args[0] == "user" || args[0] == "users" || args[0] == "player" || args[0] == "players") { + } else if (args[0] == "player" || args[0] == "players") { SearchPlayers(p, keyword, modifier); } else if (args[0] == "loaded") { SearchLoaded(p, keyword, modifier); - } else if (args[0] == "level" || args[0] == "levels") { - SearchUnloaded(p, keyword, modifier); + } else if (args[0] == "level" || args[0] == "levels" || args[0] == "maps") { + SearchMaps(p, keyword, modifier); } else { Help(p); } } + static string CoreBlockName(ExtBlock block) { return Block.Name(block.BlockID); } static void SearchBlocks(Player p, string keyword, string modifier) { - List blocks = new List(); + List blockIDs = new List(); for (byte id = 0; id < Block.Invalid; id++) { - string name = Block.Name(id); - if (name.CaselessContains(keyword) && !name.CaselessEq("unknown")) - blocks.Add(name); + if (!Block.Name(id).CaselessEq("unknown")) { + blockIDs.Add(new ExtBlock(id, 0)); + } } + StringFormatter getName; - OutputList(p, keyword, "search blocks", "blocks", - modifier, blocks, CmdBlocks.FormatBlockName); + if (!Player.IsSuper(p)) { + for (int id = Block.CpeCount; id < Block.Count; id++) { + if (p.level.CustomBlockDefs[id] == null) continue; + blockIDs.Add(new ExtBlock(Block.custom_block, (byte)id)); + } + getName = p.level.BlockName; + } else { + getName = CoreBlockName; + } + + List blocks = FilterList(blockIDs, keyword, getName, null, + b => Group.GetColor(BlockPerms.List[b.BlockID].MinRank) + getName(b)); + OutputList(p, keyword, "search blocks", "blocks", modifier, blocks); } static void SearchCommands(Player p, string keyword, string modifier) { - List cmds = new List(); - foreach (Command cmd in Command.all.commands) { - if (cmd.name.CaselessContains(keyword)) { - cmds.Add(cmd); continue; - } - - if (String.IsNullOrEmpty(cmd.shortcut)) continue; - if (cmd.shortcut.CaselessContains(keyword)) - cmds.Add(cmd); - } + List commands = FilterList(Command.all.commands, keyword, cmd => cmd.name, + null, cmd => CmdHelp.GetColor(cmd) + cmd.name); + List shortcuts = FilterList(Command.all.commands, keyword, cmd => cmd.shortcut, + cmd => !String.IsNullOrEmpty(cmd.shortcut), + cmd => CmdHelp.GetColor(cmd) + cmd.name); - OutputList(p, keyword, "search commands", "commands", - modifier, cmds, (cmd) => CmdHelp.GetColor(cmd) + cmd.name); + // Match both names and shortcuts + foreach (string shortcutCmd in shortcuts) { + if (commands.CaselessContains(shortcutCmd)) continue; + commands.Add(shortcutCmd); + } + + OutputList(p, keyword, "search commands", "commands", modifier, commands); } static void SearchRanks(Player p, string keyword, string modifier) { - List ranks = new List(); - foreach (Group g in Group.GroupList) { - if (g.Name.CaselessContains(keyword)) { - ranks.Add(g.ColoredName); - } - } - - OutputList(p, keyword, "search ranks", "ranks", - modifier, ranks, (name) => name); + List ranks = FilterList(Group.GroupList, keyword, grp => grp.Name, + null, grp => grp.ColoredName); + OutputList(p, keyword, "search ranks", "ranks", modifier, ranks); } static void SearchPlayers(Player p, string keyword, string modifier) { - List players = new List(); Player[] online = PlayerInfo.Online.Items; - foreach (Player who in online) { - if (who.name.CaselessContains(keyword) && Entities.CanSee(p, who)) - players.Add(who.ColoredName); - } - - OutputList(p, keyword, "search players", "players", - modifier, players, (name) => name); + List players = FilterList(online, keyword, pl => pl.name, + pl => Entities.CanSee(p, pl), pl => pl.ColoredName); + OutputList(p, keyword, "search players", "players", modifier, players); } static void SearchLoaded(Player p, string keyword, string modifier) { - List levels = new List(); Level[] loaded = LevelInfo.Loaded.Items; - foreach (Level level in loaded) { - if (level.name.CaselessContains(level.name)) - levels.Add(level.name); + List levels = FilterList(loaded, keyword, level => level.name); + OutputList(p, keyword, "search loaded", "loaded levels", modifier, levels); + } + + static void SearchMaps(Player p, string keyword, string modifier) { + string[] files = LevelInfo.AllMapFiles(); + List maps = FilterList(files, keyword, + map => Path.GetFileNameWithoutExtension(map)); + OutputList(p, keyword, "search levels", "maps", modifier, maps); + } + + internal static List FilterList(IList input, string keyword, StringFormatter formatter, + Predicate filter = null, StringFormatter listFormatter = null) { + List matches = new List(); + Regex regex = null; + // wildcard matching + if (keyword.Contains("*") || keyword.Contains("?")) { + string pattern = "^" + Regex.Escape(keyword).Replace("\\?", ".").Replace("\\*", ".*") + "$"; + regex = new Regex(pattern, RegexOptions.IgnoreCase); } - OutputList(p, keyword, "search loaded", "loaded levels", - modifier, levels, (level) => level); - } - - static void SearchUnloaded(Player p, string keyword, string modifier) { - List maps = new List(); - string[] files = LevelInfo.AllMapFiles(); - foreach (string file in files) { - string map = Path.GetFileNameWithoutExtension(file); - if (map.CaselessContains(keyword)) maps.Add(map); + foreach (T item in input) { + if (filter != null && !filter(item)) continue; + string name = formatter(item); + + if (regex != null) { if (!regex.IsMatch(name)) continue; } + else { if (!name.CaselessContains(keyword)) continue; } + + // format this item for display + if (listFormatter != null) name = listFormatter(item); + matches.Add(name); } - - OutputList(p, keyword, "search levels", "maps", - modifier, maps, (map) => map); + return matches; } - static void OutputList(Player p, string keyword, string cmd, string type, string modifier, - List items, StringFormatter formatter) { + static void OutputList(Player p, string keyword, string cmd, string type, string modifier, List items) { if (items.Count == 0) { Player.Message(p, "No {0} found containing \"{1}\"", type, keyword); } else { - MultiPageOutput.Output(p, items, formatter, cmd + " " + keyword, type, modifier, false); + MultiPageOutput.Output(p, items, item => item, cmd + " " + keyword, type, modifier, false); } } public override void Help(Player p) { - Player.Message(p, "%T/Search blocks [keyword] %H- finds blocks with that keyword"); - Player.Message(p, "%T/Search commands [keyword] %H- finds commands with that keyword"); - Player.Message(p, "%T/Search ranks [keyword] %H- finds ranks with that keyword"); - Player.Message(p, "%T/Search players [keyword] %H- find players with that keyword"); - Player.Message(p, "%T/Search loaded [keyword] %H- finds loaded levels with that keyword"); - Player.Message(p, "%T/Search levels [keyword] %H- find all levels with that keyword"); + Player.Message(p, "%T/Search [list] [keyword]"); + Player.Message(p, "%HFinds entries in a list that match the given keyword"); + Player.Message(p, "%H keyword can also include wildcard characters:"); + Player.Message(p, "%H * - placeholder for zero or more characters"); + Player.Message(p, "%H ? - placeholder for exactly one character"); + Player.Message(p, "%HLists available: &fblocks/commands/ranks/players/loaded/maps"); } } } diff --git a/MCGalaxy/Commands/World/CmdGoto.cs b/MCGalaxy/Commands/World/CmdGoto.cs index 4aac09b43..11b2e66dc 100644 --- a/MCGalaxy/Commands/World/CmdGoto.cs +++ b/MCGalaxy/Commands/World/CmdGoto.cs @@ -16,7 +16,9 @@ permissions and limitations under the Licenses. */ using System; +using System.Collections.Generic; using System.IO; +using MCGalaxy.Commands.Info; namespace MCGalaxy.Commands.World { public sealed class CmdGoto : Command { @@ -32,11 +34,25 @@ namespace MCGalaxy.Commands.World { public override void Use(Player p, string message) { if (message.Length == 0) { Help(p); return; } - if (message.CaselessEq("-random")) { + if (message.CaselessStarts("-random")) { string[] files = LevelInfo.AllMapFiles(); - string map = files[new Random().Next(files.Length)]; + string[] args = message.SplitSpaces(2); + string map; - map = Path.GetFileNameWithoutExtension(map); + // randomly only visit certain number of maps + if (args.Length > 1) { + List maps = CmdSearch.FilterList(files, args[1], + mapFile => Path.GetFileNameWithoutExtension(mapFile)); + if (maps.Count == 0) { + Player.Message(p, "No maps found containing \"{0}\"", args[1]); + return; + } + map = maps[new Random().Next(maps.Count)]; + } else { + map = files[new Random().Next(files.Length)]; + map = Path.GetFileNameWithoutExtension(map); + } + PlayerActions.ChangeMap(p, map); } else if (Formatter.ValidName(p, message, "level")) { PlayerActions.ChangeMap(p, message); diff --git a/MCGalaxy/Commands/building/CmdMark.cs b/MCGalaxy/Commands/building/CmdMark.cs index 13cefda17..f48d207b9 100644 --- a/MCGalaxy/Commands/building/CmdMark.cs +++ b/MCGalaxy/Commands/building/CmdMark.cs @@ -100,10 +100,11 @@ namespace MCGalaxy.Commands.Building { public override void Help(Player p) { Player.Message(p, "%T/Mark %H- Places a marker for selections, e.g for %T/z"); Player.Message(p, "%HUse ~ before a coordinate to mark relative to current position"); - Player.Message(p, "%HIf is not given, marks at where you are standing"); + Player.Message(p, "%HIf no coordinates are given, marks at where you are standing"); + Player.Message(p, "%HIf only x coordinate is given, it is used for y and z too"); Player.Message(p, " %He.g. /mark 30 y 20 will mark at (30, last y, 20)"); - Player.Message(p, "%HActivates the block (e.g. door) if no selection is in progress"); Player.Message(p, "%T/Mark all %H- Places markers at min and max corners of the map"); + Player.Message(p, "%HActivates the block (e.g. door) if no selection is in progress"); } } } diff --git a/MCGalaxy/Levels/IO/Importers/LvlImporter.cs b/MCGalaxy/Levels/IO/Importers/LvlImporter.cs index 2667cb588..7eaea4a27 100644 --- a/MCGalaxy/Levels/IO/Importers/LvlImporter.cs +++ b/MCGalaxy/Levels/IO/Importers/LvlImporter.cs @@ -50,11 +50,16 @@ namespace MCGalaxy.Levels.IO { lvl.roty = header[offset + 11]; gs.Read(lvl.blocks, 0, lvl.blocks.Length); - ReadCustomBlocksSection(lvl, gs); - + ReadCustomBlocksSection(lvl, gs); if (!metadata) return lvl; - ReadPhysicsSection(lvl, gs); - return lvl; + + for (;;) { + int section = gs.ReadByte(); + if (section == 0xFC) { + ReadPhysicsSection(lvl, gs); continue; + } + return lvl; + } } } @@ -98,7 +103,6 @@ namespace MCGalaxy.Levels.IO { } unsafe static void ReadPhysicsSection(Level lvl, Stream gs) { - if (gs.ReadByte() != 0xFC) return; byte[] buffer = new byte[sizeof(int)]; int read = gs.Read(buffer, 0, sizeof(int)); if (read < sizeof(int)) return; diff --git a/MCGalaxy/Player/PlayerActions.cs b/MCGalaxy/Player/PlayerActions.cs index af081519c..134ffaf2c 100644 --- a/MCGalaxy/Player/PlayerActions.cs +++ b/MCGalaxy/Player/PlayerActions.cs @@ -25,8 +25,7 @@ namespace MCGalaxy { public static class PlayerActions { /// Moves the player to the specified block coordinates. (bY is treated as player feet) - public static void MoveCoords(Player p, int bX, int bY, int bZ, - byte rotX, byte rotY) { + public static void MoveCoords(Player p, int bX, int bY, int bZ, byte rotX, byte rotY) { Position pos = Position.FromFeet(16 + bX * 32, bY * 32, 16 + bZ * 32); p.SendPos(Entities.SelfID, pos, new Orientation(rotX, rotY)); } diff --git a/MCGalaxy/Server/ServerConfig.cs b/MCGalaxy/Server/ServerConfig.cs index 7c060a1ee..2d95755ae 100644 --- a/MCGalaxy/Server/ServerConfig.cs +++ b/MCGalaxy/Server/ServerConfig.cs @@ -95,7 +95,7 @@ namespace MCGalaxy { public static int BackupInterval = 300; public static int BlockDBSaveInterval = 60; [ConfigString("backup-location", "Backup", "")] - public static string BackupDirectory = Path.Combine(Utils.FolderPath, "levels/backups"); + public static string BackupDirectory = Path.Combine(Utils.FolderPath, "levels/backups"); [ConfigInt("afk-minutes", "Other", 10)] public static int AutoAfkMins = 10;