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 @@ - +