diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 40bc8756..abbe3680 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -2835,26 +2835,66 @@ namespace TShockAPI { if (args.Parameters.Count > 1) { - string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); - Region r = TShock.Regions.GetRegionByName(regionName); - - if (r == null) + string regionName = args.Parameters[1]; + Region region = TShock.Regions.GetRegionByName(regionName); + if (region == null) { - args.Player.SendMessage("Region {0} does not exist"); + args.Player.SendErrorMessage("Region {0} does not exist.", regionName); break; } - args.Player.SendMessage(r.Name + ": P: " + r.DisableBuild + " X: " + r.Area.X + " Y: " + r.Area.Y + " W: " + - r.Area.Width + " H: " + r.Area.Height); - foreach (int s in r.AllowedIDs) - { - var user = TShock.Users.GetUserByID(s); - args.Player.SendMessage(r.Name + ": " + (user != null ? user.Name : "Unknown")); + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 2, args.Player, out pageNumber)) + break; + + List lines = new List + { + string.Format("X: {0}; Y: {1}; W: {2}; H: {3}, Z: {4}", region.Area.X, region.Area.Y, region.Area.Width, region.Area.Height, region.Z), + string.Concat("Owner: ", region.Owner), + string.Concat("Protected: ", region.DisableBuild.ToString()), + }; + + if (region.AllowedIDs.Count > 0) + { + IEnumerable sharedUsersSelector = region.AllowedIDs.Select(userId => + { + User user = TShock.Users.GetUserByID(userId); + if (user != null) + return user.Name; + else + return string.Concat("{ID: ", userId, "}"); + }); + List extraLines = PaginationTools.BuildLinesFromTerms(sharedUsersSelector.Distinct()); + extraLines[0] = "Shared with: " + extraLines[0]; + lines.AddRange(extraLines); } + else + { + lines.Add("Region is not shared with any users."); + } + + if (region.AllowedGroups.Count > 0) + { + List extraLines = PaginationTools.BuildLinesFromTerms(region.AllowedGroups.Distinct()); + extraLines[0] = "Shared with groups: " + extraLines[0]; + lines.AddRange(extraLines); + } + else + { + lines.Add("Region is not shared with any groups."); + } + + PaginationTools.SendPage( + args.Player, pageNumber, lines, new PaginationTools.Settings + { + HeaderFormat = string.Format("Information About Region \"{0}\" ({{0}}/{{1}}):", region.Name), + FooterFormat = "Type /region info {0} for more information." + } + ); } else { - args.Player.SendMessage("Invalid syntax! Proper syntax: /region info [name]", Color.Red); + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /region info [name]"); } break; @@ -2937,17 +2977,41 @@ namespace TShockAPI } break; } - case "help": - default: - { - args.Player.SendMessage("Avialable region commands:", Color.Green); - args.Player.SendMessage("/region set [1/2] /region define [name] /region protect [name] [true/false]", - Color.Yellow); - args.Player.SendMessage("/region name (provides region name)", Color.Yellow); - args.Player.SendMessage("/region delete [name] /region clear (temporary region)", Color.Yellow); - args.Player.SendMessage("/region allow [name] [regionname]", Color.Yellow); - args.Player.SendMessage("/region resize [regionname] [u/d/l/r] [amount]", Color.Yellow); - break; + case "help": + default: + { + int pageNumber; + int pageParamIndex = 0; + if (args.Parameters.Count > 1) + pageParamIndex = 1; + if (!PaginationTools.TryParsePageNumber(args.Parameters, pageParamIndex, args.Player, out pageNumber)) + return; + + PaginationTools.SendPage( + args.Player, pageNumber, new[] + { + "set [1/2] - Sets the temporary region points.", + "clear - Clears the temporary region points.", + "define [name] - Defines the region.", + "delete [name] - Deletes the given region.", + "name - Shows the name of the region at the given point.", + "list - Lists all regions.", + "resize [region] [u/d/l/r] [amount] - Resizes a region.", + "allow [user] [region] - Allows a user to a region.", + "remove [user] [region] - Removes a user from a region.", + "allowg [group] [region] - Allows a user group to a region.", + "removeg [group] [region] - Removes a user group from a region.", + "info [region] - Displays several information about the given region.", + "protect [name] [true/false] - Sets whether the tiles inside the region are protected or not.", + "z [name] [#] - Sets the z-order of the region.", + }, + new PaginationTools.Settings + { + HeaderFormat = "Available Region Sub-Commands ({0}/{1}):", + FooterFormat = "Type /region {0} for more sub-commands." + } + ); + break; } } } diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs index a0382a2e..78e7deaf 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/DB/RegionManager.cs @@ -381,30 +381,36 @@ namespace TShockAPI.DB return false; } - public bool AddNewUser(string regionName, String userName) + public bool AddNewUser(string regionName, string userName) { try { - string MergedIDs = string.Empty; + string mergedIDs = string.Empty; using ( - var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, + var reader = database.QueryReader("SELECT UserIds FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) - MergedIDs = reader.Get("UserIds"); + mergedIDs = reader.Get("UserIds"); } - if (string.IsNullOrEmpty(MergedIDs)) - MergedIDs = Convert.ToString(TShock.Users.GetUserID(userName)); - else - MergedIDs = MergedIDs + "," + Convert.ToString(TShock.Users.GetUserID(userName)); + string userIdToAdd = Convert.ToString(TShock.Users.GetUserID(userName)); + string[] ids = mergedIDs.Split(','); + // Is the user already allowed to the region? + if (ids.Contains(userIdToAdd)) + return true; - int q = database.Query("UPDATE Regions SET UserIds=@0 WHERE RegionName=@1 AND WorldID=@2", MergedIDs, + if (string.IsNullOrEmpty(mergedIDs)) + mergedIDs = userIdToAdd; + else + mergedIDs = string.Concat(mergedIDs, ",", userIdToAdd); + + int q = database.Query("UPDATE Regions SET UserIds=@0 WHERE RegionName=@1 AND WorldID=@2", mergedIDs, regionName, Main.worldID.ToString()); foreach (var r in Regions) { if (r.Name == regionName && r.WorldID == Main.worldID.ToString()) - r.setAllowedIDs(MergedIDs); + r.setAllowedIDs(mergedIDs); } return q != 0; } @@ -467,27 +473,33 @@ namespace TShockAPI.DB return false; } - public bool AllowGroup(string regionName, string groups) + public bool AllowGroup(string regionName, string groupName) { - string groupsNew = ""; + string mergedGroups = ""; using ( - var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, + var reader = database.QueryReader("SELECT Groups FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) - groupsNew = reader.Get("Groups"); + mergedGroups = reader.Get("Groups"); } - if (groupsNew != "") - groupsNew += ","; - groupsNew += groups; - int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groupsNew, + string[] groups = mergedGroups.Split(','); + // Is the group already allowed to the region? + if (groups.Contains(groupName)) + return true; + + if (mergedGroups != "") + mergedGroups += ","; + mergedGroups += groupName; + + int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", mergedGroups, regionName, Main.worldID.ToString()); Region r = GetRegionByName(regionName); if (r != null) { - r.SetAllowedGroups(groupsNew); + r.SetAllowedGroups(mergedGroups); } else { diff --git a/TShockAPI/PaginationTools.cs b/TShockAPI/PaginationTools.cs new file mode 100644 index 00000000..f7ab193c --- /dev/null +++ b/TShockAPI/PaginationTools.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TShockAPI { + public static class PaginationTools { + public delegate Tuple LineFormatterDelegate(object lineData, int lineIndex, int pageNumber); + + #region [Nested: Settings Class] + public class Settings { + public bool IncludeHeader { get; set; } + + private string headerFormat; + public string HeaderFormat + { + get { return this.headerFormat; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.headerFormat = value; + } + } + + public Color HeaderTextColor { get; set; } + public bool IncludeFooter { get; set; } + + private string footerFormat; + public string FooterFormat + { + get { return this.footerFormat; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.footerFormat = value; + } + } + + public Color FooterTextColor { get; set; } + public string NothingToDisplayString { get; set; } + public LineFormatterDelegate LineFormatter { get; set; } + public Color LineTextColor { get; set; } + + private int maxLinesPerPage; + + public int MaxLinesPerPage + { + get { return this.maxLinesPerPage; } + set + { + if (value <= 0) + throw new ArgumentException("The value has to be greater than zero."); + + this.maxLinesPerPage = value; + } + } + + private int pageLimit; + + public int PageLimit + { + get { return this.pageLimit; } + set + { + if (value < 0) + throw new ArgumentException("The value has to be greater than or equal to zero."); + + this.pageLimit = value; + } + } + + + public Settings() + { + this.IncludeHeader = true; + this.headerFormat = "Page {0} of {1}"; + this.HeaderTextColor = Color.Green; + this.IncludeFooter = true; + this.footerFormat = "Type / {0} for more."; + this.FooterTextColor = Color.Yellow; + this.NothingToDisplayString = null; + this.LineFormatter = null; + this.LineTextColor = Color.White; + this.maxLinesPerPage = 4; + this.pageLimit = 0; + } + } + #endregion + + public static void SendPage( + TSPlayer player, int pageNumber, IEnumerable dataToPaginate, int dataToPaginateCount, Settings settings = null) + { + if (settings == null) + settings = new Settings(); + + if (dataToPaginateCount == 0) + { + if (settings.NothingToDisplayString != null) + player.SendMessage(settings.NothingToDisplayString, settings.HeaderTextColor); + + return; + } + + int pageCount = ((dataToPaginateCount - 1) / settings.MaxLinesPerPage) + 1; + if (settings.PageLimit > 0 && pageCount > settings.PageLimit) + pageCount = settings.PageLimit; + if (pageNumber > pageCount) + pageNumber = pageCount; + + if (settings.IncludeHeader) + player.SendMessage(string.Format(settings.HeaderFormat, pageNumber, pageCount), settings.HeaderTextColor); + + int listOffset = (pageNumber - 1) * settings.MaxLinesPerPage; + int offsetCounter = 0; + int lineCounter = 0; + foreach (object lineData in dataToPaginate) + { + if (lineData == null) + continue; + if (offsetCounter++ < listOffset) + continue; + if (lineCounter++ == settings.MaxLinesPerPage) + break; + + string lineMessage; + Color lineColor = settings.LineTextColor; + if (lineData is Tuple) + { + var lineFormat = (Tuple)lineData; + lineMessage = lineFormat.Item1; + lineColor = lineFormat.Item2; + } + else if (settings.LineFormatter != null) + { + try + { + Tuple lineFormat = settings.LineFormatter(lineData, offsetCounter, pageNumber); + if (lineFormat == null) + continue; + + lineMessage = lineFormat.Item1; + lineColor = lineFormat.Item2; + } + catch (Exception ex) + { + throw new InvalidOperationException( + "The method referenced by LineFormatter has thrown an exception. See inner exception for details.", ex); + } + } + else + { + lineMessage = lineData.ToString(); + } + + if (lineMessage != null) + player.SendMessage(lineMessage, lineColor); + } + + if (lineCounter == 0) + { + if (settings.NothingToDisplayString != null) + player.SendMessage(settings.NothingToDisplayString, settings.HeaderTextColor); + } + else if (settings.IncludeFooter && pageNumber + 1 <= pageCount) + { + player.SendMessage(string.Format(settings.FooterFormat, pageNumber + 1, pageNumber, pageCount), settings.FooterTextColor); + } + } + + public static void SendPage(TSPlayer player, int pageNumber, IList dataToPaginate, Settings settings = null) + { + PaginationTools.SendPage(player, pageNumber, dataToPaginate, dataToPaginate.Count, settings); + } + + public static List BuildLinesFromTerms( + IEnumerable terms, Func termFormatter = null, string separator = ", ", int maxCharsPerLine = 80) + { + List lines = new List(); + StringBuilder lineBuilder = new StringBuilder(); + foreach (object term in terms) + { + if (term == null && termFormatter == null) + continue; + + string termString; + if (termFormatter != null) + { + try { + termString = termFormatter(term); + + if (termString == null) + continue; + } catch (Exception ex) + { + throw new ArgumentException( + "The method represented by termFormatter has thrown an exception. See inner exception for details.", ex); + } + } + else + { + termString = term.ToString(); + } + + bool goesOnNextLine = (lineBuilder.Length + termString.Length > maxCharsPerLine); + if (!goesOnNextLine) + { + if (lineBuilder.Length > 0) { + lineBuilder.Append(separator); + } + lineBuilder.Append(termString); + } + else + { + // A separator should always be at the end of a line as we know it is followed by another line. + lineBuilder.Append(separator); + lines.Add(lineBuilder.ToString()); + lineBuilder.Clear(); + + lineBuilder.Append(termString); + } + } + if (lineBuilder.Length > 0) + lines.Add(lineBuilder.ToString()); + + return lines; + } + + public static bool TryParsePageNumber( + List commandParameters, int expectedParamterIndex, TSPlayer errorMessageReceiver, out int pageNumber) + { + pageNumber = 1; + if (commandParameters.Count <= expectedParamterIndex) + return true; + + string pageNumberRaw = commandParameters[expectedParamterIndex]; + if (!int.TryParse(pageNumberRaw, out pageNumber) || pageNumber < 1) + { + if (errorMessageReceiver != null) + errorMessageReceiver.SendErrorMessage(string.Format("\"{0}\" is not a valid page number.", pageNumberRaw)); + + pageNumber = 1; + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index b93a594a..b0ec9f4c 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -604,21 +604,41 @@ namespace TShockAPI SendMessage(msg, Color.Yellow); } + public void SendInfoMessage(string format, params object[] args) + { + SendInfoMessage(string.Format(format, args)); + } + public virtual void SendSuccessMessage(string msg) { SendMessage(msg, Color.Green); } + public void SendSuccessMessage(string format, params object[] args) + { + SendSuccessMessage(string.Format(format, args)); + } + public virtual void SendWarningMessage(string msg) { SendMessage(msg, Color.OrangeRed); } + public void SendWarningMessage(string format, params object[] args) + { + SendWarningMessage(string.Format(format, args)); + } + public virtual void SendErrorMessage(string msg) { SendMessage(msg, Color.Red); } + public void SendErrorMessage(string format, params object[] args) + { + SendErrorMessage(string.Format(format, args)); + } + [Obsolete("Use SendErrorMessage, SendInfoMessage, or SendWarningMessage, or a custom color instead.")] public virtual void SendMessage(string msg) { diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 01ff3b32..b98c1645 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -1,203 +1,204 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {49606449-072B-4CF5-8088-AA49DA586694} - Library - Properties - TShockAPI - TShockAPI - v4.0 - 512 - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - bin\Debug\TShockAPI.XML - - - pdbonly - true - bin\Release\ - TRACE;COMPAT_SIGS - prompt - 4 - true - bin\Release\TShockAPI.XML - - - - ..\HttpBins\HttpServer.dll - - - ..\SqlBins\Mono.Data.Sqlite.dll - - - False - ..\SqlBins\MySql.Data.dll - True - - - False - ..\SqlBins\MySql.Web.dll - True - - - .\Newtonsoft.Json.dll - - - - - - - - - - ..\TerrariaServerBins\TerrariaServer.exe - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator - Designer - Resources.Designer.cs - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - - - - - - - - - "$(ProjectDir)postbuild.bat" - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {49606449-072B-4CF5-8088-AA49DA586694} + Library + Properties + TShockAPI + TShockAPI + v4.0 + 512 + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + bin\Debug\TShockAPI.XML + + + pdbonly + true + bin\Release\ + TRACE;COMPAT_SIGS + prompt + 4 + true + bin\Release\TShockAPI.XML + + + + ..\HttpBins\HttpServer.dll + + + ..\SqlBins\Mono.Data.Sqlite.dll + + + False + ..\SqlBins\MySql.Data.dll + True + + + False + ..\SqlBins\MySql.Web.dll + True + + + .\Newtonsoft.Json.dll + + + + + + + + + + ..\TerrariaServerBins\TerrariaServer.exe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Designer + Resources.Designer.cs + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + + + + + + + + + "$(ProjectDir)postbuild.bat" + + + + + + + --> \ No newline at end of file