diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2c4f47c..7a25311e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,7 +41,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* 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)
+* Removed `TShock.CheckProjectilePermission`. (@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)
@@ -90,7 +90,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* `Utils.TryParseTime` can now take spaces (e.g., `3d 5h 2m 3s`) (@QuiCM)
* Enabled banning unregistered users (@QuiCM)
* Added filtering and validation on packet 96 (Teleport player through portal) (@QuiCM)
-* Update tracker now uses TLS (@pandabear41)
+* Update tracker now uses TLS (@pandabear41)
* When deleting an user account, any player logged in to that account is now logged out properly (@Enerdy)
* Add NPCAddBuff data handler and bouncer (@AxeelAnder)
* Improved config file documentation (@Enerdy)
@@ -98,6 +98,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Update sqlite binaries to 32bit 3.27.2 for Windows (@hakusaro)
* Fix banned armour checks not clearing properly (thanks @tysonstrange)
* Added warning message on invalid group comand (@hakusaro, thanks to IcyPhoenix, nuLLzy & Cy on Discord)
+* Moved item bans subsystem to isolated file/contained mini-plugin & reorganized codebase accordingly. (@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
index 7b253ed4..f55c7aaa 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -757,7 +757,7 @@ namespace TShockAPI
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.
@@ -803,8 +803,25 @@ namespace TShockAPI
return;
}
- bool hasPermission = args.Player.HasProjectilePermission(index, type);
- if (!TShock.Config.IgnoreProjUpdate && !hasPermission && !args.Player.HasPermission(Permissions.ignoreprojectiledetection))
+ // Main.projHostile contains projectiles that can harm players
+ // without PvP enabled and belong to enemy mobs, so they shouldn't be
+ // possible for players to create. (Source: Ijwu, QuiCM)
+ if (Main.projHostile[type])
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ // Tombstones should never be permitted by players
+ if (type == ProjectileID.Tombstone)
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Config.IgnoreProjUpdate && !args.Player.HasPermission(Permissions.ignoreprojectiledetection))
{
if (type == ProjectileID.BlowupSmokeMoonlord
|| type == ProjectileID.PhantasmalEye
@@ -856,8 +873,7 @@ namespace TShockAPI
}
}
- if (hasPermission &&
- (type == ProjectileID.Bomb
+ if ((type == ProjectileID.Bomb
|| type == ProjectileID.Dynamite
|| type == ProjectileID.StickyBomb
|| type == ProjectileID.StickyDynamite))
@@ -866,7 +882,7 @@ namespace TShockAPI
args.Player.RecentFuse = 10;
}
}
-
+
/// Handles the NPC Strike event for Bouncer.
/// The object that triggered the event.
/// The packet arguments that the event has.
diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs
index f8083b60..3af965a8 100644
--- a/TShockAPI/Commands.cs
+++ b/TShockAPI/Commands.cs
@@ -3274,7 +3274,32 @@ namespace TShockAPI
}
else
{
- TShock.Itembans.AddNewBan(EnglishLanguage.GetItemNameById(items[0].type));
+ // Yes this is required because of localization
+ // User may have passed in localized name but itembans works on English names
+ string englishNameForStorage = EnglishLanguage.GetItemNameById(items[0].type);
+ TShock.Itembans.AddNewBan(englishNameForStorage);
+
+ // It was decided in Telegram that we would continue to ban
+ // projectiles based on whether or not their associated item was
+ // banned. However, it was also decided that we'd change the way
+ // this worked: in particular, we'd make it so that the item ban
+ // system just adds things to the projectile ban system at the
+ // command layer instead of inferring the state of projectile
+ // bans based on the state of the item ban system.
+
+ if (items[0].type == ItemID.DirtRod)
+ {
+ TShock.ProjectileBans.AddNewBan(ProjectileID.DirtBall);
+ }
+
+ if (items[0].type == ItemID.Sandgun)
+ {
+ TShock.ProjectileBans.AddNewBan(ProjectileID.SandBallGun);
+ TShock.ProjectileBans.AddNewBan(ProjectileID.EbonsandBallGun);
+ TShock.ProjectileBans.AddNewBan(ProjectileID.PearlSandBallGun);
+ }
+
+ // This returns the localized name to the player, not the item as it was stored.
args.Player.SendSuccessMessage("Banned " + items[0].Name + ".");
}
}
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index e663af32..64242a07 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -344,7 +344,7 @@ namespace TShockAPI
PlayerUpdate.Invoke(null, args);
return args.Handled;
}
-
+
///
/// For use in a PlayerHP event
///
@@ -767,7 +767,7 @@ namespace TShockAPI
PlayerSpawn.Invoke(null, args);
return args.Handled;
}
-
+
///
/// For use in a ChestItemChange event
///
@@ -816,7 +816,7 @@ namespace TShockAPI
ChestItemChange.Invoke(null, args);
return args.Handled;
}
-
+
///
/// For use with a ChestOpen event
///
@@ -2054,15 +2054,6 @@ namespace TShockAPI
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))
- {
- control[5] = false;
- args.Player.Disable("using a banned item ({0})".SFormat(itemName), DisableFlags.WriteToLogAndConsole);
- 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)
{
@@ -2364,12 +2355,24 @@ namespace TShockAPI
return true;
}
- var type = Main.projectile[index].type;
+ short type = (short) Main.projectile[index].type;
// TODO: This needs to be moved somewhere else.
- if (!args.Player.HasProjectilePermission(index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill)
+
+ if (type == ProjectileID.Tombstone)
{
- args.Player.Disable("Does not have projectile permission to kill projectile.", DisableFlags.WriteToLogAndConsole);
+ args.Player.RemoveProjectile(ident, owner);
+ return true;
+ }
+
+ if (TShock.ProjectileBans.ProjectileIsBanned(type, args.Player) && !TShock.Config.IgnoreProjKill)
+ {
+ // According to 2012 deathmax, this is a workaround to fix skeletron prime issues
+ // https://github.com/Pryaxis/TShock/commit/a5aa9231239926f361b7246651e32144bbf28dda
+ if (type == ProjectileID.Bomb || type == ProjectileID.DeathLaser)
+ {
+ return false;
+ }
args.Player.RemoveProjectile(ident, owner);
return true;
}
@@ -2424,7 +2427,7 @@ namespace TShockAPI
Item item = new Item();
item.netDefaults(type);
- if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player))
+ if (stacks > item.maxStack)
{
return true;
}
diff --git a/TShockAPI/ItemBans.cs b/TShockAPI/ItemBans.cs
new file mode 100644
index 00000000..b719db9c
--- /dev/null
+++ b/TShockAPI/ItemBans.cs
@@ -0,0 +1,216 @@
+/*
+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.
+ internal sealed class ItemBans
+ {
+
+ /// The database connection layer to for the item ban subsystem.
+ private 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;
+ }
+
+ /// 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 = ((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);
+ }
+ }
+}
diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs
index 9bcdd0b9..5829f877 100644
--- a/TShockAPI/TSPlayer.cs
+++ b/TShockAPI/TSPlayer.cs
@@ -1173,48 +1173,6 @@ 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.
///
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index bf662d38..bb33bcc9 100644
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -134,6 +134,9 @@ namespace TShockAPI
/// The TShock anti-cheat/anti-exploit system.
internal Bouncer Bouncer;
+ /// The TShock item ban system.
+ internal ItemBans ItemBans;
+
///
/// TShock's Region subsystem.
///
@@ -325,6 +328,7 @@ namespace TShockAPI
RestManager.RegisterRestfulCommands();
Bouncer = new Bouncer();
RegionSystem = new RegionHandler(Regions);
+ ItemBans = new ItemBans(this, DB);
var geoippath = "GeoIP.dat";
if (Config.EnableGeoIP && File.Exists(geoippath))
@@ -1056,92 +1060,17 @@ namespace TShockAPI
player.Spawn();
}
- if (Main.ServerSideCharacter && !player.IsLoggedIn)
- {
- if (player.IsBeingDisabled())
- {
- player.Disable(flags: flags);
- }
- else if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].netID), player))
- {
- player.Disable($"holding banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}", flags);
- player.SendErrorMessage($"You are holding a banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}");
- }
- }
- else if (!Main.ServerSideCharacter || (Main.ServerSideCharacter && player.IsLoggedIn))
+ if (!Main.ServerSideCharacter || (Main.ServerSideCharacter && player.IsLoggedIn))
{
if (!player.HasPermission(Permissions.ignorestackhackdetection))
{
player.IsDisabledForStackDetection = player.HasHackedItemStacks(shouldWarnPlayer: true);
}
- string check = "none";
- // Please don't remove this for the time being; without it, players wearing banned equipment will only get debuffed once
- foreach (Item item in player.TPlayer.armor)
- {
- if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
- {
- player.SetBuff(BuffID.Frozen, 330, true);
- player.SetBuff(BuffID.Stoned, 330, true);
- player.SetBuff(BuffID.Webbed, 330, true);
- check = "Remove armor/accessory " + item.Name;
-
- player.SendErrorMessage("You are wearing banned equipment. {0}", check);
- break;
- }
- }
- foreach (Item item in player.TPlayer.dye)
- {
- if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
- {
- player.SetBuff(BuffID.Frozen, 330, true);
- player.SetBuff(BuffID.Stoned, 330, true);
- player.SetBuff(BuffID.Webbed, 330, true);
- check = "Remove dye " + item.Name;
-
- player.SendErrorMessage("You are wearing banned equipment. {0}", check);
- break;
- }
- }
- foreach (Item item in player.TPlayer.miscEquips)
- {
- if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
- {
- player.SetBuff(BuffID.Frozen, 330, true);
- player.SetBuff(BuffID.Stoned, 330, true);
- player.SetBuff(BuffID.Webbed, 330, true);
- check = "Remove misc equip " + item.Name;
-
- player.SendErrorMessage("You are wearing banned equipment. {0}", check);
- break;
- }
- }
- foreach (Item item in player.TPlayer.miscDyes)
- {
- if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
- {
- player.SetBuff(BuffID.Frozen, 330, true);
- player.SetBuff(BuffID.Stoned, 330, true);
- player.SetBuff(BuffID.Webbed, 330, true);
- check = "Remove misc dye " + item.Name;
-
- player.SendErrorMessage("You are wearing banned equipment. {0}", check);
- break;
- }
- }
- if (check != "none")
- player.IsDisabledForBannedWearable = true;
- else
- player.IsDisabledForBannedWearable = false;
if (player.IsBeingDisabled())
{
player.Disable(flags: flags);
}
- else if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].netID), player))
- {
- player.Disable($"holding banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}", flags);
- player.SendErrorMessage($"You are holding a banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}");
- }
}
}
}
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index b3deaaea..27c812c1 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -39,7 +39,6 @@
bin\Debug\TShockAPI.XML
x86
false
-
pdbonly
@@ -133,6 +132,7 @@
+
True
True