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),