diff --git a/CHANGELOG.md b/CHANGELOG.md index a59a5159..7a888f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Fixed /wind command. (@AxeelAnder) * Fixed NPC buff bouncer. (@AxeelAnder) * Fixed players are unable to remove an NPC. Change `byte NPCHomeChangeEventArgs.Homeless` to `HouseholdStatus NPCHomeChangeEventArgs.HouseholdStatus`. (@AxeelAnder) +* Fixed lava, wet, honey, and dry bombs; + and lava, wet, honey, and dry grenades; + and lava, wet, honey, and dry rockets; + and lava, wet, honey, and dry mines. (@Olink) +* Fix Bloody Tear displaying the wrong text when used. (@Olink) + ## TShock 4.4.0 (Pre-release 7 (Entangled)) * Fixed bed spawn issues when trying to remove spawn point in SSC. (@Olink) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 28406095..ad6e516a 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -19,18 +19,17 @@ using System; using System.Collections.Generic; using System.Linq; using Terraria.ID; -using TShockAPI.DB; using TShockAPI.Net; using Terraria; using Microsoft.Xna.Framework; using OTAPI.Tile; using TShockAPI.Localization; using static TShockAPI.GetDataHandlers; -using TerrariaApi.Server; using Terraria.ObjectData; using Terraria.DataStructures; using Terraria.Localization; using TShockAPI.Models.PlayerUpdate; +using System.Threading.Tasks; namespace TShockAPI { @@ -1290,6 +1289,24 @@ namespace TShockAPI args.Player.TileLiquidThreshold++; } + bool wasThereABombNearby = false; + lock (args.Player.RecentlyCreatedProjectiles) + { + IEnumerable projectileTypesThatPerformThisOperation; + if (amount > 0) //handle the projectiles that create fluid. + { + projectileTypesThatPerformThisOperation = projectileCreatesLiquid.Where(k => k.Value == type).Select(k => k.Key); + } + else //handle the scenario where we are removing liquid + { + projectileTypesThatPerformThisOperation = projectileCreatesLiquid.Where(k => k.Value == LiquidType.Removal).Select(k => k.Key); + } + + var recentBombs = args.Player.RecentlyCreatedProjectiles.Where(p => projectileTypesThatPerformThisOperation.Contains(Main.projectile[p.Index].type)); + wasThereABombNearby = recentBombs.Any(r => Math.Abs(args.TileX - (Main.projectile[r.Index].position.X / 16.0f)) < TShock.Config.BombExplosionRadius + && Math.Abs(args.TileY - (Main.projectile[r.Index].position.Y / 16.0f)) < TShock.Config.BombExplosionRadius); + } + // Liquid anti-cheat // Arguably the banned buckets bit should be in the item bans system if (amount != 0) @@ -1326,7 +1343,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 +1353,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 +1363,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 +1373,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 +1383,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 +1393,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 +1412,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); @@ -2045,6 +2062,24 @@ namespace TShockAPI } } + internal void OnSecondUpdate() + { + Task.Run(() => + { + foreach (var player in TShock.Players) + { + 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(); + } + } + } + }); + } + // These time values are references from Projectile.cs, at npc.AddBuff() calls. private static Dictionary NPCAddBuffTimeMax = new Dictionary() { diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index bda097c3..78184977 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -527,6 +527,9 @@ namespace TShockAPI [Description("Whether or not the server should output debug level messages related to system operation.")] public bool DebugLogs = false; + [Description("Determines the range in tiles that a bomb can affect tiles from detonation point.")] + public int BombExplosionRadius = 5; + /// /// Reads a configuration file from a given path /// diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 8c5a314b..8076d30b 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1141,7 +1141,8 @@ namespace TShockAPI { Water = 0, Lava = 1, - Honey = 2 + Honey = 2, + Removal = 255 //@Olink: lets hope they never invent 255 fluids or decide to also use this :( } /// @@ -2329,6 +2330,17 @@ namespace TShockAPI if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player)) return true; + lock (args.Player.RecentlyCreatedProjectiles) + { + if (!args.Player.RecentlyCreatedProjectiles.Any(p => p.Index == index)) + { + args.Player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct() + { + Index = index, + CreatedAt = DateTime.Now + }); + } + } return false; } @@ -2393,6 +2405,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; } @@ -2834,38 +2850,44 @@ namespace TShockAPI string thing; switch (thingType) { + case -11: + thing = "applied advanced combat techniques"; + break; + case -10: + thing = "summoned a Blood Moon"; + break; case -8: - thing = "a Moon Lord"; + thing = "summoned a Moon Lord"; break; case -7: - thing = "a Martian invasion"; + thing = "summoned a Martian invasion"; break; case -6: - thing = "an eclipse"; + thing = "summoned an eclipse"; break; case -5: - thing = "a frost moon"; + thing = "summoned a frost moon"; break; case -4: - thing = "a pumpkin moon"; + thing = "summoned a pumpkin moon"; break; case -3: - thing = "the Pirates"; + thing = "summoned the Pirates"; break; case -2: - thing = "the Snow Legion"; + thing = "summoned the Snow Legion"; break; case -1: - thing = "a Goblin Invasion"; + thing = "summoned a Goblin Invasion"; break; default: - thing = String.Format("the {0}", npc.FullName); + thing = String.Format("summoned the {0}", npc.FullName); break; } if (TShock.Config.AnonymousBossInvasions) - TShock.Utils.SendLogs(string.Format("{0} summoned {1}!", args.Player.Name, thing), Color.PaleVioletRed, args.Player); + TShock.Utils.SendLogs(string.Format("{0} {1}!", args.Player.Name, thing), Color.PaleVioletRed, args.Player); else - TShock.Utils.Broadcast(String.Format("{0} summoned {1}!", args.Player.Name, thing), 175, 75, 255); + TShock.Utils.Broadcast(String.Format("{0} {1}!", args.Player.Name, thing), 175, 75, 255); return false; } @@ -3453,6 +3475,30 @@ namespace TShockAPI { ProjectileID.MysticSnakeCoil, TileID.MysticSnakeRope } }; + internal static Dictionary projectileCreatesLiquid = new Dictionary + { + {ProjectileID.LavaBomb, LiquidType.Lava}, + {ProjectileID.LavaRocket, LiquidType.Lava }, + {ProjectileID.LavaGrenade, LiquidType.Lava }, + {ProjectileID.LavaMine, LiquidType.Lava }, + //{ProjectileID.LavaSnowmanRocket, LiquidType.Lava }, //these require additional checks. + {ProjectileID.WetBomb, LiquidType.Water}, + {ProjectileID.WetRocket, LiquidType.Water }, + {ProjectileID.WetGrenade, LiquidType.Water}, + {ProjectileID.WetMine, LiquidType.Water}, + //{ProjectileID.WetSnowmanRocket, LiquidType.Water}, //these require additional checks. + {ProjectileID.HoneyBomb, LiquidType.Honey}, + {ProjectileID.HoneyRocket, LiquidType.Honey }, + {ProjectileID.HoneyGrenade, LiquidType.Honey }, + {ProjectileID.HoneyMine, LiquidType.Honey }, + //{ProjectileID.HoneySnowmanRocket, LiquidType.Honey }, //these require additional checks. + {ProjectileID.DryBomb, LiquidType.Removal }, + {ProjectileID.DryRocket, LiquidType.Removal }, + {ProjectileID.DryGrenade, LiquidType.Removal }, + {ProjectileID.DryMine, LiquidType.Removal }, + //{ProjectileID.DrySnowmanRocket, LiquidType.Removal } //these require additional checks. + }; + internal static Dictionary ropeCoilPlacements = new Dictionary { {ItemID.RopeCoil, TileID.Rope}, @@ -3468,5 +3514,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..3d7deaae 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..6e0ed31c 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1065,6 +1065,8 @@ namespace TShockAPI } } } + + Bouncer.OnSecondUpdate(); Utils.SetConsoleTitle(false); } @@ -1620,6 +1622,32 @@ namespace TShockAPI e.Handled = true; return; } + } else if (e.MsgId == PacketTypes.ProjectileNew) + { + if (e.number >= 0 && e.number < Main.projectile.Length) + { + var projectile = Main.projectile[e.number]; + if (projectile.active && projectile.owner >= 0 && GetDataHandlers.projectileCreatesLiquid.ContainsKey(projectile.type)) + { + var player = Players[projectile.owner]; + if (player != null) + { + if (player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number && p.Killed)) + { + player.RecentlyCreatedProjectiles.RemoveAll(p => p.Index == e.number && p.Killed); + } + + if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number)) { + player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct() + { + Index = e.number, + CreatedAt = DateTime.Now + }); + } + } + } + } + } }