/* TShock, a server mod for Terraria Copyright (C) 2011-2018 Pryaxis & TShock Contributors 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 TShockAPI.Localization; using static TShockAPI.GetDataHandlers; using TerrariaApi.Server; using Terraria.ObjectData; using Terraria.DataStructures; using Terraria.Localization; using System.Data; namespace TShockAPI { /// The TShock item ban subsystem. It handles keeping things out of people's inventories. public sealed class ItemBans { /// The database connection layer to for the item ban subsystem. public ItemManager DataModel; /// The last time the second update process was run. Used to throttle task execution. private DateTime LastTimelyRun = DateTime.UtcNow; /// A reference to the TShock plugin so we can register events. private TShock Plugin; /// Creates an ItemBan system given a plugin to register events to and a database. /// The executing plugin. /// The database the item ban information is stored in. /// A new item ban system. internal ItemBans(TShock plugin, IDbConnection database) { DataModel = new ItemManager(database); Plugin = plugin; ServerApi.Hooks.GameUpdate.Register(plugin, OnGameUpdate); GetDataHandlers.PlayerUpdate += OnPlayerUpdate; GetDataHandlers.ChestItemChange += OnChestItemChange; GetDataHandlers.TileEdit += OnTileEdit; } /// Called on the game update loop (the XNA tickrate). /// The standard event arguments. internal void OnGameUpdate(EventArgs args) { if ((DateTime.UtcNow - LastTimelyRun).TotalSeconds >= 1) { OnSecondlyUpdate(args); } } /// Called by OnGameUpdate once per second to execute tasks regularly but not too often. /// The standard event arguments. internal void OnSecondlyUpdate(EventArgs args) { DisableFlags disableFlags = TShock.Config.Settings.DisableSecondUpdateLogs ? DisableFlags.WriteToConsole : DisableFlags.WriteToLogAndConsole; foreach (TSPlayer player in TShock.Players) { if (player == null || !player.Active) { continue; } // Untaint now, re-taint if they fail the check. UnTaint(player); // No matter the player type, we do a check when a player is holding an item that's banned. if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].type), player)) { string itemName = player.TPlayer.inventory[player.TPlayer.selectedItem].Name; player.Disable(GetString($"holding banned item: {itemName}"), disableFlags); SendCorrectiveMessage(player, itemName); } // If SSC isn't enabled OR if SSC is enabled and the player is logged in // In a case like this, we do the full check too. if (!Main.ServerSideCharacter || (Main.ServerSideCharacter && player.IsLoggedIn)) { // The Terraria inventory is composed of a multicultural set of arrays // with various different contents and beliefs // Armor ban checks foreach (Item item in player.TPlayer.armor) { if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player)) { Taint(player); SendCorrectiveMessage(player, item.Name); } } // Dye ban checks foreach (Item item in player.TPlayer.dye) { if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player)) { Taint(player); SendCorrectiveMessage(player, item.Name); } } // Misc equip ban checks foreach (Item item in player.TPlayer.miscEquips) { if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player)) { Taint(player); SendCorrectiveMessage(player, item.Name); } } // Misc dye ban checks foreach (Item item in player.TPlayer.miscDyes) { if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player)) { Taint(player); SendCorrectiveMessage(player, item.Name); } } } } // Set the update time to now, so that we know when to execute next. // We do this at the end so that the task can't re-execute faster than we expected. // (If we did this at the start of the method, the method execution would count towards the timer.) LastTimelyRun = DateTime.UtcNow; } internal void OnPlayerUpdate(object sender, PlayerUpdateEventArgs args) { DisableFlags disableFlags = TShock.Config.Settings.DisableSecondUpdateLogs ? DisableFlags.WriteToConsole : DisableFlags.WriteToLogAndConsole; bool useItem = args.Control.IsUsingItem; TSPlayer player = args.Player; string itemName = player.TPlayer.inventory[args.SelectedItem].Name; if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[args.SelectedItem].type), args.Player)) { player.TPlayer.controlUseItem = false; player.Disable(GetString($"holding banned item: {itemName}"), disableFlags); SendCorrectiveMessage(player, itemName); player.TPlayer.Update(player.TPlayer.whoAmI); NetMessage.SendData((int)PacketTypes.PlayerUpdate, -1, player.Index, NetworkText.Empty, player.Index); args.Handled = true; return; } args.Handled = false; return; } internal void OnChestItemChange(object sender, ChestItemEventArgs args) { Item item = new Item(); item.netDefaults(args.Type); if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player)) { SendCorrectiveMessage(args.Player, item.Name); args.Handled = true; return; } args.Handled = false; return; } internal void OnTileEdit(object sender, TileEditEventArgs args) { if (args.Action == EditAction.PlaceTile || args.Action == EditAction.PlaceWall) { if (args.Player.TPlayer.autoActuator && DataModel.ItemIsBanned("Actuator", args.Player)) { args.Player.SendTileSquareCentered(args.X, args.Y, 1); args.Player.SendErrorMessage(GetString("You do not have permission to place actuators.")); args.Handled = true; return; } if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(args.Player.SelectedItem.type), args.Player)) { args.Player.SendTileSquareCentered(args.X, args.Y, 4); args.Handled = true; return; } } } private void UnTaint(TSPlayer player) { player.IsDisabledForBannedWearable = false; } private void Taint(TSPlayer player) { // Arbitrarily does things to the player player.SetBuff(BuffID.Frozen, 330, true); player.SetBuff(BuffID.Stoned, 330, true); player.SetBuff(BuffID.Webbed, 330, true); // Marks them as a target for future disables player.IsDisabledForBannedWearable = true; } private void SendCorrectiveMessage(TSPlayer player, string itemName) { player.SendErrorMessage(GetString("{0} is banned! Remove it!", itemName)); } } }