Merge branch 'hackedinventory' into crp
This commit is contained in:
commit
848d3c4778
5 changed files with 285 additions and 218 deletions
|
|
@ -63,6 +63,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
|
|||
* Move `TShock.CheckRangePermission()` to `TSPlayer.IsInRange` which **returns the opposite** of what the previous method did (see updated docs). (@hakusaro)
|
||||
* Move `TShock.CheckSpawn` to `Utils.IsInSpawn`. (@hakusaro)
|
||||
* Replace `TShock.CheckTilePermission` with `TSPlayer.HasBuildPermission`, `TSPlayer.HasPaintPermission`, and `TSPlayer.HasModifiedIceSuccessfully` respectively. (@hakusaro)
|
||||
* Fix stack hack detection being inconsistent between two different check points. Moved `TShock.HackedInventory` to `TSPlayer.HasHackedItemStacks`. Added `GetDataHandlers.GetDataHandledEventArgs` which is where most hooks will inherit from in the future. (@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.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace TShockAPI
|
|||
{
|
||||
// Setup hooks
|
||||
|
||||
GetDataHandlers.GetSection += OnGetSection;
|
||||
GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame;
|
||||
GetDataHandlers.GemLockToggle += OnGemLockToggle;
|
||||
GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity;
|
||||
|
|
@ -64,6 +65,28 @@ namespace TShockAPI
|
|||
GetDataHandlers.TileEdit += OnTileEdit;
|
||||
}
|
||||
|
||||
internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs args)
|
||||
{
|
||||
if (args.Player.RequestedSection)
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
args.Player.RequestedSection = true;
|
||||
|
||||
if (String.IsNullOrEmpty(args.Player.Name))
|
||||
{
|
||||
TShock.Utils.ForceKick(args.Player, "Blank name.", true);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.Player.HasPermission(Permissions.ignorestackhackdetection))
|
||||
{
|
||||
args.Player.IsDisabledForStackDetection = args.Player.HasHackedItemStacks(shouldWarnPlayer: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fired when an item frame is placed for anti-cheat detection.</summary>
|
||||
/// <param name="sender">The object that triggered the event.</param>
|
||||
/// <param name="args">The packet arguments that the event has.</param>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,19 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom HandledEventArgs that contains TShock's TSPlayer for the triggering uesr and the Terraria MP data stream.
|
||||
/// Differentiated by GetDataHandlerArgs because it can be handled and responds to being handled.
|
||||
/// </summary>
|
||||
public class GetDataHandledEventArgs : HandledEventArgs
|
||||
{
|
||||
/// <summary>The TSPlayer that triggered the event.</summary>
|
||||
public TSPlayer Player { get; set; }
|
||||
|
||||
/// <summary>The raw MP packet data associated with the event.</summary>
|
||||
public MemoryStream Data { get; set; }
|
||||
}
|
||||
|
||||
public static class GetDataHandlers
|
||||
{
|
||||
private static Dictionary<PacketTypes, GetDataHandlerDelegate> GetDataHandlerDelegates;
|
||||
|
|
@ -1903,21 +1916,46 @@ namespace TShockAPI
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>The arguments to a GetSection packet.</summary>
|
||||
public class GetSectionEventArgs : GetDataHandledEventArgs
|
||||
{
|
||||
/// <summary>The X position requested. Or -1 for spawn.</summary>
|
||||
public int X { get; set; }
|
||||
|
||||
/// <summary>The Y position requested. Or -1 for spawn.</summary>
|
||||
public int Y { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>The hook for a GetSection event.</summary>
|
||||
public static HandlerList<GetSectionEventArgs> GetSection = new HandlerList<GetSectionEventArgs>();
|
||||
|
||||
/// <summary>Fires a GetSection event.</summary>
|
||||
/// <param name="player">The TSPlayer that caused the GetSection.</param>
|
||||
/// <param name="data">The raw MP protocol data.</param>
|
||||
/// <param name="x">The x coordinate requested or -1 for spawn.</param>
|
||||
/// <param name="y">The y coordinate requested or -1 for spawn.</param>
|
||||
/// <returns>bool</returns>
|
||||
private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y)
|
||||
{
|
||||
if (GetSection == null)
|
||||
return false;
|
||||
|
||||
var args = new GetSectionEventArgs
|
||||
{
|
||||
Player = player,
|
||||
Data = data,
|
||||
X = x,
|
||||
Y = y,
|
||||
};
|
||||
|
||||
GetSection.Invoke(null, args);
|
||||
return args.Handled;
|
||||
}
|
||||
|
||||
private static bool HandleGetSection(GetDataHandlerArgs args)
|
||||
{
|
||||
if (args.Player.RequestedSection)
|
||||
if (OnGetSection(args.Player, args.Data, args.Data.ReadInt32(), args.Data.ReadInt32()))
|
||||
return true;
|
||||
args.Player.RequestedSection = true;
|
||||
if (String.IsNullOrEmpty(args.Player.Name))
|
||||
{
|
||||
TShock.Utils.ForceKick(args.Player, "Blank name.", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!args.Player.HasPermission(Permissions.ignorestackhackdetection))
|
||||
{
|
||||
TShock.HackedInventory(args.Player);
|
||||
}
|
||||
|
||||
if (TShock.Utils.ActivePlayers() + 1 > TShock.Config.MaxSlots &&
|
||||
!args.Player.HasPermission(Permissions.reservedslot))
|
||||
|
|
|
|||
|
|
@ -307,6 +307,215 @@ namespace TShockAPI
|
|||
|| !IsLoggedIn && TShock.Config.RequireLogin;
|
||||
}
|
||||
|
||||
/// <summary>Checks to see if a player has hacked item stacks in their inventory, and messages them as it checks.</summary>
|
||||
/// <param name="shouldWarnPlayer">If the check should send a message to the player with the results of the check.</param>
|
||||
/// <returns>True if any stacks don't conform.</returns>
|
||||
public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
|
||||
{
|
||||
// Iterates through each inventory location a player has.
|
||||
// This section is sub divided into number ranges for what each range of slots corresponds to.
|
||||
bool check = false;
|
||||
|
||||
Item[] inventory = TPlayer.inventory;
|
||||
Item[] armor = TPlayer.armor;
|
||||
Item[] dye = TPlayer.dye;
|
||||
Item[] miscEquips = TPlayer.miscEquips;
|
||||
Item[] miscDyes = TPlayer.miscDyes;
|
||||
Item[] piggy = TPlayer.bank.item;
|
||||
Item[] safe = TPlayer.bank2.item;
|
||||
Item[] forge = TPlayer.bank3.item;
|
||||
Item trash = TPlayer.trashItem;
|
||||
for (int i = 0; i < NetItem.MaxInventory; i++)
|
||||
{
|
||||
if (i < NetItem.InventoryIndex.Item2)
|
||||
{
|
||||
// From above: this is slots 0-58 in the inventory.
|
||||
// 0-58
|
||||
Item item = new Item();
|
||||
if (inventory[i] != null && inventory[i].netID != 0)
|
||||
{
|
||||
item.netDefaults(inventory[i].netID);
|
||||
item.Prefix(inventory[i].prefix);
|
||||
item.AffixName();
|
||||
if (inventory[i].stack > item.maxStack || inventory[i].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove item {0} ({1}) and then rejoin.", item.Name, inventory[i].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.ArmorIndex.Item2)
|
||||
{
|
||||
// 59-78
|
||||
var index = i - NetItem.ArmorIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (armor[index] != null && armor[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(armor[index].netID);
|
||||
item.Prefix(armor[index].prefix);
|
||||
item.AffixName();
|
||||
if (armor[index].stack > item.maxStack || armor[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove armor {0} ({1}) and then rejoin.", item.Name, armor[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.DyeIndex.Item2)
|
||||
{
|
||||
// 79-88
|
||||
var index = i - NetItem.DyeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (dye[index] != null && dye[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(dye[index].netID);
|
||||
item.Prefix(dye[index].prefix);
|
||||
item.AffixName();
|
||||
if (dye[index].stack > item.maxStack || dye[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove dye {0} ({1}) and then rejoin.", item.Name, dye[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.MiscEquipIndex.Item2)
|
||||
{
|
||||
// 89-93
|
||||
var index = i - NetItem.MiscEquipIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (miscEquips[index] != null && miscEquips[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(miscEquips[index].netID);
|
||||
item.Prefix(miscEquips[index].prefix);
|
||||
item.AffixName();
|
||||
if (miscEquips[index].stack > item.maxStack || miscEquips[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove item {0} ({1}) and then rejoin.", item.Name, miscEquips[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.MiscDyeIndex.Item2)
|
||||
{
|
||||
// 93-98
|
||||
var index = i - NetItem.MiscDyeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (miscDyes[index] != null && miscDyes[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(miscDyes[index].netID);
|
||||
item.Prefix(miscDyes[index].prefix);
|
||||
item.AffixName();
|
||||
if (miscDyes[index].stack > item.maxStack || miscDyes[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove item dye {0} ({1}) and then rejoin.", item.Name, miscDyes[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.PiggyIndex.Item2)
|
||||
{
|
||||
// 98-138
|
||||
var index = i - NetItem.PiggyIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (piggy[index] != null && piggy[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(piggy[index].netID);
|
||||
item.Prefix(piggy[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (piggy[index].stack > item.maxStack || piggy[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove piggy-bank item {0} ({1}) and then rejoin.", item.Name, piggy[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.SafeIndex.Item2)
|
||||
{
|
||||
// 138-178
|
||||
var index = i - NetItem.SafeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (safe[index] != null && safe[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(safe[index].netID);
|
||||
item.Prefix(safe[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (safe[index].stack > item.maxStack || safe[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove safe item {0} ({1}) and then rejoin.", item.Name, safe[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.TrashIndex.Item2)
|
||||
{
|
||||
// 179-219
|
||||
Item item = new Item();
|
||||
if (trash != null && trash.netID != 0)
|
||||
{
|
||||
item.netDefaults(trash.netID);
|
||||
item.Prefix(trash.prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (trash.stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove trash item {0} ({1}) and then rejoin.", item.Name, trash.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 220
|
||||
var index = i - NetItem.ForgeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (forge[index] != null && forge[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(forge[index].netID);
|
||||
item.Prefix(forge[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (forge[index].stack > item.maxStack || forge[index].stack < 0)
|
||||
{
|
||||
check = true;
|
||||
if (shouldWarnPlayer)
|
||||
{
|
||||
SendErrorMessage("Stack cheat detected. Remove Defender's Forge item {0} ({1}) and then rejoin.", item.Name, forge[index].stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return check;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The player's server side inventory data.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1082,17 +1082,10 @@ namespace TShockAPI
|
|||
else if (!Main.ServerSideCharacter || (Main.ServerSideCharacter && player.IsLoggedIn))
|
||||
{
|
||||
string check = "none";
|
||||
foreach (Item item in player.TPlayer.inventory)
|
||||
if (!player.HasPermission(Permissions.ignorestackhackdetection))
|
||||
{
|
||||
if (!player.HasPermission(Permissions.ignorestackhackdetection) && (item.stack > item.maxStack || item.stack < 0) &&
|
||||
item.type != 0)
|
||||
{
|
||||
check = "Remove item " + item.Name + " (" + item.stack + ") exceeds max stack of " + item.maxStack;
|
||||
player.SendErrorMessage(check);
|
||||
break;
|
||||
}
|
||||
player.IsDisabledForStackDetection = player.HasHackedItemStacks(shouldWarnPlayer: true);
|
||||
}
|
||||
player.IsDisabledForStackDetection = true;
|
||||
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)
|
||||
|
|
@ -1753,203 +1746,6 @@ namespace TShockAPI
|
|||
return Utils.Distance(value1, value2);
|
||||
}
|
||||
|
||||
/// <summary>HackedInventory - Checks to see if a user has a hacked inventory. In addition, messages players the result.</summary>
|
||||
/// <param name="player">player - The TSPlayer object.</param>
|
||||
/// <returns>bool - True if the player has a hacked inventory.</returns>
|
||||
public static bool HackedInventory(TSPlayer player)
|
||||
{
|
||||
bool check = false;
|
||||
|
||||
Item[] inventory = player.TPlayer.inventory;
|
||||
Item[] armor = player.TPlayer.armor;
|
||||
Item[] dye = player.TPlayer.dye;
|
||||
Item[] miscEquips = player.TPlayer.miscEquips;
|
||||
Item[] miscDyes = player.TPlayer.miscDyes;
|
||||
Item[] piggy = player.TPlayer.bank.item;
|
||||
Item[] safe = player.TPlayer.bank2.item;
|
||||
Item[] forge = player.TPlayer.bank3.item;
|
||||
Item trash = player.TPlayer.trashItem;
|
||||
for (int i = 0; i < NetItem.MaxInventory; i++)
|
||||
{
|
||||
if (i < NetItem.InventoryIndex.Item2)
|
||||
{
|
||||
//0-58
|
||||
Item item = new Item();
|
||||
if (inventory[i] != null && inventory[i].netID != 0)
|
||||
{
|
||||
item.netDefaults(inventory[i].netID);
|
||||
item.Prefix(inventory[i].prefix);
|
||||
item.AffixName();
|
||||
if (inventory[i].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.Name, inventory[i].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.ArmorIndex.Item2)
|
||||
{
|
||||
//59-78
|
||||
var index = i - NetItem.ArmorIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (armor[index] != null && armor[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(armor[index].netID);
|
||||
item.Prefix(armor[index].prefix);
|
||||
item.AffixName();
|
||||
if (armor[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove armor {0} ({1}) and then rejoin", item.Name, armor[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.DyeIndex.Item2)
|
||||
{
|
||||
//79-88
|
||||
var index = i - NetItem.DyeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (dye[index] != null && dye[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(dye[index].netID);
|
||||
item.Prefix(dye[index].prefix);
|
||||
item.AffixName();
|
||||
if (dye[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove dye {0} ({1}) and then rejoin", item.Name, dye[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.MiscEquipIndex.Item2)
|
||||
{
|
||||
//89-93
|
||||
var index = i - NetItem.MiscEquipIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (miscEquips[index] != null && miscEquips[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(miscEquips[index].netID);
|
||||
item.Prefix(miscEquips[index].prefix);
|
||||
item.AffixName();
|
||||
if (miscEquips[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.Name, miscEquips[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.MiscDyeIndex.Item2)
|
||||
{
|
||||
//93-98
|
||||
var index = i - NetItem.MiscDyeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (miscDyes[index] != null && miscDyes[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(miscDyes[index].netID);
|
||||
item.Prefix(miscDyes[index].prefix);
|
||||
item.AffixName();
|
||||
if (miscDyes[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove item dye {0} ({1}) and then rejoin", item.Name, miscDyes[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.PiggyIndex.Item2)
|
||||
{
|
||||
//98-138
|
||||
var index = i - NetItem.PiggyIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (piggy[index] != null && piggy[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(piggy[index].netID);
|
||||
item.Prefix(piggy[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (piggy[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove Piggy-bank item {0} ({1}) and then rejoin", item.Name, piggy[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.SafeIndex.Item2)
|
||||
{
|
||||
//138-178
|
||||
var index = i - NetItem.SafeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (safe[index] != null && safe[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(safe[index].netID);
|
||||
item.Prefix(safe[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (safe[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove Safe item {0} ({1}) and then rejoin", item.Name, safe[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i < NetItem.TrashIndex.Item2)
|
||||
{
|
||||
//179-219
|
||||
Item item = new Item();
|
||||
if (trash != null && trash.netID != 0)
|
||||
{
|
||||
item.netDefaults(trash.netID);
|
||||
item.Prefix(trash.prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (trash.stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove trash item {0} ({1}) and then rejoin", item.Name, trash.stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//220
|
||||
var index = i - NetItem.ForgeIndex.Item1;
|
||||
Item item = new Item();
|
||||
if (forge[index] != null && forge[index].netID != 0)
|
||||
{
|
||||
item.netDefaults(forge[index].netID);
|
||||
item.Prefix(forge[index].prefix);
|
||||
item.AffixName();
|
||||
|
||||
if (forge[index].stack > item.maxStack)
|
||||
{
|
||||
check = true;
|
||||
player.SendMessage(
|
||||
String.Format("Stack cheat detected. Remove Defender's Forge item {0} ({1}) and then rejoin", item.Name, forge[index].stack),
|
||||
Color.Cyan);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return check;
|
||||
}
|
||||
|
||||
/// <summary>OnConfigRead - Fired when the config file has been read.</summary>
|
||||
/// <param name="file">file - The config file object.</param>
|
||||
public void OnConfigRead(ConfigFile file)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue