/*
TShock, a server mod for Terraria
Copyright (C) 2011-2013 Nyx Studios (fka. 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 timer to keep track of whether or not the player has recently thrown an explosive
//
public int RecentFuse = 0;
///
/// 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; }
public string[] AwaitingNameParameters { 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; }
///
/// Whether the player performed a valid login attempt (i.e. entered valid user name and password) but is still blocked
/// from logging in because of SSI.
///
public bool LoginFailsBySsi { get; set; }
///
/// Whether the player is logged in or not.
///
public bool IsLoggedIn;
///
/// Whether the player has sent their whole inventory to the server while connecting.
///
public bool HasSentInventory { get; set; }
///
/// 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 < 50; i++) //51 is trash can, 52-55 is coins, 56-59 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 = Group.DefaultGroup;
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 = Group.DefaultGroup;
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,
MoonType = (byte)Main.moonType,
TreeX0 = Main.treeX[0],
TreeX1 = Main.treeX[1],
TreeX2 = Main.treeX[2],
TreeStyle0 = (byte)Main.treeStyle[0],
TreeStyle1 = (byte)Main.treeStyle[1],
TreeStyle2 = (byte)Main.treeStyle[2],
TreeStyle3 = (byte)Main.treeStyle[3],
CaveBackX0 = Main.caveBackX[0],
CaveBackX1 = Main.caveBackX[1],
CaveBackX2 = Main.caveBackX[2],
CaveBackStyle0 = (byte)Main.caveBackStyle[0],
CaveBackStyle1 = (byte)Main.caveBackStyle[1],
CaveBackStyle2 = (byte)Main.caveBackStyle[2],
CaveBackStyle3 = (byte)Main.caveBackStyle[3],
SetBG0 = (byte)WorldGen.treeBG,
SetBG1 = (byte)WorldGen.corruptBG,
SetBG2 = (byte)WorldGen.jungleBG,
SetBG3 = (byte)WorldGen.snowBG,
SetBG4 = (byte)WorldGen.hallowBG,
SetBG5 = (byte)WorldGen.crimsonBG,
SetBG6 = (byte)WorldGen.desertBG,
SetBG7 = (byte)WorldGen.oceanBG,
IceBackStyle = (byte)Main.iceBackStyle,
JungleBackStyle = (byte)Main.jungleBackStyle,
HellBackStyle = (byte)Main.hellBackStyle,
WindSpeed = Main.windSpeed,
NumberOfClouds = (byte)Main.numClouds,
BossFlags = (WorldGen.shadowOrbSmashed ? BossFlags.OrbSmashed : BossFlags.None) |
(NPC.downedBoss1 ? BossFlags.DownedBoss1 : BossFlags.None) |
(NPC.downedBoss2 ? BossFlags.DownedBoss2 : BossFlags.None) |
(NPC.downedBoss3 ? BossFlags.DownedBoss3 : BossFlags.None) |
(Main.hardMode ? BossFlags.HardMode : BossFlags.None) |
(NPC.downedClown ? BossFlags.DownedClown : BossFlags.None),
BossFlags2 = (NPC.downedMechBoss1 ? BossFlags2.DownedMechBoss1 : BossFlags2.None) |
(NPC.downedMechBoss2 ? BossFlags2.DownedMechBoss2 : BossFlags2.None) |
(NPC.downedMechBoss3 ? BossFlags2.DownedMechBoss3 : BossFlags2.None) |
(NPC.downedMechBossAny ? BossFlags2.DownedMechBossAny : BossFlags2.None) |
(Main.cloudBGActive == 1f ? BossFlags2.CloudBg : BossFlags2.None) |
(WorldGen.crimson ? BossFlags2.Crimson : BossFlags2.None),
Rain = Main.maxRaining,
WorldName = TShock.Config.UseServerName ? TShock.Config.ServerName : Main.worldName
};
msg.PackFull(ms);
SendRawData(ms.ToArray());
}
}
public bool Teleport(float x, float y, byte style = 1)
{
if (x > Main.rightWorld - 500)
{
x = Main.rightWorld - 500;
}
if (x < 500)
{
x = 500;
}
if (y > Main.bottomWorld - 500)
{
y = Main.bottomWorld - 500;
}
if (y < 500)
{
y = 500;
}
TPlayer.Teleport(new Vector2(x, y), style);
NetMessage.SendData(65, -1, -1, "", 0, TPlayer.whoAmi, x, y, style);
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, true);
// 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;
Main.item[itemid].noGrabDelay = 1;
Main.item[itemid].velocity = Main.player[this.Index].velocity;
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 void SendInfoMessage(string format, params object[] args)
{
SendInfoMessage(string.Format(format, args));
}
public virtual void SendSuccessMessage(string msg)
{
SendMessage(msg, Color.Green);
}
public void SendSuccessMessage(string format, params object[] args)
{
SendSuccessMessage(string.Format(format, args));
}
public virtual void SendWarningMessage(string msg)
{
SendMessage(msg, Color.OrangeRed);
}
public void SendWarningMessage(string format, params object[] args)
{
SendWarningMessage(string.Format(format, args));
}
public virtual void SendErrorMessage(string msg)
{
SendMessage(msg, Color.Red);
}
public void SendErrorMessage(string format, params object[] args)
{
SendErrorMessage(string.Format(format, args));
}
[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