diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4e2b5a..54e5d06a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,9 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Added `TSPlayer.CheckIgnores()` and removed `TShock.CheckIgnores(TSPlayer)`. (@hakusaro) * Hooks inside TShock can now be registered with their `Register` method and can be prioritized according to the TShock HandlerList system. (@hakusaro) * Fix message requiring login not using the command specifier set in the config file. (@hakusaro) +* Move `TShock.CheckRangePermission()` to `TSPlayer.IsInRange` which **returns the opposite** of what the previous method did (see updated docs). (@hakusaro) * Move `TShock.CheckSpawn` to `Utils.IsInSpawn`. (@hakusaro) +* Replace `TShock.CheckTilePermission` with `TSPlayer.HasBuildPermission`, `TSPlayer.HasPaintPermission`, and `TSPlayer.HasModifiedIceSuccessfully` respectively. (@hakusaro) * Fix stack hack detection being inconsistent between two different check points. Moved `TShock.HackedInventory` to `TSPlayer.HasHackedItemStacks`. Added `GetDataHandlers.GetDataHandledEventArgs` which is where most hooks will inherit from in the future. (@hakusaro) * All `GetDataHandlers` hooks now inherit from `GetDataHandledEventArgs` which includes a `TSPlayer` and a `MemoryStream` of raw data. (@hakusaro) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 3f14e977..40e2c355 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -99,14 +99,14 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, args.X, args.Y)) + if (!args.Player.HasBuildPermission(args.X, args.Y)) { NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, args.X, args.Y)) + if (!args.Player.IsInRange(args.X, args.Y)) { NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); args.Handled = true; @@ -137,7 +137,7 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, args.X, args.Y)) + if (!args.Player.HasBuildPermission(args.X, args.Y)) { args.Handled = true; return; @@ -155,13 +155,13 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, args.X, args.Y)) + if (!args.Player.HasBuildPermission(args.X, args.Y)) { args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, args.X, args.Y)) + if (!args.Player.IsInRange(args.X, args.Y)) { args.Handled = true; return; @@ -206,7 +206,7 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, x, y)) + if (!args.Player.HasBuildPermission(x, y)) { args.Handled = true; return; @@ -276,7 +276,7 @@ namespace TShockAPI } if (TShock.Config.RangeChecks && - TShock.CheckRangePermission(args.Player, (int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128)) + !args.Player.IsInRange((int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128)) { args.Player.SendData(PacketTypes.NpcUpdate, "", id); args.Handled = true; @@ -342,7 +342,7 @@ namespace TShockAPI return; } - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) + if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) { args.Player.SendData(PacketTypes.PlayerHp, "", id); args.Player.SendData(PacketTypes.PlayerUpdate, "", id); @@ -396,7 +396,7 @@ namespace TShockAPI // client side (but only if it passed the range check) (i.e., return false) if (type == 0) { - if (TShock.CheckRangePermission(args.Player, (int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) + if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) { // Causes item duplications. Will be re added if necessary //args.Player.SendData(PacketTypes.ItemDrop, "", id); @@ -408,7 +408,7 @@ namespace TShockAPI return; } - if (TShock.CheckRangePermission(args.Player, (int)(pos.X / 16f), (int)(pos.Y / 16f))) + if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f))) { args.Player.SendData(PacketTypes.ItemDrop, "", id); args.Handled = true; @@ -488,7 +488,7 @@ namespace TShockAPI return; } - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) + if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) { args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); args.Handled = true; @@ -533,13 +533,13 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) + if (!args.Player.HasBuildPermission(Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) { args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, Main.chest[id].x, Main.chest[id].y)) + if (!args.Player.IsInRange(Main.chest[id].x, Main.chest[id].y)) { args.Handled = true; return; @@ -556,18 +556,15 @@ namespace TShockAPI short y = args.Y; byte homeless = args.Homeless; - // Calls to TShock.CheckTilePermission need to be broken up into different subsystems - // In particular, this handles both regions and other things. Ouch. - if (TShock.CheckTilePermission(args.Player, x, y)) + if (!args.Player.HasBuildPermission(x, y)) { - args.Player.SendErrorMessage("You do not have access to modify this area."); args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, Convert.ToByte(Main.npc[id].homeless)); args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, x, y)) + if (!args.Player.IsInRange(x, y)) { args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, Convert.ToByte(Main.npc[id].homeless)); @@ -587,13 +584,13 @@ namespace TShockAPI return; } - if (TShock.CheckRangePermission(args.Player, args.X, args.Y)) + if (!args.Player.IsInRange(args.X, args.Y)) { args.Handled = true; return; } - if (TShock.CheckTilePermission(args.Player, args.X, args.Y) && TShock.Config.RegionProtectChests) + if (!args.Player.HasBuildPermission(args.X, args.Y) && TShock.Config.RegionProtectChests) { args.Handled = true; return; @@ -648,14 +645,14 @@ namespace TShockAPI } } - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) + if (!args.Player.HasBuildPermission(tileX, tileY)) { args.Player.SendTileSquare(tileX, tileY, 3); args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) + if (!args.Player.IsInRange(tileX, tileY)) { args.Player.SendTileSquare(tileX, tileY, 3); args.Handled = true; @@ -781,14 +778,14 @@ namespace TShockAPI } } - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) + if (!args.Player.HasBuildPermission(tileX, tileY)) { args.Player.SendTileSquare(tileX, tileY, 1); args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, tileX, tileY, 16)) + if (!args.Player.IsInRange(tileX, tileY, 16)) { args.Player.SendTileSquare(tileX, tileY, 1); args.Handled = true; @@ -1169,7 +1166,8 @@ namespace TShockAPI { for (int j = y; j < y + tileData.Height; j++) { - if (TShock.CheckTilePermission(args.Player, i, j, type, EditAction.PlaceTile)) + if (!args.Player.HasModifiedIceSuccessfully(i, j, type, EditAction.PlaceTile) + && !args.Player.HasBuildPermission(i, j)) { args.Player.SendTileSquare(i, j, 4); args.Handled = true; @@ -1183,7 +1181,7 @@ namespace TShockAPI || type != TileID.SilkRope || type != TileID.VineRope || type != TileID.WebRope) - && TShock.CheckRangePermission(args.Player, x, y)) + && !args.Player.IsInRange(x, y)) { args.Player.SendTileSquare(x, y, 4); args.Handled = true; @@ -1500,14 +1498,15 @@ namespace TShockAPI return; } - if (TShock.CheckTilePermission(args.Player, tileX, tileY, editData, action)) + if (!args.Player.HasModifiedIceSuccessfully(tileX, tileY, editData, action) + && !args.Player.HasBuildPermission(tileX, tileY)) { args.Player.SendTileSquare(tileX, tileY, 4); args.Handled = true; return; } - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) + if (!args.Player.IsInRange(tileX, tileY)) { if (action == EditAction.PlaceTile && (editData == TileID.Rope || editData == TileID.SilkRope || editData == TileID.VineRope || editData == TileID.WebRope)) { @@ -1683,8 +1682,8 @@ namespace TShockAPI var tile = Main.tile[realx, realy]; var newtile = tiles[x, y]; - if (TShock.CheckTilePermission(args.Player, realx, realy) || - TShock.CheckRangePermission(args.Player, realx, realy)) + if (!args.Player.HasBuildPermission(realx, realy) || + !args.Player.IsInRange(realx, realy)) { continue; } diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 627c0e49..d5fafec5 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -2563,7 +2563,7 @@ namespace TShockAPI args.Player.ActiveChest = id; - if (TShock.CheckTilePermission(args.Player, x, y) && TShock.Config.RegionProtectChests) + if (!args.Player.HasBuildPermission(x, y) && TShock.Config.RegionProtectChests) { args.Player.SendData(PacketTypes.ChestOpen, "", -1); return true; @@ -2603,13 +2603,13 @@ namespace TShockAPI if (OnSignEvent(args.Player, args.Data, id, x, y)) return true; - if (TShock.CheckTilePermission(args.Player, x, y)) + if (!args.Player.HasBuildPermission(x, y)) { args.Player.SendData(PacketTypes.SignNew, "", id); return true; } - if (TShock.CheckRangePermission(args.Player, x, y)) + if (!args.Player.IsInRange(x, y)) { args.Player.SendData(PacketTypes.SignNew, "", id); return true; @@ -2928,8 +2928,8 @@ namespace TShockAPI } if (args.Player.IsBouncerThrottled() || - TShock.CheckTilePermission(args.Player, x, y, true) || - TShock.CheckRangePermission(args.Player, x, y)) + !args.Player.HasPaintPermission(x, y) || + !args.Player.IsInRange(x, y)) { args.Player.SendData(PacketTypes.PaintTile, "", x, y, Main.tile[x, y].color()); return true; @@ -2972,8 +2972,8 @@ namespace TShockAPI } if (args.Player.IsBouncerThrottled() || - TShock.CheckTilePermission(args.Player, x, y, true) || - TShock.CheckRangePermission(args.Player, x, y)) + !args.Player.HasPaintPermission(x, y) || + !args.Player.IsInRange(x, y)) { args.Player.SendData(PacketTypes.PaintWall, "", x, y, Main.tile[x, y].wallColor()); return true; @@ -3322,7 +3322,7 @@ namespace TShockAPI return true; } - if (TShock.CheckRangePermission(args.Player, (int)position.X, (int)position.Y)) + if (!args.Player.IsInRange((int)position.X, (int)position.Y)) { return true; } diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 89bf758c..3c747b9d 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -530,30 +530,147 @@ namespace TShockAPI public bool SilentJoinInProgress; + /// Checks if a player is in range of a given tile if range checks are enabled. + /// The x coordinate of the tile. + /// The y coordinate of the tile. + /// The range to check for. + /// True if the player is in range of a tile or if range checks are off. False if not. + public bool IsInRange(int x, int y, int range = 32) + { + if (TShock.Config.RangeChecks && ((Math.Abs(TileX - x) > range) || (Math.Abs(TileY - y) > range))) + { + return false; + } + return true; + } + + private enum BuildPermissionFailPoint + { + GeneralBuild, + SpawnProtect, + Regions + } + + /// Determines if the player can build on a given point. + /// The x coordinate they want to build at. + /// The y coordinate they want to paint at. + /// True if the player can build at the given point from build, spawn, and region protection. + public bool HasBuildPermission(int x, int y, bool shouldWarnPlayer = true) + { + BuildPermissionFailPoint failure = BuildPermissionFailPoint.GeneralBuild; + // The goal is to short circuit on easy stuff as much as possible. + // Don't compute permissions unless needed, and don't compute taxing stuff unless needed. + + // If the player has bypass on build protection or building is enabled; continue + // (General build protection takes precedence over spawn protection) + if (!TShock.Config.DisableBuild || HasPermission(Permissions.antibuild)) + { + failure = BuildPermissionFailPoint.SpawnProtect; + // If they have spawn protect bypass, or it isn't spawn, or it isn't in spawn; continue + // (If they have spawn protect bypass, we don't care if it's spawn or not) + if (!TShock.Config.SpawnProtection || HasPermission(Permissions.editspawn) || !Utils.IsInSpawn(x, y)) + { + failure = BuildPermissionFailPoint.Regions; + // If they have build permission in this region, then they're allowed to continue + if (TShock.Regions.CanBuild(x, y, this)) + { + return true; + } + } + } + // If they lack build permission, they end up here. + // If they have build permission but lack the ability to edit spawn and it's spawn, they end up here. + // If they have build, it isn't spawn, or they can edit spawn, but they fail the region check, they end up here. + + // If they shouldn't be warned, exit early. + if (!shouldWarnPlayer) + return false; + + // Space out warnings by 2 seconds so that they don't get spammed. + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - lastPermissionWarning) < 2000) + { + return false; + } + + // If they should be warned, warn them. + switch (failure) + { + case BuildPermissionFailPoint.GeneralBuild: + SendErrorMessage("You lack permission to build on this server."); + break; + case BuildPermissionFailPoint.SpawnProtect: + SendErrorMessage("You lack permission to build in the spawn point."); + break; + case BuildPermissionFailPoint.Regions: + SendErrorMessage("You lack permission to build in this region."); + break; + } + + // Set the last warning time to now. + lastPermissionWarning = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + + return false; + } + + /// Determines if the player can paint on a given point. Checks general build permissions, then paint. + /// The x coordinate they want to paint at. + /// The y coordinate they want to paint at. + /// True if they can paint. + public bool HasPaintPermission(int x, int y) + { + return HasBuildPermission(x, y) || HasPermission(Permissions.canpaint); + } + + /// Checks if a player can place ice, and if they can, tracks ice placements and removals. + /// The x coordinate of the suspected ice block. + /// The y coordinate of the suspected ice block. + /// The tile type of the suspected ice block. + /// The EditAction on the suspected ice block. + /// True if a player successfully places an ice tile or removes one of their past ice tiles. + public bool HasModifiedIceSuccessfully(int x, int y, short tileType, GetDataHandlers.EditAction editAction) + { + // The goal is to short circuit ASAP. + // A subsequent call to HasBuildPermission can figure this out if not explicitly ice. + if (!TShock.Config.AllowIce) + { + return false; + } + + // They've placed some ice. Horrible! + if (editAction == GetDataHandlers.EditAction.PlaceTile && tileType == TileID.MagicalIceBlock) + { + IceTiles.Add(new Point(x, y)); + return true; + } + + // The edit wasn't an add, so we check to see if the position matches any of the known ice tiles + if (editAction == GetDataHandlers.EditAction.KillTile) + { + foreach (Point p in IceTiles) + { + // If they're trying to kill ice or dirt, and the tile was in the list, we allow it. + if (p.X == x && p.Y == y && (Main.tile[p.X, p.Y].type == TileID.Dirt || Main.tile[p.X, p.Y].type == TileID.MagicalIceBlock)) + { + IceTiles.Remove(p); + return true; + } + } + } + + // Only a small number of cases let this happen. + return false; + } + /// /// A list of points where ice tiles have been placed. /// public List IceTiles; /// - /// Unused, can be removed. + /// The last time the player was warned for build permissions. + /// In MS, defaults to 1 (so it will warn on the first attempt). /// - public long RPm = 1; - - /// - /// World protection message cool down. - /// - public long WPm = 1; - - /// - /// Spawn protection message cool down. - /// - public long SPm = 1; - - /// - /// Permission to build message cool down. - /// - public long BPm = 1; + public long lastPermissionWarning = 1; /// /// The time in ms when the player has logged in. diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 0996c9a9..d534d67e 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -576,7 +576,7 @@ namespace TShockAPI return; } - if (CheckRangePermission(tsplr, args.Chest.x, args.Chest.y)) + if (!tsplr.IsInRange(args.Chest.x, args.Chest.y)) { args.Handled = true; return; @@ -1726,178 +1726,6 @@ namespace TShockAPI e.Handled = true; } - - - - /// CheckRangePermission - Checks if a player has permission to modify a tile dependent on range checks. - /// player - The TSPlayer object. - /// x - The x coordinate of the tile. - /// y - The y coordinate of the tile. - /// range - The range to check for. - /// bool - True if the player should not be able to place the tile. False if they can, or if range checks are off. - public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32) - { - if (Config.RangeChecks && ((Math.Abs(player.TileX - x) > range) || (Math.Abs(player.TileY - y) > range))) - { - return true; - } - return false; - } - - /// CheckTilePermission - Checks to see if a player has permission to modify a tile in general. - /// player - The TSPlayer object. - /// tileX - The x coordinate of the tile. - /// tileY - The y coordinate of the tile. - /// tileType - The tile type. - /// actionType - The type of edit that took place. - /// bool - True if the player should not be able to modify a tile. - public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, short tileType, GetDataHandlers.EditAction actionType) - { - if (!player.HasPermission(Permissions.canbuild)) - { - if (TShock.Config.AllowIce && actionType != GetDataHandlers.EditAction.PlaceTile) - { - foreach (Point p in player.IceTiles) - { - if (p.X == tileX && p.Y == tileY && (Main.tile[p.X, p.Y].type == 0 || Main.tile[p.X, p.Y].type == 127)) - { - player.IceTiles.Remove(p); - return false; - } - } - - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000) - { - player.SendErrorMessage("You do not have permission to build!"); - player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (TShock.Config.AllowIce && actionType == GetDataHandlers.EditAction.PlaceTile && tileType == 127) - { - player.IceTiles.Add(new Point(tileX, tileY)); - return false; - } - - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000) - { - player.SendErrorMessage("You do not have permission to build!"); - player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (!Regions.CanBuild(tileX, tileY, player)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000) - { - player.SendErrorMessage("This region is protected from changes."); - player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (Config.DisableBuild) - { - if (!player.HasPermission(Permissions.antibuild)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000) - { - player.SendErrorMessage("The world is protected from changes."); - player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - - if (Config.SpawnProtection) - { - if (!player.HasPermission(Permissions.editspawn)) - { - if (Utils.IsInSpawn(tileX, tileY)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 2000) - { - player.SendErrorMessage("Spawn is protected from changes."); - player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - } - return false; - } - - /// CheckTilePermission - Checks to see if a player has the ability to modify a tile at a given position. - /// player - The TSPlayer object. - /// tileX - The x coordinate of the tile. - /// tileY - The y coordinate of the tile. - /// paint - Whether or not the tile is paint. - /// bool - True if the player should not be able to modify the tile. - public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, bool paint = false) - { - if ((!paint && !player.HasPermission(Permissions.canbuild)) || - (paint && !player.HasPermission(Permissions.canpaint))) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000) - { - if (paint) - { - player.SendErrorMessage("You do not have permission to paint!"); - } - else - { - player.SendErrorMessage("You do not have permission to build!"); - } - player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (!Regions.CanBuild(tileX, tileY, player)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000) - { - player.SendErrorMessage("This region is protected from changes."); - player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (Config.DisableBuild) - { - if (!player.HasPermission(Permissions.antibuild)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000) - { - player.SendErrorMessage("The world is protected from changes."); - player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - - if (Config.SpawnProtection) - { - if (!player.HasPermission(Permissions.editspawn)) - { - if (Utils.IsInSpawn(tileX, tileY)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 1000) - { - player.SendErrorMessage("Spawn is protected from changes."); - player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - } - return false; - } - - - /// Distance - Determines the distance between two vectors. /// value1 - The first vector location. /// value2 - The second vector location.