diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94b9e1ba..c79a1ea9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Fixed kick on hardcore death / kick on mediumcore death / ban on either from taking action against journey mode players. (@hakusaro)
* Attempted to fix the problem with the magic mirror spawn problems. You should be able to remove your spawn point in SSC by right clicking on a bed now. (@hakusaro, @AxeelAnder)
* Add HandleFoodPlatterTryPlacing event, which is called whenever a player places a food in a plate. Add antihack to bouncer, to prevent removing food from plates if the region is protected; To prevent placement if they are not in range; To prevent placement if the item is not placed from player hand. (@Patrikkk)
+* Add FishOutNPC event handler, which is called whenever a player fishes out an NPC using a fishing rod. Added antihack to Bouncer, to prevent unathorized and invalid mob spawning, by checking player action, NPC IDs and range. (@Patrikkk, @moisterrific)
## TShock 4.4.0 (Pre-release 8)
* Update for OTAPI 2.0.0.36 and Terraria 1.4.0.4. (@hakusaro, @Patrikkk, @DeathCradle)
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index abd5fe05..b0bc2e9b 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -67,6 +67,7 @@ namespace TShockAPI
GetDataHandlers.MassWireOperation += OnMassWireOperation;
GetDataHandlers.PlayerDamage += OnPlayerDamage;
GetDataHandlers.KillMe += OnKillMe;
+ GetDataHandlers.FishOutNPC += OnFishOutNPC;
GetDataHandlers.FoodPlatterTryPlacing += OnFoodPlatterTryPlacing;
}
@@ -2063,6 +2064,41 @@ namespace TShockAPI
}
}
+ ///
+ /// Called when the player fishes out an NPC.
+ ///
+ ///
+ ///
+ internal void OnFishOutNPC(object sender, GetDataHandlers.FishOutNPCEventArgs args)
+ {
+ Projectile projectile = null;
+ foreach (var recentProjectile in args.Player.RecentlyCreatedProjectiles)
+ {
+ if (Main.projectile[recentProjectile.Index] != null && Main.projectile[recentProjectile.Index].Name == "Bobber")
+ {
+ projectile = Main.projectile[recentProjectile.Index];
+ break;
+ }
+ }
+ if (!FishingRodItemIDs.Contains(args.Player.SelectedItem.type) || projectile == null || !FishableNpcIDs.Contains(args.NpcID))
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnFoodPlatterTryPlacing rejected invalid NPC spawning from {0}", args.Player.Name);
+ args.Handled = true;
+ return;
+ }
+ if (args.NpcID == NPCID.DukeFishron && !args.Player.HasPermission(Permissions.summonboss))
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnFoodPlatterTryPlacing rejected summon boss permissions from {0}", args.Player.Name);
+ args.Handled = true;
+ return;
+ }
+ if (args.Player.IsInRange(args.TileX, args.TileY, 55))
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnFishOutNPC rejected range checks from {0}", args.Player.Name);
+ args.Handled = true;
+ }
+ }
+
///
/// Called when a player is trying to place an item into a food plate.
///
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 74dbff85..9d2b63ef 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -151,6 +151,7 @@ namespace TShockAPI
{ PacketTypes.CrystalInvasionStart, HandleOldOnesArmy },
{ PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 },
{ PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 },
+ { PacketTypes.FishOutNPC, HandleFishOutNPC },
{ PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing }
};
}
@@ -1864,6 +1865,45 @@ namespace TShockAPI
return args.Handled;
}
+ ///
+ /// For use in a FishOutNPC event.
+ ///
+ public class FishOutNPCEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The X world position of the spawning NPC.
+ ///
+ public ushort TileX { get; set; }
+ ///
+ /// The Y world position of the spawning NPC.
+ ///
+ public ushort TileY { get; set; }
+ ///
+ /// The NPC type that is being spawned.
+ ///
+ public short NpcID { get; set; }
+ }
+ ///
+ /// Called when a player fishes out an NPC.
+ ///
+ public static HandlerList FishOutNPC = new HandlerList();
+ private static bool OnFishOutNPC(TSPlayer player, MemoryStream data, ushort tileX, ushort tileY, short npcID)
+ {
+ if (FoodPlatterTryPlacing == null)
+ return false;
+
+ var args = new FishOutNPCEventArgs
+ {
+ Player = player,
+ Data = data,
+ TileX = tileX,
+ TileY = tileY,
+ NpcID = npcID
+ };
+ FishOutNPC.Invoke(null, args);
+ return args.Handled;
+ }
+
public class FoodPlatterTryPlacingEventArgs : GetDataHandledEventArgs
{
///
@@ -3606,6 +3646,19 @@ namespace TShockAPI
return false;
}
+
+ private static bool HandleFishOutNPC(GetDataHandlerArgs args)
+ {
+ ushort tileX = args.Data.ReadUInt16();
+ ushort tileY = args.Data.ReadUInt16();
+ short npcType = args.Data.ReadInt16();
+
+ if (OnFishOutNPC(args.Player, args.Data, tileX, tileY, npcType))
+ return true;
+
+ return false;
+ }
+
private static bool HandleFoodPlatterTryPlacing(GetDataHandlerArgs args)
{
short tileX = args.Data.ReadInt16();
@@ -3679,6 +3732,39 @@ namespace TShockAPI
TileID.Womannequin,
};
+ ///
+ /// List of Fishing rod item IDs.
+ ///
+ internal static readonly List FishingRodItemIDs = new List()
+ {
+ ItemID.WoodFishingPole,
+ ItemID.ReinforcedFishingPole,
+ ItemID.FiberglassFishingPole,
+ ItemID.FisherofSouls,
+ ItemID.GoldenFishingRod,
+ ItemID.MechanicsRod,
+ ItemID.SittingDucksFishingRod,
+ ItemID.Fleshcatcher,
+ ItemID.HotlineFishingHook,
+ ItemID.BloodFishingRod,
+ ItemID.ScarabFishingRod
+ };
+
+ ///
+ /// List of NPC IDs that can be fished out by the player.
+ ///
+ internal static readonly List FishableNpcIDs = new List()
+ {
+ NPCID.EyeballFlyingFish,
+ NPCID.ZombieMerman,
+ NPCID.GoblinShark,
+ NPCID.BloodEelHead,
+ NPCID.BloodEelBody,
+ NPCID.BloodEelTail,
+ NPCID.BloodNautilus,
+ NPCID.DukeFishron
+ };
+
///
/// These projectiles create tiles on death.
///