diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index a680c08c..c783652f 100755 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -240,6 +240,10 @@ namespace TShockAPI { HelpText = "Manages projectile bans." }); + add(new Command(Permissions.managetile, TileBan, "tileban") + { + HelpText = "Manages tile bans." + }); add(new Command(Permissions.manageregion, Region, "region") { HelpText = "Manages regions." @@ -3090,9 +3094,178 @@ namespace TShockAPI } #endregion Projectile Management - #region Server Config Commands + #region Tile Management + private static void TileBan(CommandArgs args) + { + string subCmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower(); + switch (subCmd) + { + case "add": + #region Add tile + { + if (args.Parameters.Count != 2) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /tileban add "); + return; + } + short id; + if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Main.maxTileSets) + { + TShock.TileBans.AddNewBan(id); + args.Player.SendSuccessMessage("Banned tile {0}.", id); + } + else + args.Player.SendErrorMessage("Invalid tile ID!"); + } + #endregion + return; + case "allow": + #region Allow group to place tile + { + if (args.Parameters.Count != 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /tileban allow "); + return; + } - private static void SetSpawn(CommandArgs args) + short id; + if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Main.maxTileSets) + { + if (!TShock.Groups.GroupExists(args.Parameters[2])) + { + args.Player.SendErrorMessage("Invalid group."); + return; + } + + TileBan ban = TShock.TileBans.GetBanById(id); + if (ban == null) + { + args.Player.SendErrorMessage("Tile {0} is not banned.", id); + return; + } + if (!ban.AllowedGroups.Contains(args.Parameters[2])) + { + TShock.TileBans.AllowGroup(id, args.Parameters[2]); + args.Player.SendSuccessMessage("{0} has been allowed to place tile {1}.", args.Parameters[2], id); + } + else + args.Player.SendWarningMessage("{0} is already allowed to place tile {1}.", args.Parameters[2], id); + } + else + args.Player.SendErrorMessage("Invalid tile ID!"); + } + #endregion + return; + case "del": + #region Delete tile ban + { + if (args.Parameters.Count != 2) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /tileban del "); + return; + } + + short id; + if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Main.maxTileSets) + { + TShock.TileBans.RemoveBan(id); + args.Player.SendSuccessMessage("Unbanned tile {0}.", id); + return; + } + else + args.Player.SendErrorMessage("Invalid tile ID!"); + } + #endregion + return; + case "disallow": + #region Disallow group from placing tile + { + if (args.Parameters.Count != 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /tileban disallow "); + return; + } + + short id; + if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Main.maxTileSets) + { + if (!TShock.Groups.GroupExists(args.Parameters[2])) + { + args.Player.SendErrorMessage("Invalid group."); + return; + } + + TileBan ban = TShock.TileBans.GetBanById(id); + if (ban == null) + { + args.Player.SendErrorMessage("Tile {0} is not banned.", id); + return; + } + if (ban.AllowedGroups.Contains(args.Parameters[2])) + { + TShock.TileBans.RemoveGroup(id, args.Parameters[2]); + args.Player.SendSuccessMessage("{0} has been disallowed from placing tile {1}.", args.Parameters[2], id); + return; + } + else + args.Player.SendWarningMessage("{0} is already prevented from placing tile {1}.", args.Parameters[2], id); + } + else + args.Player.SendErrorMessage("Invalid tile ID!"); + } + #endregion + return; + case "help": + #region Help + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + + var lines = new List + { + "add - Adds a tile ban.", + "allow - Allows a group to place a tile.", + "del - Deletes a tile ban.", + "disallow - Disallows a group from place a tile.", + "list [page] - Lists all tile bans." + }; + + PaginationTools.SendPage(args.Player, pageNumber, lines, + new PaginationTools.Settings + { + HeaderFormat = "Tile Ban Sub-Commands ({0}/{1}):", + FooterFormat = "Type /tileban help {0} for more sub-commands." + } + ); + } + #endregion + return; + case "list": + #region List tile bans + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + IEnumerable tileIds = from tileBan in TShock.TileBans.TileBans + select tileBan.ID; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(tileIds), + new PaginationTools.Settings + { + HeaderFormat = "Tile bans ({0}/{1}):", + FooterFormat = "Type /tileban list {0} for more.", + NothingToDisplayString = "There are currently no banned tiles." + }); + } + #endregion + return; + } + } + #endregion Tile Management + + #region Server Config Commands + + private static void SetSpawn(CommandArgs args) { Main.spawnTileX = args.Player.TileX + 1; Main.spawnTileY = args.Player.TileY + 3; diff --git a/TShockAPI/DB/TileManager.cs b/TShockAPI/DB/TileManager.cs new file mode 100644 index 00000000..dc73f2c3 --- /dev/null +++ b/TShockAPI/DB/TileManager.cs @@ -0,0 +1,253 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2014 Nyx Studios (fka. The TShock Team) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using MySql.Data.MySqlClient; + +namespace TShockAPI.DB +{ + public class TileManager + { + private IDbConnection database; + public List TileBans = new List(); + + public TileManager(IDbConnection db) + { + database = db; + + var table = new SqlTable("TileBans", + new SqlColumn("TileId", MySqlDbType.Int32) { Primary = true }, + new SqlColumn("AllowedGroups", MySqlDbType.Text) + ); + var creator = new SqlTableCreator(db, + db.GetSqlType() == SqlType.Sqlite + ? (IQueryBuilder)new SqliteQueryCreator() + : new MysqlQueryCreator()); + creator.EnsureExists(table); + UpdateBans(); + } + + public void UpdateBans() + { + TileBans.Clear(); + + using (var reader = database.QueryReader("SELECT * FROM TileBans")) + { + while (reader != null && reader.Read()) + { + TileBan ban = new TileBan((short)reader.Get("TileId")); + ban.SetAllowedGroups(reader.Get("AllowedGroups")); + TileBans.Add(ban); + } + } + } + + public void AddNewBan(short id = 0) + { + try + { + database.Query("INSERT INTO TileBans (TileId, AllowedGroups) VALUES (@0, @1);", + id, ""); + + if (!TileIsBanned(id, null)) + TileBans.Add(new TileBan(id)); + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + + public void RemoveBan(short id) + { + if (!TileIsBanned(id, null)) + return; + try + { + database.Query("DELETE FROM TileBans WHERE TileId=@0;", id); + TileBans.Remove(new TileBan(id)); + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + + public bool TileIsBanned(short id) + { + if (TileBans.Contains(new TileBan(id))) + { + return true; + } + return false; + } + + public bool TileIsBanned(short id, TSPlayer ply) + { + if (TileBans.Contains(new TileBan(id))) + { + TileBan b = GetBanById(id); + return !b.HasPermissionToPlaceTile(ply); + } + return false; + } + + public bool AllowGroup(short id, string name) + { + string groupsNew = ""; + TileBan b = GetBanById(id); + if (b != null) + { + try + { + groupsNew = String.Join(",", b.AllowedGroups); + if (groupsNew.Length > 0) + groupsNew += ","; + groupsNew += name; + b.SetAllowedGroups(groupsNew); + + int q = database.Query("UPDATE TileBans SET AllowedGroups=@0 WHERE TileId=@1", groupsNew, + id); + + return q > 0; + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + + return false; + } + + public bool RemoveGroup(short id, string group) + { + TileBan b = GetBanById(id); + if (b != null) + { + try + { + b.RemoveGroup(group); + string groups = string.Join(",", b.AllowedGroups); + int q = database.Query("UPDATE TileBans SET AllowedGroups=@0 WHERE TileId=@1", groups, + id); + + if (q > 0) + return true; + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + return false; + } + + public TileBan GetBanById(short id) + { + foreach (TileBan b in TileBans) + { + if (b.ID == id) + { + return b; + } + } + return null; + } + } + + public class TileBan : IEquatable + { + public short ID { get; set; } + public List AllowedGroups { get; set; } + + public TileBan(short id) + : this() + { + ID = id; + AllowedGroups = new List(); + } + + public TileBan() + { + ID = 0; + AllowedGroups = new List(); + } + + public bool Equals(TileBan other) + { + return ID == other.ID; + } + + public bool HasPermissionToPlaceTile(TSPlayer ply) + { + if (ply == null) + return false; + + if (ply.Group.HasPermission(Permissions.canusebannedtiles)) + return true; + + var cur = ply.Group; + var traversed = new List(); + while (cur != null) + { + if (AllowedGroups.Contains(cur.Name)) + { + return true; + } + if (traversed.Contains(cur)) + { + throw new InvalidOperationException("Infinite group parenting ({0})".SFormat(cur.Name)); + } + traversed.Add(cur); + cur = cur.Parent; + } + return false; + // could add in the other permissions in this class instead of a giant if switch. + } + + public void SetAllowedGroups(String groups) + { + // prevent null pointer exceptions + if (!string.IsNullOrEmpty(groups)) + { + List groupArr = groups.Split(',').ToList(); + + for (int i = 0; i < groupArr.Count; i++) + { + groupArr[i] = groupArr[i].Trim(); + //Console.WriteLine(groupArr[i]); + } + AllowedGroups = groupArr; + } + } + + public bool RemoveGroup(string groupName) + { + return AllowedGroups.Remove(groupName); + } + + public override string ToString() + { + return ID + (AllowedGroups.Count > 0 ? " (" + String.Join(",", AllowedGroups) + ")" : ""); + } + } +} \ No newline at end of file diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index b5c3bc5b..88724a27 100755 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1910,6 +1910,17 @@ namespace TShockAPI Item selectedItem = args.Player.SelectedItem; int lastKilledProj = args.Player.LastKilledProjectile; Tile tile = Main.tile[tileX, tileY]; + + if (action == EditAction.PlaceTile) + { + if (TShock.TileBans.TileIsBanned(editData, args.Player)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Player.SendErrorMessage("You do not have permission to place this tile."); + return true; + } + } + if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type)) { // If the tile is an axe tile and they aren't selecting an axe, they're hacking. diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index aa8a54d4..3edd76aa 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -67,6 +67,9 @@ namespace TShockAPI [Description("User can manage projectile bans.")] public static readonly string manageprojectile = "tshock.admin.projectileban"; + [Description("User can manage tile bans.")] + public static readonly string managetile = "tshock.admin.tileban"; + [Description("User can manage groups.")] public static readonly string managegroup = "tshock.admin.group"; @@ -343,6 +346,9 @@ namespace TShockAPI [Description("Player can use banned projectiles.")] public static readonly string canusebannedprojectiles = "tshock.projectiles.usebanned"; + [Description("Player can place banned tiles.")] + public static readonly string canusebannedtiles = "tshock.tiles.usebanned"; + /// /// Lists all commands associated with a given permission /// diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index eb487cce..dc6b6843 100755 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -64,6 +64,7 @@ namespace TShockAPI public static UserManager Users; public static ItemManager Itembans; public static ProjectileManagager ProjectileBans; + public static TileManager TileBans; public static RememberedPosManager RememberedPos; public static CharacterManager CharacterDB; public static ConfigFile Config { get; set; } @@ -231,6 +232,7 @@ namespace TShockAPI Groups = new GroupManager(DB); Itembans = new ItemManager(DB); ProjectileBans = new ProjectileManagager(DB); + TileBans = new TileManager(DB); RememberedPos = new RememberedPosManager(DB); CharacterDB = new CharacterManager(DB); RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 6d8b0d64..75ef9782 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -74,6 +74,7 @@ + @@ -180,7 +181,7 @@ - +