diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index f31f9dcc..087e092a 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -36,64 +36,39 @@ namespace TShockAPI /// Bouncer is the TShock anti-hack and anti-cheat system. internal sealed class Bouncer { - static Dictionary NPCAddBuffTimeMax = new Dictionary() - { - { BuffID.Poisoned, 3600 }, - { BuffID.OnFire, 1200 }, - { BuffID.CursedInferno, 420 }, - { BuffID.Frostburn, 900 }, - { BuffID.Ichor, 1200 }, - { BuffID.Venom, 1260 }, - { BuffID.Midas, 120 }, - { BuffID.Wet, 1500 }, - { BuffID.Slimed, 1500 }, - { BuffID.Lovestruck, 1800 }, - { BuffID.Stinky, 1800 }, - { BuffID.SoulDrain, 30 }, - { BuffID.ShadowFlame, 660 }, - { BuffID.DryadsWard, 120 }, - { BuffID.BoneJavelin, 900 }, - { BuffID.StardustMinionBleed, 900 }, - { BuffID.DryadsWardDebuff, 120 }, - { BuffID.Daybreak, 300 }, - { BuffID.BetsysCurse, 600 }, - { BuffID.Oiled, 540 } - }; - - /// Constructor call initializes Bouncer and related functionality. /// A new Bouncer. internal Bouncer() { // Setup hooks - GetDataHandlers.GetSection += OnGetSection; - GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame; - GetDataHandlers.GemLockToggle += OnGemLockToggle; - GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity; - GetDataHandlers.PlayerAnimation += OnPlayerAnimation; - GetDataHandlers.NPCStrike += OnNPCStrike; + GetDataHandlers.PlayerUpdate += OnPlayerUpdate; + GetDataHandlers.TileEdit += OnTileEdit; + GetDataHandlers.SendTileSquare += OnSendTileSquare; GetDataHandlers.ItemDrop += OnItemDrop; - GetDataHandlers.NPCAddBuff += OnNPCAddBuff; - GetDataHandlers.PlayerBuff += OnPlayerBuff; + GetDataHandlers.NewProjectile += OnNewProjectile; + GetDataHandlers.NPCStrike += OnNPCStrike; + GetDataHandlers.ProjectileKill += OnProjectileKill; GetDataHandlers.ChestItemChange += OnChestItemChange; - GetDataHandlers.NPCHome += OnUpdateNPCHome; GetDataHandlers.ChestOpen += OnChestOpen; GetDataHandlers.PlaceChest += OnPlaceChest; - GetDataHandlers.LiquidSet += OnLiquidSet; - GetDataHandlers.ProjectileKill += OnProjectileKill; - GetDataHandlers.PlayerUpdate += OnPlayerUpdate; GetDataHandlers.PlayerZone += OnPlayerZone; - GetDataHandlers.KillMe += OnKillMe; - GetDataHandlers.NewProjectile += OnNewProjectile; - GetDataHandlers.PlaceObject += OnPlaceObject; - GetDataHandlers.SendTileSquare += OnSendTileSquare; + GetDataHandlers.PlayerAnimation += OnPlayerAnimation; + GetDataHandlers.LiquidSet += OnLiquidSet; + GetDataHandlers.PlayerBuff += OnPlayerBuff; + GetDataHandlers.NPCAddBuff += OnNPCAddBuff; + GetDataHandlers.NPCHome += OnUpdateNPCHome; GetDataHandlers.HealOtherPlayer += OnHealOtherPlayer; - GetDataHandlers.TileEdit += OnTileEdit; - GetDataHandlers.MassWireOperation += OnMassWireOperation; + GetDataHandlers.PlaceObject += OnPlaceObject; + GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity; + GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame; GetDataHandlers.PortalTeleport += OnPlayerPortalTeleport; + GetDataHandlers.GemLockToggle += OnGemLockToggle; + GetDataHandlers.MassWireOperation += OnMassWireOperation; + GetDataHandlers.PlayerDamage += OnPlayerDamage; + GetDataHandlers.KillMe += OnKillMe; } - + internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs args) { if (args.Player.RequestedSection) @@ -116,804 +91,6 @@ namespace TShockAPI } } - /// Fired when an item frame is placed for anti-cheat detection. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEventArgs args) - { - if (args.Player.IsBeingDisabled()) - { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(args.X, args.Y)) - { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(args.X, args.Y)) - { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); - args.Handled = true; - return; - } - } - - /// Handles the anti-cheat components of gem lock toggles. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnGemLockToggle(object sender, GetDataHandlers.GemLockToggleEventArgs args) - { - if (args.X < 0 || args.Y < 0 || args.X >= Main.maxTilesX || args.Y >= Main.maxTilesY) - { - args.Handled = true; - return; - } - - if (!TShock.Utils.TilePlacementValid(args.X, args.Y) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(args.X, args.Y)) - { - args.Handled = true; - return; - } - } - - /// Fired when a PlaceTileEntity occurs for basic anti-cheat on perms and range. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlaceTileEntity(object sender, GetDataHandlers.PlaceTileEntityEventArgs args) - { - if (args.Player.IsBeingDisabled()) - { - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(args.X, args.Y)) - { - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(args.X, args.Y)) - { - args.Handled = true; - return; - } - } - - /// Handles validation of of basic anti-cheat on mass wire operations. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnMassWireOperation(object sender, GetDataHandlers.MassWireOperationEventArgs args) - { - short startX = args.StartX; - short startY = args.StartY; - short endX = args.EndX; - short endY = args.EndY; - - List points = Utils.Instance.GetMassWireOperationRange( - new Point(startX, startY), - new Point(endX, endY), - args.Player.TPlayer.direction == 1); - - int x; - int y; - foreach (Point p in points) - { - /* Perform similar checks to TileKill - * The server-side nature of this packet removes the need to use SendTileSquare - * Range checks are currently ignored here as the items that send this seem to have infinite range */ - - x = p.X; - y = p.Y; - - if (!TShock.Utils.TilePlacementValid(x, y) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(x, y)) - { - args.Handled = true; - return; - } - } - } - - /// Handles basic animation throttling for disabled players. - /// sender - /// args - internal void OnPlayerAnimation(object sender, GetDataHandlers.PlayerAnimationEventArgs args) - { - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - args.Handled = true; - return; - } - } - - /// Handles the NPC Strike event for Bouncer. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnNPCStrike(object sender, GetDataHandlers.NPCStrikeEventArgs args) - { - short id = args.ID; - byte direction = args.Direction; - short damage = args.Damage; - float knockback = args.Knockback; - byte crit = args.Critical; - - if (Main.npc[id] == null) - { - args.Handled = true; - return; - } - - if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap)) - { - if (TShock.Config.KickOnDamageThresholdBroken) - { - args.Player.Kick(string.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage)); - args.Handled = true; - return; - } - else - { - args.Player.Disable(String.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole); - } - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - args.Handled = true; - return; - } - - if (TShock.Config.RangeChecks && - !args.Player.IsInRange((int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128)) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - args.Handled = true; - return; - } - } - - /// Called when a player is damaged. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlayerDamage(object sender, GetDataHandlers.PlayerDamageEventArgs args) - { - byte id = args.ID; - short damage = args.Damage; - bool pvp = args.PVP; - bool crit = args.Critical; - byte direction = args.Direction; - - if (id >= Main.maxPlayers || TShock.Players[id] == null) - { - args.Handled = true; - return; - } - - if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap) && id != args.Player.Index) - { - if (TShock.Config.KickOnDamageThresholdBroken) - { - args.Player.Kick(string.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage)); - args.Handled = true; - return; - } - else - { - args.Player.Disable(String.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole); - } - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - args.Handled = true; - return; - } - - if (!TShock.Players[id].TPlayer.hostile && pvp && id != args.Player.Index) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - args.Handled = true; - return; - } - - } - - /// Registered when items fall to the ground to prevent cheating. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs 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; - - // player is attempting to crash clients - if (type < -48 || type >= Main.maxItemTypes) - { - // Causes item duplications. Will be re added later if necessary - //args.Player.SendData(PacketTypes.ItemDrop, "", id); - args.Handled = true; - return; - } - - // make sure the prefix is a legit value - // Note: Not checking if prefix is less than 1 because if it is, this check - // will break item pickups on the client. - if (prefix > PrefixID.Count) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - args.Handled = true; - return; - } - - //Item removed, let client do this to prevent item duplication - // client side (but only if it passed the range check) (i.e., return false) - 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); - args.Handled = true; - return; - } - - args.Handled = false; - return; - } - - if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f))) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - args.Handled = true; - return; - } - - // stop the client from changing the item type of a drop but - // only if the client isn't picking up the item - if (Main.item[id].active && Main.item[id].netID != type) - { - 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.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems))) - { - args.Player.SendData(PacketTypes.ItemDrop, "", 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.LogonDiscardThreshold)) - { - //Player is probably trying to sneak items onto the server in their hands!!! - TShock.Log.ConsoleInfo("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name); - args.Player.SendData(PacketTypes.ItemDrop, "", id); - args.Handled = true; - return; - - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - args.Handled = true; - return; - } - } - - /// Handles NPCAddBuff events. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnNPCAddBuff(object sender, GetDataHandlers.NPCAddBuffEventArgs args) - { - short id = args.ID; - byte type = args.Type; - short time = args.Time; - - if (id >= Main.npc.Length) - { - args.Handled = true; - return; - } - - NPC npc = Main.npc[id]; - - if (npc == null) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Handled = true; - return; - } - - bool detectedNPCBuffTimeCheat = false; - - if (NPCAddBuffTimeMax.ContainsKey(type)) - { - if (time > NPCAddBuffTimeMax[type]) - { - detectedNPCBuffTimeCheat = true; - } - - if (npc.townNPC && npc.netID != NPCID.Guide && npc.netID != NPCID.Clothier) - { - if (type != BuffID.Lovestruck && type != BuffID.Stinky && type != BuffID.DryadsWard && - type != BuffID.Wet && type != BuffID.Slimed) - { - detectedNPCBuffTimeCheat = true; - } - } - } - else - { - detectedNPCBuffTimeCheat = true; - } - - if (detectedNPCBuffTimeCheat) - { - args.Player.Kick("Added buff to NPC abnormally.", true); - args.Handled = true; - } - } - - /// Handles Buff events. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlayerBuff(object sender, GetDataHandlers.PlayerBuffEventArgs args) - { - byte id = args.ID; - byte type = args.Type; - int time = args.Time; - - if (TShock.Players[id] == null) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - args.Handled = true; - return; - } - - if (id >= Main.maxPlayers) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - args.Handled = true; - return; - } - - if (!TShock.Players[id].TPlayer.hostile || !Main.pvpBuff[type]) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - args.Handled = true; - return; - } - - if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type]) - { - args.Handled = false; - return; - } - } - - /// Handles when a chest item is changed. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnChestItemChange(object sender, GetDataHandlers.ChestItemEventArgs args) - { - short id = args.ID; - byte slot = args.Slot; - short stacks = args.Stacks; - byte prefix = args.Prefix; - short type = args.Type; - - if (args.Player.TPlayer.chest != id) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendData(PacketTypes.ChestItem, "", id, slot); - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) - { - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(Main.chest[id].x, Main.chest[id].y)) - { - args.Handled = true; - return; - } - } - - /// The Bouncer handler for when an NPC is rehomed. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnUpdateNPCHome(object sender, GetDataHandlers.NPCHomeChangeEventArgs args) - { - int id = args.ID; - short x = args.X; - short y = args.Y; - byte homeless = args.Homeless; - - if (!args.Player.HasBuildPermission(x, y)) - { - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(x, y)) - { - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - args.Handled = true; - return; - } - } - - /// The Bouncer handler for when chests are opened. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnChestOpen(object sender, GetDataHandlers.ChestOpenEventArgs args) - { - if (args.Player.IsBeingDisabled()) - { - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(args.X, args.Y)) - { - args.Handled = true; - return; - } - - if (!args.Player.HasBuildPermission(args.X, args.Y) && TShock.Config.RegionProtectChests) - { - args.Handled = true; - return; - } - - int id = Chest.FindChest(args.X, args.Y); - args.Player.ActiveChest = id; - } - - /// The place chest event that Bouncer hooks to prevent accidental damage. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlaceChest(object sender, GetDataHandlers.PlaceChestEventArgs args) - { - int tileX = args.TileX; - int tileY = args.TileY; - int flag = args.Flag; - - if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendTileSquare(tileX, tileY, 3); - args.Handled = true; - return; - } - - if (flag != 0 && flag != 4 // if no container or container2 placement - && Main.tile[tileX, tileY].type != TileID.Containers - && Main.tile[tileX, tileY].type != TileID.Dressers - && Main.tile[tileX, tileY].type != TileID.Containers2 - && (!TShock.Utils.HasWorldReachedMaxChests() && Main.tile[tileX, tileY].type != TileID.Dirt)) //Chest - { - args.Player.SendTileSquare(tileX, tileY, 3); - args.Handled = true; - return; - } - - if (flag == 2) //place dresser - { - if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Teleporter) || - (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Teleporter)) - { - //Prevent a dresser from being placed on a teleporter, as this can cause client and server crashes. - args.Player.SendTileSquare(tileX, tileY, 3); - args.Handled = true; - return; - } - } - - if (!args.Player.HasBuildPermission(tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - args.Handled = true; - return; - } - } - - /// Handles Bouncer's liquid set anti-cheat. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnLiquidSet(object sender, GetDataHandlers.LiquidSetEventArgs args) - { - int tileX = args.TileX; - int tileY = args.TileY; - byte amount = args.Amount; - byte type = args.Type; - - if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold) - { - args.Player.Disable("Reached TileLiquid threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (!args.Player.HasPermission(Permissions.ignoreliquidsetdetection)) - { - args.Player.TileLiquidThreshold++; - } - - // Liquid anti-cheat - // Arguably the banned buckets bit should be in the item bans system - if (amount != 0) - { - int bucket = -1; - if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.EmptyBucket) - { - bucket = 0; - } - else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.WaterBucket) - { - bucket = 1; - } - else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.LavaBucket) - { - bucket = 2; - } - else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.HoneyBucket) - { - bucket = 3; - } - else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.BottomlessBucket || - args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.SuperAbsorbantSponge) - { - bucket = 4; - } - - if (type == 1 && !(bucket == 2 || bucket == 0)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Spreading lava without holding a lava bucket", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (type == 1 && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Using banned lava bucket without permissions", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (type == 0 && !(bucket == 1 || bucket == 0 || bucket == 4)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Spreading water without holding a water bucket", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (type == 0 && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Using banned water bucket without permissions", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (type == 2 && !(bucket == 3 || bucket == 0)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Spreading honey without holding a honey bucket", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (type == 2 && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player)) - { - args.Player.SendErrorMessage("You do not have permission to perform this action."); - args.Player.Disable("Using banned honey bucket without permissions", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - } - - if (!args.Player.HasBuildPermission(tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (!args.Player.IsInRange(tileX, tileY, 16)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Handled = true; - return; - } - } - - /// Handles ProjectileKill events for throttling and out of bounds projectiles. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnProjectileKill(object sender, GetDataHandlers.ProjectileKillEventArgs args) - { - if (args.ProjectileIndex < 0) - { - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); - args.Handled = true; - return; - } - } - /// Handles disabling enforcement and minor anti-exploit stuff /// The object that triggered the event. /// The packet arguments that the event has. @@ -1001,319 +178,6 @@ namespace TShockAPI return; } - /// Handles PlayerZone events for preventing spawning NPC maliciously. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlayerZone(object sender, GetDataHandlers.PlayerZoneEventArgs args) - { - if(args.Zone2[1] || args.Zone2[2] || args.Zone2[3] || args.Zone2[4]) - { - bool hasSolarTower = false; - bool hasVortexTower = false; - bool hasNebulaTower = false; - bool hasStardustTower = false; - - foreach (var npc in Main.npc) - { - if (npc.netID == NPCID.LunarTowerSolar) - hasSolarTower = true; - else if (npc.netID == NPCID.LunarTowerVortex) - hasVortexTower = true; - else if (npc.netID == NPCID.LunarTowerNebula) - hasNebulaTower = true; - else if (npc.netID == NPCID.LunarTowerStardust) - hasStardustTower = true; - } - - if ((args.Zone2[1] && !hasSolarTower) - || (args.Zone2[2] && !hasVortexTower) - || (args.Zone2[3] && !hasNebulaTower) - || (args.Zone2[4] && !hasStardustTower) - ) - { - args.Handled = true; - return; - } - } - } - - /// Bouncer's KillMe hook stops crash exploits from out of bounds values. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnKillMe(object sender, GetDataHandlers.KillMeEventArgs args) - { - short damage = args.Damage; - short id = args.PlayerId; - PlayerDeathReason playerDeathReason = args.PlayerDeathReason; - - if (damage > 20000) //Abnormal values have the potential to cause infinite loops in the server. - { - args.Player.Kick("Failed to shade polygon normals.", true, true); - TShock.Log.ConsoleError("Death Exploit Attempt: Damage {0}", damage); - args.Handled = true; - return; - } - - if (id >= Main.maxPlayers) - { - args.Handled = true; - return; - } - - // This was formerly marked as a crash check; does not actually crash on this specific packet. - if (playerDeathReason != null) - { - if (playerDeathReason.GetDeathText(TShock.Players[id].Name).ToString().Length > 500) - { - TShock.Players[id].Kick("Death reason outside of normal bounds.", true); - args.Handled = true; - return; - } - } - } - - /// Bouncer's projectile trigger hook stops world damaging projectiles from destroying the world. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventArgs args) - { - short ident = args.Identity; - Vector2 pos = args.Position; - Vector2 vel = args.Velocity; - float knockback = args.Knockback; - short damage = args.Damage; - byte owner = args.Owner; - short type = args.Type; - int index = args.Index; - - if (index > Main.maxProjectiles) - { - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - if (TShock.ProjectileBans.ProjectileIsBanned(type, args.Player)) - { - args.Player.Disable("Player does not have permission to create that projectile.", DisableFlags.WriteToLogAndConsole); - args.Player.SendErrorMessage("You do not have permission to create that projectile."); - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - if (damage > TShock.Config.MaxProjDamage && !args.Player.HasPermission(Permissions.ignoredamagecap)) - { - args.Player.Disable(String.Format("Projectile damage is higher than {0}.", TShock.Config.MaxProjDamage), DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - bool hasPermission = args.Player.HasProjectilePermission(index, type); - if (!TShock.Config.IgnoreProjUpdate && !hasPermission && !args.Player.HasPermission(Permissions.ignoreprojectiledetection)) - { - if (type == ProjectileID.BlowupSmokeMoonlord - || type == ProjectileID.PhantasmalEye - || type == ProjectileID.CultistBossIceMist - || (type >= ProjectileID.MoonlordBullet && type <= ProjectileID.MoonlordTurretLaser) - || type == ProjectileID.DeathLaser || type == ProjectileID.Landmine - || type == ProjectileID.BulletDeadeye || type == ProjectileID.BoulderStaffOfEarth - || (type > ProjectileID.ConfettiMelee && type < ProjectileID.SpiritHeal) - || (type >= ProjectileID.FlamingWood && type <= ProjectileID.GreekFire3) - || (type >= ProjectileID.PineNeedleHostile && type <= ProjectileID.Spike) - || (type >= ProjectileID.MartianTurretBolt && type <= ProjectileID.RayGunnerLaser) - || type == ProjectileID.CultistBossLightningOrb) - { - TShock.Log.Debug("Certain projectiles have been ignored for cheat detection."); - } - else - { - args.Player.Disable(String.Format("Does not have projectile permission to update projectile. ({0})", type), DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); - } - args.Handled = true; - return; - } - - if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold) - { - args.Player.Disable("Reached projectile update threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - if (args.Player.IsBouncerThrottled()) - { - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } - - if (!args.Player.HasPermission(Permissions.ignoreprojectiledetection)) - { - if (type == ProjectileID.CrystalShard && TShock.Config.ProjIgnoreShrapnel) // Ignore crystal shards - { - TShock.Log.Debug("Ignoring shrapnel per config.."); - } - else if (!Main.projectile[index].active) - { - args.Player.ProjectileThreshold++; // Creating new projectile - } - } - - if (hasPermission && - (type == ProjectileID.Bomb - || type == ProjectileID.Dynamite - || type == ProjectileID.StickyBomb - || type == ProjectileID.StickyDynamite)) - { - // Denotes that the player has recently set a fuse - used for cheat detection. - args.Player.RecentFuse = 10; - } - } - - /// Bouncer's PlaceObject hook reverts malicious tile placement. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnPlaceObject(object sender, GetDataHandlers.PlaceObjectEventArgs args) - { - short x = args.X; - short y = args.Y; - short type = args.Type; - short style = args.Style; - byte alternate = args.Alternate; - bool direction = args.Direction; - - if (type < 0 || type >= Main.maxTileSets) - { - args.Handled = true; - return; - } - - if (x < 0 || x >= Main.maxTilesX) - { - args.Handled = true; - return; - } - - if (y < 0 || y >= Main.maxTilesY) - { - args.Handled = true; - return; - } - - //style 52 and 53 are used by ItemID.Fake_newchest1 and ItemID.Fake_newchest2 - //These two items cause localised lag and rendering issues - if (type == TileID.FakeContainers && (style == 52 || style == 53)) - { - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - // TODO: REMOVE. This does NOT look like Bouncer code. - if (TShock.TileBans.TileIsBanned(type, args.Player)) - { - args.Player.SendTileSquare(x, y, 1); - args.Player.SendErrorMessage("You do not have permission to place this tile."); - args.Handled = true; - return; - } - - if (!TShock.Utils.TilePlacementValid(x, y)) - { - args.Player.SendTileSquare(x, y, 1); - args.Handled = true; - return; - } - - if (args.Player.Dead && TShock.Config.PreventDeadModification) - { - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled()) - { - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - // This is neccessary to check in order to prevent special tiles such as - // queen bee larva, paintings etc that use this packet from being placed - // without selecting the right item. - if (type != args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].createTile) - { - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - TileObjectData tileData = TileObjectData.GetTileData(type, style, 0); - if (tileData == null) - { - args.Handled = true; - return; - } - - x -= tileData.Origin.X; - y -= tileData.Origin.Y; - - for (int i = x; i < x + tileData.Width; i++) - { - for (int j = y; j < y + tileData.Height; j++) - { - if (!args.Player.HasModifiedIceSuccessfully(i, j, type, EditAction.PlaceTile) - && !args.Player.HasBuildPermission(i, j)) - { - args.Player.SendTileSquare(i, j, 4); - args.Handled = true; - return; - } - } - } - - // Ignore rope placement range - if ((type != TileID.Rope - || type != TileID.SilkRope - || type != TileID.VineRope - || type != TileID.WebRope) - && !args.Player.IsInRange(x, y)) - { - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) - { - args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(x, y, 4); - args.Handled = true; - return; - } - - if (!args.Player.HasPermission(Permissions.ignoreplacetiledetection)) - { - args.Player.TilePlaceThreshold++; - var coords = new Vector2(x, y); - lock (args.Player.TilesCreated) - if (!args.Player.TilesCreated.ContainsKey(coords)) - args.Player.TilesCreated.Add(coords, Main.tile[x, y]); - } - } - /// Bouncer's TileEdit hook is used to revert malicious tile changes. /// The object that triggered the event. /// The packet arguments that the event has. @@ -1347,7 +211,7 @@ namespace TShockAPI args.Handled = false; return; } - + if (args.Player.Dead && TShock.Config.PreventDeadModification) { args.Player.SendTileSquare(tileX, tileY, 4); @@ -1608,49 +472,7 @@ namespace TShockAPI return; } } - - /// Bouncer's HealOther handler prevents gross misuse of HealOther packets by hackers. - /// The object that triggered the event. - /// The packet arguments that the event has. - internal void OnHealOtherPlayer(object sender, GetDataHandlers.HealOtherPlayerEventArgs args) - { - short amount = args.Amount; - byte plr = args.TargetPlayerIndex; - - if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active) - { - args.Handled = true; - return; - } - - // Why 0.2? - // @bartico6: Because heal other player only happens when you are using the spectre armor with the hood, - // and the healing you can do with that is 20% of your damage. - if (amount > TShock.Config.MaxDamage * 0.2) - { - args.Player.Disable("HealOtherPlayer cheat attempt!", DisableFlags.WriteToLogAndConsole); - args.Handled = true; - return; - } - - if (args.Player.HealOtherThreshold > TShock.Config.HealOtherThreshold) - { - args.Player.Disable("Reached HealOtherPlayer threshold.", DisableFlags.WriteToLogAndConsole); - args.Handled = true; - return; - } - - if (args.Player.IsBeingDisabled() || args.Player.IsBouncerThrottled()) - { - args.Handled = true; - return; - } - - args.Player.HealOtherThreshold++; - args.Handled = false; - return; - } - + /// Bouncer's SendTileSquare hook halts large scope world destruction. /// The object that triggered the event. /// The packet arguments that the event has. @@ -1815,7 +637,7 @@ namespace TShockAPI if ((tile.type == TileID.TrapdoorClosed && (newtile.Type == TileID.TrapdoorOpen || !newtile.Active)) || (tile.type == TileID.TrapdoorOpen && (newtile.Type == TileID.TrapdoorClosed || !newtile.Active)) || - (!tile.active() && newtile.Active && (newtile.Type == TileID.TrapdoorOpen||newtile.Type == TileID.TrapdoorClosed))) + (!tile.active() && newtile.Active && (newtile.Type == TileID.TrapdoorOpen || newtile.Type == TileID.TrapdoorClosed))) { Main.tile[realx, realy].type = newtile.Type; Main.tile[realx, realy].frameX = newtile.FrameX; @@ -1843,7 +665,980 @@ namespace TShockAPI args.Handled = true; } + + /// Registered when items fall to the ground to prevent cheating. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs 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; + // player is attempting to crash clients + if (type < -48 || type >= Main.maxItemTypes) + { + // Causes item duplications. Will be re added later if necessary + //args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + + // make sure the prefix is a legit value + // Note: Not checking if prefix is less than 1 because if it is, this check + // will break item pickups on the client. + if (prefix > PrefixID.Count) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + + //Item removed, let client do this to prevent item duplication + // client side (but only if it passed the range check) (i.e., return false) + 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); + args.Handled = true; + return; + } + + args.Handled = false; + return; + } + + if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f))) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + + // stop the client from changing the item type of a drop but + // only if the client isn't picking up the item + if (Main.item[id].active && Main.item[id].netID != type) + { + 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.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems))) + { + args.Player.SendData(PacketTypes.ItemDrop, "", 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.LogonDiscardThreshold)) + { + //Player is probably trying to sneak items onto the server in their hands!!! + TShock.Log.ConsoleInfo("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name); + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Handled = true; + return; + } + } + + /// Bouncer's projectile trigger hook stops world damaging projectiles from destroying the world. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventArgs args) + { + short ident = args.Identity; + Vector2 pos = args.Position; + Vector2 vel = args.Velocity; + float knockback = args.Knockback; + short damage = args.Damage; + byte owner = args.Owner; + short type = args.Type; + int index = args.Index; + + if (index > Main.maxProjectiles) + { + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + if (TShock.ProjectileBans.ProjectileIsBanned(type, args.Player)) + { + args.Player.Disable("Player does not have permission to create that projectile.", DisableFlags.WriteToLogAndConsole); + args.Player.SendErrorMessage("You do not have permission to create that projectile."); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + if (damage > TShock.Config.MaxProjDamage && !args.Player.HasPermission(Permissions.ignoredamagecap)) + { + args.Player.Disable(String.Format("Projectile damage is higher than {0}.", TShock.Config.MaxProjDamage), DisableFlags.WriteToLogAndConsole); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + bool hasPermission = args.Player.HasProjectilePermission(index, type); + if (!TShock.Config.IgnoreProjUpdate && !hasPermission && !args.Player.HasPermission(Permissions.ignoreprojectiledetection)) + { + if (type == ProjectileID.BlowupSmokeMoonlord + || type == ProjectileID.PhantasmalEye + || type == ProjectileID.CultistBossIceMist + || (type >= ProjectileID.MoonlordBullet && type <= ProjectileID.MoonlordTurretLaser) + || type == ProjectileID.DeathLaser || type == ProjectileID.Landmine + || type == ProjectileID.BulletDeadeye || type == ProjectileID.BoulderStaffOfEarth + || (type > ProjectileID.ConfettiMelee && type < ProjectileID.SpiritHeal) + || (type >= ProjectileID.FlamingWood && type <= ProjectileID.GreekFire3) + || (type >= ProjectileID.PineNeedleHostile && type <= ProjectileID.Spike) + || (type >= ProjectileID.MartianTurretBolt && type <= ProjectileID.RayGunnerLaser) + || type == ProjectileID.CultistBossLightningOrb) + { + TShock.Log.Debug("Certain projectiles have been ignored for cheat detection."); + } + else + { + args.Player.Disable(String.Format("Does not have projectile permission to update projectile. ({0})", type), DisableFlags.WriteToLogAndConsole); + args.Player.RemoveProjectile(ident, owner); + } + args.Handled = true; + return; + } + + if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold) + { + args.Player.Disable("Reached projectile update threshold.", DisableFlags.WriteToLogAndConsole); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + if (!args.Player.HasPermission(Permissions.ignoreprojectiledetection)) + { + if (type == ProjectileID.CrystalShard && TShock.Config.ProjIgnoreShrapnel) // Ignore crystal shards + { + TShock.Log.Debug("Ignoring shrapnel per config.."); + } + else if (!Main.projectile[index].active) + { + args.Player.ProjectileThreshold++; // Creating new projectile + } + } + + if (hasPermission && + (type == ProjectileID.Bomb + || type == ProjectileID.Dynamite + || type == ProjectileID.StickyBomb + || type == ProjectileID.StickyDynamite)) + { + // Denotes that the player has recently set a fuse - used for cheat detection. + args.Player.RecentFuse = 10; + } + } + + /// Handles the NPC Strike event for Bouncer. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnNPCStrike(object sender, GetDataHandlers.NPCStrikeEventArgs args) + { + short id = args.ID; + byte direction = args.Direction; + short damage = args.Damage; + float knockback = args.Knockback; + byte crit = args.Critical; + + if (Main.npc[id] == null) + { + args.Handled = true; + return; + } + + if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap)) + { + if (TShock.Config.KickOnDamageThresholdBroken) + { + args.Player.Kick(string.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage)); + args.Handled = true; + return; + } + else + { + args.Player.Disable(String.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole); + } + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + args.Handled = true; + return; + } + + if (TShock.Config.RangeChecks && + !args.Player.IsInRange((int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128)) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + args.Handled = true; + return; + } + } + + /// Handles ProjectileKill events for throttling and out of bounds projectiles. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnProjectileKill(object sender, GetDataHandlers.ProjectileKillEventArgs args) + { + if (args.ProjectileIndex < 0) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); + args.Handled = true; + return; + } + } + + /// Handles when a chest item is changed. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnChestItemChange(object sender, GetDataHandlers.ChestItemEventArgs args) + { + short id = args.ID; + byte slot = args.Slot; + short stacks = args.Stacks; + byte prefix = args.Prefix; + short type = args.Type; + + if (args.Player.TPlayer.chest != id) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.ChestItem, "", id, slot); + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) + { + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(Main.chest[id].x, Main.chest[id].y)) + { + args.Handled = true; + return; + } + } + + /// The Bouncer handler for when chests are opened. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnChestOpen(object sender, GetDataHandlers.ChestOpenEventArgs args) + { + if (args.Player.IsBeingDisabled()) + { + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(args.X, args.Y)) + { + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(args.X, args.Y) && TShock.Config.RegionProtectChests) + { + args.Handled = true; + return; + } + + int id = Chest.FindChest(args.X, args.Y); + args.Player.ActiveChest = id; + } + + /// The place chest event that Bouncer hooks to prevent accidental damage. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlaceChest(object sender, GetDataHandlers.PlaceChestEventArgs args) + { + int tileX = args.TileX; + int tileY = args.TileY; + int flag = args.Flag; + + if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + + if (flag != 0 && flag != 4 // if no container or container2 placement + && Main.tile[tileX, tileY].type != TileID.Containers + && Main.tile[tileX, tileY].type != TileID.Dressers + && Main.tile[tileX, tileY].type != TileID.Containers2 + && (!TShock.Utils.HasWorldReachedMaxChests() && Main.tile[tileX, tileY].type != TileID.Dirt)) //Chest + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + + if (flag == 2) //place dresser + { + if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Teleporter) || + (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Teleporter)) + { + //Prevent a dresser from being placed on a teleporter, as this can cause client and server crashes. + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + } + + if (!args.Player.HasBuildPermission(tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + } + + /// Handles PlayerZone events for preventing spawning NPC maliciously. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlayerZone(object sender, GetDataHandlers.PlayerZoneEventArgs args) + { + if (args.Zone2[1] || args.Zone2[2] || args.Zone2[3] || args.Zone2[4]) + { + bool hasSolarTower = false; + bool hasVortexTower = false; + bool hasNebulaTower = false; + bool hasStardustTower = false; + + foreach (var npc in Main.npc) + { + if (npc.netID == NPCID.LunarTowerSolar) + hasSolarTower = true; + else if (npc.netID == NPCID.LunarTowerVortex) + hasVortexTower = true; + else if (npc.netID == NPCID.LunarTowerNebula) + hasNebulaTower = true; + else if (npc.netID == NPCID.LunarTowerStardust) + hasStardustTower = true; + } + + if ((args.Zone2[1] && !hasSolarTower) + || (args.Zone2[2] && !hasVortexTower) + || (args.Zone2[3] && !hasNebulaTower) + || (args.Zone2[4] && !hasStardustTower) + ) + { + args.Handled = true; + return; + } + } + } + + /// Handles basic animation throttling for disabled players. + /// sender + /// args + internal void OnPlayerAnimation(object sender, GetDataHandlers.PlayerAnimationEventArgs args) + { + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); + args.Handled = true; + return; + } + } + + /// Handles Bouncer's liquid set anti-cheat. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnLiquidSet(object sender, GetDataHandlers.LiquidSetEventArgs args) + { + int tileX = args.TileX; + int tileY = args.TileY; + byte amount = args.Amount; + byte type = args.Type; + + if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold) + { + args.Player.Disable("Reached TileLiquid threshold.", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (!args.Player.HasPermission(Permissions.ignoreliquidsetdetection)) + { + args.Player.TileLiquidThreshold++; + } + + // Liquid anti-cheat + // Arguably the banned buckets bit should be in the item bans system + if (amount != 0) + { + int bucket = -1; + if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.EmptyBucket) + { + bucket = 0; + } + else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.WaterBucket) + { + bucket = 1; + } + else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.LavaBucket) + { + bucket = 2; + } + else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.HoneyBucket) + { + bucket = 3; + } + else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.BottomlessBucket || + args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.SuperAbsorbantSponge) + { + bucket = 4; + } + + if (type == 1 && !(bucket == 2 || bucket == 0)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Spreading lava without holding a lava bucket", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (type == 1 && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Using banned lava bucket without permissions", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (type == 0 && !(bucket == 1 || bucket == 0 || bucket == 4)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Spreading water without holding a water bucket", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (type == 0 && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Using banned water bucket without permissions", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (type == 2 && !(bucket == 3 || bucket == 0)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Spreading honey without holding a honey bucket", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (type == 2 && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player)) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Using banned honey bucket without permissions", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + + if (!args.Player.HasBuildPermission(tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(tileX, tileY, 16)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + + /// Handles Buff events. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlayerBuff(object sender, GetDataHandlers.PlayerBuffEventArgs args) + { + byte id = args.ID; + byte type = args.Type; + int time = args.Time; + + if (TShock.Players[id] == null) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + args.Handled = true; + return; + } + + if (id >= Main.maxPlayers) + { + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + args.Handled = true; + return; + } + + if (!TShock.Players[id].TPlayer.hostile || !Main.pvpBuff[type]) + { + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) + { + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + args.Handled = true; + return; + } + + if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type]) + { + args.Handled = false; + return; + } + } + + /// Handles NPCAddBuff events. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnNPCAddBuff(object sender, GetDataHandlers.NPCAddBuffEventArgs args) + { + short id = args.ID; + byte type = args.Type; + short time = args.Time; + + if (id >= Main.npc.Length) + { + args.Handled = true; + return; + } + + NPC npc = Main.npc[id]; + + if (npc == null) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Handled = true; + return; + } + + bool detectedNPCBuffTimeCheat = false; + + if (NPCAddBuffTimeMax.ContainsKey(type)) + { + if (time > NPCAddBuffTimeMax[type]) + { + detectedNPCBuffTimeCheat = true; + } + + if (npc.townNPC && npc.netID != NPCID.Guide && npc.netID != NPCID.Clothier) + { + if (type != BuffID.Lovestruck && type != BuffID.Stinky && type != BuffID.DryadsWard && + type != BuffID.Wet && type != BuffID.Slimed) + { + detectedNPCBuffTimeCheat = true; + } + } + } + else + { + detectedNPCBuffTimeCheat = true; + } + + if (detectedNPCBuffTimeCheat) + { + args.Player.Kick("Added buff to NPC abnormally.", true); + args.Handled = true; + } + } + + /// The Bouncer handler for when an NPC is rehomed. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnUpdateNPCHome(object sender, GetDataHandlers.NPCHomeChangeEventArgs args) + { + int id = args.ID; + short x = args.X; + short y = args.Y; + byte homeless = args.Homeless; + + if (!args.Player.HasBuildPermission(x, y)) + { + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(x, y)) + { + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + args.Handled = true; + return; + } + } + + /// Bouncer's HealOther handler prevents gross misuse of HealOther packets by hackers. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnHealOtherPlayer(object sender, GetDataHandlers.HealOtherPlayerEventArgs args) + { + short amount = args.Amount; + byte plr = args.TargetPlayerIndex; + + if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active) + { + args.Handled = true; + return; + } + + // Why 0.2? + // @bartico6: Because heal other player only happens when you are using the spectre armor with the hood, + // and the healing you can do with that is 20% of your damage. + if (amount > TShock.Config.MaxDamage * 0.2) + { + args.Player.Disable("HealOtherPlayer cheat attempt!", DisableFlags.WriteToLogAndConsole); + args.Handled = true; + return; + } + + if (args.Player.HealOtherThreshold > TShock.Config.HealOtherThreshold) + { + args.Player.Disable("Reached HealOtherPlayer threshold.", DisableFlags.WriteToLogAndConsole); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled() || args.Player.IsBouncerThrottled()) + { + args.Handled = true; + return; + } + + args.Player.HealOtherThreshold++; + args.Handled = false; + return; + } + + /// Bouncer's PlaceObject hook reverts malicious tile placement. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlaceObject(object sender, GetDataHandlers.PlaceObjectEventArgs args) + { + short x = args.X; + short y = args.Y; + short type = args.Type; + short style = args.Style; + byte alternate = args.Alternate; + bool direction = args.Direction; + + if (type < 0 || type >= Main.maxTileSets) + { + args.Handled = true; + return; + } + + if (x < 0 || x >= Main.maxTilesX) + { + args.Handled = true; + return; + } + + if (y < 0 || y >= Main.maxTilesY) + { + args.Handled = true; + return; + } + + //style 52 and 53 are used by ItemID.Fake_newchest1 and ItemID.Fake_newchest2 + //These two items cause localised lag and rendering issues + if (type == TileID.FakeContainers && (style == 52 || style == 53)) + { + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + // TODO: REMOVE. This does NOT look like Bouncer code. + if (TShock.TileBans.TileIsBanned(type, args.Player)) + { + args.Player.SendTileSquare(x, y, 1); + args.Player.SendErrorMessage("You do not have permission to place this tile."); + args.Handled = true; + return; + } + + if (!TShock.Utils.TilePlacementValid(x, y)) + { + args.Player.SendTileSquare(x, y, 1); + args.Handled = true; + return; + } + + if (args.Player.Dead && TShock.Config.PreventDeadModification) + { + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + // This is neccessary to check in order to prevent special tiles such as + // queen bee larva, paintings etc that use this packet from being placed + // without selecting the right item. + if (type != args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].createTile) + { + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + TileObjectData tileData = TileObjectData.GetTileData(type, style, 0); + if (tileData == null) + { + args.Handled = true; + return; + } + + x -= tileData.Origin.X; + y -= tileData.Origin.Y; + + for (int i = x; i < x + tileData.Width; i++) + { + for (int j = y; j < y + tileData.Height; j++) + { + if (!args.Player.HasModifiedIceSuccessfully(i, j, type, EditAction.PlaceTile) + && !args.Player.HasBuildPermission(i, j)) + { + args.Player.SendTileSquare(i, j, 4); + args.Handled = true; + return; + } + } + } + + // Ignore rope placement range + if ((type != TileID.Rope + || type != TileID.SilkRope + || type != TileID.VineRope + || type != TileID.WebRope) + && !args.Player.IsInRange(x, y)) + { + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) + { + args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(x, y, 4); + args.Handled = true; + return; + } + + if (!args.Player.HasPermission(Permissions.ignoreplacetiledetection)) + { + args.Player.TilePlaceThreshold++; + var coords = new Vector2(x, y); + lock (args.Player.TilesCreated) + if (!args.Player.TilesCreated.ContainsKey(coords)) + args.Player.TilesCreated.Add(coords, Main.tile[x, y]); + } + } + + /// Fired when a PlaceTileEntity occurs for basic anti-cheat on perms and range. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlaceTileEntity(object sender, GetDataHandlers.PlaceTileEntityEventArgs args) + { + if (args.Player.IsBeingDisabled()) + { + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(args.X, args.Y)) + { + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(args.X, args.Y)) + { + args.Handled = true; + return; + } + } + + /// Fired when an item frame is placed for anti-cheat detection. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEventArgs args) + { + if (args.Player.IsBeingDisabled()) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(args.X, args.Y)) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(args.X, args.Y)) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.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 @@ -1871,7 +1666,212 @@ namespace TShockAPI return; } } + + /// Handles the anti-cheat components of gem lock toggles. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnGemLockToggle(object sender, GetDataHandlers.GemLockToggleEventArgs args) + { + if (args.X < 0 || args.Y < 0 || args.X >= Main.maxTilesX || args.Y >= Main.maxTilesY) + { + args.Handled = true; + return; + } + if (!TShock.Utils.TilePlacementValid(args.X, args.Y) || (args.Player.Dead && TShock.Config.PreventDeadModification)) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(args.X, args.Y)) + { + args.Handled = true; + return; + } + } + + /// Handles validation of of basic anti-cheat on mass wire operations. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnMassWireOperation(object sender, GetDataHandlers.MassWireOperationEventArgs args) + { + short startX = args.StartX; + short startY = args.StartY; + short endX = args.EndX; + short endY = args.EndY; + + List points = Utils.Instance.GetMassWireOperationRange( + new Point(startX, startY), + new Point(endX, endY), + args.Player.TPlayer.direction == 1); + + int x; + int y; + foreach (Point p in points) + { + /* Perform similar checks to TileKill + * The server-side nature of this packet removes the need to use SendTileSquare + * Range checks are currently ignored here as the items that send this seem to have infinite range */ + + x = p.X; + y = p.Y; + + if (!TShock.Utils.TilePlacementValid(x, y) || (args.Player.Dead && TShock.Config.PreventDeadModification)) + { + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Handled = true; + return; + } + + if (!args.Player.HasBuildPermission(x, y)) + { + args.Handled = true; + return; + } + } + } + + /// Called when a player is damaged. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnPlayerDamage(object sender, GetDataHandlers.PlayerDamageEventArgs args) + { + byte id = args.ID; + short damage = args.Damage; + bool pvp = args.PVP; + bool crit = args.Critical; + byte direction = args.Direction; + + if (id >= Main.maxPlayers || TShock.Players[id] == null) + { + args.Handled = true; + return; + } + + if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap) && id != args.Player.Index) + { + if (TShock.Config.KickOnDamageThresholdBroken) + { + args.Player.Kick(string.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage)); + args.Handled = true; + return; + } + else + { + args.Player.Disable(String.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole); + } + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + if (!TShock.Players[id].TPlayer.hostile && pvp && id != args.Player.Index) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + if (args.Player.IsBeingDisabled()) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + } + + /// Bouncer's KillMe hook stops crash exploits from out of bounds values. + /// The object that triggered the event. + /// The packet arguments that the event has. + internal void OnKillMe(object sender, GetDataHandlers.KillMeEventArgs args) + { + short damage = args.Damage; + short id = args.PlayerId; + PlayerDeathReason playerDeathReason = args.PlayerDeathReason; + + if (damage > 20000) //Abnormal values have the potential to cause infinite loops in the server. + { + args.Player.Kick("Failed to shade polygon normals.", true, true); + TShock.Log.ConsoleError("Death Exploit Attempt: Damage {0}", damage); + args.Handled = true; + return; + } + + if (id >= Main.maxPlayers) + { + args.Handled = true; + return; + } + + // This was formerly marked as a crash check; does not actually crash on this specific packet. + if (playerDeathReason != null) + { + if (playerDeathReason.GetDeathText(TShock.Players[id].Name).ToString().Length > 500) + { + TShock.Players[id].Kick("Death reason outside of normal bounds.", true); + args.Handled = true; + return; + } + } + } + + + private static Dictionary NPCAddBuffTimeMax = new Dictionary() + { + { BuffID.Poisoned, 3600 }, + { BuffID.OnFire, 1200 }, + { BuffID.CursedInferno, 420 }, + { BuffID.Frostburn, 900 }, + { BuffID.Ichor, 1200 }, + { BuffID.Venom, 1260 }, + { BuffID.Midas, 120 }, + { BuffID.Wet, 1500 }, + { BuffID.Slimed, 1500 }, + { BuffID.Lovestruck, 1800 }, + { BuffID.Stinky, 1800 }, + { BuffID.SoulDrain, 30 }, + { BuffID.ShadowFlame, 660 }, + { BuffID.DryadsWard, 120 }, + { BuffID.BoneJavelin, 900 }, + { BuffID.StardustMinionBleed, 900 }, + { BuffID.DryadsWardDebuff, 120 }, + { BuffID.Daybreak, 300 }, + { BuffID.BetsysCurse, 600 }, + { BuffID.Oiled, 540 } + }; + /// /// Tile IDs that can be oriented: /// Cannon, diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index dd18e502..4a126d56 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -74,136 +74,148 @@ namespace TShockAPI public static class GetDataHandlers { 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 }, + { PacketTypes.PlayerSlot, HandlePlayerSlot }, + { PacketTypes.ContinueConnecting2, HandleConnecting }, + { PacketTypes.TileGetSection, HandleGetSection }, + { PacketTypes.PlayerSpawn, HandleSpawn }, + { PacketTypes.PlayerUpdate, HandlePlayerUpdate }, + { PacketTypes.PlayerHp, HandlePlayerHp }, + { PacketTypes.Tile, HandleTile }, + { PacketTypes.DoorUse, HandleDoorUse }, + { PacketTypes.TileSendSquare, HandleSendTileSquare }, + { PacketTypes.ItemDrop, HandleItemDrop }, + { PacketTypes.ItemOwner, HandleItemOwner }, + { PacketTypes.ProjectileNew, HandleProjectileNew }, + { PacketTypes.NpcStrike, HandleNpcStrike }, + { PacketTypes.ProjectileDestroy, HandleProjectileKill }, + { PacketTypes.TogglePvp, HandleTogglePvp }, + { PacketTypes.ChestGetContents, HandleChestOpen }, + { PacketTypes.ChestItem, HandleChestItem }, + { PacketTypes.ChestOpen, HandleChestActive }, + { PacketTypes.PlaceChest, HandlePlaceChest }, + { PacketTypes.Zones, HandlePlayerZone }, + { PacketTypes.PasswordSend, HandlePassword }, + { PacketTypes.PlayerAnimation, HandlePlayerAnimation }, + { PacketTypes.PlayerMana, HandlePlayerMana }, + { PacketTypes.PlayerTeam, HandlePlayerTeam }, + { PacketTypes.SignNew, HandleSign }, + { PacketTypes.LiquidSet, HandleLiquidSet }, + { PacketTypes.PlayerBuff, HandlePlayerBuffList }, + { PacketTypes.NpcSpecial, HandleSpecial }, + { PacketTypes.NpcAddBuff, HandleNPCAddBuff }, + { PacketTypes.PlayerAddBuff, HandlePlayerAddBuff }, + { PacketTypes.UpdateNPCHome, UpdateNPCHome }, + { PacketTypes.SpawnBossorInvasion, HandleSpawnBoss }, + { PacketTypes.PaintTile, HandlePaintTile }, + { PacketTypes.PaintWall, HandlePaintWall }, + { PacketTypes.Teleport, HandleTeleport }, + { PacketTypes.PlayerHealOther, HandleHealOther }, + { PacketTypes.CatchNPC, HandleCatchNpc }, + { PacketTypes.CompleteAnglerQuest, HandleCompleteAnglerQuest }, + { PacketTypes.NumberOfAnglerQuestsCompleted, HandleNumberOfAnglerQuestsCompleted }, + { PacketTypes.PlaceObject, HandlePlaceObject }, + { PacketTypes.LoadNetModule, HandleLoadNetModule }, + { PacketTypes.PlaceTileEntity, HandlePlaceTileEntity }, + { PacketTypes.PlaceItemFrame, HandlePlaceItemFrame }, + { PacketTypes.UpdateItemDrop, HandleItemDrop }, + { PacketTypes.SyncExtraValue, HandleSyncExtraValue }, + { PacketTypes.KillPortal, HandleKillPortal }, + { PacketTypes.PlayerTeleportPortal, HandlePlayerPortalTeleport }, + { PacketTypes.NpcTeleportPortal, HandleNpcTeleportPortal }, + { PacketTypes.GemLockToggle, HandleGemLockToggle }, + { PacketTypes.MassWireOperation, HandleMassWireOperation }, + { PacketTypes.ToggleParty, HandleToggleParty }, + { PacketTypes.CrystalInvasionStart, HandleOldOnesArmy }, + { PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 }, + { PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 } + }; + } + + public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data) + { + GetDataHandlerDelegate handler; + if (GetDataHandlerDelegates.TryGetValue(type, out handler)) + { + try + { + return handler(new GetDataHandlerArgs(player, data)); + } + catch (Exception ex) + { + TShock.Log.Error(ex.ToString()); + return true; + } + } + return false; + } + #region Events - /// - /// Used when a TileEdit event is called. - /// - public class TileEditEventArgs : GetDataHandledEventArgs + public class PlayerInfoEventArgs : GetDataHandledEventArgs { /// - /// The tile coordinate on the X plane - /// - public int X { get; set; } - - /// - /// The tile coordinate on the Y plane - /// - public int Y { get; set; } - - /// - /// The Tile ID being edited. - /// - public short EditData { get; set; } - /// - /// The EditType. - /// (KillTile = 0, PlaceTile = 1, KillWall = 2, PlaceWall = 3, KillTileNoItem = 4, PlaceWire = 5, KillWire = 6) - /// - public EditAction Action { get; set; } - - /// - /// Did the tile get destroyed successfully. - /// - public EditType editDetail { get; set; } - - /// - /// Used when a tile is placed to denote a subtype of tile. (e.g. for tile id 21: Chest = 0, Gold Chest = 1) - /// - public byte Style { get; set; } - } - - /// - /// TileEdit - called when a tile is placed or destroyed - /// - public static HandlerList TileEdit = new HandlerList(); - private static bool OnTileEdit(TSPlayer ply, MemoryStream data, int x, int y, EditAction action, EditType editDetail, short editData, byte style) - { - if (TileEdit == null) - return false; - - var args = new TileEditEventArgs - { - Player = ply, - Data = data, - X = x, - Y = y, - Action = action, - EditData = editData, - editDetail = editDetail, - Style = style - }; - TileEdit.Invoke(null, args); - return args.Handled; - } - /// - /// For use in a TogglePvp event - /// - public class TogglePvpEventArgs : GetDataHandledEventArgs - { - /// - /// The Terraria player ID of the player + /// The Terraria playerID of the player /// public byte PlayerId { get; set; } /// - /// Enable/disable pvp? + /// Hair color /// - public bool Pvp { get; set; } + public byte Hair { get; set; } + /// + /// Clothing style. 0-3 are for male characters, and 4-7 are for female characters. + /// + public int Style { get; set; } + /// + /// Character difficulty + /// + public byte Difficulty { get; set; } + /// + /// Player/character name + /// + public string Name { get; set; } } /// - /// TogglePvp - called when a player toggles pvp + /// PlayerInfo - called at a PlayerInfo event + /// If this is cancelled, the server will kick the player. If this should be changed in the future, let someone know. /// - public static HandlerList TogglePvp = new HandlerList(); - private static bool OnPvpToggled(TSPlayer player, MemoryStream data, byte _id, bool _pvp) + public static HandlerList PlayerInfo = new HandlerList(); + private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid, byte _hair, int _style, byte _difficulty, string _name) { - if (TogglePvp == null) + if (PlayerInfo == null) return false; - var args = new TogglePvpEventArgs + var args = new PlayerInfoEventArgs { Player = player, Data = data, - PlayerId = _id, - Pvp = _pvp, + PlayerId = _plrid, + Hair = _hair, + Style = _style, + Difficulty = _difficulty, + Name = _name, }; - TogglePvp.Invoke(null, args); + PlayerInfo.Invoke(null, args); return args.Handled; } - - /// - /// For use in a PlayerTeam event - /// - public class PlayerTeamEventArgs : GetDataHandledEventArgs - { - /// - /// The Terraria player ID of the player - /// - public byte PlayerId { get; set; } - /// - /// Enable/disable pvp? - /// - public byte Team { get; set; } - } - /// - /// TogglePvp - called when a player toggles pvp - /// - public static HandlerList PlayerTeam = new HandlerList(); - private static bool OnPlayerTeam(TSPlayer player, MemoryStream data, byte _id, byte _team) - { - if (PlayerTeam == null) - return false; - - var args = new PlayerTeamEventArgs - { - Player = player, - Data = data, - PlayerId = _id, - Team = _team, - }; - PlayerTeam.Invoke(null, args); - return args.Handled; - } - + /// /// For use in a PlayerSlot event /// @@ -252,251 +264,35 @@ namespace TShockAPI PlayerSlot.Invoke(null, args); return args.Handled; } - - /// - /// For use in a PlayerHP event - /// - public class PlayerHPEventArgs : GetDataHandledEventArgs + + /// The arguments to a GetSection packet. + public class GetSectionEventArgs : GetDataHandledEventArgs { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// Current HP - /// - public short Current { get; set; } - /// - /// Maximum HP - /// - public short Max { get; set; } + /// The X position requested. Or -1 for spawn. + public int X { get; set; } + + /// The Y position requested. Or -1 for spawn. + public int Y { get; set; } } - /// - /// PlayerHP - called at a PlayerHP event - /// - public static HandlerList PlayerHP = new HandlerList(); - - private static bool OnPlayerHP(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max) + /// The hook for a GetSection event. + public static HandlerList GetSection = new HandlerList(); + private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y) { - if (PlayerHP == null) + if (GetSection == null) return false; - var args = new PlayerHPEventArgs + var args = new GetSectionEventArgs { Player = player, Data = data, - PlayerId = _plr, - Current = _cur, - Max = _max, - }; - PlayerHP.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerMana event - /// - public class PlayerManaEventArgs : GetDataHandledEventArgs - { - public byte PlayerId { get; set; } - public short Current { get; set; } - public short Max { get; set; } - } - /// - /// PlayerMana - called at a PlayerMana event - /// - public static HandlerList PlayerMana = new HandlerList(); - - private static bool OnPlayerMana(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max) - { - if (PlayerMana == null) - return false; - - var args = new PlayerManaEventArgs - { - Player = player, - Data = data, - PlayerId = _plr, - Current = _cur, - Max = _max, - }; - PlayerMana.Invoke(null, args); - return args.Handled; - } - - public class PlayerInfoEventArgs : GetDataHandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// Hair color - /// - public byte Hair { get; set; } - /// - /// Clothing style. 0-3 are for male characters, and 4-7 are for female characters. - /// - public int Style { get; set; } - /// - /// Character difficulty - /// - public byte Difficulty { get; set; } - /// - /// Player/character name - /// - public string Name { get; set; } - } - /// - /// PlayerInfo - called at a PlayerInfo event - /// If this is cancelled, the server will kick the player. If this should be changed in the future, let someone know. - /// - public static HandlerList PlayerInfo = new HandlerList(); - - private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid, byte _hair, int _style, byte _difficulty, string _name) - { - if (PlayerInfo == null) - return false; - - var args = new PlayerInfoEventArgs - { - Player = player, - Data = data, - PlayerId = _plrid, - Hair = _hair, - Style = _style, - Difficulty = _difficulty, - Name = _name, - }; - PlayerInfo.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlaceChest event - /// - public class PlaceChestEventArgs : GetDataHandledEventArgs - { - /// What the packet is doing (see MP packet docs). - public int Flag { get; set; } - /// - /// The X coordinate - /// - public int TileX { get; set; } - /// - /// The Y coordinate - /// - public int TileY { get; set; } - } - /// - /// When a chest is added or removed from the world. - /// - public static HandlerList PlaceChest = new HandlerList(); - - private static bool OnPlaceChest(TSPlayer player, MemoryStream data, int flag, int tilex, int tiley) - { - if (PlaceChest == null) - return false; - - var args = new PlaceChestEventArgs - { - Player = player, - Data = data, - Flag = flag, - TileX = tilex, - TileY = tiley, - }; - PlaceChest.Invoke(null, args); - return args.Handled; - } - - /// The arguments to the ProjectileKill packet. - public class ProjectileKillEventArgs : GetDataHandledEventArgs - { - /// The projectile's identity...? - public int ProjectileIdentity; - /// The the player index of the projectile's owner (Main.players). - public byte ProjectileOwner; - /// The index of the projectile in Main.projectile. - public int ProjectileIndex; - } - - /// The event fired when a projectile kill packet is received. - public static HandlerList ProjectileKill = new HandlerList(); - - /// Fires the ProjectileKill event. - /// The TSPlayer that caused the event. - /// The MemoryStream containing the raw event data. - /// The projectile identity (from the packet). - /// The projectile's owner (from the packet). - /// The projectile's index (from Main.projectiles). - /// bool - private static bool OnProjectileKill(TSPlayer player, MemoryStream data, int identity, byte owner, int index) - { - if (ProjectileKill == null) - return false; - - var args = new ProjectileKillEventArgs - { - Player = player, - Data = data, - ProjectileIdentity = identity, - ProjectileOwner = owner, - ProjectileIndex = index, + X = x, + Y = y, }; - ProjectileKill.Invoke(null, args); + GetSection.Invoke(null, args); return args.Handled; } - - /// - /// For use in a KillMe event - /// - public class KillMeEventArgs : GetDataHandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// The direction the damage is coming from (?) - /// - public byte Direction { get; set; } - /// - /// Amount of damage delt - /// - public short Damage { get; set; } - /// - /// Player's current pvp setting - /// - public bool Pvp { get; set; } - /// The reason the player died. - public PlayerDeathReason PlayerDeathReason { get; set; } - } - /// - /// KillMe - Terraria's crappy way of handling damage from players - /// - public static HandlerList KillMe = new HandlerList(); - - private static bool OnKillMe(TSPlayer player, MemoryStream data, byte plr, byte direction, short damage, bool pvp, PlayerDeathReason playerDeathReason) - { - if (KillMe == null) - return false; - - var args = new KillMeEventArgs - { - Player = player, - Data = data, - PlayerId = plr, - Direction = direction, - Damage = damage, - Pvp = pvp, - PlayerDeathReason = playerDeathReason, - }; - KillMe.Invoke(null, args); - return args.Handled; - } - + /// /// For use in a PlayerUpdate event /// @@ -529,7 +325,6 @@ namespace TShockAPI /// PlayerUpdate - When the player sends it's updated information to the server /// public static HandlerList PlayerUpdate = new HandlerList(); - private static bool OnPlayerUpdate(TSPlayer player, MemoryStream data, byte plr, byte control, byte item, Vector2 position, Vector2 velocity, byte pulley) { if (PlayerUpdate == null) @@ -549,93 +344,105 @@ namespace TShockAPI PlayerUpdate.Invoke(null, args); return args.Handled; } - + /// - /// For use in a PlayerZone event + /// For use in a PlayerHP event /// - public class PlayerZoneEventArgs : GetDataHandledEventArgs + public class PlayerHPEventArgs : GetDataHandledEventArgs { /// /// The Terraria playerID of the player /// public byte PlayerId { get; set; } /// - /// 0 = Dungeon, 1 = Corruption,2 =Holy, 3 = Meteor, 4 = Jungle, 5 = Snow, 6 = Crimson, 7 = Water Candle + /// Current HP /// - public BitsByte Zone1 { get; set; } + public short Current { get; set; } /// - /// 0 = Peace Candle, 1 = Solar Tower, 2 = Vortex Tower, 3 = Nebula Tower, 4 = Stardust Tower, 5 = Desert, 6 = Glowshroom, 7 = Underground Desert + /// Maximum HP /// - public BitsByte Zone2 { get; set; } - /// - /// 0 = Overworld, 1 = Dirt Layer, 2 = Rock Layer, 3 = Underworld, 4 = Beach, 5 = Rain, 6 = Sandstorm - /// - public BitsByte Zone3 { get; set; } - /// - /// 0 = Old One's Army - /// - public BitsByte Zone4 { get; set; } + public short Max { get; set; } } /// - /// PlayerZone - When the player sends it's zone/biome information to the server + /// PlayerHP - called at a PlayerHP event /// - public static HandlerList PlayerZone = new HandlerList(); + public static HandlerList PlayerHP = new HandlerList(); + private static bool OnPlayerHP(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max) + { + if (PlayerHP == null) + return false; + + var args = new PlayerHPEventArgs + { + Player = player, + Data = data, + PlayerId = _plr, + Current = _cur, + Max = _max, + }; + PlayerHP.Invoke(null, args); + return args.Handled; + } - private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4) + /// + /// Used when a TileEdit event is called. + /// + public class TileEditEventArgs : GetDataHandledEventArgs { - if (PlayerZone == null) + /// + /// The tile coordinate on the X plane + /// + public int X { get; set; } + + /// + /// The tile coordinate on the Y plane + /// + public int Y { get; set; } + + /// + /// The Tile ID being edited. + /// + public short EditData { get; set; } + /// + /// The EditType. + /// (KillTile = 0, PlaceTile = 1, KillWall = 2, PlaceWall = 3, KillTileNoItem = 4, PlaceWire = 5, KillWire = 6) + /// + public EditAction Action { get; set; } + + /// + /// Did the tile get destroyed successfully. + /// + public EditType editDetail { get; set; } + + /// + /// Used when a tile is placed to denote a subtype of tile. (e.g. for tile id 21: Chest = 0, Gold Chest = 1) + /// + public byte Style { get; set; } + } + /// + /// TileEdit - called when a tile is placed or destroyed + /// + public static HandlerList TileEdit = new HandlerList(); + private static bool OnTileEdit(TSPlayer ply, MemoryStream data, int x, int y, EditAction action, EditType editDetail, short editData, byte style) + { + if (TileEdit == null) return false; - var args = new PlayerZoneEventArgs + var args = new TileEditEventArgs { - Player = player, + Player = ply, Data = data, - PlayerId = plr, - Zone1 = zone1, - Zone2 = zone2, - Zone3 = zone3, - Zone4 = zone4 + X = x, + Y = y, + Action = action, + EditData = editData, + editDetail = editDetail, + Style = style }; - PlayerZone.Invoke(null, args); + TileEdit.Invoke(null, args); return args.Handled; } - - /// The event args object for the HealOtherPlayer event - public class HealOtherPlayerEventArgs : GetDataHandledEventArgs - { - /// The Terraria player index of the target player - public byte TargetPlayerIndex { get; set; } - - /// The amount to heal by - public short Amount { get; set; } - } - - /// When a player heals another player - public static HandlerList HealOtherPlayer = new HandlerList(); - - /// Fires the HealOtherPlayer event - /// The TSPlayer that caused the event. - /// The MemoryStream containing the raw event data. - /// The Terraria player index that the event targets - /// The amount to heal - /// bool - private static bool OnHealOtherPlayer(TSPlayer player, MemoryStream data, byte targetPlayerIndex, short amount) - { - if (HealOtherPlayer == null) - return false; - - var args = new HealOtherPlayerEventArgs - { - Player = player, - Data = data, - TargetPlayerIndex = targetPlayerIndex, - Amount = amount, - }; - - HealOtherPlayer.Invoke(null, args); - return args.Handled; - } - + /// /// For use in a SendTileSquare event /// @@ -660,7 +467,6 @@ namespace TShockAPI /// When the player sends a tile square /// public static HandlerList SendTileSquare = new HandlerList(); - private static bool OnSendTileSquare(TSPlayer player, MemoryStream data, short size, int tilex, int tiley) { if (SendTileSquare == null) @@ -678,64 +484,67 @@ namespace TShockAPI SendTileSquare.Invoke(null, args); return args.Handled; } - - /// The arguments to the PlaceObject hook. - public class PlaceObjectEventArgs : GetDataHandledEventArgs + + /// + /// For use in an ItemDrop event + /// + public class ItemDropEventArgs : GetDataHandledEventArgs { - /// The X location where the object was placed. - public short X { get; set ; } - - /// The Y location where the object was placed. - public short Y { get; set; } - - /// The type of object that was placed. + /// + /// 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; } - - /// The style of the object was placed. - public short Style { get; set; } - - /// Alternate variation of the object placed. - public byte Alternate { get; set; } - - /// The direction the object was placed. - public bool Direction { get; set; } } - - /// Fired when an object is placed in the world. - public static HandlerList PlaceObject = new HandlerList(); - - /// Fires the PlaceObject hook. To be called when an object is placed in the world. - /// The TSPlayer that caused the event. - /// The MemoryStream containing the raw event data. - /// The x position where the object is placed. - /// The y position where the object is placed. - /// The type of object. - /// The object's style data. - /// The object's alternate data. - /// The direction of the object. - /// bool - private static bool OnPlaceObject(TSPlayer player, MemoryStream data, short x, short y, short type, short style, byte alternate, bool direction) + /// + /// ItemDrop - Called when an item is dropped + /// + public static HandlerList ItemDrop = new HandlerList(); + private static bool OnItemDrop(TSPlayer player, MemoryStream data, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type) { - if (PlaceObject == null) + if (ItemDrop == null) return false; - var args = new PlaceObjectEventArgs + var args = new ItemDropEventArgs { Player = player, Data = data, - X = x, - Y = y, + ID = id, + Position = pos, + Velocity = vel, + Stacks = stacks, + Prefix = prefix, + NoDelay = noDelay, Type = type, - Style = style, - Alternate = alternate, - Direction = direction }; - - PlaceObject.Invoke(null, args); + ItemDrop.Invoke(null, args); return args.Handled; } - - + /// /// For use in a NewProjectile event /// @@ -778,7 +587,6 @@ namespace TShockAPI /// NewProjectile - Called when a client creates a new projectile /// public static HandlerList NewProjectile = new HandlerList(); - private static bool OnNewProjectile(MemoryStream data, short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index, TSPlayer player) { if (NewProjectile == null) @@ -800,51 +608,127 @@ namespace TShockAPI NewProjectile.Invoke(null, args); return args.Handled; } - + /// - /// For use in a LiquidSet event + /// For use with a NPCStrike event /// - public class LiquidSetEventArgs : GetDataHandledEventArgs + public class NPCStrikeEventArgs : GetDataHandledEventArgs { /// - /// X location of the tile + /// ??? /// - public int TileX { get; set; } + public short ID { get; set; } /// - /// Y location of the tile + /// Direction the damage occurred from /// - public int TileY { get; set; } + public byte Direction { get; set; } /// - /// Amount of liquid + /// Amount of damage /// - public byte Amount { get; set; } + public short Damage { get; set; } /// - /// Type of Liquid: 0=water, 1=lave, 2=honey + /// Knockback /// - public byte Type { get; set; } + public float Knockback { get; set; } + /// + /// Critical? + /// + public byte Critical { get; set; } } /// - /// LiquidSet - When ever a liquid is set + /// NPCStrike - Called when an NPC is attacked /// - public static HandlerList LiquidSet = new HandlerList(); - - private static bool OnLiquidSet(TSPlayer player, MemoryStream data, int tilex, int tiley, byte amount, byte type) + public static HandlerList NPCStrike = new HandlerList(); + private static bool OnNPCStrike(TSPlayer player, MemoryStream data, short id, byte dir, short dmg, float knockback, byte crit) { - if (LiquidSet == null) + if (NPCStrike == null) return false; - var args = new LiquidSetEventArgs + var args = new NPCStrikeEventArgs { Player = player, Data = data, - TileX = tilex, - TileY = tiley, - Amount = amount, - Type = type, + ID = id, + Direction = dir, + Damage = dmg, + Knockback = knockback, + Critical = crit, }; - LiquidSet.Invoke(null, args); + NPCStrike.Invoke(null, args); return args.Handled; } + + /// The arguments to the ProjectileKill packet. + public class ProjectileKillEventArgs : GetDataHandledEventArgs + { + /// The projectile's identity...? + public int ProjectileIdentity; + /// The the player index of the projectile's owner (Main.players). + public byte ProjectileOwner; + /// The index of the projectile in Main.projectile. + public int ProjectileIndex; + } + /// The event fired when a projectile kill packet is received. + public static HandlerList ProjectileKill = new HandlerList(); + /// Fires the ProjectileKill event. + /// The TSPlayer that caused the event. + /// The MemoryStream containing the raw event data. + /// The projectile identity (from the packet). + /// The projectile's owner (from the packet). + /// The projectile's index (from Main.projectiles). + /// bool + private static bool OnProjectileKill(TSPlayer player, MemoryStream data, int identity, byte owner, int index) + { + if (ProjectileKill == null) + return false; + + var args = new ProjectileKillEventArgs + { + Player = player, + Data = data, + ProjectileIdentity = identity, + ProjectileOwner = owner, + ProjectileIndex = index, + }; + + ProjectileKill.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a TogglePvp event + /// + public class TogglePvpEventArgs : GetDataHandledEventArgs + { + /// + /// The Terraria player ID of the player + /// + public byte PlayerId { get; set; } + /// + /// Enable/disable pvp? + /// + public bool Pvp { get; set; } + } + /// + /// TogglePvp - called when a player toggles pvp + /// + public static HandlerList TogglePvp = new HandlerList(); + private static bool OnPvpToggled(TSPlayer player, MemoryStream data, byte _id, bool _pvp) + { + if (TogglePvp == null) + return false; + + var args = new TogglePvpEventArgs + { + Player = player, + Data = data, + PlayerId = _id, + Pvp = _pvp, + }; + TogglePvp.Invoke(null, args); + return args.Handled; + } + /// /// For use in a PlayerSpawn event /// @@ -867,7 +751,6 @@ namespace TShockAPI /// PlayerSpawn - When a player spawns /// public static HandlerList PlayerSpawn = new HandlerList(); - private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY) { if (PlayerSpawn == null) @@ -884,41 +767,7 @@ namespace TShockAPI PlayerSpawn.Invoke(null, args); return args.Handled; } - /// - /// For use with a ChestOpen event - /// - public class ChestOpenEventArgs : GetDataHandledEventArgs - { - /// - /// X location of said chest - /// - public int X { get; set; } - /// - /// Y location of said chest - /// - public int Y { get; set; } - } - /// - /// ChestOpen - Called when any chest is opened - /// - public static HandlerList ChestOpen = new HandlerList(); - - private static bool OnChestOpen(MemoryStream data, int x, int y, TSPlayer player) - { - if (ChestOpen == null) - return false; - - var args = new ChestOpenEventArgs - { - Data = data, - X = x, - Y = y, - Player = player, - }; - ChestOpen.Invoke(null, args); - return args.Handled; - } - + /// /// For use in a ChestItemChange event /// @@ -949,7 +798,6 @@ namespace TShockAPI /// ChestItemChange - Called when an item in a chest changes /// public static HandlerList ChestItemChange = new HandlerList(); - private static bool OnChestItemChange(TSPlayer player, MemoryStream data, short id, byte slot, short stacks, byte prefix, short type) { if (ChestItemChange == null) @@ -968,7 +816,213 @@ namespace TShockAPI ChestItemChange.Invoke(null, args); return args.Handled; } + + /// + /// For use with a ChestOpen event + /// + public class ChestOpenEventArgs : GetDataHandledEventArgs + { + /// + /// X location of said chest + /// + public int X { get; set; } + /// + /// Y location of said chest + /// + public int Y { get; set; } + } + /// + /// ChestOpen - Called when any chest is opened + /// + public static HandlerList ChestOpen = new HandlerList(); + private static bool OnChestOpen(MemoryStream data, int x, int y, TSPlayer player) + { + if (ChestOpen == null) + return false; + var args = new ChestOpenEventArgs + { + Data = data, + X = x, + Y = y, + Player = player, + }; + ChestOpen.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlaceChest event + /// + public class PlaceChestEventArgs : GetDataHandledEventArgs + { + /// What the packet is doing (see MP packet docs). + public int Flag { get; set; } + /// + /// The X coordinate + /// + public int TileX { get; set; } + /// + /// The Y coordinate + /// + public int TileY { get; set; } + } + /// + /// When a chest is added or removed from the world. + /// + public static HandlerList PlaceChest = new HandlerList(); + private static bool OnPlaceChest(TSPlayer player, MemoryStream data, int flag, int tilex, int tiley) + { + if (PlaceChest == null) + return false; + + var args = new PlaceChestEventArgs + { + Player = player, + Data = data, + Flag = flag, + TileX = tilex, + TileY = tiley, + }; + PlaceChest.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerZone event + /// + public class PlayerZoneEventArgs : GetDataHandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte PlayerId { get; set; } + /// + /// 0 = Dungeon, 1 = Corruption,2 =Holy, 3 = Meteor, 4 = Jungle, 5 = Snow, 6 = Crimson, 7 = Water Candle + /// + public BitsByte Zone1 { get; set; } + /// + /// 0 = Peace Candle, 1 = Solar Tower, 2 = Vortex Tower, 3 = Nebula Tower, 4 = Stardust Tower, 5 = Desert, 6 = Glowshroom, 7 = Underground Desert + /// + public BitsByte Zone2 { get; set; } + /// + /// 0 = Overworld, 1 = Dirt Layer, 2 = Rock Layer, 3 = Underworld, 4 = Beach, 5 = Rain, 6 = Sandstorm + /// + public BitsByte Zone3 { get; set; } + /// + /// 0 = Old One's Army + /// + public BitsByte Zone4 { get; set; } + } + /// + /// PlayerZone - When the player sends it's zone/biome information to the server + /// + public static HandlerList PlayerZone = new HandlerList(); + private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4) + { + if (PlayerZone == null) + return false; + + var args = new PlayerZoneEventArgs + { + Player = player, + Data = data, + PlayerId = plr, + Zone1 = zone1, + Zone2 = zone2, + Zone3 = zone3, + Zone4 = zone4 + }; + PlayerZone.Invoke(null, args); + return args.Handled; + } + + /// + /// For use with a PlayerAnimation event + /// + public class PlayerAnimationEventArgs : GetDataHandledEventArgs { } + /// + /// PlayerAnimation - Called when a player animates + /// + public static HandlerList PlayerAnimation = new HandlerList(); + private static bool OnPlayerAnimation(TSPlayer player, MemoryStream data) + { + if (PlayerAnimation == null) + return false; + + var args = new PlayerAnimationEventArgs + { + Player = player, + Data = data, + }; + PlayerAnimation.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerMana event + /// + public class PlayerManaEventArgs : GetDataHandledEventArgs + { + public byte PlayerId { get; set; } + public short Current { get; set; } + public short Max { get; set; } + } + /// + /// PlayerMana - called at a PlayerMana event + /// + public static HandlerList PlayerMana = new HandlerList(); + private static bool OnPlayerMana(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max) + { + if (PlayerMana == null) + return false; + + var args = new PlayerManaEventArgs + { + Player = player, + Data = data, + PlayerId = _plr, + Current = _cur, + Max = _max, + }; + PlayerMana.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerTeam event + /// + public class PlayerTeamEventArgs : GetDataHandledEventArgs + { + /// + /// The Terraria player ID of the player + /// + public byte PlayerId { get; set; } + /// + /// Enable/disable pvp? + /// + public byte Team { get; set; } + } + /// + /// TogglePvp - called when a player toggles pvp + /// + public static HandlerList PlayerTeam = new HandlerList(); + private static bool OnPlayerTeam(TSPlayer player, MemoryStream data, byte _id, byte _team) + { + if (PlayerTeam == null) + return false; + + var args = new PlayerTeamEventArgs + { + Player = player, + Data = data, + PlayerId = _id, + Team = _team, + }; + PlayerTeam.Invoke(null, args); + return args.Handled; + } + /// /// For use in a Sign event /// @@ -991,7 +1045,6 @@ namespace TShockAPI /// Sign - Called when a sign is changed /// public static HandlerList Sign = new HandlerList(); - private static bool OnSignEvent(TSPlayer player, MemoryStream data, short id, int x, int y) { if (Sign == null) @@ -1008,7 +1061,192 @@ namespace TShockAPI Sign.Invoke(null, args); return args.Handled; } + + /// + /// For use in a LiquidSet event + /// + public class LiquidSetEventArgs : GetDataHandledEventArgs + { + /// + /// X location of the tile + /// + public int TileX { get; set; } + /// + /// Y location of the tile + /// + public int TileY { get; set; } + /// + /// Amount of liquid + /// + public byte Amount { get; set; } + /// + /// Type of Liquid: 0=water, 1=lave, 2=honey + /// + public byte Type { get; set; } + } + /// + /// LiquidSet - When ever a liquid is set + /// + public static HandlerList LiquidSet = new HandlerList(); + private static bool OnLiquidSet(TSPlayer player, MemoryStream data, int tilex, int tiley, byte amount, byte type) + { + if (LiquidSet == null) + return false; + var args = new LiquidSetEventArgs + { + Player = player, + Data = data, + TileX = tilex, + TileY = tiley, + Amount = amount, + Type = type, + }; + LiquidSet.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerBuffUpdate event + /// + public class PlayerBuffUpdateEventArgs : GetDataHandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte ID { get; set; } + } + /// + /// PlayerBuffUpdate - Called when a player updates buffs + /// + public static HandlerList PlayerBuffUpdate = new HandlerList(); + private static bool OnPlayerBuffUpdate(TSPlayer player, MemoryStream data, byte id) + { + if (PlayerBuffUpdate == null) + return false; + + var args = new PlayerBuffUpdateEventArgs + { + Player = player, + Data = data, + ID = id, + }; + PlayerBuffUpdate.Invoke(null, args); + return args.Handled; + } + + /// + /// For use with a NPCSpecial event + /// + public class NPCSpecialEventArgs : GetDataHandledEventArgs + { + /// + /// ??? + /// + public byte ID { get; set; } + /// + /// Type...? + /// + public byte Type { get; set; } + } + /// + /// NPCSpecial - Called at some point + /// + public static HandlerList NPCSpecial = new HandlerList(); + private static bool OnNPCSpecial(TSPlayer player, MemoryStream data, byte id, byte type) + { + if (NPCSpecial == null) + return false; + + var args = new NPCSpecialEventArgs + { + Player = player, + Data = data, + ID = id, + Type = type, + }; + NPCSpecial.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a NPCAddBuff event + /// + public class NPCAddBuffEventArgs : GetDataHandledEventArgs + { + /// + /// The ID of the npc + /// + public short ID { get; set; } + /// + /// Buff Type + /// + public byte Type { get; set; } + /// + /// Time the buff lasts + /// + public short Time { get; set; } + } + /// + /// NPCAddBuff - Called when a npc is buffed + /// + public static HandlerList NPCAddBuff = new HandlerList(); + private static bool OnNPCAddBuff(TSPlayer player, MemoryStream data, short id, byte type, short time) + { + if (NPCAddBuff == null) + return false; + + var args = new NPCAddBuffEventArgs + { + Player = player, + Data = data, + ID = id, + Type = type, + Time = time + }; + NPCAddBuff.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerBuff event + /// + public class PlayerBuffEventArgs : GetDataHandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte ID { get; set; } + /// + /// Buff Type + /// + public byte Type { get; set; } + /// + /// Time the buff lasts + /// + public int Time { get; set; } + } + /// + /// PlayerBuff - Called when a player is buffed + /// + public static HandlerList PlayerBuff = new HandlerList(); + private static bool OnPlayerBuff(TSPlayer player, MemoryStream data, byte id, byte type, int time) + { + if (PlayerBuff == null) + return false; + + var args = new PlayerBuffEventArgs + { + Player = player, + Data = data, + ID = id, + Type = type, + Time = time + }; + PlayerBuff.Invoke(null, args); + return args.Handled; + } + /// /// For use in a NPCHome event /// @@ -1035,7 +1273,6 @@ namespace TShockAPI /// NPCHome - Called when an NPC's home is changed /// public static HandlerList NPCHome = new HandlerList(); - private static bool OnUpdateNPCHome(TSPlayer player, MemoryStream data, short id, short x, short y, byte homeless) { if (NPCHome == null) @@ -1053,145 +1290,397 @@ namespace TShockAPI NPCHome.Invoke(null, args); return args.Handled; } - + /// - /// For use in a NPCAddBuff event + /// For use with a PaintTile event /// - public class NPCAddBuffEventArgs : GetDataHandledEventArgs + public class PaintTileEventArgs : GetDataHandledEventArgs { /// - /// The ID of the npc + /// X Location /// - public short ID { get; set; } + public Int32 X { get; set; } /// - /// Buff Type + /// Y Location /// - public byte Type { get; set; } + public Int32 Y { get; set; } /// - /// Time the buff lasts + /// Type /// - public short Time { get; set; } + public byte type { get; set; } } /// - /// NPCAddBuff - Called when a npc is buffed + /// NPCStrike - Called when an NPC is attacked /// - public static HandlerList NPCAddBuff = new HandlerList(); - - private static bool OnNPCAddBuff(TSPlayer player, MemoryStream data, short id, byte type, short time) + public static HandlerList PaintTile = new HandlerList(); + private static bool OnPaintTile(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t) { - if (NPCAddBuff == null) + if (PaintTile == null) return false; - var args = new NPCAddBuffEventArgs + var args = new PaintTileEventArgs { Player = player, Data = data, - ID = id, - Type = type, - Time = time + X = x, + Y = y, + type = t }; - NPCAddBuff.Invoke(null, args); + PaintTile.Invoke(null, args); return args.Handled; } /// - /// For use in a PlayerBuff event + /// For use with a PaintWall event /// - public class PlayerBuffEventArgs : GetDataHandledEventArgs + public class PaintWallEventArgs : GetDataHandledEventArgs { /// - /// The Terraria playerID of the player + /// X Location /// - public byte ID { get; set; } + public Int32 X { get; set; } /// - /// Buff Type + /// Y Location /// - public byte Type { get; set; } + public Int32 Y { get; set; } /// - /// Time the buff lasts + /// Type /// - public int Time { get; set; } + public byte type { get; set; } } /// - /// PlayerBuff - Called when a player is buffed + /// Called When a wall is painted /// - public static HandlerList PlayerBuff = new HandlerList(); - - private static bool OnPlayerBuff(TSPlayer player, MemoryStream data, byte id, byte type, int time) + public static HandlerList PaintWall = new HandlerList(); + private static bool OnPaintWall(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t) { - if (PlayerBuff == null) + if (PaintWall == null) return false; - var args = new PlayerBuffEventArgs + var args = new PaintWallEventArgs { Player = player, Data = data, - ID = id, - Type = type, - Time = time + X = x, + Y = y, + type = t }; - PlayerBuff.Invoke(null, args); + PaintWall.Invoke(null, args); return args.Handled; } /// - /// For use in an ItemDrop event + /// For use with a NPCStrike event /// - public class ItemDropEventArgs : GetDataHandledEventArgs + public class TeleportEventArgs : 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; } + public Int16 ID { get; set; } /// - /// Position of the item + /// Flag is a bit field + /// if the first bit is set -> 0 = player, 1 = NPC + /// if the second bit is set, ignore this packet + /// if the third bit is set, style +1 + /// if the fourth bit is set, style +1 /// - public Vector2 Position { get; set; } + public byte Flag { get; set; } /// - /// Velocity at which the item is deployed + /// X Location /// - public Vector2 Velocity { get; set; } + public float X { 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 + /// Y Location /// + public float Y { get; set; } + } + /// + /// NPCStrike - Called when an NPC is attacked + /// + public static HandlerList Teleport = new HandlerList(); + private static bool OnTeleport(TSPlayer player, MemoryStream data, Int16 id, byte f, float x, float y) + { + if (Teleport == null) + return false; + + var args = new TeleportEventArgs + { + Player = player, + Data = data, + ID = id, + Flag = f, + X = x, + Y = y + }; + Teleport.Invoke(null, args); + return args.Handled; + } + + /// The event args object for the HealOtherPlayer event + public class HealOtherPlayerEventArgs : GetDataHandledEventArgs + { + /// The Terraria player index of the target player + public byte TargetPlayerIndex { get; set; } + + /// The amount to heal by + public short Amount { get; set; } + } + /// When a player heals another player + public static HandlerList HealOtherPlayer = new HandlerList(); + private static bool OnHealOtherPlayer(TSPlayer player, MemoryStream data, byte targetPlayerIndex, short amount) + { + if (HealOtherPlayer == null) + return false; + + var args = new HealOtherPlayerEventArgs + { + Player = player, + Data = data, + TargetPlayerIndex = targetPlayerIndex, + Amount = amount, + }; + + HealOtherPlayer.Invoke(null, args); + return args.Handled; + } + + /// The arguments to the PlaceObject hook. + public class PlaceObjectEventArgs : GetDataHandledEventArgs + { + /// The X location where the object was placed. + public short X { get; set; } + + /// The Y location where the object was placed. + public short Y { get; set; } + + /// The type of object that was placed. public short Type { get; set; } - } - /// - /// ItemDrop - Called when an item is dropped - /// - public static HandlerList ItemDrop = new HandlerList(); - private static bool OnItemDrop(TSPlayer player, MemoryStream data, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type) + /// The style of the object was placed. + public short Style { get; set; } + + /// Alternate variation of the object placed. + public byte Alternate { get; set; } + + /// The direction the object was placed. + public bool Direction { get; set; } + } + /// Fired when an object is placed in the world. + public static HandlerList PlaceObject = new HandlerList(); + private static bool OnPlaceObject(TSPlayer player, MemoryStream data, short x, short y, short type, short style, byte alternate, bool direction) { - if (ItemDrop == null) + if (PlaceObject == null) return false; - var args = new ItemDropEventArgs + var args = new PlaceObjectEventArgs { Player = player, Data = data, - ID = id, - Position = pos, - Velocity = vel, - Stacks = stacks, - Prefix = prefix, - NoDelay = noDelay, + X = x, + Y = y, Type = type, + Style = style, + Alternate = alternate, + Direction = direction }; - ItemDrop.Invoke(null, args); + + PlaceObject.Invoke(null, args); + return args.Handled; + } + + /// For use in a PlaceTileEntity event. + public class PlaceTileEntityEventArgs : GetDataHandledEventArgs + { + /// The X coordinate of the event. + public short X { get; set; } + + /// The Y coordinate of the event. + public short Y { get; set; } + + /// The Type of event. + public byte Type { get; set; } + } + /// Fired when a PlaceTileEntity event occurs. + public static HandlerList PlaceTileEntity = new HandlerList(); + private static bool OnPlaceTileEntity(TSPlayer player, MemoryStream data, short x, short y, byte type) + { + if (PlaceTileEntity == null) + return false; + + var args = new PlaceTileEntityEventArgs + { + Player = player, + Data = data, + X = x, + Y = y, + Type = type + }; + + PlaceTileEntity.Invoke(null, args); + return args.Handled; + } + + /// The arguments to the PlaceItemFrame event. + public class PlaceItemFrameEventArgs : GetDataHandledEventArgs + { + /// The X coordinate of the item frame. + public short X { get; set; } + + /// The Y coordinate of the item frame. + public short Y { get; set; } + + /// The ItemID of the item frame. + 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 TEItemFrame ItemFrame { get; set; } + } + /// Fired when an ItemFrame is placed. + public static HandlerList PlaceItemFrame = new HandlerList(); + private static bool OnPlaceItemFrame(TSPlayer player, MemoryStream data, short x, short y, short itemID, byte prefix, short stack, TEItemFrame itemFrame) + { + if (PlaceItemFrame == null) + return false; + + var args = new PlaceItemFrameEventArgs + { + Player = player, + Data = data, + X = x, + Y = y, + ItemID = itemID, + Prefix = prefix, + Stack = stack, + ItemFrame = itemFrame, + }; + + PlaceItemFrame.Invoke(null, args); + return args.Handled; + } + + /// The event args object for the PortalTeleport event + public class TeleportThroughPortalEventArgs : GetDataHandledEventArgs + { + /// The Terraria player index of the target player + public byte TargetPlayerIndex { get; set; } + + /// + /// The position the target player will be at after going through the portal + /// + public Vector2 NewPosition { get; set; } + + /// + /// The velocity the target player will have after going through the portal + /// + public Vector2 NewVelocity { get; set; } + + /// + /// Index of the portal's color (for use with ) + /// + public int PortalColorIndex { get; set; } + } + /// When a player passes through a portal + public static HandlerList PortalTeleport = new HandlerList(); + private static bool OnPlayerTeleportThroughPortal(TSPlayer sender, byte targetPlayerIndex, MemoryStream data, Vector2 position, Vector2 velocity, int colorIndex) + { + TeleportThroughPortalEventArgs args = new TeleportThroughPortalEventArgs + { + TargetPlayerIndex = targetPlayerIndex, + Data = data, + Player = sender, + NewPosition = position, + NewVelocity = velocity, + PortalColorIndex = colorIndex + }; + + PortalTeleport.Invoke(null, args); + + return args.Handled; + } + + /// + /// For use with a ToggleGemLock event + /// + public class GemLockToggleEventArgs : GetDataHandledEventArgs + { + /// + /// X Location + /// + public short X { get; set; } + /// + /// Y Location + /// + public short Y { get; set; } + /// + /// On status + /// + public bool On { get; set; } + } + /// + /// GemLockToggle - Called when a gem lock is switched + /// + public static HandlerList GemLockToggle = new HandlerList(); + private static bool OnGemLockToggle(TSPlayer player, MemoryStream data, short x, short y, bool on) + { + if (GemLockToggle == null) + return false; + + var args = new GemLockToggleEventArgs + { + Player = player, + Data = data, + X = x, + Y = y, + On = on + }; + GemLockToggle.Invoke(null, args); + return args.Handled; + } + + /// The arguments to the MassWireOperation event. + public class MassWireOperationEventArgs : GetDataHandledEventArgs + { + /// The start X point in the operation. + public short StartX { get; set; } + + /// The start Y point in the operation. + public short StartY { get; set; } + + /// The end X point in the operation. + public short EndX { get; set; } + + /// The end Y point in the operation. + public short EndY { get; set; } + + /// ToolMode + public byte ToolMode { get; set; } + } + /// Fired on a mass wire edit operation. + public static HandlerList MassWireOperation = new HandlerList(); + private static bool OnMassWireOperation(TSPlayer player, MemoryStream data, short startX, short startY, short endX, short endY, byte toolMode) + { + if (MassWireOperation == null) + return false; + + var args = new MassWireOperationEventArgs + { + Player = player, + Data = data, + StartX = startX, + StartY = startY, + EndX = endX, + EndY = endY, + ToolMode = toolMode, + }; + + MassWireOperation.Invoke(null, args); return args.Handled; } @@ -1227,7 +1716,6 @@ namespace TShockAPI /// PlayerDamage - Called when a player is damaged /// public static HandlerList PlayerDamage = new HandlerList(); - private static bool OnPlayerDamage(TSPlayer player, MemoryStream data, byte id, byte dir, short dmg, bool pvp, bool crit, PlayerDeathReason playerDeathReason) { if (PlayerDamage == null) @@ -1249,535 +1737,54 @@ namespace TShockAPI } /// - /// For use with a NPCStrike event + /// For use in a KillMe event /// - public class NPCStrikeEventArgs : GetDataHandledEventArgs - { - /// - /// ??? - /// - public short ID { get; set; } - /// - /// Direction the damage occurred from - /// - public byte Direction { get; set; } - /// - /// Amount of damage - /// - public short Damage { get; set; } - /// - /// Knockback - /// - public float Knockback { get; set; } - /// - /// Critical? - /// - public byte Critical { get; set; } - } - /// - /// NPCStrike - Called when an NPC is attacked - /// - public static HandlerList NPCStrike = new HandlerList(); - - private static bool OnNPCStrike(TSPlayer player, MemoryStream data, short id, byte dir, short dmg, float knockback, byte crit) - { - if (NPCStrike == null) - return false; - - var args = new NPCStrikeEventArgs - { - Player = player, - Data = data, - ID = id, - Direction = dir, - Damage = dmg, - Knockback = knockback, - Critical = crit, - }; - NPCStrike.Invoke(null, args); - return args.Handled; - } - - /// The arguments to the MassWireOperation event. - public class MassWireOperationEventArgs : GetDataHandledEventArgs - { - /// The start X point in the operation. - public short StartX { get; set; } - - /// The start Y point in the operation. - public short StartY { get; set; } - - /// The end X point in the operation. - public short EndX { get; set; } - - /// The end Y point in the operation. - public short EndY { get; set; } - - /// ToolMode - public byte ToolMode { get; set; } - } - - /// Fired on a mass wire edit operation. - public static HandlerList MassWireOperation = new HandlerList(); - - private static bool OnMassWireOperation(TSPlayer player, MemoryStream data, short startX, short startY, short endX, short endY, byte toolMode) - { - if (MassWireOperation == null) - return false; - - var args = new MassWireOperationEventArgs - { - Player = player, - Data = data, - StartX = startX, - StartY = startY, - EndX = endX, - EndY = endY, - ToolMode = toolMode, - }; - - MassWireOperation.Invoke(null, args); - return args.Handled; - } - - /// For use in a PlaceTileEntity event. - public class PlaceTileEntityEventArgs : GetDataHandledEventArgs - { - /// The X coordinate of the event. - public short X { get; set; } - - /// The Y coordinate of the event. - public short Y { get; set; } - - /// The Type of event. - public byte Type { get; set; } - } - - /// Fired when a PlaceTileEntity event occurs. - public static HandlerList PlaceTileEntity = new HandlerList(); - - private static bool OnPlaceTileEntity(TSPlayer player, MemoryStream data, short x, short y, byte type) - { - if (PlaceTileEntity == null) - return false; - - var args = new PlaceTileEntityEventArgs - { - Player = player, - Data = data, - X = x, - Y = y, - Type = type - }; - - PlaceTileEntity.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a NPCSpecial event - /// - public class NPCSpecialEventArgs : GetDataHandledEventArgs - { - /// - /// ??? - /// - public byte ID { get; set; } - /// - /// Type...? - /// - public byte Type { get; set; } - } - /// - /// NPCSpecial - Called at some point - /// - public static HandlerList NPCSpecial = new HandlerList(); - - private static bool OnNPCSpecial(TSPlayer player, MemoryStream data, byte id, byte type) - { - if (NPCSpecial == null) - return false; - - var args = new NPCSpecialEventArgs - { - Player = player, - Data = data, - ID = id, - Type = type, - }; - NPCSpecial.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a PlayerAnimation event - /// - public class PlayerAnimationEventArgs : GetDataHandledEventArgs { } - - /// - /// PlayerAnimation - Called when a player animates - /// - public static HandlerList PlayerAnimation = new HandlerList(); - - private static bool OnPlayerAnimation(TSPlayer player, MemoryStream data) - { - if (PlayerAnimation == null) - return false; - - var args = new PlayerAnimationEventArgs - { - Player = player, - Data = data, - }; - PlayerAnimation.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerBuffUpdate event - /// - public class PlayerBuffUpdateEventArgs : GetDataHandledEventArgs + public class KillMeEventArgs : GetDataHandledEventArgs { /// /// The Terraria playerID of the player /// - public byte ID { get; set; } + public byte PlayerId { get; set; } + /// + /// The direction the damage is coming from (?) + /// + public byte Direction { get; set; } + /// + /// Amount of damage delt + /// + public short Damage { get; set; } + /// + /// Player's current pvp setting + /// + public bool Pvp { get; set; } + /// The reason the player died. + public PlayerDeathReason PlayerDeathReason { get; set; } } /// - /// PlayerBuffUpdate - Called when a player updates buffs + /// KillMe - Terraria's crappy way of handling damage from players /// - public static HandlerList PlayerBuffUpdate = new HandlerList(); - - private static bool OnPlayerBuffUpdate(TSPlayer player, MemoryStream data, byte id) + public static HandlerList KillMe = new HandlerList(); + private static bool OnKillMe(TSPlayer player, MemoryStream data, byte plr, byte direction, short damage, bool pvp, PlayerDeathReason playerDeathReason) { - if (PlayerBuffUpdate == null) + if (KillMe == null) return false; - var args = new PlayerBuffUpdateEventArgs + var args = new KillMeEventArgs { Player = player, Data = data, - ID = id, + PlayerId = plr, + Direction = direction, + Damage = damage, + Pvp = pvp, + PlayerDeathReason = playerDeathReason, }; - PlayerBuffUpdate.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a NPCStrike event - /// - public class TeleportEventArgs : GetDataHandledEventArgs - { - /// - /// ??? - /// - public Int16 ID { get; set; } - /// - /// Flag is a bit field - /// if the first bit is set -> 0 = player, 1 = NPC - /// if the second bit is set, ignore this packet - /// if the third bit is set, style +1 - /// if the fourth bit is set, style +1 - /// - public byte Flag { get; set; } - /// - /// X Location - /// - public float X { get; set; } - /// - /// Y Location - /// - public float Y { get; set; } - } - /// - /// NPCStrike - Called when an NPC is attacked - /// - public static HandlerList Teleport = new HandlerList(); - - private static bool OnTeleport(TSPlayer player, MemoryStream data, Int16 id, byte f, float x, float y) - { - if (Teleport == null) - return false; - - var args = new TeleportEventArgs - { - Player = player, - Data = data, - ID = id, - Flag = f, - X = x, - Y = y - }; - Teleport.Invoke(null, args); + KillMe.Invoke(null, args); return args.Handled; } #endregion - 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 }, - { PacketTypes.PlayerUpdate, HandlePlayerUpdate }, - { PacketTypes.Zones, HandlePlayerZone }, - { PacketTypes.Tile, HandleTile }, - { PacketTypes.PlaceObject, HandlePlaceObject }, - { PacketTypes.TileSendSquare, HandleSendTileSquare }, - { PacketTypes.ProjectileNew, HandleProjectileNew }, - { PacketTypes.TogglePvp, HandleTogglePvp }, - { PacketTypes.PlayerTeam, HandlePlayerTeam }, - { PacketTypes.PlaceChest, HandlePlaceChest }, - { PacketTypes.LiquidSet, HandleLiquidSet }, - { PacketTypes.PlayerSpawn, HandleSpawn }, - { PacketTypes.ChestGetContents, HandleChestOpen }, - { PacketTypes.ChestOpen, HandleChestActive }, - { PacketTypes.ChestItem, HandleChestItem }, - { PacketTypes.SignNew, HandleSign }, - { PacketTypes.PlayerSlot, HandlePlayerSlot }, - { PacketTypes.TileGetSection, HandleGetSection }, - { PacketTypes.UpdateNPCHome, UpdateNPCHome }, - { PacketTypes.NpcAddBuff, HandleNPCAddBuff }, - { PacketTypes.PlayerAddBuff, HandlePlayerAddBuff }, - { PacketTypes.ItemDrop, HandleItemDrop }, - { PacketTypes.UpdateItemDrop, HandleItemDrop }, - { PacketTypes.ItemOwner, HandleItemOwner }, - { PacketTypes.PlayerHp, HandlePlayerHp }, - { PacketTypes.PlayerMana, HandlePlayerMana }, - { PacketTypes.NpcStrike, HandleNpcStrike }, - { PacketTypes.NpcSpecial, HandleSpecial }, - { PacketTypes.PlayerAnimation, HandlePlayerAnimation }, - { PacketTypes.PlayerBuff, HandlePlayerBuffList }, - { PacketTypes.PasswordSend, HandlePassword }, - { PacketTypes.ContinueConnecting2, HandleConnecting }, - { PacketTypes.ProjectileDestroy, HandleProjectileKill }, - { PacketTypes.SpawnBossorInvasion, HandleSpawnBoss }, - { PacketTypes.Teleport, HandleTeleport }, - { PacketTypes.PaintTile, HandlePaintTile }, - { PacketTypes.PaintWall, HandlePaintWall }, - { PacketTypes.DoorUse, HandleDoorUse }, - { PacketTypes.CompleteAnglerQuest, HandleCompleteAnglerQuest }, - { PacketTypes.NumberOfAnglerQuestsCompleted, HandleNumberOfAnglerQuestsCompleted }, - { PacketTypes.MassWireOperation, HandleMassWireOperation }, - { PacketTypes.GemLockToggle, HandleGemLockToggle }, - { PacketTypes.CatchNPC, HandleCatchNpc }, - { PacketTypes.NpcTeleportPortal, HandleNpcTeleportPortal }, - { PacketTypes.KillPortal, HandleKillPortal }, - { PacketTypes.PlaceTileEntity, HandlePlaceTileEntity }, - { PacketTypes.PlaceItemFrame, HandlePlaceItemFrame }, - { PacketTypes.SyncExtraValue, HandleSyncExtraValue }, - { PacketTypes.LoadNetModule, HandleLoadNetModule }, - { PacketTypes.ToggleParty, HandleToggleParty }, - { PacketTypes.PlayerHealOther, HandleHealOther }, - { PacketTypes.CrystalInvasionStart, HandleOldOnesArmy }, - { PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 }, - { PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 }, - { PacketTypes.PlayerTeleportPortal, HandlePlayerPortalTeleport } - }; - } - - public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data) - { - GetDataHandlerDelegate handler; - if (GetDataHandlerDelegates.TryGetValue(type, out handler)) - { - try - { - return handler(new GetDataHandlerArgs(player, data)); - } - catch (Exception ex) - { - TShock.Log.Error(ex.ToString()); - return true; - } - } - return false; - } - - /// The event args object for the PortalTeleport event - public class TeleportThroughPortalEventArgs : GetDataHandledEventArgs - { - /// The Terraria player index of the target player - public byte TargetPlayerIndex { get; set; } - - /// - /// The position the target player will be at after going through the portal - /// - public Vector2 NewPosition { get; set; } - - /// - /// The velocity the target player will have after going through the portal - /// - public Vector2 NewVelocity { get; set; } - - /// - /// Index of the portal's color (for use with ) - /// - public int PortalColorIndex { get; set; } - } - - /// When a player passes through a portal - public static HandlerList PortalTeleport = new HandlerList(); - - private static bool OnPlayerTeleportThroughPortal(TSPlayer sender, byte targetPlayerIndex, MemoryStream data, Vector2 position, Vector2 velocity, int colorIndex) - { - TeleportThroughPortalEventArgs args = new TeleportThroughPortalEventArgs - { - TargetPlayerIndex = targetPlayerIndex, - Data = data, - Player = sender, - NewPosition = position, - NewVelocity = velocity, - PortalColorIndex = colorIndex - }; - - PortalTeleport.Invoke(null, args); - - return args.Handled; - } - - private static bool HandlePlayerPortalTeleport(GetDataHandlerArgs args) - { - byte plr = args.Data.ReadInt8(); - short portalColorIndex = args.Data.ReadInt16(); - float newPositionX = args.Data.ReadSingle(); - float newPositionY = args.Data.ReadSingle(); - float newVelocityX = args.Data.ReadSingle(); - float newVelocityY = args.Data.ReadSingle(); - - return OnPlayerTeleportThroughPortal( - args.Player, - plr, - args.Data, - new Vector2(newPositionX, newPositionY), - new Vector2(newVelocityX, newVelocityY), - portalColorIndex - ); - } - - private static bool HandleHealOther(GetDataHandlerArgs args) - { - byte plr = args.Data.ReadInt8(); - short amount = args.Data.ReadInt16(); - - if (OnHealOtherPlayer(args.Player, args.Data, plr, amount)) - return true; - - return false; - } - - private static bool HandlePlayerSlot(GetDataHandlerArgs args) - { - byte plr = args.Data.ReadInt8(); - byte slot = args.Data.ReadInt8(); - short stack = args.Data.ReadInt16(); - byte prefix = args.Data.ReadInt8(); - short type = args.Data.ReadInt16(); - - // Players send a slot update packet for each inventory slot right after they've joined. - bool bypassTrashCanCheck = false; - if (plr == args.Player.Index && !args.Player.HasSentInventory && slot == NetItem.MaxInventory) - { - args.Player.HasSentInventory = true; - bypassTrashCanCheck = true; - } - - if (OnPlayerSlot(args.Player, args.Data, plr, slot, stack, prefix, type) || plr != args.Player.Index || slot < 0 || - slot > NetItem.MaxInventory) - return true; - if (args.Player.IgnoreSSCPackets) - { - args.Player.SendData(PacketTypes.PlayerSlot, "", args.Player.Index, slot, prefix); - return true; - } - - // Garabage? Or will it cause some internal initialization or whatever? - var item = new Item(); - item.netDefaults(type); - item.Prefix(prefix); - - if (args.Player.IsLoggedIn) - { - args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); - } - else if (Main.ServerSideCharacter && TShock.Config.DisableLoginBeforeJoin && !bypassTrashCanCheck && - args.Player.HasSentInventory && !args.Player.HasPermission(Permissions.bypassssc)) - { - // The player might have moved an item to their trash can before they performed a single login attempt yet. - args.Player.IsDisabledPendingTrashRemoval = true; - } - - if (slot == 58) //this is the hand - { - item.stack = stack; - args.Player.ItemInHand = item; - } - - return false; - } - - public static bool HandlePlayerHp(GetDataHandlerArgs args) - { - var plr = args.Data.ReadInt8(); - var cur = args.Data.ReadInt16(); - var max = args.Data.ReadInt16(); - - if (OnPlayerHP(args.Player, args.Data, plr, cur, max) || cur <= 0 || max <= 0 || args.Player.IgnoreSSCPackets) - return true; - - if (max > TShock.Config.MaxHP && !args.Player.HasPermission(Permissions.ignorehp)) - { - args.Player.Disable("Maximum HP beyond limit", DisableFlags.WriteToLogAndConsole); - return true; - } - - if (args.Player.IsLoggedIn) - { - args.Player.TPlayer.statLife = cur; - args.Player.TPlayer.statLifeMax = max; - args.Player.PlayerData.maxHealth = max; - } - - if (args.Player.GodMode && (cur < max)) - { - args.Player.Heal(args.TPlayer.statLifeMax2); - } - return false; - } - - private static bool HandlePlayerMana(GetDataHandlerArgs args) - { - var plr = args.Data.ReadInt8(); - var cur = args.Data.ReadInt16(); - var max = args.Data.ReadInt16(); - - if (OnPlayerMana(args.Player, args.Data, plr, cur, max) || cur < 0 || max < 0 || args.Player.IgnoreSSCPackets) - return true; - - if (max > TShock.Config.MaxMP && !args.Player.HasPermission(Permissions.ignoremp)) - { - args.Player.Disable("Maximum MP beyond limit", DisableFlags.WriteToLogAndConsole); - return true; - } - - if (args.Player.IsLoggedIn) - { - args.Player.TPlayer.statMana = cur; - args.Player.TPlayer.statManaMax = max; - args.Player.PlayerData.maxMana = max; - } - return false; - } - + private static bool HandlePlayerInfo(GetDataHandlerArgs args) { byte playerid = args.Data.ReadInt8(); @@ -1863,6 +1870,56 @@ namespace TShockAPI return false; } + private static bool HandlePlayerSlot(GetDataHandlerArgs args) + { + byte plr = args.Data.ReadInt8(); + byte slot = args.Data.ReadInt8(); + short stack = args.Data.ReadInt16(); + byte prefix = args.Data.ReadInt8(); + short type = args.Data.ReadInt16(); + + // Players send a slot update packet for each inventory slot right after they've joined. + bool bypassTrashCanCheck = false; + if (plr == args.Player.Index && !args.Player.HasSentInventory && slot == NetItem.MaxInventory) + { + args.Player.HasSentInventory = true; + bypassTrashCanCheck = true; + } + + if (OnPlayerSlot(args.Player, args.Data, plr, slot, stack, prefix, type) || plr != args.Player.Index || slot < 0 || + slot > NetItem.MaxInventory) + return true; + if (args.Player.IgnoreSSCPackets) + { + args.Player.SendData(PacketTypes.PlayerSlot, "", args.Player.Index, slot, prefix); + return true; + } + + // Garabage? Or will it cause some internal initialization or whatever? + var item = new Item(); + item.netDefaults(type); + item.Prefix(prefix); + + if (args.Player.IsLoggedIn) + { + args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); + } + else if (Main.ServerSideCharacter && TShock.Config.DisableLoginBeforeJoin && !bypassTrashCanCheck && + args.Player.HasSentInventory && !args.Player.HasPermission(Permissions.bypassssc)) + { + // The player might have moved an item to their trash can before they performed a single login attempt yet. + args.Player.IsDisabledPendingTrashRemoval = true; + } + + if (slot == 58) //this is the hand + { + item.stack = stack; + args.Player.ItemInHand = item; + } + + return false; + } + private static bool HandleConnecting(GetDataHandlerArgs args) { var account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name); @@ -1928,126 +1985,14 @@ namespace TShockAPI NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index); return true; } - - private static bool HandlePassword(GetDataHandlerArgs args) - { - if (!args.Player.RequiresPassword) - return true; - - string password = args.Data.ReadString(); - - if (Hooks.PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, password)) - return true; - - var account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name); - if (account != null && !TShock.Config.DisableLoginBeforeJoin) - { - if (account.VerifyPassword(password)) - { - args.Player.RequiresPassword = false; - args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID); - - if (args.Player.State == 1) - args.Player.State = 2; - NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index); - - var group = TShock.Groups.GetGroupByName(account.Group); - - args.Player.Group = group; - args.Player.tempGroup = null; - args.Player.Account = account; - args.Player.IsLoggedIn = true; - args.Player.IsDisabledForSSC = false; - - if (Main.ServerSideCharacter) - { - if (args.Player.HasPermission(Permissions.bypassssc)) - { - args.Player.PlayerData.CopyCharacter(args.Player); - TShock.CharacterDB.InsertPlayerData(args.Player); - } - args.Player.PlayerData.RestoreCharacter(args.Player); - } - args.Player.LoginFailsBySsi = false; - - if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) - args.Player.IsDisabledForStackDetection = false; - - if (args.Player.HasPermission(Permissions.usebanneditem)) - args.Player.IsDisabledForBannedWearable = false; - - - args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); - TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); - TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID); - Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); - return true; - } - args.Player.Kick("Your password did not match this character's password.", true, true); - return true; - } - - if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) - { - if (TShock.Config.ServerPassword == password) - { - args.Player.RequiresPassword = false; - if (args.Player.State == 1) - args.Player.State = 2; - NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index); - return true; - } - args.Player.Kick("Invalid server password.", true, true); - return true; - } - - args.Player.Kick("You have been Bounced.", true, true); - return true; - } - - /// The arguments to a GetSection packet. - public class GetSectionEventArgs : GetDataHandledEventArgs - { - /// The X position requested. Or -1 for spawn. - public int X { get; set; } - - /// The Y position requested. Or -1 for spawn. - public int Y { get; set; } - } - - /// The hook for a GetSection event. - public static HandlerList GetSection = new HandlerList(); - - /// Fires a GetSection event. - /// The TSPlayer that caused the GetSection. - /// The raw MP protocol data. - /// The x coordinate requested or -1 for spawn. - /// The y coordinate requested or -1 for spawn. - /// bool - private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y) - { - if (GetSection == null) - return false; - - var args = new GetSectionEventArgs - { - Player = player, - Data = data, - X = x, - Y = y, - }; - - GetSection.Invoke(null, args); - return args.Handled; - } - + private static bool HandleGetSection(GetDataHandlerArgs args) { if (OnGetSection(args.Player, args.Data, args.Data.ReadInt32(), args.Data.ReadInt32())) return true; if (TShock.Utils.GetActivePlayerCount() + 1 > TShock.Config.MaxSlots && - !args.Player.HasPermission(Permissions.reservedslot)) + !args.Player.HasPermission(Permissions.reservedslot)) { args.Player.Kick(TShock.Config.ServerFullReason, true, true); return true; @@ -2056,259 +2001,33 @@ namespace TShockAPI NetMessage.SendData((int)PacketTypes.TimeSet, -1, -1, NetworkText.Empty, Main.dayTime ? 1 : 0, (int)Main.time, Main.sunModY, Main.moonModY); return false; } - - private static bool HandleSendTileSquare(GetDataHandlerArgs args) + + private static bool HandleSpawn(GetDataHandlerArgs args) { - var player = args.Player; - var size = args.Data.ReadInt16(); - var tileX = args.Data.ReadInt16(); - var tileY = args.Data.ReadInt16(); - var data = args.Data; + var player = args.Data.ReadInt8(); + var spawnx = args.Data.ReadInt16(); + var spawny = args.Data.ReadInt16(); - if (OnSendTileSquare(player, data, size, tileX, tileY)) + if (OnPlayerSpawn(args.Player, args.Data, player, spawnx, spawny)) return true; - return false; - } - - public enum EditAction - { - KillTile = 0, - PlaceTile, - KillWall, - PlaceWall, - KillTileNoItem, - PlaceWire, - KillWire, - PoundTile, - PlaceActuator, - KillActuator, - PlaceWire2, - KillWire2, - PlaceWire3, - KillWire3, - SlopeTile, - FrameTrack, - PlaceWire4, - KillWire4 - } - public enum EditType - { - Fail = 0, - Type, - Slope, - } - - /// - /// Tiles that can be broken without any pickaxes/etc. - /// - internal static int[] breakableTiles = new int[] - { - TileID.Books, - TileID.Bottles, - TileID.BreakableIce, - TileID.Candles, - TileID.CorruptGrass, - TileID.Dirt, - TileID.FleshGrass, - TileID.Grass, - TileID.HallowedGrass, - TileID.MagicalIceBlock, - TileID.Mannequin, - TileID.Torches, - TileID.WaterCandle, - TileID.Womannequin, - }; - /// - /// The maximum place styles for each tile. - /// - public static Dictionary MaxPlaceStyles = new Dictionary(); - /// - /// These projectiles create tiles on death. - /// - internal static Dictionary projectileCreatesTile = new Dictionary - { - { ProjectileID.DirtBall, TileID.Dirt }, - { ProjectileID.SandBallGun, TileID.Sand }, - { ProjectileID.EbonsandBallGun, TileID.Ebonsand }, - { ProjectileID.PearlSandBallGun, TileID.Pearlsand }, - { ProjectileID.CrimsandBallGun, TileID.Crimsand }, - }; - - internal static Dictionary ropeCoilPlacements = new Dictionary - { - {ItemID.RopeCoil, TileID.Rope}, - {ItemID.SilkRopeCoil, TileID.SilkRope}, - {ItemID.VineRopeCoil, TileID.VineRope}, - {ItemID.WebRopeCoil, TileID.WebRope} - }; - - /// - /// Extra place style limits for strange hardcoded values in Terraria - /// - internal static Dictionary ExtraneousPlaceStyles = new Dictionary - { - {TileID.MinecartTrack, 3} - }; - - private static bool HandleTile(GetDataHandlerArgs args) - { - EditAction action = (EditAction)args.Data.ReadInt8(); - var tileX = args.Data.ReadInt16(); - var tileY = args.Data.ReadInt16(); - var editData = args.Data.ReadInt16(); - EditType type = (action == EditAction.KillTile || action == EditAction.KillWall || - action == EditAction.KillTileNoItem) - ? EditType.Fail - : (action == EditAction.PlaceTile || action == EditAction.PlaceWall) - ? EditType.Type - : EditType.Slope; - - var style = args.Data.ReadInt8(); - - if (OnTileEdit(args.Player, args.Data, tileX, tileY, action, type, editData, style)) - return true; - - return false; - } - - /// - /// Handle PlaceObject event - /// - private static bool HandlePlaceObject(GetDataHandlerArgs args) - { - short x = args.Data.ReadInt16(); - short y = args.Data.ReadInt16(); - short type = args.Data.ReadInt16(); - short style = args.Data.ReadInt16(); - byte alternate = args.Data.ReadInt8(); - bool direction = args.Data.ReadBoolean(); - - if (OnPlaceObject(args.Player, args.Data, x, y, type, style, alternate, direction)) - return true; - - return false; - } - - /// - /// For use with a PaintTile event - /// - public class PaintTileEventArgs : GetDataHandledEventArgs - { - /// - /// X Location - /// - public Int32 X { get; set; } - /// - /// Y Location - /// - public Int32 Y { get; set; } - /// - /// Type - /// - public byte type { get; set; } - } - /// - /// NPCStrike - Called when an NPC is attacked - /// - public static HandlerList PaintTile = new HandlerList(); - - private static bool OnPaintTile(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t) - { - if (PaintTile == null) - return false; - - var args = new PaintTileEventArgs + if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0) && (args.TPlayer.SpawnX > 0) && ((args.TPlayer.SpawnX != args.Player.sX) && (args.TPlayer.SpawnY != args.Player.sY))) { - Player = player, - Data = data, - X = x, - Y = y, - type = t - }; - PaintTile.Invoke(null, args); - return args.Handled; - } + args.Player.sX = args.TPlayer.SpawnX; + args.Player.sY = args.TPlayer.SpawnY; - /// - /// For use with a PaintWall event - /// - public class PaintWallEventArgs : GetDataHandledEventArgs - { - /// - /// X Location - /// - public Int32 X { get; set; } - /// - /// Y Location - /// - public Int32 Y { get; set; } - /// - /// Type - /// - public byte type { get; set; } - } - /// - /// Called When a wall is painted - /// - public static HandlerList PaintWall = new HandlerList(); - - private static bool OnPaintWall(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t) - { - if (PaintWall == null) - return false; - - var args = new PaintWallEventArgs - { - Player = player, - Data = data, - X = x, - Y = y, - type = t - }; - PaintWall.Invoke(null, args); - return args.Handled; - } - - private static bool HandleTogglePvp(GetDataHandlerArgs args) - { - byte id = args.Data.ReadInt8(); - bool pvp = args.Data.ReadBoolean(); - if (OnPvpToggled(args.Player, args.Data, id, pvp)) - return true; - - if (id != args.Player.Index) - return true; - - string pvpMode = TShock.Config.PvPMode.ToLowerInvariant(); - if (pvpMode == "disabled" || pvpMode == "always" || (DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5) - { - args.Player.SendData(PacketTypes.TogglePvp, "", id); - return true; + if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) + args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); } - args.Player.LastPvPTeamChange = DateTime.UtcNow; - return false; - } - - private static bool HandlePlayerTeam(GetDataHandlerArgs args) - { - byte id = args.Data.ReadInt8(); - byte team = args.Data.ReadInt8(); - if (OnPlayerTeam(args.Player, args.Data, id, team)) - return true; - - if (id != args.Player.Index) - return true; - - if ((DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5) + else if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0)) { - args.Player.SendData(PacketTypes.PlayerTeam, "", id); - return true; + if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) + args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); } - args.Player.LastPvPTeamChange = DateTime.UtcNow; + args.Player.Dead = false; return false; } @@ -2453,241 +2172,78 @@ namespace TShockAPI return true; } - private static bool HandlePlayerZone(GetDataHandlerArgs args) + private static bool HandlePlayerHp(GetDataHandlerArgs args) { - if (args.Player == null || args.TPlayer == null || args.Data == null) - { - return true; - } - var plr = args.Data.ReadInt8(); - BitsByte zone1 = args.Data.ReadInt8(); - BitsByte zone2 = args.Data.ReadInt8(); - BitsByte zone3 = args.Data.ReadInt8(); - BitsByte zone4 = args.Data.ReadInt8(); + var cur = args.Data.ReadInt16(); + var max = args.Data.ReadInt16(); - if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4)) + if (OnPlayerHP(args.Player, args.Data, plr, cur, max) || cur <= 0 || max <= 0 || args.Player.IgnoreSSCPackets) return true; - + + if (max > TShock.Config.MaxHP && !args.Player.HasPermission(Permissions.ignorehp)) + { + args.Player.Disable("Maximum HP beyond limit", DisableFlags.WriteToLogAndConsole); + return true; + } + + if (args.Player.IsLoggedIn) + { + args.Player.TPlayer.statLife = cur; + args.Player.TPlayer.statLifeMax = max; + args.Player.PlayerData.maxHealth = max; + } + + if (args.Player.GodMode && (cur < max)) + { + args.Player.Heal(args.TPlayer.statLifeMax2); + } return false; } - private static bool HandleProjectileNew(GetDataHandlerArgs args) + private static bool HandleTile(GetDataHandlerArgs args) { - short ident = args.Data.ReadInt16(); - var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - float knockback = args.Data.ReadSingle(); - short dmg = args.Data.ReadInt16(); - byte owner = args.Data.ReadInt8(); - short type = args.Data.ReadInt16(); - BitsByte bits = args.Data.ReadInt8(); - //owner = (byte)args.Player.Index; - float[] ai = new float[Projectile.maxAI]; + EditAction action = (EditAction)args.Data.ReadInt8(); + var tileX = args.Data.ReadInt16(); + var tileY = args.Data.ReadInt16(); + var editData = args.Data.ReadInt16(); + EditType type = (action == EditAction.KillTile || action == EditAction.KillWall || + action == EditAction.KillTileNoItem) + ? EditType.Fail + : (action == EditAction.PlaceTile || action == EditAction.PlaceWall) + ? EditType.Type + : EditType.Slope; - for (int i = 0; i < Projectile.maxAI; i++) - { - if (bits[i]) - ai[i] = args.Data.ReadSingle(); - else - ai[i] = 0f; - } + var style = args.Data.ReadInt8(); - var index = TShock.Utils.SearchProjectile(ident, owner); - - if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player)) + if (OnTileEdit(args.Player, args.Data, tileX, tileY, action, type, editData, style)) return true; return false; } - private static bool HandleProjectileKill(GetDataHandlerArgs args) + private static bool HandleDoorUse(GetDataHandlerArgs args) { - var ident = args.Data.ReadInt16(); - var owner = args.Data.ReadInt8(); - owner = (byte)args.Player.Index; - var index = TShock.Utils.SearchProjectile(ident, owner); + byte type = (byte)args.Data.ReadByte(); + short x = args.Data.ReadInt16(); + short y = args.Data.ReadInt16(); + args.Data.ReadByte(); //Ignore direction - if (OnProjectileKill(args.Player, args.Data, ident, owner, index)) + if (x >= Main.maxTilesX || y >= Main.maxTilesY || x < 0 || y < 0) // Check for out of range { return true; } - var type = Main.projectile[index].type; - - // TODO: This needs to be moved somewhere else. - if (!args.Player.HasProjectilePermission(index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) + if (type < 0 || type > 5) { - args.Player.Disable("Does not have projectile permission to kill projectile.", DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); return true; } - args.Player.LastKilledProjectile = type; + ushort tileType = Main.tile[x, y].type; - return false; - } - - private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - PlayerDeathReason playerDeathReason = PlayerDeathReason.FromReader(new BinaryReader(args.Data)); - var dmg = args.Data.ReadInt16(); - var direction = (byte)(args.Data.ReadInt8() - 1); - BitsByte bits = (BitsByte)args.Data.ReadByte(); - bool pvp = bits[0]; - - if (OnKillMe(args.Player, args.Data, id, direction, dmg, pvp, playerDeathReason)) - return true; - - args.Player.Dead = true; - args.Player.RespawnTimer = TShock.Config.RespawnSeconds; - - foreach (NPC npc in Main.npc) - { - if (npc.active && (npc.boss || npc.type == 13 || npc.type == 14 || npc.type == 15) && - Math.Abs(args.TPlayer.Center.X - npc.Center.X) + Math.Abs(args.TPlayer.Center.Y - npc.Center.Y) < 4000f) - { - args.Player.RespawnTimer = TShock.Config.RespawnBossSeconds; - break; - } - } - - // Handle kicks/bans on mediumcore/hardcore deaths. - if (args.TPlayer.difficulty != 0) // Player is not softcore - { - bool mediumcore = args.TPlayer.difficulty == 1; - bool shouldBan = mediumcore ? TShock.Config.BanOnMediumcoreDeath : TShock.Config.BanOnHardcoreDeath; - bool shouldKick = mediumcore ? TShock.Config.KickOnMediumcoreDeath : TShock.Config.KickOnHardcoreDeath; - string banReason = mediumcore ? TShock.Config.MediumcoreBanReason : TShock.Config.HardcoreBanReason; - string kickReason = mediumcore ? TShock.Config.MediumcoreKickReason : TShock.Config.HardcoreKickReason; - - if(shouldBan) { - if (!args.Player.Ban(banReason, false, "TShock")) - args.Player.Kick("You died! Normally, you'd be banned.", true, true); - } - else if(shouldKick) { - args.Player.Kick(kickReason, true, true, null, false); - } - } - - if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn) - { - if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID)) - { - args.Player.SendErrorMessage("You have fallen in hardcore mode, and your items have been lost forever."); - TShock.CharacterDB.SeedInitialData(args.Player.Account); - } - } - - return false; - } - - private static bool HandleLiquidSet(GetDataHandlerArgs args) - { - int tileX = args.Data.ReadInt16(); - int tileY = args.Data.ReadInt16(); - byte amount = args.Data.ReadInt8(); - byte type = args.Data.ReadInt8(); - - if (OnLiquidSet(args.Player, args.Data, tileX, tileY, amount, type)) - return true; - - return false; - } - - private static bool HandlePlaceChest(GetDataHandlerArgs args) - { - int flag = args.Data.ReadByte(); - int tileX = args.Data.ReadInt16(); - int tileY = args.Data.ReadInt16(); - args.Data.ReadInt16(); // Ignore style - - if (OnPlaceChest(args.Player, args.Data, flag, tileX, tileY)) - return true; - - return false; - } - - private static bool HandleSpawn(GetDataHandlerArgs args) - { - var player = args.Data.ReadInt8(); - var spawnx = args.Data.ReadInt16(); - var spawny = args.Data.ReadInt16(); - - if (OnPlayerSpawn(args.Player, args.Data, player, spawnx, spawny)) - return true; - - if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0) && (args.TPlayer.SpawnX > 0) && ((args.TPlayer.SpawnX != args.Player.sX) && (args.TPlayer.SpawnY != args.Player.sY))) - { - - args.Player.sX = args.TPlayer.SpawnX; - args.Player.sY = args.TPlayer.SpawnY; - - if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) - args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); - } - - else if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0)) - { - if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) - args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); - } - - args.Player.Dead = false; - return false; - } - - private static bool HandleChestOpen(GetDataHandlerArgs args) - { - var x = args.Data.ReadInt16(); - var y = args.Data.ReadInt16(); - - if (OnChestOpen(args.Data, x, y, args.Player)) - return true; - - return false; - } - - private static bool HandleChestActive(GetDataHandlerArgs args) - { - //chest ID - var id = args.Data.ReadInt16(); - //chest x - var x = args.Data.ReadInt16(); - //chest y - var y = args.Data.ReadInt16(); - //chest name length - var nameLen = args.Data.ReadInt8(); - - if (nameLen != 0 && nameLen <= 20) - args.Data.ReadString(); // Ignore the name - - args.Player.ActiveChest = id; - - if (!args.Player.HasBuildPermission(x, y) && TShock.Config.RegionProtectChests) - { - args.Player.SendData(PacketTypes.ChestOpen, "", -1); - return true; - } - - return false; - } - - private static bool HandleChestItem(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var slot = args.Data.ReadInt8(); - var stacks = args.Data.ReadInt16(); - var prefix = args.Data.ReadInt8(); - var type = args.Data.ReadInt16(); - - if (OnChestItemChange(args.Player, args.Data, id, slot, stacks, prefix, type)) - return true; - - Item item = new Item(); - item.netDefaults(type); - if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player)) + if (tileType != TileID.ClosedDoor && tileType != TileID.OpenDoor + && tileType != TileID.TallGateClosed && tileType != TileID.TallGateOpen + && tileType != TileID.TrapdoorClosed && tileType != TileID.TrapdoorOpen) { return true; } @@ -2695,75 +2251,20 @@ namespace TShockAPI return false; } - private static bool HandleSign(GetDataHandlerArgs args) + private static bool HandleSendTileSquare(GetDataHandlerArgs args) { - var id = args.Data.ReadInt16(); - var x = args.Data.ReadInt16(); - var y = args.Data.ReadInt16(); - args.Data.ReadString(); // Ignore sign text + var player = args.Player; + var size = args.Data.ReadInt16(); + var tileX = args.Data.ReadInt16(); + var tileY = args.Data.ReadInt16(); + var data = args.Data; - if (OnSignEvent(args.Player, args.Data, id, x, y)) + if (OnSendTileSquare(player, data, size, tileX, tileY)) return true; - if (!args.Player.HasBuildPermission(x, y)) - { - args.Player.SendData(PacketTypes.SignNew, "", id); - return true; - } - - if (!args.Player.IsInRange(x, y)) - { - args.Player.SendData(PacketTypes.SignNew, "", id); - return true; - } return false; } - private static bool UpdateNPCHome(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var x = args.Data.ReadInt16(); - var y = args.Data.ReadInt16(); - var homeless = args.Data.ReadInt8(); - - if (OnUpdateNPCHome(args.Player, args.Data, id, x, y, homeless)) - return true; - - if (!args.Player.HasPermission(Permissions.movenpc)) - { - args.Player.SendErrorMessage("You do not have permission to relocate NPCs."); - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - } - return false; - } - - private static bool HandleNPCAddBuff(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var type = args.Data.ReadInt8(); - var time = args.Data.ReadInt16(); - - if (OnNPCAddBuff(args.Player, args.Data, id, type, time)) - return true; - - return false; - } - - private static bool HandlePlayerAddBuff(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var type = args.Data.ReadInt8(); - var time = args.Data.ReadInt32(); - - if (OnPlayerBuff(args.Player, args.Data, id, type, time)) - return true; - - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - private static bool HandleItemDrop(GetDataHandlerArgs args) { var id = args.Data.ReadInt16(); @@ -2797,24 +2298,32 @@ namespace TShockAPI return false; } - private static bool HandlePlayerDamageV2(GetDataHandlerArgs args) + private static bool HandleProjectileNew(GetDataHandlerArgs args) { - var id = args.Data.ReadInt8(); - PlayerDeathReason playerDeathReason = PlayerDeathReason.FromReader(new BinaryReader(args.Data)); - var dmg = args.Data.ReadInt16(); - var direction = (byte)(args.Data.ReadInt8() - 1); - var bits = (BitsByte)(args.Data.ReadByte()); - var crit = bits[0]; - var pvp = bits[1]; + short ident = args.Data.ReadInt16(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + float knockback = args.Data.ReadSingle(); + short dmg = args.Data.ReadInt16(); + byte owner = args.Data.ReadInt8(); + short type = args.Data.ReadInt16(); + BitsByte bits = args.Data.ReadInt8(); + //owner = (byte)args.Player.Index; + float[] ai = new float[Projectile.maxAI]; - if (OnPlayerDamage(args.Player, args.Data, id, direction, dmg, pvp, crit, playerDeathReason)) - return true; - - if (TShock.Players[id].GodMode) + for (int i = 0; i < Projectile.maxAI; i++) { - TShock.Players[id].Heal(args.TPlayer.statLifeMax); + if (bits[i]) + ai[i] = args.Data.ReadSingle(); + else + ai[i] = 0f; } + var index = TShock.Utils.SearchProjectile(ident, owner); + + if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player)) + return true; + return false; } @@ -2839,30 +2348,219 @@ namespace TShockAPI return false; } - private static bool HandleSpecial(GetDataHandlerArgs args) + private static bool HandleProjectileKill(GetDataHandlerArgs args) { - var id = args.Data.ReadInt8(); - var type = args.Data.ReadInt8(); + var ident = args.Data.ReadInt16(); + var owner = args.Data.ReadInt8(); + owner = (byte)args.Player.Index; + var index = TShock.Utils.SearchProjectile(ident, owner); - if (OnNPCSpecial(args.Player, args.Data, id, type)) - return true; - - if (type == 1 && TShock.Config.DisableDungeonGuardian) + if (OnProjectileKill(args.Player, args.Data, ident, owner, index)) { - args.Player.SendMessage("The Dungeon Guardian returned you to your spawn point", Color.Purple); - args.Player.Spawn(); return true; } - if (type == 3 & !args.Player.HasPermission(Permissions.usesundial)) + var type = Main.projectile[index].type; + + // TODO: This needs to be moved somewhere else. + if (!args.Player.HasProjectilePermission(index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) + { + args.Player.Disable("Does not have projectile permission to kill projectile.", DisableFlags.WriteToLogAndConsole); + args.Player.RemoveProjectile(ident, owner); + return true; + } + + args.Player.LastKilledProjectile = type; + + return false; + } + + private static bool HandleTogglePvp(GetDataHandlerArgs args) + { + byte id = args.Data.ReadInt8(); + bool pvp = args.Data.ReadBoolean(); + if (OnPvpToggled(args.Player, args.Data, id, pvp)) + return true; + + if (id != args.Player.Index) + return true; + + string pvpMode = TShock.Config.PvPMode.ToLowerInvariant(); + if (pvpMode == "disabled" || pvpMode == "always" || (DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5) + { + args.Player.SendData(PacketTypes.TogglePvp, "", id); + return true; + } + + args.Player.LastPvPTeamChange = DateTime.UtcNow; + return false; + } + + private static bool HandleChestOpen(GetDataHandlerArgs args) + { + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); + + if (OnChestOpen(args.Data, x, y, args.Player)) + return true; + + return false; + } + + private static bool HandleChestItem(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var slot = args.Data.ReadInt8(); + var stacks = args.Data.ReadInt16(); + var prefix = args.Data.ReadInt8(); + var type = args.Data.ReadInt16(); + + if (OnChestItemChange(args.Player, args.Data, id, slot, stacks, prefix, type)) + return true; + + Item item = new Item(); + item.netDefaults(type); + if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player)) { - args.Player.SendErrorMessage("You do not have permission to use the Enchanted Sundial!"); return true; } return false; } + private static bool HandleChestActive(GetDataHandlerArgs args) + { + //chest ID + var id = args.Data.ReadInt16(); + //chest x + var x = args.Data.ReadInt16(); + //chest y + var y = args.Data.ReadInt16(); + //chest name length + var nameLen = args.Data.ReadInt8(); + + if (nameLen != 0 && nameLen <= 20) + args.Data.ReadString(); // Ignore the name + + args.Player.ActiveChest = id; + + if (!args.Player.HasBuildPermission(x, y) && TShock.Config.RegionProtectChests) + { + args.Player.SendData(PacketTypes.ChestOpen, "", -1); + return true; + } + + return false; + } + + private static bool HandlePlaceChest(GetDataHandlerArgs args) + { + int flag = args.Data.ReadByte(); + int tileX = args.Data.ReadInt16(); + int tileY = args.Data.ReadInt16(); + args.Data.ReadInt16(); // Ignore style + + if (OnPlaceChest(args.Player, args.Data, flag, tileX, tileY)) + return true; + + return false; + } + + private static bool HandlePlayerZone(GetDataHandlerArgs args) + { + if (args.Player == null || args.TPlayer == null || args.Data == null) + { + return true; + } + + var plr = args.Data.ReadInt8(); + BitsByte zone1 = args.Data.ReadInt8(); + BitsByte zone2 = args.Data.ReadInt8(); + BitsByte zone3 = args.Data.ReadInt8(); + BitsByte zone4 = args.Data.ReadInt8(); + + if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4)) + return true; + + return false; + } + + private static bool HandlePassword(GetDataHandlerArgs args) + { + if (!args.Player.RequiresPassword) + return true; + + string password = args.Data.ReadString(); + + if (Hooks.PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, password)) + return true; + + var account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name); + if (account != null && !TShock.Config.DisableLoginBeforeJoin) + { + if (account.VerifyPassword(password)) + { + args.Player.RequiresPassword = false; + args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID); + + if (args.Player.State == 1) + args.Player.State = 2; + NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index); + + var group = TShock.Groups.GetGroupByName(account.Group); + + args.Player.Group = group; + args.Player.tempGroup = null; + args.Player.Account = account; + args.Player.IsLoggedIn = true; + args.Player.IsDisabledForSSC = false; + + if (Main.ServerSideCharacter) + { + if (args.Player.HasPermission(Permissions.bypassssc)) + { + args.Player.PlayerData.CopyCharacter(args.Player); + TShock.CharacterDB.InsertPlayerData(args.Player); + } + args.Player.PlayerData.RestoreCharacter(args.Player); + } + args.Player.LoginFailsBySsi = false; + + if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) + args.Player.IsDisabledForStackDetection = false; + + if (args.Player.HasPermission(Permissions.usebanneditem)) + args.Player.IsDisabledForBannedWearable = false; + + + args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); + TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); + TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID); + Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); + return true; + } + args.Player.Kick("Your password did not match this character's password.", true, true); + return true; + } + + if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) + { + if (TShock.Config.ServerPassword == password) + { + args.Player.RequiresPassword = false; + if (args.Player.State == 1) + args.Player.State = 2; + NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index); + return true; + } + args.Player.Kick("Invalid server password.", true, true); + return true; + } + + args.Player.Kick("You have been Bounced.", true, true); + return true; + } + private static bool HandlePlayerAnimation(GetDataHandlerArgs args) { if (OnPlayerAnimation(args.Player, args.Data)) @@ -2871,6 +2569,87 @@ namespace TShockAPI return false; } + private static bool HandlePlayerMana(GetDataHandlerArgs args) + { + var plr = args.Data.ReadInt8(); + var cur = args.Data.ReadInt16(); + var max = args.Data.ReadInt16(); + + if (OnPlayerMana(args.Player, args.Data, plr, cur, max) || cur < 0 || max < 0 || args.Player.IgnoreSSCPackets) + return true; + + if (max > TShock.Config.MaxMP && !args.Player.HasPermission(Permissions.ignoremp)) + { + args.Player.Disable("Maximum MP beyond limit", DisableFlags.WriteToLogAndConsole); + return true; + } + + if (args.Player.IsLoggedIn) + { + args.Player.TPlayer.statMana = cur; + args.Player.TPlayer.statManaMax = max; + args.Player.PlayerData.maxMana = max; + } + return false; + } + + private static bool HandlePlayerTeam(GetDataHandlerArgs args) + { + byte id = args.Data.ReadInt8(); + byte team = args.Data.ReadInt8(); + if (OnPlayerTeam(args.Player, args.Data, id, team)) + return true; + + if (id != args.Player.Index) + return true; + + if ((DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5) + { + args.Player.SendData(PacketTypes.PlayerTeam, "", id); + return true; + } + + args.Player.LastPvPTeamChange = DateTime.UtcNow; + return false; + } + + private static bool HandleSign(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); + args.Data.ReadString(); // Ignore sign text + + if (OnSignEvent(args.Player, args.Data, id, x, y)) + return true; + + if (!args.Player.HasBuildPermission(x, y)) + { + args.Player.SendData(PacketTypes.SignNew, "", id); + return true; + } + + if (!args.Player.IsInRange(x, y)) + { + args.Player.SendData(PacketTypes.SignNew, "", id); + return true; + } + return false; + } + + private static bool HandleLiquidSet(GetDataHandlerArgs args) + { + int tileX = args.Data.ReadInt16(); + int tileY = args.Data.ReadInt16(); + byte amount = args.Data.ReadInt8(); + byte type = args.Data.ReadInt8(); + + if (OnLiquidSet(args.Player, args.Data, tileX, tileY, amount, type)) + return true; + + return false; + } + private static bool HandlePlayerBuffList(GetDataHandlerArgs args) { var id = args.Data.ReadInt8(); @@ -2904,6 +2683,75 @@ namespace TShockAPI return true; } + private static bool HandleSpecial(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var type = args.Data.ReadInt8(); + + if (OnNPCSpecial(args.Player, args.Data, id, type)) + return true; + + if (type == 1 && TShock.Config.DisableDungeonGuardian) + { + args.Player.SendMessage("The Dungeon Guardian returned you to your spawn point", Color.Purple); + args.Player.Spawn(); + return true; + } + + if (type == 3 & !args.Player.HasPermission(Permissions.usesundial)) + { + args.Player.SendErrorMessage("You do not have permission to use the Enchanted Sundial!"); + return true; + } + + return false; + } + + private static bool HandleNPCAddBuff(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var type = args.Data.ReadInt8(); + var time = args.Data.ReadInt16(); + + if (OnNPCAddBuff(args.Player, args.Data, id, type, time)) + return true; + + return false; + } + + private static bool HandlePlayerAddBuff(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var type = args.Data.ReadInt8(); + var time = args.Data.ReadInt32(); + + if (OnPlayerBuff(args.Player, args.Data, id, type, time)) + return true; + + args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); + return true; + } + + private static bool UpdateNPCHome(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); + var homeless = args.Data.ReadInt8(); + + if (OnUpdateNPCHome(args.Player, args.Data, id, x, y, homeless)) + return true; + + if (!args.Player.HasPermission(Permissions.movenpc)) + { + args.Player.SendErrorMessage("You do not have permission to relocate NPCs."); + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + return true; + } + return false; + } + private static bool HandleSpawnBoss(GetDataHandlerArgs args) { if (args.Player.IsBouncerThrottled()) @@ -3164,159 +3012,13 @@ namespace TShockAPI return false; } - private static bool HandleDoorUse(GetDataHandlerArgs args) + private static bool HandleHealOther(GetDataHandlerArgs args) { - byte type = (byte)args.Data.ReadByte(); - short x = args.Data.ReadInt16(); - short y = args.Data.ReadInt16(); - args.Data.ReadByte(); //Ignore direction + byte plr = args.Data.ReadInt8(); + short amount = args.Data.ReadInt16(); - if (x >= Main.maxTilesX || y >= Main.maxTilesY || x < 0 || y < 0) // Check for out of range - { + if (OnHealOtherPlayer(args.Player, args.Data, plr, amount)) return true; - } - - if (type < 0 || type > 5) - { - return true; - } - - ushort tileType = Main.tile[x, y].type; - - if (tileType != TileID.ClosedDoor && tileType != TileID.OpenDoor - && tileType != TileID.TallGateClosed && tileType != TileID.TallGateOpen - && tileType != TileID.TrapdoorClosed && tileType != TileID.TrapdoorOpen) - { - return true; - } - - return false; - } - - private static bool HandleCompleteAnglerQuest(GetDataHandlerArgs args) - { - // Since packet 76 is NEVER sent to us, we actually have to rely on this to get the true count - args.TPlayer.anglerQuestsFinished++; - return false; - } - - private static bool HandleNumberOfAnglerQuestsCompleted(GetDataHandlerArgs args) - { - // Never sent by vanilla client, ignore this - return true; - } - - private static bool HandleMassWireOperation(GetDataHandlerArgs args) - { - short startX = args.Data.ReadInt16(); - short startY = args.Data.ReadInt16(); - short endX = args.Data.ReadInt16(); - short endY = args.Data.ReadInt16(); - byte toolMode = (byte) args.Data.ReadByte(); - - if (OnMassWireOperation(args.Player, args.Data, startX, startY, endX, endY, toolMode)) - return true; - - return false; - } - - /// The arguments to the PlaceItemFrame event. - public class PlaceItemFrameEventArgs : GetDataHandledEventArgs - { - /// The X coordinate of the item frame. - public short X { get; set; } - - /// The Y coordinate of the item frame. - public short Y { get; set; } - - /// The ItemID of the item frame. - 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 TEItemFrame ItemFrame { get; set; } - } - - /// Fired when an ItemFrame is placed. - public static HandlerList PlaceItemFrame = new HandlerList(); - - private static bool OnPlaceItemFrame(TSPlayer player, MemoryStream data, short x, short y, short itemID, byte prefix, short stack, TEItemFrame itemFrame) - { - if (PlaceItemFrame == null) - return false; - - var args = new PlaceItemFrameEventArgs - { - Player = player, - Data = data, - X = x, - Y = y, - ItemID = itemID, - Prefix = prefix, - Stack = stack, - ItemFrame = itemFrame, - }; - - PlaceItemFrame.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a ToggleGemLock event - /// - public class GemLockToggleEventArgs : GetDataHandledEventArgs - { - /// - /// X Location - /// - public short X { get; set; } - /// - /// Y Location - /// - public short Y { get; set; } - /// - /// On status - /// - public bool On { get; set; } - } - - /// - /// GemLockToggle - Called when a gem lock is switched - /// - public static HandlerList GemLockToggle = new HandlerList(); - - private static bool OnGemLockToggle(TSPlayer player, MemoryStream data, short x, short y, bool on) - { - if (GemLockToggle == null) - return false; - - var args = new GemLockToggleEventArgs - { - Player = player, - Data = data, - X = x, - Y = y, - On = on - }; - GemLockToggle.Invoke(null, args); - return args.Handled; - } - - private static bool HandleGemLockToggle(GetDataHandlerArgs args) - { - var x = args.Data.ReadInt16(); - var y = args.Data.ReadInt16(); - var on = args.Data.ReadBoolean(); - - if (OnGemLockToggle(args.Player, args.Data, x, y, on)) - { - return true; - } return false; } @@ -3336,50 +3038,45 @@ namespace TShockAPI return false; } - private static bool HandleNpcTeleportPortal(GetDataHandlerArgs args) + private static bool HandleCompleteAnglerQuest(GetDataHandlerArgs args) { - var npcIndex = args.Data.ReadByte(); - var portalColorIndex = args.Data.ReadInt16(); - var newPosition = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var velocity = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var projectile = Main.projectile.FirstOrDefault(p => p.position.X == newPosition.X && p.position.Y == newPosition.Y); // Check for projectiles at this location + // Since packet 76 is NEVER sent to us, we actually have to rely on this to get the true count + args.TPlayer.anglerQuestsFinished++; + return false; + } - if (projectile == null || !projectile.active) - { - NetMessage.SendData((int)PacketTypes.NpcUpdate, -1, -1, NetworkText.Empty, npcIndex); - return true; - } + private static bool HandleNumberOfAnglerQuestsCompleted(GetDataHandlerArgs args) + { + // Never sent by vanilla client, ignore this + return true; + } - if (projectile.type != ProjectileID.PortalGunGate) - { - NetMessage.SendData((int)PacketTypes.NpcUpdate, -1, -1, NetworkText.Empty, npcIndex); + private static bool HandlePlaceObject(GetDataHandlerArgs args) + { + short x = args.Data.ReadInt16(); + short y = args.Data.ReadInt16(); + short type = args.Data.ReadInt16(); + short style = args.Data.ReadInt16(); + byte alternate = args.Data.ReadInt8(); + bool direction = args.Data.ReadBoolean(); + + if (OnPlaceObject(args.Player, args.Data, x, y, type, style, alternate, direction)) return true; - } return false; } - private static bool HandleKillPortal(GetDataHandlerArgs args) + private static bool HandleLoadNetModule(GetDataHandlerArgs args) { - short projectileIndex = args.Data.ReadInt16(); - - Projectile projectile = Main.projectile[projectileIndex]; - if (projectile != null && projectile.active) - { - if (projectile.owner != args.TPlayer.whoAmI) - { - return true; - } - } - - return false; + // Since this packet is never actually sent to us, every attempt at sending it can be considered as a liquid exploit attempt + return true; } private static bool HandlePlaceTileEntity(GetDataHandlerArgs args) { var x = args.Data.ReadInt16(); var y = args.Data.ReadInt16(); - var type = (byte) args.Data.ReadByte(); + var type = (byte)args.Data.ReadByte(); if (OnPlaceTileEntity(args.Player, args.Data, x, y, type)) { @@ -3414,7 +3111,7 @@ namespace TShockAPI return false; } - + private static bool HandleSyncExtraValue(GetDataHandlerArgs args) { var npcIndex = args.Data.ReadInt16(); @@ -3438,11 +3135,91 @@ namespace TShockAPI return false; } - - private static bool HandleLoadNetModule(GetDataHandlerArgs args) + + private static bool HandleKillPortal(GetDataHandlerArgs args) { - // Since this packet is never actually sent to us, every attempt at sending it can be considered as a liquid exploit attempt - return true; + short projectileIndex = args.Data.ReadInt16(); + + Projectile projectile = Main.projectile[projectileIndex]; + if (projectile != null && projectile.active) + { + if (projectile.owner != args.TPlayer.whoAmI) + { + return true; + } + } + + return false; + } + + private static bool HandlePlayerPortalTeleport(GetDataHandlerArgs args) + { + byte plr = args.Data.ReadInt8(); + short portalColorIndex = args.Data.ReadInt16(); + float newPositionX = args.Data.ReadSingle(); + float newPositionY = args.Data.ReadSingle(); + float newVelocityX = args.Data.ReadSingle(); + float newVelocityY = args.Data.ReadSingle(); + + return OnPlayerTeleportThroughPortal( + args.Player, + plr, + args.Data, + new Vector2(newPositionX, newPositionY), + new Vector2(newVelocityX, newVelocityY), + portalColorIndex + ); + } + + private static bool HandleNpcTeleportPortal(GetDataHandlerArgs args) + { + var npcIndex = args.Data.ReadByte(); + var portalColorIndex = args.Data.ReadInt16(); + var newPosition = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var velocity = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var projectile = Main.projectile.FirstOrDefault(p => p.position.X == newPosition.X && p.position.Y == newPosition.Y); // Check for projectiles at this location + + if (projectile == null || !projectile.active) + { + NetMessage.SendData((int)PacketTypes.NpcUpdate, -1, -1, NetworkText.Empty, npcIndex); + return true; + } + + if (projectile.type != ProjectileID.PortalGunGate) + { + NetMessage.SendData((int)PacketTypes.NpcUpdate, -1, -1, NetworkText.Empty, npcIndex); + return true; + } + + return false; + } + + private static bool HandleGemLockToggle(GetDataHandlerArgs args) + { + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); + var on = args.Data.ReadBoolean(); + + if (OnGemLockToggle(args.Player, args.Data, x, y, on)) + { + return true; + } + + return false; + } + + private static bool HandleMassWireOperation(GetDataHandlerArgs args) + { + short startX = args.Data.ReadInt16(); + short startY = args.Data.ReadInt16(); + short endX = args.Data.ReadInt16(); + short endY = args.Data.ReadInt16(); + byte toolMode = (byte)args.Data.ReadByte(); + + if (OnMassWireOperation(args.Player, args.Data, startX, startY, endX, endY, toolMode)) + return true; + + return false; } private static bool HandleToggleParty(GetDataHandlerArgs args) @@ -3475,5 +3252,166 @@ namespace TShockAPI TShock.Utils.Broadcast(string.Format("{0} started the Old One's Army event!", args.Player.Name), 175, 75, 255); return false; } + + private static bool HandlePlayerDamageV2(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + PlayerDeathReason playerDeathReason = PlayerDeathReason.FromReader(new BinaryReader(args.Data)); + var dmg = args.Data.ReadInt16(); + var direction = (byte)(args.Data.ReadInt8() - 1); + var bits = (BitsByte)(args.Data.ReadByte()); + var crit = bits[0]; + var pvp = bits[1]; + + if (OnPlayerDamage(args.Player, args.Data, id, direction, dmg, pvp, crit, playerDeathReason)) + return true; + + if (TShock.Players[id].GodMode) + { + TShock.Players[id].Heal(args.TPlayer.statLifeMax); + } + + return false; + } + + private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + PlayerDeathReason playerDeathReason = PlayerDeathReason.FromReader(new BinaryReader(args.Data)); + var dmg = args.Data.ReadInt16(); + var direction = (byte)(args.Data.ReadInt8() - 1); + BitsByte bits = (BitsByte)args.Data.ReadByte(); + bool pvp = bits[0]; + + if (OnKillMe(args.Player, args.Data, id, direction, dmg, pvp, playerDeathReason)) + return true; + + args.Player.Dead = true; + args.Player.RespawnTimer = TShock.Config.RespawnSeconds; + + foreach (NPC npc in Main.npc) + { + if (npc.active && (npc.boss || npc.type == 13 || npc.type == 14 || npc.type == 15) && + Math.Abs(args.TPlayer.Center.X - npc.Center.X) + Math.Abs(args.TPlayer.Center.Y - npc.Center.Y) < 4000f) + { + args.Player.RespawnTimer = TShock.Config.RespawnBossSeconds; + break; + } + } + + // Handle kicks/bans on mediumcore/hardcore deaths. + if (args.TPlayer.difficulty != 0) // Player is not softcore + { + bool mediumcore = args.TPlayer.difficulty == 1; + bool shouldBan = mediumcore ? TShock.Config.BanOnMediumcoreDeath : TShock.Config.BanOnHardcoreDeath; + bool shouldKick = mediumcore ? TShock.Config.KickOnMediumcoreDeath : TShock.Config.KickOnHardcoreDeath; + string banReason = mediumcore ? TShock.Config.MediumcoreBanReason : TShock.Config.HardcoreBanReason; + string kickReason = mediumcore ? TShock.Config.MediumcoreKickReason : TShock.Config.HardcoreKickReason; + + if (shouldBan) + { + if (!args.Player.Ban(banReason, false, "TShock")) + args.Player.Kick("You died! Normally, you'd be banned.", true, true); + } + else if (shouldKick) + { + args.Player.Kick(kickReason, true, true, null, false); + } + } + + if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn) + { + if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID)) + { + args.Player.SendErrorMessage("You have fallen in hardcore mode, and your items have been lost forever."); + TShock.CharacterDB.SeedInitialData(args.Player.Account); + } + } + + return false; + } + + + public enum EditAction + { + KillTile = 0, + PlaceTile, + KillWall, + PlaceWall, + KillTileNoItem, + PlaceWire, + KillWire, + PoundTile, + PlaceActuator, + KillActuator, + PlaceWire2, + KillWire2, + PlaceWire3, + KillWire3, + SlopeTile, + FrameTrack, + PlaceWire4, + KillWire4 + } + public enum EditType + { + Fail = 0, + Type, + Slope, + } + + /// + /// The maximum place styles for each tile. + /// + public static Dictionary MaxPlaceStyles = new Dictionary(); + + /// + /// Tiles that can be broken without any pickaxes/etc. + /// + internal static int[] breakableTiles = new int[] + { + TileID.Books, + TileID.Bottles, + TileID.BreakableIce, + TileID.Candles, + TileID.CorruptGrass, + TileID.Dirt, + TileID.FleshGrass, + TileID.Grass, + TileID.HallowedGrass, + TileID.MagicalIceBlock, + TileID.Mannequin, + TileID.Torches, + TileID.WaterCandle, + TileID.Womannequin, + }; + + /// + /// These projectiles create tiles on death. + /// + internal static Dictionary projectileCreatesTile = new Dictionary + { + { ProjectileID.DirtBall, TileID.Dirt }, + { ProjectileID.SandBallGun, TileID.Sand }, + { ProjectileID.EbonsandBallGun, TileID.Ebonsand }, + { ProjectileID.PearlSandBallGun, TileID.Pearlsand }, + { ProjectileID.CrimsandBallGun, TileID.Crimsand }, + }; + + internal static Dictionary ropeCoilPlacements = new Dictionary + { + {ItemID.RopeCoil, TileID.Rope}, + {ItemID.SilkRopeCoil, TileID.SilkRope}, + {ItemID.VineRopeCoil, TileID.VineRope}, + {ItemID.WebRopeCoil, TileID.WebRope} + }; + + /// + /// Extra place style limits for strange hardcoded values in Terraria + /// + internal static Dictionary ExtraneousPlaceStyles = new Dictionary + { + {TileID.MinecartTrack, 3} + }; } }