From b1ccfbdc14161ce2550aa82898de4806b710f9b6 Mon Sep 17 00:00:00 2001 From: CoderCow Date: Sun, 4 Jun 2017 18:20:17 +0200 Subject: [PATCH] feat(/scanchests): add /scanchests command to search chests for items and /tpchest to teleport to them Closes #57 Closes #55 --- Implementation/ChestManager.cs | 2 +- Implementation/ProtectionManager.cs | 7 + Implementation/ProtectorPlugin.cs | 1 + Implementation/UserInteractionHandler.cs | 164 +++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) diff --git a/Implementation/ChestManager.cs b/Implementation/ChestManager.cs index ffd5e54..ceaacc3 100644 --- a/Implementation/ChestManager.cs +++ b/Implementation/ChestManager.cs @@ -414,7 +414,7 @@ public void DestroyChest(DPoint anyTileLocation) { public IEnumerable EnumerateAllChests() { for (int i = 0; i < Main.chest.Length; i++) { - if (Main.chest[i] != null) + if (Main.chest[i] != null && i != ChestManager.DummyChestIndex) yield return new ChestAdapter(i, Main.chest[i]); } diff --git a/Implementation/ProtectionManager.cs b/Implementation/ProtectionManager.cs index 8db6518..105442e 100644 --- a/Implementation/ProtectionManager.cs +++ b/Implementation/ProtectionManager.cs @@ -57,6 +57,13 @@ public ProtectionManager( this.WorldMetadata = worldMetadata; } + public ProtectionEntry GetProtectionAt(DPoint tileLocation) { + foreach (ProtectionEntry protection in this.EnumerateProtectionEntries(tileLocation)) + return protection; + + return null; + } + public IEnumerable EnumerateProtectionEntries(DPoint tileLocation) { ITile tile = TerrariaUtils.Tiles[tileLocation]; if (!tile.active()) diff --git a/Implementation/ProtectorPlugin.cs b/Implementation/ProtectorPlugin.cs index 698279c..8886f01 100644 --- a/Implementation/ProtectorPlugin.cs +++ b/Implementation/ProtectorPlugin.cs @@ -38,6 +38,7 @@ public class ProtectorPlugin: TerrariaPlugin, IDisposable { public const string BankChestShare_Permission = "prot.bankchestshare"; public const string NoBankChestLimits_Permission = "prot.nobankchestlimits"; public const string FreeTradeChests_Permission = "prot.freetradechests"; + public const string ScanChests_Permission = "prot.scanchests"; public const string Utility_Permission = "prot.utility"; public const string Cfg_Permission = "prot.cfg"; diff --git a/Implementation/UserInteractionHandler.cs b/Implementation/UserInteractionHandler.cs index 9ed04cc..6f57e82 100644 --- a/Implementation/UserInteractionHandler.cs +++ b/Implementation/UserInteractionHandler.cs @@ -1,10 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using Microsoft.Xna.Framework; using OTAPI.Tile; @@ -15,6 +17,7 @@ using Terraria.Plugins.Common; using Terraria.Plugins.Common.Collections; using TShockAPI; +using TShockAPI.DB; namespace Terraria.Plugins.CoderCow.Protector { public class UserInteractionHandler: UserInteractionHandlerBase, IDisposable { @@ -134,6 +137,15 @@ public UserInteractionHandler( this.TradeChestCommand_Exec, this.TradeChestCommand_HelpCallback, ProtectorPlugin.SetTradeChests_Permission, allowServer: false ); + base.RegisterCommand( + new[] { "scanchests" }, + this.ScanChestsCommand_Exec, this.ScanChestsCommand_HelpCallback, ProtectorPlugin.ScanChests_Permission + ); + base.RegisterCommand( + new[] { "tpchest" }, + this.TpChestCommand_Exec, this.TpChestCommand_HelpCallback, ProtectorPlugin.ScanChests_Permission, + allowServer: false + ); #endregion #if DEBUG @@ -2080,6 +2092,158 @@ private bool TradeChestCommand_HelpCallback(CommandArgs args) { } #endregion + private readonly ConditionalWeakTable scanChestsResults = new ConditionalWeakTable(); + #region [Command Handling /scanchests] + private void ScanChestsCommand_Exec(CommandArgs args) { + if (args == null || this.IsDisposed) + return; + + if (args.Parameters.Count == 0) { + args.Player.SendErrorMessage("Proper syntax: /scanchests []"); + args.Player.SendInfoMessage("Type /scanchests help to get more information about this command."); + return; + } + + string itemNamePart; + int pageNumber = 1; + if (args.Parameters.Count == 1) { + itemNamePart = args.Parameters[0]; + } else { + string lastParam = args.Parameters[args.Parameters.Count - 1]; + if (lastParam.Length <= 2 && int.TryParse(lastParam, out pageNumber)) + itemNamePart = args.ParamsToSingleString(0, 1); + else + itemNamePart = args.ParamsToSingleString(); + + if (pageNumber < 1) { + args.Player.SendErrorMessage($"\"{lastParam}\" is not a valid page number."); + return; + } + } + + List itemsToLookup = TShock.Utils.GetItemByIdOrName(itemNamePart); + if (itemsToLookup.Count == 0) { + args.Player.SendErrorMessage($"Unable to guess a valid item type from \"{itemNamePart}\"."); + return; + } + + // DPoint is the chest location. + List> results = new List>(); + foreach (IChest chest in this.ChestManager.EnumerateAllChests()) { + List matchingItems = new List( + from item in chest.Items + where itemsToLookup.Any(li => li.netID == item.Type) + select item); + + if (matchingItems.Count > 0) + results.Add(new Tuple(matchingItems.ToArray(), chest.Location)); + } + + DPoint[] resultsChestLocations = results.Select(r => r.Item2).ToArray(); + this.scanChestsResults.Remove(args.Player); + this.scanChestsResults.Add(args.Player, resultsChestLocations); + + PaginationTools.SendPage(args.Player, pageNumber, results, new PaginationTools.Settings { + HeaderFormat = $"The Following Chests Contain \"{itemNamePart}\" (Page {{0}} of {{1}})", + NothingToDisplayString = $"No chest contains items matching \"{itemNamePart}\"", + LineTextColor = Color.LightGray, + MaxLinesPerPage = 10, + LineFormatter = (lineData, dataIndex, pageNumberLocal) => { + var result = (lineData as Tuple); + if (result == null) + return null; + + ItemData[] foundItems = result.Item1; + DPoint chestLocation = result.Item2; + + string foundItemsString = string.Join(" ", foundItems.Select(i => TShock.Utils.ItemTag(i.ToItem()))); + + string chestOwner = "{not protected}"; + ProtectionEntry protection = this.ProtectionManager.GetProtectionAt(chestLocation); + if (protection != null) { + User tsUser = TShock.Users.GetUserByID(protection.Owner); + chestOwner = tsUser?.Name ?? $"{{user id: {protection.Owner}}}"; + } + + return new Tuple($"{dataIndex}. Chest owned by {TShock.Utils.ColorTag(chestOwner, Color.Red)} contains {foundItemsString}", Color.LightGray); + } + }); + + if (results.Count > 0) + args.Player.SendSuccessMessage("Type /tpchest to teleport to the respective chest."); + } + + private bool ScanChestsCommand_HelpCallback(CommandArgs args) { + if (args == null || this.IsDisposed) + return false; + + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return false; + + switch (pageNumber) { + default: + args.Player.SendMessage("Command reference for /scanchests (Page 1 of 1)", Color.Lime); + args.Player.SendMessage("/scanchests [page]", Color.White); + args.Player.SendMessage("Searches all chests in the current world for items matching the given name. The user will be able to teleport to the chests found by this command using /tpchest.", Color.LightGray); + args.Player.SendMessage(string.Empty, Color.LightGray); + args.Player.SendMessage("item name = Part of name of the item(s) to check for.", Color.LightGray); + break; + } + + return true; + } + #endregion + + #region [Command Handling /tpchest] + private void TpChestCommand_Exec(CommandArgs args) { + if (args == null || this.IsDisposed) + return; + + if (args.Parameters.Count != 1) { + args.Player.SendErrorMessage("Proper syntax: /tpchest "); + args.Player.SendInfoMessage("Type /tpchest help to get more information about this command."); + return; + } + + DPoint[] chestLocations; + if (!this.scanChestsResults.TryGetValue(args.Player, out chestLocations)) { + args.Player.SendErrorMessage("You have to use /scanchests before using this command."); + return; + } + + int chestIndex; + if (!int.TryParse(args.Parameters[0], out chestIndex) || chestIndex < 1 || chestIndex > chestLocations.Length) { + args.Player.SendErrorMessage($"\"{args.Parameters[0]}\" is not a valid result index."); + return; + } + + DPoint chestLocation = chestLocations[chestIndex - 1]; + args.Player.Teleport(chestLocation.X * 16, chestLocation.Y * 16); + } + + private bool TpChestCommand_HelpCallback(CommandArgs args) { + if (args == null || this.IsDisposed) + return false; + + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return false; + + switch (pageNumber) { + default: + args.Player.SendMessage("Command reference for /tpchest (Page 1 of 1)", Color.Lime); + args.Player.SendMessage("/tpchest ", Color.White); + args.Player.SendMessage("Teleports you to a chest that was found by the /scanchests command.", Color.LightGray); + args.Player.SendMessage(string.Empty, Color.LightGray); + args.Player.SendMessage("result index = The index of the search result.", Color.LightGray); + break; + } + + return true; + } + #endregion + #region [Hook Handlers] public override bool HandleTileEdit(TSPlayer player, TileEditType editType, int blockType, DPoint location, int objectStyle) { return this.HandleTileEdit(player, editType, blockType, location, objectStyle, false);