TShock/TShockAPI/ItemBans.cs
Lucas Nicodemus b5f95d5918
Fragments: Separate out item bans (#1595)
* Remove commented out warning disable

* Add initial ItemBans segregation infrastructure

* Add shell for initial OnSecondUpdate stuff

* Add comments yo

* Remove duplicated logic

* Split out more item ban code

This part of the fragments work is primarily aimed at reducing the
complexity of OnSecondUpdate in TShock and moving that check out into
the ItemBans subsytem.

Of major note in this is the removal of "check", which was a string
variable that tracked state and replacement of many of the item ban
activities with sane private methods that are at least somewhat
sensible. Obviously there's a lot to be desired in this system and I'm
really going for a run here by trying to continue a branch from so long
ago that I barely even remember the whole point of existence.

Still to do: GetDataHandlers related item ban code needs to be moved
into its own hook in the ItemBan system. Finally, there is a downside to
some of this: we're basically iterating over players again and again if
we keep this pattern up, which is kinda lame for complexity purposes.

* alt j: comment changes

* Move item ban check out of main playerupdate check

Separates out item ban logic from the rest of GetDataHandlers so that
item bans is more isolated in terms of what fragments is asking for.

* alt-j: convert indentation to tabs

* alt-j: fix botching source code

* Move item ban related chest checks out of gdh

* Remove chest item change detection from item bans

It doesn't do anything. If a user removes an item from a chest, it
bypasses this check. If a user adds an item to a chest, the server seems
to persist the change anyway, even if the event is handled. That's a bug
for sure, but fundamentally, it's not the item ban system's fault.

* Revert "Remove chest item change detection from item bans"

This reverts commit 758541ac5c4d4096df2db05ba2a398968113e1e4.

* Fix logic issues related to item ban handling

Re-implements chest item handling and correctly handles events and
returns after setting handled event state.

* Remove TSPlayer.HasProjectilePermission

In infinite wisdom, it turns out this is not a good method for TSPlayer
to have. It just checks the states of things as per what the item ban
system says is banned and then creates implicit relationships to the
projectile ban system.

Doing this effectively knocks down another external reference to the
item ban system outside of the context of the implementation for the
system itself and its related hooks.

This commit also adds context around what the heck is going on with some
of our more interesting checks as per discussions in Telegram with @Ijwu
and @QuiCM.

* Update changelog

* Remove useless ref to Projectile.SetDefaults

* Change item ban to ban based on ID not strings

I think I was so confused as to why we were passing strings everywhere
that I just felt inclined to continue the trend in previous commits.
2020-05-16 16:27:34 -07:00

216 lines
6.9 KiB
C#

/*
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 <http://www.gnu.org/licenses/>.
*/
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
{
/// <summary>The TShock item ban subsystem. It handles keeping things out of people's inventories.</summary>
internal sealed class ItemBans
{
/// <summary>The database connection layer to for the item ban subsystem.</summary>
private ItemManager DataModel;
/// <summary>The last time the second update process was run. Used to throttle task execution.</summary>
private DateTime LastTimelyRun = DateTime.UtcNow;
/// <summary>A reference to the TShock plugin so we can register events.</summary>
private TShock Plugin;
/// <summary>Creates an ItemBan system given a plugin to register events to and a database.</summary>
/// <param name="plugin">The executing plugin.</param>
/// <param name="database">The database the item ban information is stored in.</param>
/// <returns>A new item ban system.</returns>
internal ItemBans(TShock plugin, IDbConnection database)
{
DataModel = new ItemManager(database);
Plugin = plugin;
ServerApi.Hooks.GameUpdate.Register(plugin, OnGameUpdate);
GetDataHandlers.PlayerUpdate += OnPlayerUpdate;
GetDataHandlers.ChestItemChange += OnChestItemChange;
}
/// <summary>Called on the game update loop (the XNA tickrate).</summary>
/// <param name="args">The standard event arguments.</param>
internal void OnGameUpdate(EventArgs args)
{
if ((DateTime.UtcNow - LastTimelyRun).TotalSeconds >= 1)
{
OnSecondlyUpdate(args);
}
}
/// <summary>Called by OnGameUpdate once per second to execute tasks regularly but not too often.</summary>
/// <param name="args">The standard event arguments.</param>
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 = ((BitsByte) args.Control)[5];
TSPlayer player = args.Player;
string itemName = player.TPlayer.inventory[args.Item].Name;
if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[args.Item].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;
}
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);
}
}
}