From 4d35ee395ee0e54e50c09b6dc7a409e368b95936 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 22 Oct 2022 14:01:17 -0700 Subject: [PATCH 1/7] Version tick: 5.0.0-beta.0.6 --- TShockAPI/TShockAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 999ed882..b549a46c 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -18,7 +18,7 @@ Also, be sure to release on github with the exact assembly version tag as below so that the update manager works correctly (via the Github releases api and mimic) --> - 5.0.0-beta.0.5 + 5.0.0-beta.0.6 TShock for Terraria Pryaxis & TShock Contributors TShockAPI From 3163c88d4a31c0c0cc61763aace3ef536006be92 Mon Sep 17 00:00:00 2001 From: James Puleo Date: Wed, 5 Oct 2022 05:32:27 -0400 Subject: [PATCH 2/7] Allow `PacketTypes.SyncLoadout` during early connection This mirrors the behavior in `MessageBuffer` when only accepting specific packets under state `10`. --- TShockAPI/TShock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index a2c52ad3..d3fadac3 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1605,7 +1605,7 @@ namespace TShockAPI } if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 && - (int)type != 38 && (int)type != 21 && (int)type != 22) + (int)type != 38 && (int)type != 21 && (int)type != 22 && type != PacketTypes.SyncLoadout) { e.Handled = true; return; From bfaa47ad1abf89aeb1e132a04befb3944328583f Mon Sep 17 00:00:00 2001 From: James Puleo Date: Wed, 5 Oct 2022 05:43:24 -0400 Subject: [PATCH 3/7] Introduce support for loadouts, and save current loadout index to SSC We needed to modify `NetItem` to know that these new inventory now exist. `PlayerData` can now re/store these items, and properly sync them. It also now knows of the player's currently selected index, and how to sync it. --- TShockAPI/Commands.cs | 27 +++++ TShockAPI/DB/CharacterManager.cs | 22 ++-- TShockAPI/GetDataHandlers.cs | 66 +++++++++++- TShockAPI/NetItem.cs | 23 +++- TShockAPI/PlayerData.cs | 176 ++++++++++++++++++++++++++++++- 5 files changed, 300 insertions(+), 14 deletions(-) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index dc5bb180..e15f14af 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -779,6 +779,33 @@ namespace TShockAPI return; } + // We need to emulate the checks done in Player.TrySwitchingLoadout, because otherwise the server is not allowed to sync the + // loadout index to the player, causing catastrophic desync. + // The player must not be dead, using an item, or CC'd to switch loadouts. + // FIXME: There is always the chance that in-between the time we check these requirements on the server, and the loadout sync + // packet reaches the client, that the client state has changed, causing the loadout sync to be rejected, even though + // we expected it to succeed. + + if (args.TPlayer.dead) + { + args.Player.SendErrorMessage(GetString("You cannot login whilst dead.")); + return; + } + + // FIXME: This check is not correct -- even though we reject PlayerAnimation whilst disabled, we don't re-sync it to the client, + // meaning these will still be set on the client, and they WILL reject the loadout sync. + if (args.TPlayer.itemTime > 0 || args.TPlayer.itemAnimation > 0) + { + args.Player.SendErrorMessage(GetString("You cannot login whilst using an item.")); + return; + } + + if (args.TPlayer.CCed) + { + args.Player.SendErrorMessage(GetString("You cannot login whilst crowd controlled.")); + return; + } + UserAccount account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name); string password = ""; bool usingUUID = false; diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs index 4e2e07d1..4b43dcc1 100644 --- a/TShockAPI/DB/CharacterManager.cs +++ b/TShockAPI/DB/CharacterManager.cs @@ -58,7 +58,8 @@ namespace TShockAPI.DB new SqlColumn("questsCompleted", MySqlDbType.Int32), new SqlColumn("usingBiomeTorches", MySqlDbType.Int32), new SqlColumn("happyFunTorchTime", MySqlDbType.Int32), - new SqlColumn("unlockedBiomeTorches", MySqlDbType.Int32) + new SqlColumn("unlockedBiomeTorches", MySqlDbType.Int32), + new SqlColumn("currentLoadoutIndex", MySqlDbType.Int32) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite @@ -114,6 +115,7 @@ namespace TShockAPI.DB playerData.usingBiomeTorches = reader.Get("usingBiomeTorches"); playerData.happyFunTorchTime = reader.Get("happyFunTorchTime"); playerData.unlockedBiomeTorches = reader.Get("unlockedBiomeTorches"); + playerData.currentLoadoutIndex = reader.Get("currentLoadoutIndex"); return playerData; } } @@ -180,8 +182,8 @@ namespace TShockAPI.DB 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) 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);", - 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); + "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) 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);", + 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); return true; } catch (Exception ex) @@ -194,8 +196,8 @@ namespace TShockAPI.DB 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 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); + "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 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); return true; } catch (Exception ex) @@ -250,7 +252,7 @@ namespace TShockAPI.DB 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) 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);", + "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) 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);", player.Account.ID, playerData.health, playerData.maxHealth, @@ -274,7 +276,8 @@ namespace TShockAPI.DB playerData.questsCompleted, playerData.usingBiomeTorches, playerData.happyFunTorchTime, - playerData.unlockedBiomeTorches); + playerData.unlockedBiomeTorches, + playerData.currentLoadoutIndex); return true; } catch (Exception ex) @@ -287,7 +290,7 @@ namespace TShockAPI.DB 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 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 WHERE Account = @5;", playerData.health, playerData.maxHealth, playerData.mana, @@ -311,7 +314,8 @@ namespace TShockAPI.DB playerData.extraSlot ?? 0, playerData.usingBiomeTorches, playerData.happyFunTorchTime, - playerData.unlockedBiomeTorches); + playerData.unlockedBiomeTorches, + playerData.currentLoadoutIndex); return true; } catch (Exception ex) diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 0929d928..22feee47 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -4350,14 +4350,76 @@ namespace TShockAPI if (loadoutIndex == args.TPlayer.CurrentLoadoutIndex) return false; - if (args.Player.IsBeingDisabled()) + if (loadoutIndex >= args.TPlayer.Loadouts.Length) { - TShock.Log.ConsoleDebug("GetDataHandlers / HandleSyncLoadout rejected loadout index sync {0}", args.Player.Name); + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleSyncLoadout rejected loadout index sync out of bounds {0}", + args.Player.Name)); NetMessage.SendData((int)PacketTypes.SyncLoadout, number: args.Player.Index, number2: args.TPlayer.CurrentLoadoutIndex); return true; } + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleSyncLoadout rejected loadout index sync {0}", args.Player.Name)); + NetMessage.SendData((int)PacketTypes.SyncLoadout, number: args.Player.Index, number2: args.TPlayer.CurrentLoadoutIndex); + + return true; + } + + // The client does not sync slot changes when changing loadouts, it only tells the server the loadout index changed, + // and the server will replicate the changes the client did. This means that PlayerData.StoreSlot is never called, so we need to + // swap around the PlayerData items ourself. + + Tuple GetArmorSlotsForLoadoutIndex(int index) + { + return index switch + { + 0 => NetItem.Loadout1Armor, + 1 => NetItem.Loadout2Armor, + 2 => NetItem.Loadout3Armor + }; + } + + Tuple GetDyeSlotsForLoadoutIndex(int index) + { + return index switch + { + 0 => NetItem.Loadout1Dye, + 1 => NetItem.Loadout2Dye, + 2 => NetItem.Loadout3Dye + }; + } + + var (currentLoadoutArmorSlotStartIndex, _) = GetArmorSlotsForLoadoutIndex(args.TPlayer.CurrentLoadoutIndex); + var (currentLoadoutDyeSlotStartIndex, _) = GetDyeSlotsForLoadoutIndex(args.TPlayer.CurrentLoadoutIndex); + + var (switchedLoadoutArmorSlotStartIndex, _) = GetArmorSlotsForLoadoutIndex(loadoutIndex); + var (switchedLoadoutDyeSlotStartIndex, _) = GetDyeSlotsForLoadoutIndex(loadoutIndex); + + // Emulate what is seen in Player.TrySwitchingLoadout: + // - Swap the current loadout items with the player's equipment + // - Swap the switching loadout items with the player's equipment + + // At the end of all of this: + // - The current loadout will contain the player's original equipment + // - The switched loadout will contain the current loadout's items + // - The player's equipment will contain the switched loadout's item + + for (var i = 0; i < NetItem.LoadoutArmorSlots; i++) + Terraria.Utils.Swap(ref args.Player.PlayerData.inventory[currentLoadoutArmorSlotStartIndex + i], + ref args.Player.PlayerData.inventory[NetItem.ArmorIndex.Item1 + i]); + for (var i = 0; i < NetItem.LoadoutDyeSlots; i++) + Terraria.Utils.Swap(ref args.Player.PlayerData.inventory[currentLoadoutDyeSlotStartIndex + i], + ref args.Player.PlayerData.inventory[NetItem.DyeIndex.Item1 + i]); + + for (var i = 0; i < NetItem.LoadoutArmorSlots; i++) + Terraria.Utils.Swap(ref args.Player.PlayerData.inventory[switchedLoadoutArmorSlotStartIndex + i], + ref args.Player.PlayerData.inventory[NetItem.ArmorIndex.Item1 + i]); + for (var i = 0; i < NetItem.LoadoutDyeSlots; i++) + Terraria.Utils.Swap(ref args.Player.PlayerData.inventory[switchedLoadoutDyeSlotStartIndex + i], + ref args.Player.PlayerData.inventory[NetItem.DyeIndex.Item1 + i]); + return false; } diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 13ac0989..278fda1c 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -81,10 +81,22 @@ namespace TShockAPI /// public static readonly int TrashSlots = 1; + /// + /// The number of armor slots in a loadout. + /// + public static readonly int LoadoutArmorSlots = ArmorSlots; + + /// + /// The number of dye slots in a loadout. + /// + public static readonly int LoadoutDyeSlots = DyeSlots; + /// /// 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 int MaxInventory = InventorySlots + ArmorSlots + DyeSlots + MiscEquipSlots + MiscDyeSlots + PiggySlots + + SafeSlots + ForgeSlots + VoidSlots + TrashSlots + (LoadoutArmorSlots * 3) + + (LoadoutDyeSlots * 3); public static readonly Tuple InventoryIndex = new Tuple(0, InventorySlots); public static readonly Tuple ArmorIndex = new Tuple(InventoryIndex.Item2, InventoryIndex.Item2 + ArmorSlots); @@ -97,6 +109,15 @@ namespace TShockAPI public static readonly Tuple ForgeIndex = new Tuple(TrashIndex.Item2, TrashIndex.Item2 + ForgeSlots); public static readonly Tuple VoidIndex = new Tuple(ForgeIndex.Item2, ForgeIndex.Item2 + VoidSlots); + public static readonly Tuple Loadout1Armor = new Tuple(VoidIndex.Item2, VoidIndex.Item2 + LoadoutArmorSlots); + public static readonly Tuple Loadout1Dye = new Tuple(Loadout1Armor.Item2, Loadout1Armor.Item2 + LoadoutDyeSlots); + + public static readonly Tuple Loadout2Armor = new Tuple(Loadout1Dye.Item2, Loadout1Dye.Item2 + LoadoutArmorSlots); + public static readonly Tuple Loadout2Dye = new Tuple(Loadout2Armor.Item2, Loadout2Armor.Item2 + LoadoutDyeSlots); + + public static readonly Tuple Loadout3Armor = new Tuple(Loadout2Dye.Item2, Loadout2Dye.Item2 + LoadoutArmorSlots); + public static readonly Tuple Loadout3Dye = new Tuple(Loadout3Armor.Item2, Loadout3Armor.Item2 + LoadoutDyeSlots); + [JsonProperty("netID")] private int _netId; [JsonProperty("prefix")] diff --git a/TShockAPI/PlayerData.cs b/TShockAPI/PlayerData.cs index 5e24570b..eec2b60b 100644 --- a/TShockAPI/PlayerData.cs +++ b/TShockAPI/PlayerData.cs @@ -52,6 +52,7 @@ namespace TShockAPI public int usingBiomeTorches; public int happyFunTorchTime; public int unlockedBiomeTorches; + public int currentLoadoutIndex; public PlayerData(TSPlayer player) { @@ -120,6 +121,7 @@ namespace TShockAPI this.usingBiomeTorches = player.TPlayer.UsingBiomeTorches ? 1 : 0; this.happyFunTorchTime = player.TPlayer.happyFunTorchTime ? 1 : 0; this.unlockedBiomeTorches = player.TPlayer.unlockedBiomeTorches ? 1 : 0; + this.currentLoadoutIndex = player.TPlayer.CurrentLoadoutIndex; Item[] inventory = player.TPlayer.inventory; Item[] armor = player.TPlayer.armor; @@ -131,6 +133,12 @@ namespace TShockAPI Item[] forge = player.TPlayer.bank3.item; Item[] voidVault = player.TPlayer.bank4.item; Item trash = player.TPlayer.trashItem; + Item[] loadout1Armor = player.TPlayer.Loadouts[0].Armor; + Item[] loadout1Dye = player.TPlayer.Loadouts[0].Dye; + Item[] loadout2Armor = player.TPlayer.Loadouts[1].Armor; + Item[] loadout2Dye = player.TPlayer.Loadouts[1].Dye; + Item[] loadout3Armor = player.TPlayer.Loadouts[2].Armor; + Item[] loadout3Dye = player.TPlayer.Loadouts[2].Dye; for (int i = 0; i < NetItem.MaxInventory; i++) { @@ -186,12 +194,42 @@ namespace TShockAPI var index = i - NetItem.ForgeIndex.Item1; this.inventory[i] = (NetItem)forge[index]; } - else + else if(i < NetItem.VoidIndex.Item2) { //220 var index = i - NetItem.VoidIndex.Item1; this.inventory[i] = (NetItem)voidVault[index]; } + else if(i < NetItem.Loadout1Armor.Item2) + { + var index = i - NetItem.Loadout1Armor.Item1; + this.inventory[i] = (NetItem)loadout1Armor[index]; + } + else if(i < NetItem.Loadout1Dye.Item2) + { + var index = i - NetItem.Loadout1Dye.Item1; + this.inventory[i] = (NetItem)loadout1Dye[index]; + } + else if(i < NetItem.Loadout2Armor.Item2) + { + var index = i - NetItem.Loadout2Armor.Item1; + this.inventory[i] = (NetItem)loadout2Armor[index]; + } + else if(i < NetItem.Loadout2Dye.Item2) + { + var index = i - NetItem.Loadout2Dye.Item1; + this.inventory[i] = (NetItem)loadout2Dye[index]; + } + else if(i < NetItem.Loadout3Armor.Item2) + { + var index = i - NetItem.Loadout3Armor.Item1; + this.inventory[i] = (NetItem)loadout3Armor[index]; + } + else if(i < NetItem.Loadout3Dye.Item2) + { + var index = i - NetItem.Loadout3Dye.Item1; + this.inventory[i] = (NetItem)loadout3Dye[index]; + } } } @@ -217,6 +255,7 @@ namespace TShockAPI player.TPlayer.UsingBiomeTorches = this.usingBiomeTorches == 1; player.TPlayer.happyFunTorchTime = this.happyFunTorchTime == 1; player.TPlayer.unlockedBiomeTorches = this.unlockedBiomeTorches == 1; + player.TPlayer.CurrentLoadoutIndex = this.currentLoadoutIndex; if (extraSlot != null) player.TPlayer.extraAccessory = extraSlot.Value == 1 ? true : false; @@ -353,7 +392,7 @@ namespace TShockAPI player.TPlayer.bank3.item[index].Prefix((byte)this.inventory[i].PrefixId); } } - else + else if (i < NetItem.VoidIndex.Item2) { //260 var index = i - NetItem.VoidIndex.Item1; @@ -365,8 +404,81 @@ namespace TShockAPI player.TPlayer.bank4.item[index].Prefix((byte)this.inventory[i].PrefixId); } } + else if (i < NetItem.Loadout1Armor.Item2) + { + var index = i - NetItem.Loadout1Armor.Item1; + player.TPlayer.Loadouts[0].Armor[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[0].Armor[index].netID != 0) + { + player.TPlayer.Loadouts[0].Armor[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[0].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + } + } + else if (i < NetItem.Loadout1Dye.Item2) + { + var index = i - NetItem.Loadout1Dye.Item1; + player.TPlayer.Loadouts[0].Dye[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[0].Dye[index].netID != 0) + { + player.TPlayer.Loadouts[0].Dye[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[0].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + } + } + else if (i < NetItem.Loadout2Armor.Item2) + { + var index = i - NetItem.Loadout2Armor.Item1; + player.TPlayer.Loadouts[1].Armor[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[1].Armor[index].netID != 0) + { + player.TPlayer.Loadouts[1].Armor[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[1].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + } + } + else if (i < NetItem.Loadout2Dye.Item2) + { + var index = i - NetItem.Loadout2Dye.Item1; + player.TPlayer.Loadouts[1].Dye[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[1].Dye[index].netID != 0) + { + player.TPlayer.Loadouts[1].Dye[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[1].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + } + } + else if (i < NetItem.Loadout3Armor.Item2) + { + var index = i - NetItem.Loadout3Armor.Item1; + player.TPlayer.Loadouts[2].Armor[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[2].Armor[index].netID != 0) + { + player.TPlayer.Loadouts[2].Armor[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[2].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + } + } + else if (i < NetItem.Loadout3Dye.Item2) + { + var index = i - NetItem.Loadout3Dye.Item1; + player.TPlayer.Loadouts[2].Dye[index].netDefaults(this.inventory[i].NetId); + + if (player.TPlayer.Loadouts[2].Dye[index].netID != 0) + { + player.TPlayer.Loadouts[2].Dye[index].stack = this.inventory[i].Stack; + player.TPlayer.Loadouts[2].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + } + } } + // Just like in MessageBuffer when the client receives a ContinueConnecting, let's sync the CurrentLoadoutIndex _before_ any of + // the items. + // This is sent to everyone BUT this player, and then ONLY this player. When using UUID login, it is too soon for the server to + // broadcast packets to this client. + NetMessage.SendData((int)PacketTypes.SyncLoadout, remoteClient: player.Index, number: player.Index, number2: player.TPlayer.CurrentLoadoutIndex); + NetMessage.SendData((int)PacketTypes.SyncLoadout, ignoreClient: player.Index, number: player.Index, number2: player.TPlayer.CurrentLoadoutIndex); + float slot = 0f; for (int k = 0; k < NetItem.InventorySlots; k++) { @@ -414,6 +526,36 @@ namespace TShockAPI 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++; } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[0].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[0].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[0].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[0].Dye[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[1].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[1].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[1].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[1].Dye[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[2].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[2].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, -1, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[1].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[2].Dye[k].prefix); + slot++; + } NetMessage.SendData(4, -1, -1, NetworkText.FromLiteral(player.Name), player.Index, 0f, 0f, 0f, 0); @@ -467,6 +609,36 @@ namespace TShockAPI 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++; } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[0].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[0].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[0].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[0].Dye[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[1].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[1].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[1].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[1].Dye[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutArmorSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[2].Armor[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[2].Armor[k].prefix); + slot++; + } + for (int k = 0; k < NetItem.LoadoutDyeSlots; k++) + { + NetMessage.SendData(5, player.Index, -1, NetworkText.FromLiteral(Main.player[player.Index].Loadouts[2].Dye[k].Name), player.Index, slot, (float)Main.player[player.Index].Loadouts[2].Dye[k].prefix); + slot++; + } From 9dbe8c9e4043e98832f93806c8f5878dea397905 Mon Sep 17 00:00:00 2001 From: James Puleo Date: Wed, 5 Oct 2022 05:44:08 -0400 Subject: [PATCH 4/7] Check loadout slots for hacked item stacks --- TShockAPI/TSPlayer.cs | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 15001757..e0b09993 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -386,6 +386,12 @@ namespace TShockAPI Item[] safe = TPlayer.bank2.item; Item[] forge = TPlayer.bank3.item; Item[] voidVault = TPlayer.bank4.item; + Item[] loadout1Armor = TPlayer.Loadouts[0].Armor; + Item[] loadout1Dye = TPlayer.Loadouts[0].Dye; + Item[] loadout2Armor = TPlayer.Loadouts[1].Armor; + Item[] loadout2Dye = TPlayer.Loadouts[1].Dye; + Item[] loadout3Armor = TPlayer.Loadouts[2].Armor; + Item[] loadout3Dye = TPlayer.Loadouts[2].Dye; Item trash = TPlayer.trashItem; for (int i = 0; i < NetItem.MaxInventory; i++) @@ -594,7 +600,126 @@ namespace TShockAPI } } } + else if (i < NetItem.Loadout1Armor.Item2) + { + var index = i - NetItem.Loadout1Armor.Item1; + Item item = new Item(); + if (loadout1Armor[index] != null && loadout1Armor[index].netID != 0) + { + item.netDefaults(loadout1Armor[index].netID); + item.Prefix(loadout1Armor[index].prefix); + item.AffixName(); + if (loadout1Armor[index].stack > item.maxStack || loadout1Armor[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 1 item {0} ({1}) and then rejoin.", item.Name, loadout1Armor[index].stack)); + } + } + } + } + else if (i < NetItem.Loadout1Dye.Item2) + { + var index = i - NetItem.Loadout1Dye.Item1; + Item item = new Item(); + if (loadout1Dye[index] != null && loadout1Dye[index].netID != 0) + { + item.netDefaults(loadout1Dye[index].netID); + item.Prefix(loadout1Dye[index].prefix); + item.AffixName(); + + if (loadout1Dye[index].stack > item.maxStack || loadout1Dye[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 1 item {0} ({1}) and then rejoin.", item.Name, loadout1Dye[index].stack)); + } + } + } + } + else if (i < NetItem.Loadout2Armor.Item2) + { + var index = i - NetItem.Loadout2Armor.Item1; + Item item = new Item(); + if (loadout2Armor[index] != null && loadout2Armor[index].netID != 0) + { + item.netDefaults(loadout2Armor[index].netID); + item.Prefix(loadout2Armor[index].prefix); + item.AffixName(); + + if (loadout2Armor[index].stack > item.maxStack || loadout2Armor[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 2 item {0} ({1}) and then rejoin.", item.Name, loadout2Armor[index].stack)); + } + } + } + } + else if (i < NetItem.Loadout2Dye.Item2) + { + var index = i - NetItem.Loadout2Dye.Item1; + Item item = new Item(); + if (loadout2Dye[index] != null && loadout2Dye[index].netID != 0) + { + item.netDefaults(loadout2Dye[index].netID); + item.Prefix(loadout2Dye[index].prefix); + item.AffixName(); + + if (loadout2Dye[index].stack > item.maxStack || loadout2Dye[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 2 item {0} ({1}) and then rejoin.", item.Name, loadout2Dye[index].stack)); + } + } + } + } + else if (i < NetItem.Loadout3Armor.Item2) + { + var index = i - NetItem.Loadout3Armor.Item1; + Item item = new Item(); + if (loadout3Armor[index] != null && loadout3Armor[index].netID != 0) + { + item.netDefaults(loadout3Armor[index].netID); + item.Prefix(loadout3Armor[index].prefix); + item.AffixName(); + + if (loadout3Armor[index].stack > item.maxStack || loadout3Armor[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 3 item {0} ({1}) and then rejoin.", item.Name, loadout3Armor[index].stack)); + } + } + } + } + else if (i < NetItem.Loadout3Dye.Item2) + { + var index = i - NetItem.Loadout3Dye.Item1; + Item item = new Item(); + if (loadout3Dye[index] != null && loadout3Dye[index].netID != 0) + { + item.netDefaults(loadout3Dye[index].netID); + item.Prefix(loadout3Dye[index].prefix); + item.AffixName(); + + if (loadout3Dye[index].stack > item.maxStack || loadout3Dye[index].stack < 0) + { + check = true; + if (shouldWarnPlayer) + { + SendErrorMessage(GetString("Stack cheat detected. Remove Loadout 3 item {0} ({1}) and then rejoin.", item.Name, loadout3Dye[index].stack)); + } + } + } + } } return check; From 0a25857278f7f46d58dfcb7d433227db3d1364f0 Mon Sep 17 00:00:00 2001 From: James Puleo Date: Wed, 5 Oct 2022 05:45:42 -0400 Subject: [PATCH 5/7] Update `CHANGELOG.md` --- docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 166645b3..a5819808 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -64,6 +64,9 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added an internationalization system. The base for the i18n system was built by Janet Blackquill ([@pontaoski](https://github.com/pontaoski)). A small donation in her honor was made to the [KDE project](https://kde.org/) as a thankyou for this work. This also includes the `TSHOCK_LANGUAGE` environment variable. Setting `TSHOCK_LANGUAGE=tok` will enable a small number of [Toki Pona](https://tokipona.org/) translations as a proof-of-concept. (@pontaoski) * Added support for Terraria 1.4.4.6, through OTAPI 3.1.5. (@SignatureBeef) * Added GeoIP.dat back to the included list of files. (@SignatureBeef) +* Allow loadouts to properly sync by allowing the `SyncLoadout` packet during early connection. (@drunderscore) +* Introduced support for loadouts, and saving the current loadout index to SSC. Both `NetItem` and `PlayerData` were modified to support this. (@drunderscore) +* Check loadout slots for hacked item stacks. (@drunderscore) ## TShock 4.5.18 * Fixed `TSPlayer.GiveItem` not working if the player is in lava. (@PotatoCider) From 45380d29534ef4e0929341463a0b2958f9ccc393 Mon Sep 17 00:00:00 2001 From: Cardinal System Date: Sat, 22 Oct 2022 21:54:34 +0000 Subject: [PATCH 6/7] =?UTF-8?q?Update=20translation=20template=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/template.pot | 208 ++++++++++++++++++++++++++++------------------ 1 file changed, 125 insertions(+), 83 deletions(-) diff --git a/i18n/template.pot b/i18n/template.pot index fc1ad784..04ee80d0 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: TShock\n" -"POT-Creation-Date: 2022-10-22 08:09:52+0000\n" -"PO-Revision-Date: 2022-10-22 08:09:53+0000\n" +"POT-Creation-Date: 2022-10-22 21:54:33+0000\n" +"PO-Revision-Date: 2022-10-22 21:54:34+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -10,13 +10,13 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: GetText.NET Extractor\n" -#: ../../TShockAPI/DB/CharacterManager.cs:174 +#: ../../TShockAPI/DB/CharacterManager.cs:176 #, csharp-format msgctxt "{0} is a player name" msgid "Skipping SSC save (due to tshock.ignore.ssc) for {0}" msgstr "" -#: ../../TShockAPI/DB/CharacterManager.cs:244 +#: ../../TShockAPI/DB/CharacterManager.cs:246 msgctxt "{0} is a player name" msgid "Skipping SSC save (due to tshock.ignore.ssc) for {player.Account.Name}" msgstr "" @@ -43,12 +43,12 @@ msgstr "" msgid "\"{0}\" requested REST endpoint: {1}" msgstr "" -#: ../../TShockAPI/Commands.cs:940 +#: ../../TShockAPI/Commands.cs:967 #, csharp-format msgid "{0} ({1}) changed the password for account {2}." msgstr "" -#: ../../TShockAPI/Commands.cs:950 +#: ../../TShockAPI/Commands.cs:977 #, csharp-format msgid "{0} ({1}) failed to change the password for account {2}." msgstr "" @@ -60,26 +60,26 @@ msgid "" "automatically." msgstr "" -#: ../../TShockAPI/Commands.cs:1070 +#: ../../TShockAPI/Commands.cs:1097 #, csharp-format msgid "{0} added account {1} to group {2}" msgstr "" -#: ../../TShockAPI/Commands.cs:1028 +#: ../../TShockAPI/Commands.cs:1055 msgid "{0} attempted to register for the account {1} but it was already taken." msgstr "" -#: ../../TShockAPI/Commands.cs:869 +#: ../../TShockAPI/Commands.cs:896 #, csharp-format msgid "{0} authenticated successfully as user: {1}." msgstr "" -#: ../../TShockAPI/Commands.cs:1144 +#: ../../TShockAPI/Commands.cs:1171 #, csharp-format msgid "{0} changed account {1} to group {2}." msgstr "" -#: ../../TShockAPI/Commands.cs:1118 +#: ../../TShockAPI/Commands.cs:1145 #, csharp-format msgid "{0} changed the password for account {1}" msgstr "" @@ -89,12 +89,12 @@ msgstr "" msgid "{0} executed: {1}{2}." msgstr "" -#: ../../TShockAPI/Commands.cs:894 +#: ../../TShockAPI/Commands.cs:921 #, csharp-format msgid "{0} failed to authenticate as user: {1}" msgstr "" -#: ../../TShockAPI/Commands.cs:1150 +#: ../../TShockAPI/Commands.cs:1177 #, csharp-format msgid "{0} has changed your group to {1}." msgstr "" @@ -111,12 +111,12 @@ msgid_plural "{killcount} NPCs have been killed." msgstr[0] "" msgstr[1] "" -#: ../../TShockAPI/Commands.cs:1022 +#: ../../TShockAPI/Commands.cs:1049 #, csharp-format msgid "{0} registered an account: \"{1}\"." msgstr "" -#: ../../TShockAPI/Commands.cs:1096 +#: ../../TShockAPI/Commands.cs:1123 #, csharp-format msgid "{0} successfully deleted account: {1}" msgstr "" @@ -126,38 +126,38 @@ msgstr "" msgid "{0} tried to execute {1}{2}." msgstr "" -#: ../../TShockAPI/Commands.cs:814 +#: ../../TShockAPI/Commands.cs:841 #, csharp-format msgid "{0}login - Logs in using your UUID and character name." msgstr "" -#: ../../TShockAPI/Commands.cs:819 +#: ../../TShockAPI/Commands.cs:846 #, csharp-format msgid "{0}login {1} - Logs in using your password and character name.password" msgstr "" -#: ../../TShockAPI/Commands.cs:817 +#: ../../TShockAPI/Commands.cs:844 #, csharp-format msgid "" "{0}login {1} {2} - Logs in using your username and password.usernamepassword" msgstr "" -#: ../../TShockAPI/Commands.cs:1169 +#: ../../TShockAPI/Commands.cs:1196 #, csharp-format msgid "{0}user add username password group -- Adds a specified user" msgstr "" -#: ../../TShockAPI/Commands.cs:1170 +#: ../../TShockAPI/Commands.cs:1197 #, csharp-format msgid "{0}user del username -- Removes a specified user" msgstr "" -#: ../../TShockAPI/Commands.cs:1172 +#: ../../TShockAPI/Commands.cs:1199 #, csharp-format msgid "{0}user group username newgroup -- Changes a user's group" msgstr "" -#: ../../TShockAPI/Commands.cs:1171 +#: ../../TShockAPI/Commands.cs:1198 #, csharp-format msgid "{0}user password username newpassword -- Changes a user's password" msgstr "" @@ -175,26 +175,26 @@ msgid "" "and is at the RESTMaximumRequestsPerInterval threshold." msgstr "" -#: ../../TShockAPI/Commands.cs:828 +#: ../../TShockAPI/Commands.cs:855 msgid "A user account by that name does not exist." msgstr "" -#: ../../TShockAPI/Commands.cs:1010 +#: ../../TShockAPI/Commands.cs:1037 #, csharp-format msgid "Account \"{0}\" has been registered." msgstr "" -#: ../../TShockAPI/Commands.cs:1123 +#: ../../TShockAPI/Commands.cs:1150 #, csharp-format msgid "Account {0} does not exist! Therefore, the password cannot be changed." msgstr "" -#: ../../TShockAPI/Commands.cs:1069 +#: ../../TShockAPI/Commands.cs:1096 #, csharp-format msgid "Account {0} has been added to group {1}!" msgstr "" -#: ../../TShockAPI/Commands.cs:1145 +#: ../../TShockAPI/Commands.cs:1172 #, csharp-format msgid "Account {0} has been changed to group {1}." msgstr "" @@ -204,7 +204,7 @@ msgstr "" msgid "Account needed! Please {0}register or {0}login to play!" msgstr "" -#: ../../TShockAPI/Commands.cs:1095 +#: ../../TShockAPI/Commands.cs:1122 msgid "Account removed successfully." msgstr "" @@ -218,7 +218,7 @@ msgstr "" msgid "AddUser SQL returned an error ({0})" msgstr "" -#: ../../TShockAPI/Commands.cs:1187 +#: ../../TShockAPI/Commands.cs:1214 #, csharp-format msgid "Allocated memory: {0}" msgstr "" @@ -259,7 +259,7 @@ msgstr "" msgid "Anonymous requested REST endpoint: {0}" msgstr "" -#: ../../TShockAPI/Commands.cs:867 +#: ../../TShockAPI/Commands.cs:894 #, csharp-format msgid "Authenticated as {0} successfully." msgstr "" @@ -293,7 +293,7 @@ msgstr "" msgid "Backup Thread" msgstr "" -#: ../../TShockAPI/Commands.cs:801 +#: ../../TShockAPI/Commands.cs:828 msgid "Bad login attempt." msgstr "" @@ -1460,6 +1460,18 @@ msgstr "" msgid "Forces all liquids to update immediately." msgstr "" +#: ../../TShockAPI/GetDataHandlers.cs:4364 +#, csharp-format +msgid "GetDataHandlers / HandleSyncLoadout rejected loadout index sync {0}" +msgstr "" + +#: ../../TShockAPI/GetDataHandlers.cs:4355 +#, csharp-format +msgid "" +"GetDataHandlers / HandleSyncLoadout rejected loadout index sync out of bounds " +"{0}" +msgstr "" + #: ../../TShockAPI/DB/UserManager.cs:291 #, csharp-format msgid "GetUser SQL returned an error {0}" @@ -1526,7 +1538,7 @@ msgstr "" msgid "Group {0} does not exist" msgstr "" -#: ../../TShockAPI/Commands.cs:1074 +#: ../../TShockAPI/Commands.cs:1101 #, csharp-format msgid "Group {0} does not exist!" msgstr "" @@ -1589,7 +1601,7 @@ msgstr "" msgid "Heals a player in HP and MP." msgstr "" -#: ../../TShockAPI/Commands.cs:1199 +#: ../../TShockAPI/Commands.cs:1226 #, csharp-format msgid "ID: {0}" msgstr "" @@ -1630,7 +1642,7 @@ msgid "" "were disabled for to TShock so we can improve this!" msgstr "" -#: ../../TShockAPI/Commands.cs:821 +#: ../../TShockAPI/Commands.cs:848 msgid "If you forgot your password, contact the administrator for help." msgstr "" @@ -1657,7 +1669,7 @@ msgstr "" msgid "Infinite group parenting ({0})" msgstr "" -#: ../../TShockAPI/Commands.cs:1196 +#: ../../TShockAPI/Commands.cs:1223 msgid "Information about the currently running world" msgstr "" @@ -1691,7 +1703,7 @@ msgstr "" msgid "Invalid parent group {0} for group {1}." msgstr "" -#: ../../TShockAPI/Commands.cs:892 +#: ../../TShockAPI/Commands.cs:919 msgid "Invalid password!" msgstr "" @@ -1704,7 +1716,7 @@ msgid "" "Press any key to exit." msgstr "" -#: ../../TShockAPI/Commands.cs:1001 +#: ../../TShockAPI/Commands.cs:1028 #, csharp-format msgid "Invalid syntax! Proper syntax: {0}register " msgstr "" @@ -1714,8 +1726,8 @@ msgstr "" msgid "Invalid Type: '{0}'" msgstr "" -#: ../../TShockAPI/Commands.cs:1043 -#: ../../TShockAPI/Commands.cs:1176 +#: ../../TShockAPI/Commands.cs:1070 +#: ../../TShockAPI/Commands.cs:1203 #, csharp-format msgid "Invalid user syntax. Try {0}user help." msgstr "" @@ -1767,7 +1779,7 @@ msgstr "" msgid "Lists commands or gives help on them." msgstr "" -#: ../../TShockAPI/Commands.cs:838 +#: ../../TShockAPI/Commands.cs:865 msgid "Login attempt failed - see the message above." msgstr "" @@ -1779,7 +1791,7 @@ msgstr "" msgid "Logs you out of your current account." msgstr "" -#: ../../TShockAPI/Commands.cs:1191 +#: ../../TShockAPI/Commands.cs:1218 #, csharp-format msgid "Machine name: {0}" msgstr "" @@ -1824,7 +1836,7 @@ msgstr "" msgid "Manages user accounts." msgstr "" -#: ../../TShockAPI/Commands.cs:1186 +#: ../../TShockAPI/Commands.cs:1213 #, csharp-format msgid "Memory usage: {0}" msgstr "" @@ -1843,7 +1855,7 @@ msgstr "" msgid "Missing or invalid {0} parameter" msgstr "" -#: ../../TShockAPI/Commands.cs:1201 +#: ../../TShockAPI/Commands.cs:1228 #, csharp-format msgid "Mode: {0}" msgstr "" @@ -1857,7 +1869,7 @@ msgstr "" msgid "Multiple user accounts found for {0} '{1}'" msgstr "" -#: ../../TShockAPI/Commands.cs:1197 +#: ../../TShockAPI/Commands.cs:1224 #, csharp-format msgid "Name: {0}" msgstr "" @@ -1905,7 +1917,7 @@ msgid "" "Not authorized. User \"{0}\" has no access to use the specified API endpoint." msgstr "" -#: ../../TShockAPI/Commands.cs:955 +#: ../../TShockAPI/Commands.cs:982 #, csharp-format msgid "" "Not logged in or invalid syntax! Proper syntax: {0}password " @@ -1927,7 +1939,7 @@ msgstr "" msgid "One of your UserIDs is not a usable integer: {0}" msgstr "" -#: ../../TShockAPI/Commands.cs:1189 +#: ../../TShockAPI/Commands.cs:1216 #, csharp-format msgid "Operating system: {0}" msgstr "" @@ -1941,14 +1953,14 @@ msgstr "" msgid "Parenting group {0} to {1} would cause loops in the parent chain." msgstr "" -#: ../../TShockAPI/Commands.cs:1127 +#: ../../TShockAPI/Commands.cs:1154 #, csharp-format msgid "" "Password change attempt for {0} failed for an unknown reason. Check the " "server console for more details." msgstr "" -#: ../../TShockAPI/Commands.cs:1119 +#: ../../TShockAPI/Commands.cs:1146 #, csharp-format msgid "Password change succeeded for {0}." msgstr "" @@ -1959,21 +1971,21 @@ msgstr "" msgid "Password must be at least {0} characters." msgstr "" -#: ../../TShockAPI/Commands.cs:944 -#: ../../TShockAPI/Commands.cs:981 -#: ../../TShockAPI/Commands.cs:995 -#: ../../TShockAPI/Commands.cs:1061 -#: ../../TShockAPI/Commands.cs:1132 +#: ../../TShockAPI/Commands.cs:971 +#: ../../TShockAPI/Commands.cs:1008 +#: ../../TShockAPI/Commands.cs:1022 +#: ../../TShockAPI/Commands.cs:1088 +#: ../../TShockAPI/Commands.cs:1159 #, csharp-format msgid "Password must be greater than or equal to {0} characters." msgstr "" -#: ../../TShockAPI/Commands.cs:961 +#: ../../TShockAPI/Commands.cs:988 #, csharp-format msgid "PasswordUser returned an error: {0}" msgstr "" -#: ../../TShockAPI/Commands.cs:1202 +#: ../../TShockAPI/Commands.cs:1229 #, csharp-format msgid "Path: {0}" msgstr "" @@ -2021,11 +2033,11 @@ msgstr "" msgid "Player does not have permission to create projectile {0}." msgstr "" -#: ../../TShockAPI/Commands.cs:915 +#: ../../TShockAPI/Commands.cs:942 msgid "Please close NPC windows before logging out." msgstr "" -#: ../../TShockAPI/Commands.cs:1027 +#: ../../TShockAPI/Commands.cs:1054 msgid "Please try a different username." msgstr "" @@ -2038,7 +2050,7 @@ msgstr "" msgid "Prevents a player from talking." msgstr "" -#: ../../TShockAPI/Commands.cs:1190 +#: ../../TShockAPI/Commands.cs:1217 #, csharp-format msgid "Proc count: {0}" msgstr "" @@ -2083,7 +2095,7 @@ msgstr "" msgid "Registers you an account." msgstr "" -#: ../../TShockAPI/Commands.cs:1034 +#: ../../TShockAPI/Commands.cs:1061 #, csharp-format msgid "RegisterUser returned an error: {0}" msgstr "" @@ -2148,7 +2160,7 @@ msgstr "" msgid "Saves the world file." msgstr "" -#: ../../TShockAPI/Commands.cs:1200 +#: ../../TShockAPI/Commands.cs:1227 #, csharp-format msgid "Seed: {0}" msgstr "" @@ -2183,7 +2195,7 @@ msgstr "" msgid "Server map saving..." msgstr "" -#: ../../TShockAPI/Commands.cs:923 +#: ../../TShockAPI/Commands.cs:950 msgid "Server side characters are enabled. You need to be logged-in to play." msgstr "" @@ -2263,7 +2275,7 @@ msgstr "" msgid "Shuts down the server without saving." msgstr "" -#: ../../TShockAPI/Commands.cs:1198 +#: ../../TShockAPI/Commands.cs:1225 #, csharp-format msgid "Size: {0}x{1}" msgstr "" @@ -2272,13 +2284,13 @@ msgstr "" msgid "Slaps a player, dealing damage." msgstr "" -#: ../../TShockAPI/Commands.cs:1026 +#: ../../TShockAPI/Commands.cs:1053 #, csharp-format msgid "Sorry, {0} was already taken by another person." msgstr "" -#: ../../TShockAPI/Commands.cs:960 -#: ../../TShockAPI/Commands.cs:1033 +#: ../../TShockAPI/Commands.cs:987 +#: ../../TShockAPI/Commands.cs:1060 #, csharp-format msgid "Sorry, an error occurred: {0}." msgstr "" @@ -2317,6 +2329,24 @@ msgstr "" msgid "Spreading water without holding a water bucket" msgstr "" +#: ../../TShockAPI/TSPlayer.cs:618 +#: ../../TShockAPI/TSPlayer.cs:638 +#, csharp-format +msgid "Stack cheat detected. Remove Loadout 1 item {0} ({1}) and then rejoin." +msgstr "" + +#: ../../TShockAPI/TSPlayer.cs:658 +#: ../../TShockAPI/TSPlayer.cs:678 +#, csharp-format +msgid "Stack cheat detected. Remove Loadout 2 item {0} ({1}) and then rejoin." +msgstr "" + +#: ../../TShockAPI/TSPlayer.cs:698 +#: ../../TShockAPI/TSPlayer.cs:718 +#, csharp-format +msgid "Stack cheat detected. Remove Loadout 3 item {0} ({1}) and then rejoin." +msgstr "" + #: ../../TShockAPI/Rest/SecureRest.cs:167 msgid "Successful login" msgstr "" @@ -2355,7 +2385,7 @@ msgstr "" msgid "Temporarily sets another player's group." msgstr "" -#: ../../TShockAPI/Commands.cs:1154 +#: ../../TShockAPI/Commands.cs:1181 msgid "That group does not exist!" msgstr "" @@ -2413,7 +2443,7 @@ msgstr "" msgid "The specified token queued for destruction failed to be deleted." msgstr "" -#: ../../TShockAPI/Commands.cs:1100 +#: ../../TShockAPI/Commands.cs:1127 #, csharp-format msgid "The user {0} does not exist! Therefore, the account was not deleted!" msgstr "" @@ -2430,7 +2460,7 @@ msgstr "" msgid "The world's chest limit has been reached - unable to place more." msgstr "" -#: ../../TShockAPI/Commands.cs:900 +#: ../../TShockAPI/Commands.cs:927 msgid "" "There was an error processing your login or authentication related request." msgstr "" @@ -2485,22 +2515,22 @@ msgstr "" msgid "Too many invalid login attempts." msgstr "" -#: ../../TShockAPI/Commands.cs:1188 +#: ../../TShockAPI/Commands.cs:1215 #, csharp-format msgid "Total processor time: {0}" msgstr "" -#: ../../TShockAPI/Commands.cs:1017 +#: ../../TShockAPI/Commands.cs:1044 #, csharp-format msgid "Type {0}login \"{1}\" {2} to sign in to your account." msgstr "" -#: ../../TShockAPI/Commands.cs:1019 +#: ../../TShockAPI/Commands.cs:1046 #, csharp-format msgid "Type {0}login {1} to sign in to your account." msgstr "" -#: ../../TShockAPI/Commands.cs:1014 +#: ../../TShockAPI/Commands.cs:1041 #, csharp-format msgid "Type {0}login to sign in to your account using your UUID." msgstr "" @@ -2533,22 +2563,22 @@ msgstr "" msgid "User {0} '{1}' doesn't exist" msgstr "" -#: ../../TShockAPI/Commands.cs:1078 +#: ../../TShockAPI/Commands.cs:1105 #, csharp-format msgid "User {0} already exists!" msgstr "" -#: ../../TShockAPI/Commands.cs:1082 +#: ../../TShockAPI/Commands.cs:1109 #, csharp-format msgid "User {0} could not be added, check console for details." msgstr "" -#: ../../TShockAPI/Commands.cs:1162 +#: ../../TShockAPI/Commands.cs:1189 #, csharp-format msgid "User {0} could not be added. Check console for details." msgstr "" -#: ../../TShockAPI/Commands.cs:1158 +#: ../../TShockAPI/Commands.cs:1185 #, csharp-format msgid "User {0} does not exist!" msgstr "" @@ -2563,7 +2593,7 @@ msgstr "" msgid "User account {0} does not exist" msgstr "" -#: ../../TShockAPI/Commands.cs:1168 +#: ../../TShockAPI/Commands.cs:1195 msgid "User management command help:" msgstr "" @@ -2591,7 +2621,7 @@ msgstr "" msgid "Using banned water bucket without permissions" msgstr "" -#: ../../TShockAPI/Commands.cs:888 +#: ../../TShockAPI/Commands.cs:915 msgid "UUID does not match this character!" msgstr "" @@ -2608,7 +2638,7 @@ msgstr "" msgid "You are already logged in, and cannot login again." msgstr "" -#: ../../TShockAPI/Commands.cs:909 +#: ../../TShockAPI/Commands.cs:936 msgid "You are not logged-in. Therefore, you cannot logout." msgstr "" @@ -2622,6 +2652,18 @@ msgstr "" msgid "You can't remove the default guest group." msgstr "" +#: ../../TShockAPI/Commands.cs:805 +msgid "You cannot login whilst crowd controlled." +msgstr "" + +#: ../../TShockAPI/Commands.cs:791 +msgid "You cannot login whilst dead." +msgstr "" + +#: ../../TShockAPI/Commands.cs:799 +msgid "You cannot login whilst using an item." +msgstr "" + #: ../../TShockAPI/Commands.cs:688 msgid "You do not have access to this command." msgstr "" @@ -2722,7 +2764,7 @@ msgstr "" msgid "You do not have permission to toggle godmode." msgstr "" -#: ../../TShockAPI/Commands.cs:949 +#: ../../TShockAPI/Commands.cs:976 msgid "You failed to change your password!" msgstr "" @@ -2734,11 +2776,11 @@ msgstr "" msgid "You have been remotely unmmuted" msgstr "" -#: ../../TShockAPI/Commands.cs:920 +#: ../../TShockAPI/Commands.cs:947 msgid "You have been successfully logged out of your account." msgstr "" -#: ../../TShockAPI/Commands.cs:938 +#: ../../TShockAPI/Commands.cs:965 msgid "You have successfully changed your password." msgstr "" @@ -2769,7 +2811,7 @@ msgstr "" msgid "Your database contains invalid UserIDs (they should be integers)." msgstr "" -#: ../../TShockAPI/Commands.cs:1011 +#: ../../TShockAPI/Commands.cs:1038 #, csharp-format msgid "Your password is {0}." msgstr "" From 890ddf8cad1c1d2417a5df7cc9174c34fb0417e5 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 22 Oct 2022 15:16:18 -0700 Subject: [PATCH 7/7] Document SSC caveats as a result of loadout sync issues --- docs/ssc.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/ssc.md b/docs/ssc.md index da91136a..607e670a 100644 --- a/docs/ssc.md +++ b/docs/ssc.md @@ -6,6 +6,12 @@ When enabled in TShock, SSC takes over control of inventory management for a pla For developers, TShock's SSC implementation should be considered the reference implementation. More things are possible with this system than what TShock does. +## Limitations + +In TShock 5 (Terraria 1.4.4.x), loadouts have some caveats. **Data loss or corruption may occur if you aren't careful.** If a player is de-buffed, crowd-controlled, or dead, and the player's client will ignore the loadout synchronization packet. TShock's `Disable()` method, webbing, and other de-buffs may cause desyncs between the server and client. Further, if players use items or otherwise become debuffed, they may also desynchronize. Because of this, we strongly advise telling players to be careful and not use items during loadout changes, or they risk data loss or corruption. + +Admins that use `Dimensions` or other proxy tools that join multiple servers together may experience this if those proxies disable or de-buff players during the server switch window, before the player is logged in and synchronized. + ## Setting up SSC To setup SSC, simply change `Enabled` to `true` in `sscconfig.json` in the `tshock` config folder. @@ -90,7 +96,3 @@ If a TShock player has `tshock.ignore.ssc`, and `WarnPlayersAboutBypassPermissio Sometimes, you want to import player data from players that join your server. For example, if you trust your friends not to bring hacked items in, you can import their data into the system. This is done with the `/uploadssc` command. The `/overridessc` command can be used to upload SSC data from a given player. The difference between this command is that `/uploadssc` uploads their data from when they joined, whereas `/overridessc` will just save whatever their current state is to the database. - -## Limitations - -Currently, SSC does not support loadouts (1.4.4.x content).