diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7e71388..c00e802a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,12 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Allowed the Axe of Regrowth and the Rubblemaker to pass Bouncer checks. (@drunderscore)
* The Axe of Regrowth places a `Saplings` where a tree used to be, which previously failed.
* The Rubblemaker places rubble (which are echo piles), of varying styles, which previously failed.
+* Fixed `HandlePlayerAddBuff` data handler always being marked as `Handled`, and therefore never allowing the `PlayerAddBuff` to be sent to anyone. (@drunderscore)
+* Improved `OnPlayerBuff` logic to properly handle players adding buffs to other players. (@drunderscore)
+ * Check if the target ID is within bounds as the first thing to check.
+ * Check if the buff type being applied is within bounds.
+ * Introduce `AddPlayerBuffWhitelist` (replacing `WhitelistBuffMaxTime`), which allows us to specify the maximum amount of ticks a buff can be applied for, and if it can be applied without the target being in PvP.
+ * When rejecting from `OnPlayerBuff`, instead of sending a `PlayerAddBuff` packet with the rejected buff (essentially a no-op, as the sender implicitly applies the buff to the target, and causes desync as the buff was rejected), send a `PlayerBuff` to re-sync the target's buffs, without the buff we just rejected.
## TShock 4.5.18
* Fixed `TSPlayer.GiveItem` not working if the player is in lava. (@PotatoCider)
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index c874e37e..8e028c95 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -44,6 +44,27 @@ namespace TShockAPI
internal Handlers.LandGolfBallInCupHandler LandGolfBallInCupHandler { get; private set; }
internal Handlers.SyncTilePickingHandler SyncTilePickingHandler { get; private set; }
+ ///
+ /// A class that represents the limits for a particular buff when a client applies it with PlayerAddBuff.
+ ///
+ internal class BuffLimit
+ {
+ ///
+ /// How many ticks at the maximum a player can apply this to another player for.
+ ///
+ public int MaxTicks { get; set; }
+ ///
+ /// Can this buff be added without the receiver being hostile (PvP)
+ ///
+ public bool CanBeAddedWithoutHostile { get; set; }
+ ///
+ /// Can this buff only be applied to the sender?
+ ///
+ public bool CanOnlyBeAppliedToSender { get; set; }
+ }
+
+ internal static BuffLimit[] PlayerAddBuffWhitelist;
+
///
/// Represents a place style corrector.
///
@@ -231,6 +252,172 @@ namespace TShockAPI
return actualItemPlaceStyle;
}
});
+
+ #region PlayerAddBuff Whitelist
+
+ PlayerAddBuffWhitelist = new BuffLimit[Main.maxBuffTypes];
+ PlayerAddBuffWhitelist[BuffID.Poisoned] = new BuffLimit
+ {
+ MaxTicks = 60 * 60
+ };
+ PlayerAddBuffWhitelist[BuffID.OnFire] = new BuffLimit
+ {
+ MaxTicks = 60 * 20
+ };
+ PlayerAddBuffWhitelist[BuffID.Confused] = new BuffLimit
+ {
+ MaxTicks = 60 * 4
+ };
+ PlayerAddBuffWhitelist[BuffID.CursedInferno] = new BuffLimit
+ {
+ MaxTicks = 60 * 7
+ };
+ PlayerAddBuffWhitelist[BuffID.Wet] = new BuffLimit
+ {
+ MaxTicks = 60 * 30,
+ // The Water Gun can be shot at other players and inflict Wet while not in PvP
+ CanBeAddedWithoutHostile = true
+ };
+ PlayerAddBuffWhitelist[BuffID.Ichor] = new BuffLimit
+ {
+ MaxTicks = 60 * 20
+ };
+ PlayerAddBuffWhitelist[BuffID.Venom] = new BuffLimit
+ {
+ MaxTicks = 60 * 30
+ };
+ PlayerAddBuffWhitelist[BuffID.GelBalloonBuff] = new BuffLimit
+ {
+ MaxTicks = 60 * 30,
+ // The Sparkle Slime Balloon inflicts this while not in PvP
+ CanBeAddedWithoutHostile = true
+ };
+ PlayerAddBuffWhitelist[BuffID.Frostburn] = new BuffLimit
+ {
+ MaxTicks = 60 * 8
+ };
+ PlayerAddBuffWhitelist[BuffID.Campfire] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.Sunflower] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.WaterCandle] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleEndurance1] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleEndurance2] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleEndurance3] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleMight1] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleMight2] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.BeetleMight3] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true,
+ };
+ PlayerAddBuffWhitelist[BuffID.SolarShield1] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = false,
+ };
+ PlayerAddBuffWhitelist[BuffID.SolarShield2] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = false,
+ };
+ PlayerAddBuffWhitelist[BuffID.SolarShield3] = new BuffLimit
+ {
+ MaxTicks = 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = false,
+ };
+ PlayerAddBuffWhitelist[BuffID.MonsterBanner] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.HeartLamp] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.PeaceCandle] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.StarInBottle] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.CatBast] = new BuffLimit
+ {
+ MaxTicks = 2,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.OnFire3] = new BuffLimit
+ {
+ MaxTicks = 60 * 5,
+ CanBeAddedWithoutHostile = false,
+ CanOnlyBeAppliedToSender = false
+ };
+ PlayerAddBuffWhitelist[BuffID.HeartyMeal] = new BuffLimit
+ {
+ MaxTicks = 60 * 7,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
+ PlayerAddBuffWhitelist[BuffID.Frostburn2] = new BuffLimit
+ {
+ MaxTicks = 60 * 7,
+ CanBeAddedWithoutHostile = false,
+ CanOnlyBeAppliedToSender = false
+ };
+
+ #endregion Whitelist
}
internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs args)
@@ -1675,9 +1862,24 @@ namespace TShockAPI
int type = args.Type;
int time = args.Time;
+ if (id >= Main.maxPlayers)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected player cap from {0}", args.Player.Name);
+ args.Handled = true;
+ return;
+ }
+
if (TShock.Players[id] == null)
{
- TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected null check");
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected null check from {0}", args.Player.Name);
+ args.Handled = true;
+ return;
+ }
+
+ if (type >= Main.maxBuffTypes)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected invalid buff type {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
return;
}
@@ -1685,31 +1887,7 @@ namespace TShockAPI
if (args.Player.IsBeingDisabled())
{
TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected disabled from {0}", args.Player.Name);
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
- args.Handled = true;
- return;
- }
-
- if (id >= Main.maxPlayers)
- {
- TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected player cap from {0}", args.Player.Name);
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
- args.Handled = true;
- return;
- }
-
- if (!TShock.Players[id].TPlayer.hostile || !Main.pvpBuff[type])
- {
- TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected hostile/pvp from {0}", args.Player.Name);
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
- args.Handled = true;
- return;
- }
-
- if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 50))
- {
- TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected range check from {0}", args.Player.Name);
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
return;
}
@@ -1717,15 +1895,51 @@ namespace TShockAPI
if (args.Player.IsBouncerThrottled())
{
TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected throttled from {0}", args.Player.Name);
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
return;
}
- if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type])
+ var targetPlayer = TShock.Players[id];
+ var buffLimit = PlayerAddBuffWhitelist[type];
+
+ if (!args.Player.IsInRange(targetPlayer.TileX, targetPlayer.TileY, 50))
{
- TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected buff time whitelists from {0}", args.Player.Name);
- args.Handled = false;
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected range check from {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (buffLimit == null)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected non-whitelisted buff {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (buffLimit.CanOnlyBeAppliedToSender && id != args.Player.Index)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected applied to non-sender from {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (!buffLimit.CanBeAddedWithoutHostile && !targetPlayer.TPlayer.hostile)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected hostile/pvp from {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (time <= 0 || time > buffLimit.MaxTicks)
+ {
+ TShock.Log.ConsoleDebug("Bouncer / OnPlayerBuff rejected time too long from {0}", args.Player.Name);
+ args.Player.SendData(PacketTypes.PlayerBuff, "", id);
+ args.Handled = true;
return;
}
}
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 5e1369e0..0929d928 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -79,20 +79,8 @@ namespace TShockAPI
{
private static Dictionary GetDataHandlerDelegates;
- public static int[] WhitelistBuffMaxTime;
-
public static void InitGetDataHandler()
{
- #region Blacklists
-
- WhitelistBuffMaxTime = new int[Main.maxBuffTypes];
- WhitelistBuffMaxTime[20] = 600;
- WhitelistBuffMaxTime[0x18] = 1200;
- WhitelistBuffMaxTime[0x1f] = 120;
- WhitelistBuffMaxTime[0x27] = 420;
-
- #endregion Blacklists
-
GetDataHandlerDelegates = new Dictionary
{
{ PacketTypes.PlayerInfo, HandlePlayerInfo },
@@ -3467,8 +3455,7 @@ namespace TShockAPI
if (OnPlayerBuff(args.Player, args.Data, id, type, time))
return true;
- args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
- return true;
+ return false;
}
private static bool HandleUpdateNPCHome(GetDataHandlerArgs args)