Merge pull request #1573 from Pryaxis/checkignores

Rebuilt TSPlayer.CheckIgnores() as TSPlayer.IsBeingDisabled(). Fixes #1561.
This commit is contained in:
Ivan 2017-12-22 00:57:54 +01:00 committed by GitHub
commit d4cb07379b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 59 deletions

View file

@ -56,6 +56,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Added `TSPlayer` to `GetDataHandlers.GemLockToggle`. (@hakusaro) * Added `TSPlayer` to `GetDataHandlers.GemLockToggle`. (@hakusaro)
* Added `GetDataHandlers.PlaceItemFrame` hook and related arguments. (@hakusaro) * Added `GetDataHandlers.PlaceItemFrame` hook and related arguments. (@hakusaro)
* Added `TSPlayer.IsBouncerThrottled()`. (@hakusaro) * Added `TSPlayer.IsBouncerThrottled()`. (@hakusaro)
* Added `TSPlayer.IsBeingDisabled()` and removed `TShock.CheckIgnores(TSPlayer)`. (@hakusaro)
* Added `TSPlayer.CheckIgnores()` and removed `TShock.CheckIgnores(TSPlayer)`. (@hakusaro) * Added `TSPlayer.CheckIgnores()` and removed `TShock.CheckIgnores(TSPlayer)`. (@hakusaro)
* Hooks inside TShock can now be registered with their `Register` method and can be prioritized according to the TShock HandlerList system. (@hakusaro) * Hooks inside TShock can now be registered with their `Register` method and can be prioritized according to the TShock HandlerList system. (@hakusaro)
* Fix message requiring login not using the command specifier set in the config file. (@hakusaro) * Fix message requiring login not using the command specifier set in the config file. (@hakusaro)

View file

@ -69,7 +69,7 @@ namespace TShockAPI
/// <param name="args">The packet arguments that the event has.</param> /// <param name="args">The packet arguments that the event has.</param>
internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEventArgs args) internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEventArgs args)
{ {
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1); NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1);
args.Handled = true; args.Handled = true;
@ -108,7 +108,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Handled = true; args.Handled = true;
return; return;
@ -126,7 +126,7 @@ namespace TShockAPI
/// <param name="args">The packet arguments that the event has.</param> /// <param name="args">The packet arguments that the event has.</param>
internal void OnPlaceTileEntity(object sender, GetDataHandlers.PlaceTileEntityEventArgs args) internal void OnPlaceTileEntity(object sender, GetDataHandlers.PlaceTileEntityEventArgs args)
{ {
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Handled = true; args.Handled = true;
return; return;
@ -177,7 +177,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Handled = true; args.Handled = true;
return; return;
@ -196,7 +196,7 @@ namespace TShockAPI
/// <param name="args">args</param> /// <param name="args">args</param>
internal void OnPlayerAnimation(object sender, GetDataHandlers.PlayerAnimationEventArgs args) internal void OnPlayerAnimation(object sender, GetDataHandlers.PlayerAnimationEventArgs args)
{ {
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index);
args.Handled = true; args.Handled = true;
@ -245,7 +245,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.NpcUpdate, "", id); args.Player.SendData(PacketTypes.NpcUpdate, "", id);
args.Handled = true; args.Handled = true;
@ -311,7 +311,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.PlayerHp, "", id); args.Player.SendData(PacketTypes.PlayerHp, "", id);
args.Player.SendData(PacketTypes.PlayerUpdate, "", id); args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
@ -421,7 +421,7 @@ namespace TShockAPI
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.ItemDrop, "", id); args.Player.SendData(PacketTypes.ItemDrop, "", id);
args.Handled = true; args.Handled = true;
@ -444,7 +444,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.PlayerAddBuff, "", id); args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
args.Handled = true; args.Handled = true;
@ -503,7 +503,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendData(PacketTypes.ChestItem, "", id, slot); args.Player.SendData(PacketTypes.ChestItem, "", id, slot);
args.Handled = true; args.Handled = true;
@ -558,7 +558,7 @@ namespace TShockAPI
/// <param name="args">The packet arguments that the event has.</param> /// <param name="args">The packet arguments that the event has.</param>
internal void OnChestOpen(object sender, GetDataHandlers.ChestOpenEventArgs args) internal void OnChestOpen(object sender, GetDataHandlers.ChestOpenEventArgs args)
{ {
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Handled = true; args.Handled = true;
return; return;
@ -595,7 +595,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendTileSquare(tileX, tileY, 3); args.Player.SendTileSquare(tileX, tileY, 3);
args.Handled = true; args.Handled = true;
@ -656,7 +656,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendTileSquare(tileX, tileY, 1); args.Player.SendTileSquare(tileX, tileY, 1);
args.Handled = true; args.Handled = true;
@ -791,7 +791,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner);
args.Handled = true; args.Handled = true;
@ -841,29 +841,29 @@ namespace TShockAPI
float distance = Vector2.Distance(new Vector2(pos.X / 16f, pos.Y / 16f), float distance = Vector2.Distance(new Vector2(pos.X / 16f, pos.Y / 16f),
new Vector2(args.Player.LastNetPosition.X / 16f, args.Player.LastNetPosition.Y / 16f)); new Vector2(args.Player.LastNetPosition.X / 16f, args.Player.LastNetPosition.Y / 16f));
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
// If the player has moved outside the disabled zone... // If the player has moved outside the disabled zone...
if (distance > TShock.Config.MaxRangeForDisabled) if (distance > TShock.Config.MaxRangeForDisabled)
{ {
// We need to tell them they were disabled and why, then revert the change. // We need to tell them they were disabled and why, then revert the change.
if (args.Player.IgnoreActionsForCheating != "none") if (args.Player.IsDisabledForStackDetection)
{ {
args.Player.SendErrorMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating); args.Player.SendErrorMessage("Disabled. You went too far with hacked item stacks.");
} }
else if (args.Player.IgnoreActionsForDisabledArmor != "none") else if (args.Player.IsDisabledForBannedWearable)
{ {
args.Player.SendErrorMessage("Disabled for banned armor: " + args.Player.IgnoreActionsForDisabledArmor); args.Player.SendErrorMessage("Disabled. You went too far with banned armor.");
} }
else if (args.Player.IgnoreActionsForInventory != "none") else if (args.Player.IsDisabledForSSC)
{ {
args.Player.SendErrorMessage("Disabled for Server Side Inventory: " + args.Player.IgnoreActionsForInventory); args.Player.SendErrorMessage("Disabled. You need to {0}login to load your saved data.", TShock.Config.CommandSpecifier);
} }
else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn) else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn)
{ {
args.Player.SendErrorMessage("Account needed! Please {0}register or {0}login to play!", TShock.Config.CommandSpecifier); args.Player.SendErrorMessage("Account needed! Please {0}register or {0}login to play!", TShock.Config.CommandSpecifier);
} }
else if (args.Player.IgnoreActionsForClearingTrashCan) else if (args.Player.IsDisabledPendingTrashRemoval)
{ {
args.Player.SendErrorMessage("You need to rejoin to ensure your trash can is cleared!"); args.Player.SendErrorMessage("You need to rejoin to ensure your trash can is cleared!");
} }
@ -982,7 +982,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.RemoveProjectile(ident, owner); args.Player.RemoveProjectile(ident, owner);
args.Handled = true; args.Handled = true;
@ -1115,7 +1115,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendTileSquare(x, y, 4); args.Player.SendTileSquare(x, y, 4);
args.Handled = true; args.Handled = true;
@ -1470,7 +1470,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendTileSquare(tileX, tileY, 4); args.Player.SendTileSquare(tileX, tileY, 4);
args.Handled = true; args.Handled = true;
@ -1586,7 +1586,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores() || args.Player.IsBouncerThrottled()) if (args.Player.IsBeingDisabled() || args.Player.IsBouncerThrottled())
{ {
args.Handled = true; args.Handled = true;
return; return;
@ -1627,7 +1627,7 @@ namespace TShockAPI
return; return;
} }
if (args.Player.CheckIgnores()) if (args.Player.IsBeingDisabled())
{ {
args.Player.SendTileSquare(tileX, tileY, size); args.Player.SendTileSquare(tileX, tileY, size);
args.Handled = true; args.Handled = true;

View file

@ -848,7 +848,7 @@ namespace TShockAPI
args.Player.tempGroup = null; args.Player.tempGroup = null;
args.Player.Account = account; args.Player.Account = account;
args.Player.IsLoggedIn = true; args.Player.IsLoggedIn = true;
args.Player.IgnoreActionsForInventory = "none"; args.Player.IsDisabledForSSC = false;
if (Main.ServerSideCharacter) if (Main.ServerSideCharacter)
{ {
@ -862,10 +862,10 @@ namespace TShockAPI
args.Player.LoginFailsBySsi = false; args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
args.Player.IgnoreActionsForCheating = "none"; args.Player.IsDisabledForStackDetection = false;
if (args.Player.HasPermission(Permissions.usebanneditem)) if (args.Player.HasPermission(Permissions.usebanneditem))
args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.IsDisabledForBannedWearable = false;
args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully."); args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully.");
@ -1636,7 +1636,7 @@ namespace TShockAPI
args.Player.SendSuccessMessage("SSC has been saved."); args.Player.SendSuccessMessage("SSC has been saved.");
foreach (TSPlayer player in TShock.Players) foreach (TSPlayer player in TShock.Players)
{ {
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
{ {
TShock.CharacterDB.InsertPlayerData(player, true); TShock.CharacterDB.InsertPlayerData(player, true);
} }
@ -1681,7 +1681,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Player \"{0}\" has to perform a /login attempt first.", matchedPlayer.Name); args.Player.SendErrorMessage("Player \"{0}\" has to perform a /login attempt first.", matchedPlayer.Name);
return; return;
} }
if (matchedPlayer.IgnoreActionsForClearingTrashCan) if (matchedPlayer.IsDisabledPendingTrashRemoval)
{ {
args.Player.SendErrorMessage("Player \"{0}\" has to reconnect first.", matchedPlayer.Name); args.Player.SendErrorMessage("Player \"{0}\" has to reconnect first.", matchedPlayer.Name);
return; return;
@ -1887,7 +1887,7 @@ namespace TShockAPI
{ {
foreach (TSPlayer player in TShock.Players) foreach (TSPlayer player in TShock.Players)
{ {
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
{ {
player.SaveServerCharacter(); player.SaveServerCharacter();
} }

View file

@ -1612,7 +1612,7 @@ namespace TShockAPI
args.Player.HasSentInventory && !args.Player.HasPermission(Permissions.bypassssc)) args.Player.HasSentInventory && !args.Player.HasPermission(Permissions.bypassssc))
{ {
// The player might have moved an item to their trash can before they performed a single login attempt yet. // The player might have moved an item to their trash can before they performed a single login attempt yet.
args.Player.IgnoreActionsForClearingTrashCan = true; args.Player.IsDisabledPendingTrashRemoval = true;
} }
if (slot == 58) //this is the hand if (slot == 58) //this is the hand
@ -1784,7 +1784,7 @@ namespace TShockAPI
args.Player.tempGroup = null; args.Player.tempGroup = null;
args.Player.Account = account; args.Player.Account = account;
args.Player.IsLoggedIn = true; args.Player.IsLoggedIn = true;
args.Player.IgnoreActionsForInventory = "none"; args.Player.IsDisabledForSSC = false;
if (Main.ServerSideCharacter) if (Main.ServerSideCharacter)
{ {
@ -1798,10 +1798,10 @@ namespace TShockAPI
args.Player.LoginFailsBySsi = false; args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
args.Player.IgnoreActionsForCheating = "none"; args.Player.IsDisabledForStackDetection = false;
if (args.Player.HasPermission(Permissions.usebanneditem)) if (args.Player.HasPermission(Permissions.usebanneditem))
args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.IsDisabledForBannedWearable = false;
args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully."); args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully.");
TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + ".");
@ -1856,7 +1856,7 @@ namespace TShockAPI
args.Player.tempGroup = null; args.Player.tempGroup = null;
args.Player.Account = account; args.Player.Account = account;
args.Player.IsLoggedIn = true; args.Player.IsLoggedIn = true;
args.Player.IgnoreActionsForInventory = "none"; args.Player.IsDisabledForSSC = false;
if (Main.ServerSideCharacter) if (Main.ServerSideCharacter)
{ {
@ -1870,10 +1870,10 @@ namespace TShockAPI
args.Player.LoginFailsBySsi = false; args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
args.Player.IgnoreActionsForCheating = "none"; args.Player.IsDisabledForStackDetection = false;
if (args.Player.HasPermission(Permissions.usebanneditem)) if (args.Player.HasPermission(Permissions.usebanneditem))
args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.IsDisabledForBannedWearable = false;
args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen);

View file

@ -277,13 +277,17 @@ namespace TShockAPI
private string CacheIP; private string CacheIP;
public string IgnoreActionsForInventory = "none"; /// <summary>Determines if the player is disabled by the SSC subsystem for not being logged in.</summary>
public bool IsDisabledForSSC = false;
public string IgnoreActionsForCheating = "none"; /// <summary>Determines if the player is disabled by Bouncer for having hacked item stacks.</summary>
public bool IsDisabledForStackDetection = false;
public string IgnoreActionsForDisabledArmor = "none"; /// <summary>Determines if the player is disabled by the item bans system for having banned wearables on the server.</summary>
public bool IsDisabledForBannedWearable = false;
public bool IgnoreActionsForClearingTrashCan; /// <summary>Determines if the player is disabled for not clearing their trash. A re-login is the only way to reset this.</summary>
public bool IsDisabledPendingTrashRemoval;
/// <summary>Checks to see if active throttling is happening on events by Bouncer. Rejects repeated events by malicious clients in a short window.</summary> /// <summary>Checks to see if active throttling is happening on events by Bouncer. Rejects repeated events by malicious clients in a short window.</summary>
/// <returns>If the player is currently being throttled by Bouncer, or not.</returns> /// <returns>If the player is currently being throttled by Bouncer, or not.</returns>
@ -292,12 +296,15 @@ namespace TShockAPI
return (DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000; return (DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000;
} }
/// <summary>CheckIgnores - Checks a players ignores...?</summary> /// <summary>Easy check if a player has any of IsDisabledForSSC, IsDisabledForStackDetection, IsDisabledForBannedWearable, or IsDisabledPendingTrashRemoval set. Or if they're not logged in and a login is required.</summary>
/// <param name="player">player - The TSPlayer object.</param> /// <returns>If any of the checks that warrant disabling are set on this player. If true, Disable() is repeatedly called on them.</returns>
/// <returns>bool - True if any ignore is not none, false, or login state differs from the required state.</returns> public bool IsBeingDisabled()
public bool CheckIgnores()
{ {
return IgnoreActionsForInventory != "none" || IgnoreActionsForCheating != "none" || IgnoreActionsForDisabledArmor != "none" || IgnoreActionsForClearingTrashCan || !IsLoggedIn && TShock.Config.RequireLogin; return IsDisabledForSSC
|| IsDisabledForStackDetection
|| IsDisabledForBannedWearable
|| IsDisabledPendingTrashRemoval
|| !IsLoggedIn && TShock.Config.RequireLogin;
} }
/// <summary> /// <summary>
@ -656,8 +663,8 @@ namespace TShockAPI
PlayerHooks.OnPlayerLogout(this); PlayerHooks.OnPlayerLogout(this);
if (Main.ServerSideCharacter) if (Main.ServerSideCharacter)
{ {
IgnoreActionsForInventory = $"Server side characters is enabled! Please {Commands.Specifier}register or {Commands.Specifier}login to play!"; IsDisabledForSSC = true;
if (!IgnoreActionsForClearingTrashCan && (!Dead || TPlayer.difficulty != 2)) if (!IsDisabledPendingTrashRemoval && (!Dead || TPlayer.difficulty != 2))
{ {
PlayerData.CopyCharacter(this); PlayerData.CopyCharacter(this);
TShock.CharacterDB.InsertPlayerData(this); TShock.CharacterDB.InsertPlayerData(this);

View file

@ -945,7 +945,7 @@ namespace TShockAPI
foreach (TSPlayer player in Players) foreach (TSPlayer player in Players)
{ {
// prevent null point exceptions // prevent null point exceptions
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
{ {
CharacterDB.InsertPlayerData(player); CharacterDB.InsertPlayerData(player);
@ -1083,7 +1083,7 @@ namespace TShockAPI
if (Main.ServerSideCharacter && !player.IsLoggedIn) if (Main.ServerSideCharacter && !player.IsLoggedIn)
{ {
if (player.CheckIgnores()) if (player.IsBeingDisabled())
{ {
player.Disable(flags: flags); player.Disable(flags: flags);
} }
@ -1106,7 +1106,7 @@ namespace TShockAPI
break; break;
} }
} }
player.IgnoreActionsForCheating = check; player.IsDisabledForStackDetection = true;
check = "none"; check = "none";
// Please don't remove this for the time being; without it, players wearing banned equipment will only get debuffed once // 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) foreach (Item item in player.TPlayer.armor)
@ -1161,9 +1161,9 @@ namespace TShockAPI
break; break;
} }
} }
player.IgnoreActionsForDisabledArmor = check; player.IsDisabledForBannedWearable = true;
if (player.CheckIgnores()) if (player.IsBeingDisabled())
{ {
player.Disable(flags: flags); player.Disable(flags: flags);
} }
@ -1409,7 +1409,7 @@ namespace TShockAPI
Utils.Broadcast(tsplr.Name + " has left.", Color.Yellow); Utils.Broadcast(tsplr.Name + " has left.", Color.Yellow);
Log.Info("{0} disconnected.", tsplr.Name); Log.Info("{0} disconnected.", tsplr.Name);
if (tsplr.IsLoggedIn && !tsplr.IgnoreActionsForClearingTrashCan && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2)) if (tsplr.IsLoggedIn && !tsplr.IsDisabledPendingTrashRemoval && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2))
{ {
tsplr.PlayerData.CopyCharacter(tsplr); tsplr.PlayerData.CopyCharacter(tsplr);
CharacterDB.InsertPlayerData(tsplr); CharacterDB.InsertPlayerData(tsplr);
@ -1684,8 +1684,8 @@ namespace TShockAPI
{ {
if (Main.ServerSideCharacter) if (Main.ServerSideCharacter)
{ {
player.SendErrorMessage( player.IsDisabledForSSC = true;
player.IgnoreActionsForInventory = String.Format("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier)); player.SendErrorMessage(String.Format("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier));
player.LoginHarassed = true; player.LoginHarassed = true;
} }
else if (Config.RequireLogin) else if (Config.RequireLogin)