/*
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 OTAPI.Tile;
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.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].netID), player))
{
string itemName = player.TPlayer.inventory[player.TPlayer.selectedItem].Name;
player.Disable($"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.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].netID), args.Player))
{
player.TPlayer.controlUseItem = false;
player.Disable($"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.SendTileSquare(args.X, args.Y, 1);
args.Player.SendErrorMessage("You do not have permission to place actuators.");
args.Handled = true;
return;
}
if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(args.Player.SelectedItem.netID), args.Player))
{
args.Player.SendTileSquare(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("{0} is banned! Remove it!", itemName);
}
}
}