diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs
index 4fc949b75..f7aafeb61 100644
--- a/TShockAPI/Commands.cs
+++ b/TShockAPI/Commands.cs
@@ -817,8 +817,6 @@ private static void AttemptLogin(CommandArgs args)
(usingUUID && account.UUID == args.Player.UUID && !TShock.Config.DisableUUIDLogin &&
!String.IsNullOrWhiteSpace(args.Player.UUID)))
{
- args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
-
var group = TShock.Groups.GetGroupByName(account.Group);
args.Player.Group = group;
@@ -826,16 +824,6 @@ private static void AttemptLogin(CommandArgs args)
args.Player.Account = account;
args.Player.IsLoggedIn = true;
args.Player.IsDisabledForSSC = false;
-
- if (Main.ServerSideCharacter)
- {
- if (args.Player.HasPermission(Permissions.bypassssc))
- {
- args.Player.PlayerData.CopyCharacter(args.Player);
- TShock.CharacterDB.InsertPlayerData(args.Player);
- }
- args.Player.PlayerData.RestoreCharacter(args.Player);
- }
args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
@@ -1612,14 +1600,8 @@ private static void SaveSSC(CommandArgs args)
{
if (Main.ServerSideCharacter)
{
+ ServerSideCharacters.ServerSideCoordinator.SaveAllPlayersData();
args.Player.SendSuccessMessage("SSC has been saved.");
- foreach (TSPlayer player in TShock.Players)
- {
- if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
- {
- TShock.CharacterDB.InsertPlayerData(player, true);
- }
- }
}
}
@@ -1650,23 +1632,13 @@ private static void OverrideSSC(CommandArgs args)
}
TSPlayer matchedPlayer = matchedPlayers[0];
- if (matchedPlayer.IsLoggedIn)
- {
- args.Player.SendErrorMessage("Player \"{0}\" is already logged in.", matchedPlayer.Name);
- return;
- }
- if (!matchedPlayer.LoginFailsBySsi)
- {
- args.Player.SendErrorMessage("Player \"{0}\" has to perform a /login attempt first.", matchedPlayer.Name);
- return;
- }
if (matchedPlayer.IsDisabledPendingTrashRemoval)
{
args.Player.SendErrorMessage("Player \"{0}\" has to reconnect first.", matchedPlayer.Name);
return;
}
- TShock.CharacterDB.InsertPlayerData(matchedPlayer);
+ ServerSideCharacters.ServerSideCoordinator.ApplyServerSidePlayerData(matchedPlayer, temporaryData: matchedPlayer.SscDataWhenJoined);
args.Player.SendSuccessMessage("SSC of player \"{0}\" has been overriden.", matchedPlayer.Name);
}
@@ -1710,16 +1682,9 @@ private static void UploadJoinData(CommandArgs args)
if (targetPlayer.IsLoggedIn)
{
- if (TShock.CharacterDB.InsertSpecificPlayerData(targetPlayer, targetPlayer.DataWhenJoined))
- {
- targetPlayer.DataWhenJoined.RestoreCharacter(targetPlayer);
- targetPlayer.SendSuccessMessage("Your local character data has been uploaded to the server.");
- args.Player.SendSuccessMessage("The player's character data was successfully uploaded.");
- }
- else
- {
- args.Player.SendErrorMessage("Failed to upload your character data, are you logged in to an account?");
- }
+ TShock.CharacterDB.UpdatePlayerData(targetPlayer.SscDataWhenJoined, targetPlayer.Account.ID);
+ targetPlayer.SendSuccessMessage("Your local character data has been uploaded to the server.");
+ args.Player.SendSuccessMessage("The player's character data was successfully uploaded.");
}
else
{
@@ -1864,13 +1829,7 @@ private static void Off(CommandArgs args)
if (Main.ServerSideCharacter)
{
- foreach (TSPlayer player in TShock.Players)
- {
- if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
- {
- player.SaveServerCharacter();
- }
- }
+ ServerSideCharacters.ServerSideCoordinator.SaveAllPlayersData();
}
string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!");
@@ -4133,10 +4092,7 @@ private static void ServerPassword(CommandArgs args)
private static void Save(CommandArgs args)
{
SaveManager.Instance.SaveWorld(false);
- foreach (TSPlayer tsply in TShock.Players.Where(tsply => tsply != null))
- {
- tsply.SaveServerCharacter();
- }
+ ServerSideCharacters.ServerSideCoordinator.SaveAllPlayersData();
}
private static void Settle(CommandArgs args)
diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs
index cd2ff4269..f095da881 100644
--- a/TShockAPI/DB/CharacterManager.cs
+++ b/TShockAPI/DB/CharacterManager.cs
@@ -22,7 +22,8 @@ You should have received a copy of the GNU General Public License
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
-using Terraria;
+using TShockAPI.Net;
+using TShockAPI.ServerSideCharacters;
namespace TShockAPI.DB
{
@@ -35,12 +36,12 @@ public CharacterManager(IDbConnection db)
database = db;
var table = new SqlTable("tsCharacter",
- new SqlColumn("Account", MySqlDbType.Int32) {Primary = true},
+ new SqlColumn("Account", MySqlDbType.Int32) { Primary = true },
new SqlColumn("Health", MySqlDbType.Int32),
- new SqlColumn("MaxHealth", MySqlDbType.Int32),
+ new SqlColumn("MaxHealth", MySqlDbType.Int32),
new SqlColumn("Mana", MySqlDbType.Int32),
- new SqlColumn("MaxMana", MySqlDbType.Int32),
- new SqlColumn("Inventory", MySqlDbType.Text),
+ new SqlColumn("MaxMana", MySqlDbType.Int32),
+ new SqlColumn("Inventory", MySqlDbType.Text),
new SqlColumn("extraSlot", MySqlDbType.Int32),
new SqlColumn("spawnX", MySqlDbType.Int32),
new SqlColumn("spawnY", MySqlDbType.Int32),
@@ -55,259 +56,162 @@ public CharacterManager(IDbConnection db)
new SqlColumn("hideVisuals", MySqlDbType.Int32),
new SqlColumn("skinColor", MySqlDbType.Int32),
new SqlColumn("eyeColor", MySqlDbType.Int32),
- new SqlColumn("questsCompleted", MySqlDbType.Int32)
+ new SqlColumn("questsCompleted", MySqlDbType.Int32),
+ new SqlColumn("golfScoreAccumulated", MySqlDbType.Int32)
);
var creator = new SqlTableCreator(db,
- db.GetSqlType() == SqlType.Sqlite
- ? (IQueryBuilder) new SqliteQueryCreator()
- : new MysqlQueryCreator());
+ db.GetSqlType() == SqlType.Sqlite
+ ? (IQueryBuilder)new SqliteQueryCreator()
+ : new MysqlQueryCreator());
creator.EnsureTableStructure(table);
}
- public PlayerData GetPlayerData(TSPlayer player, int acctid)
+ ///
+ /// Determines if the given account ID has saved SSC data or not
+ ///
+ ///
+ ///
+ public bool HasPlayerData(int accountId)
{
- PlayerData playerData = new PlayerData(player);
-
- try
- {
- using (var reader = database.QueryReader("SELECT * FROM tsCharacter WHERE Account=@0", acctid))
- {
- if (reader.Read())
- {
- playerData.exists = true;
- playerData.health = reader.Get("Health");
- playerData.maxHealth = reader.Get("MaxHealth");
- playerData.mana = reader.Get("Mana");
- playerData.maxMana = reader.Get("MaxMana");
- List inventory = reader.Get("Inventory").Split('~').Select(NetItem.Parse).ToList();
- if (inventory.Count < NetItem.MaxInventory)
- {
- //TODO: unhardcode this - stop using magic numbers and use NetItem numbers
- //Set new armour slots empty
- inventory.InsertRange(67, new NetItem[2]);
- //Set new vanity slots empty
- inventory.InsertRange(77, new NetItem[2]);
- //Set new dye slots empty
- inventory.InsertRange(87, new NetItem[2]);
- //Set the rest of the new slots empty
- inventory.AddRange(new NetItem[NetItem.MaxInventory - inventory.Count]);
- }
- playerData.inventory = inventory.ToArray();
- playerData.extraSlot = reader.Get("extraSlot");
- playerData.spawnX = reader.Get("spawnX");
- playerData.spawnY = reader.Get("spawnY");
- playerData.skinVariant = reader.Get("skinVariant");
- playerData.hair = reader.Get("hair");
- playerData.hairDye = (byte)reader.Get("hairDye");
- playerData.hairColor = TShock.Utils.DecodeColor(reader.Get("hairColor"));
- playerData.pantsColor = TShock.Utils.DecodeColor(reader.Get("pantsColor"));
- playerData.shirtColor = TShock.Utils.DecodeColor(reader.Get("shirtColor"));
- playerData.underShirtColor = TShock.Utils.DecodeColor(reader.Get("underShirtColor"));
- playerData.shoeColor = TShock.Utils.DecodeColor(reader.Get("shoeColor"));
- playerData.hideVisuals = TShock.Utils.DecodeBoolArray(reader.Get("hideVisuals"));
- playerData.skinColor = TShock.Utils.DecodeColor(reader.Get("skinColor"));
- playerData.eyeColor = TShock.Utils.DecodeColor(reader.Get("eyeColor"));
- playerData.questsCompleted = reader.Get("questsCompleted");
- return playerData;
- }
- }
- }
- catch (Exception ex)
+ using (var reader = database.QueryReader("SELECT 1 FROM tsCharacter WHERE Account=@0", accountId))
{
- TShock.Log.Error(ex.ToString());
+ return reader.Read();
}
-
- return playerData;
}
- public bool SeedInitialData(UserAccount account)
+ ///
+ /// Creates the given server side player data in the database for the given account ID
+ ///
+ ///
+ ///
+ public void InsertPlayerData(ServerSidePlayerData data, int accountId)
{
- var inventory = new StringBuilder();
+ database.Query(
+ "INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY," +
+ "skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, skinColor, eyeColor, hideVisuals, questsCompleted, golfScoreAccumulated) " +
+ "VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21)",
+ accountId, data.Stats.Health, data.Stats.MaxHealth, data.Stats.Mana, data.Stats.MaxMana, String.Join("~", data.Inventory.Items),
+ data.Stats.HasExtraSlot ? 1 : 0, data.Spawn.TileX, data.Spawn.TileY, data.Vanity.SkinVariant, data.Vanity.Hair, data.Vanity.HairDye,
+ TShock.Utils.EncodeColor(data.Vanity.HairColor), TShock.Utils.EncodeColor(data.Vanity.PantsColor), TShock.Utils.EncodeColor(data.Vanity.ShirtColor),
+ TShock.Utils.EncodeColor(data.Vanity.UnderShirtColor), TShock.Utils.EncodeColor(data.Vanity.ShoeColor), TShock.Utils.EncodeColor(data.Vanity.SkinColor),
+ TShock.Utils.EncodeColor(data.Vanity.EyeColor), TShock.Utils.EncodeBoolArray(data.Vanity.HideVisuals), data.Stats.QuestsCompleted, data.Stats.GolfScoreAccumulated
+ );
+ }
- var items = new List(TShock.ServerSideCharacterConfig.StartingInventory);
- if (items.Count < NetItem.MaxInventory)
- items.AddRange(new NetItem[NetItem.MaxInventory - items.Count]);
+ ///
+ /// Reads the server side player data from the database for the given account ID
+ ///
+ ///
+ ///
+ public ServerSidePlayerData ReadPlayerData(int accountId)
+ {
+ ServerSidePlayerData data = new ServerSidePlayerData();
- string initialItems = String.Join("~", items.Take(NetItem.MaxInventory));
- try
- {
- database.Query("INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, spawnX, spawnY, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8);",
- account.ID,
- TShock.ServerSideCharacterConfig.StartingHealth,
- TShock.ServerSideCharacterConfig.StartingHealth,
- TShock.ServerSideCharacterConfig.StartingMana,
- TShock.ServerSideCharacterConfig.StartingMana,
- initialItems,
- -1,
- -1,
- 0);
- return true;
- }
- catch (Exception ex)
+ using (var reader = database.QueryReader("SELECT * FROM tsCharacter WHERE Account=@0", accountId))
{
- TShock.Log.Error(ex.ToString());
+ if (reader.Read())
+ {
+ data.Stats = ReadStats(reader);
+ data.Inventory = ReadInventory(reader);
+ data.Spawn = ReadSpawn(reader);
+ data.Vanity = ReadVanity(reader);
+ }
}
- return false;
+ return data;
}
- ///
- /// Inserts player data to the tsCharacter database table
- ///
- /// player to take data from
- /// true if inserted successfully
- public bool InsertPlayerData(TSPlayer player, bool fromCommand = false)
+ internal ServerSideStats ReadStats(QueryResult reader)
{
- PlayerData playerData = player.PlayerData;
+ ServerSideStats stats = new ServerSideStats
+ {
+ Health = reader.Get("Health"),
+ Mana = reader.Get("Mana"),
+ MaxHealth = reader.Get("MaxHealth"),
+ MaxMana = reader.Get("MaxMana"),
+ HasExtraSlot = reader.Get("extraSlot") == 1,
+ QuestsCompleted = reader.Get("questsCompleted"),
+ GolfScoreAccumulated = reader.Get("golfScoreAccumulated")
+ };
+
+ return stats;
+ }
- if (!player.IsLoggedIn)
- return false;
+ internal ServerSideInventory ReadInventory(QueryResult reader)
+ {
+ IEnumerable items = reader.Get("Inventory").Split('~').Select(NetItem.Parse);
+ return ServerSideInventory.CreateFromNetItems(items);
+ }
- if (player.HasPermission(Permissions.bypassssc) && !fromCommand)
+ internal ServerSideSpawn ReadSpawn(QueryResult reader)
+ {
+ ServerSideSpawn spawn = new ServerSideSpawn
{
- TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.Account.Name); // Debug code
- return false;
- }
+ TileX = reader.Get("spawnX"),
+ TileY = reader.Get("spawnY")
+ };
- if (!GetPlayerData(player, player.Account.ID).exists)
- {
- try
- {
- database.Query(
- "INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20);",
- player.Account.ID, playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), playerData.extraSlot, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.skinVariant, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor),TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor),TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished);
- return true;
- }
- catch (Exception ex)
- {
- TShock.Log.Error(ex.ToString());
- }
- }
- else
+ return spawn;
+ }
+
+ internal ServerSideVanity ReadVanity(QueryResult reader)
+ {
+ ServerSideVanity vanity = new ServerSideVanity
{
- try
- {
- database.Query(
- "UPDATE tsCharacter SET Health = @0, MaxHealth = @1, Mana = @2, MaxMana = @3, Inventory = @4, spawnX = @6, spawnY = @7, hair = @8, hairDye = @9, hairColor = @10, pantsColor = @11, shirtColor = @12, underShirtColor = @13, shoeColor = @14, hideVisuals = @15, skinColor = @16, eyeColor = @17, questsCompleted = @18, skinVariant = @19, extraSlot = @20 WHERE Account = @5;",
- playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), player.Account.ID, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor), TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor), TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.skinVariant, player.TPlayer.extraAccessory ? 1 : 0);
- return true;
- }
- catch (Exception ex)
- {
- TShock.Log.Error(ex.ToString());
- }
- }
- return false;
+ SkinVariant = reader.Get("skinVariant"),
+ Hair = reader.Get("hair"),
+ HairDye = (byte)reader.Get("hairDye"),
+ HairColor = TShock.Utils.DecodeColor(reader.Get("hairColor")),
+ PantsColor = TShock.Utils.DecodeColor(reader.Get("pantsColor")),
+ ShirtColor = TShock.Utils.DecodeColor(reader.Get("shirtColor")),
+ UnderShirtColor = TShock.Utils.DecodeColor(reader.Get("underShirtColor")),
+ ShoeColor = TShock.Utils.DecodeColor(reader.Get("shoeColor")),
+ SkinColor = TShock.Utils.DecodeColor(reader.Get("skinColor")),
+ EyeColor = TShock.Utils.DecodeColor(reader.Get("eyeColor")),
+ HideVisuals = TShock.Utils.DecodeBoolArray(reader.Get("hideVisuals"))
+ };
+
+ return vanity;
}
///
- /// Removes a player's data from the tsCharacter database table
+ /// Updates the given server side player data in the database for the given account ID
///
- /// User ID of the player
- /// true if removed successfully
- public bool RemovePlayer(int userid)
+ ///
+ ///
+ public void UpdatePlayerData(ServerSidePlayerData data, int accountId)
{
- try
- {
- database.Query("DELETE FROM tsCharacter WHERE Account = @0;", userid);
- return true;
- }
- catch (Exception ex)
- {
- TShock.Log.Error(ex.ToString());
- }
-
- return false;
+ StringBuilder sb = new StringBuilder("UPDATE tsCharacter SET ")
+ .Append("Health = @0, MaxHealth = @1, Mana = @2, MaxMana = @3, extraSlot = @4, questsCompleted = @5, golfScoreAccumulated = @6,")
+ .Append("spawnX = @7, spawnY = @8,")
+ .Append("skinVariant = @9, hair = @10, hairDye = @11," +
+ "hairColor = @12, pantsColor = @13, shirtColor = @14," +
+ "underShirtColor = @15, shoeColor = @16, skinColor = @17," +
+ "eyeColor = @18, hideVisuals = @19,")
+ .Append("inventory = @20 ")
+ .Append("WHERE Account = @21");
+
+ database.Query(
+ sb.ToString(),
+ data.Stats.Health, data.Stats.MaxHealth, data.Stats.Mana, data.Stats.MaxMana, data.Stats.HasExtraSlot ? 1 : 0, data.Stats.QuestsCompleted, data.Stats.GolfScoreAccumulated,
+ data.Spawn.TileX, data.Spawn.TileY,
+ data.Vanity.SkinVariant, data.Vanity.Hair, data.Vanity.HairDye,
+ TShock.Utils.EncodeColor(data.Vanity.HairColor), TShock.Utils.EncodeColor(data.Vanity.PantsColor), TShock.Utils.EncodeColor(data.Vanity.ShirtColor),
+ TShock.Utils.EncodeColor(data.Vanity.UnderShirtColor), TShock.Utils.EncodeColor(data.Vanity.ShoeColor), TShock.Utils.EncodeColor(data.Vanity.SkinColor),
+ TShock.Utils.EncodeColor(data.Vanity.EyeColor), TShock.Utils.EncodeBoolArray(data.Vanity.HideVisuals),
+ String.Join("~", data.Inventory.Items),
+ accountId
+ );
}
///
- /// Inserts a specific PlayerData into the SSC table for a player.
+ /// Deletes the server side player data associated with the given account ID
///
- /// The player to store the data for.
- /// The player data to store.
- /// If the command succeeds.
- public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data)
+ /// Account ID of the player
+ /// true if removed successfully
+ public void DeletePlayerData(int accountId)
{
- PlayerData playerData = data;
-
- if (!player.IsLoggedIn)
- return false;
-
- if (player.HasPermission(Permissions.bypassssc))
- {
- TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.Account.Name); // Debug code
- return true;
- }
-
- if (!GetPlayerData(player, player.Account.ID).exists)
- {
- try
- {
- database.Query(
- "INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20);",
- player.Account.ID,
- playerData.health,
- playerData.maxHealth,
- playerData.mana,
- playerData.maxMana,
- String.Join("~", playerData.inventory),
- playerData.extraSlot,
- playerData.spawnX,
- playerData.spawnX,
- playerData.skinVariant,
- playerData.hair,
- playerData.hairDye,
- TShock.Utils.EncodeColor(playerData.hairColor),
- TShock.Utils.EncodeColor(playerData.pantsColor),
- TShock.Utils.EncodeColor(playerData.shirtColor),
- TShock.Utils.EncodeColor(playerData.underShirtColor),
- TShock.Utils.EncodeColor(playerData.shoeColor),
- TShock.Utils.EncodeBoolArray(playerData.hideVisuals),
- TShock.Utils.EncodeColor(playerData.skinColor),
- TShock.Utils.EncodeColor(playerData.eyeColor),
- playerData.questsCompleted);
- return true;
- }
- catch (Exception ex)
- {
- TShock.Log.Error(ex.ToString());
- }
- }
- else
- {
- try
- {
- database.Query(
- "UPDATE tsCharacter SET Health = @0, MaxHealth = @1, Mana = @2, MaxMana = @3, Inventory = @4, spawnX = @6, spawnY = @7, hair = @8, hairDye = @9, hairColor = @10, pantsColor = @11, shirtColor = @12, underShirtColor = @13, shoeColor = @14, hideVisuals = @15, skinColor = @16, eyeColor = @17, questsCompleted = @18, skinVariant = @19, extraSlot = @20 WHERE Account = @5;",
- playerData.health,
- playerData.maxHealth,
- playerData.mana,
- playerData.maxMana,
- String.Join("~", playerData.inventory),
- player.Account.ID,
- playerData.spawnX,
- playerData.spawnX,
- playerData.skinVariant,
- playerData.hair,
- playerData.hairDye,
- TShock.Utils.EncodeColor(playerData.hairColor),
- TShock.Utils.EncodeColor(playerData.pantsColor),
- TShock.Utils.EncodeColor(playerData.shirtColor),
- TShock.Utils.EncodeColor(playerData.underShirtColor),
- TShock.Utils.EncodeColor(playerData.shoeColor),
- TShock.Utils.EncodeBoolArray(playerData.hideVisuals),
- TShock.Utils.EncodeColor(playerData.skinColor),
- TShock.Utils.EncodeColor(playerData.eyeColor),
- playerData.questsCompleted,
- playerData.extraSlot ?? 0);
- return true;
- }
- catch (Exception ex)
- {
- TShock.Log.Error(ex.ToString());
- }
- }
- return false;
+ database.Query("DELETE FROM tsCharacter WHERE Account = @0;", accountId);
}
}
}
diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs
index ba6a92726..ead1dac3a 100644
--- a/TShockAPI/FileTools.cs
+++ b/TShockAPI/FileTools.cs
@@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License
using System;
using System.Collections.Generic;
using System.IO;
+using Terraria.ID;
+using TShockAPI.Net;
using TShockAPI.ServerSideCharacters;
namespace TShockAPI
@@ -122,9 +124,9 @@ public static void SetupConfig()
StartingInventory =
new List
{
- new NetItem(-15, 1, 0),
- new NetItem(-13, 1, 0),
- new NetItem(-16, 1, 0)
+ new NetItem(ItemID.CopperShortsword, 1, 0),
+ new NetItem(ItemID.CopperPickaxe, 1, 0),
+ new NetItem(ItemID.CopperAxe, 1, 0)
}
};
}
@@ -164,4 +166,4 @@ public static bool OnWhitelist(string ip)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index cf13e396c..b7713c2e5 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -276,6 +276,25 @@ private static bool OnPlayerSlot(TSPlayer player, MemoryStream data, byte _plr,
return args.Handled;
}
+ ///
+ /// Invoked when a player connects to the server
+ ///
+ public static HandlerList PlayerConnect = new HandlerList();
+ private static bool OnPlayerConnect(TSPlayer player, MemoryStream data)
+ {
+ if (PlayerConnect == null)
+ return false;
+
+ var args = new GetDataHandledEventArgs
+ {
+ Data = data,
+ Player = player
+ };
+
+ PlayerConnect.Invoke(null, args);
+ return args.Handled;
+ }
+
/// The arguments to a GetSection packet.
public class GetSectionEventArgs : GetDataHandledEventArgs
{
@@ -2175,15 +2194,13 @@ private static bool HandlePlayerSlot(GetDataHandlerArgs args)
short type = args.Data.ReadInt16();
// Players send a slot update packet for each inventory slot right after they've joined.
- bool bypassTrashCanCheck = false;
- if (plr == args.Player.Index && !args.Player.HasSentInventory && slot == NetItem.MaxInventory)
+ if (plr == args.Player.Index && !args.Player.HasSentInventory && slot == NetItem.TotalSlots)
{
args.Player.HasSentInventory = true;
- bypassTrashCanCheck = true;
}
if (OnPlayerSlot(args.Player, args.Data, plr, slot, stack, prefix, type) || plr != args.Player.Index || slot < 0 ||
- slot > NetItem.MaxInventory)
+ slot > NetItem.TotalSlots)
return true;
if (args.Player.IgnoreSSCPackets)
{
@@ -2197,17 +2214,6 @@ private static bool HandlePlayerSlot(GetDataHandlerArgs args)
item.netDefaults(type);
item.Prefix(prefix);
- if (args.Player.IsLoggedIn)
- {
- args.Player.PlayerData.StoreSlot(slot, type, prefix, stack);
- }
- else if (Main.ServerSideCharacter && TShock.Config.DisableLoginBeforeJoin && !bypassTrashCanCheck &&
- 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.
- args.Player.IsDisabledPendingTrashRemoval = true;
- }
-
if (slot == 58) //this is the hand
{
item.stack = stack;
@@ -2219,11 +2225,12 @@ private static bool HandlePlayerSlot(GetDataHandlerArgs args)
private static bool HandleConnecting(GetDataHandlerArgs args)
{
- var account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name);//
- args.Player.DataWhenJoined = new PlayerData(args.Player);
- args.Player.DataWhenJoined.CopyCharacter(args.Player);
- args.Player.PlayerData = new PlayerData(args.Player);
- args.Player.PlayerData.CopyCharacter(args.Player);
+ if (OnPlayerConnect(args.Player, args.Data))
+ {
+ return true;
+ }
+
+ var account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name);
if (account != null && !TShock.Config.DisableUUIDLogin)
{
@@ -2233,8 +2240,6 @@ private static bool HandleConnecting(GetDataHandlerArgs args)
args.Player.State = 2;
NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index);
- args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
-
var group = TShock.Groups.GetGroupByName(account.Group);
args.Player.Group = group;
@@ -2242,16 +2247,6 @@ private static bool HandleConnecting(GetDataHandlerArgs args)
args.Player.Account = account;
args.Player.IsLoggedIn = true;
args.Player.IsDisabledForSSC = false;
-
- if (Main.ServerSideCharacter)
- {
- if (args.Player.HasPermission(Permissions.bypassssc))
- {
- args.Player.PlayerData.CopyCharacter(args.Player);
- TShock.CharacterDB.InsertPlayerData(args.Player);
- }
- args.Player.PlayerData.RestoreCharacter(args.Player);
- }
args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
@@ -2406,13 +2401,6 @@ private static bool HandlePlayerHp(GetDataHandlerArgs args)
return true;
}
- if (args.Player.IsLoggedIn)
- {
- args.Player.TPlayer.statLife = cur;
- args.Player.TPlayer.statLifeMax = max;
- args.Player.PlayerData.maxHealth = max;
- }
-
if (args.Player.GodMode && (cur < max))
{
args.Player.Heal(args.TPlayer.statLifeMax2);
@@ -2768,7 +2756,6 @@ private static bool HandlePassword(GetDataHandlerArgs args)
if (account.VerifyPassword(password))
{
args.Player.RequiresPassword = false;
- args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
if (args.Player.State == 1)
args.Player.State = 2;
@@ -2781,16 +2768,6 @@ private static bool HandlePassword(GetDataHandlerArgs args)
args.Player.Account = account;
args.Player.IsLoggedIn = true;
args.Player.IsDisabledForSSC = false;
-
- if (Main.ServerSideCharacter)
- {
- if (args.Player.HasPermission(Permissions.bypassssc))
- {
- args.Player.PlayerData.CopyCharacter(args.Player);
- TShock.CharacterDB.InsertPlayerData(args.Player);
- }
- args.Player.PlayerData.RestoreCharacter(args.Player);
- }
args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
@@ -2851,13 +2828,6 @@ private static bool HandlePlayerMana(GetDataHandlerArgs args)
args.Player.Disable("Maximum MP beyond limit", DisableFlags.WriteToLogAndConsole);
return true;
}
-
- if (args.Player.IsLoggedIn)
- {
- args.Player.TPlayer.statMana = cur;
- args.Player.TPlayer.statManaMax = max;
- args.Player.PlayerData.maxMana = max;
- }
return false;
}
@@ -3630,16 +3600,6 @@ private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args)
}
}
- if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn)
- {
- if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID))
- {
- TShock.Log.ConsoleDebug("GetDataHandlers / HandlePlayerKillMeV2 ssc delete {0} {1}", args.Player.Name, args.TPlayer.difficulty);
- args.Player.SendErrorMessage("You have fallen in hardcore mode, and your items have been lost forever.");
- TShock.CharacterDB.SeedInitialData(args.Player.Account);
- }
- }
-
return false;
}
diff --git a/TShockAPI/Net/NetItem.cs b/TShockAPI/Net/NetItem.cs
new file mode 100644
index 000000000..430ebebde
--- /dev/null
+++ b/TShockAPI/Net/NetItem.cs
@@ -0,0 +1,363 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+using System;
+using Newtonsoft.Json;
+using Terraria;
+
+namespace TShockAPI.Net
+{
+ ///
+ /// Helper class to group inventory indexes.
+ /// Contains the starting index of an inventory group, and the number of items in the group
+ ///
+ public class InventoryGrouping
+ {
+ ///
+ /// The starting index of the inventory group
+ ///
+ public int Start;
+ ///
+ /// The number of items in the group
+ ///
+ public int Count;
+ ///
+ /// The ending index of the inventory group
+ ///
+ public int End => Start + Count - 1;
+
+ ///
+ /// Creates a new grouping with the given start and count
+ ///
+ ///
+ ///
+ public InventoryGrouping(int start, int count)
+ {
+ Start = start;
+ Count = count;
+ }
+
+ ///
+ /// Creates a new grouping using the given previous grouping to determine the start of this grouping
+ ///
+ ///
+ ///
+ public InventoryGrouping(InventoryGrouping previous, int count)
+ {
+ Start = previous.Start + previous.Count;
+ Count = count;
+ }
+ }
+
+ ///
+ /// Represents an item.
+ ///
+ [JsonObject(MemberSerialization.OptIn)]
+ public struct NetItem
+ {
+ ///
+ /// The maximum number of slots in the main inventory. This does not include coins, ammo, or the held item
+ ///
+ public static readonly int InventorySlots = 50;
+ ///
+ /// The maximum number of coin slots
+ ///
+ public static readonly int CoinSlots = 4;
+ ///
+ /// The maximum number of ammo slots
+ ///
+ public static readonly int AmmoSlots = 4;
+ ///
+ /// The maximum number of held item slots
+ ///
+ public static readonly int HeldItemSlots = 1;
+
+ ///
+ /// The maximum number of armor slots
+ ///
+ public static readonly int ArmorSlots = 3;
+ ///
+ /// The maximum number of accessory slots
+ ///
+ public static readonly int AccessorySlots = 7;
+
+ ///
+ /// The maximum number of armor vanity slots
+ ///
+ public static readonly int ArmorVanitySlots = ArmorSlots;
+ ///
+ /// The maximum number of accessory vanity slots
+ ///
+ public static readonly int AccessoryVanitySlots = AccessorySlots;
+
+ ///
+ /// The maximum number of armor dye slots
+ ///
+ public static readonly int ArmorDyeSlots = ArmorVanitySlots;
+ ///
+ /// The maximum number of accessory dye slots
+ ///
+ public static readonly int AccessoryDyeSlots = AccessoryVanitySlots;
+
+ ///
+ /// The maximum number of misc equipment slots. This is the mount/hook/etc equipment
+ ///
+ public static readonly int MiscEquipSlots = 5;
+ ///
+ /// The maximum number of misc dye slots
+ ///
+ public static readonly int MiscDyeSlots = MiscEquipSlots;
+
+ ///
+ /// The maximum number of slots in a piggy bank
+ ///
+ public static readonly int PiggySlots = 40;
+
+ ///
+ /// The maximum number of slots in the trash can
+ ///
+ public static readonly int TrashSlots = 1;
+
+ ///
+ /// The maximum number of slots in a safe
+ ///
+ public static readonly int SafeSlots = PiggySlots;
+
+ ///
+ /// The maximum number of slots in a defender's forge
+ ///
+ public static readonly int ForgeSlots = SafeSlots;
+
+ ///
+ /// The maximum numbert of slots in a void bank
+ ///
+ public static readonly int VoidSlots = ForgeSlots;
+
+ ///
+ /// The total number of slots available across the entire inventory
+ ///
+ public static readonly int TotalSlots =
+ InventorySlots + CoinSlots + AmmoSlots + HeldItemSlots +
+ ArmorSlots + AccessorySlots +
+ ArmorVanitySlots + AccessoryVanitySlots +
+ ArmorDyeSlots + AccessoryDyeSlots +
+ MiscEquipSlots + MiscDyeSlots +
+ PiggySlots +
+ TrashSlots +
+ SafeSlots +
+ ForgeSlots +
+ VoidSlots;
+
+ ///
+ /// Groups the main inventory slots
+ ///
+ public static readonly InventoryGrouping InventoryGroup = new InventoryGrouping(0, InventorySlots);
+ ///
+ /// Groups the coin slots
+ ///
+ public static readonly InventoryGrouping CoinGroup = new InventoryGrouping(InventoryGroup, CoinSlots);
+ ///
+ /// Groups the ammo slots
+ ///
+ public static readonly InventoryGrouping AmmoGroup = new InventoryGrouping(CoinGroup, AmmoSlots);
+ ///
+ /// Groups the held item slot
+ ///
+ public static readonly InventoryGrouping HeldItemGroup = new InventoryGrouping(AmmoGroup, HeldItemSlots);
+
+ ///
+ /// Groups all of the main inventory slots (main inventory + coin slots + ammo slots + held item slots)
+ ///
+ public static readonly InventoryGrouping FullInventoryGroup =
+ new InventoryGrouping(0, InventorySlots + CoinSlots + AmmoSlots + HeldItemSlots);
+
+ ///
+ /// Groups the armor slots
+ ///
+ public static readonly InventoryGrouping ArmorGroup = new InventoryGrouping(HeldItemGroup, ArmorSlots);
+ ///
+ /// Groups the accessory slots
+ ///
+ public static readonly InventoryGrouping AccessoryGroup = new InventoryGrouping(ArmorGroup, AccessorySlots);
+
+ ///
+ /// Groups equippable non-vanity armour and accessories
+ ///
+ public static readonly InventoryGrouping FullEquipmentGroup
+ = new InventoryGrouping(HeldItemGroup, ArmorSlots + AccessorySlots);
+
+ ///
+ /// Groups the armor vanity slots
+ ///
+ public static readonly InventoryGrouping ArmorVanityGroup = new InventoryGrouping(AccessoryGroup, ArmorVanitySlots);
+ ///
+ /// Groups the accessory vanity slots
+ ///
+ public static readonly InventoryGrouping AccessoryVanityGroup = new InventoryGrouping(ArmorVanityGroup, AccessoryVanitySlots);
+
+ ///
+ /// Groups equippable vanity armour and accessories
+ ///
+ public static readonly InventoryGrouping FullVanityEquipmentGroup
+ = new InventoryGrouping(AccessoryGroup, ArmorVanitySlots + AccessoryVanitySlots);
+
+ ///
+ /// Groups all equippable armor and accessories as well as vanity armor and accessories
+ ///
+ public static readonly InventoryGrouping FullEquipmentAndVanityGroup
+ = new InventoryGrouping(HeldItemGroup, ArmorSlots + AccessorySlots + ArmorVanitySlots + AccessoryVanitySlots);
+
+ ///
+ /// Groups the armor dye slots
+ ///
+ public static readonly InventoryGrouping ArmorDyeGroup = new InventoryGrouping(AccessoryVanityGroup, ArmorDyeSlots);
+ ///
+ /// Groups the accessory dye slots
+ ///
+ public static readonly InventoryGrouping AccessoryDyeGroup = new InventoryGrouping(ArmorDyeGroup, AccessoryDyeSlots);
+
+ ///
+ /// Groups all equippable dyes for armor and accessories
+ ///
+ public static readonly InventoryGrouping FullArmorAndVanityDyeGroup =
+ new InventoryGrouping(AccessoryVanityGroup, ArmorDyeSlots + AccessoryDyeSlots);
+
+ ///
+ /// Groups the misc equipment slots
+ ///
+ public static readonly InventoryGrouping MiscEquipGroup = new InventoryGrouping(AccessoryDyeGroup, MiscEquipSlots);
+ ///
+ /// Groups the misc dye slots
+ ///
+ public static readonly InventoryGrouping MiscDyeGroup = new InventoryGrouping(MiscEquipGroup, MiscDyeSlots);
+
+ ///
+ /// Groups the piggy bank slots
+ ///
+ public static readonly InventoryGrouping PiggyGroup = new InventoryGrouping(MiscDyeGroup, PiggySlots);
+
+ ///
+ /// Groups the safe item slots
+ ///
+ public static readonly InventoryGrouping SafeGroup = new InventoryGrouping(PiggyGroup, SafeSlots);
+
+ ///
+ /// Groups the trash item slot
+ ///
+ public static readonly InventoryGrouping TrashGroup = new InventoryGrouping(SafeGroup, TrashSlots);
+
+ ///
+ /// Groups the defender's forge item slots
+ ///
+ public static readonly InventoryGrouping ForgeGroup = new InventoryGrouping(TrashGroup, ForgeSlots);
+
+ ///
+ /// Groups the void bank item slots
+ ///
+ public static readonly InventoryGrouping VoidGroup = new InventoryGrouping(ForgeGroup, VoidSlots);
+
+
+ [JsonProperty("netID")]
+ private int _netId;
+ [JsonProperty("prefix")]
+ private byte _prefixId;
+ [JsonProperty("stack")]
+ private int _stack;
+
+ ///
+ /// Gets the net ID.
+ ///
+ public int NetId
+ {
+ get { return _netId; }
+ }
+
+ ///
+ /// Gets the prefix.
+ ///
+ public byte PrefixId
+ {
+ get { return _prefixId; }
+ }
+
+ ///
+ /// Gets the stack.
+ ///
+ public int Stack
+ {
+ get { return _stack; }
+ }
+
+ ///
+ /// Creates a new .
+ ///
+ /// The net ID.
+ /// The stack.
+ /// The prefix ID.
+ public NetItem(int netId, int stack, byte prefixId)
+ {
+ _netId = netId;
+ _stack = stack;
+ _prefixId = prefixId;
+ }
+
+ ///
+ /// Converts the to a string.
+ ///
+ ///
+ public override string ToString()
+ {
+ return String.Format("{0},{1},{2}", _netId, _stack, _prefixId);
+ }
+
+ ///
+ /// Converts a string into a .
+ ///
+ /// The string.
+ ///
+ ///
+ ///
+ public static NetItem Parse(string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException("str");
+
+ string[] comp = str.Split(',');
+ if (comp.Length != 3)
+ throw new FormatException("String does not contain three sections.");
+
+ int netId = Int32.Parse(comp[0]);
+ int stack = Int32.Parse(comp[1]);
+ byte prefixId = Byte.Parse(comp[2]);
+
+ return new NetItem(netId, stack, prefixId);
+ }
+
+ ///
+ /// Converts an into a .
+ ///
+ /// The .
+ ///
+ public static explicit operator NetItem(Item item)
+ {
+ return item == null
+ ? new NetItem()
+ : new NetItem(item.netID, item.stack, item.prefix);
+ }
+ }
+}
diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs
deleted file mode 100644
index 13ac0989c..000000000
--- a/TShockAPI/NetItem.cs
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-TShock, a server mod for Terraria
-Copyright (C) 2011-2019 Pryaxis & TShock Contributors
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
- using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Newtonsoft.Json;
-using Terraria;
-
-namespace TShockAPI
-{
- ///
- /// Represents an item.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- public struct NetItem
- {
- ///
- /// 40 - The number of slots in a piggy bank
- ///
- public static readonly int PiggySlots = 40;
-
- ///
- /// 40 - The number of slots in a safe
- ///
- public static readonly int SafeSlots = PiggySlots;
-
- ///
- /// 40 - The number of slots in a forge
- ///
- public static readonly int ForgeSlots = SafeSlots;
-
- ///
- /// 40 - The number of slots in a void vault
- ///
- public static readonly int VoidSlots = ForgeSlots;
-
- ///
- /// 59 - The size of the player's inventory (inventory, coins, ammo, held item)
- ///
- public static readonly int InventorySlots = 59;
-
- ///
- /// 20 - The number of armor slots.
- ///
- public static readonly int ArmorSlots = 20;
-
- ///
- /// 5 - The number of other equippable items
- ///
- public static readonly int MiscEquipSlots = 5;
-
- ///
- /// 10 - The number of dye slots.
- ///
- public static readonly int DyeSlots = 10;
-
- ///
- /// 5 - The number of other dye slots (for )
- ///
- public static readonly int MiscDyeSlots = MiscEquipSlots;
-
- ///
- /// 1 - The number of trash can slots.
- ///
- public static readonly int TrashSlots = 1;
-
- ///
- /// 180 - The inventory size (inventory, held item, armour, dies, coins, ammo, piggy, safe, and trash)
- ///
- public static readonly int MaxInventory = InventorySlots + ArmorSlots + DyeSlots + MiscEquipSlots + MiscDyeSlots + PiggySlots + SafeSlots + ForgeSlots + VoidSlots + 1;
-
- public static readonly Tuple InventoryIndex = new Tuple(0, InventorySlots);
- public static readonly Tuple ArmorIndex = new Tuple(InventoryIndex.Item2, InventoryIndex.Item2 + ArmorSlots);
- public static readonly Tuple DyeIndex = new Tuple(ArmorIndex.Item2, ArmorIndex.Item2 + DyeSlots);
- public static readonly Tuple MiscEquipIndex = new Tuple(DyeIndex.Item2, DyeIndex.Item2 + MiscEquipSlots);
- public static readonly Tuple MiscDyeIndex = new Tuple(MiscEquipIndex.Item2, MiscEquipIndex.Item2 + MiscDyeSlots);
- public static readonly Tuple PiggyIndex = new Tuple(MiscDyeIndex.Item2, MiscDyeIndex.Item2 + PiggySlots);
- public static readonly Tuple SafeIndex = new Tuple(PiggyIndex.Item2, PiggyIndex.Item2 + SafeSlots);
- public static readonly Tuple TrashIndex = new Tuple(SafeIndex.Item2, SafeIndex.Item2 + TrashSlots);
- public static readonly Tuple ForgeIndex = new Tuple(TrashIndex.Item2, TrashIndex.Item2 + ForgeSlots);
- public static readonly Tuple VoidIndex = new Tuple(ForgeIndex.Item2, ForgeIndex.Item2 + VoidSlots);
-
- [JsonProperty("netID")]
- private int _netId;
- [JsonProperty("prefix")]
- private byte _prefixId;
- [JsonProperty("stack")]
- private int _stack;
-
- ///
- /// Gets the net ID.
- ///
- public int NetId
- {
- get { return _netId; }
- }
-
- ///
- /// Gets the prefix.
- ///
- public byte PrefixId
- {
- get { return _prefixId; }
- }
-
- ///
- /// Gets the stack.
- ///
- public int Stack
- {
- get { return _stack; }
- }
-
- ///
- /// Creates a new .
- ///
- /// The net ID.
- /// The stack.
- /// The prefix ID.
- public NetItem(int netId, int stack, byte prefixId)
- {
- _netId = netId;
- _stack = stack;
- _prefixId = prefixId;
- }
-
- ///
- /// Converts the to a string.
- ///
- ///
- public override string ToString()
- {
- return String.Format("{0},{1},{2}", _netId, _stack, _prefixId);
- }
-
- ///
- /// Converts a string into a .
- ///
- /// The string.
- ///
- ///
- ///
- public static NetItem Parse(string str)
- {
- if (str == null)
- throw new ArgumentNullException("str");
-
- string[] comp = str.Split(',');
- if (comp.Length != 3)
- throw new FormatException("String does not contain three sections.");
-
- int netId = Int32.Parse(comp[0]);
- int stack = Int32.Parse(comp[1]);
- byte prefixId = Byte.Parse(comp[2]);
-
- return new NetItem(netId, stack, prefixId);
- }
-
- ///
- /// Converts an into a .
- ///
- /// The .
- ///
- public static explicit operator NetItem(Item item)
- {
- return item == null
- ? new NetItem()
- : new NetItem(item.netID, item.stack, item.prefix);
- }
- }
-}
diff --git a/TShockAPI/PlayerData.cs b/TShockAPI/PlayerData.cs
deleted file mode 100644
index d6540e34a..000000000
--- a/TShockAPI/PlayerData.cs
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
-TShock, a server mod for Terraria
-Copyright (C) 2011-2019 Pryaxis & TShock Contributors
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-using Microsoft.Xna.Framework;
-using Terraria;
-using TShockAPI;
-using Terraria.Localization;
-using Terraria.GameContent.NetModules;
-using Terraria.Net;
-using Terraria.ID;
-
-namespace TShockAPI
-{
- public class PlayerData
- {
- public NetItem[] inventory = new NetItem[NetItem.MaxInventory];
- public int health = TShock.ServerSideCharacterConfig.StartingHealth;
- public int maxHealth = TShock.ServerSideCharacterConfig.StartingHealth;
- public int mana = TShock.ServerSideCharacterConfig.StartingMana;
- public int maxMana = TShock.ServerSideCharacterConfig.StartingMana;
- public bool exists;
- public int spawnX = -1;
- public int spawnY = -1;
- public int? extraSlot;
- public int? skinVariant;
- public int? hair;
- public byte hairDye;
- public Color? hairColor;
- public Color? pantsColor;
- public Color? shirtColor;
- public Color? underShirtColor;
- public Color? shoeColor;
- public Color? skinColor;
- public Color? eyeColor;
- public bool[] hideVisuals;
- public int questsCompleted;
-
- public PlayerData(TSPlayer player)
- {
- for (int i = 0; i < NetItem.MaxInventory; i++)
- {
- this.inventory[i] = new NetItem();
- }
-
- for (int i = 0; i < TShock.ServerSideCharacterConfig.StartingInventory.Count; i++)
- {
- var item = TShock.ServerSideCharacterConfig.StartingInventory[i];
- StoreSlot(i, item.NetId, item.PrefixId, item.Stack);
- }
- }
-
- ///
- /// Stores an item at the specific storage slot
- ///
- ///
- ///
- ///
- ///
- public void StoreSlot(int slot, int netID, byte prefix, int stack)
- {
- if (slot > (this.inventory.Length - 1)) //if the slot is out of range then dont save
- {
- return;
- }
-
- this.inventory[slot] = new NetItem(netID, stack, prefix);
- }
-
- ///
- /// Copies a characters data to this object
- ///
- ///
- public void CopyCharacter(TSPlayer player)
- {
- this.health = player.TPlayer.statLife > 0 ? player.TPlayer.statLife : 1;
- this.maxHealth = player.TPlayer.statLifeMax;
- this.mana = player.TPlayer.statMana;
- this.maxMana = player.TPlayer.statManaMax;
- if (player.sX > 0 && player.sY > 0)
- {
- this.spawnX = player.sX;
- this.spawnY = player.sY;
- }
- else
- {
- this.spawnX = player.TPlayer.SpawnX;
- this.spawnY = player.TPlayer.SpawnY;
- }
- extraSlot = player.TPlayer.extraAccessory ? 1 : 0;
- this.skinVariant = player.TPlayer.skinVariant;
- this.hair = player.TPlayer.hair;
- this.hairDye = player.TPlayer.hairDye;
- this.hairColor = player.TPlayer.hairColor;
- this.pantsColor = player.TPlayer.pantsColor;
- this.shirtColor = player.TPlayer.shirtColor;
- this.underShirtColor = player.TPlayer.underShirtColor;
- this.shoeColor = player.TPlayer.shoeColor;
- this.hideVisuals = player.TPlayer.hideVisibleAccessory;
- this.skinColor = player.TPlayer.skinColor;
- this.eyeColor = player.TPlayer.eyeColor;
- this.questsCompleted = player.TPlayer.anglerQuestsFinished;
-
- Item[] inventory = player.TPlayer.inventory;
- Item[] armor = player.TPlayer.armor;
- Item[] dye = player.TPlayer.dye;
- Item[] miscEqups = player.TPlayer.miscEquips;
- Item[] miscDyes = player.TPlayer.miscDyes;
- Item[] piggy = player.TPlayer.bank.item;
- Item[] safe = player.TPlayer.bank2.item;
- Item[] forge = player.TPlayer.bank3.item;
- Item[] voidVault = player.TPlayer.bank4.item;
- Item trash = player.TPlayer.trashItem;
-
- for (int i = 0; i < NetItem.MaxInventory; i++)
- {
- if (i < NetItem.InventoryIndex.Item2)
- {
- //0-58
- this.inventory[i] = (NetItem)inventory[i];
- }
- else if (i < NetItem.ArmorIndex.Item2)
- {
- //59-78
- var index = i - NetItem.ArmorIndex.Item1;
- this.inventory[i] = (NetItem)armor[index];
- }
- else if (i < NetItem.DyeIndex.Item2)
- {
- //79-88
- var index = i - NetItem.DyeIndex.Item1;
- this.inventory[i] = (NetItem)dye[index];
- }
- else if (i < NetItem.MiscEquipIndex.Item2)
- {
- //89-93
- var index = i - NetItem.MiscEquipIndex.Item1;
- this.inventory[i] = (NetItem)miscEqups[index];
- }
- else if (i < NetItem.MiscDyeIndex.Item2)
- {
- //93-98
- var index = i - NetItem.MiscDyeIndex.Item1;
- this.inventory[i] = (NetItem)miscDyes[index];
- }
- else if (i < NetItem.PiggyIndex.Item2)
- {
- //98-138
- var index = i - NetItem.PiggyIndex.Item1;
- this.inventory[i] = (NetItem)piggy[index];
- }
- else if (i < NetItem.SafeIndex.Item2)
- {
- //138-178
- var index = i - NetItem.SafeIndex.Item1;
- this.inventory[i] = (NetItem)safe[index];
- }
- else if (i < NetItem.TrashIndex.Item2)
- {
- //179-219
- this.inventory[i] = (NetItem)trash;
- }
- else if (i < NetItem.ForgeIndex.Item2)
- {
- //220
- var index = i - NetItem.ForgeIndex.Item1;
- this.inventory[i] = (NetItem)forge[index];
- }
- else
- {
- //220
- var index = i - NetItem.VoidIndex.Item1;
- this.inventory[i] = (NetItem)voidVault[index];
- }
- }
- }
-
- ///
- /// Restores a player's character to the state stored in the database
- ///
- ///
- public void RestoreCharacter(TSPlayer player)
- {
- // Start ignoring SSC-related packets! This is critical so that we don't send or receive dirty data!
- player.IgnoreSSCPackets = true;
-
- player.TPlayer.statLife = this.health;
- player.TPlayer.statLifeMax = this.maxHealth;
- player.TPlayer.statMana = this.maxMana;
- player.TPlayer.statManaMax = this.maxMana;
- player.TPlayer.SpawnX = this.spawnX;
- player.TPlayer.SpawnY = this.spawnY;
- player.sX = this.spawnX;
- player.sY = this.spawnY;
- player.TPlayer.hairDye = this.hairDye;
- player.TPlayer.anglerQuestsFinished = this.questsCompleted;
-
- if (extraSlot != null)
- player.TPlayer.extraAccessory = extraSlot.Value == 1 ? true : false;
- if (this.skinVariant != null)
- player.TPlayer.skinVariant = this.skinVariant.Value;
- if (this.hair != null)
- player.TPlayer.hair = this.hair.Value;
- if (this.hairColor != null)
- player.TPlayer.hairColor = this.hairColor.Value;
- if (this.pantsColor != null)
- player.TPlayer.pantsColor = this.pantsColor.Value;
- if (this.shirtColor != null)
- player.TPlayer.shirtColor = this.shirtColor.Value;
- if (this.underShirtColor != null)
- player.TPlayer.underShirtColor = this.underShirtColor.Value;
- if (this.shoeColor != null)
- player.TPlayer.shoeColor = this.shoeColor.Value;
- if (this.skinColor != null)
- player.TPlayer.skinColor = this.skinColor.Value;
- if (this.eyeColor != null)
- player.TPlayer.eyeColor = this.eyeColor.Value;
-
- if (this.hideVisuals != null)
- player.TPlayer.hideVisibleAccessory = this.hideVisuals;
- else
- player.TPlayer.hideVisibleAccessory = new bool[player.TPlayer.hideVisibleAccessory.Length];
-
- for (int i = 0; i < NetItem.MaxInventory; i++)
- {
- if (i < NetItem.InventoryIndex.Item2)
- {
- //0-58
- player.TPlayer.inventory[i].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.inventory[i].netID != 0)
- {
- player.TPlayer.inventory[i].stack = this.inventory[i].Stack;
- player.TPlayer.inventory[i].prefix = this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.ArmorIndex.Item2)
- {
- //59-78
- var index = i - NetItem.ArmorIndex.Item1;
- player.TPlayer.armor[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.armor[index].netID != 0)
- {
- player.TPlayer.armor[index].stack = this.inventory[i].Stack;
- player.TPlayer.armor[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.DyeIndex.Item2)
- {
- //79-88
- var index = i - NetItem.DyeIndex.Item1;
- player.TPlayer.dye[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.dye[index].netID != 0)
- {
- player.TPlayer.dye[index].stack = this.inventory[i].Stack;
- player.TPlayer.dye[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.MiscEquipIndex.Item2)
- {
- //89-93
- var index = i - NetItem.MiscEquipIndex.Item1;
- player.TPlayer.miscEquips[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.miscEquips[index].netID != 0)
- {
- player.TPlayer.miscEquips[index].stack = this.inventory[i].Stack;
- player.TPlayer.miscEquips[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.MiscDyeIndex.Item2)
- {
- //93-98
- var index = i - NetItem.MiscDyeIndex.Item1;
- player.TPlayer.miscDyes[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.miscDyes[index].netID != 0)
- {
- player.TPlayer.miscDyes[index].stack = this.inventory[i].Stack;
- player.TPlayer.miscDyes[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.PiggyIndex.Item2)
- {
- //98-138
- var index = i - NetItem.PiggyIndex.Item1;
- player.TPlayer.bank.item[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.bank.item[index].netID != 0)
- {
- player.TPlayer.bank.item[index].stack = this.inventory[i].Stack;
- player.TPlayer.bank.item[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.SafeIndex.Item2)
- {
- //138-178
- var index = i - NetItem.SafeIndex.Item1;
- player.TPlayer.bank2.item[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.bank2.item[index].netID != 0)
- {
- player.TPlayer.bank2.item[index].stack = this.inventory[i].Stack;
- player.TPlayer.bank2.item[index].prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.TrashIndex.Item2)
- {
- //179-219
- var index = i - NetItem.TrashIndex.Item1;
- player.TPlayer.trashItem.netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.trashItem.netID != 0)
- {
- player.TPlayer.trashItem.stack = this.inventory[i].Stack;
- player.TPlayer.trashItem.prefix = (byte)this.inventory[i].PrefixId;
- }
- }
- else if (i < NetItem.ForgeIndex.Item2)
- {
- //220
- var index = i - NetItem.ForgeIndex.Item1;
- player.TPlayer.bank3.item[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.bank3.item[index].netID != 0)
- {
- player.TPlayer.bank3.item[index].stack = this.inventory[i].Stack;
- player.TPlayer.bank3.item[index].Prefix((byte)this.inventory[i].PrefixId);
- }
- }
- else
- {
- //260
- var index = i - NetItem.VoidIndex.Item1;
- player.TPlayer.bank4.item[index].netDefaults(this.inventory[i].NetId);
-
- if (player.TPlayer.bank4.item[index].netID != 0)
- {
- player.TPlayer.bank4.item[index].stack = this.inventory[i].Stack;
- player.TPlayer.bank4.item[index].Prefix((byte)this.inventory[i].PrefixId);
- }
- }
- }
-
- float slot = 0f;
- for (int k = 0; k < NetItem.InventorySlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].inventory[k].Name), player.Index, slot, (float)Main.player[player.Index].inventory[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.ArmorSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].armor[k].Name), player.Index, slot, (float)Main.player[player.Index].armor[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.DyeSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].dye[k].Name), player.Index, slot, (float)Main.player[player.Index].dye[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.MiscEquipSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].miscEquips[k].Name), player.Index, slot, (float)Main.player[player.Index].miscEquips[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.MiscDyeSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].miscDyes[k].Name), player.Index, slot, (float)Main.player[player.Index].miscDyes[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.PiggySlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].bank.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank.item[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.SafeSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].bank2.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank2.item[k].prefix);
- slot++;
- }
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].trashItem.Name), player.Index, slot++, (float)Main.player[player.Index].trashItem.prefix);
- for (int k = 0; k < NetItem.ForgeSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].bank3.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank3.item[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.VoidSlots; k++)
- {
- NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].bank4.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank4.item[k].prefix);
- slot++;
- }
-
-
- NetMessage.SendData(4, -1, -1, NetworkText.FromLiteral(player.Name), player.Index, 0f, 0f, 0f, 0);
- NetMessage.SendData(42, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
- NetMessage.SendData(16, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
-
- slot = 0f;
- for (int k = 0; k < NetItem.InventorySlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].inventory[k].Name), player.Index, slot, (float)Main.player[player.Index].inventory[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.ArmorSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].armor[k].Name), player.Index, slot, (float)Main.player[player.Index].armor[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.DyeSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].dye[k].Name), player.Index, slot, (float)Main.player[player.Index].dye[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.MiscEquipSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].miscEquips[k].Name), player.Index, slot, (float)Main.player[player.Index].miscEquips[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.MiscDyeSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].miscDyes[k].Name), player.Index, slot, (float)Main.player[player.Index].miscDyes[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.PiggySlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].bank.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank.item[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.SafeSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].bank2.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank2.item[k].prefix);
- slot++;
- }
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].trashItem.Name), player.Index, slot++, (float)Main.player[player.Index].trashItem.prefix);
- for (int k = 0; k < NetItem.ForgeSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].bank3.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank3.item[k].prefix);
- slot++;
- }
- for (int k = 0; k < NetItem.VoidSlots; k++)
- {
- NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].bank4.item[k].Name), player.Index, slot, (float)Main.player[player.Index].bank4.item[k].prefix);
- slot++;
- }
-
-
-
- NetMessage.SendData(4, player.Index, -1, NetworkText.FromLiteral(player.Name), player.Index, 0f, 0f, 0f, 0);
- NetMessage.SendData(42, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
- NetMessage.SendData(16, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
-
- for (int k = 0; k < 22; k++)
- {
- player.TPlayer.buffType[k] = 0;
- }
-
- /*
- * The following packets are sent twice because the server will not send a packet to a client
- * if they have not spawned yet if the remoteclient is -1
- * This is for when players login via uuid or serverpassword instead of via
- * the login command.
- */
- NetMessage.SendData(50, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
- NetMessage.SendData(50, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
-
- NetMessage.SendData(76, player.Index, -1, NetworkText.Empty, player.Index);
- NetMessage.SendData(76, -1, -1, NetworkText.Empty, player.Index);
-
- NetMessage.SendData(39, player.Index, -1, NetworkText.Empty, 400);
-
- if (Main.GameModeInfo.IsJourneyMode)
- {
- var sacrificedItems = TShock.ResearchDatastore.GetSacrificedItems();
- for(int i = 0; i < ItemID.Count; i++)
- {
- var amount = 0;
- if (sacrificedItems.ContainsKey(i))
- {
- amount = sacrificedItems[i];
- }
-
- var response = NetCreativeUnlocksModule.SerializeItemSacrifice(i, amount);
- NetManager.Instance.SendToClient(response, player.Index);
- }
- }
- }
- }
-}
diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs
index 95d9a39a5..f96fb2583 100644
--- a/TShockAPI/Rest/RestManager.cs
+++ b/TShockAPI/Rest/RestManager.cs
@@ -29,6 +29,7 @@ You should have received a copy of the GNU General Public License
using Terraria;
using TShockAPI.DB;
using Newtonsoft.Json;
+using TShockAPI.Net;
namespace TShockAPI
{
diff --git a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs
index 31d5caac7..566deaa58 100644
--- a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs
+++ b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs
@@ -23,11 +23,15 @@ You should have received a copy of the GNU General Public License
using System.Linq;
using System.Text;
using Newtonsoft.Json;
+using TShockAPI.Net;
namespace TShockAPI.ServerSideCharacters
{
public class ServerSideConfig
{
+ // Disable the 'Missing XML comment for publicly visible type or member' for these fields as they have
+ // description attributes
+#pragma warning disable CS1591
[Description("Enable server side characters, This stops the client from saving character data! EXPERIMENTAL!!!!!")]
public bool Enabled = false;
@@ -45,6 +49,7 @@ public class ServerSideConfig
[Description("The starting default inventory for new SSC.")]
public List StartingInventory = new List();
+#pragma warning restore CS1591
public static ServerSideConfig Read(string path)
{
diff --git a/TShockAPI/ServerSideCharacters/ServerSideCoordinator.cs b/TShockAPI/ServerSideCharacters/ServerSideCoordinator.cs
new file mode 100644
index 000000000..1a6a3333f
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSideCoordinator.cs
@@ -0,0 +1,495 @@
+using System;
+using Terraria;
+using Terraria.GameContent.NetModules;
+using Terraria.ID;
+using Terraria.Localization;
+using Terraria.Net;
+using TShockAPI.DB;
+using TShockAPI.Hooks;
+using TShockAPI.Net;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Provides and executes CRUD operations on server side player data
+ ///
+ public class ServerSideCoordinator
+ {
+ private DateTime _lastSave;
+
+ ///
+ /// Creates a new instance of the server side coordinator, hooking into {put hook names here}
+ ///
+ public ServerSideCoordinator()
+ {
+ AccountHooks.AccountDelete += OnAccountDelete;
+ PlayerHooks.PlayerPostLogin += OnPlayerPostLogin;
+ PlayerHooks.PlayerLogout += OnPlayerLogout;
+
+ GetDataHandlers.PlayerConnect += OnPlayerConnect;
+ GetDataHandlers.KillMe += OnPlayerDeath;
+ GetDataHandlers.PlayerSlot += OnPlayerSlotChange;
+ GetDataHandlers.PlayerHP += OnPlayerHpChange;
+ GetDataHandlers.PlayerMana += OnPlayerManaChange;
+
+ _lastSave = DateTime.UtcNow;
+ }
+
+ private void OnAccountDelete(AccountDeleteEventArgs e)
+ {
+ DeleteAccountData(e.Account);
+ }
+
+ private void OnPlayerPostLogin(PlayerPostLoginEventArgs e)
+ {
+ if (!Main.ServerSideCharacter)
+ {
+ return;
+ }
+
+ if (e.Player.HasPermission(Permissions.bypassssc))
+ {
+ // Do bypass stuff?
+ return;
+ }
+
+ ServerSidePlayerData data;
+
+ if (TShock.CharacterDB.HasPlayerData(e.Player.Account.ID))
+ {
+ // Retrieve existing data
+ data = ReadPlayerData(e.Player);
+ }
+ else
+ {
+ // Create new data and write it to disk
+ data = CreateNewPlayerData(e.Player, writeToDisk: true);
+ }
+
+ e.Player.SscData = data;
+ ApplyServerSidePlayerData(e.Player);
+ }
+
+ private void OnPlayerLogout(PlayerLogoutEventArgs e)
+ {
+ if (!Main.ServerSideCharacter)
+ {
+ // Ignore non-SSC logouts
+ return;
+ }
+
+ // If the player is logging out and isn't trying to cheat their SSC data, update their data
+ if (!e.Player.IsDisabledPendingTrashRemoval && (!e.Player.Dead || e.Player.TPlayer.difficulty != 2))
+ {
+ SavePlayerData(e.Player);
+ }
+ }
+
+ private void OnPlayerConnect(object sender, GetDataHandledEventArgs args)
+ {
+ // Save their character data from on join.
+ // This can be set as their real player data by commands, or applied temporarily when they logout or use the overridessc command
+ args.Player.SscDataWhenJoined = ServerSidePlayerData.CreateFromPlayer(args.Player.TPlayer);
+ }
+
+ private void OnPlayerDeath(object sender, GetDataHandlers.KillMeEventArgs args)
+ {
+ if (!Main.ServerSideCharacter || !args.Player.IsLoggedIn)
+ {
+ // Ignore non-SSC deaths and non-logged in deaths
+ return;
+ }
+
+ if (args.Player.TPlayer.difficulty == PlayerDifficultyID.Hardcore)
+ {
+ // The player has died in hardcore, so delete their saved data
+ DeletePlayerData(args.Player);
+ TShock.Log.ConsoleDebug("GetDataHandlers / HandlePlayerKillMeV2 ssc delete {0} {1}", args.Player.Name, args.Player.TPlayer.difficulty);
+ args.Player.SendErrorMessage("You have fallen in hardcore mode, and your items have been lost forever.");
+ // Then create new data, write it to disk, and apply it
+ args.Player.SscData = CreateNewPlayerData(args.Player, writeToDisk: true);
+ ApplyServerSidePlayerData(args.Player);
+ }
+ }
+
+ private void OnPlayerSlotChange(object sender, GetDataHandlers.PlayerSlotEventArgs args)
+ {
+ if (!Main.ServerSideCharacter)
+ {
+ return;
+ }
+
+ if (args.Player.IsLoggedIn)
+ {
+ args.Player.SscData.Inventory.UpdateInventorySlot(args.Slot, new NetItem(args.Type, args.Stack, args.Prefix));
+ }
+ else if (TShock.Config.DisableLoginBeforeJoin && 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.
+ args.Player.IsDisabledPendingTrashRemoval = true;
+ }
+ }
+
+ private void OnPlayerHpChange(object sender, GetDataHandlers.PlayerHPEventArgs args)
+ {
+ if (args.Player.IsLoggedIn)
+ {
+ args.Player.TPlayer.statLife = args.Current;
+ args.Player.TPlayer.statLifeMax = args.Max;
+
+ args.Player.SscData.Stats.Health = args.Current;
+ args.Player.SscData.Stats.MaxHealth = args.Max;
+ }
+ }
+
+ private void OnPlayerManaChange(object sender, GetDataHandlers.PlayerManaEventArgs args)
+ {
+ if (args.Player.IsLoggedIn)
+ {
+ args.Player.TPlayer.statMana = args.Current;
+ args.Player.TPlayer.statManaMax = args.Max;
+
+ args.Player.SscData.Stats.Mana = args.Current;
+ args.Player.SscData.Stats.MaxMana = args.Max;
+ }
+ }
+
+ ///
+ /// Invoked approximately once per second
+ ///
+ internal void OnSecondUpdate()
+ {
+ int saveInterval = TShock.ServerSideCharacterConfig.ServerSideCharacterSave * 60; // converting minutes to seconds
+ if ((DateTime.UtcNow - _lastSave).TotalSeconds >= saveInterval)
+ {
+ SaveAllPlayersData();
+
+ _lastSave = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Creates new server side player data for the given player and optionally writes it to disk
+ ///
+ ///
+ ///
+ public static ServerSidePlayerData CreateNewPlayerData(TSPlayer player, bool writeToDisk)
+ {
+ var data = ServerSidePlayerData.CreateDefaultFromPlayer(player.TPlayer);
+
+ if (writeToDisk)
+ {
+ TShock.CharacterDB.InsertPlayerData(data, player.Account.ID);
+ }
+
+ return data;
+ }
+
+ ///
+ /// Reads a player's server side player data from disk
+ ///
+ ///
+ ///
+ public static ServerSidePlayerData ReadPlayerData(TSPlayer player)
+ {
+ return TShock.CharacterDB.ReadPlayerData(player.Account.ID);
+ }
+
+ ///
+ /// Writes a player's current player data to disk
+ ///
+ ///
+ public static void SavePlayerData(TSPlayer player)
+ {
+ TShock.CharacterDB.UpdatePlayerData(player.SscData, player.Account.ID);
+ }
+
+ ///
+ /// Writes all connected players current player data to disk
+ ///
+ public static void SaveAllPlayersData()
+ {
+ foreach (TSPlayer player in TShock.Players)
+ {
+ if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
+ {
+ SavePlayerData(player);
+ }
+ }
+ }
+
+ ///
+ /// Deletes a player's server side player data from disk
+ ///
+ ///
+ public static void DeletePlayerData(TSPlayer player) => DeleteAccountData(player.Account);
+
+ ///
+ /// Deletes an account's server side player data from disk
+ ///
+ ///
+ public static void DeleteAccountData(UserAccount account)
+ {
+ TShock.CharacterDB.DeletePlayerData(account.ID);
+ }
+
+ ///
+ /// Applies a set of server side player data, syncing it from the server to the client
+ ///
+ ///
+ ///
+ public static void ApplyServerSidePlayerData(TSPlayer player, ServerSidePlayerData temporaryData = null)
+ {
+ ServerSidePlayerData data = temporaryData ?? player.SscData;
+
+ // Start ignoring SSC-related packets! This is critical so that we don't send or receive dirty data!
+ player.IgnoreSSCPackets = true;
+
+ ApplyStatData(player.TPlayer, data.Stats);
+ ApplySpawnData(player.TPlayer, data.Spawn);
+ ApplyVanityData(player.TPlayer, data.Vanity);
+ ApplyInventoryData(player.TPlayer, data.Inventory);
+
+ SyncData(player, data);
+ }
+
+ static void ApplyStatData(Player player, ServerSideStats stats)
+ {
+ player.statLife = stats.Health;
+ player.statLifeMax = stats.MaxHealth;
+ player.statMana = stats.Mana;
+ player.statManaMax = stats.MaxMana;
+ player.extraAccessory = stats.HasExtraSlot;
+ player.anglerQuestsFinished = stats.QuestsCompleted;
+ player.golferScoreAccumulated = stats.GolfScoreAccumulated;
+ }
+
+ static void ApplySpawnData(Player player, ServerSideSpawn spawn)
+ {
+ player.SpawnX = spawn.TileX;
+ player.SpawnY = spawn.TileY;
+ }
+
+ static void ApplyVanityData(Player player, ServerSideVanity vanity)
+ {
+ player.hairDye = vanity.HairDye;
+ player.skinVariant = vanity.SkinVariant;
+ player.hair = vanity.Hair;
+ player.hairColor = vanity.HairColor;
+ player.pantsColor = vanity.PantsColor;
+ player.shirtColor = vanity.ShirtColor;
+ player.underShirtColor = vanity.UnderShirtColor;
+ player.shoeColor = vanity.ShoeColor;
+ player.skinColor = vanity.SkinColor;
+ player.eyeColor = vanity.EyeColor;
+ player.hideVisibleAccessory = vanity.HideVisuals;
+ }
+
+ static void ApplyInventoryData(Player player, ServerSideInventory inventory)
+ {
+ for (int i = 0; i < NetItem.TotalSlots; i++)
+ {
+ if (i <= NetItem.FullInventoryGroup.End)
+ {
+ //0-58
+ player.inventory[i].netDefaults(inventory[i].NetId);
+
+ if (player.inventory[i].netID != 0)
+ {
+ player.inventory[i].stack = inventory[i].Stack;
+ player.inventory[i].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.FullEquipmentAndVanityGroup.End)
+ {
+ //59-78
+ var index = i - NetItem.FullEquipmentAndVanityGroup.Start;
+ player.armor[index].netDefaults(inventory[i].NetId);
+
+ if (player.armor[index].netID != 0)
+ {
+ player.armor[index].stack = inventory[i].Stack;
+ player.armor[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.FullArmorAndVanityDyeGroup.End)
+ {
+ //79-88
+ var index = i - NetItem.FullArmorAndVanityDyeGroup.Start;
+ player.dye[index].netDefaults(inventory[i].NetId);
+
+ if (player.dye[index].netID != 0)
+ {
+ player.dye[index].stack = inventory[i].Stack;
+ player.dye[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.MiscEquipGroup.End)
+ {
+ //89-93
+ var index = i - NetItem.MiscEquipGroup.Start;
+ player.miscEquips[index].netDefaults(inventory[i].NetId);
+
+ if (player.miscEquips[index].netID != 0)
+ {
+ player.miscEquips[index].stack = inventory[i].Stack;
+ player.miscEquips[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.MiscDyeGroup.End)
+ {
+ //93-98
+ var index = i - NetItem.MiscDyeGroup.Start;
+ player.miscDyes[index].netDefaults(inventory[i].NetId);
+
+ if (player.miscDyes[index].netID != 0)
+ {
+ player.miscDyes[index].stack = inventory[i].Stack;
+ player.miscDyes[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.PiggyGroup.End)
+ {
+ //99-138
+ var index = i - NetItem.PiggyGroup.Start;
+ player.bank.item[index].netDefaults(inventory[i].NetId);
+
+ if (player.bank.item[index].netID != 0)
+ {
+ player.bank.item[index].stack = inventory[i].Stack;
+ player.bank.item[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.SafeGroup.End)
+ {
+ //139-178
+ var index = i - NetItem.SafeGroup.Start;
+ player.bank2.item[index].netDefaults(inventory[i].NetId);
+
+ if (player.bank2.item[index].netID != 0)
+ {
+ player.bank2.item[index].stack = inventory[i].Stack;
+ player.bank2.item[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.TrashGroup.End)
+ {
+ //179
+ player.trashItem.netDefaults(inventory[i].NetId);
+
+ if (player.trashItem.netID != 0)
+ {
+ player.trashItem.stack = inventory[i].Stack;
+ player.trashItem.Prefix(inventory[i].PrefixId);
+ }
+ }
+ else if (i <= NetItem.ForgeGroup.End)
+ {
+ //180-219
+ var index = i - NetItem.ForgeGroup.Start;
+ player.bank3.item[index].netDefaults(inventory[i].NetId);
+
+ if (player.bank3.item[index].netID != 0)
+ {
+ player.bank3.item[index].stack = inventory[i].Stack;
+ player.bank3.item[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ else
+ {
+ //220-259
+ var index = i - NetItem.VoidGroup.Start;
+ player.bank4.item[index].netDefaults(inventory[i].NetId);
+
+ if (player.bank4.item[index].netID != 0)
+ {
+ player.bank4.item[index].stack = inventory[i].Stack;
+ player.bank4.item[index].Prefix(inventory[i].PrefixId);
+ }
+ }
+ }
+ }
+
+ private static void SyncData(TSPlayer player, ServerSidePlayerData data)
+ {
+ // Broadcast a player slot packet for each item in the server side inventory to everyone on the server.
+ for (int slot = 0; slot < NetItem.TotalSlots; slot++)
+ {
+ NetItem item = data.Inventory[slot];
+ NetMessage.SendData(
+ (int)PacketTypes.PlayerSlot,
+ -1,
+ -1,
+ NetworkText.FromLiteral(Lang.GetItemNameValue(item.NetId)),
+ player.Index,
+ slot,
+ item.PrefixId
+ );
+ }
+
+ // Broadcast appearance and stats to the server
+ NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(player.Name), player.Index, 0f, 0f, 0f, 0);
+ NetMessage.SendData((int)PacketTypes.PlayerMana, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+ NetMessage.SendData((int)PacketTypes.PlayerHp, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+
+ // Send a player slot packet for each item in the server side inventory to the player joining
+ // This is required so that the player actually receives the inventory they're meant to
+ for (int slot = 0; slot < NetItem.TotalSlots; slot++)
+ {
+ NetItem item = data.Inventory[slot];
+ NetMessage.SendData(
+ (int)PacketTypes.PlayerSlot,
+ player.Index,
+ -1,
+ NetworkText.FromLiteral(Lang.GetItemNameValue(item.NetId)),
+ player.Index,
+ slot,
+ item.PrefixId
+ );
+ }
+
+ // Sync appearance and stats with the player
+ NetMessage.SendData((int)PacketTypes.PlayerInfo, player.Index, -1, NetworkText.FromLiteral(player.Name), player.Index, 0f, 0f, 0f, 0);
+ NetMessage.SendData((int)PacketTypes.PlayerMana, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+ NetMessage.SendData((int)PacketTypes.PlayerHp, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+
+
+ // Reset player buffs
+ for (int k = 0; k < 22; k++)
+ {
+ player.TPlayer.buffType[k] = 0;
+ }
+
+ /*
+ * The following packets are sent twice because the server will not send a packet to a client
+ * if they have not spawned yet if the remoteclient is -1
+ * This is for when players login via uuid or serverpassword instead of via
+ * the login command.
+ */
+ NetMessage.SendData((int)PacketTypes.PlayerBuff, -1, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+ NetMessage.SendData((int)PacketTypes.PlayerBuff, player.Index, -1, NetworkText.Empty, player.Index, 0f, 0f, 0f, 0);
+
+ NetMessage.SendData((int)PacketTypes.NumberOfAnglerQuestsCompleted, player.Index, -1, NetworkText.Empty, player.Index);
+ NetMessage.SendData((int)PacketTypes.NumberOfAnglerQuestsCompleted, -1, -1, NetworkText.Empty, player.Index);
+
+ NetMessage.SendData((int)PacketTypes.RemoveItemOwner, player.Index, -1, NetworkText.Empty, 400);
+
+ // Sync journey research with the player
+ if (Main.GameModeInfo.IsJourneyMode)
+ {
+ var sacrificedItems = TShock.ResearchDatastore.GetSacrificedItems();
+ for (int i = 0; i < ItemID.Count; i++)
+ {
+ var amount = 0;
+ if (sacrificedItems.ContainsKey(i))
+ {
+ amount = sacrificedItems[i];
+ }
+
+ var response = NetCreativeUnlocksModule.SerializeItemSacrifice(i, amount);
+ NetManager.Instance.SendToClient(response, player.Index);
+ }
+ }
+ }
+ }
+}
diff --git a/TShockAPI/ServerSideCharacters/ServerSideInventory.cs b/TShockAPI/ServerSideCharacters/ServerSideInventory.cs
new file mode 100644
index 000000000..7662819e4
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSideInventory.cs
@@ -0,0 +1,265 @@
+using System;
+using System.Collections.Generic;
+using Terraria;
+using TShockAPI.Net;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Contains a server side player's inventory
+ ///
+ public class ServerSideInventory
+ {
+ ///
+ /// Array of all items in the player's inventory
+ ///
+ internal NetItem[] Items = new NetItem[NetItem.TotalSlots];
+
+ ///
+ /// Gets the item at the given index of the inventory
+ ///
+ ///
+ ///
+ public NetItem this[int index] { get => Items[index]; set => Items[index] = value; }
+
+ ///
+ /// Items in the player's main inventory slots
+ ///
+ public ArraySegment Inventory =>
+ new ArraySegment(Items, NetItem.InventoryGroup.Start, NetItem.InventoryGroup.Count);
+
+ ///
+ /// Items in the player's coin slots
+ ///
+ public ArraySegment Coins =>
+ new ArraySegment(Items, NetItem.CoinGroup.Start, NetItem.CoinGroup.Count);
+
+ ///
+ /// Items in the player's ammo slots
+ ///
+ public ArraySegment Ammo =>
+ new ArraySegment(Items, NetItem.AmmoGroup.Start, NetItem.AmmoGroup.Count);
+
+ ///
+ /// Items in the player's held item slot
+ ///
+ public ArraySegment HeldItem =>
+ new ArraySegment(Items, NetItem.HeldItemGroup.Start, NetItem.HeldItemGroup.Count);
+
+ ///
+ /// Armor equipped by the player
+ ///
+ public ArraySegment Armor =>
+ new ArraySegment(Items, NetItem.ArmorGroup.Start, NetItem.ArmorGroup.Count);
+
+ ///
+ /// Accessories equipped by the player
+ ///
+ public ArraySegment Accessory =>
+ new ArraySegment(Items, NetItem.AccessoryGroup.Start, NetItem.AccessoryGroup.Count);
+
+ ///
+ /// Vanity armor equipped by the player
+ ///
+ public ArraySegment ArmorVanity =>
+ new ArraySegment(Items, NetItem.ArmorVanityGroup.Start, NetItem.ArmorVanityGroup.Count);
+
+ ///
+ /// Vanity accessories equipped by the player
+ ///
+ public ArraySegment AccessoryVanity =>
+ new ArraySegment(Items, NetItem.AccessoryVanityGroup.Start, NetItem.AccessoryVanityGroup.Count);
+
+ ///
+ /// Armor dyes equipped by the player
+ ///
+ public ArraySegment ArmorDye =>
+ new ArraySegment(Items, NetItem.ArmorDyeGroup.Start, NetItem.ArmorDyeGroup.Count);
+
+ ///
+ /// Vanity dyes equipped by the player
+ ///
+ public ArraySegment AccessoryDye =>
+ new ArraySegment(Items, NetItem.AccessoryDyeGroup.Start, NetItem.AccessoryDyeGroup.Count);
+
+ ///
+ /// Miscellanious items equipped by the player. This is the Mount/Hook/etc section of the inventory
+ ///
+ public ArraySegment MiscEquips =>
+ new ArraySegment(Items, NetItem.MiscEquipGroup.Start, NetItem.MiscEquipGroup.Count);
+
+ ///
+ /// Dyes for the miscellanious items sections equipped by the player
+ ///
+ public ArraySegment MiscEquipDyes =>
+ new ArraySegment(Items, NetItem.MiscDyeGroup.Start, NetItem.MiscDyeGroup.Count);
+
+ ///
+ /// Items in the player's piggy bank
+ ///
+ public ArraySegment PiggyBank =>
+ new ArraySegment(Items, NetItem.PiggyGroup.Start, NetItem.PiggyGroup.Count);
+
+ ///
+ /// Items in the player's safe
+ ///
+ public ArraySegment SafeBank =>
+ new ArraySegment(Items, NetItem.SafeGroup.Start, NetItem.SafeGroup.Count);
+
+ ///
+ /// Items in the player's trash slot
+ ///
+ public ArraySegment TrashSlot =>
+ new ArraySegment(Items, NetItem.TrashGroup.Start, NetItem.TrashGroup.Count);
+
+ ///
+ /// Items in the player's defender's forge
+ ///
+ public ArraySegment ForgeBank =>
+ new ArraySegment(Items, NetItem.ForgeGroup.Start, NetItem.ForgeGroup.Count);
+
+ ///
+ /// Items in the player's void bank
+ ///
+ public ArraySegment VoidBank =>
+ new ArraySegment(Items, NetItem.VoidGroup.Start, NetItem.VoidGroup.Count);
+
+ ///
+ /// Updates an inventory slot with a new item
+ ///
+ ///
+ ///
+ public void UpdateInventorySlot(int slot, Item item) => UpdateInventorySlot(slot, (NetItem)item);
+
+ ///
+ /// Updates an inventory slot with a new item
+ ///
+ ///
+ ///
+ public void UpdateInventorySlot(int slot, NetItem item)
+ {
+ if (slot < 0 || slot > NetItem.TotalSlots)
+ {
+ return;
+ }
+
+ Items[slot] = item;
+ }
+
+ ///
+ /// Creates a default inventory using the items listed in
+ ///
+ ///
+ public static ServerSideInventory CreateDefault()
+ {
+ ServerSideInventory inventory = new ServerSideInventory();
+
+ // First fill as much of the inventory as possible with items from the SSC Config
+ for (int i = 0; i < TShock.ServerSideCharacterConfig.StartingInventory.Count; i++)
+ {
+ inventory.Items[i] = TShock.ServerSideCharacterConfig.StartingInventory[i];
+ }
+
+ // Then fill the rest with empty items
+ for (int i = TShock.ServerSideCharacterConfig.StartingInventory.Count; i < NetItem.TotalSlots; i++)
+ {
+ inventory.Items[i] = new NetItem();
+ }
+
+ return inventory;
+ }
+
+ ///
+ /// Creates an inventory from an enumerable of NetItems
+ ///
+ ///
+ ///
+ public static ServerSideInventory CreateFromNetItems(IEnumerable items)
+ {
+ ServerSideInventory inventory = new ServerSideInventory();
+
+ int index = 0;
+ foreach (NetItem item in items)
+ {
+ inventory.Items[index] = item;
+ index++;
+ }
+
+ return inventory;
+ }
+
+ ///
+ /// Creates an inventory using the given player's inventory
+ ///
+ ///
+ ///
+ public static ServerSideInventory CreateFromPlayer(Player player)
+ {
+ ServerSideInventory inventory = new ServerSideInventory();
+
+ for (int i = 0; i < NetItem.TotalSlots; i++)
+ {
+ if (i <= NetItem.FullInventoryGroup.End)
+ {
+ //0-58
+ inventory[i] = (NetItem)player.inventory[i];
+ }
+ else if (i <= NetItem.FullEquipmentAndVanityGroup.End)
+ {
+ //59-78
+ var index = i - NetItem.FullEquipmentAndVanityGroup.Start;
+ inventory[i] = (NetItem)player.armor[index];
+ }
+ else if (i <= NetItem.FullArmorAndVanityDyeGroup.End)
+ {
+ //79-88
+ var index = i - NetItem.FullArmorAndVanityDyeGroup.Start;
+ inventory[i] = (NetItem)player.dye[index];
+ }
+ else if (i <= NetItem.MiscEquipGroup.End)
+ {
+ //89-93
+ var index = i - NetItem.MiscEquipGroup.Start;
+ inventory[i] = (NetItem)player.miscEquips[index];
+ }
+ else if (i <= NetItem.MiscDyeGroup.End)
+ {
+ //93-98
+ var index = i - NetItem.MiscDyeGroup.Start;
+ inventory[i] = (NetItem)player.miscDyes[index];
+ }
+ else if (i <= NetItem.PiggyGroup.End)
+ {
+ //99-138
+ var index = i - NetItem.PiggyGroup.Start;
+ inventory[i] = (NetItem)player.bank.item[index];
+ }
+ else if (i <= NetItem.SafeGroup.End)
+ {
+ //139-178
+ var index = i - NetItem.SafeGroup.Start;
+ inventory[i] = (NetItem)player.bank2.item[index];
+ }
+ else if (i <= NetItem.TrashGroup.End)
+ {
+ //179
+ inventory[i] = (NetItem)player.trashItem;
+ }
+ else if (i <= NetItem.ForgeGroup.End)
+ {
+ //180-219
+ var index = i - NetItem.ForgeGroup.Start;
+ inventory[i] = (NetItem)player.bank3.item[index];
+ }
+ else
+ {
+ //220-259
+ var index = i - NetItem.VoidGroup.Start;
+ inventory[i] = (NetItem)player.bank4.item[index];
+ }
+ }
+
+ return inventory;
+ }
+ }
+}
diff --git a/TShockAPI/ServerSideCharacters/ServerSidePlayerData.cs b/TShockAPI/ServerSideCharacters/ServerSidePlayerData.cs
new file mode 100644
index 000000000..e94f3ca31
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSidePlayerData.cs
@@ -0,0 +1,64 @@
+using Terraria;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Contains information about the server side state of a player
+ ///
+ public class ServerSidePlayerData
+ {
+ ///
+ /// Contains the server side player's stats
+ ///
+ public ServerSideStats Stats { get; set; }
+
+ ///
+ /// Contains the server side player's vanity values
+ ///
+ public ServerSideVanity Vanity { get; set; }
+
+ ///
+ /// Contains the server side player's inventory values
+ ///
+ public ServerSideInventory Inventory { get; set; }
+
+ ///
+ /// Contains the server side player's spawn information
+ ///
+ public ServerSideSpawn Spawn { get; set; }
+
+ ///
+ /// Creates a basic server side player using inventory and stat defaults from the config, and vanity from the player
+ ///
+ ///
+ public static ServerSidePlayerData CreateDefaultFromPlayer(Player player)
+ {
+ ServerSidePlayerData data = new ServerSidePlayerData
+ {
+ Stats = ServerSideStats.CreateDefault(),
+ Inventory = ServerSideInventory.CreateDefault(),
+ Vanity = ServerSideVanity.CreateFromPlayer(player),
+ Spawn = ServerSideSpawn.CreateDefault()
+ };
+
+ return data;
+ }
+
+ ///
+ /// Creates a set of server side player data using the given player's character data
+ ///
+ ///
+ ///
+ public static ServerSidePlayerData CreateFromPlayer(Player player)
+ {
+ ServerSidePlayerData data = new ServerSidePlayerData
+ {
+ Stats = ServerSideStats.CreateFromPlayer(player),
+ Inventory = ServerSideInventory.CreateFromPlayer(player),
+ Vanity = ServerSideVanity.CreateFromPlayer(player)
+ };
+
+ return data;
+ }
+ }
+}
diff --git a/TShockAPI/ServerSideCharacters/ServerSideSpawn.cs b/TShockAPI/ServerSideCharacters/ServerSideSpawn.cs
new file mode 100644
index 000000000..31d034d39
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSideSpawn.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Contains details about a server side player's spawn location
+ ///
+ public class ServerSideSpawn
+ {
+ ///
+ /// The tile coordinate x position the player will spawn at
+ ///
+ public int TileX { get; set; }
+
+ ///
+ /// The tile coordinate y position the player will spawn at
+ ///
+ public int TileY { get; set; }
+
+ ///
+ /// Creates a default spawn point that spawns the player at the world's spawn point
+ ///
+ ///
+ public static ServerSideSpawn CreateDefault()
+ {
+ ServerSideSpawn spawn = new ServerSideSpawn
+ {
+ TileX = -1,
+ TileY = -1
+ };
+
+ return spawn;
+ }
+ }
+}
diff --git a/TShockAPI/ServerSideCharacters/ServerSideStats.cs b/TShockAPI/ServerSideCharacters/ServerSideStats.cs
new file mode 100644
index 000000000..03f9c8d1c
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSideStats.cs
@@ -0,0 +1,81 @@
+using System;
+using Terraria;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Contains a server side player's stats
+ ///
+ public class ServerSideStats
+ {
+ ///
+ /// The player's current health
+ ///
+ public int Health { get; set; }
+ ///
+ /// The player's maximum health
+ ///
+ public int MaxHealth { get; set; }
+ ///
+ /// The player's current mana
+ ///
+ public int Mana { get; set; }
+ ///
+ /// The player's maximum mana
+ ///
+ public int MaxMana { get; set; }
+ ///
+ /// Whether or not the player has unlocked extra slots
+ ///
+ public bool HasExtraSlot { get; set; }
+ ///
+ /// The number of angler quests completed by the player
+ ///
+ public int QuestsCompleted { get; set; }
+ ///
+ /// The amount of golf score accumulated by the player
+ ///
+ public int GolfScoreAccumulated { get; set; }
+
+ ///
+ /// Creates a default set of stats using the settings
+ ///
+ ///
+ public static ServerSideStats CreateDefault()
+ {
+ ServerSideStats stats = new ServerSideStats
+ {
+ Health = TShock.ServerSideCharacterConfig.StartingHealth,
+ MaxHealth = TShock.ServerSideCharacterConfig.StartingHealth,
+ Mana = TShock.ServerSideCharacterConfig.StartingMana,
+ MaxMana = TShock.ServerSideCharacterConfig.StartingMana,
+ HasExtraSlot = false,
+ QuestsCompleted = 0,
+ GolfScoreAccumulated = 0
+ };
+
+ return stats;
+ }
+
+ ///
+ /// Creates a set of server side stats using the given player's stats
+ ///
+ ///
+ ///
+ public static ServerSideStats CreateFromPlayer(Player player)
+ {
+ ServerSideStats stats = new ServerSideStats
+ {
+ Health = player.statLife,
+ MaxHealth = player.statLifeMax,
+ Mana = player.statMana,
+ MaxMana = player.statManaMax,
+ HasExtraSlot = player.extraAccessory,
+ QuestsCompleted = player.anglerQuestsFinished,
+ GolfScoreAccumulated = player.golferScoreAccumulated
+ };
+
+ return stats;
+ }
+ }
+}
diff --git a/TShockAPI/ServerSideCharacters/ServerSideVanity.cs b/TShockAPI/ServerSideCharacters/ServerSideVanity.cs
new file mode 100644
index 000000000..877125e3b
--- /dev/null
+++ b/TShockAPI/ServerSideCharacters/ServerSideVanity.cs
@@ -0,0 +1,80 @@
+using Microsoft.Xna.Framework;
+using Terraria;
+
+namespace TShockAPI.ServerSideCharacters
+{
+ ///
+ /// Contains a server side player's vanity display
+ ///
+ public class ServerSideVanity
+ {
+ ///
+ /// Player's skin variant
+ ///
+ public int SkinVariant { get; set; }
+ ///
+ /// Player's hair type
+ ///
+ public int Hair { get; set; }
+ ///
+ /// Hair dye applied to the player's hair
+ ///
+ public byte HairDye { get; set; }
+ ///
+ /// Player's hair color
+ ///
+ public Color HairColor { get; set; }
+ ///
+ /// Player's pants color
+ ///
+ public Color PantsColor { get; set; }
+ ///
+ /// Player's shirt color
+ ///
+ public Color ShirtColor { get; set; }
+ ///
+ /// Player's undershirt color
+ ///
+ public Color UnderShirtColor { get; set; }
+ ///
+ /// Player's shoe color
+ ///
+ public Color ShoeColor { get; set; }
+ ///
+ /// Player's skin color
+ ///
+ public Color SkinColor { get; set; }
+ ///
+ /// Player's eye color
+ ///
+ public Color EyeColor { get; set; }
+ ///
+ /// Player's hidden accessories
+ ///
+ public bool[] HideVisuals { get; set; }
+
+ ///
+ /// Creates server side vanity for the given player
+ ///
+ ///
+ ///
+ public static ServerSideVanity CreateFromPlayer(Player player)
+ {
+ ServerSideVanity vanity = new ServerSideVanity
+ {
+ Hair = player.hair,
+ HairDye = player.hairDye,
+ HairColor = player.hairColor,
+ PantsColor = player.pantsColor,
+ ShirtColor = player.shirtColor,
+ UnderShirtColor = player.underShirtColor,
+ ShoeColor = player.shoeColor,
+ SkinColor = player.skinColor,
+ EyeColor = player.eyeColor,
+ HideVisuals = player.hideVisibleAccessory
+ };
+
+ return vanity;
+ }
+ }
+}
diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs
index ade162346..db71c7120 100644
--- a/TShockAPI/TSPlayer.cs
+++ b/TShockAPI/TSPlayer.cs
@@ -35,6 +35,7 @@ You should have received a copy of the GNU General Public License
using TShockAPI.Net;
using Timer = System.Timers.Timer;
using System.Linq;
+using TShockAPI.ServerSideCharacters;
namespace TShockAPI
{
@@ -156,7 +157,13 @@ public static List FindByNameOrID(string plr)
///
public int RPPending = 0;
+ ///
+ /// Spawn position X
+ ///
public int sX = -1;
+ ///
+ /// Spawn position Y
+ ///
public int sY = -1;
///
@@ -188,6 +195,9 @@ public Group Group
///
public Group tempGroup = null;
+ ///
+ /// Time remaining in a temporary group
+ ///
public Timer tempGroupTimer;
private Group group = null;
@@ -368,9 +378,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
Item[] voidVault = TPlayer.bank4.item;
Item trash = TPlayer.trashItem;
- for (int i = 0; i < NetItem.MaxInventory; i++)
+
+ for (int i = 0; i < NetItem.TotalSlots; i++)
{
- if (i < NetItem.InventoryIndex.Item2)
+ if (i <= NetItem.FullInventoryGroup.End)
{
// From above: this is slots 0-58 in the inventory.
// 0-58
@@ -390,10 +401,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.ArmorIndex.Item2)
+ else if (i <= NetItem.FullEquipmentAndVanityGroup.End)
{
// 59-78
- var index = i - NetItem.ArmorIndex.Item1;
+ var index = i - NetItem.FullEquipmentAndVanityGroup.Start;
Item item = new Item();
if (armor[index] != null && armor[index].netID != 0)
{
@@ -410,10 +421,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.DyeIndex.Item2)
+ else if (i <= NetItem.FullArmorAndVanityDyeGroup.End)
{
// 79-88
- var index = i - NetItem.DyeIndex.Item1;
+ var index = i - NetItem.FullArmorAndVanityDyeGroup.Start;
Item item = new Item();
if (dye[index] != null && dye[index].netID != 0)
{
@@ -430,10 +441,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.MiscEquipIndex.Item2)
+ else if (i <= NetItem.MiscEquipGroup.End)
{
// 89-93
- var index = i - NetItem.MiscEquipIndex.Item1;
+ var index = i - NetItem.MiscEquipGroup.Start;
Item item = new Item();
if (miscEquips[index] != null && miscEquips[index].netID != 0)
{
@@ -450,10 +461,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.MiscDyeIndex.Item2)
+ else if (i <= NetItem.MiscDyeGroup.End)
{
// 93-98
- var index = i - NetItem.MiscDyeIndex.Item1;
+ var index = i - NetItem.MiscDyeGroup.Start;
Item item = new Item();
if (miscDyes[index] != null && miscDyes[index].netID != 0)
{
@@ -470,10 +481,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.PiggyIndex.Item2)
+ else if (i <= NetItem.PiggyGroup.End)
{
// 98-138
- var index = i - NetItem.PiggyIndex.Item1;
+ var index = i - NetItem.PiggyGroup.Start;
Item item = new Item();
if (piggy[index] != null && piggy[index].netID != 0)
{
@@ -491,10 +502,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.SafeIndex.Item2)
+ else if (i <= NetItem.SafeGroup.End)
{
// 138-178
- var index = i - NetItem.SafeIndex.Item1;
+ var index = i - NetItem.SafeGroup.Start;
Item item = new Item();
if (safe[index] != null && safe[index].netID != 0)
{
@@ -512,7 +523,7 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.TrashIndex.Item2)
+ else if (i <= NetItem.TrashGroup.End)
{
// 178-179
Item item = new Item();
@@ -532,10 +543,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.ForgeIndex.Item2)
+ else if (i <= NetItem.ForgeGroup.End)
{
// 179-220
- var index = i - NetItem.ForgeIndex.Item1;
+ var index = i - NetItem.ForgeGroup.Start;
Item item = new Item();
if (forge[index] != null && forge[index].netID != 0)
{
@@ -553,10 +564,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
}
- else if (i < NetItem.VoidIndex.Item2)
+ else if (i <= NetItem.VoidGroup.End)
{
// 220-260
- var index = i - NetItem.VoidIndex.Item1;
+ var index = i - NetItem.VoidGroup.Start;
Item item = new Item();
if (voidVault[index] != null && voidVault[index].netID != 0)
{
@@ -576,15 +587,10 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false)
}
}
-
+
return check;
}
- ///
- /// 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 ).
///
@@ -877,11 +883,15 @@ public IEnumerable- Accessories
{
get
{
- for (int i = 3; i < 8; i++)
+ for (int i = NetItem.AccessoryGroup.Start; i < NetItem.AccessoryGroup.Count; i++)
yield return TPlayer.armor[i];
}
}
+ public ServerSidePlayerData SscData { get; set; }
+
+ public ServerSidePlayerData SscDataWhenJoined { get; set; }
+
///
/// Saves the player's inventory to SSC
///
@@ -899,30 +909,7 @@ public bool SaveServerCharacter()
TShock.Log.ConsoleInfo("Skipping SSC Backup for " + Account.Name); // Debug Code
return true;
}
- PlayerData.CopyCharacter(this);
- TShock.CharacterDB.InsertPlayerData(this);
- return true;
- }
- catch (Exception e)
- {
- TShock.Log.Error(e.Message);
- return false;
- }
- }
-
- ///
- /// Sends the players server side character to client
- ///
- /// bool - True/false if it saved successfully
- public bool SendServerCharacter()
- {
- if (!Main.ServerSideCharacter)
- {
- return false;
- }
- try
- {
- PlayerData.RestoreCharacter(this);
+ ServerSideCharacters.ServerSideCoordinator.SavePlayerData(this);
return true;
}
catch (Exception e)
@@ -930,7 +917,6 @@ public bool SendServerCharacter()
TShock.Log.Error(e.Message);
return false;
}
-
}
///
@@ -1007,7 +993,7 @@ public bool InventorySlotAvailable
bool flag = false;
if (RealPlayer)
{
- for (int i = 0; i < 50; i++) //51 is trash can, 52-55 is coins, 56-59 is ammo
+ for (int i = 0; i < NetItem.InventorySlots; i++)
{
if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].Name == "")
{
@@ -1020,11 +1006,6 @@ public bool InventorySlotAvailable
}
}
- ///
- /// This contains the character data a player has when they join the server.
- ///
- public PlayerData DataWhenJoined { get; set; }
-
///
/// Determines whether the player's storage contains the given key.
///
@@ -1087,17 +1068,6 @@ public object RemoveData(string key)
public void Logout()
{
PlayerHooks.OnPlayerLogout(this);
- if (Main.ServerSideCharacter)
- {
- IsDisabledForSSC = true;
- if (!IsDisabledPendingTrashRemoval && (!Dead || TPlayer.difficulty != 2))
- {
- PlayerData.CopyCharacter(this);
- TShock.CharacterDB.InsertPlayerData(this);
- }
- }
-
- PlayerData = new PlayerData(this);
Group = TShock.Groups.GetGroupByName(TShock.Config.DefaultGuestGroupName);
tempGroup = null;
if (tempGroupTimer != null)
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index 74e356360..77729041f 100644
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -44,6 +44,7 @@ You should have received a copy of the GNU General Public License
using TShockAPI.Sockets;
using TShockAPI.CLI;
using TShockAPI.Localization;
+using TShockAPI.Net;
namespace TShockAPI
{
@@ -136,6 +137,9 @@ public class TShock : TerrariaPlugin
/// The TShock anti-cheat/anti-exploit system.
internal Bouncer Bouncer;
+ /// Coordinates the server side player system.
+ internal ServerSideCoordinator SSC;
+
/// The TShock item ban system.
internal ItemBans ItemBans;
@@ -184,9 +188,9 @@ public TShock(Main game)
{
Config = new ConfigFile();
ServerSideCharacterConfig = new ServerSideConfig();
- ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-15, 1, 0));
- ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-13, 1, 0));
- ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-16, 1, 0));
+ ServerSideCharacterConfig.StartingInventory.Add(new NetItem(ItemID.CopperShortsword, 1, 0));
+ ServerSideCharacterConfig.StartingInventory.Add(new NetItem(ItemID.CopperPickaxe, 1, 0));
+ ServerSideCharacterConfig.StartingInventory.Add(new NetItem(ItemID.CopperAxe, 1, 0));
Order = 0;
instance = this;
}
@@ -361,14 +365,14 @@ public override void Initialize()
ServerApi.Hooks.NetNameCollision.Register(this, NetHooks_NameCollision);
ServerApi.Hooks.ItemForceIntoChest.Register(this, OnItemForceIntoChest);
ServerApi.Hooks.WorldGrassSpread.Register(this, OnWorldGrassSpread);
- Hooks.PlayerHooks.PlayerPreLogin += OnPlayerPreLogin;
Hooks.PlayerHooks.PlayerPostLogin += OnPlayerLogin;
- Hooks.AccountHooks.AccountDelete += OnAccountDelete;
- Hooks.AccountHooks.AccountCreate += OnAccountCreate;
GetDataHandlers.InitGetDataHandler();
Commands.InitCommands();
+ // New this up after GetDataHandlers has initialized
+ SSC = new ServerSideCoordinator();
+
EnglishLanguage.Initialize();
if (Config.RestApiEnabled)
@@ -504,28 +508,6 @@ private void OnPlayerLogin(PlayerPostLoginEventArgs args)
}
}
- /// OnAccountDelete - Internal hook fired on account delete.
- /// args - The AccountDeleteEventArgs object.
- private void OnAccountDelete(Hooks.AccountDeleteEventArgs args)
- {
- CharacterDB.RemovePlayer(args.Account.ID);
- }
-
- /// OnAccountCreate - Internal hook fired on account creation.
- /// args - The AccountCreateEventArgs object.
- private void OnAccountCreate(Hooks.AccountCreateEventArgs args)
- {
- CharacterDB.SeedInitialData(UserAccounts.GetUserAccount(args.Account));
- }
-
- /// OnPlayerPreLogin - Internal hook fired when on player pre login.
- /// args - The PlayerPreLoginEventArgs object.
- private void OnPlayerPreLogin(Hooks.PlayerPreLoginEventArgs args)
- {
- if (args.Player.IsLoggedIn)
- args.Player.SaveServerCharacter();
- }
-
/// NetHooks_NameCollision - Internal hook fired when a name collision happens.
/// args - The NameCollisionEventArgs object.
private void NetHooks_NameCollision(NameCollisionEventArgs args)
@@ -922,20 +904,6 @@ private void OnUpdate(EventArgs args)
OnSecondUpdate();
LastCheck = DateTime.UtcNow;
}
-
- if (Main.ServerSideCharacter && (DateTime.UtcNow - LastSave).TotalMinutes >= ServerSideCharacterConfig.ServerSideCharacterSave)
- {
- foreach (TSPlayer player in Players)
- {
- // prevent null point exceptions
- if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
- {
-
- CharacterDB.InsertPlayerData(player);
- }
- }
- LastSave = DateTime.UtcNow;
- }
}
/// OnSecondUpdate - Called effectively every second for all time based checks.
@@ -1080,6 +1048,7 @@ private void OnSecondUpdate()
}
Bouncer.OnSecondUpdate();
+ SSC.OnSecondUpdate();
Utils.SetConsoleTitle(false);
}
@@ -1298,12 +1267,6 @@ private void OnLeave(LeaveEventArgs args)
Utils.Broadcast(tsplr.Name + " has left.", Color.Yellow);
Log.Info("{0} disconnected.", tsplr.Name);
- if (tsplr.IsLoggedIn && !tsplr.IsDisabledPendingTrashRemoval && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2))
- {
- tsplr.PlayerData.CopyCharacter(tsplr);
- CharacterDB.InsertPlayerData(tsplr);
- }
-
if (Config.RememberLeavePos && !tsplr.LoginHarassed)
{
RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int)(tsplr.X / 16), (int)(tsplr.Y / 16));
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index 495127c83..2b2e77c01 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -111,9 +111,14 @@
-
-
+
+
+
+
+
+
+
@@ -223,7 +228,7 @@
-
+