diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 161834318..98cc639d1 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -629,6 +629,24 @@ public static void InitCommands() { HelpText = GetString("Shows the server's rules.") }); + add(new Command(ShowDeath, "death") + { + HelpText = GetString("Shows your number of deaths."), + AllowServer = false + }); + add(new Command(ShowPVPDeath, "pvpdeath") + { + HelpText = GetString("Shows your number of PVP deaths."), + AllowServer = false + }); + add(new Command(ShowAllDeath, "alldeath") + { + HelpText = GetString("Shows the number of deaths for all online players."), + }); + add(new Command(ShowAllPVPDeath, "allpvpdeath") + { + HelpText = GetString("Shows the number of PVP deaths for all online players."), + }); TShockCommands = new ReadOnlyCollection(tshockCommands); } @@ -5815,6 +5833,49 @@ private static void SyncLocalArea(CommandArgs args) return; } + private static void ShowDeath(CommandArgs args) + { + args.Player.SendErrorMessage(GetString($"*You were slain {args.Player.DeathsPVE} times.")); + } + + private static void ShowPVPDeath(CommandArgs args) + { + args.Player.SendErrorMessage(GetString($"*You were slain by other players {args.Player.DeathsPVP} times.")); + } + + private static void ShowAllDeath(CommandArgs args) + { + if (TShock.Utils.GetActivePlayerCount() == 0) + { + args.Player.SendErrorMessage(GetString("There are currently no players online.")); + return; + } + + var deathsRank = TShock.Players + .Where(p => p is { Active: true }) + .OrderByDescending(x => x.DeathsPVE) + .Select(x => GetString($"*{x.Name} was slain {x.DeathsPVE} times.")); + + args.Player.SendErrorMessage(string.Join('\n',deathsRank)); + } + + private static void ShowAllPVPDeath(CommandArgs args) + { + if (TShock.Utils.GetActivePlayerCount() == 0) + { + args.Player.SendErrorMessage(GetString("There are currently no players online.")); + return; + } + + var deathsRank = TShock.Players + .Where(p => p is { Active: true }) + .OrderByDescending(x => x.DeathsPVP) + .Select(x => GetString($"*{x.Name} was slain by other players {x.DeathsPVP} times.")); + + args.Player.SendErrorMessage(string.Join('\n',deathsRank)); + } + + #endregion General Commands #region Game Commands diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs index 3b9890a31..db14488c4 100644 --- a/TShockAPI/DB/CharacterManager.cs +++ b/TShockAPI/DB/CharacterManager.cs @@ -69,7 +69,9 @@ public CharacterManager(IDbConnection db) new SqlColumn("usedGummyWorm", MySqlDbType.Int32), new SqlColumn("usedAmbrosia", MySqlDbType.Int32), new SqlColumn("unlockedSuperCart", MySqlDbType.Int32), - new SqlColumn("enabledSuperCart", MySqlDbType.Int32) + new SqlColumn("enabledSuperCart", MySqlDbType.Int32), + new SqlColumn("deathsPVE", MySqlDbType.Int32), + new SqlColumn("deathsPVP", MySqlDbType.Int32) ); SqlTableCreator creator = new(db, db.GetSqlQueryBuilder()); @@ -132,6 +134,8 @@ public PlayerData GetPlayerData(TSPlayer player, int acctid) playerData.usedAmbrosia = reader.Get("usedAmbrosia"); playerData.unlockedSuperCart = reader.Get("unlockedSuperCart"); playerData.enabledSuperCart = reader.Get("enabledSuperCart"); + playerData.deathsPVE = reader.Get("deathsPVE"); + playerData.deathsPVP = reader.Get("deathsPVP"); return playerData; } } @@ -200,8 +204,8 @@ public bool InsertPlayerData(TSPlayer player, bool fromCommand = false) 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, usingBiomeTorches, happyFunTorchTime, unlockedBiomeTorches, currentLoadoutIndex,ateArtisanBread, usedAegisCrystal, usedAegisFruit, usedArcaneCrystal, usedGalaxyPearl, usedGummyWorm, usedAmbrosia, unlockedSuperCart, enabledSuperCart) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33);", - 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, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0); + "INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted, usingBiomeTorches, happyFunTorchTime, unlockedBiomeTorches, currentLoadoutIndex,ateArtisanBread, usedAegisCrystal, usedAegisFruit, usedArcaneCrystal, usedGalaxyPearl, usedGummyWorm, usedAmbrosia, unlockedSuperCart, enabledSuperCart, deathsPVE, deathsPVP) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33, @34, @35);", + 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, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0, player.sscDeathsPVE, player.sscDeathsPVP); return true; } catch (Exception ex) @@ -214,8 +218,8 @@ public bool InsertPlayerData(TSPlayer player, bool fromCommand = false) 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, usingBiomeTorches = @21, happyFunTorchTime = @22, unlockedBiomeTorches = @23, currentLoadoutIndex = @24, ateArtisanBread = @25, usedAegisCrystal = @26, usedAegisFruit = @27, usedArcaneCrystal = @28, usedGalaxyPearl = @29, usedGummyWorm = @30, usedAmbrosia = @31, unlockedSuperCart = @32, enabledSuperCart = @33 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, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0); + "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, usingBiomeTorches = @21, happyFunTorchTime = @22, unlockedBiomeTorches = @23, currentLoadoutIndex = @24, ateArtisanBread = @25, usedAegisCrystal = @26, usedAegisFruit = @27, usedArcaneCrystal = @28, usedGalaxyPearl = @29, usedGummyWorm = @30, usedAmbrosia = @31, unlockedSuperCart = @32, enabledSuperCart = @33, deathsPVE = @34, deathsPVP = @35 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, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0, player.sscDeathsPVE, player.sscDeathsPVP); return true; } catch (Exception ex) @@ -270,7 +274,7 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) 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, usingBiomeTorches, happyFunTorchTime, unlockedBiomeTorches, currentLoadoutIndex, ateArtisanBread, usedAegisCrystal, usedAegisFruit, usedArcaneCrystal, usedGalaxyPearl, usedGummyWorm, usedAmbrosia, unlockedSuperCart, enabledSuperCart) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33);", + "INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted, usingBiomeTorches, happyFunTorchTime, unlockedBiomeTorches, currentLoadoutIndex, ateArtisanBread, usedAegisCrystal, usedAegisFruit, usedArcaneCrystal, usedGalaxyPearl, usedGummyWorm, usedAmbrosia, unlockedSuperCart, enabledSuperCart) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33, @34, @35);", player.Account.ID, playerData.health, playerData.maxHealth, @@ -304,7 +308,10 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) playerData.usedGummyWorm, playerData.usedAmbrosia, playerData.unlockedSuperCart, - playerData.enabledSuperCart); + playerData.enabledSuperCart, + playerData.deathsPVE, + playerData.deathsPVP + ); return true; } catch (Exception ex) @@ -317,7 +324,7 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) 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, usingBiomeTorches = @21, happyFunTorchTime = @22, unlockedBiomeTorches = @23, currentLoadoutIndex = @24, ateArtisanBread = @25, usedAegisCrystal = @26, usedAegisFruit = @27, usedArcaneCrystal = @28, usedGalaxyPearl = @29, usedGummyWorm = @30, usedAmbrosia = @31, unlockedSuperCart = @32, enabledSuperCart = @33 WHERE Account = @5;", + "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, usingBiomeTorches = @21, happyFunTorchTime = @22, unlockedBiomeTorches = @23, currentLoadoutIndex = @24, ateArtisanBread = @25, usedAegisCrystal = @26, usedAegisFruit = @27, usedArcaneCrystal = @28, usedGalaxyPearl = @29, usedGummyWorm = @30, usedAmbrosia = @31, unlockedSuperCart = @32, enabledSuperCart = @33, deathsPVE = @34, deathsPVP = @35 WHERE Account = @5;", playerData.health, playerData.maxHealth, playerData.mana, @@ -351,7 +358,10 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) playerData.usedGummyWorm, playerData.usedAmbrosia, playerData.unlockedSuperCart, - playerData.enabledSuperCart); + playerData.enabledSuperCart, + playerData.deathsPVE, + playerData.deathsPVP + ); return true; } catch (Exception ex) diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 6ed0f8de4..fc6cebfef 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -4325,6 +4325,15 @@ private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) args.Player.Dead = true; args.Player.RespawnTimer = TShock.Config.Settings.RespawnSeconds; + if (Main.ServerSideCharacter && !args.Player.HasPermission(Permissions.bypassssc)) + { + if (pvp) + { + args.Player.sscDeathsPVP++; + } + args.Player.sscDeathsPVE++; + } + foreach (NPC npc in Main.npc) { if (npc.active && (npc.boss || npc.type == 13 || npc.type == 14 || npc.type == 15) && diff --git a/TShockAPI/PlayerData.cs b/TShockAPI/PlayerData.cs index c84da7c71..1ab5430fa 100644 --- a/TShockAPI/PlayerData.cs +++ b/TShockAPI/PlayerData.cs @@ -63,6 +63,8 @@ public class PlayerData public int usedAmbrosia; public int unlockedSuperCart; public int enabledSuperCart; + public int deathsPVE; + public int deathsPVP; /// /// Sets the default values for the inventory. @@ -152,6 +154,8 @@ public void CopyCharacter(TSPlayer player) this.usedAmbrosia = player.TPlayer.usedAmbrosia ? 1 : 0; this.unlockedSuperCart = player.TPlayer.unlockedSuperCart ? 1 : 0; this.enabledSuperCart = player.TPlayer.enabledSuperCart ? 1 : 0; + this.deathsPVE = player.TPlayer.numberOfDeathsPVE; + this.deathsPVP = player.TPlayer.numberOfDeathsPVP; Item[] inventory = player.TPlayer.inventory; Item[] armor = player.TPlayer.armor; @@ -293,6 +297,8 @@ public void RestoreCharacter(TSPlayer player) player.TPlayer.usedAmbrosia = this.usedAmbrosia == 1; player.TPlayer.unlockedSuperCart = this.unlockedSuperCart == 1; player.TPlayer.enabledSuperCart = this.enabledSuperCart == 1; + player.sscDeathsPVE = this.deathsPVE; + player.sscDeathsPVP = this.deathsPVP; if (extraSlot != null) player.TPlayer.extraAccessory = extraSlot.Value == 1 ? true : false; diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index af009fa71..52d319fcc 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -401,6 +401,48 @@ public int RespawnTimer /// Determines if the player has finished the handshake (Sent all necessary packets for connection, such as Request World Data, Spawn Player, etc). A normal client would do all of this no problem. public bool FinishedHandshake = false; + /// Server-side character's recorded death count. + public int sscDeathsPVE = 0; + + /// Server-side character's recorded PVP death count. + public int sscDeathsPVP = 0; + + /// + /// Gets the player's total death count. + /// If server-side characters are enabled and player doesn't have bypass permission, + /// returns the server-stored value; otherwise returns the client's value. + /// + public int DeathsPVE + { + get + { + if (Main.ServerSideCharacter && !this.HasPermission(Permissions.bypassssc)) + { + return sscDeathsPVE; + } + + return this.TPlayer.numberOfDeathsPVE; + } + } + + /// + /// Gets the player's total PVP death count. + /// If server-side characters are enabled and player doesn't have bypass permission, + /// returns the server-stored value; otherwise returns the client's value. + /// + public int DeathsPVP + { + get + { + if (Main.ServerSideCharacter && !this.HasPermission(Permissions.bypassssc)) + { + return sscDeathsPVP; + } + + return this.TPlayer.numberOfDeathsPVP; + } + } + /// Checks to see if active throttling is happening on events by Bouncer. Rejects repeated events by malicious clients in a short window. /// If the player is currently being throttled by Bouncer, or not. public bool IsBouncerThrottled()