diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 909a84d68..08d6f98ab 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -130,6 +130,7 @@ internal Bouncer() GetDataHandlers.PlaceObject += OnPlaceObject; GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity; GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame; + GetDataHandlers.WeaponsRackTryPlacing += OnWeaponsRackTryPlacing; GetDataHandlers.PortalTeleport += OnPlayerPortalTeleport; GetDataHandlers.GemLockToggle += OnGemLockToggle; GetDataHandlers.MassWireOperation += OnMassWireOperation; @@ -137,7 +138,10 @@ internal Bouncer() GetDataHandlers.KillMe += OnKillMe; GetDataHandlers.FishOutNPC += OnFishOutNPC; GetDataHandlers.FoodPlatterTryPlacing += OnFoodPlatterTryPlacing; + GetDataHandlers.SyncItemsWithShimmer += OnSyncItemsWithShimmer; + GetDataHandlers.SyncItemCannotBeTakenByEnemies += OnSyncItemCannotBeTakenByEnemies; GetDataHandlers.DisplayJarTryPlacing += OnDisplayJarTryPlacing; + GetDataHandlers.LeashedEntityAnchorPlaceItem += OnLeashedEntityAnchorPlaceItem; OTAPI.Hooks.Chest.QuickStack += OnQuickStack; HookEvents.Terraria.Projectile.Kill_DirtAndFluidProjectiles_RunDelegateMethodPushUpForHalfBricks += OnProjectileDirtFluidKill; HookEvents.Terraria.GameContent.CraftingRequests.CanCraftFromChest += OnChestCraftRequest; @@ -652,6 +656,15 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) } } + if (tile.type == TileID.WeaponsRack2) + { + int weaponRackId = TEWeaponsRack.Find(tileX - tile.frameX % 36 / 18, tileY - tile.frameY % 36 / 18); + if (weaponRackId != -1) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, weaponRackId, 0, 1); + } + } + if (tile.type == TileID.DeadCellsDisplayJar) { var displayJar = TEDeadCellsDisplayJar.Find(tileX - tile.frameX % 18 / 18, tileY - tile.frameY % 32 / 18); @@ -783,6 +796,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) // 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.WeaponsRack2 && tile.type != TileID.DeadCellsDisplayJar && tile.type != TileID.MysticSnakeRope && !ItemID.Sets.Explosives[selectedItem.type] && @@ -2696,6 +2710,48 @@ internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEven } } + /// Fired when an weapon rack is placed for anti-cheat detection. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnWeaponsRackTryPlacing(object sender, GetDataHandlers.WeaponsRackTryPlacingEventArgs args) + { + if (!TShock.Utils.TilePlacementValid(args.X, args.Y)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnWeaponsRackTryPlacing rejected tile placement valid from {0}", args.Player.Name)); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnWeaponsRackTryPlacing rejected disabled from {0}", args.Player.Name)); + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.WeaponRack.ID, 0, 1); + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(args.X, args.Y)) + { + int num = Item.NewItem(null, (args.X * 16) + 8, (args.Y * 16) + 8, args.Player.TPlayer.width, args.Player.TPlayer.height, args.ItemID, args.Stack, noBroadcast: true, args.Prefix, noGrabDelay: true); + Main.item[num].playerIndexTheItemIsReservedFor = args.Player.Index; + NetMessage.SendData((int)PacketTypes.ItemDrop, args.Player.Index, -1, NetworkText.Empty, num, 1f); + NetMessage.SendData((int)PacketTypes.ItemOwner, args.Player.Index, -1, NetworkText.Empty, num); + + TShock.Log.ConsoleDebug(GetString("Bouncer / OnWeaponsRackTryPlacing rejected permissions from {0}", args.Player.Name)); + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.WeaponRack.ID, 0, 1); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(args.X, args.Y)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnWeaponsRackTryPlacing rejected range checks from {0}", args.Player.Name)); + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.WeaponRack.ID, 0, 1); + args.Handled = true; + return; + } + } + internal void OnPlayerPortalTeleport(object sender, GetDataHandlers.TeleportThroughPortalEventArgs args) { //Packet 96 (player teleport through portal) has no validation on whether or not the player id provided @@ -3055,6 +3111,246 @@ internal void OnFoodPlatterTryPlacing(object sender, GetDataHandlers.FoodPlatter } } + /// Registered when shimmer items fall to the ground to prevent cheating. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnSyncItemsWithShimmer(object sender, GetDataHandlers.SyncItemsWithShimmerEventArgs args) + { + short id = args.ID; + Vector2 pos = args.Position; + Vector2 vel = args.Velocity; + short stacks = args.Stacks; + short prefix = args.Prefix; + bool noDelay = args.NoDelay; + short type = args.Type; + + if (!float.IsFinite(pos.X) || !float.IsFinite(pos.Y)) + { + TShock.Log.ConsoleInfo(GetString("Bouncer / OnSyncItemsWithShimmer force kicked (attempted to set position to infinity or NaN) from {0}", args.Player.Name)); + args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); + args.Handled = true; + return; + } + + if (!float.IsFinite(vel.X) || !float.IsFinite(vel.Y)) + { + TShock.Log.ConsoleInfo(GetString("Bouncer / OnSyncItemsWithShimmer force kicked (attempted to set velocity to infinity or NaN) from {0}", args.Player.Name)); + args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); + args.Handled = true; + return; + } + + // player is attempting to crash clients + if (type < -48 || type >= Terraria.ID.ItemID.Count) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from attempt crash from {0}", args.Player.Name)); + args.Handled = true; + return; + } + + // make sure the prefix is a legit value + if (prefix > PrefixID.Count) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from prefix check from {0}", args.Player.Name)); + + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + if (type == 0) + { + if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) + { + // Causes item duplications. Will be re added if necessary + //args.Player.SendData(PacketTypes.ItemDrop, "", id); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from dupe range check from {0}", args.Player.Name)); + args.Handled = true; + return; + } + + args.Handled = false; + return; + } + + if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f), 128)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from range check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + // stop the client from changing the item type of a drop + if (Main.item[id].active && Main.item[id].type != type && + !(Main.item[id].type == ItemID.EmptyBucket && type == ItemID.WaterBucket)) // Empty bucket turns into Water Bucket on rainy days + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from item drop check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + + Item item = new Item(); + item.netDefaults(type); + if ((stacks > item.maxStack || stacks <= 0) || (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems))) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from drop item ban check / max stack check / min stack check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + // TODO: Remove item ban part of this check + if ((Main.ServerSideCharacter) && (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - args.Player.LoginMS < TShock.ServerSideCharacterConfig.Settings.LogonDiscardThreshold)) + { + //Player is probably trying to sneak items onto the server in their hands!!! + TShock.Log.ConsoleInfo(GetString("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from sneaky from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + + } + + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected from disabled from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + if (type == ItemID.GuideVoodooDoll && args.Player.TPlayer.ZoneUnderworldHeight && !args.Player.HasPermission(Permissions.summonboss)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemsWithShimmer rejected Guide Voodoo Doll drop from {0}", args.Player.Name)); + args.Player.SendErrorMessage(GetString("You do not have permission to summon the Wall of Flesh.")); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + } + + /// Registered when item items fall from enemy strike to the ground to prevent cheating. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnSyncItemCannotBeTakenByEnemies(object sender, GetDataHandlers.SyncItemCannotBeTakenByEnemiesEventArgs args) + { + short id = args.ID; + Vector2 pos = args.Position; + Vector2 vel = args.Velocity; + short stacks = args.Stacks; + short prefix = args.Prefix; + bool noDelay = args.NoDelay; + short type = args.Type; + + if (!float.IsFinite(pos.X) || !float.IsFinite(pos.Y)) + { + TShock.Log.ConsoleInfo(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies force kicked (attempted to set position to infinity or NaN) from {0}", args.Player.Name)); + args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); + args.Handled = true; + return; + } + + if (!float.IsFinite(vel.X) || !float.IsFinite(vel.Y)) + { + TShock.Log.ConsoleInfo(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies force kicked (attempted to set velocity to infinity or NaN) from {0}", args.Player.Name)); + args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); + args.Handled = true; + return; + } + + // player is attempting to crash clients + if (type < -48 || type >= Terraria.ID.ItemID.Count) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from attempt crash from {0}", args.Player.Name)); + args.Handled = true; + return; + } + + // make sure the prefix is a legit value + if (prefix > PrefixID.Count) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from prefix check from {0}", args.Player.Name)); + + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + if (type == 0) + { + if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) + { + // Causes item duplications. Will be re added if necessary + //args.Player.SendData(PacketTypes.ItemDrop, "", id); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from dupe range check from {0}", args.Player.Name)); + args.Handled = true; + return; + } + + args.Handled = false; + return; + } + + if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f), 128)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from range check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + // stop the client from changing the item type of a drop + if (Main.item[id].active && Main.item[id].type != type && + !(Main.item[id].type == ItemID.EmptyBucket && type == ItemID.WaterBucket)) // Empty bucket turns into Water Bucket on rainy days + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from item drop check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + + Item item = new Item(); + item.netDefaults(type); + if ((stacks > item.maxStack || stacks <= 0) || (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems))) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from drop item ban check / max stack check / min stack check from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + // TODO: Remove item ban part of this check + if ((Main.ServerSideCharacter) && (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - args.Player.LoginMS < TShock.ServerSideCharacterConfig.Settings.LogonDiscardThreshold)) + { + //Player is probably trying to sneak items onto the server in their hands!!! + TShock.Log.ConsoleInfo(GetString("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from sneaky from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + + } + + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected from disabled from {0}", args.Player.Name)); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + + if (type == ItemID.GuideVoodooDoll && args.Player.TPlayer.ZoneUnderworldHeight && !args.Player.HasPermission(Permissions.summonboss)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnSyncItemCannotBeTakenByEnemies rejected Guide Voodoo Doll drop from {0}", args.Player.Name)); + args.Player.SendErrorMessage(GetString("You do not have permission to summon the Wall of Flesh.")); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); + args.Handled = true; + return; + } + } + /// /// Called when dirt/fluid projectiles (dirt bombs, liquid bombs, liquid rockets) are killed and attempt to place tiles or liquids. /// @@ -3124,6 +3420,72 @@ internal void OnDisplayJarTryPlacing(object sender, GetDataHandlers.DisplayJarTr return; } } + + /// + /// Called when a player is trying to put an leashed entity + /// + /// + /// + internal void OnLeashedEntityAnchorPlaceItem(object sender, GetDataHandlers.LeashedEntityAnchorPlaceItemEventArgs args) + { + int tileX = args.TileX; + int tileY = args.TileY; + + if (!TShock.Utils.TilePlacementValid(tileX, tileY)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from (tile placement valid) {0}", args.Player.Name)); + args.Handled = true; + return; + } + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from disabled from {0}", args.Player.Name)); + args.Handled = true; + return; + } + if (args.Player.Dead && TShock.Config.Settings.PreventDeadModification) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from deadmod from {0}", args.Player.Name)); + args.Handled = true; + return; + } + if (!args.Player.HasBuildPermission(tileX, tileY)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from permissions from {0}", args.Player.Name)); + args.Handled = true; + return; + } + if (!args.Player.IsInRange(tileX, tileY)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from range checks from {0}", args.Player.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 1); + args.Handled = true; + return; + } + if (!args.Player.ItemInHand.IsAir && args.Player.ItemInHand.type != args.EntityID) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from item not placed by hand from {0}", args.Player.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 1); + args.Handled = true; + return; + } + if (!args.Player.SelectedItem.IsAir && args.Player.SelectedItem.type != args.EntityID) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from item not selected from {0}", args.Player.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 1); + args.Handled = true; + return; + } + + //valid item + if (!ItemID.Sets.PlaceTileOnAltUse[args.EntityID]) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnLeashedEntityAnchorPlaceItem rejected from invalid item alt use from {0}", args.Player.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 1); + args.Handled = true; + return; + } + } /// /// Called when a player is trying to put an item into chest through Quick Stack. @@ -3296,6 +3658,7 @@ internal static int GetMaxPlaceStyle(int tileID) TileID.Womannequin, TileID.MinecartTrack, TileID.WeaponsRack, + TileID.WeaponsRack2, TileID.ItemFrame, TileID.LunarMonolith, TileID.TargetDummy, diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index ef4b0c2e9..b444329d2 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors @@ -16,20 +16,21 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +using Microsoft.Xna.Framework; using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.IO.Streams; using System.Linq; -using Microsoft.Xna.Framework; using Terraria; using Terraria.DataStructures; using Terraria.GameContent.Tile_Entities; using Terraria.ID; using Terraria.Localization; -using TShockAPI.Models.PlayerUpdate; using TShockAPI.Configuration; +using TShockAPI.Models.PlayerUpdate; namespace TShockAPI { @@ -136,15 +137,19 @@ public static void InitGetDataHandler() { PacketTypes.Emoji, HandleEmoji }, { PacketTypes.TileEntityDisplayDollItemSync, HandleTileEntityDisplayDollItemSync }, { PacketTypes.RequestTileEntityInteraction, HandleRequestTileEntityInteraction }, + { PacketTypes.WeaponsRackTryPlacing, HandleWeaponsRackTryPlacing }, { PacketTypes.SyncTilePicking, HandleSyncTilePicking }, { PacketTypes.SyncRevengeMarker, HandleSyncRevengeMarker }, { PacketTypes.LandGolfBallInCup, HandleLandGolfBallInCup }, { PacketTypes.FishOutNPC, HandleFishOutNPC }, { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing }, { PacketTypes.SyncCavernMonsterType, HandleSyncCavernMonsterType }, + { PacketTypes.SyncItemsWithShimmer, HandleSyncItemsWithShimmer }, { PacketTypes.SyncLoadout, HandleSyncLoadout }, { PacketTypes.TeamChangeFromUI, HandlePlayerTeam }, // Same packet as PlayerTeam - { PacketTypes.TEDeadCellsDisplayJar, HandleDisplayJar } + { PacketTypes.SyncItemCannotBeTakenByEnemies, HandleSyncItemCannotBeTakenByEnemies }, + { PacketTypes.TEDeadCellsDisplayJar, HandleDisplayJar }, + { PacketTypes.TELeashedEntityAnchorPlaceItem, HandleLeashedEntityAnchorPlaceItem } }; } @@ -2321,6 +2326,50 @@ private static bool OnRequestTileEntityInteraction(TSPlayer player, MemoryStream RequestTileEntityInteraction.Invoke(null, args); return args.Handled; } + /// + /// For use in an OnWeaponsRackTryPlacing event. + /// + public class WeaponsRackTryPlacingEventArgs : GetDataHandledEventArgs + { + /// The X coordinate of the weapon rack. + public short X { get; set; } + + /// The Y coordinate of the weapon rack. + public short Y { get; set; } + + /// The ItemID of the weapon rack. + public short ItemID { get; set; } + + /// The prefix. + public byte Prefix { get; set; } + + /// The stack. + public short Stack { get; set; } + + /// The ItemFrame object associated with this event. + public TEWeaponsRack WeaponRack { get; set; } + } + /// Fired when an WeaponRack is placed. + public static HandlerList WeaponsRackTryPlacing = new HandlerList(); + private static bool OnWeaponsRackTryPlacing(TSPlayer player, MemoryStream data, short x, short y, short itemID, byte prefix, short stack, TEWeaponsRack weaponRack) + { + if (WeaponsRackTryPlacing == null) + return false; + + var args = new WeaponsRackTryPlacingEventArgs + { + Player = player, + Data = data, + X = x, + Y = y, + ItemID = itemID, + Prefix = prefix, + Stack = stack, + WeaponRack = weaponRack + }; + WeaponsRackTryPlacing.Invoke(null, args); + return args.Handled; + } /// /// For use in a SyncTilePicking event. @@ -2499,6 +2548,127 @@ private static bool OnFoodPlatterTryPlacing(TSPlayer player, MemoryStream data, FoodPlatterTryPlacing.Invoke(null, args); return args.Handled; } + + /// + /// For use in an SyncItemsWithShimmer event + /// + public class SyncItemsWithShimmerEventArgs : GetDataHandledEventArgs + { + /// + /// ID of the item. + /// If below 400 and NetID(Type) is 0 Then Set Null. If ItemID is 400 Then New Item + /// + public short ID { get; set; } + /// + /// Position of the item + /// + public Vector2 Position { get; set; } + /// + /// Velocity at which the item is deployed + /// + public Vector2 Velocity { get; set; } + /// + /// Stacks + /// + public short Stacks { get; set; } + /// + /// Prefix of the item + /// + public byte Prefix { get; set; } + /// + /// No Delay on pickup + /// + public bool NoDelay { get; set; } + /// + /// Item type + /// + public short Type { get; set; } + } + + /// + /// SyncItemsWithShimmer - Called when an item is sync on shimmer + /// + public static HandlerList SyncItemsWithShimmer = new HandlerList(); + private static bool OnSyncItemsWithShimmer(TSPlayer player, MemoryStream data, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type) + { + if (SyncItemsWithShimmer == null) + return false; + + var args = new SyncItemsWithShimmerEventArgs + { + Player = player, + Data = data, + ID = id, + Position = pos, + Velocity = vel, + Stacks = stacks, + Prefix = prefix, + NoDelay = noDelay, + Type = type, + }; + SyncItemsWithShimmer.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in an SyncItemCannotBeTakenByEnemies event + /// + public class SyncItemCannotBeTakenByEnemiesEventArgs : GetDataHandledEventArgs + { + /// + /// ID of the item. + /// If below 400 and NetID(Type) is 0 Then Set Null. If ItemID is 400 Then New Item + /// + public short ID { get; set; } + /// + /// Position of the item + /// + public Vector2 Position { get; set; } + /// + /// Velocity at which the item is deployed + /// + public Vector2 Velocity { get; set; } + /// + /// Stacks + /// + public short Stacks { get; set; } + /// + /// Prefix of the item + /// + public byte Prefix { get; set; } + /// + /// No Delay on pickup + /// + public bool NoDelay { get; set; } + /// + /// Item type + /// + public short Type { get; set; } + } + /// + /// SyncItemCannotBeTakenByEnemies - Called when an item is dropped + /// + public static HandlerList SyncItemCannotBeTakenByEnemies = new HandlerList(); + private static bool OnSyncItemCannotBeTakenByEnemies(TSPlayer player, MemoryStream data, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type) + { + if (SyncItemCannotBeTakenByEnemies == null) + return false; + + var args = new SyncItemCannotBeTakenByEnemiesEventArgs + { + Player = player, + Data = data, + ID = id, + Position = pos, + Velocity = vel, + Stacks = stacks, + Prefix = prefix, + NoDelay = noDelay, + Type = type, + }; + SyncItemCannotBeTakenByEnemies.Invoke(null, args); + return args.Handled; + } public class DisplayJarTryPlacingEventArgs : GetDataHandledEventArgs { @@ -2579,7 +2749,43 @@ private static bool OnReadNetModule(TSPlayer player, MemoryStream data, NetModul ReadNetModule.Invoke(null, args); return args.Handled; } + + public class LeashedEntityAnchorPlaceItemEventArgs : 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 Entity ID that is being placed in the display jar. + /// + public short EntityID { get; set; } + } + /// + /// Called when a player is placing an item in a Leashed Entity Anchor. + /// + public static HandlerList LeashedEntityAnchorPlaceItem = new HandlerList(); + private static bool OnLeashedEntityAnchorPlaceItem(TSPlayer player, MemoryStream data, ushort tileX, ushort tileY, short EntityID) + { + if (LeashedEntityAnchorPlaceItem == null) + return false; + var args = new LeashedEntityAnchorPlaceItemEventArgs + { + Player = player, + Data = data, + TileX = tileX, + TileY = tileY, + EntityID = EntityID, + }; + LeashedEntityAnchorPlaceItem.Invoke(null, args); + return args.Handled; + } + #endregion private static bool HandlePlayerInfo(GetDataHandlerArgs args) @@ -4811,6 +5017,23 @@ private static bool HandleRequestTileEntityInteraction(GetDataHandlerArgs args) return false; } + private static bool HandleWeaponsRackTryPlacing(GetDataHandlerArgs args) + { + short x = args.Data.ReadInt16(); + short y = args.Data.ReadInt16(); + short itemID = args.Data.ReadInt16(); + byte prefix = args.Data.ReadInt8(); + short stack = args.Data.ReadInt16(); + TEWeaponsRack WeaponRack = (TEWeaponsRack)TileEntity.ByID[TEWeaponsRack.Find(x, y)]; + + if (OnWeaponsRackTryPlacing(args.Player, args.Data, x, y, itemID, prefix, stack, WeaponRack)) + { + return true; + } + + return false; + } + private static bool HandleSyncTilePicking(GetDataHandlerArgs args) { byte playerIndex = args.Data.ReadInt8(); @@ -4886,6 +5109,22 @@ private static bool HandleSyncCavernMonsterType(GetDataHandlerArgs args) return true; } + private static bool HandleSyncItemsWithShimmer(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var stacks = args.Data.ReadInt16(); + var prefix = args.Data.ReadInt8(); + var noDelay = args.Data.ReadInt8() == 1; + var type = args.Data.ReadInt16(); + + if (OnSyncItemsWithShimmer(args.Player, args.Data, id, pos, vel, stacks, prefix, noDelay, type)) + return true; + + return false; + } + private static bool HandleSyncLoadout(GetDataHandlerArgs args) { var playerIndex = args.Data.ReadInt8(); @@ -4974,6 +5213,22 @@ Tuple GetDyeSlotsForLoadoutIndex(int index) return false; } + private static bool HandleSyncItemCannotBeTakenByEnemies(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var stacks = args.Data.ReadInt16(); + var prefix = args.Data.ReadInt8(); + var noDelay = args.Data.ReadInt8() == 1; + var type = args.Data.ReadInt16(); + + if (OnSyncItemCannotBeTakenByEnemies(args.Player, args.Data, id, pos, vel, stacks, prefix, noDelay, type)) + return true; + + return false; + } + private static bool HandleDisplayJar(GetDataHandlerArgs args) { ushort tileX = args.Data.ReadUInt16(); @@ -4988,6 +5243,20 @@ private static bool HandleDisplayJar(GetDataHandlerArgs args) return false; } + private static bool HandleLeashedEntityAnchorPlaceItem(GetDataHandlerArgs args) + { + var x = args.Data.ReadUInt16(); + var y = args.Data.ReadUInt16(); + var EntityID = args.Data.ReadInt16(); + //var leashedEntityAnchor = (TELeashedEntityAnchor)TileEntity.ByID[TELeashedEntityAnchor.fi(x, y)]; + + if (OnLeashedEntityAnchorPlaceItem(args.Player, args.Data, x, y, EntityID)) + { + return true; + } + + return false; + } public enum DoorAction {