diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c81892a..30241bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,29 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Removed `Permissions.updateplugins` permission. (@hakusaro) * Removed REST `/v3/server/restart/` route and `/server/restart/` route. (@hakusaro) * The "auth system" is now referred to as the initial setup system (what it actually is). This is better verbiage for basically all situations. Who really wants to turn off the "authentication system?" In addition, the system now makes it more clear what the point of it is, rather than that it grants permissions. (@hakusaro) +* `GetDataHandlers.SendTileSquare` hook now sends a `TSPlayer` and a `MemoryStream` of raw data. (@hakusaro) +* Added `GetDataHandlers.HealOtherPlayer` hook. (@hakusaro) +* Added `GetDataHandlers.PlaceObject` hook. (@hakusaro) +* `GetDataHandlers.KillMe` now sends a `TSPlayer` and a `PlayerDeathReason`. (@hakusaro) +* Added `GetDataHandlers.ProjectileKill` hook. (@hakusaro) +* Removed `TShock.CheckProjectilePermission` and replaced it with `TSPlayer.HasProjectilePermission` and `TSPlayer.LacksProjectilePermission` respectively. (@hakusaro) +* Added `TSPlayer` object to `GetDataHandlers.LiquidSetEventArgs`. (@hakusaro) +* Removed `TShock.StartInvasion` for public use (moved to Utils and marked internal). (@hakusaro) +* Fixed invasions started by TShock not reporting size correctly and probably not working at all. (@hakusaro) +* Removed `GetDataHandlers.TileKill` and replaced it with `GetDataHandlers.PlaceChest` as the packet originally designated as tile kill is now only used for chests. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.NPCHome`. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.ChestItemChanged`. (@hakusaro) +* Fixed chest item changes not triggering any range checks, tile checks, or correct chest checks. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.PlayerBuff`. (@hakusaro) +* Added `TSPlayer` and `PlayerDeathReason` to `GetDataHandlers.PlayerDamage`. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.NPCStrike`. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.PlayerAnimation`. (@hakusaro) +* Added `GetDataHandlers.MassWireOperation` hook and related arguments. (@hakusaro) +* Added `GetDataHandlers.PlaceTileEntity` hook and related arguments. (@hakusaro) +* Added `TSPlayer` to `GetDataHandlers.GemLockToggle`. (@hakusaro) +* Added `GetDataHandlers.PlaceItemFrame` hook and related arguments. (@hakusaro) +* Added `TSPlayer.IsBouncerThrottled()`. (@hakusaro) +* Added `TSPlayer.CheckIgnores()` and removed `TShock.CheckIgnores(TSPlayer)`. (@hakusaro) ## TShock 4.3.25 * Fixed a critical exploit in the Terraria protocol that could cause massive unpreventable world corruption as well as a number of other problems. Thanks to @bartico6 for reporting. Fixed by the efforts of @QuiCM, @hakusaro, and tips in the right directioon from @bartico6. diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs new file mode 100644 index 00000000..a65f840c --- /dev/null +++ b/TShockAPI/Bouncer.cs @@ -0,0 +1,1827 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Linq; +using Terraria.ID; +using TShockAPI.DB; +using TShockAPI.Net; +using Terraria; +using Microsoft.Xna.Framework; +using OTAPI.Tile; +using TShockAPI.Localization; +using static TShockAPI.GetDataHandlers; +using TerrariaApi.Server; +using Terraria.ObjectData; +using Terraria.DataStructures; +using Terraria.Localization; + +namespace TShockAPI +{ + /// Bouncer is the TShock anti-hack and anti-cheat system. + internal sealed class Bouncer + { + /// Constructor call initializes Bouncer and related functionality. + /// A new Bouncer. + internal Bouncer() + { + // Setup hooks + + GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame; + GetDataHandlers.GemLockToggle += OnGemLockToggle; + GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity; + GetDataHandlers.PlayerAnimation += OnPlayerAnimation; + GetDataHandlers.NPCStrike += OnNPCStrike; + GetDataHandlers.ItemDrop += OnItemDrop; + GetDataHandlers.PlayerBuff += OnPlayerBuff; + GetDataHandlers.ChestItemChange += OnChestItemChange; + GetDataHandlers.NPCHome += OnUpdateNPCHome; + GetDataHandlers.ChestOpen += OnChestOpen; + GetDataHandlers.PlaceChest += OnPlaceChest; + GetDataHandlers.LiquidSet += OnLiquidSet; + GetDataHandlers.ProjectileKill += OnProjectileKill; + GetDataHandlers.PlayerUpdate += OnPlayerUpdate; + GetDataHandlers.KillMe += OnKillMe; + GetDataHandlers.NewProjectile += OnNewProjectile; + GetDataHandlers.PlaceObject += OnPlaceObject; + GetDataHandlers.SendTileSquare += OnSendTileSquare; + GetDataHandlers.HealOtherPlayer += OnHealOtherPlayer; + GetDataHandlers.TileEdit += OnTileEdit; + } + + /// 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.CheckIgnores()) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, args.X, args.Y)) + { + NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, 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.CheckIgnores()) + { + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, args.X, args.Y)) + { + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, 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.CheckIgnores()) + { + 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) + { + TShock.Utils.Kick(args.Player, 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.CheckIgnores()) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + args.Handled = true; + return; + } + + if (TShock.Config.RangeChecks && + TShock.CheckRangePermission(args.Player, (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) + { + TShock.Utils.Kick(args.Player, 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.CheckIgnores()) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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 (TShock.CheckRangePermission(args.Player, (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 (TShock.CheckRangePermission(args.Player, (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.CheckIgnores()) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + 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.CheckIgnores()) + { + 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 (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + args.Player.SendData(PacketTypes.ChestItem, "", id, slot); + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) + { + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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; + + // Calls to TShock.CheckTilePermission need to be broken up into different subsystems + // In particular, this handles both regions and other things. Ouch. + if (TShock.CheckTilePermission(args.Player, x, y)) + { + args.Player.SendErrorMessage("You do not have access to modify this area."); + 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 (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, args.X, args.Y)) + { + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, 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.CheckIgnores()) + { + 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.MaxChests() && 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 (TShock.CheckTilePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + 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 (TShock.CheckTilePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, 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.CheckIgnores()) + { + 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. + internal void OnPlayerUpdate(object sender, GetDataHandlers.PlayerUpdateEventArgs args) + { + byte plr = args.PlayerId; + BitsByte control = args.Control; + BitsByte pulley = args.Pulley; + byte item = args.Item; + var pos = args.Position; + var vel = args.Velocity; + + if (pos.X < 0 || pos.Y < 0 || pos.X >= Main.maxTilesX * 16 - 16 || pos.Y >= Main.maxTilesY * 16 - 16) + { + args.Handled = true; + return; + } + + if (item < 0 || item >= args.Player.TPlayer.inventory.Length) + { + args.Handled = true; + return; + } + + if (args.Player.LastNetPosition == Vector2.Zero) + { + args.Handled = true; + return; + } + + if (!pos.Equals(args.Player.LastNetPosition)) + { + float distance = Vector2.Distance(new Vector2(pos.X / 16f, pos.Y / 16f), + new Vector2(args.Player.LastNetPosition.X / 16f, args.Player.LastNetPosition.Y / 16f)); + + if (args.Player.CheckIgnores()) + { + // If the player has moved outside the disabled zone... + if (distance > TShock.Config.MaxRangeForDisabled) + { + // We need to tell them they were disabled and why, then revert the change. + if (args.Player.IgnoreActionsForCheating != "none") + { + args.Player.SendErrorMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating); + } + else if (args.Player.IgnoreActionsForDisabledArmor != "none") + { + args.Player.SendErrorMessage("Disabled for banned armor: " + args.Player.IgnoreActionsForDisabledArmor); + } + else if (args.Player.IgnoreActionsForInventory != "none") + { + args.Player.SendErrorMessage("Disabled for Server Side Inventory: " + args.Player.IgnoreActionsForInventory); + } + else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn) + { + args.Player.SendErrorMessage("Please /register or /login to play!"); + } + else if (args.Player.IgnoreActionsForClearingTrashCan) + { + args.Player.SendErrorMessage("You need to rejoin to ensure your trash can is cleared!"); + } + + // ?? + var lastTileX = args.Player.LastNetPosition.X; + var lastTileY = args.Player.LastNetPosition.Y - 48; + if (!args.Player.Teleport(lastTileX, lastTileY)) + { + args.Player.Spawn(); + } + args.Handled = true; + return; + } + args.Handled = true; + return; + } + + // Corpses don't move + if (args.Player.Dead) + { + args.Handled = true; + return; + } + + // Noclip detection + if (!args.Player.HasPermission(Permissions.ignorenoclipdetection) && + TSCheckNoclip(pos, args.Player.TPlayer.width, args.Player.TPlayer.height - (args.Player.TPlayer.mount.Active ? args.Player.TPlayer.mount.HeightBoost : 0)) && !TShock.Config.IgnoreNoClip + && !args.Player.TPlayer.tongued) + { + var lastTileX = args.Player.LastNetPosition.X; + var lastTileY = args.Player.LastNetPosition.Y; + if (!args.Player.Teleport(lastTileX, lastTileY)) + { + args.Player.SendErrorMessage("You got stuck in a solid object, Sent to spawn point."); + args.Player.Spawn(); + } + args.Handled = true; + return; + } + } + + 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. + { + TShock.Utils.ForceKick(args.Player, "Crash Exploit Attempt", 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.Utils.Kick(TShock.Players[id], "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 || index < 0) + { + 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.CheckIgnores()) + { + 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.CheckIgnores()) + { + 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 (TShock.CheckTilePermission(args.Player, i, j, type, EditAction.PlaceTile)) + { + 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) + && TShock.CheckRangePermission(args.Player, 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. + internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) + { + EditAction action = args.Action; + int tileX = args.X; + int tileY = args.Y; + short editData = args.EditData; + EditType type = args.editDetail; + byte style = args.Style; + + try + { + if (editData < 0) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (!TShock.Utils.TilePlacementValid(tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + + if (action == EditAction.KillTile && Main.tile[tileX, tileY].type == TileID.MagicalIceBlock) + { + args.Handled = false; + return; + } + + if (args.Player.Dead && TShock.Config.PreventDeadModification) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + // TODO: Remove from bouncer (does't look like Bouncer code) + if (args.Player.AwaitingName) + { + bool includeUnprotected = false; + bool includeZIndexes = false; + bool persistentMode = false; + foreach (string parameter in args.Player.AwaitingNameParameters) + { + if (parameter.Equals("-u", StringComparison.InvariantCultureIgnoreCase)) + includeUnprotected = true; + if (parameter.Equals("-z", StringComparison.InvariantCultureIgnoreCase)) + includeZIndexes = true; + if (parameter.Equals("-p", StringComparison.InvariantCultureIgnoreCase)) + persistentMode = true; + } + + + // TODO: REMOVE. This does NOT look like Bouncer code. + List outputRegions = new List(); + foreach (Region region in TShock.Regions.Regions.OrderBy(r => r.Z).Reverse()) + { + if (!includeUnprotected && !region.DisableBuild) + continue; + if (tileX < region.Area.Left || tileX > region.Area.Right) + continue; + if (tileY < region.Area.Top || tileY > region.Area.Bottom) + continue; + + string format = "{1}"; + if (includeZIndexes) + format = "{1} (z:{0})"; + + outputRegions.Add(string.Format(format, region.Z, region.Name)); + } + + if (outputRegions.Count == 0) + { + if (includeUnprotected) + args.Player.SendInfoMessage("There are no regions at this point."); + else + args.Player.SendInfoMessage("There are no regions at this point or they are not protected."); + } + else + { + if (includeUnprotected) + args.Player.SendSuccessMessage("Regions at this point:"); + else + args.Player.SendSuccessMessage("Protected regions at this point:"); + + foreach (string line in PaginationTools.BuildLinesFromTerms(outputRegions)) + args.Player.SendMessage(line, Color.White); + } + + if (!persistentMode) + { + args.Player.AwaitingName = false; + args.Player.AwaitingNameParameters = null; + } + + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + // TODO: REMOVE. This does NOT look like Bouncer code. + if (args.Player.AwaitingTempPoint > 0) + { + args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].X = tileX; + args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].Y = tileY; + args.Player.SendInfoMessage("Set temp point {0}.", args.Player.AwaitingTempPoint); + args.Player.SendTileSquare(tileX, tileY, 4); + args.Player.AwaitingTempPoint = 0; + args.Handled = true; + return; + } + + Item selectedItem = args.Player.SelectedItem; + int lastKilledProj = args.Player.LastKilledProjectile; + ITile tile = Main.tile[tileX, tileY]; + + if (action == EditAction.PlaceTile) + { + if (TShock.TileBans.TileIsBanned(editData, args.Player)) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Player.SendErrorMessage("You do not have permission to place this tile."); + args.Handled = true; + return; + } + } + + if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type)) + { + //TPlayer.mount.Type 8 => Drill Containment Unit. + + // If the tile is an axe tile and they aren't selecting an axe, they're hacking. + if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + // If the tile is a hammer tile and they aren't selecting a hammer, they're hacking. + else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + // If the tile is a pickaxe tile and they aren't selecting a pickaxe, they're hacking. + // Item frames can be modified without pickaxe tile. + else if (tile.type != TileID.ItemFrame + && !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 && args.Player.TPlayer.mount.Type != 8 && selectedItem.pick == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + } + else if (action == EditAction.KillWall) + { + // If they aren't selecting a hammer, they could be hacking. + if (selectedItem.hammer == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0 && selectedItem.createWall == 0) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + else if (action == EditAction.PlaceTile && (projectileCreatesTile.ContainsKey(lastKilledProj) && editData == projectileCreatesTile[lastKilledProj])) + { + args.Player.LastKilledProjectile = 0; + } + else if (action == EditAction.PlaceTile || action == EditAction.PlaceWall) + { + if ((action == EditAction.PlaceTile && TShock.Config.PreventInvalidPlaceStyle) && + (MaxPlaceStyles.ContainsKey(editData) && style > MaxPlaceStyles[editData]) && + (ExtraneousPlaceStyles.ContainsKey(editData) && style > ExtraneousPlaceStyles[editData])) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + // If they aren't selecting the item which creates the tile or wall, they're hacking. + if (!(selectedItem.netID == ItemID.IceRod && editData == TileID.MagicalIceBlock) && + (editData != (action == EditAction.PlaceTile ? selectedItem.createTile : selectedItem.createWall) && + !(ropeCoilPlacements.ContainsKey(selectedItem.netID) && editData == ropeCoilPlacements[selectedItem.netID]))) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + // Using the actuation accessory can lead to actuator hacking + if (TShock.Itembans.ItemIsBanned("Actuator", args.Player) && args.Player.TPlayer.autoActuator) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Player.SendErrorMessage("You do not have permission to place actuators."); + args.Handled = true; + return; + } + if (TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(selectedItem.netID), args.Player) || editData >= (action == EditAction.PlaceTile ? Main.maxTileSets : Main.maxWallTypes)) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + if (action == EditAction.PlaceTile && (editData == TileID.PiggyBank || editData == TileID.Safes) && Main.ServerSideCharacter) + { + args.Player.SendErrorMessage("You cannot place this tile because server side characters are enabled."); + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + if (action == EditAction.PlaceTile && (editData == TileID.Containers || editData == TileID.Containers2)) + { + if (TShock.Utils.MaxChests()) + { + args.Player.SendErrorMessage("The world's chest limit has been reached - unable to place more."); + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Boulder) || + (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Boulder)) + { + args.Player.SendTileSquare(tileX, tileY, 3); + args.Handled = true; + return; + } + } + } + else if (action == EditAction.PlaceWire || action == EditAction.PlaceWire2 || action == EditAction.PlaceWire3) + { + // If they aren't selecting a wrench, they're hacking. + // WireKite = The Grand Design + if (selectedItem.type != ItemID.Wrench + && selectedItem.type != ItemID.BlueWrench + && selectedItem.type != ItemID.GreenWrench + && selectedItem.type != ItemID.YellowWrench + && selectedItem.type != ItemID.MulticolorWrench + && selectedItem.type != ItemID.WireKite) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + else if (action == EditAction.KillActuator || action == EditAction.KillWire || + action == EditAction.KillWire2 || action == EditAction.KillWire3) + { + // If they aren't selecting the wire cutter, they're hacking. + if (selectedItem.type != ItemID.WireCutter + && selectedItem.type != ItemID.WireKite + && selectedItem.type != ItemID.MulticolorWrench) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + else if (action == EditAction.PlaceActuator) + { + // If they aren't selecting the actuator and don't have the Presserator equipped, they're hacking. + if (selectedItem.type != ItemID.Actuator && !args.Player.TPlayer.autoActuator) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + } + if (TShock.Config.AllowCutTilesAndBreakables && Main.tileCut[tile.type]) + { + if (action == EditAction.KillWall) + { + args.Player.SendTileSquare(tileX, tileY, 1); + args.Handled = true; + return; + } + args.Handled = false; + return; + } + + if (args.Player.CheckIgnores()) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (TShock.CheckTilePermission(args.Player, tileX, tileY, editData, action)) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (TShock.CheckRangePermission(args.Player, tileX, tileY)) + { + if (action == EditAction.PlaceTile && (editData == TileID.Rope || editData == TileID.SilkRope || editData == TileID.VineRope || editData == TileID.WebRope)) + { + args.Handled = false; + return; + } + + if (action == EditAction.KillTile || action == EditAction.KillWall && ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0) + { + args.Handled = false; + return; + } + + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold) + { + args.Player.Disable("Reached TileKill threshold.", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) + { + args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole); + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendTileSquare(tileX, tileY, 4); + args.Handled = true; + return; + } + + if ((action == EditAction.PlaceTile || action == EditAction.PlaceWall) && !args.Player.HasPermission(Permissions.ignoreplacetiledetection)) + { + args.Player.TilePlaceThreshold++; + var coords = new Vector2(tileX, tileY); + lock (args.Player.TilesCreated) + if (!args.Player.TilesCreated.ContainsKey(coords)) + args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY]); + } + + if ((action == EditAction.KillTile || action == EditAction.KillTileNoItem || action == EditAction.KillWall) && Main.tileSolid[Main.tile[tileX, tileY].type] && + !args.Player.HasPermission(Permissions.ignorekilltiledetection)) + { + args.Player.TileKillThreshold++; + var coords = new Vector2(tileX, tileY); + lock (args.Player.TilesDestroyed) + if (!args.Player.TilesDestroyed.ContainsKey(coords)) + args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY]); + } + args.Handled = false; + return; + } + catch + { + args.Player.SendTileSquare(tileX, tileY, 4); + 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.CheckIgnores() || 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. + internal void OnSendTileSquare(object sender, GetDataHandlers.SendTileSquareEventArgs args) + { + short size = args.Size; + int tileX = args.TileX; + int tileY = args.TileY; + + if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) + { + args.Handled = false; + return; + } + + // From White: + // IIRC it's because 5 means a 5x5 square which is normal for a tile square, and anything bigger is a non-vanilla tile modification attempt + if (size > 5) + { + args.Handled = true; + return; + } + + if (args.Player.IsBouncerThrottled()) + { + args.Player.SendTileSquare(tileX, tileY, size); + args.Handled = true; + return; + } + + if (args.Player.CheckIgnores()) + { + args.Player.SendTileSquare(tileX, tileY, size); + args.Handled = true; + return; + } + + try + { + var tiles = new NetTile[size, size]; + for (int x = 0; x < size; x++) + { + for (int y = 0; y < size; y++) + { + tiles[x, y] = new NetTile(args.Data); + } + } + + bool changed = false; + for (int x = 0; x < size; x++) + { + int realx = tileX + x; + if (realx < 0 || realx >= Main.maxTilesX) + continue; + + for (int y = 0; y < size; y++) + { + int realy = tileY + y; + if (realy < 0 || realy >= Main.maxTilesY) + continue; + + var tile = Main.tile[realx, realy]; + var newtile = tiles[x, y]; + if (TShock.CheckTilePermission(args.Player, realx, realy) || + TShock.CheckRangePermission(args.Player, realx, realy)) + { + continue; + } + + // Fixes the Flower Boots not creating flowers issue + if (size == 1 && args.Player.Accessories.Any(i => i.active && i.netID == ItemID.FlowerBoots)) + { + if (Main.tile[realx, realy + 1].type == TileID.Grass && (newtile.Type == TileID.Plants || newtile.Type == TileID.Plants2)) + { + args.Handled = false; + return; + } + + if (Main.tile[realx, realy + 1].type == TileID.HallowedGrass && (newtile.Type == TileID.HallowedPlants || newtile.Type == TileID.HallowedPlants2)) + { + args.Handled = false; + return; + } + + if (Main.tile[realx, realy + 1].type == TileID.JungleGrass && newtile.Type == TileID.JunglePlants2) + { + args.Handled = false; + return; + } + } + + // Junction Box + if (tile.type == TileID.WirePipe) + { + args.Handled = false; + return; + } + + // Orientable tiles + if (tile.type == newtile.Type && orientableTiles.Contains(tile.type)) + { + Main.tile[realx, realy].frameX = newtile.FrameX; + Main.tile[realx, realy].frameY = newtile.FrameY; + changed = true; + } + // Landmine + if (tile.type == TileID.LandMine && !newtile.Active) + { + Main.tile[realx, realy].active(false); + changed = true; + } + // Sensors + if(newtile.Type == TileID.LogicSensor && !Main.tile[realx, realy].active()) + { + Main.tile[realx, realy].type = newtile.Type; + Main.tile[realx, realy].frameX = newtile.FrameX; + Main.tile[realx, realy].frameY = newtile.FrameY; + Main.tile[realx, realy].active(true); + changed = true; + } + + if (tile.active() && newtile.Active && tile.type != newtile.Type) + { + // Grass <-> Grass + if ((TileID.Sets.Conversion.Grass[tile.type] && TileID.Sets.Conversion.Grass[newtile.Type]) || + // Dirt <-> Dirt + ((tile.type == 0 || tile.type == 59) && + (newtile.Type == 0 || newtile.Type == 59)) || + // Ice <-> Ice + (TileID.Sets.Conversion.Ice[tile.type] && TileID.Sets.Conversion.Ice[newtile.Type]) || + // Stone <-> Stone + ((TileID.Sets.Conversion.Stone[tile.type] || Main.tileMoss[tile.type]) && + (TileID.Sets.Conversion.Stone[newtile.Type] || Main.tileMoss[newtile.Type])) || + // Sand <-> Sand + (TileID.Sets.Conversion.Sand[tile.type] && TileID.Sets.Conversion.Sand[newtile.Type]) || + // Sandstone <-> Sandstone + (TileID.Sets.Conversion.Sandstone[tile.type] && TileID.Sets.Conversion.Sandstone[newtile.Type]) || + // Hardened Sand <-> Hardened Sand + (TileID.Sets.Conversion.HardenedSand[tile.type] && TileID.Sets.Conversion.HardenedSand[newtile.Type])) + { + Main.tile[realx, realy].type = newtile.Type; + changed = true; + } + } + // Stone wall <-> Stone wall + if (((tile.wall == 1 || tile.wall == 3 || tile.wall == 28 || tile.wall == 83) && + (newtile.Wall == 1 || newtile.Wall == 3 || newtile.Wall == 28 || newtile.Wall == 83)) || + // Leaf wall <-> Leaf wall + (((tile.wall >= 63 && tile.wall <= 70) || tile.wall == 81) && + ((newtile.Wall >= 63 && newtile.Wall <= 70) || newtile.Wall == 81))) + { + Main.tile[realx, realy].wall = newtile.Wall; + changed = true; + } + + 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))) + { + Main.tile[realx, realy].type = newtile.Type; + Main.tile[realx, realy].frameX = newtile.FrameX; + Main.tile[realx, realy].frameY = newtile.FrameY; + Main.tile[realx, realy].active(newtile.Active); + changed = true; + } + } + } + + if (changed) + { + TSPlayer.All.SendTileSquare(tileX, tileY, size + 1); + WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size); + } + else + { + args.Player.SendTileSquare(tileX, tileY, size); + } + } + catch + { + args.Player.SendTileSquare(tileX, tileY, size); + } + args.Handled = false; + return; + } + + /// + /// Tile IDs that can be oriented: + /// Cannon, + /// Chairs, + /// Beds, + /// Bathtubs, + /// Statues, + /// Mannequin, + /// Traps, + /// MusicBoxes, + /// ChristmasTree, + /// WaterFountain, + /// Womannequin, + /// MinecartTrack, + /// WeaponsRack, + /// LunarMonolith, + /// TargetDummy, + /// Campfire + /// + private static int[] orientableTiles = new int[] + { + TileID.Cannon, + TileID.Chairs, + TileID.Beds, + TileID.Bathtubs, + TileID.Statues, + TileID.Mannequin, + TileID.Traps, + TileID.MusicBoxes, + TileID.ChristmasTree, + TileID.WaterFountain, + TileID.Womannequin, + TileID.MinecartTrack, + TileID.WeaponsRack, + TileID.ItemFrame, + TileID.LunarMonolith, + TileID.TargetDummy, + TileID.Campfire + }; + + } +} \ No newline at end of file diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 1964d071..79ed00c4 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -2044,19 +2044,19 @@ namespace TShockAPI case "goblin": case "goblins": TSPlayer.All.SendInfoMessage("{0} has started a goblin army invasion.", args.Player.Name); - TShock.StartInvasion(1); + TShock.Utils.StartInvasion(1); break; case "snowman": case "snowmen": TSPlayer.All.SendInfoMessage("{0} has started a snow legion invasion.", args.Player.Name); - TShock.StartInvasion(2); + TShock.Utils.StartInvasion(2); break; case "pirate": case "pirates": TSPlayer.All.SendInfoMessage("{0} has started a pirate invasion.", args.Player.Name); - TShock.StartInvasion(3); + TShock.Utils.StartInvasion(3); break; case "pumpkin": @@ -2098,7 +2098,7 @@ namespace TShockAPI case "martian": case "martians": TSPlayer.All.SendInfoMessage("{0} has started a martian invasion.", args.Player.Name); - TShock.StartInvasion(4); + TShock.Utils.StartInvasion(4); break; } } diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 3fa71b4d..959ec454 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -352,35 +352,80 @@ namespace TShockAPI } /// - /// For use in a TileKill event + /// For use in a PlaceChest event /// - public class TileKillEventArgs : HandledEventArgs + public class PlaceChestEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event + public TSPlayer Player { get; set; } + /// What the packet is doing (see MP packet docs). + public int Flag { get; set; } /// - /// The X coordinate that is being killed + /// The X coordinate /// public int TileX { get; set; } /// - /// The Y coordinate that is being killed + /// The Y coordinate /// public int TileY { get; set; } } /// - /// TileKill - When a tile is removed from the world + /// When a chest is added or removed from the world. /// - public static HandlerList TileKill; + public static HandlerList PlaceChest; - private static bool OnTileKill(int tilex, int tiley) + private static bool OnPlaceChest(TSPlayer player, int flag, int tilex, int tiley) { - if (TileKill == null) + if (PlaceChest == null) return false; - var args = new TileKillEventArgs + var args = new PlaceChestEventArgs { + Player = player, + Flag = flag, TileX = tilex, TileY = tiley, }; - TileKill.Invoke(null, args); + PlaceChest.Invoke(null, args); + return args.Handled; + } + + /// The arguments to the ProjectileKill packet. + public class ProjectileKillEventArgs : HandledEventArgs + { + /// The TSPlayer that fired the event. + public TSPlayer Player; + /// 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; + + /// Fires the ProjectileKill event. + /// The TSPlayer that caused the event. + /// 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, int identity, byte owner, int index) + { + if (ProjectileKill == null) + return false; + + var args = new ProjectileKillEventArgs + { + Player = player, + ProjectileIdentity = identity, + ProjectileOwner = owner, + ProjectileIndex = index, + }; + + ProjectileKill.Invoke(null, args); return args.Handled; } @@ -389,6 +434,8 @@ namespace TShockAPI /// public class KillMeEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } /// /// The Terraria playerID of the player /// @@ -405,23 +452,27 @@ namespace TShockAPI /// 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; - private static bool OnKillMe(byte plr, byte direction, short damage, bool pvp) + private static bool OnKillMe(TSPlayer player, byte plr, byte direction, short damage, bool pvp, PlayerDeathReason playerDeathReason) { if (KillMe == null) return false; var args = new KillMeEventArgs { + Player = player, PlayerId = plr, Direction = direction, Damage = damage, Pvp = pvp, + PlayerDeathReason = playerDeathReason, }; KillMe.Invoke(null, args); return args.Handled; @@ -432,16 +483,18 @@ namespace TShockAPI /// public class PlayerUpdateEventArgs : HandledEventArgs { + /// The TSPlayer object that triggered the event + public TSPlayer Player { get; set; } /// /// The Terraria playerID of the player /// public byte PlayerId { get; set; } /// - /// ??? + /// Control direction (BitFlags) /// public byte Control { get; set; } /// - /// Current item? + /// Selected item /// public byte Item { get; set; } /// @@ -452,7 +505,7 @@ namespace TShockAPI /// Velocity of the player /// public Vector2 Velocity { get; set; } - + /// Pulley update (BitFlags) public byte Pulley { get; set; } } /// @@ -460,14 +513,15 @@ namespace TShockAPI /// public static HandlerList PlayerUpdate; - private static bool OnPlayerUpdate(byte player, byte control, byte item, Vector2 position, Vector2 velocity, byte pulley) + private static bool OnPlayerUpdate(TSPlayer player, byte plr, byte control, byte item, Vector2 position, Vector2 velocity, byte pulley) { if (PlayerUpdate == null) return false; var args = new PlayerUpdateEventArgs { - PlayerId = player, + Player = player, + PlayerId = plr, Control = control, Item = item, Position = position, @@ -532,48 +586,157 @@ namespace TShockAPI return false; } + /// The event args object for the HealOtherPlayer event + public class HealOtherPlayerEventArgs : HandledEventArgs + { + /// The TSPlayer object that caused the event + public TSPlayer Player { get; set; } + + /// 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; + + /// Fires the HealOtherPlayer event + /// The TSPlayer that started the event + /// The Terraria player index that the event targets + /// The amount to heal + /// bool + private static bool OnHealOtherPlayer(TSPlayer player, byte targetPlayerIndex, short amount) + { + if (HealOtherPlayer == null) + return false; + + var args = new HealOtherPlayerEventArgs + { + Player = player, + TargetPlayerIndex = targetPlayerIndex, + Amount = amount, + }; + + HealOtherPlayer.Invoke(null, args); + return args.Handled; + } + /// /// For use in a SendTileSquare event /// public class SendTileSquareEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } + + /// The raw memory stream from the original event + public MemoryStream Data { get; set; } + /// /// Size of the area /// public short Size { get; set; } + /// /// A corner of the section /// public int TileX { get; set; } + /// /// A corner of the section /// public int TileY { get; set; } } /// - /// SendTileSquare - When the player sends a tile square + /// When the player sends a tile square /// public static HandlerList SendTileSquare; - private static bool OnSendTileSquare(short size, int tilex, int tiley) + private static bool OnSendTileSquare(TSPlayer player, MemoryStream data, short size, int tilex, int tiley) { if (SendTileSquare == null) return false; var args = new SendTileSquareEventArgs { + Player = player, + Data = data, Size = size, TileX = tilex, TileY = tiley, }; + SendTileSquare.Invoke(null, args); return args.Handled; } + + /// The arguments to the PlaceObject hook. + public class PlaceObjectEventArgs : HandledEventArgs + { + /// The calling Player. + public TSPlayer Player { get; set; } + + /// 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; } + + /// 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; + + /// Fires the PlaceObject hook. To be called when an object is placed in the world. + /// The originating player. + /// 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, short x, short y, short type, short style, byte alternate, bool direction) + { + if (PlaceObject == null) + return false; + + var args = new PlaceObjectEventArgs + { + Player = player, + X = x, + Y = y, + Type = type, + Style = style, + Alternate = alternate, + Direction = direction + }; + + PlaceObject.Invoke(null, args); + return args.Handled; + } + + /// /// For use in a NewProjectile event /// public class NewProjectileEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the new projectile. + public TSPlayer Player { get; set; } /// /// ??? /// @@ -612,7 +775,7 @@ namespace TShockAPI /// public static HandlerList NewProjectile; - private static bool OnNewProjectile(short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index) + private static bool OnNewProjectile(short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index, TSPlayer player) { if (NewProjectile == null) return false; @@ -627,6 +790,7 @@ namespace TShockAPI Owner = owner, Type = type, Index = index, + Player = player, }; NewProjectile.Invoke(null, args); return args.Handled; @@ -637,6 +801,8 @@ namespace TShockAPI /// public class LiquidSetEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } /// /// X location of the tile /// @@ -659,13 +825,14 @@ namespace TShockAPI /// public static HandlerList LiquidSet; - private static bool OnLiquidSet(int tilex, int tiley, byte amount, byte type) + private static bool OnLiquidSet(TSPlayer player, int tilex, int tiley, byte amount, byte type) { if (LiquidSet == null) return false; var args = new LiquidSetEventArgs { + Player = player, TileX = tilex, TileY = tiley, Amount = amount, @@ -755,6 +922,8 @@ namespace TShockAPI /// public class ChestItemEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } /// /// ChestID /// @@ -781,13 +950,14 @@ namespace TShockAPI /// public static HandlerList ChestItemChange; - private static bool OnChestItemChange(short id, byte slot, short stacks, byte prefix, short type) + private static bool OnChestItemChange(TSPlayer player, short id, byte slot, short stacks, byte prefix, short type) { if (ChestItemChange == null) return false; var args = new ChestItemEventArgs { + Player = player, ID = id, Slot = slot, Stacks = stacks, @@ -841,6 +1011,8 @@ namespace TShockAPI /// public class NPCHomeChangeEventArgs : HandledEventArgs { + /// The TSPlayer that caused the event. + public TSPlayer Player { get; set; } /// /// The Terraria playerID of the player /// @@ -863,13 +1035,14 @@ namespace TShockAPI /// public static HandlerList NPCHome; - private static bool OnUpdateNPCHome(short id, short x, short y, byte homeless) + private static bool OnUpdateNPCHome(TSPlayer player, short id, short x, short y, byte homeless) { if (NPCHome == null) return false; var args = new NPCHomeChangeEventArgs { + Player = player, ID = id, X = x, Y = y, @@ -884,6 +1057,7 @@ namespace TShockAPI /// public class PlayerBuffEventArgs : HandledEventArgs { + public TSPlayer Player { get; set; } /// /// The Terraria playerID of the player /// @@ -902,13 +1076,14 @@ namespace TShockAPI /// public static HandlerList PlayerBuff; - private static bool OnPlayerBuff(byte id, byte type, int time) + private static bool OnPlayerBuff(TSPlayer player, byte id, byte type, int time) { if (PlayerBuff == null) return false; var args = new PlayerBuffEventArgs { + Player = player, ID = id, Type = type, Time = time @@ -986,6 +1161,7 @@ namespace TShockAPI /// public class PlayerDamageEventArgs : HandledEventArgs { + public TSPlayer Player { get; set; } /// /// The Terraria playerID of the player /// @@ -1006,24 +1182,28 @@ namespace TShockAPI /// Is the damage critical? /// public bool Critical { get; set; } + /// The reason the player took damage and/or died. + public PlayerDeathReason PlayerDeathReason { get; set; } } /// /// PlayerDamage - Called when a player is damaged /// public static HandlerList PlayerDamage; - private static bool OnPlayerDamage(byte id, byte dir, short dmg, bool pvp, bool crit) + private static bool OnPlayerDamage(TSPlayer player, byte id, byte dir, short dmg, bool pvp, bool crit, PlayerDeathReason playerDeathReason) { if (PlayerDamage == null) return false; var args = new PlayerDamageEventArgs { + Player = player, ID = id, Direction = dir, Damage = dmg, PVP = pvp, Critical = crit, + PlayerDeathReason = playerDeathReason, }; PlayerDamage.Invoke(null, args); return args.Handled; @@ -1034,6 +1214,8 @@ namespace TShockAPI /// public class NPCStrikeEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } /// /// ??? /// @@ -1060,13 +1242,14 @@ namespace TShockAPI /// public static HandlerList NPCStrike; - private static bool OnNPCStrike(short id, byte dir, short dmg, float knockback, byte crit) + private static bool OnNPCStrike(TSPlayer player, short id, byte dir, short dmg, float knockback, byte crit) { if (NPCStrike == null) return false; var args = new NPCStrikeEventArgs { + Player = player, ID = id, Direction = dir, Damage = dmg, @@ -1077,6 +1260,86 @@ namespace TShockAPI return args.Handled; } + /// The arguments to the MassWireOperation event. + public class MassWireOperationEventArgs : HandledEventArgs + { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } + + /// 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; + + private static bool OnMassWireOperation(TSPlayer player, short startX, short startY, short endX, short endY, byte toolMode) + { + if (MassWireOperation == null) + return false; + + var args = new MassWireOperationEventArgs + { + Player = player, + 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 : HandledEventArgs + { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } + + /// 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; + + private static bool OnPlaceTileEntity(TSPlayer player, short x, short y, byte type) + { + if (PlaceTileEntity == null) + return false; + + var args = new PlaceTileEntityEventArgs + { + Player = player, + X = x, + Y = y, + Type = type + }; + + PlaceTileEntity.Invoke(null, args); + return args.Handled; + } + /// /// For use with a NPCSpecial event /// @@ -1115,6 +1378,8 @@ namespace TShockAPI /// public class PlayerAnimationEventArgs : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } } /// @@ -1122,12 +1387,15 @@ namespace TShockAPI /// public static HandlerList PlayerAnimation; - private static bool OnPlayerAnimation() + private static bool OnPlayerAnimation(TSPlayer player) { if (PlayerAnimation == null) return false; - var args = new PlayerAnimationEventArgs { }; + var args = new PlayerAnimationEventArgs + { + Player = player, + }; PlayerAnimation.Invoke(null, args); return args.Handled; } @@ -1230,8 +1498,7 @@ namespace TShockAPI { PacketTypes.ProjectileNew, HandleProjectileNew }, { PacketTypes.TogglePvp, HandleTogglePvp }, { PacketTypes.PlayerTeam, HandlePlayerTeam }, - { PacketTypes.PlaceChest, HandleTileKill }, - { PacketTypes.PlayerKillMe, HandlePlayerKillMe }, + { PacketTypes.PlaceChest, HandlePlaceChest }, { PacketTypes.LiquidSet, HandleLiquidSet }, { PacketTypes.PlayerSpawn, HandleSpawn }, { PacketTypes.ChestGetContents, HandleChestOpen }, @@ -1247,7 +1514,6 @@ namespace TShockAPI { PacketTypes.ItemOwner, HandleItemOwner }, { PacketTypes.PlayerHp, HandlePlayerHp }, { PacketTypes.PlayerMana, HandlePlayerMana }, - { PacketTypes.PlayerDamage, HandlePlayerDamage }, { PacketTypes.NpcStrike, HandleNpcStrike }, { PacketTypes.NpcSpecial, HandleSpecial }, { PacketTypes.PlayerAnimation, HandlePlayerAnimation }, @@ -1302,29 +1568,9 @@ namespace TShockAPI byte plr = args.Data.ReadInt8(); short amount = args.Data.ReadInt16(); - if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active) - { + if (OnHealOtherPlayer(args.Player, plr, amount)) return true; - } - if (amount > TShock.Config.MaxDamage * 0.2) - { - args.Player.Disable("HealOtherPlayer cheat attempt!", DisableFlags.WriteToLogAndConsole); - return true; - } - - if (args.Player.HealOtherThreshold > TShock.Config.HealOtherThreshold) - { - args.Player.Disable("Reached HealOtherPlayer threshold.", DisableFlags.WriteToLogAndConsole); - return true; - } - - if (TShock.CheckIgnores(args.Player) || (DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - return true; - } - - args.Player.HealOtherThreshold++; return false; } @@ -1684,213 +1930,18 @@ namespace TShockAPI return false; } - /// - /// Tile IDs that can be oriented: - /// Cannon, - /// Chairs, - /// Beds, - /// Bathtubs, - /// Statues, - /// Mannequin, - /// Traps, - /// MusicBoxes, - /// ChristmasTree, - /// WaterFountain, - /// Womannequin, - /// MinecartTrack, - /// WeaponsRack, - /// LunarMonolith, - /// TargetDummy, - /// Campfire - /// - private static int[] orientableTiles = new int[] - { - TileID.Cannon, - TileID.Chairs, - TileID.Beds, - TileID.Bathtubs, - TileID.Statues, - TileID.Mannequin, - TileID.Traps, - TileID.MusicBoxes, - TileID.ChristmasTree, - TileID.WaterFountain, - TileID.Womannequin, - TileID.MinecartTrack, - TileID.WeaponsRack, - TileID.ItemFrame, - TileID.LunarMonolith, - TileID.TargetDummy, - TileID.Campfire - }; - private static bool HandleSendTileSquare(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; - if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) - return false; - - if (OnSendTileSquare(size, tileX, tileY)) + if (OnSendTileSquare(player, data, size, tileX, tileY)) return true; - if (size > 5) - return true; - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY, size); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY, size); - return true; - } - - try - { - var tiles = new NetTile[size, size]; - for (int x = 0; x < size; x++) - { - for (int y = 0; y < size; y++) - { - tiles[x, y] = new NetTile(args.Data); - } - } - - bool changed = false; - for (int x = 0; x < size; x++) - { - int realx = tileX + x; - if (realx < 0 || realx >= Main.maxTilesX) - continue; - - for (int y = 0; y < size; y++) - { - int realy = tileY + y; - if (realy < 0 || realy >= Main.maxTilesY) - continue; - - var tile = Main.tile[realx, realy]; - var newtile = tiles[x, y]; - if (TShock.CheckTilePermission(args.Player, realx, realy) || - TShock.CheckRangePermission(args.Player, realx, realy)) - { - continue; - } - - // Fixes the Flower Boots not creating flowers issue - if (size == 1 && args.Player.Accessories.Any(i => i.active && i.netID == ItemID.FlowerBoots)) - { - if (Main.tile[realx, realy + 1].type == TileID.Grass && (newtile.Type == TileID.Plants || newtile.Type == TileID.Plants2)) - { - return false; - } - - if (Main.tile[realx, realy + 1].type == TileID.HallowedGrass && (newtile.Type == TileID.HallowedPlants || newtile.Type == TileID.HallowedPlants2)) - { - return false; - } - - if (Main.tile[realx, realy + 1].type == TileID.JungleGrass && newtile.Type == TileID.JunglePlants2) - { - return false; - } - } - - // Junction Box - if (tile.type == TileID.WirePipe) - return false; - - // Orientable tiles - if (tile.type == newtile.Type && orientableTiles.Contains(tile.type)) - { - Main.tile[realx, realy].frameX = newtile.FrameX; - Main.tile[realx, realy].frameY = newtile.FrameY; - changed = true; - } - // Landmine - if (tile.type == TileID.LandMine && !newtile.Active) - { - Main.tile[realx, realy].active(false); - changed = true; - } - // Sensors - if(newtile.Type == TileID.LogicSensor && !Main.tile[realx, realy].active()) - { - Main.tile[realx, realy].type = newtile.Type; - Main.tile[realx, realy].frameX = newtile.FrameX; - Main.tile[realx, realy].frameY = newtile.FrameY; - Main.tile[realx, realy].active(true); - changed = true; - } - - if (tile.active() && newtile.Active && tile.type != newtile.Type) - { - // Grass <-> Grass - if ((TileID.Sets.Conversion.Grass[tile.type] && TileID.Sets.Conversion.Grass[newtile.Type]) || - // Dirt <-> Dirt - ((tile.type == 0 || tile.type == 59) && - (newtile.Type == 0 || newtile.Type == 59)) || - // Ice <-> Ice - (TileID.Sets.Conversion.Ice[tile.type] && TileID.Sets.Conversion.Ice[newtile.Type]) || - // Stone <-> Stone - ((TileID.Sets.Conversion.Stone[tile.type] || Main.tileMoss[tile.type]) && - (TileID.Sets.Conversion.Stone[newtile.Type] || Main.tileMoss[newtile.Type])) || - // Sand <-> Sand - (TileID.Sets.Conversion.Sand[tile.type] && TileID.Sets.Conversion.Sand[newtile.Type]) || - // Sandstone <-> Sandstone - (TileID.Sets.Conversion.Sandstone[tile.type] && TileID.Sets.Conversion.Sandstone[newtile.Type]) || - // Hardened Sand <-> Hardened Sand - (TileID.Sets.Conversion.HardenedSand[tile.type] && TileID.Sets.Conversion.HardenedSand[newtile.Type])) - { - Main.tile[realx, realy].type = newtile.Type; - changed = true; - } - } - // Stone wall <-> Stone wall - if (((tile.wall == 1 || tile.wall == 3 || tile.wall == 28 || tile.wall == 83) && - (newtile.Wall == 1 || newtile.Wall == 3 || newtile.Wall == 28 || newtile.Wall == 83)) || - // Leaf wall <-> Leaf wall - (((tile.wall >= 63 && tile.wall <= 70) || tile.wall == 81) && - ((newtile.Wall >= 63 && newtile.Wall <= 70) || newtile.Wall == 81))) - { - Main.tile[realx, realy].wall = newtile.Wall; - changed = true; - } - - 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))) - { - Main.tile[realx, realy].type = newtile.Type; - Main.tile[realx, realy].frameX = newtile.FrameX; - Main.tile[realx, realy].frameY = newtile.FrameY; - Main.tile[realx, realy].active(newtile.Active); - changed = true; - } - } - } - - if (changed) - { - TSPlayer.All.SendTileSquare(tileX, tileY, size + 1); - WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size); - } - else - { - args.Player.SendTileSquare(tileX, tileY, size); - } - } - catch - { - args.Player.SendTileSquare(tileX, tileY, size); - } - return true; + return false; } public enum EditAction @@ -1924,7 +1975,7 @@ namespace TShockAPI /// /// Tiles that can be broken without any pickaxes/etc. /// - private static int[] breakableTiles = new int[] + internal static int[] breakableTiles = new int[] { TileID.Books, TileID.Bottles, @@ -1948,7 +1999,7 @@ namespace TShockAPI /// /// These projectiles create tiles on death. /// - private static Dictionary projectileCreatesTile = new Dictionary + internal static Dictionary projectileCreatesTile = new Dictionary { { ProjectileID.DirtBall, TileID.Dirt }, { ProjectileID.SandBallGun, TileID.Sand }, @@ -1957,7 +2008,7 @@ namespace TShockAPI { ProjectileID.CrimsandBallGun, TileID.Crimsand }, }; - private static Dictionary ropeCoilPlacements = new Dictionary + internal static Dictionary ropeCoilPlacements = new Dictionary { {ItemID.RopeCoil, TileID.Rope}, {ItemID.SilkRopeCoil, TileID.SilkRope}, @@ -1968,7 +2019,7 @@ namespace TShockAPI /// /// Extra place style limits for strange hardcoded values in Terraria /// - private static Dictionary ExtraneousPlaceStyles = new Dictionary + internal static Dictionary ExtraneousPlaceStyles = new Dictionary { {TileID.MinecartTrack, 3} }; @@ -1978,332 +2029,21 @@ namespace TShockAPI 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; - try - { - 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(); - var style = args.Data.ReadInt8(); - - if (editData < 0) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (OnTileEdit(args.Player, tileX, tileY, action, type, editData, style)) - return true; - if (!TShock.Utils.TilePlacementValid(tileX, tileY)) - return true; - if (action == EditAction.KillTile && Main.tile[tileX, tileY].type == TileID.MagicalIceBlock) - return false; - if (args.Player.Dead && TShock.Config.PreventDeadModification) - return true; - - if (args.Player.AwaitingName) - { - Debug.Assert(args.Player.AwaitingNameParameters != null); - - bool includeUnprotected = false; - bool includeZIndexes = false; - bool persistentMode = false; - foreach (string parameter in args.Player.AwaitingNameParameters) - { - if (parameter.Equals("-u", StringComparison.InvariantCultureIgnoreCase)) - includeUnprotected = true; - if (parameter.Equals("-z", StringComparison.InvariantCultureIgnoreCase)) - includeZIndexes = true; - if (parameter.Equals("-p", StringComparison.InvariantCultureIgnoreCase)) - persistentMode = true; - } - - List outputRegions = new List(); - foreach (Region region in TShock.Regions.Regions.OrderBy(r => r.Z).Reverse()) - { - if (!includeUnprotected && !region.DisableBuild) - continue; - if (tileX < region.Area.Left || tileX > region.Area.Right) - continue; - if (tileY < region.Area.Top || tileY > region.Area.Bottom) - continue; - - string format = "{1}"; - if (includeZIndexes) - format = "{1} (z:{0})"; - - outputRegions.Add(string.Format(format, region.Z, region.Name)); - } - - if (outputRegions.Count == 0) - { - if (includeUnprotected) - args.Player.SendInfoMessage("There are no regions at this point."); - else - args.Player.SendInfoMessage("There are no regions at this point or they are not protected."); - } - else - { - if (includeUnprotected) - args.Player.SendSuccessMessage("Regions at this point:"); - else - args.Player.SendSuccessMessage("Protected regions at this point:"); - - foreach (string line in PaginationTools.BuildLinesFromTerms(outputRegions)) - args.Player.SendMessage(line, Color.White); - } - - if (!persistentMode) - { - args.Player.AwaitingName = false; - args.Player.AwaitingNameParameters = null; - } - - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (args.Player.AwaitingTempPoint > 0) - { - args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].X = tileX; - args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].Y = tileY; - args.Player.SendInfoMessage("Set temp point {0}.", args.Player.AwaitingTempPoint); - args.Player.SendTileSquare(tileX, tileY, 4); - args.Player.AwaitingTempPoint = 0; - return true; - } - - Item selectedItem = args.Player.SelectedItem; - int lastKilledProj = args.Player.LastKilledProjectile; - ITile tile = Main.tile[tileX, tileY]; - - if (action == EditAction.PlaceTile) - { - if (TShock.TileBans.TileIsBanned(editData, args.Player)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Player.SendErrorMessage("You do not have permission to place this tile."); - return true; - } - } - - if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type)) - { - //TPlayer.mount.Type 8 => Drill Containment Unit. - - // If the tile is an axe tile and they aren't selecting an axe, they're hacking. - if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - // If the tile is a hammer tile and they aren't selecting a hammer, they're hacking. - else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - // If the tile is a pickaxe tile and they aren't selecting a pickaxe, they're hacking. - // Item frames can be modified without pickaxe tile. - else if (tile.type != TileID.ItemFrame - && !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 && args.TPlayer.mount.Type != 8 && selectedItem.pick == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - } - else if (action == EditAction.KillWall) - { - // If they aren't selecting a hammer, they could be hacking. - if (selectedItem.hammer == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0 && selectedItem.createWall == 0) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - } - else if (action == EditAction.PlaceTile && (projectileCreatesTile.ContainsKey(lastKilledProj) && editData == projectileCreatesTile[lastKilledProj])) - { - args.Player.LastKilledProjectile = 0; - } - else if (action == EditAction.PlaceTile || action == EditAction.PlaceWall) - { - if ((action == EditAction.PlaceTile && TShock.Config.PreventInvalidPlaceStyle) && - (MaxPlaceStyles.ContainsKey(editData) && style > MaxPlaceStyles[editData]) && - (ExtraneousPlaceStyles.ContainsKey(editData) && style > ExtraneousPlaceStyles[editData])) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - // If they aren't selecting the item which creates the tile or wall, they're hacking. - if (!(selectedItem.netID == ItemID.IceRod && editData == TileID.MagicalIceBlock) && - (editData != (action == EditAction.PlaceTile ? selectedItem.createTile : selectedItem.createWall) && - !(ropeCoilPlacements.ContainsKey(selectedItem.netID) && editData == ropeCoilPlacements[selectedItem.netID]))) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - // Using the actuation accessory can lead to actuator hacking - if (TShock.Itembans.ItemIsBanned("Actuator", args.Player) && args.Player.TPlayer.autoActuator) - { - args.Player.SendTileSquare(tileX, tileY, 1); - args.Player.SendErrorMessage("You do not have permission to place actuators."); - return true; - } - if (TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(selectedItem.netID), args.Player) || editData >= (action == EditAction.PlaceTile ? Main.maxTileSets : Main.maxWallTypes)) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - if (action == EditAction.PlaceTile && (editData == 29 || editData == 97) && Main.ServerSideCharacter) - { - args.Player.SendErrorMessage("You cannot place this tile because server side characters are enabled."); - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - if (action == EditAction.PlaceTile && (editData == TileID.Containers || editData == TileID.Containers2)) - { - if (TShock.Utils.MaxChests()) - { - args.Player.SendErrorMessage("The world's chest limit has been reached - unable to place more."); - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Boulder) || - (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Boulder)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - } - } - else if (action == EditAction.PlaceWire || action == EditAction.PlaceWire2 || action == EditAction.PlaceWire3) - { - // If they aren't selecting a wrench, they're hacking. - // WireKite = The Grand Design - if (selectedItem.type != ItemID.Wrench - && selectedItem.type != ItemID.BlueWrench - && selectedItem.type != ItemID.GreenWrench - && selectedItem.type != ItemID.YellowWrench - && selectedItem.type != ItemID.MulticolorWrench - && selectedItem.type != ItemID.WireKite) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - } - else if (action == EditAction.KillActuator || action == EditAction.KillWire || - action == EditAction.KillWire2 || action == EditAction.KillWire3) - { - // If they aren't selecting the wire cutter, they're hacking. - if (selectedItem.type != ItemID.WireCutter - && selectedItem.type != ItemID.WireKite - && selectedItem.type != ItemID.MulticolorWrench) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - } - else if (action == EditAction.PlaceActuator) - { - // If they aren't selecting the actuator and don't have the Presserator equipped, they're hacking. - if (selectedItem.type != ItemID.Actuator && !args.Player.TPlayer.autoActuator) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - } - if (TShock.Config.AllowCutTilesAndBreakables && Main.tileCut[tile.type]) - { - if (action == EditAction.KillWall) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - return false; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY, editData, action)) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) - { - if (action == EditAction.PlaceTile && (editData == TileID.Rope || editData == TileID.SilkRope || editData == TileID.VineRope || editData == TileID.WebRope)) - { - return false; - } - - if (action == EditAction.KillTile || action == EditAction.KillWall && ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0) - { - return false; - } - - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold) - { - args.Player.Disable("Reached TileKill threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) - { - args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY, 4); - return true; - } - - if ((action == EditAction.PlaceTile || action == EditAction.PlaceWall) && !args.Player.HasPermission(Permissions.ignoreplacetiledetection)) - { - args.Player.TilePlaceThreshold++; - var coords = new Vector2(tileX, tileY); - lock (args.Player.TilesCreated) - if (!args.Player.TilesCreated.ContainsKey(coords)) - args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY]); - } - - if ((action == EditAction.KillTile || action == EditAction.KillTileNoItem || action == EditAction.KillWall) && Main.tileSolid[Main.tile[tileX, tileY].type] && - !args.Player.HasPermission(Permissions.ignorekilltiledetection)) - { - args.Player.TileKillThreshold++; - var coords = new Vector2(tileX, tileY); - lock (args.Player.TilesDestroyed) - if (!args.Player.TilesDestroyed.ContainsKey(coords)) - args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY]); - } - return false; - } - catch - { - args.Player.SendTileSquare(tileX, tileY, 4); + if (OnTileEdit(args.Player, tileX, tileY, action, type, editData, style)) return true; - } - } + return false; + } /// /// Handle PlaceObject event @@ -2317,96 +2057,9 @@ namespace TShockAPI byte alternate = args.Data.ReadInt8(); bool direction = args.Data.ReadBoolean(); - if (type < 0 || type >= Main.maxTileSets) + if (OnPlaceObject(args.Player, x, y, type, style, alternate, direction)) return true; - if (x < 0 || x >= Main.maxTilesX) - return true; - - if (y < 0 || y >= Main.maxTilesY) - return true; - - //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); - return true; - } - - 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."); - return true; - } - - if (!TShock.Utils.TilePlacementValid(x, y)) - return true; - if (args.Player.Dead && TShock.Config.PreventDeadModification) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(x, y, 4); - return true; - } - - // 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.TPlayer.inventory[args.TPlayer.selectedItem].createTile) - { - args.Player.SendTileSquare(x, y, 4); - return true; - } - - TileObjectData tileData = TileObjectData.GetTileData(type, style, 0); - if (tileData == null) - return true; - - 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 (TShock.CheckTilePermission(args.Player, i, j, type, EditAction.PlaceTile)) - { - args.Player.SendTileSquare(i, j, 4); - return true; - } - } - } - - // Ignore rope placement range - if ((type != TileID.Rope - || type != TileID.SilkRope - || type != TileID.VineRope - || type != TileID.WebRope) - && TShock.CheckRangePermission(args.Player, x, y)) - { - args.Player.SendTileSquare(x, y, 4); - return true; - } - - if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) - { - args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(x, y, 4); - return true; - } - - 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]); - } - return false; } @@ -2532,7 +2185,7 @@ namespace TShockAPI { if (args.Player == null || args.TPlayer == null || args.Data == null) { - return false; + return true; } byte plr = args.Data.ReadInt8(); @@ -2544,86 +2197,12 @@ namespace TShockAPI if (pulley[2]) vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - if (OnPlayerUpdate(plr, control, item, pos, vel, pulley)) + if (OnPlayerUpdate(args.Player, plr, control, item, pos, vel, pulley)) return true; - if (pos.X < 0 || pos.Y < 0 || pos.X >= Main.maxTilesX * 16 - 16 || pos.Y >= Main.maxTilesY * 16 - 16) - { - return true; - } - - if (item < 0 || item >= args.TPlayer.inventory.Length) - { - return true; - } - - if (args.Player.LastNetPosition == Vector2.Zero) - { - return true; - } - - if (!pos.Equals(args.Player.LastNetPosition)) - { - float distance = Vector2.Distance(new Vector2(pos.X / 16f, pos.Y / 16f), - new Vector2(args.Player.LastNetPosition.X / 16f, args.Player.LastNetPosition.Y / 16f)); - if (TShock.CheckIgnores(args.Player)) - { - if (distance > TShock.Config.MaxRangeForDisabled) - { - if (args.Player.IgnoreActionsForCheating != "none") - { - args.Player.SendErrorMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating); - } - else if (args.Player.IgnoreActionsForDisabledArmor != "none") - { - args.Player.SendErrorMessage("Disabled for banned armor: " + args.Player.IgnoreActionsForDisabledArmor); - } - else if (args.Player.IgnoreActionsForInventory != "none") - { - args.Player.SendErrorMessage("Disabled for Server Side Inventory: " + args.Player.IgnoreActionsForInventory); - } - else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn) - { - args.Player.SendErrorMessage("Please /register or /login to play!"); - } - else if (args.Player.IgnoreActionsForClearingTrashCan) - { - args.Player.SendErrorMessage("You need to rejoin to ensure your trash can is cleared!"); - } - var lastTileX = args.Player.LastNetPosition.X; - var lastTileY = args.Player.LastNetPosition.Y - 48; - if (!args.Player.Teleport(lastTileX, lastTileY)) - { - args.Player.Spawn(); - } - return true; - } - return true; - } - - if (args.Player.Dead) - { - return true; - } - - if (!args.Player.HasPermission(Permissions.ignorenoclipdetection) && - TSCheckNoclip(pos, args.TPlayer.width, args.TPlayer.height - (args.TPlayer.mount.Active ? args.Player.TPlayer.mount.HeightBoost : 0)) && !TShock.Config.IgnoreNoClip - && !args.TPlayer.tongued) - { - var lastTileX = args.Player.LastNetPosition.X; - var lastTileY = args.Player.LastNetPosition.Y; - if (!args.Player.Teleport(lastTileX, lastTileY)) - { - args.Player.SendErrorMessage("You got stuck in a solid object, Sent to spawn point."); - args.Player.Spawn(); - } - return true; - } - args.Player.LastNetPosition = pos; - } - if (control[5]) { + // ItemBan system string itemName = args.TPlayer.inventory[item].Name; if (TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(args.TPlayer.inventory[item].netID), args.Player)) { @@ -2632,6 +2211,7 @@ namespace TShockAPI args.Player.SendErrorMessage("You cannot use {0} on this server. Your actions are being ignored.", itemName); } + // Reimplementation of normal Terraria stuff? if (args.TPlayer.inventory[item].Name == "Mana Crystal" && args.Player.TPlayer.statManaMax <= 180) { args.Player.TPlayer.statMana += 20; @@ -2652,6 +2232,7 @@ namespace TShockAPI } } + // Where we rebuild sync data for Terraria? args.TPlayer.selectedItem = item; args.TPlayer.position = pos; args.TPlayer.oldVelocity = args.TPlayer.velocity; @@ -2708,7 +2289,6 @@ namespace TShockAPI args.TPlayer.direction = -1; } - if (args.Player.Confused && Main.ServerSideCharacter && args.Player.IsLoggedIn) { if (args.TPlayer.controlUp) @@ -2733,7 +2313,6 @@ namespace TShockAPI args.TPlayer.controlLeft = true; } - args.TPlayer.Update(args.TPlayer.whoAmI); NetMessage.SendData((int)PacketTypes.PlayerUpdate, -1, -1, NetworkText.Empty, args.Player.Index); return true; @@ -2764,100 +2343,11 @@ namespace TShockAPI ai[i] = 0f; } - var index = TShock.Utils.SearchProjectile(ident, owner); - if (OnNewProjectile(ident, pos, vel, knockback, dmg, owner, type, index)) + if (OnNewProjectile(ident, pos, vel, knockback, dmg, owner, type, index, args.Player)) return true; - if (index > Main.maxProjectiles || index < 0) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - 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); - return true; - } - - if (dmg > 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); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - bool hasPermission = !TShock.CheckProjectilePermission(args.Player, 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); - } - return true; - } - - if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold) - { - args.Player.Disable("Reached projectile update threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - 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; - //return true; - } - return false; } @@ -2868,110 +2358,26 @@ namespace TShockAPI owner = (byte)args.Player.Index; var index = TShock.Utils.SearchProjectile(ident, owner); - if (index > Main.maxProjectiles || index < 0) + if (OnProjectileKill(args.Player, ident, owner, index)) { - return false; + return true; } var type = Main.projectile[index].type; - // Players can no longer destroy projectiles that are not theirs as of 1.1.2 - /*if (args.Player.Index != Main.projectile[index].owner && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) // workaround for skeletron prime projectiles - { - args.Player.Disable(String.Format("Owner ({0}) and player ID ({1}) does not match to kill projectile of type: {3}", Main.projectile[index].owner, args.Player.Index, type)); - args.Player.RemoveProjectile(ident, owner); - return true; - }*/ - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if (TShock.CheckProjectilePermission(args.Player, index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) + // 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; } - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - args.Player.LastKilledProjectile = type; return false; } - private static bool HandlePlayerKillMe(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var direction = (byte)(args.Data.ReadInt8() - 1); - var dmg = args.Data.ReadInt16(); - var pvp = args.Data.ReadInt8() == 0; - var text = args.Data.ReadString(); - if (dmg > 20000) //Abnormal values have the potential to cause infinite loops in the server. - { - TShock.Utils.ForceKick(args.Player, "Crash Exploit Attempt", true); - TShock.Log.ConsoleError("Death Exploit Attempt: Damage {0}", dmg); - return false; - } - - if (id >= Main.maxPlayers) - { - return true; - } - - if (OnKillMe(id, direction, dmg, pvp)) - return true; - - if (text.Length > 500) - { - TShock.Utils.Kick(TShock.Players[id], "Crash attempt", true); - 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; - } - } - - if (args.TPlayer.difficulty == 2 && (TShock.Config.KickOnHardcoreDeath || TShock.Config.BanOnHardcoreDeath)) - { - if (TShock.Config.BanOnHardcoreDeath) - { - if (!TShock.Utils.Ban(args.Player, TShock.Config.HardcoreBanReason, false, "hardcore-death")) - TShock.Utils.ForceKick(args.Player, "Death results in a ban, but you are immune to bans.", true); - } - else - { - TShock.Utils.ForceKick(args.Player, TShock.Config.HardcoreKickReason, true, false); - } - } - - if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn) - { - if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID)) - { - TShock.CharacterDB.SeedInitialData(args.Player.Account); - } - } - - return false; - } - private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) { var id = args.Data.ReadInt8(); @@ -2980,26 +2386,9 @@ namespace TShockAPI var direction = (byte)(args.Data.ReadInt8() - 1); BitsByte bits = (BitsByte)args.Data.ReadByte(); bool pvp = bits[0]; - if (dmg > 20000) //Abnormal values have the potential to cause infinite loops in the server. - { - TShock.Utils.ForceKick(args.Player, "Crash Exploit Attempt", true); - TShock.Log.ConsoleError("Death Exploit Attempt: Damage {0}", dmg); - return false; - } - if (id >= Main.maxPlayers) - { + if (OnKillMe(args.Player, id, direction, dmg, pvp, playerDeathReason)) return true; - } - - if (OnKillMe(id, direction, dmg, pvp)) - return true; - - if (playerDeathReason.GetDeathText(TShock.Players[id].Name).ToString().Length > 500) - { - TShock.Utils.Kick(TShock.Players[id], "Crash attempt", true); - return true; - } args.Player.Dead = true; args.Player.RespawnTimer = TShock.Config.RespawnSeconds; @@ -3031,6 +2420,7 @@ namespace TShockAPI { 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); } } @@ -3045,174 +2435,22 @@ namespace TShockAPI byte amount = args.Data.ReadInt8(); byte type = args.Data.ReadInt8(); - if (OnLiquidSet(tileX, tileY, amount, type)) + if (OnLiquidSet(args.Player, tileX, tileY, amount, type)) return true; - if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - - if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold) - { - args.Player.Disable("Reached TileLiquid threshold.", DisableFlags.WriteToLogAndConsole); - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - - if (!args.Player.HasPermission(Permissions.ignoreliquidsetdetection)) - { - args.Player.TileLiquidThreshold++; - } - if (amount != 0) - { - int bucket = -1; - if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 205) - { - bucket = 0; - } - else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 206) - { - bucket = 1; - } - else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 207) - { - bucket = 2; - } - else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 1128) - { - bucket = 3; - } - else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 3031 || - args.TPlayer.inventory[args.TPlayer.selectedItem].type == 3032) - { - 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); - return true; - } - - 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); - return true; - } - - 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); - return true; - } - - 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); - return true; - } - - 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); - return true; - } - - 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); - return true; - } - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY, 16)) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY, 1); - return true; - } - return false; } - private static bool HandleTileKill(GetDataHandlerArgs args) + 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 (OnTileKill(tileX, tileY)) + if (OnPlaceChest(args.Player, flag, tileX, tileY)) return true; - if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - - 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.MaxChests() && Main.tile[tileX, tileY].type != TileID.Dirt)) //Chest - { - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - - 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); - return true; - } - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY, 3); - return true; - } + return false; } @@ -3275,24 +2513,6 @@ namespace TShockAPI if (OnChestOpen(x, y, args.Player)) return true; - if (TShock.CheckIgnores(args.Player)) - { - return true; - } - - if (TShock.CheckRangePermission(args.Player, x, y)) - { - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y) && TShock.Config.RegionProtectChests) - { - return true; - } - - int id = Chest.FindChest(x, y); - args.Player.ActiveChest = id; - return false; } @@ -3329,35 +2549,14 @@ namespace TShockAPI var prefix = args.Data.ReadInt8(); var type = args.Data.ReadInt16(); - if (OnChestItemChange(id, slot, stacks, prefix, type)) + if (OnChestItemChange(args.Player, id, slot, stacks, prefix, type)) return true; - if (args.TPlayer.chest != id) - { - return false; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.ChestItem, "", id, slot); - return true; - } - Item item = new Item(); item.netDefaults(type); if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player)) { - return false; - } - - if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) - { - return false; - } - - if (TShock.CheckRangePermission(args.Player, Main.chest[id].x, Main.chest[id].y)) - { - return false; + return true; } return false; @@ -3394,7 +2593,7 @@ namespace TShockAPI var y = args.Data.ReadInt16(); var homeless = args.Data.ReadInt8(); - if (OnUpdateNPCHome(id, x, y, homeless)) + if (OnUpdateNPCHome(args.Player, id, x, y, homeless)) return true; if (!args.Player.HasPermission(Permissions.movenpc)) @@ -3404,22 +2603,6 @@ namespace TShockAPI Convert.ToByte(Main.npc[id].homeless)); return true; } - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - args.Player.SendErrorMessage("You do not have access to modify this area."); - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - } - - //removed until NPC Home packet actually sends their home coords. - /*if (TShock.CheckRangePermission(args.Player, x, y)) - { - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - }*/ return false; } @@ -3429,45 +2612,9 @@ namespace TShockAPI var type = args.Data.ReadInt8(); var time = args.Data.ReadInt32(); - if (OnPlayerBuff(id, type, time)) + if (OnPlayerBuff(args.Player, id, type, time)) return true; - if (TShock.Players[id] == null) - return false; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - - if (id >= Main.maxPlayers) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - - if (!TShock.Players[id].TPlayer.hostile || !Main.pvpBuff[type]) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); - return true; - } - - if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type]) - { - return false; - } - args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); return true; } @@ -3485,65 +2632,6 @@ namespace TShockAPI if (OnItemDrop(args.Player, id, pos, vel, stacks, prefix, noDelay, type)) return true; - // 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); - return true; - } - - if (prefix > PrefixID.Count) //make sure the prefix is a legit value - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - - if (type == 0) //Item removed, let client do this to prevent item duplication client side (but only if it passed the range check) - { - if (TShock.CheckRangePermission(args.Player, (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); - return true; - } - - return false; - } - - if (TShock.CheckRangePermission(args.Player, (int)(pos.X / 16f), (int)(pos.Y / 16f))) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - - if (Main.item[id].active && Main.item[id].netID != type) //stop the client from changing the item type of a drop but only if the client isn't picking up the item - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - - 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); - return true; - } - 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); - return true; - - } - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - return false; } @@ -3564,76 +2652,6 @@ namespace TShockAPI return false; } - private static bool HandlePlayerDamage(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var direction = (byte)(args.Data.ReadInt8() - 1); - var dmg = args.Data.ReadInt16(); - args.Data.ReadString(); // don't store damage text - var bits = (BitsByte)args.Data.ReadInt8(); - var pvp = bits[0]; - var crit = bits[1]; - - if (OnPlayerDamage(id, direction, dmg, pvp, crit)) - return true; - - if (id >= Main.maxPlayers || TShock.Players[id] == null) - { - return true; - } - - if (dmg > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap) && id != args.Player.Index) - { - if (TShock.Config.KickOnDamageThresholdBroken) - { - TShock.Utils.Kick(args.Player, string.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage)); - return true; - } - 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); - return true; - } - - if (!TShock.Players[id].TPlayer.hostile && pvp && id != args.Player.Index) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.Players[id].GodMode) - { - TShock.Players[id].Heal(args.TPlayer.statLifeMax); - } - - return false; - } - private static bool HandlePlayerDamageV2(GetDataHandlerArgs args) { var id = args.Data.ReadInt8(); @@ -3644,58 +2662,9 @@ namespace TShockAPI var crit = bits[0]; var pvp = bits[1]; - if (OnPlayerDamage(id, direction, dmg, pvp, crit)) + if (OnPlayerDamage(args.Player, id, direction, dmg, pvp, crit, playerDeathReason)) return true; - if (id >= Main.maxPlayers || TShock.Players[id] == null) - { - return true; - } - - if (dmg > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap) && id != args.Player.Index) - { - if (TShock.Config.KickOnDamageThresholdBroken) - { - TShock.Utils.Kick(args.Player, string.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage)); - return true; - } - 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); - return true; - } - - if (!TShock.Players[id].TPlayer.hostile && pvp && id != args.Player.Index) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - if (TShock.Players[id].GodMode) { TShock.Players[id].Heal(args.TPlayer.statLifeMax); @@ -3712,33 +2681,9 @@ namespace TShockAPI var direction = (byte)(args.Data.ReadInt8() - 1); var crit = args.Data.ReadInt8(); - if (OnNPCStrike(id, direction, dmg, knockback, crit)) + if (OnNPCStrike(args.Player, id, direction, dmg, knockback, crit)) return true; - if (Main.npc[id] == null) - return true; - - if (dmg > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap)) - { - if (TShock.Config.KickOnDamageThresholdBroken) - { - TShock.Utils.Kick(args.Player, string.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage)); - return true; - } - else - { - args.Player.Disable(String.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole); - } - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - if (Main.npc[id].townNPC && !args.Player.HasPermission(Permissions.hurttownnpc)) { args.Player.SendErrorMessage("You do not have permission to hurt this NPC."); @@ -3746,19 +2691,6 @@ namespace TShockAPI return true; } - if (TShock.Config.RangeChecks && - TShock.CheckRangePermission(args.Player, (int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128)) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - return false; } @@ -3788,21 +2720,9 @@ namespace TShockAPI private static bool HandlePlayerAnimation(GetDataHandlerArgs args) { - if (OnPlayerAnimation()) + if (OnPlayerAnimation(args.Player)) return true; - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - return true; - } - return false; } @@ -3817,16 +2737,6 @@ namespace TShockAPI { var buff = args.Data.ReadInt8(); - /*if (TShock.Itembans.ItemBans.Any(s => - { - Item item = new Item(); - item.SetDefaults(s.Name); - return item.buffType == buff; - })) - { - buff = 0; - }*/ - if (buff == 10 && TShock.Config.DisableInvisPvP && args.TPlayer.hostile) buff = 0; @@ -3851,7 +2761,7 @@ namespace TShockAPI private static bool HandleSpawnBoss(GetDataHandlerArgs args) { - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + if (args.Player.IsBouncerThrottled()) { return true; } @@ -3986,7 +2896,7 @@ namespace TShockAPI return true; } - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000 || + if (args.Player.IsBouncerThrottled() || TShock.CheckTilePermission(args.Player, x, y, true) || TShock.CheckRangePermission(args.Player, x, y)) { @@ -4030,7 +2940,7 @@ namespace TShockAPI return true; } - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000 || + if (args.Player.IsBouncerThrottled() || TShock.CheckTilePermission(args.Player, x, y, true) || TShock.CheckRangePermission(args.Player, x, y)) { @@ -4157,50 +3067,77 @@ namespace TShockAPI short startY = args.Data.ReadInt16(); short endX = args.Data.ReadInt16(); short endY = args.Data.ReadInt16(); - args.Data.ReadByte(); // Ignore toolmode + byte toolMode = (byte) args.Data.ReadByte(); - 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)) - return true; - - if (TShock.CheckIgnores(args.Player)) - return true; - - if (TShock.CheckTilePermission(args.Player, x, y)) - return true; - } + if (OnMassWireOperation(args.Player, startX, startY, endX, endY, toolMode)) + return true; return false; } + /// The arguments to the PlaceItemFrame event. + public class PlaceItemFrameEventArgs : HandledEventArgs + { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } + + /// 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; + + private static bool OnPlaceItemFrame(TSPlayer player, short x, short y, short itemID, byte prefix, short stack, TEItemFrame itemFrame) + { + if (PlaceItemFrame == null) + return false; + + var args = new PlaceItemFrameEventArgs + { + Player = player, + 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 : HandledEventArgs { + /// The TSPlayer that triggered the event. + public TSPlayer Player { get; set; } /// /// X Location /// - public Int32 X { get; set; } + public short X { get; set; } /// /// Y Location /// - public Int32 Y { get; set; } + public short Y { get; set; } /// /// On status /// @@ -4212,7 +3149,7 @@ namespace TShockAPI /// public static HandlerList GemLockToggle; - private static bool OnGemLockToggle(Int32 x, Int32 y, bool on) + private static bool OnGemLockToggle(short x, short y, bool on) { if (GemLockToggle == null) return false; @@ -4229,15 +3166,10 @@ namespace TShockAPI private static bool HandleGemLockToggle(GetDataHandlerArgs args) { - var x = (int)args.Data.ReadInt16(); - var y = (int)args.Data.ReadInt16(); + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); var on = args.Data.ReadBoolean(); - if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY) - { - return true; - } - if (OnGemLockToggle(x, y, on)) { return true; @@ -4248,21 +3180,6 @@ namespace TShockAPI return false; } - if (!TShock.Utils.TilePlacementValid(x, y) || (args.Player.Dead && TShock.Config.PreventDeadModification)) - { - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - return true; - } - return false; } @@ -4324,7 +3241,14 @@ namespace TShockAPI { var x = args.Data.ReadInt16(); var y = args.Data.ReadInt16(); - var type = args.Data.ReadByte(); + var type = (byte) args.Data.ReadByte(); + + if (OnPlaceTileEntity(args.Player, x, y, type)) + { + return true; + } + + // ItemBan subsystem if (TShock.TileBans.TileIsBanned((short)TileID.LogicSensor, args.Player)) { @@ -4333,21 +3257,6 @@ namespace TShockAPI return true; } - if (TShock.CheckIgnores(args.Player)) - { - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - return true; - } - - if (TShock.CheckRangePermission(args.Player, x, y)) - { - return true; - } - return false; } @@ -4360,21 +3269,8 @@ namespace TShockAPI var stack = args.Data.ReadInt16(); var itemFrame = (TEItemFrame)TileEntity.ByID[TEItemFrame.Find(x, y)]; - if (TShock.CheckIgnores(args.Player)) + if (OnPlaceItemFrame(args.Player, x, y, itemID, prefix, stack, itemFrame)) { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, itemFrame.ID, 0, 1); - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, itemFrame.ID, 0, 1); - return true; - } - - if (TShock.CheckRangePermission(args.Player, x, y)) - { - NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, itemFrame.ID, 0, 1); return true; } @@ -4424,7 +3320,7 @@ namespace TShockAPI private static bool HandleOldOnesArmy(GetDataHandlerArgs args) { - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + if (args.Player.IsBouncerThrottled()) { return true; } diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index c43669f7..868f20b2 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -285,6 +285,21 @@ namespace TShockAPI public bool IgnoreActionsForClearingTrashCan; + /// Checks to see if active throttling is happening on events by Bouncer. Rejects repeated events by malicious clients in a short window. + /// If the player is currently being throttled by Bouncer, or not. + public bool IsBouncerThrottled() + { + return (DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000; + } + + /// CheckIgnores - Checks a players ignores...? + /// player - The TSPlayer object. + /// bool - True if any ignore is not none, false, or login state differs from the required state. + public bool CheckIgnores() + { + return IgnoreActionsForInventory != "none" || IgnoreActionsForCheating != "none" || IgnoreActionsForDisabledArmor != "none" || IgnoreActionsForClearingTrashCan || !IsLoggedIn && TShock.Config.RequireLogin; + } + /// /// The player's server side inventory data. /// @@ -789,6 +804,48 @@ namespace TShockAPI } } + /// Checks to see if this player object has access rights to a given projectile. Used by projectile bans. + /// The projectile index from Main.projectiles (NOT from a packet directly). + /// The type of projectile, from Main.projectiles. + /// If the player has access rights to the projectile. + public bool HasProjectilePermission(int index, int type) + { + // Players never have the rights to tombstones. + if (type == ProjectileID.Tombstone) + { + return false; + } + + // Dirt balls are the projectiles from dirt rods. + // If the dirt rod item is banned, they probably shouldn't have this projectile. + if (type == ProjectileID.DirtBall && TShock.Itembans.ItemIsBanned("Dirt Rod", this)) + { + return false; + } + + // If the sandgun is banned, block sand bullets. + if (TShock.Itembans.ItemIsBanned("Sandgun", this)) + { + if (type == ProjectileID.SandBallGun + || type == ProjectileID.EbonsandBallGun + || type == ProjectileID.PearlSandBallGun) + { + return false; + } + } + + // If the projectile is hostile, block it? + Projectile tempProjectile = new Projectile(); + tempProjectile.SetDefaults(type); + + if (Main.projHostile[type]) + { + return false; + } + + return true; + } + /// /// Removes the projectile with the given index and owner. /// @@ -808,6 +865,15 @@ namespace TShockAPI } } + /// Sends a tile square at a location with a given size. + /// Typically used to revert changes by Bouncer through sending the + /// "old" version of modified data back to a client. + /// Prevents desync issues. + /// + /// The x coordinate to send. + /// The y coordinate to send. + /// The size square set of tiles to send. + /// Status if the tile square was sent successfully (i.e. no exceptions). public virtual bool SendTileSquare(int x, int y, int size = 10) { try diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 65c67441..9766b93d 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -138,6 +138,9 @@ namespace TShockAPI /// public static Dictionary RESTStartupTokens = new Dictionary(); + /// The TShock anti-cheat/anti-exploit system. + internal Bouncer Bouncer; + /// /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. /// @@ -322,6 +325,7 @@ namespace TShockAPI RestApi = new SecureRest(Netplay.ServerIP, Config.RestApiPort); RestManager = new RestManager(RestApi); RestManager.RegisterRestfulCommands(); + Bouncer = new Bouncer(); var geoippath = "GeoIP.dat"; if (Config.EnableGeoIP && File.Exists(geoippath)) @@ -378,6 +382,7 @@ namespace TShockAPI Log.ConsoleInfo("Welcome to TShock for Terraria!"); Log.ConsoleInfo("TShock comes with no warranty & is free software."); Log.ConsoleInfo("You can modify & distribute it under the terms of the GNU GPLv3."); + } catch (Exception ex) { @@ -849,7 +854,7 @@ namespace TShockAPI /// args - The EventArgs object. private void OnPostInit(EventArgs args) { - SetConsoleTitle(false); + Utils.SetConsoleTitle(false); //This is to prevent a bug where a CLI-defined password causes packets to be //sent in an unexpected order, resulting in clients being unable to connect @@ -902,8 +907,8 @@ namespace TShockAPI Regions.Reload(); Warps.ReloadWarps(); - ComputeMaxStyles(); - FixChestStacks(); + Utils.ComputeMaxStyles(); + Utils.FixChestStacks(); Utils.UpgradeMotD(); @@ -916,45 +921,6 @@ namespace TShockAPI StatTracker.Start(); } - /// ComputeMaxStyles - Computes the max styles... - private void ComputeMaxStyles() - { - var item = new Item(); - for (int i = 0; i < Main.maxItemTypes; i++) - { - item.netDefaults(i); - if (item.placeStyle > 0) - { - if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile)) - { - if (item.placeStyle > GetDataHandlers.MaxPlaceStyles[item.createTile]) - GetDataHandlers.MaxPlaceStyles[item.createTile] = item.placeStyle; - } - else - GetDataHandlers.MaxPlaceStyles.Add(item.createTile, item.placeStyle); - } - } - } - - /// FixChestStacks - Verifies that each stack in each chest is valid and not over the max stack count. - private void FixChestStacks() - { - if (Config.IgnoreChestStacksOnLoad) - return; - - foreach (Chest chest in Main.chest) - { - if (chest != null) - { - foreach (Item item in chest.item) - { - if (item != null && item.stack > item.maxStack) - item.stack = item.maxStack; - } - } - } - } - /// LastCheck - Used to keep track of the last check for basically all time based checks. private DateTime LastCheck = DateTime.UtcNow; @@ -1117,7 +1083,7 @@ namespace TShockAPI if (Main.ServerSideCharacter && !player.IsLoggedIn) { - if (CheckIgnores(player)) + if (player.CheckIgnores()) { player.Disable(flags: flags); } @@ -1197,7 +1163,7 @@ namespace TShockAPI } player.IgnoreActionsForDisabledArmor = check; - if (CheckIgnores(player)) + if (player.CheckIgnores()) { player.Disable(flags: flags); } @@ -1225,17 +1191,7 @@ namespace TShockAPI } } } - SetConsoleTitle(false); - } - - /// SetConsoleTitle - Updates the console title with some pertinent information. - /// empty - True/false if the server is empty; determines if we should use Utils.ActivePlayers() for player count or 0. - private void SetConsoleTitle(bool empty) - { - Console.Title = string.Format("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})", - !string.IsNullOrWhiteSpace(Config.ServerName) ? Config.ServerName + " - " : "", - empty ? 0 : Utils.ActivePlayers(), - Config.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, Version); + Utils.SetConsoleTitle(false); } /// OnHardUpdate - Fired when a hardmode tile update event happens. @@ -1481,7 +1437,7 @@ namespace TShockAPI { if (Config.SaveWorldOnLastPlayerExit) SaveManager.Instance.SaveWorld(); - SetConsoleTitle(true); + Utils.SetConsoleTitle(true); } } @@ -1802,62 +1758,7 @@ namespace TShockAPI } - /// StartInvasion - Starts an invasion on the server. - /// type - The invasion type id. - //TODO: Why is this in TShock's main class? - public static void StartInvasion(int type) - { - int invasionSize = 0; - if (Config.InfiniteInvasion) - { - invasionSize = 20000000; - } - else - { - invasionSize = 100 + (Config.InvasionMultiplier * Utils.ActivePlayers()); - } - - // Note: This is a workaround to previously providing the size as a parameter in StartInvasion - Main.invasionSize = invasionSize; - - Main.StartInvasion(type); - } - - /// CheckProjectilePermission - Checks if a projectile is banned. - /// player - The TSPlayer object that created the projectile. - /// index - The projectile index. - /// type - The projectile type. - /// bool - True if the player does not have permission to use a projectile. - public static bool CheckProjectilePermission(TSPlayer player, int index, int type) - { - if (type == 43) - { - return true; - } - - if (type == 17 && Itembans.ItemIsBanned("Dirt Rod", player)) - //Dirt Rod Projectile - { - return true; - } - - if ((type == 42 || type == 65 || type == 68) && Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles - { - return true; - } - - Projectile proj = new Projectile(); - proj.SetDefaults(type); - - if (Main.projHostile[type]) - { - //player.SendMessage( proj.name, Color.Yellow); - return true; - } - - return false; - } /// CheckRangePermission - Checks if a player has permission to modify a tile dependent on range checks. /// player - The TSPlayer object. @@ -2034,19 +1935,17 @@ namespace TShockAPI { Vector2 tile = new Vector2(x, y); Vector2 spawn = new Vector2(Main.spawnTileX, Main.spawnTileY); - return Distance(spawn, tile) <= Config.SpawnProtectionRadius; + return Utils.Distance(spawn, tile) <= Config.SpawnProtectionRadius; } /// Distance - Determines the distance between two vectors. /// value1 - The first vector location. /// value2 - The second vector location. /// float - The distance between the two vectors. + [Obsolete("Use TShock.Utils.Distance(Vector2, Vector2) instead.", true)] public static float Distance(Vector2 value1, Vector2 value2) { - float num2 = value1.X - value2.X; - float num = value1.Y - value2.Y; - float num3 = (num2 * num2) + (num * num); - return (float)Math.Sqrt(num3); + return Utils.Distance(value1, value2); } /// HackedInventory - Checks to see if a user has a hacked inventory. In addition, messages players the result. @@ -2246,14 +2145,6 @@ namespace TShockAPI return check; } - /// CheckIgnores - Checks a players ignores...? - /// player - The TSPlayer object. - /// bool - True if any ignore is not none, false, or login state differs from the required state. - public static bool CheckIgnores(TSPlayer player) - { - return player.IgnoreActionsForInventory != "none" || player.IgnoreActionsForCheating != "none" || player.IgnoreActionsForDisabledArmor != "none" || player.IgnoreActionsForClearingTrashCan || !player.IsLoggedIn && Config.RequireLogin; - } - /// OnConfigRead - Fired when the config file has been read. /// file - The config file object. public void OnConfigRead(ConfigFile file) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index a612f444..b7bf6f8e 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -131,6 +131,7 @@ + True True diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index 8314de92..b27bee5d 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -925,7 +925,7 @@ namespace TShockAPI /// /// identity /// owner - /// projectile ID + /// projectile ID or -1 if not found public int SearchProjectile(short identity, int owner) { for (int i = 0; i < Main.maxProjectiles; i++) @@ -933,7 +933,7 @@ namespace TShockAPI if (Main.projectile[i].identity == identity && Main.projectile[i].owner == owner) return i; } - return 1000; + return -1; } /// @@ -1456,5 +1456,93 @@ namespace TShockAPI } } } + + /// Starts an invasion on the server. + /// The invasion type id. + internal void StartInvasion(int type) + { + int invasionSize = 0; + + if (TShock.Config.InfiniteInvasion) + { + // Not really an infinite size + invasionSize = 20000000; + } + else + { + invasionSize = 100 + (TShock.Config.InvasionMultiplier * ActivePlayers()); + } + + // Order matters + // StartInvasion will reset the invasion size + + Main.StartInvasion(type); + + // Note: This is a workaround to previously providing the size as a parameter in StartInvasion + // Have to set start size to report progress correctly + Main.invasionSizeStart = invasionSize; + Main.invasionSize = invasionSize; + } + + /// Verifies that each stack in each chest is valid and not over the max stack count. + internal void FixChestStacks() + { + if (TShock.Config.IgnoreChestStacksOnLoad) + return; + + foreach (Chest chest in Main.chest) + { + if (chest != null) + { + foreach (Item item in chest.item) + { + if (item != null && item.stack > item.maxStack) + item.stack = item.maxStack; + } + } + } + } + + /// Updates the console title with some pertinent information. + /// If the server is empty; determines if we should use Utils.ActivePlayers() for player count or 0. + internal void SetConsoleTitle(bool empty) + { + Console.Title = string.Format("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})", + !string.IsNullOrWhiteSpace(TShock.Config.ServerName) ? TShock.Config.ServerName + " - " : "", + empty ? 0 : ActivePlayers(), + TShock.Config.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, TShock.VersionNum); + } + + /// Determines the distance between two vectors. + /// The first vector location. + /// The second vector location. + /// The distance between the two vectors. + public static float Distance(Vector2 value1, Vector2 value2) + { + float num2 = value1.X - value2.X; + float num = value1.Y - value2.Y; + float num3 = (num2 * num2) + (num * num); + return (float)Math.Sqrt(num3); + } + + /// Computes the max styles... + internal void ComputeMaxStyles() + { + var item = new Item(); + for (int i = 0; i < Main.maxItemTypes; i++) + { + item.netDefaults(i); + if (item.placeStyle > 0) + { + if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile)) + { + if (item.placeStyle > GetDataHandlers.MaxPlaceStyles[item.createTile]) + GetDataHandlers.MaxPlaceStyles[item.createTile] = item.placeStyle; + } + else + GetDataHandlers.MaxPlaceStyles.Add(item.createTile, item.placeStyle); + } + } + } } } diff --git a/TShockRestTestPlugin/Properties/AssemblyInfo.cs b/TShockRestTestPlugin/Properties/AssemblyInfo.cs deleted file mode 100644 index dfb6c9aa..00000000 --- a/TShockRestTestPlugin/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011-2016 Nyx Studios (fka. The TShock Team) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ClassLibrary1")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Multiplay")] -[assembly: AssemblyProduct("ClassLibrary1")] -[assembly: AssemblyCopyright("Copyright © Multiplay 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6aed7ee-6282-49a2-8177-b79cad20d6d3")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.cs b/TShockRestTestPlugin/TShockRestTestPlugin.cs deleted file mode 100644 index 250ff087..00000000 --- a/TShockRestTestPlugin/TShockRestTestPlugin.cs +++ /dev/null @@ -1,210 +0,0 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011-2016 Nyx Studios (fka. The TShock Team) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; -using System.Web; -using System.Web.Script.Serialization; -using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestTools.WebTesting; -using Microsoft.VisualStudio.TestTools.WebTesting.Rules; -using Rests; - -namespace TshockRestTestPlugin -{ - [DisplayName("JSON Status")] - [Description("Checks to see the that the JSON response has the specified status response")] - public class JsonValidateStatus : JsonValidate - { - public override void Validate(object sender, ValidationEventArgs e) - { - if (null != ValidateJson(sender, e)) - e.IsValid = true; - } - } - - [DisplayName("JSON Regexp Property")] - [Description("Checks to see the that the JSON response contains the specified property and is matches the specified regexp")] - public class JsonValidateRegexpProperty : JsonValidateProperty - { - // The name of the desired JSON property - [DisplayName("Regexp")] - [DefaultValue(true)] - public new bool UseRegularExpression { get { return base.UseRegularExpression; } set { base.UseRegularExpression = value; } } - } - - [DisplayName("JSON Error")] - [Description("Checks to see the that the JSON response contains the specified error")] - public class JsonValidateError : JsonValidateProperty - { - // The status of the JSON request - [DisplayName("JSON Status")] - [DefaultValue("400")] - public new string JSonStatus { get { return base.JSonStatus; } set { base.JSonStatus = value; } } - - // The name of the desired JSON property - [DisplayName("Property")] - [DefaultValue("error")] - public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } - } - - [DisplayName("JSON Missing Parameter")] - [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] - public class JsonValidateMissingParameter : JsonValidateError - { - // The value of the desired JSON property - [DisplayName("Missing Value")] - public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or empty {0} parameter", value); } } - } - - [DisplayName("JSON Invalid Parameter")] - [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] - public class JsonValidateInvalidParameter : JsonValidateError - { - // The value of the desired JSON property - [DisplayName("Invalid Value")] - public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or invalid {0} parameter", value); } } - } - - [DisplayName("JSON Response")] - [Description("Checks to see the that the JSON response contains the specified message")] - public class JsonValidateResponse : JsonValidateProperty - { - // The name of the desired JSON property - [DisplayName("Response")] - [DefaultValue("response")] - public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } - } - - [DisplayName("JSON Property")] - [Description("Checks to see the that the JSON response contains the specified property and is set to the specified value")] - public class JsonValidateProperty : JsonValidate - { - // The name of the desired JSON property - [DisplayName("Property")] - public string PropertyName { get; set; } - - // The value of the desired JSON property - [DisplayName("Value")] - public string PropertyValue { get; set; } - - // Is the value a regexp of the desired JSON property - [DisplayName("Regexp")] - [DefaultValue(false)] - public bool UseRegularExpression { get; set; } - - public override void Validate(object sender, ValidationEventArgs e) - { - RestObject response = ValidateJson(sender, e); - if (null == response) - return; - - if (null == response[PropertyName]) - { - e.Message = String.Format("{0} Not Found", PropertyName); - e.IsValid = false; - return; - } - - if (UseRegularExpression) - { - var re = new Regex(PropertyValue); - if (!re.IsMatch((string)response[PropertyName])) - { - e.Message = String.Format("{0} => '{1}' !~ '{2}'", PropertyName, response[PropertyName], PropertyValue); - e.IsValid = false; - return; - } - } - else - { - if (PropertyValue != (string)response[PropertyName]) - { - e.Message = String.Format("{0} => '{1}' != '{2}'", PropertyName, response[PropertyName], PropertyValue); - e.IsValid = false; - return; - } - } - - e.IsValid = true; - //e.WebTest.Context.Add(ContextParameterName, propertyValue); - } - } - - [DisplayName("JSON Has Properties")] - [Description("Checks to see the that the JSON response contains the specified properties (comma seperated)")] - public class JsonHasProperties : JsonValidate - { - // The name of the desired JSON properties to check - [DisplayName("Properties")] - [Description("A comma seperated list of property names to check exist")] - public string PropertyNames { get; set; } - - //--------------------------------------------------------------------- - public override void Validate(object sender, ValidationEventArgs e) - { - RestObject response = ValidateJson(sender, e); - if (null == response) - return; - foreach (var p in PropertyNames.Split(',')) - { - if (null == response[p]) - { - e.Message = String.Format("'{0}' Not Found", p); - e.IsValid = false; - return; - } - } - e.IsValid = true; - - //e.WebTest.Context.Add(ContextParameterName, propertyValue); - } - } - - public abstract class JsonValidate : ValidationRule - { - // The status of the JSON request - [DisplayName("JSON Status")] - [DefaultValue("200")] - public string JSonStatus { get; set; } - - public RestObject ValidateJson(object sender, ValidationEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.Response.BodyString)) - { - e.IsValid = false; - e.Message = String.Format("Empty or null response {0}", e.Response.StatusCode); - return null; - } - JavaScriptSerializer serialiser = new JavaScriptSerializer(); - //dynamic data = serialiser.Deserialize(e.Response.BodyString); - RestObject response = serialiser.Deserialize(e.Response.BodyString); - - if (JSonStatus != response.Status) - { - e.IsValid = false; - e.Message = String.Format("Response Status '{0}' not '{1}'", response.Status, JSonStatus); - return null; - } - - return response; - } - } -} \ No newline at end of file diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.csproj b/TShockRestTestPlugin/TShockRestTestPlugin.csproj deleted file mode 100644 index e315c7d3..00000000 --- a/TShockRestTestPlugin/TShockRestTestPlugin.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F2FEDAFB-58DE-4611-9168-A86112C346C7} - Library - Properties - TshockRestTestPlugin - TshockRestTestPlugin - v4.0 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - {49606449-072B-4CF5-8088-AA49DA586694} - TShockAPI - - - - - \ No newline at end of file