diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index df3c43b5..460d4811 100755 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -298,6 +298,10 @@ namespace TShockAPI { HelpText = "Saves all serverside characters." }); + add(new Command(Permissions.uploaddata, UploadJoinData, "uploadssc") + { + HelpText = "Upload the account information when you joined the server as your Server Side Character data." + }); add(new Command(Permissions.settempgroup, TempGroup, "tempgroup") { HelpText = "Temporarily sets another player's group." @@ -1686,6 +1690,63 @@ namespace TShockAPI args.Player.SendSuccessMessage("SSC of player \"{0}\" has been overriden.", matchedPlayer.Name); } + private static void UploadJoinData(CommandArgs args) + { + TSPlayer targetPlayer = args.Player; + if (args.Parameters.Count == 1 && args.Player.HasPermission(Permissions.uploadothersdata)) + { + List players = TShock.Utils.FindPlayer(args.Parameters[0]); + if (players.Count > 1) + { + TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name)); + return; + } + else if (players.Count == 0) + { + args.Player.SendErrorMessage("No player was found matching'{0}'", args.Parameters[0]); + return; + } + else + { + targetPlayer = players[0]; + } + } + else if (args.Parameters.Count == 1) + { + args.Player.SendErrorMessage("You do not have permission to upload another player's data."); + return; + } + else if (args.Parameters.Count > 0) + { + args.Player.SendErrorMessage("Usage: /uploadssc [playername]"); + return; + } + else if (args.Parameters.Count == 0 && args.Player is TSServerPlayer) + { + args.Player.SendErrorMessage("A console can not upload their player data."); + args.Player.SendErrorMessage("Usage: /uploadssc [playername]"); + return; + } + + if (targetPlayer.IsLoggedIn) + { + if (TShock.CharacterDB.InsertSpecificPlayerData(targetPlayer, targetPlayer.DataWhenJoined)) + { + targetPlayer.DataWhenJoined.RestoreCharacter(targetPlayer); + targetPlayer.SendSuccessMessage("Your Join Data has been uploaded to the server."); + args.Player.SendSuccessMessage("The player's data was successfully uploaded."); + } + else + { + args.Player.SendErrorMessage("Failed to upload your data, are you logged in to an account?"); + } + } + else + { + args.Player.SendErrorMessage("The target player has not logged in yet."); + } + } + private static void ForceHalloween(CommandArgs args) { TShock.Config.ForceHalloween = !TShock.Config.ForceHalloween; diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs index e2461205..57f28560 100755 --- a/TShockAPI/DB/CharacterManager.cs +++ b/TShockAPI/DB/CharacterManager.cs @@ -131,15 +131,15 @@ namespace TShockAPI.DB 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);", + database.Query("INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, spawnX, spawnY, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8);", user.ID, TShock.ServerSideCharacterConfig.StartingHealth, TShock.ServerSideCharacterConfig.StartingHealth, TShock.ServerSideCharacterConfig.StartingMana, - TShock.ServerSideCharacterConfig.StartingMana, - initialItems, - -1, - -1, + TShock.ServerSideCharacterConfig.StartingMana, + initialItems, + -1, + -1, 0); return true; } @@ -159,7 +159,7 @@ namespace TShockAPI.DB public bool InsertPlayerData(TSPlayer player) { PlayerData playerData = player.PlayerData; - + if (!player.IsLoggedIn) return false; @@ -219,5 +219,95 @@ namespace TShockAPI.DB return false; } + + /// + /// Inserts a specific PlayerData into the SSC table for a player. + /// + /// The player to store the data for. + /// The player data to store. + /// If the command succeeds. + public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) + { + PlayerData playerData = data; + + if (!player.IsLoggedIn) + return false; + + if (player.HasPermission(Permissions.bypassssc)) + { + TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.User.Name); // Debug code + return true; + } + + if (!GetPlayerData(player, player.User.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.User.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.User.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; + } } } diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index aae3839b..fd9ce50a 100755 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1506,6 +1506,8 @@ namespace TShockAPI private static bool HandleConnecting(GetDataHandlerArgs args) { var user = TShock.Users.GetUserByName(args.Player.Name); + args.Player.DataWhenJoined = new PlayerData(args.Player); + args.Player.DataWhenJoined.CopyCharacter(args.Player); if (user != null && !TShock.Config.DisableUUIDLogin) { diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index 2f297f5d..8a80879f 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -88,6 +88,12 @@ namespace TShockAPI [Description("User can save all the players SSI state.")] public static readonly string savessc = "tshock.admin.savessi"; + [Description("User can upload their joined character data as SSC data.")] + public static readonly string uploaddata = "tshock.ssc.upload"; + + [Description("User can upload other players join data to the SSC database.")] + public static readonly string uploadothersdata = "tshock.ssc.upload.others"; + [Description("User can elevate other users' groups temporarily.")] public static readonly string settempgroup = "tshock.admin.tempgroup"; diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index ccea8c27..ed496ac2 100755 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -73,7 +73,7 @@ namespace TShockAPI /// The amount of tiles that the player has killed in the last second. /// public int TileKillThreshold { get; set; } - + /// /// The amount of tiles the player has placed in the last second. /// @@ -113,10 +113,10 @@ namespace TShockAPI /// A system to delay Remembered Position Teleports a few seconds /// public int RPPending = 0; - + public int sX = -1; public int sY = -1; - + /// /// A queue of tiles destroyed by the player for reverting. /// @@ -145,7 +145,7 @@ namespace TShockAPI /// The player's temporary group. This overrides the user's actual group. /// public Group tempGroup = null; - + public Timer tempGroupTimer; private Group group = null; @@ -158,7 +158,7 @@ namespace TShockAPI public int Index { get; protected set; } /// - /// The last time the player changed their team or pvp status. + /// The last time the player changed their team or pvp status. /// public DateTime LastPvPTeamChange; @@ -175,7 +175,7 @@ namespace TShockAPI /// /// A list of command callbacks indexed by the command they need to do. /// - public Dictionary> AwaitingResponse; + public Dictionary> AwaitingResponse; public bool AwaitingName { get; set; } @@ -314,14 +314,14 @@ namespace TShockAPI /// Spawn protection message cool down. /// public long SPm = 1; - + /// /// Permission to build message cool down. /// public long BPm = 1; /// - /// The time in ms when the player has logged in. + /// The time in ms when the player has logged in. /// public long LoginMS; @@ -354,7 +354,7 @@ namespace TShockAPI /// Contains data stored by plugins /// protected ConcurrentDictionary data = new ConcurrentDictionary(); - + /// /// Whether the player is a real, human, player on the server. /// @@ -573,6 +573,11 @@ namespace TShockAPI } } + /// + /// 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. /// @@ -727,7 +732,7 @@ namespace TShockAPI /// Spawns the player at his spawn point. /// public void Spawn() - { + { if (this.sX > 0 && this.sY > 0) { Spawn(this.sX, this.sY); @@ -839,7 +844,7 @@ namespace TShockAPI /// True or false, depending if the item passed the check or not. public bool GiveItemCheck(int type, string name, int width, int height, int stack, int prefix = 0) { - if ((TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) && + if ((TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) && (TShock.Itembans.ItemIsBanned(name, this) || !TShock.Config.AllowAllowedGroupsToSpawnBannedItems)) return false; @@ -955,7 +960,7 @@ namespace TShockAPI } /// - /// Sends a message with the specified color. + /// Sends a message with the specified color. /// /// The message. /// The message color.