From 965361c4066203e2e59c9b49a92f7d728864afaf Mon Sep 17 00:00:00 2001 From: Olink Date: Sun, 24 May 2020 03:48:08 -0400 Subject: [PATCH] Add a projectile tracker, so that we can allow fluid bombs. --- TShockAPI/Bouncer.cs | 38 +++++++++++++++++++++++++++++------- TShockAPI/GetDataHandlers.cs | 18 +++++++++++++++++ TShockAPI/TSPlayer.cs | 6 ++++++ TShockAPI/TShock.cs | 13 ++++++++++++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index dd55f6ef..f3a85f40 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -962,6 +962,18 @@ namespace TShockAPI // Denotes that the player has recently set a fuse - used for cheat detection. args.Player.RecentFuse = 10; } + + if (projectileCreatesLiquid.ContainsKey(type)) + { + lock (args.Player.RecentlyCreatedProjectiles) + { + args.Player.RecentlyCreatedProjectiles.Add(new ProjectileStruct() + { + Index = ident, + CreatedAt = DateTime.Now + }); + } + } } /// Handles the NPC Strike event for Bouncer. @@ -1290,6 +1302,18 @@ namespace TShockAPI args.Player.TileLiquidThreshold++; } + bool wasThereABombNearby = false; + + lock (args.Player.RecentlyCreatedProjectiles) + { + var keys = projectileCreatesLiquid.Where(k => k.Value == type).Select(k => k.Key); + var recentBombs = args.Player.RecentlyCreatedProjectiles.Where(keys.Contains(Main.projectile[p.Index].type)); + wasThereABombNearby = recentBombs.Any(r => (args.TileX > (Main.projectile[r.Index].position.X / 16.0f) - 32 + && args.TileX < (Main.projectile[r.Index].position.X / 16.0f) + 32) + && (args.TileY > (Main.projectile[r.Index].position.Y / 16.0f) - 32 + && args.TileY < (Main.projectile[r.Index].position.Y / 16.0f) + 32)); + } + // Liquid anti-cheat // Arguably the banned buckets bit should be in the item bans system if (amount != 0) @@ -1326,7 +1350,7 @@ namespace TShockAPI bucket = 6; } - if (type == LiquidType.Lava && !(bucket == 2 || bucket == 0 || bucket == 5 || bucket == 6)) + if (!wasThereABombNearby && type == LiquidType.Lava && !(bucket == 2 || bucket == 0 || bucket == 5 || bucket == 6)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 1 from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1336,7 +1360,7 @@ namespace TShockAPI return; } - if (type == LiquidType.Lava && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) + if (!wasThereABombNearby && type == LiquidType.Lava && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected lava bucket from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1346,7 +1370,7 @@ namespace TShockAPI return; } - if (type == LiquidType.Water && !(bucket == 1 || bucket == 0 || bucket == 4)) + if (!wasThereABombNearby && type == LiquidType.Water && !(bucket == 1 || bucket == 0 || bucket == 4)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 2 from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1356,7 +1380,7 @@ namespace TShockAPI return; } - if (type == LiquidType.Water && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) + if (!wasThereABombNearby && type == LiquidType.Water && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 3 from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1366,7 +1390,7 @@ namespace TShockAPI return; } - if (type == LiquidType.Honey && !(bucket == 3 || bucket == 0)) + if (!wasThereABombNearby && type == LiquidType.Honey && !(bucket == 3 || bucket == 0)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 4 from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1376,7 +1400,7 @@ namespace TShockAPI return; } - if (type == LiquidType.Honey && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player)) + if (!wasThereABombNearby && type == LiquidType.Honey && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 5 from {0}", args.Player.Name); args.Player.SendErrorMessage("You do not have permission to perform this action."); @@ -1395,7 +1419,7 @@ namespace TShockAPI return; } - if (!args.Player.IsInRange(tileX, tileY, 16)) + if (!wasThereABombNearby && !args.Player.IsInRange(tileX, tileY, 16)) { TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected range checks from {0}", args.Player.Name); args.Player.SendTileSquare(tileX, tileY, 1); diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 19c6f4b0..57c8985f 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -2386,6 +2386,10 @@ namespace TShockAPI } args.Player.LastKilledProjectile = type; + lock (args.Player.RecentlyCreatedProjectiles) + { + args.Player.RecentlyCreatedProjectiles.ForEach(s => { if (s.Index == index) { s.Killed = true; } }); + } return false; } @@ -3446,6 +3450,13 @@ namespace TShockAPI { ProjectileID.MysticSnakeCoil, TileID.MysticSnakeRope } }; + internal static Dictionary projectileCreatesLiquid = new Dictionary + { + {ProjectileID.LavaBomb, LiquidType.Lava}, + {ProjectileID.WetBomb, LiquidType.Water}, + {ProjectileID.HoneyBomb, LiquidType.Honey} + }; + internal static Dictionary ropeCoilPlacements = new Dictionary { {ItemID.RopeCoil, TileID.Rope}, @@ -3461,5 +3472,12 @@ namespace TShockAPI { {TileID.MinecartTrack, 3} }; + + internal struct ProjectileStruct + { + public int Index { get; set; } + public DateTime CreatedAt { get; set; } + public bool Killed { get; internal set; } + } } } diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 86642e69..277e9b00 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -764,6 +764,12 @@ namespace TShockAPI /// public int LastKilledProjectile = 0; + /// + /// Keeps track of recently created projectiles by this player. TShock.cs OnSecondUpdate() removes from this in an async task. + /// Projectiles older than 5 seconds are purged from this collection as they are no longer "recent". + /// + internal List RecentlyCreatedProjectiles = new List(); + /// /// The current region this player is in, or null if none. /// diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index d0b174b4..656a1314 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -44,6 +44,7 @@ using Microsoft.Xna.Framework; using TShockAPI.Sockets; using TShockAPI.CLI; using TShockAPI.Localization; +using System.Threading.Tasks; namespace TShockAPI { @@ -1064,6 +1065,18 @@ namespace TShockAPI } } } + + Task.Run(() => + { + if (player != null && player.TPlayer.whoAmI >= 0) + { + var threshold = DateTime.Now.AddSeconds(-5); + lock (player.RecentlyCreatedProjectiles) + { + player.RecentlyCreatedProjectiles = player.RecentlyCreatedProjectiles.Where(s => s.CreatedAt > threshold).ToList(); + } + } + }); } Utils.SetConsoleTitle(false); }