diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index fd6458d29..b30376dec 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -137,6 +137,7 @@ internal Bouncer()
GetDataHandlers.KillMe += OnKillMe;
GetDataHandlers.FishOutNPC += OnFishOutNPC;
GetDataHandlers.FoodPlatterTryPlacing += OnFoodPlatterTryPlacing;
+ GetDataHandlers.DisplayJarTryPlacing += OnDisplayJarTryPlacing;
OTAPI.Hooks.Chest.QuickStack += OnQuickStack;
@@ -649,6 +650,15 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args)
}
}
+ if (tile.type == TileID.DeadCellsDisplayJar)
+ {
+ var displayJar = TEDeadCellsDisplayJar.Find(tileX - tile.frameX % 18 / 18, tileY - tile.frameY % 32 / 18);
+ if (displayJar != -1)
+ {
+ NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, displayJar, 0, 1);
+ }
+ }
+
GetRollbackRectSize(tileX, tileY, out byte width, out byte length, out int offsetY);
args.Player.SendTileRect((short)(tileX - width), (short)(tileY + offsetY), (byte)(width * 2), (byte)(length + 1));
args.Handled = true;
@@ -751,13 +761,15 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args)
// Item frames can be modified without pickaxe tile.
// also add an exception for snake coils, they can be removed when the player places a new one or after x amount of time
// If the tile is part of the breakable when placing set, it might be getting broken by a placement.
- else if (tile.type != TileID.ItemFrame && tile.type != TileID.MysticSnakeRope
- && !ItemID.Sets.Explosives[selectedItem.type]
- && !TileID.Sets.BreakableWhenPlacing[tile.type]
- && !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0
- && selectedItem.pick == 0 && selectedItem.type != ItemID.GravediggerShovel
- && args.Player.TPlayer.mount.Type != MountID.Drill
- && args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart)
+ else if (tile.type != TileID.ItemFrame &&
+ tile.type != TileID.DeadCellsDisplayJar &&
+ tile.type != TileID.MysticSnakeRope &&
+ !ItemID.Sets.Explosives[selectedItem.type] &&
+ !TileID.Sets.BreakableWhenPlacing[tile.type] &&
+ !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 &&
+ selectedItem.pick == 0 && selectedItem.type != ItemID.GravediggerShovel &&
+ args.Player.TPlayer.mount.Type != MountID.Drill &&
+ args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart)
{
if (args.Player.TPlayer.ownedProjectileCounts[ProjectileID.PalworldDigtoise] > 0)
{
@@ -3004,7 +3016,7 @@ internal void OnFoodPlatterTryPlacing(object sender, GetDataHandlers.FoodPlatter
return;
}
- if (!args.Player.IsInRange(args.TileX, args.TileY, range: 13)) // To my knowledge, max legit tile reach with accessories.
+ if (!args.Player.IsInRange(args.TileX, args.TileY))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnFoodPlatterTryPlacing rejected range checks from {0}", args.Player.Name));
args.Player.SendTileSquareCentered(args.TileX, args.TileY, 1);
@@ -3013,6 +3025,58 @@ internal void OnFoodPlatterTryPlacing(object sender, GetDataHandlers.FoodPlatter
}
}
+ ///
+ /// Called when a player is trying to place an item into a display jar.
+ ///
+ ///
+ ///
+ internal void OnDisplayJarTryPlacing(object sender, GetDataHandlers.DisplayJarTryPlacingEventArgs args)
+ {
+ if (!TShock.Utils.TilePlacementValid(args.TileX, args.TileY))
+ {
+ TShock.Log.ConsoleDebug(GetString("Bouncer / OnDisplayJarTryPlacing rejected tile placement valid from {0}", args.Player.Name));
+ args.Handled = true;
+ return;
+ }
+
+ if ((args.Player.SelectedItem.type != args.ItemID && args.Player.ItemInHand.type != args.ItemID))
+ {
+ TShock.Log.ConsoleDebug(GetString("Bouncer / OnDisplayJarTryPlacing rejected item not placed by hand from {0}", args.Player.Name));
+ args.Player.SendTileSquareCentered(args.TileX, args.TileY, 1);
+ args.Handled = true;
+ return;
+ }
+ if (args.Player.IsBeingDisabled())
+ {
+ TShock.Log.ConsoleDebug(GetString("Bouncer / OnDisplayJarTryPlacing rejected disabled from {0}", args.Player.Name));
+ Item item = new Item();
+ item.netDefaults(args.ItemID);
+ args.Player.GiveItemCheck(args.ItemID, item.Name, args.Stack, args.Prefix);
+ args.Player.SendTileSquareCentered(args.TileX, args.TileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(args.TileX, args.TileY))
+ {
+ TShock.Log.ConsoleDebug(GetString("Bouncer / OnDisplayJarTryPlacing rejected permissions from {0}", args.Player.Name));
+ Item item = new Item();
+ item.netDefaults(args.ItemID);
+ args.Player.GiveItemCheck(args.ItemID, item.Name, args.Stack, args.Prefix);
+ args.Player.SendTileSquareCentered(args.TileX, args.TileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(args.TileX, args.TileY))
+ {
+ TShock.Log.ConsoleDebug(GetString("Bouncer / OnDisplayJarTryPlacing rejected range checks from {0}", args.Player.Name));
+ args.Player.SendTileSquareCentered(args.TileX, args.TileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+
///
/// Called when a player is trying to put an item into chest through Quick Stack.
///
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 915489b27..7afcc1024 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -142,7 +142,8 @@ public static void InitGetDataHandler()
{ PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing },
{ PacketTypes.SyncCavernMonsterType, HandleSyncCavernMonsterType },
{ PacketTypes.SyncLoadout, HandleSyncLoadout },
- { PacketTypes.TeamChangeFromUI, HandlePlayerTeam } // Same packet as PlayerTeam
+ { PacketTypes.TeamChangeFromUI, HandlePlayerTeam }, // Same packet as PlayerTeam
+ { PacketTypes.TEDeadCellsDisplayJar, HandleDisplayJar }
};
}
@@ -2498,6 +2499,52 @@ private static bool OnFoodPlatterTryPlacing(TSPlayer player, MemoryStream data,
return args.Handled;
}
+ public class DisplayJarTryPlacingEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The X tile position of the placement action.
+ ///
+ public ushort TileX { get; set; }
+ ///
+ /// The Y tile position of the placement action.
+ ///
+ public ushort TileY { get; set; }
+ ///
+ /// The Item ID that is being placed in the display jar.
+ ///
+ public short ItemID { get; set; }
+ ///
+ /// The prefix of the item that is being placed in the display jar.
+ ///
+ public byte Prefix { get; set; }
+ ///
+ /// The stack of the item that is being placed in the display jar.
+ ///
+ public short Stack { get; set; }
+ }
+ ///
+ /// Called when a player is placing an item in a display jar.
+ ///
+ public static HandlerList DisplayJarTryPlacing = new HandlerList();
+ private static bool OnDisplayJarTryPlacing(TSPlayer player, MemoryStream data, ushort tileX, ushort tileY, short itemID, byte prefix, short stack)
+ {
+ if (DisplayJarTryPlacing == null)
+ return false;
+
+ var args = new DisplayJarTryPlacingEventArgs
+ {
+ Player = player,
+ Data = data,
+ TileX = tileX,
+ TileY = tileY,
+ ItemID = itemID,
+ Prefix = prefix,
+ Stack = stack,
+ };
+ DisplayJarTryPlacing.Invoke(null, args);
+ return args.Handled;
+ }
+
///
/// Used when a net module is loaded
///
@@ -4805,6 +4852,21 @@ Tuple GetDyeSlotsForLoadoutIndex(int index)
return false;
}
+ private static bool HandleDisplayJar(GetDataHandlerArgs args)
+ {
+ ushort tileX = args.Data.ReadUInt16();
+ ushort tileY = args.Data.ReadUInt16();
+ short itemID = args.Data.ReadInt16();
+ byte prefix = args.Data.ReadInt8();
+ short stack = args.Data.ReadInt16();
+
+ if (OnDisplayJarTryPlacing(args.Player, args.Data, tileX, tileY, itemID, prefix, stack))
+ return true;
+
+ return false;
+ }
+
+
public enum DoorAction
{
OpenDoor = 0,
diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs
index a3aa662a6..34c94cf5d 100644
--- a/TShockAPI/Handlers/SendTileRectHandler.cs
+++ b/TShockAPI/Handlers/SendTileRectHandler.cs
@@ -189,6 +189,24 @@ public static TileRectMatch Removal(int width, int height, ushort tileType)
/// , if the rect matches this operation and the changes have been applied, otherwise .
public MatchResult Matches(TSPlayer player, TileRect rect)
{
+ // DeadCellsDisplayJar is a 1x2 rect, but the client sends a 2x2 tile rect; only the first column is relevant.
+ if (TileType == TileID.DeadCellsDisplayJar)
+ {
+ if (rect.Width != 2)
+ {
+ return MatchResult.NotMatched;
+ }
+
+ var newTiles = new NetTile[1, rect.Height];
+
+ for (var y = 0; y < rect.Height; y++)
+ {
+ newTiles[0, y] = rect[0, y];
+ }
+
+ rect = new TileRect(newTiles, rect.X, rect.Y, 1, rect.Height);
+ }
+
if (rect.Width != Width || rect.Height != Height)
{
return MatchResult.NotMatched;
@@ -378,6 +396,7 @@ private MatchResult MatchRemoval(TSPlayer player, TileRect rect)
TileRectMatch.Placement(1, 1, TileID.LogicSensor, 18, 108, 18, 18),
TileRectMatch.Placement(1, 1, TileID.KiteAnchor, 72, 0, 18, 18),
TileRectMatch.Placement(1, 1, TileID.CritterAnchor, 72, 72, 18, 18),
+ TileRectMatch.Placement(1, 2, TileID.DeadCellsDisplayJar, 36, 18, 18, 18),
TileRectMatch.StateChangeY(3, 2, TileID.Campfire, 54, 18),
TileRectMatch.StateChangeY(4, 3, TileID.Cannon, 468, 18),