diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index bffc67ea..d5b84911 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -66,20 +66,26 @@ namespace TShockAPI public List Names { get; protected set; } public bool AllowServer { get; set; } public bool DoLog { get; set; } - public string Permission { get; protected set; } + public List Permissions { get; protected set; } private CommandDelegate command; + public Command(List permissionsneeded, CommandDelegate cmd, params string[] names) + : this(cmd, names) + { + Permissions = permissionsneeded; + } + public Command(string permissionneeded, CommandDelegate cmd, params string[] names) : this(cmd, names) { - Permission = permissionneeded; + Permissions = new List { permissionneeded }; } public Command(CommandDelegate cmd, params string[] names) { if (names == null || names.Length < 1) throw new NotSupportedException(); - Permission = null; + Permissions = null; Names = new List(names); command = cmd; AllowServer = true; @@ -88,7 +94,7 @@ namespace TShockAPI public bool Run(string msg, TSPlayer ply, List parms) { - if (!ply.Group.HasPermission(Permission)) + if (!CanRun(ply)) return false; try @@ -111,7 +117,14 @@ namespace TShockAPI public bool CanRun(TSPlayer ply) { - return ply.Group.HasPermission(Permission); + if (Permissions == null) + return true; + foreach (var Permission in Permissions) + { + if (ply.Group.HasPermission(Permission)) + return true; + } + return false; } } @@ -133,14 +146,15 @@ namespace TShockAPI ChatCommands.Add(new Command(Permissions.cfg, SetSpawn, "setspawn") { AllowServer = false }); ChatCommands.Add(new Command(Permissions.grow, Grow, "grow") { AllowServer = false }); ChatCommands.Add(new Command(Permissions.item, Item, "item", "i") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, Home, "home") { AllowServer = false }); + ChatCommands.Add(new Command(Permissions.home, Home, "home") { AllowServer = false }); ChatCommands.Add(new Command(Permissions.canpartychat, PartyChat, "p") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, Spawn, "spawn") { AllowServer = false }); + ChatCommands.Add(new Command(Permissions.spawn, Spawn, "spawn") { AllowServer = false }); ChatCommands.Add(new Command(Permissions.tp, TP, "tp") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, TPHere, "tphere") { AllowServer = false }); + ChatCommands.Add(new Command(Permissions.tphere, TPHere, "tphere") { AllowServer = false }); ChatCommands.Add(new Command(Permissions.tpallow, TPAllow, "tpallow") { AllowServer = false }); add(Permissions.kick, Kick, "kick"); - add(Permissions.ban, Ban, "ban", "banip", "listbans", "unban", "unbanip", "clearbans"); + add(Permissions.ban, DeprecateBans, "banip", "listbans", "unban", "unbanip", "clearbans"); + add(Permissions.ban, Ban, "ban"); add(Permissions.whitelist, Whitelist, "whitelist"); add(Permissions.maintenance, Off, "off", "exit"); add(Permissions.maintenance, Restart, "restart"); //Added restart command @@ -161,7 +175,9 @@ namespace TShockAPI add(Permissions.spawnboss, SkeletronPrime, "skeletronp", "prime"); add(Permissions.spawnboss, Hardcore, "hardcore"); add(Permissions.spawnmob, SpawnMob, "spawnmob", "sm"); - add(Permissions.warp, Warp, "warp", "setwarp", "delwarp", "sendwarp", "sw"); + add(Permissions.warp, Warp, "warp"); + add(Permissions.managewarp, DeprecateWarp, "setwarp", "delwarp", "hidewarp"); + add(Permissions.tphere, DeprecateWarp, "sendwarp", "sw"); add(Permissions.managegroup, AddGroup, "addgroup"); add(Permissions.managegroup, DeleteGroup, "delgroup"); add(Permissions.managegroup, ModifyGroup, "modgroup"); @@ -209,6 +225,8 @@ namespace TShockAPI add(Permissions.cfg, WorldInfo, "world"); add(Permissions.savessi, SaveSSI, "savessi"); add(Permissions.savessi, OverrideSSI, "overridessi", "ossi"); + add(Permissions.xmas, ForceXmas, "forcexmas"); + add(Permissions.settempgroup, TempGroup, "tempgroup"); //add(null, TestCallbackCommand, "test"); } @@ -223,9 +241,12 @@ namespace TShockAPI string cmdName = args[0].ToLower(); args.RemoveAt(0); - Command cmd = ChatCommands.FirstOrDefault(c => c.HasAlias(cmdName)); + if (Hooks.PlayerHooks.OnPlayerCommand(player, cmdName, cmdText, args)) + return true; - if (cmd == null) + IEnumerable cmds = ChatCommands.Where(c => c.HasAlias(cmdName)); + + if (cmds.Count() == 0) { if (player.AwaitingResponse.ContainsKey(cmdName)) { @@ -237,23 +258,25 @@ namespace TShockAPI player.SendErrorMessage("Invalid command entered. Type /help for a list of valid commands."); return true; } - - if (!cmd.CanRun(player)) - { - TShock.Utils.SendLogs(string.Format("{0} tried to execute /{1}.", player.Name, cmdText), Color.Red); - player.SendErrorMessage("You do not have access to that command."); - } - else if (!cmd.AllowServer && !player.RealPlayer) - { - player.SendErrorMessage("You must use this command in-game."); - } - else - { - if (cmd.DoLog) - TShock.Utils.SendLogs(string.Format("{0} executed: /{1}.", player.Name, cmdText), Color.Red); - cmd.Run(cmdText, player, args); - } - return true; + foreach (Command cmd in cmds) + { + if (!cmd.CanRun(player)) + { + TShock.Utils.SendLogs(string.Format("{0} tried to execute /{1}.", player.Name, cmdText), Color.Red); + player.SendErrorMessage("You do not have access to that command."); + } + else if (!cmd.AllowServer && !player.RealPlayer) + { + player.SendErrorMessage("You must use this command in-game."); + } + else + { + if (cmd.DoLog) + TShock.Utils.SendLogs(string.Format("{0} executed: /{1}.", player.Name, cmdText), Color.Red); + cmd.Run(cmdText, player, args); + } + } + return true; } /// @@ -414,6 +437,7 @@ namespace TShockAPI args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.Group = group; + args.Player.tempGroup = null; args.Player.UserAccountName = user.Name; args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); args.Player.IsLoggedIn = true; @@ -437,6 +461,8 @@ namespace TShockAPI args.Player.LoginHarassed = false; } + + Hooks.PlayerHooks.OnPlayerLogin(args.Player); } else { @@ -798,10 +824,19 @@ namespace TShockAPI } } + private static void DeprecateBans(CommandArgs args) + { + args.Player.SendInfoMessage("All ban commands were merged into one in TShock 4.0."); + args.Player.SendInfoMessage("Syntax: /ban [option] [arguments]"); + args.Player.SendInfoMessage("Options: list, listip, clear, add, addip, del, delip"); + args.Player.SendInfoMessage("Arguments: list, listip, clear [code], add [name], addip [ip], del [name], delip [name]"); + args.Player.SendInfoMessage("In addition, a reason may be provided for all new bans after the arguments."); + return; + } + private static void Ban(CommandArgs args) { - - if (args.Parameters[0].ToLower() == "help") + if (args.Parameters.Count == 0 || args.Parameters[0].ToLower() == "help") { args.Player.SendInfoMessage("All ban commands were merged into one in TShock 4.0."); args.Player.SendInfoMessage("Syntax: /ban [option] [arguments]"); @@ -1136,6 +1171,74 @@ namespace TShockAPI } } + private static void ForceXmas(CommandArgs args) + { + if(args.Parameters.Count == 0) + { + args.Player.SendErrorMessage("Usage: /forcexmas [true/false]"); + args.Player.SendInfoMessage( + String.Format("The server is currently {0} force Christmas mode.", + (TShock.Config.ForceXmas ? "in" : "not in"))); + return; + } + + if(args.Parameters[0].ToLower() == "true") + { + TShock.Config.ForceXmas = true; + Main.checkXMas(); + } + else if(args.Parameters[0].ToLower() == "false") + { + TShock.Config.ForceXmas = false; + Main.checkXMas(); + } + else + { + args.Player.SendErrorMessage("Usage: /forcexmas [true/false]"); + return; + } + + args.Player.SendInfoMessage( + String.Format("The server is currently {0} force Christmas mode.", + (TShock.Config.ForceXmas ? "in" : "not in"))); + } + + public static void TempGroup(CommandArgs args) + { + if (args.Parameters.Count < 2) + { + args.Player.SendInfoMessage("Invalid usage"); + args.Player.SendInfoMessage("Usage: /tempgroup "); + return; + } + + List ply = TShock.Utils.FindPlayer(args.Parameters[0]); + if(ply.Count < 1) + { + args.Player.SendErrorMessage(string.Format("Could not find player {0}.", args.Parameters[0])); + return; + } + + if (ply.Count > 1) + { + args.Player.SendErrorMessage(string.Format("Found more than one match for {0}.", args.Parameters[0])); + return; + } + + if(!TShock.Groups.GroupExists(args.Parameters[1])) + { + args.Player.SendErrorMessage(string.Format("Could not find group {0}", args.Parameters[1])); + return; + } + + Group g = TShock.Utils.GetGroup(args.Parameters[1]); + + ply[0].tempGroup = g; + + args.Player.SendSuccessMessage(string.Format("You have changed {0}'s group to {1}", ply[0].Name, g.Name)); + ply[0].SendSuccessMessage(string.Format("Your group has temporarily been changed to {0}", g.Name)); + } + #endregion Player Management Commands #region Server Maintenence Commands @@ -1619,6 +1722,17 @@ namespace TShockAPI args.Player.TPAllow = !args.Player.TPAllow; } + private static void DeprecateWarp(CommandArgs args) + { + args.Player.SendInfoMessage("All warp commands were merged into one in TShock 4.0."); + args.Player.SendInfoMessage("Previous warps with spaces should be wrapped in single quotes."); + args.Player.SendInfoMessage("Invalid syntax. Syntax: /warp [command] [arguments]"); + args.Player.SendInfoMessage("Commands: add, del, hide, list, send, [warpname]"); + args.Player.SendInfoMessage("Arguments: add [warp name], del [warp name], list [page]"); + args.Player.SendInfoMessage("Arguments: send [player] [warp name], hide [warp name] [Enable(true/false)]"); + args.Player.SendInfoMessage("Examples: /warp add foobar, /warp hide foobar true, /warp foobar"); + } + private static void Warp(CommandArgs args) { bool hasManageWarpPermission = args.Player.Group.HasPermission(Permissions.managewarp); @@ -2267,8 +2381,19 @@ namespace TShockAPI return; } - int amount = Convert.ToInt32(args.Parameters[0]); - int.TryParse(args.Parameters[0], out amount); + int amount = -1; + if (!int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendWarningMessage(string.Format("Invalid spawnrate ({0})", args.Parameters[0])); + return; + } + + if (amount < 0) + { + args.Player.SendWarningMessage("Spawnrate cannot be negative!"); + return; + } + NPC.defaultSpawnRate = amount; TShock.Config.DefaultSpawnRate = amount; TSPlayer.All.SendInfoMessage(string.Format("{0} changed the spawn rate to {1}.", args.Player.Name, amount)); diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index d7b8063d..d3da94fc 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -243,6 +243,10 @@ namespace TShockAPI [Description("Hide stat tracker console messages.")] public bool HideStatTrackerDebugMessages = true; + [Description("Force Christmas only events to occur all year.")] public bool ForceXmas = false; + + [Description("Allows groups on the banned item allowed list to spawn banned items.")] public bool AllowAllowedGroupsToSpawnBannedItems = false; + /// /// Reads a configuration file from a given path /// diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs index 31519726..a0382a2e 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/DB/RegionManager.cs @@ -238,25 +238,21 @@ namespace TShockAPI.DB return false; } Region top = null; - for (int i = 0; i < Regions.Count; i++) - { - if (Regions[i].InArea(x,y) ) - { - if (top == null) - top = Regions[i]; - else - { - if (Regions[i].Z > top.Z) - top = Regions[i]; - } - } - } + + foreach (Region region in Regions.ToList()) + { + if (region.InArea(x, y)) + { + if (top == null || region.Z > top.Z) + top = region; + } + } return top == null || top.HasPermissionToBuildInRegion(ply); } public bool InArea(int x, int y) { - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && @@ -271,7 +267,7 @@ namespace TShockAPI.DB public List InAreaRegionName(int x, int y) { List regions = new List() { }; - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && @@ -286,7 +282,7 @@ namespace TShockAPI.DB public List InAreaRegion(int x, int y) { List regions = new List() { }; - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 6b34735b..d1d892f0 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1372,6 +1372,7 @@ namespace TShockAPI args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.Group = group; + args.Player.tempGroup = null; args.Player.UserAccountName = args.Player.Name; args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); args.Player.IsLoggedIn = true; @@ -1384,6 +1385,7 @@ namespace TShockAPI } args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); + Hooks.PlayerHooks.OnPlayerLogin(args.Player); return true; } TShock.Utils.ForceKick(args.Player, "Invalid user account password.", true); @@ -1442,13 +1444,15 @@ namespace TShockAPI Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", args.Player.Name, args.Player.IP, args.Player.Group.Name, args.Player.Country, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(string.Format("{0} ({1}) has joined.", args.Player.Name, args.Player.Country), Color.Yellow); + if (!args.Player.SilentJoinInProgress) + TShock.Utils.Broadcast(string.Format("{0} ({1}) has joined.", args.Player.Name, args.Player.Country), Color.Yellow); } else { Log.Info(string.Format("{0} ({1}) from '{2}' group joined. ({3}/{4})", args.Player.Name, args.Player.IP, args.Player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(args.Player.Name + " has joined.", Color.Yellow); + if (!args.Player.SilentJoinInProgress) + TShock.Utils.Broadcast(args.Player.Name + " has joined.", Color.Yellow); } if (TShock.Config.DisplayIPToAdmins) diff --git a/TShockAPI/Hooks/PlayerHooks.cs b/TShockAPI/Hooks/PlayerHooks.cs new file mode 100644 index 00000000..4b46a35d --- /dev/null +++ b/TShockAPI/Hooks/PlayerHooks.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace TShockAPI.Hooks +{ + public class PlayerLoginEventArgs + { + public TSPlayer Player { get; set; } + public PlayerLoginEventArgs(TSPlayer ply) + { + Player = ply; + } + } + + public class PlayerCommandEventArgs : HandledEventArgs + { + public TSPlayer Player { get; set; } + public string CommandName { get; set; } + public string CommandText { get; set; } + public List Parameters { get; set; } + } + + public static class PlayerHooks + { + public delegate void PlayerLoginD(PlayerLoginEventArgs e); + public static event PlayerLoginD PlayerLogin; + public delegate void PlayerCommandD(PlayerCommandEventArgs e); + public static event PlayerCommandD PlayerCommand; + + public static void OnPlayerLogin(TSPlayer ply) + { + if(PlayerLogin == null) + { + return; + } + + PlayerLoginEventArgs args = new PlayerLoginEventArgs(ply); + PlayerLogin(args); + } + + public static bool OnPlayerCommand(TSPlayer player, string cmdName, string cmdText, List args) + { + if (PlayerCommand == null) + { + return false; + } + PlayerCommandEventArgs playerCommandEventArgs = new PlayerCommandEventArgs() + { + Player = player, + CommandName = cmdName, + CommandText = cmdText, + Parameters = args + + }; + PlayerCommand(playerCommandEventArgs); + return playerCommandEventArgs.Handled; + } + } +} diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index f80ed598..83315dca 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -169,6 +169,13 @@ namespace TShockAPI [Description("User can use rest api calls.")] public static readonly string restapi; + [Description("User can force the server to Christmas mode.")] public static readonly string xmas; + + [Description("User can use /home.")] public static readonly string home; + + [Description("User can use /spawn.")] public static readonly string spawn; + + [Description("User can elevate other users' groups temporarily.")] public static readonly string settempgroup; static Permissions() { foreach (var field in typeof (Permissions).GetFields()) @@ -189,7 +196,7 @@ namespace TShockAPI { if (Commands.ChatCommands.Count < 1) Commands.InitCommands(); - return Commands.ChatCommands.Where(c => c.Permission == perm).ToList(); + return Commands.ChatCommands.Where(c => c.Permissions.Contains(perm)).ToList(); } /// diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 96a4b9c9..b93a594a 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -1,1005 +1,1044 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team - -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.Diagnostics; -using System.IO; -using System.Threading; -using Terraria; -using TShockAPI.Net; - -namespace TShockAPI -{ - public class TSPlayer - { - /// - /// This represents the server as a player. - /// - public static readonly TSServerPlayer Server = new TSServerPlayer(); - - /// - /// This player represents all the players. - /// - public static readonly TSPlayer All = new TSPlayer("All"); - - /// - /// The amount of tiles that the player has killed in the last second. - /// - public int TileKillThreshold { get; set; } - - /// - /// The amount of tiles the player has placed in the last second. - /// - public int TilePlaceThreshold { get; set; } - - /// - /// The amount of liquid( in tiles ) that the player has placed in the last second. - /// - public int TileLiquidThreshold { get; set; } - - /// - /// The number of projectiles created by the player in the last second. - /// - public int ProjectileThreshold { get; set; } - - /// - /// A queue of tiles destroyed by the player for reverting. - /// - public Dictionary TilesDestroyed { get; protected set; } - - /// - /// A queue of tiles placed by the player for reverting. - /// - public Dictionary TilesCreated { get; protected set; } - - public int FirstMaxHP { get; set; } - - public int FirstMaxMP { get; set; } - - /// - /// The player's group. - /// - public Group Group { get; set; } - - public bool ReceivedInfo { get; set; } - - /// - /// The players index in the player array( Main.players[] ). - /// - public int Index { get; protected set; } - - /// - /// The last time the player changed their team or pvp status. - /// - public DateTime LastPvpChange; - - /// - /// Temp points for use in regions and other plugins. - /// - public Point[] TempPoints = new Point[2]; - - /// - /// Whether the player is waiting to place/break a tile to set as a temp point. - /// - public int AwaitingTempPoint { get; set; } - - /// - /// A list of command callbacks indexed by the command they need to do. - /// - public Dictionary> AwaitingResponse; - - public bool AwaitingName { get; set; } - - /// - /// The last time a player broke a grief check. - /// - public DateTime LastThreat { get; set; } - - /// - /// Not used, can be removed. - /// - public DateTime LastTileChangeNotify { get; set; } - - public bool InitSpawn; - - /// - /// Whether the player should see logs. - /// - public bool DisplayLogs = true; - - public Vector2 oldSpawn = Vector2.Zero; - - /// - /// The last player that the player whispered with( to or from ). - /// - public TSPlayer LastWhisper; - - /// - /// The number of unsuccessful login attempts. - /// - public int LoginAttempts { get; set; } - - public Vector2 TeleportCoords = new Vector2(-1, -1); - - public Vector2 LastNetPosition = Vector2.Zero; - - /// - /// The player's login name. - /// - public string UserAccountName { get; set; } - - /// - /// Unused can be removed. - /// - public bool HasBeenSpammedWithBuildMessage; - - /// - /// Whether the player is logged in or not. - /// - public bool IsLoggedIn; - - /// - /// The player's user id( from the db ). - /// - public int UserID = -1; - - /// - /// Whether the player has been nagged about logging in. - /// - public bool HasBeenNaggedAboutLoggingIn; - - public bool TPAllow = true; - - /// - /// Whether the player is muted or not. - /// - public bool mute; - - public bool TpLock; - - private Player FakePlayer; - - public bool RequestedSection; - - /// - /// The last time the player died. - /// - public DateTime LastDeath { get; set; } - - /// - /// Whether the player is dead or not. - /// - public bool Dead; - - public string Country = "??"; - - /// - /// The players difficulty( normal[softcore], mediumcore, hardcore ). - /// - public int Difficulty; - - private string CacheIP; - - public string IgnoreActionsForInventory = "none"; - - public string IgnoreActionsForCheating = "none"; - - public string IgnoreActionsForDisabledArmor = "none"; - - public bool IgnoreActionsForClearingTrashCan; - - /// - /// The player's server side inventory data. - /// - public PlayerData PlayerData; - - /// - /// Whether the player needs to specify a password upon connection( either server or user account ). - /// - public bool RequiresPassword; - - public bool SilentKickInProgress; - - /// - /// A list of points where ice tiles have been placed. - /// - public List IceTiles; - - /// - /// Unused, can be removed. - /// - public long RPm = 1; - - /// - /// World protection message cool down. - /// - public long WPm = 1; - - /// - /// Spawn protection message cool down. - /// - public long SPm = 1; - - /// - /// Permission to build message cool down. - /// - public long BPm = 1; - - /// - /// The time in ms when the player has logged in. - /// - public long LoginMS; - - /// - /// Whether the player has been harrassed about logging in due to server side inventory or forced login. - /// - public bool LoginHarassed = false; - - /// - /// Whether the player is a real, human, player on the server. - /// - public bool RealPlayer - { - get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; } - } - - public bool ConnectionAlive - { - get - { - return RealPlayer && - (Netplay.serverSock[Index] != null && Netplay.serverSock[Index].active && !Netplay.serverSock[Index].kill); - } - } - - public int State - { - get { return Netplay.serverSock[Index].state; } - set { Netplay.serverSock[Index].state = value; } - } - - public string IP - { - get - { - if (string.IsNullOrEmpty(CacheIP)) - return - CacheIP = - RealPlayer - ? (Netplay.serverSock[Index].tcpClient.Connected - ? TShock.Utils.GetRealIP(Netplay.serverSock[Index].tcpClient.Client.RemoteEndPoint.ToString()) - : "") - : ""; - else - return CacheIP; - } - } - - /// - /// Saves the player's inventory to SSI - /// - /// bool - True/false if it saved successfully - public bool SaveServerInventory() - { - if (!TShock.Config.ServerSideInventory) - { - return false; - } - try - { - PlayerData.CopyInventory(this); - TShock.InventoryDB.InsertPlayerData(this); - return true; - } catch (Exception e) - { - Log.Error(e.Message); - return false; - } - - } - - /// - /// Terraria Player - /// - public Player TPlayer - { - get { return FakePlayer ?? Main.player[Index]; } - } - - public string Name - { - get { return TPlayer.name; } - } - - public bool Active - { - get { return TPlayer != null && TPlayer.active; } - } - - public int Team - { - get { return TPlayer.team; } - } - - public float X - { - get { return RealPlayer ? TPlayer.position.X : Main.spawnTileX*16; } - } - - public float Y - { - get { return RealPlayer ? TPlayer.position.Y : Main.spawnTileY*16; } - } - - public int TileX - { - get { return (int) (X/16); } - } - - public int TileY - { - get { return (int) (Y/16); } - } - - public bool InventorySlotAvailable - { - get - { - bool flag = false; - if (RealPlayer) - { - for (int i = 0; i < 40; i++) //41 is trash can, 42-45 is coins, 46-49 is ammo - { - if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].name == "") - { - flag = true; - break; - } - } - } - return flag; - } - } - - public TSPlayer(int index) - { - TilesDestroyed = new Dictionary(); - TilesCreated = new Dictionary(); - Index = index; - Group = new Group(TShock.Config.DefaultGuestGroupName); - IceTiles = new List(); - AwaitingResponse = new Dictionary>(); - } - - protected TSPlayer(String playerName) - { - TilesDestroyed = new Dictionary(); - TilesCreated = new Dictionary(); - Index = -1; - FakePlayer = new Player {name = playerName, whoAmi = -1}; - Group = new Group(TShock.Config.DefaultGuestGroupName); - AwaitingResponse = new Dictionary>(); - } - - public virtual void Disconnect(string reason) - { - SendData(PacketTypes.Disconnect, reason); - } - - public virtual void Flush() - { - var sock = Netplay.serverSock[Index]; - if (sock == null) - return; - - TShock.PacketBuffer.Flush(sock); - } - - - public void SendWorldInfo(int tilex, int tiley, bool fakeid) - { - using (var ms = new MemoryStream()) - { - var msg = new WorldInfoMsg - { - Time = (int) Main.time, - DayTime = Main.dayTime, - MoonPhase = (byte) Main.moonPhase, - BloodMoon = Main.bloodMoon, - MaxTilesX = Main.maxTilesX, - MaxTilesY = Main.maxTilesY, - SpawnX = tilex, - SpawnY = tiley, - WorldSurface = (int) Main.worldSurface, - RockLayer = (int) Main.rockLayer, - //Sending a fake world id causes the client to not be able to find a stored spawnx/y. - //This fixes the bed spawn point bug. With a fake world id it wont be able to find the bed spawn. - WorldID = !fakeid ? Main.worldID : -1, - WorldFlags = (WorldGen.shadowOrbSmashed ? WorldInfoFlag.OrbSmashed : WorldInfoFlag.None) | - (NPC.downedBoss1 ? WorldInfoFlag.DownedBoss1 : WorldInfoFlag.None) | - (NPC.downedBoss2 ? WorldInfoFlag.DownedBoss2 : WorldInfoFlag.None) | - (NPC.downedBoss3 ? WorldInfoFlag.DownedBoss3 : WorldInfoFlag.None) | - (Main.hardMode ? WorldInfoFlag.HardMode : WorldInfoFlag.None) | - (NPC.downedClown ? WorldInfoFlag.DownedClown : WorldInfoFlag.None), - WorldName = TShock.Config.UseServerName ? TShock.Config.ServerName : Main.worldName - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public bool Teleport(int tilex, int tiley) - { - InitSpawn = false; - - SendWorldInfo(tilex, tiley, true); - - //150 Should avoid all client crash errors - //The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks - //Try 300 if it does not work (Higher number - Longer load times - Less chance of error) - //Should we properly send sections so that clients don't get tiles twice? - SendTileSquare(tilex, tiley, 150); - -/* //We shouldn't need this section anymore -it can prevent otherwise acceptable teleportation under some circumstances. - - if (!SendTileSquare(tilex, tiley, 150)) - { - InitSpawn = true; - SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); - return false; - } - -*/ - Spawn(-1, -1); - - SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); - - TPlayer.position.X = (float)(tilex * 16 + 8 - TPlayer.width /2); - TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height); - //We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now. - - SendTileSquare(tilex, tiley, 10); - - return true; - } - - public void Spawn() - { - Spawn(TPlayer.SpawnX, TPlayer.SpawnY); - } - - public void Spawn(int tilex, int tiley) - { - using (var ms = new MemoryStream()) - { - var msg = new SpawnMsg - { - PlayerIndex = (byte) Index, - TileX = tilex, - TileY = tiley - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public void RemoveProjectile(int index, int owner) - { - using (var ms = new MemoryStream()) - { - var msg = new ProjectileRemoveMsg - { - Index = (short) index, - Owner = (byte) owner - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public virtual bool SendTileSquare(int x, int y, int size = 10) - { - try - { - int num = (size - 1)/2; - int m_x=0; - int m_y=0; - - if (x - num <0){ - m_x=0; - }else{ - m_x = x - num; - } - - if (y - num <0){ - m_y=0; - }else{ - m_y = y - num; - } - - if (m_x + size > Main.maxTilesX){ - m_x=Main.maxTilesX - size; - } - - if (m_y + size > Main.maxTilesY){ - m_y=Main.maxTilesY - size; - } - - SendData(PacketTypes.TileSendSquare, "", size, m_x, m_y); - return true; - } - catch (IndexOutOfRangeException) - { - - // This is expected if square exceeds array. - } - catch (Exception ex) - { - Log.Error(ex.ToString()); - } - return false; - } - - public bool GiveItemCheck(int type, string name, int width, int height, int stack, int prefix = 0) - { - if (TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) - return false; - - GiveItem(type,name,width,height,stack,prefix); - return true; - } - - public virtual void GiveItem(int type, string name, int width, int height, int stack, int prefix = 0) - { - int itemid = Item.NewItem((int) X, (int) Y, width, height, type, stack, true, prefix); - - // This is for special pickaxe/hammers/swords etc - Main.item[itemid].SetDefaults(name); - // The set default overrides the wet and stack set by NewItem - Main.item[itemid].wet = Collision.WetCollision(Main.item[itemid].position, Main.item[itemid].width, - Main.item[itemid].height); - Main.item[itemid].stack = stack; - Main.item[itemid].owner = Index; - Main.item[itemid].prefix = (byte) prefix; - NetMessage.SendData((int) PacketTypes.ItemDrop, -1, -1, "", itemid, 0f, 0f, 0f); - NetMessage.SendData((int) PacketTypes.ItemOwner, -1, -1, "", itemid, 0f, 0f, 0f); - } - - public virtual void SendInfoMessage(string msg) - { - SendMessage(msg, Color.Yellow); - } - - public virtual void SendSuccessMessage(string msg) - { - SendMessage(msg, Color.Green); - } - - public virtual void SendWarningMessage(string msg) - { - SendMessage(msg, Color.OrangeRed); - } - - public virtual void SendErrorMessage(string msg) - { - SendMessage(msg, Color.Red); - } - - [Obsolete("Use SendErrorMessage, SendInfoMessage, or SendWarningMessage, or a custom color instead.")] - public virtual void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public virtual void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public virtual void SendMessage(string msg, byte red, byte green, byte blue) - { - SendData(PacketTypes.ChatText, msg, 255, red, green, blue); - } - - public virtual void SendMessageFromPlayer(string msg, byte red, byte green, byte blue, int ply) - { - SendDataFromPlayer(PacketTypes.ChatText, ply, msg, red, green, blue, 0); - } - - public virtual void DamagePlayer(int damage) - { - NetMessage.SendData((int) PacketTypes.PlayerDamage, -1, -1, "", Index, ((new Random()).Next(-1, 1)), damage, - (float) 0); - } - - public virtual void SetTeam(int team) - { - Main.player[Index].team = team; - SendData(PacketTypes.PlayerTeam, "", Index); - } - - public virtual void Disable(string reason = "") - { - LastThreat = DateTime.UtcNow; - SetBuff(33, 330, true); //Weak - SetBuff(32, 330, true); //Slow - SetBuff(23, 330, true); //Cursed - if (!string.IsNullOrEmpty(reason)) - Log.ConsoleInfo(string.Format("Player {0} has been disabled for {1}.", Name, reason)); - - var trace = new StackTrace(); - StackFrame frame = null; - frame = trace.GetFrame(1); - if (frame != null && frame.GetMethod().DeclaringType != null) - Log.Debug(frame.GetMethod().DeclaringType.Name + " called Disable()."); - } - - public virtual void Whoopie(object time) - { - var time2 = (int) time; - var launch = DateTime.UtcNow; - var startname = Name; - SendMessage("You are now being annoyed.", Color.Red); - while ((DateTime.UtcNow - launch).TotalSeconds < time2 && startname == Name) - { - SendData(PacketTypes.NpcSpecial, number: Index, number2: 2f); - Thread.Sleep(50); - } - } - - public virtual void SetBuff(int type, int time = 3600, bool bypass = false) - { - if ((DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000 && !bypass) - return; - - SendData(PacketTypes.PlayerAddBuff, number: Index, number2: type, number3: time); - } - - //Todo: Separate this into a few functions. SendTo, SendToAll, etc - public virtual void SendData(PacketTypes msgType, string text = "", int number = 0, float number2 = 0f, - float number3 = 0f, float number4 = 0f, int number5 = 0) - { - if (RealPlayer && !ConnectionAlive) - return; - - NetMessage.SendData((int) msgType, Index, -1, text, number, number2, number3, number4, number5); - } - - public virtual void SendDataFromPlayer(PacketTypes msgType, int ply, string text = "", float number2 = 0f, float number3 = 0f, float number4 = 0f, int number5 = 0) - { - if (RealPlayer && !ConnectionAlive) - return; - - NetMessage.SendData((int) msgType, Index, -1, text, ply, number2, number3, number4, number5); - } - - public virtual bool SendRawData(byte[] data) - { - if (!RealPlayer || !ConnectionAlive) - return false; - - return TShock.SendBytes(Netplay.serverSock[Index], data); - } - - /// - /// Adds a command callback to a specified command string. - /// - /// The string representing the command i.e "yes" == /yes - /// The method that will be executed on confirmation ie user accepts - public void AddResponse( string name, Action callback) - { - if( AwaitingResponse.ContainsKey(name)) - { - AwaitingResponse.Remove(name); - } - - AwaitingResponse.Add(name, callback); - } - } - - public class TSRestPlayer : TSServerPlayer - { - internal List CommandReturn = new List(); - - public TSRestPlayer() - { - Group = new SuperAdminGroup(); - AwaitingResponse = new Dictionary>(); - } - - public override void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public override void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public override void SendMessage(string msg, byte red, byte green, byte blue) - { - CommandReturn.Add(msg); - } - - public List GetCommandOutput() - { - return CommandReturn; - } - } - - public class TSServerPlayer : TSPlayer - { - public TSServerPlayer() - : base("Server") - { - Group = new SuperAdminGroup(); - } - - public override void SendErrorMessage(string msg) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(msg); - Console.ResetColor(); - } - - public override void SendInfoMessage(string msg) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(msg); - Console.ResetColor(); - } - - public override void SendSuccessMessage(string msg) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(msg); - Console.ResetColor(); - } - - public override void SendWarningMessage(string msg) - { - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.WriteLine(msg); - Console.ResetColor(); - } - - public override void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public override void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public override void SendMessage(string msg, byte red, byte green, byte blue) - { - Console.WriteLine(msg); - //RconHandler.Response += msg + "\n"; - } - - public void SetFullMoon(bool fullmoon) - { - Main.moonPhase = 0; - SetTime(false, 0); - } - - public void SetBloodMoon(bool bloodMoon) - { - Main.bloodMoon = bloodMoon; - SetTime(false, 0); - } - - public void SetTime(bool dayTime, double time) - { - Main.dayTime = dayTime; - Main.time = time; - NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); - NetMessage.syncPlayers(); - } - - public void SpawnNPC(int type, string name, int amount, int startTileX, int startTileY, int tileXRange = 100, - int tileYRange = 50) - { - for (int i = 0; i < amount; i++) - { - int spawnTileX; - int spawnTileY; - TShock.Utils.GetRandomClearTileWithInRange(startTileX, startTileY, tileXRange, tileYRange, out spawnTileX, - out spawnTileY); - int npcid = NPC.NewNPC(spawnTileX*16, spawnTileY*16, type, 0); - // This is for special slimes - Main.npc[npcid].SetDefaults(name); - } - } - - public void StrikeNPC(int npcid, int damage, float knockBack, int hitDirection) - { - Main.npc[npcid].StrikeNPC(damage, knockBack, hitDirection); - NetMessage.SendData((int) PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection); - } - - public void RevertTiles(Dictionary tiles) - { - // Update Main.Tile first so that when tile sqaure is sent it is correct - foreach (KeyValuePair entry in tiles) - { - Main.tile[(int) entry.Key.X, (int) entry.Key.Y].Data = entry.Value; - } - // Send all players updated tile sqaures - foreach (Vector2 coords in tiles.Keys) - { - All.SendTileSquare((int) coords.X, (int) coords.Y, 3); - } - } - } - - public class PlayerData - { - public NetItem[] inventory = new NetItem[NetItem.maxNetInventory]; - public int maxHealth = 100; - //public int maxMana = 100; - public bool exists; - - public PlayerData(TSPlayer player) - { - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - this.inventory[i] = new NetItem(); - } - this.inventory[0].netID = -15; - this.inventory[0].stack = 1; - if (player.TPlayer.inventory[0] != null && player.TPlayer.inventory[0].netID == -15) - this.inventory[0].prefix = player.TPlayer.inventory[0].prefix; - this.inventory[1].netID = -13; - this.inventory[1].stack = 1; - if (player.TPlayer.inventory[1] != null && player.TPlayer.inventory[1].netID == -13) - this.inventory[1].prefix = player.TPlayer.inventory[1].prefix; - this.inventory[2].netID = -16; - this.inventory[2].stack = 1; - if (player.TPlayer.inventory[2] != null && player.TPlayer.inventory[2].netID == -16) - this.inventory[2].prefix = player.TPlayer.inventory[2].prefix; - } - - public void StoreSlot(int slot, int netID, int prefix, int stack) - { - if(slot > (this.inventory.Length - 1)) //if the slot is out of range then dont save - { - return; - } - - this.inventory[slot].netID = netID; - if (this.inventory[slot].netID != 0) - { - this.inventory[slot].stack = stack; - this.inventory[slot].prefix = prefix; - } - else - { - this.inventory[slot].stack = 0; - this.inventory[slot].prefix = 0; - } - } - - public void CopyInventory(TSPlayer player) - { - this.maxHealth = player.TPlayer.statLifeMax; - Item[] inventory = player.TPlayer.inventory; - Item[] armor = player.TPlayer.armor; - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - if (i < 49) - { - if (player.TPlayer.inventory[i] != null) - { - this.inventory[i].netID = inventory[i].netID; - } - else - { - this.inventory[i].netID = 0; - } - - if (this.inventory[i].netID != 0) - { - this.inventory[i].stack = inventory[i].stack; - this.inventory[i].prefix = inventory[i].prefix; - } - else - { - this.inventory[i].stack = 0; - this.inventory[i].prefix = 0; - } - } - else - { - if (player.TPlayer.armor[i - 48] != null) - { - this.inventory[i].netID = armor[i - 48].netID; - } - else - { - this.inventory[i].netID = 0; - } - - if (this.inventory[i].netID != 0) - { - this.inventory[i].stack = armor[i - 48].stack; - this.inventory[i].prefix = armor[i - 48].prefix; - } - else - { - this.inventory[i].stack = 0; - this.inventory[i].prefix = 0; - } - } - } - } - } - - public class NetItem - { - public static int maxNetInventory = 59; - public int netID; - public int stack; - public int prefix; - - public static string ToString(NetItem[] inventory) - { - string inventoryString = ""; - for (int i = 0; i < maxNetInventory; i++) - { - if (i != 0) - inventoryString += "~"; - inventoryString += inventory[i].netID; - if (inventory[i].netID != 0) - { - inventoryString += "," + inventory[i].stack; - inventoryString += "," + inventory[i].prefix; - } - else - { - inventoryString += ",0,0"; - } - } - return inventoryString; - } - - public static NetItem[] Parse(string data) - { - NetItem[] inventory = new NetItem[maxNetInventory]; - int i; - for (i = 0; i < maxNetInventory; i++) - { - inventory[i] = new NetItem(); - } - string[] items = data.Split('~'); - i = 0; - foreach (string item in items) - { - string[] idata = item.Split(','); - inventory[i].netID = int.Parse(idata[0]); - inventory[i].stack = int.Parse(idata[1]); - inventory[i].prefix = int.Parse(idata[2]); - i++; - } - return inventory; - } - } -} +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2012 The TShock Team + +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.Diagnostics; +using System.IO; +using System.Threading; +using Terraria; +using TShockAPI.Net; + +namespace TShockAPI +{ + public class TSPlayer + { + /// + /// This represents the server as a player. + /// + public static readonly TSServerPlayer Server = new TSServerPlayer(); + + /// + /// This player represents all the players. + /// + public static readonly TSPlayer All = new TSPlayer("All"); + + /// + /// The amount of tiles that the player has killed in the last second. + /// + public int TileKillThreshold { get; set; } + + /// + /// The amount of tiles the player has placed in the last second. + /// + public int TilePlaceThreshold { get; set; } + + /// + /// The amount of liquid( in tiles ) that the player has placed in the last second. + /// + public int TileLiquidThreshold { get; set; } + + /// + /// The number of projectiles created by the player in the last second. + /// + public int ProjectileThreshold { get; set; } + + /// + /// A queue of tiles destroyed by the player for reverting. + /// + public Dictionary TilesDestroyed { get; protected set; } + + /// + /// A queue of tiles placed by the player for reverting. + /// + public Dictionary TilesCreated { get; protected set; } + + public int FirstMaxHP { get; set; } + + public int FirstMaxMP { get; set; } + + /// + /// The player's group. + /// + public Group Group + { + get + { + if (tempGroup != null) + return tempGroup; + return group; + } + set { group = value; } + } + + /// + /// The player's temporary group. This overrides the user's actual group. + /// + public Group tempGroup = null; + + private Group group = null; + + public bool ReceivedInfo { get; set; } + + /// + /// The players index in the player array( Main.players[] ). + /// + public int Index { get; protected set; } + + /// + /// The last time the player changed their team or pvp status. + /// + public DateTime LastPvpChange; + + /// + /// Temp points for use in regions and other plugins. + /// + public Point[] TempPoints = new Point[2]; + + /// + /// Whether the player is waiting to place/break a tile to set as a temp point. + /// + public int AwaitingTempPoint { get; set; } + + /// + /// A list of command callbacks indexed by the command they need to do. + /// + public Dictionary> AwaitingResponse; + + public bool AwaitingName { get; set; } + + /// + /// The last time a player broke a grief check. + /// + public DateTime LastThreat { get; set; } + + /// + /// Not used, can be removed. + /// + public DateTime LastTileChangeNotify { get; set; } + + public bool InitSpawn; + + /// + /// Whether the player should see logs. + /// + public bool DisplayLogs = true; + + public Vector2 oldSpawn = Vector2.Zero; + + /// + /// The last player that the player whispered with( to or from ). + /// + public TSPlayer LastWhisper; + + /// + /// The number of unsuccessful login attempts. + /// + public int LoginAttempts { get; set; } + + public Vector2 TeleportCoords = new Vector2(-1, -1); + + public Vector2 LastNetPosition = Vector2.Zero; + + /// + /// The player's login name. + /// + public string UserAccountName { get; set; } + + /// + /// Unused can be removed. + /// + public bool HasBeenSpammedWithBuildMessage; + + /// + /// Whether the player is logged in or not. + /// + public bool IsLoggedIn; + + /// + /// The player's user id( from the db ). + /// + public int UserID = -1; + + /// + /// Whether the player has been nagged about logging in. + /// + public bool HasBeenNaggedAboutLoggingIn; + + public bool TPAllow = true; + + /// + /// Whether the player is muted or not. + /// + public bool mute; + + public bool TpLock; + + private Player FakePlayer; + + public bool RequestedSection; + + /// + /// The last time the player died. + /// + public DateTime LastDeath { get; set; } + + /// + /// Whether the player is dead or not. + /// + public bool Dead; + + public string Country = "??"; + + /// + /// The players difficulty( normal[softcore], mediumcore, hardcore ). + /// + public int Difficulty; + + private string CacheIP; + + public string IgnoreActionsForInventory = "none"; + + public string IgnoreActionsForCheating = "none"; + + public string IgnoreActionsForDisabledArmor = "none"; + + public bool IgnoreActionsForClearingTrashCan; + + /// + /// The player's server side inventory data. + /// + public PlayerData PlayerData; + + /// + /// Whether the player needs to specify a password upon connection( either server or user account ). + /// + public bool RequiresPassword; + + public bool SilentKickInProgress; + + public bool SilentJoinInProgress; + + /// + /// A list of points where ice tiles have been placed. + /// + public List IceTiles; + + /// + /// Unused, can be removed. + /// + public long RPm = 1; + + /// + /// World protection message cool down. + /// + public long WPm = 1; + + /// + /// Spawn protection message cool down. + /// + public long SPm = 1; + + /// + /// Permission to build message cool down. + /// + public long BPm = 1; + + /// + /// The time in ms when the player has logged in. + /// + public long LoginMS; + + /// + /// Whether the player has been harrassed about logging in due to server side inventory or forced login. + /// + public bool LoginHarassed = false; + + /// + /// Whether the player is a real, human, player on the server. + /// + public bool RealPlayer + { + get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; } + } + + public bool ConnectionAlive + { + get + { + return RealPlayer && + (Netplay.serverSock[Index] != null && Netplay.serverSock[Index].active && !Netplay.serverSock[Index].kill); + } + } + + public int State + { + get { return Netplay.serverSock[Index].state; } + set { Netplay.serverSock[Index].state = value; } + } + + public string IP + { + get + { + if (string.IsNullOrEmpty(CacheIP)) + return + CacheIP = + RealPlayer + ? (Netplay.serverSock[Index].tcpClient.Connected + ? TShock.Utils.GetRealIP(Netplay.serverSock[Index].tcpClient.Client.RemoteEndPoint.ToString()) + : "") + : ""; + else + return CacheIP; + } + } + + /// + /// Saves the player's inventory to SSI + /// + /// bool - True/false if it saved successfully + public bool SaveServerInventory() + { + if (!TShock.Config.ServerSideInventory) + { + return false; + } + try + { + PlayerData.CopyInventory(this); + TShock.InventoryDB.InsertPlayerData(this); + return true; + } catch (Exception e) + { + Log.Error(e.Message); + return false; + } + + } + + /// + /// Terraria Player + /// + public Player TPlayer + { + get { return FakePlayer ?? Main.player[Index]; } + } + + public string Name + { + get { return TPlayer.name; } + } + + public bool Active + { + get { return TPlayer != null && TPlayer.active; } + } + + public int Team + { + get { return TPlayer.team; } + } + + public float X + { + get { return RealPlayer ? TPlayer.position.X : Main.spawnTileX*16; } + } + + public float Y + { + get { return RealPlayer ? TPlayer.position.Y : Main.spawnTileY*16; } + } + + public int TileX + { + get { return (int) (X/16); } + } + + public int TileY + { + get { return (int) (Y/16); } + } + + public bool InventorySlotAvailable + { + get + { + bool flag = false; + if (RealPlayer) + { + for (int i = 0; i < 40; i++) //41 is trash can, 42-45 is coins, 46-49 is ammo + { + if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].name == "") + { + flag = true; + break; + } + } + } + return flag; + } + } + + public TSPlayer(int index) + { + TilesDestroyed = new Dictionary(); + TilesCreated = new Dictionary(); + Index = index; + Group = new Group(TShock.Config.DefaultGuestGroupName); + IceTiles = new List(); + AwaitingResponse = new Dictionary>(); + } + + protected TSPlayer(String playerName) + { + TilesDestroyed = new Dictionary(); + TilesCreated = new Dictionary(); + Index = -1; + FakePlayer = new Player {name = playerName, whoAmi = -1}; + Group = new Group(TShock.Config.DefaultGuestGroupName); + AwaitingResponse = new Dictionary>(); + } + + public virtual void Disconnect(string reason) + { + SendData(PacketTypes.Disconnect, reason); + } + + public virtual void Flush() + { + var sock = Netplay.serverSock[Index]; + if (sock == null) + return; + + TShock.PacketBuffer.Flush(sock); + } + + + public void SendWorldInfo(int tilex, int tiley, bool fakeid) + { + using (var ms = new MemoryStream()) + { + var msg = new WorldInfoMsg + { + Time = (int) Main.time, + DayTime = Main.dayTime, + MoonPhase = (byte) Main.moonPhase, + BloodMoon = Main.bloodMoon, + MaxTilesX = Main.maxTilesX, + MaxTilesY = Main.maxTilesY, + SpawnX = tilex, + SpawnY = tiley, + WorldSurface = (int) Main.worldSurface, + RockLayer = (int) Main.rockLayer, + //Sending a fake world id causes the client to not be able to find a stored spawnx/y. + //This fixes the bed spawn point bug. With a fake world id it wont be able to find the bed spawn. + WorldID = !fakeid ? Main.worldID : -1, + WorldFlags = (WorldGen.shadowOrbSmashed ? WorldInfoFlag.OrbSmashed : WorldInfoFlag.None) | + (NPC.downedBoss1 ? WorldInfoFlag.DownedBoss1 : WorldInfoFlag.None) | + (NPC.downedBoss2 ? WorldInfoFlag.DownedBoss2 : WorldInfoFlag.None) | + (NPC.downedBoss3 ? WorldInfoFlag.DownedBoss3 : WorldInfoFlag.None) | + (Main.hardMode ? WorldInfoFlag.HardMode : WorldInfoFlag.None) | + (NPC.downedClown ? WorldInfoFlag.DownedClown : WorldInfoFlag.None), + WorldName = TShock.Config.UseServerName ? TShock.Config.ServerName : Main.worldName + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public bool Teleport(int tilex, int tiley) + { + InitSpawn = false; + + SendWorldInfo(tilex, tiley, true); + + //150 Should avoid all client crash errors + //The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks + //Try 300 if it does not work (Higher number - Longer load times - Less chance of error) + //Should we properly send sections so that clients don't get tiles twice? + SendTileSquare(tilex, tiley, 150); + +/* //We shouldn't need this section anymore -it can prevent otherwise acceptable teleportation under some circumstances. + + if (!SendTileSquare(tilex, tiley, 150)) + { + InitSpawn = true; + SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); + return false; + } + +*/ + Spawn(-1, -1); + + SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); + + TPlayer.position.X = (float)(tilex * 16 + 8 - TPlayer.width /2); + TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height); + //We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now. + + SendTileSquare(tilex, tiley, 10); + + return true; + } + + public void Spawn() + { + Spawn(TPlayer.SpawnX, TPlayer.SpawnY); + } + + public void Spawn(int tilex, int tiley) + { + using (var ms = new MemoryStream()) + { + var msg = new SpawnMsg + { + PlayerIndex = (byte) Index, + TileX = tilex, + TileY = tiley + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public void RemoveProjectile(int index, int owner) + { + using (var ms = new MemoryStream()) + { + var msg = new ProjectileRemoveMsg + { + Index = (short) index, + Owner = (byte) owner + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public virtual bool SendTileSquare(int x, int y, int size = 10) + { + try + { + int num = (size - 1)/2; + int m_x=0; + int m_y=0; + + if (x - num <0){ + m_x=0; + }else{ + m_x = x - num; + } + + if (y - num <0){ + m_y=0; + }else{ + m_y = y - num; + } + + if (m_x + size > Main.maxTilesX){ + m_x=Main.maxTilesX - size; + } + + if (m_y + size > Main.maxTilesY){ + m_y=Main.maxTilesY - size; + } + + SendData(PacketTypes.TileSendSquare, "", size, m_x, m_y); + return true; + } + catch (IndexOutOfRangeException) + { + + // This is expected if square exceeds array. + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + return false; + } + + public bool GiveItemCheck(int type, string name, int width, int height, int stack, int prefix = 0) + { + if ((TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) && + (TShock.Itembans.ItemIsBanned(name, this) || !TShock.Config.AllowAllowedGroupsToSpawnBannedItems)) + return false; + + GiveItem(type,name,width,height,stack,prefix); + return true; + } + + public virtual void GiveItem(int type, string name, int width, int height, int stack, int prefix = 0) + { + int itemid = Item.NewItem((int) X, (int) Y, width, height, type, stack, true, prefix); + + // This is for special pickaxe/hammers/swords etc + Main.item[itemid].SetDefaults(name); + // The set default overrides the wet and stack set by NewItem + Main.item[itemid].wet = Collision.WetCollision(Main.item[itemid].position, Main.item[itemid].width, + Main.item[itemid].height); + Main.item[itemid].stack = stack; + Main.item[itemid].owner = Index; + Main.item[itemid].prefix = (byte) prefix; + NetMessage.SendData((int) PacketTypes.ItemDrop, -1, -1, "", itemid, 0f, 0f, 0f); + NetMessage.SendData((int) PacketTypes.ItemOwner, -1, -1, "", itemid, 0f, 0f, 0f); + } + + public virtual void SendInfoMessage(string msg) + { + SendMessage(msg, Color.Yellow); + } + + public virtual void SendSuccessMessage(string msg) + { + SendMessage(msg, Color.Green); + } + + public virtual void SendWarningMessage(string msg) + { + SendMessage(msg, Color.OrangeRed); + } + + public virtual void SendErrorMessage(string msg) + { + SendMessage(msg, Color.Red); + } + + [Obsolete("Use SendErrorMessage, SendInfoMessage, or SendWarningMessage, or a custom color instead.")] + public virtual void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public virtual void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public virtual void SendMessage(string msg, byte red, byte green, byte blue) + { + SendData(PacketTypes.ChatText, msg, 255, red, green, blue); + } + + public virtual void SendMessageFromPlayer(string msg, byte red, byte green, byte blue, int ply) + { + SendDataFromPlayer(PacketTypes.ChatText, ply, msg, red, green, blue, 0); + } + + public virtual void DamagePlayer(int damage) + { + NetMessage.SendData((int) PacketTypes.PlayerDamage, -1, -1, "", Index, ((new Random()).Next(-1, 1)), damage, + (float) 0); + } + + public virtual void SetTeam(int team) + { + Main.player[Index].team = team; + SendData(PacketTypes.PlayerTeam, "", Index); + } + + public virtual void Disable(string reason = "") + { + LastThreat = DateTime.UtcNow; + SetBuff(33, 330, true); //Weak + SetBuff(32, 330, true); //Slow + SetBuff(23, 330, true); //Cursed + if (!string.IsNullOrEmpty(reason)) + Log.ConsoleInfo(string.Format("Player {0} has been disabled for {1}.", Name, reason)); + + var trace = new StackTrace(); + StackFrame frame = null; + frame = trace.GetFrame(1); + if (frame != null && frame.GetMethod().DeclaringType != null) + Log.Debug(frame.GetMethod().DeclaringType.Name + " called Disable()."); + } + + public virtual void Whoopie(object time) + { + var time2 = (int) time; + var launch = DateTime.UtcNow; + var startname = Name; + SendMessage("You are now being annoyed.", Color.Red); + while ((DateTime.UtcNow - launch).TotalSeconds < time2 && startname == Name) + { + SendData(PacketTypes.NpcSpecial, number: Index, number2: 2f); + Thread.Sleep(50); + } + } + + public virtual void SetBuff(int type, int time = 3600, bool bypass = false) + { + if ((DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000 && !bypass) + return; + + SendData(PacketTypes.PlayerAddBuff, number: Index, number2: type, number3: time); + } + + //Todo: Separate this into a few functions. SendTo, SendToAll, etc + public virtual void SendData(PacketTypes msgType, string text = "", int number = 0, float number2 = 0f, + float number3 = 0f, float number4 = 0f, int number5 = 0) + { + if (RealPlayer && !ConnectionAlive) + return; + + NetMessage.SendData((int) msgType, Index, -1, text, number, number2, number3, number4, number5); + } + + public virtual void SendDataFromPlayer(PacketTypes msgType, int ply, string text = "", float number2 = 0f, float number3 = 0f, float number4 = 0f, int number5 = 0) + { + if (RealPlayer && !ConnectionAlive) + return; + + NetMessage.SendData((int) msgType, Index, -1, text, ply, number2, number3, number4, number5); + } + + public virtual bool SendRawData(byte[] data) + { + if (!RealPlayer || !ConnectionAlive) + return false; + + return TShock.SendBytes(Netplay.serverSock[Index], data); + } + + /// + /// Adds a command callback to a specified command string. + /// + /// The string representing the command i.e "yes" == /yes + /// The method that will be executed on confirmation ie user accepts + public void AddResponse( string name, Action callback) + { + if( AwaitingResponse.ContainsKey(name)) + { + AwaitingResponse.Remove(name); + } + + AwaitingResponse.Add(name, callback); + } + } + + public class TSRestPlayer : TSServerPlayer + { + internal List CommandReturn = new List(); + + public TSRestPlayer() + { + Group = new SuperAdminGroup(); + AwaitingResponse = new Dictionary>(); + } + + public override void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public override void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public override void SendMessage(string msg, byte red, byte green, byte blue) + { + CommandReturn.Add(msg); + } + + public override void SendInfoMessage(string msg) + { + SendMessage(msg, Color.Yellow); + } + + public override void SendSuccessMessage(string msg) + { + SendMessage(msg, Color.Green); + } + + public override void SendWarningMessage(string msg) + { + SendMessage(msg, Color.OrangeRed); + } + + public override void SendErrorMessage(string msg) + { + SendMessage(msg, Color.Red); + } + + public List GetCommandOutput() + { + return CommandReturn; + } + } + + public class TSServerPlayer : TSPlayer + { + public TSServerPlayer() + : base("Server") + { + Group = new SuperAdminGroup(); + } + + public override void SendErrorMessage(string msg) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(msg); + Console.ResetColor(); + } + + public override void SendInfoMessage(string msg) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(msg); + Console.ResetColor(); + } + + public override void SendSuccessMessage(string msg) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(msg); + Console.ResetColor(); + } + + public override void SendWarningMessage(string msg) + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine(msg); + Console.ResetColor(); + } + + public override void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public override void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public override void SendMessage(string msg, byte red, byte green, byte blue) + { + Console.WriteLine(msg); + //RconHandler.Response += msg + "\n"; + } + + public void SetFullMoon(bool fullmoon) + { + Main.moonPhase = 0; + SetTime(false, 0); + } + + public void SetBloodMoon(bool bloodMoon) + { + Main.bloodMoon = bloodMoon; + SetTime(false, 0); + } + + public void SetTime(bool dayTime, double time) + { + Main.dayTime = dayTime; + Main.time = time; + NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); + NetMessage.syncPlayers(); + } + + public void SpawnNPC(int type, string name, int amount, int startTileX, int startTileY, int tileXRange = 100, + int tileYRange = 50) + { + for (int i = 0; i < amount; i++) + { + int spawnTileX; + int spawnTileY; + TShock.Utils.GetRandomClearTileWithInRange(startTileX, startTileY, tileXRange, tileYRange, out spawnTileX, + out spawnTileY); + int npcid = NPC.NewNPC(spawnTileX*16, spawnTileY*16, type, 0); + // This is for special slimes + Main.npc[npcid].SetDefaults(name); + } + } + + public void StrikeNPC(int npcid, int damage, float knockBack, int hitDirection) + { + Main.npc[npcid].StrikeNPC(damage, knockBack, hitDirection); + NetMessage.SendData((int) PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection); + } + + public void RevertTiles(Dictionary tiles) + { + // Update Main.Tile first so that when tile sqaure is sent it is correct + foreach (KeyValuePair entry in tiles) + { + Main.tile[(int) entry.Key.X, (int) entry.Key.Y].Data = entry.Value; + } + // Send all players updated tile sqaures + foreach (Vector2 coords in tiles.Keys) + { + All.SendTileSquare((int) coords.X, (int) coords.Y, 3); + } + } + } + + public class PlayerData + { + public NetItem[] inventory = new NetItem[NetItem.maxNetInventory]; + public int maxHealth = 100; + //public int maxMana = 100; + public bool exists; + + public PlayerData(TSPlayer player) + { + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + this.inventory[i] = new NetItem(); + } + this.inventory[0].netID = -15; + this.inventory[0].stack = 1; + if (player.TPlayer.inventory[0] != null && player.TPlayer.inventory[0].netID == -15) + this.inventory[0].prefix = player.TPlayer.inventory[0].prefix; + this.inventory[1].netID = -13; + this.inventory[1].stack = 1; + if (player.TPlayer.inventory[1] != null && player.TPlayer.inventory[1].netID == -13) + this.inventory[1].prefix = player.TPlayer.inventory[1].prefix; + this.inventory[2].netID = -16; + this.inventory[2].stack = 1; + if (player.TPlayer.inventory[2] != null && player.TPlayer.inventory[2].netID == -16) + this.inventory[2].prefix = player.TPlayer.inventory[2].prefix; + } + + public void StoreSlot(int slot, int netID, int prefix, int stack) + { + if(slot > (this.inventory.Length - 1)) //if the slot is out of range then dont save + { + return; + } + + this.inventory[slot].netID = netID; + if (this.inventory[slot].netID != 0) + { + this.inventory[slot].stack = stack; + this.inventory[slot].prefix = prefix; + } + else + { + this.inventory[slot].stack = 0; + this.inventory[slot].prefix = 0; + } + } + + public void CopyInventory(TSPlayer player) + { + this.maxHealth = player.TPlayer.statLifeMax; + Item[] inventory = player.TPlayer.inventory; + Item[] armor = player.TPlayer.armor; + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + if (i < 49) + { + if (player.TPlayer.inventory[i] != null) + { + this.inventory[i].netID = inventory[i].netID; + } + else + { + this.inventory[i].netID = 0; + } + + if (this.inventory[i].netID != 0) + { + this.inventory[i].stack = inventory[i].stack; + this.inventory[i].prefix = inventory[i].prefix; + } + else + { + this.inventory[i].stack = 0; + this.inventory[i].prefix = 0; + } + } + else + { + if (player.TPlayer.armor[i - 48] != null) + { + this.inventory[i].netID = armor[i - 48].netID; + } + else + { + this.inventory[i].netID = 0; + } + + if (this.inventory[i].netID != 0) + { + this.inventory[i].stack = armor[i - 48].stack; + this.inventory[i].prefix = armor[i - 48].prefix; + } + else + { + this.inventory[i].stack = 0; + this.inventory[i].prefix = 0; + } + } + } + } + } + + public class NetItem + { + public static int maxNetInventory = 59; + public int netID; + public int stack; + public int prefix; + + public static string ToString(NetItem[] inventory) + { + string inventoryString = ""; + for (int i = 0; i < maxNetInventory; i++) + { + if (i != 0) + inventoryString += "~"; + inventoryString += inventory[i].netID; + if (inventory[i].netID != 0) + { + inventoryString += "," + inventory[i].stack; + inventoryString += "," + inventory[i].prefix; + } + else + { + inventoryString += ",0,0"; + } + } + return inventoryString; + } + + public static NetItem[] Parse(string data) + { + NetItem[] inventory = new NetItem[maxNetInventory]; + int i; + for (i = 0; i < maxNetInventory; i++) + { + inventory[i] = new NetItem(); + } + string[] items = data.Split('~'); + i = 0; + foreach (string item in items) + { + string[] idata = item.Split(','); + inventory[i].netID = int.Parse(idata[0]); + inventory[i].stack = int.Parse(idata[1]); + inventory[i].prefix = int.Parse(idata[2]); + i++; + } + return inventory; + } + } +} diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 5a370f1c..1a88e796 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -223,6 +223,7 @@ namespace TShockAPI ProjectileHooks.SetDefaults += OnProjectileSetDefaults; WorldHooks.StartHardMode += OnStartHardMode; WorldHooks.SaveWorld += SaveManager.Instance.OnSaveWorld; + WorldHooks.ChristmasCheck += OnXmasCheck; NetHooks.NameCollision += NetHooks_NameCollision; GetDataHandlers.InitGetDataHandler(); @@ -323,6 +324,7 @@ namespace TShockAPI ProjectileHooks.SetDefaults -= OnProjectileSetDefaults; WorldHooks.StartHardMode -= OnStartHardMode; WorldHooks.SaveWorld -= SaveManager.Instance.OnSaveWorld; + WorldHooks.ChristmasCheck -= OnXmasCheck; NetHooks.NameCollision -= NetHooks_NameCollision; if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) @@ -367,6 +369,17 @@ namespace TShockAPI return; } + void OnXmasCheck(ChristmasCheckEventArgs args) + { + if (args.Handled) + return; + + if(Config.ForceXmas) + { + args.Xmas = true; + args.Handled = true; + } + } /// /// Handles exceptions that we didn't catch or that Red fucked up /// @@ -812,12 +825,9 @@ namespace TShockAPI if (tsplr != null && tsplr.ReceivedInfo) { - if (!tsplr.SilentKickInProgress || tsplr.State > 1) + if (!tsplr.SilentKickInProgress && tsplr.State >= 3) { - if (tsplr.State >= 2) - { - Utils.Broadcast(tsplr.Name + " left", Color.Yellow); - } + Utils.Broadcast(tsplr.Name + " left", Color.Yellow); } Log.Info(string.Format("{0} disconnected.", tsplr.Name)); diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 468a4a30..4bb1f9d1 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -86,6 +86,7 @@ + diff --git a/TerrariaServerBins/TerrariaServer.exe b/TerrariaServerBins/TerrariaServer.exe index 49910fb8..8bd9ba49 100644 Binary files a/TerrariaServerBins/TerrariaServer.exe and b/TerrariaServerBins/TerrariaServer.exe differ