diff --git a/.github/workflows/ci-otapi3-nuget.yml b/.github/workflows/ci-otapi3-nuget.yml index 7834ccf01..c4f0649ed 100644 --- a/.github/workflows/ci-otapi3-nuget.yml +++ b/.github/workflows/ci-otapi3-nuget.yml @@ -1,8 +1,7 @@ name: Deploy NuGet(OTAPI3) on: - push: - branches: [ nuget-release ] + workflow_dispatch: jobs: build: @@ -21,10 +20,10 @@ jobs: - name: Restore dependencies run: dotnet restore - name: Build - run: dotnet build TShock.sln --configuration Release --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal --configuration Release - + run: | + dotnet build -c Release + dotnet pack -c Release -o nupkgs # Publish to nuget - name: Push TShockAPI - run: dotnet nuget push TShockAPI/bin/Release/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json + run: | + dotnet nuget push nupkgs/UnrealMultiple.*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json diff --git a/.github/workflows/ci-otapi3.yml b/.github/workflows/ci-otapi3.yml index f7e500370..54d22d834 100644 --- a/.github/workflows/ci-otapi3.yml +++ b/.github/workflows/ci-otapi3.yml @@ -39,12 +39,12 @@ jobs: - name: Produce installer run: | cd TShockInstaller - dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --self-contained true + dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --self-contained - name: Produce build run: | cd TShockLauncher - dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --self-contained false + dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --no-self-contained - name: Chmod scripts if: ${{ matrix.arch != 'win-x64' }} diff --git a/.gitignore b/.gitignore index 5ac4b6aad..fe15dca75 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ releases/ ################### *.com *.class -*.dll *.exe *.o *.so @@ -69,3 +68,5 @@ packages/* # Private key files # scripts/ssh_private_key +.idea +nupkgs diff --git a/.gitmodules b/.gitmodules index f3c700b63..4cf49db0f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ [submodule "TerrariaServerAPI"] path = TerrariaServerAPI - url = https://github.com/NyxStudios/TerrariaAPI-Server.git + url = https://github.com/WindFrost-CSFT/TSAPI.git ignore = dirty branch = general-devel diff --git a/TShock.sln b/TShock.sln index 36256729c..980783786 100644 --- a/TShock.sln +++ b/TShock.sln @@ -24,8 +24,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerrariaServerAPI", "Terrar EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TShockLauncher", "TShockLauncher\TShockLauncher.csproj", "{2A312452-A43F-43E3-8AEB-E22F9A35C210}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TShockLauncher.Tests", "TShockLauncher.Tests\TShockLauncher.Tests.csproj", "{90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockInstaller", "TShockInstaller\TShockInstaller.csproj", "{17AC4DD0-8334-4B5C-ABED-77EAF52D75FA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockPluginManager", "TShockPluginManager\TShockPluginManager.csproj", "{9FFABC7D-B042-4B58-98F5-7FA787B9A757}" @@ -90,22 +88,6 @@ Global {2A312452-A43F-43E3-8AEB-E22F9A35C210}.Release|x64.Build.0 = Release|Any CPU {2A312452-A43F-43E3-8AEB-E22F9A35C210}.Release|x86.ActiveCfg = Release|Any CPU {2A312452-A43F-43E3-8AEB-E22F9A35C210}.Release|x86.Build.0 = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|x64.ActiveCfg = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|x64.Build.0 = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|x86.ActiveCfg = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Debug|x86.Build.0 = Debug|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|Any CPU.Build.0 = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|x64.ActiveCfg = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|x64.Build.0 = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|x86.ActiveCfg = Release|Any CPU - {90AB47F3-8220-48FC-BDAB-D6E97BFDA51B}.Release|x86.Build.0 = Release|Any CPU {17AC4DD0-8334-4B5C-ABED-77EAF52D75FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17AC4DD0-8334-4B5C-ABED-77EAF52D75FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {17AC4DD0-8334-4B5C-ABED-77EAF52D75FA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index c5e241561..e5d487ab5 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -15,6 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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; @@ -28,6 +29,7 @@ You should have received a copy of the GNU General Public License using Terraria.Localization; using TShockAPI.Models.PlayerUpdate; using System.Threading.Tasks; +using NuGet.Protocol.Plugins; using OTAPI; using Terraria.GameContent.Tile_Entities; @@ -165,11 +167,12 @@ internal Bouncer() var usingBiomeTorches = player.UsingBiomeTorches; player.UsingBiomeTorches = true; // BiomeTorchPlaceStyle returns the place style of the player's current biome's biome torch - var biomeTorchPlaceStyle = player.BiomeTorchPlaceStyle(actualItemPlaceStyle); + var type = 114514; // Not used + player.BiomeTorchPlaceStyle(ref type ,ref actualItemPlaceStyle); // Reset UsingBiomeTorches value player.UsingBiomeTorches = usingBiomeTorches; - return biomeTorchPlaceStyle; + return actualItemPlaceStyle; } else { @@ -255,6 +258,19 @@ internal Bouncer() } }); + PlaceStyleCorrectors.Add(TileID.Grass, + (player, requestedPlaceStyle, actualItemPlaceStyle) => + { + if (player.selectedItem is (ItemID.AcornAxe or ItemID.StaffofRegrowth) && + actualItemPlaceStyle is <= 6 and >= 0) + { + return actualItemPlaceStyle; + } + + return requestedPlaceStyle; + + }); + #region PlayerAddBuff Whitelist PlayerAddBuffWhitelist = new BuffLimit[Terraria.ID.BuffID.Count]; @@ -456,7 +472,7 @@ internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs ar } args.Player.RequestedSection = true; - if (String.IsNullOrEmpty(args.Player.Name)) + if (string.IsNullOrEmpty(args.Player.Name)) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnGetSection rejected empty player name.")); args.Player.Kick(GetString("Your client sent a blank character name."), true, true); @@ -482,7 +498,7 @@ internal void OnPlayerUpdate(object sender, GetDataHandlers.PlayerUpdateEventArg var pos = args.Position; var vel = args.Velocity; - if (Single.IsInfinity(vel.X) || Single.IsInfinity(vel.Y)) + if (float.IsInfinity(vel.X) || float.IsInfinity(vel.Y)) { TShock.Log.ConsoleInfo(GetString("Bouncer / OnPlayerUpdate force kicked (attempted to set velocity to infinity) from {0}", args.Player.Name)); args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); @@ -490,7 +506,7 @@ internal void OnPlayerUpdate(object sender, GetDataHandlers.PlayerUpdateEventArg return; } - if (Single.IsNaN(vel.X) || Single.IsNaN(vel.Y)) + if (float.IsNaN(vel.X) || float.IsNaN(vel.Y)) { TShock.Log.ConsoleInfo(GetString("Bouncer / OnPlayerUpdate force kicked (attempted to set velocity to NaN) from {0}", args.Player.Name)); args.Player.Kick(GetString("Detected DOOM set to ON position."), true, true); @@ -609,6 +625,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) EditType type = args.editDetail; ITile tile = Main.tile[tileX, tileY]; + // 'placeStyle' is a term used in Terraria land to determine which frame of a sprite is displayed when the sprite is placed. The placeStyle // determines the frameX and frameY offsets byte requestedPlaceStyle = args.Style; @@ -720,7 +737,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type) && args.Player.RecentFuse == 0) { // If the tile is an axe tile and they aren't selecting an axe, they're hacking. - if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID])) + if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.type])) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (axe) {0} {1} {2}", args.Player.Name, action, editData)); args.Player.SendTileSquareCentered(tileX, tileY, 4); @@ -728,7 +745,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } // If the tile is a hammer tile and they aren't selecting a hammer, they're hacking. - else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID])) + else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.type])) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (hammer) {0} {1} {2}", args.Player.Name, action, editData)); args.Player.SendTileSquareCentered(tileX, tileY, 4); @@ -740,13 +757,26 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) // also add an exception for snake coils, they can be removed when the player places a new one or after x amount of time // If the tile is part of the breakable when placing set, it might be getting broken by a placement. else if (tile.type != TileID.ItemFrame && tile.type != TileID.MysticSnakeRope - && !ItemID.Sets.Explosives[selectedItem.netID] + && !ItemID.Sets.Explosives[selectedItem.type] && !TileID.Sets.BreakableWhenPlacing[tile.type] && !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 && selectedItem.pick == 0 && selectedItem.type != ItemID.GravediggerShovel && args.Player.TPlayer.mount.Type != MountID.Drill && args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart) { + if (args.Player.TPlayer.ownedProjectileCounts[ProjectileID.PalworldDigtoise] > 0) + { + var digtoiseProjectile = Main.projectile + .FirstOrDefault(p => + p != null && p.active && p.type == 1098 && p.owner == args.Player.Index); + + if (digtoiseProjectile?.ai[0] is 1f or 2f or 3f + && digtoiseProjectile.ai[1] > 40f) + { + return; + } + } + TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (pick) {0} {1} {2}", args.Player.Name, action, editData)); args.Player.SendTileSquareCentered(tileX, tileY, 4); @@ -757,7 +787,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) else if (action == EditAction.KillWall) { // If they aren't selecting a hammer, they could be hacking. - if (selectedItem.hammer == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0 && selectedItem.createWall == 0) + if (selectedItem.hammer == 0 && !ItemID.Sets.Explosives[selectedItem.type] && args.Player.RecentFuse == 0 && selectedItem.createWall == 0) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (hammer2) {0} {1} {2}", args.Player.Name, action, editData)); args.Player.SendTileSquareCentered(tileX, tileY, 1); @@ -765,7 +795,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } } - else if (action == EditAction.PlaceTile && (projectileCreatesTile.ContainsKey(lastKilledProj) && editData == projectileCreatesTile[lastKilledProj])) + else if (action == EditAction.PlaceTile && projectileCreatesTile.TryGetValue(lastKilledProj, out int value) && editData == value) { args.Player.LastKilledProjectile = 0; } @@ -774,11 +804,11 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) // Handle placement if the user is placing rope that comes from a ropecoil, // but have not created the ropecoil projectile recently or the projectile was not at the correct coordinate, or the tile that the projectile places does not match the rope it is suposed to place // projectile should be the same X coordinate as all tile places (Note by @Olink) - if (ropeCoilPlacements.ContainsKey(selectedItem.netID) && + if (ropeCoilPlacements.ContainsKey(selectedItem.type) && !args.Player.RecentlyCreatedProjectiles.Any(p => GetDataHandlers.projectileCreatesTile.ContainsKey(p.Type) && GetDataHandlers.projectileCreatesTile[p.Type] == editData && !p.Killed && Math.Abs((int)(Main.projectile[p.Index].position.X / 16f) - tileX) <= Math.Abs(Main.projectile[p.Index].velocity.X))) { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (inconceivable rope coil) {0} {1} {2} selectedItem:{3} itemCreateTile:{4}", args.Player.Name, action, editData, selectedItem.netID, selectedItem.createTile)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (inconceivable rope coil) {0} {1} {2} selectedItem:{3} itemCreateTile:{4}", args.Player.Name, action, editData, selectedItem.type, selectedItem.createTile)); args.Player.SendTileSquareCentered(tileX, tileY, 1); args.Handled = true; return; @@ -795,29 +825,66 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } - /// Handle placement action if the player is using an Ice Rod but not placing the iceblock. - if (selectedItem.netID == ItemID.IceRod && editData != TileID.MagicalIceBlock) + // Handle placement action for Regrowth tools to ensure they only replant herbs on valid containers. + if (selectedItem.type is ItemID.AcornAxe or ItemID.StaffofRegrowth) + { + if ((int)editData is not (TileID.Grass or TileID.HallowedGrass or TileID.CorruptGrass + or TileID.CrimsonGrass or TileID.JungleGrass or TileID.MushroomGrass + or TileID.CorruptJungleGrass or TileID.CrimsonJungleGrass or TileID.AshGrass) + && !TileID.Sets.Conversion.Moss[editData]) + { + if (editData != TileID.ImmatureHerbs) + { + TShock.Log.ConsoleDebug(GetString( + "Bouncer / OnTileEdit rejected {0} from placing non-herb tile {1} using {2}", + args.Player.Name, editData, selectedItem.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 4); + args.Handled = true; + } + + var containerTile = Main.tile[tileX, tileY + 1]; + if (!containerTile.active() || + containerTile.type is not (TileID.ClayPot or TileID.RockGolemHead or TileID.PlanterBox)) + { + TShock.Log.ConsoleDebug(GetString( + "Bouncer / OnTileEdit rejected {0} from planting herb on invalid tile {1} using {2}", + args.Player.Name, containerTile.type, selectedItem.Name)); + args.Player.SendTileSquareCentered(tileX, tileY, 4); + args.Handled = true; + } + } + } + + if (selectedItem.type == ItemID.AcornSlingshot && editData != TileID.Saplings) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from using acorn slingshot but not placing saplings {0} {1} {2}", args.Player.Name, action, editData)); + args.Player.SendTileSquareCentered(tileX, tileY, 4); + args.Handled = true; + } + + // Handle placement action if the player is using an Ice Rod but not placing the iceblock. + if (selectedItem.type == ItemID.IceRod && editData != TileID.MagicalIceBlock) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from using ice rod but not placing ice block {0} {1} {2}", args.Player.Name, action, editData)); args.Player.SendTileSquareCentered(tileX, tileY, 4); args.Handled = true; } - /// If they aren't selecting the item which creates the tile, they're hacking. + // If they aren't selecting the item which creates the tile, they're hacking. if ((action == EditAction.PlaceTile || action == EditAction.ReplaceTile) && editData != selectedItem.createTile) { - /// These would get caught up in the below check because Terraria does not set their createTile field. - if (selectedItem.netID != ItemID.IceRod && selectedItem.netID != ItemID.DirtBomb && selectedItem.netID != ItemID.StickyBomb && (args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart || editData != TileID.MinecartTrack)) + // These would get caught up in the below check because Terraria does not set their createTile field. + if (selectedItem.type != ItemID.IceRod && selectedItem.type != ItemID.DirtBomb && selectedItem.type != ItemID.StickyBomb && (args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart || editData != TileID.MinecartTrack) && selectedItem.type != ItemID.AcornAxe && selectedItem.type != ItemID.StaffofRegrowth && selectedItem.type != ItemID.AcornSlingshot) { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from tile placement not matching selected item createTile {0} {1} {2} selectedItemID:{3} createTile:{4}", args.Player.Name, action, editData, selectedItem.netID, selectedItem.createTile)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from tile placement not matching selected item createTile {0} {1} {2} selectedItemID:{3} createTile:{4}", args.Player.Name, action, editData, selectedItem.type, selectedItem.createTile)); args.Player.SendTileSquareCentered(tileX, tileY, 4); args.Handled = true; return; } } - /// If they aren't selecting the item which creates the wall, they're hacking. + // If they aren't selecting the item which creates the wall, they're hacking. if ((action == EditAction.PlaceWall || action == EditAction.ReplaceWall) && editData != selectedItem.createWall) { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from wall placement not matching selected item createWall {0} {1} {2} selectedItemID:{3} createWall:{4}", args.Player.Name, action, editData, selectedItem.netID, selectedItem.createWall)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from wall placement not matching selected item createWall {0} {1} {2} selectedItemID:{3} createWall:{4}", args.Player.Name, action, editData, selectedItem.type, selectedItem.createWall)); args.Player.SendTileSquareCentered(tileX, tileY, 4); args.Handled = true; return; @@ -917,7 +984,7 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } - if (action == EditAction.KillTile || action == EditAction.KillWall && ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0) + if (action == EditAction.KillTile || action == EditAction.KillWall && ItemID.Sets.Explosives[selectedItem.type] && args.Player.RecentFuse == 0) { args.Handled = false; return; @@ -1003,6 +1070,8 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) } } args.Handled = false; + + return; } catch @@ -1124,42 +1193,43 @@ internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs args) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from prefix check from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; } //Item removed, let client do this to prevent item duplication // client side (but only if it passed the range check) (i.e., return false) - if (type == 0) - { - if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) - { - // Causes item duplications. Will be re added if necessary - //args.Player.SendData(PacketTypes.ItemDrop, "", id); - TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from dupe range check from {0}", args.Player.Name)); - args.Handled = true; - return; - } - - args.Handled = false; - return; - } - - if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f))) + // if (type == 0) + // { + // if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f))) + // { + // // Causes item duplications. Will be re added if necessary + // //args.Player.SendData(PacketTypes.ItemDrop, "", id); + // TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from dupe range check from {0}", args.Player.Name)); + // args.Handled = true; + // return; + // } + // + // args.Handled = false; + // return; + // } + + if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f), 128)) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from range check from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; } // stop the client from changing the item type of a drop but // only if the client isn't picking up the item - if (Main.item[id].active && Main.item[id].netID != type) + if (Main.item[id].active && Main.item[id].type != type && + !(Main.item[id].type == ItemID.EmptyBucket && type == ItemID.WaterBucket)) // Empty bucket turns into Water Bucket on rainy days { TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from item drop/pickup check from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; } @@ -1169,7 +1239,7 @@ internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs args) if ((stacks > item.maxStack || stacks <= 0) || (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems))) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from drop item ban check / max stack check / min stack check from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; } @@ -1180,7 +1250,7 @@ internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs args) //Player is probably trying to sneak items onto the server in their hands!!! TShock.Log.ConsoleInfo(GetString("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name)); TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from sneaky from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; @@ -1189,7 +1259,7 @@ internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs args) if (args.Player.IsBeingDisabled()) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnItemDrop rejected from disabled from {0}", args.Player.Name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); + args.Player.SendData(PacketTypes.SyncItemDespawn, "", id); args.Handled = true; return; } @@ -1261,7 +1331,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA return; } - /// If the projectile is a directional projectile, check if the player is holding their respected item to validate the projectile creation. + // If the projectile is a directional projectile, check if the player is holding their respected item to validate the projectile creation. if (directionalProjectiles.ContainsKey(type)) { if (directionalProjectiles[type] == args.Player.TPlayer.HeldItem.type) @@ -1271,19 +1341,20 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA } } - /// If the created projectile is a golf club, check if the player is holding one of the golf club items to validate the projectile creation. + // If the created projectile is a golf club, check if the player is holding one of the golf club items to validate the projectile creation. if (type == ProjectileID.GolfClubHelper && Handlers.LandGolfBallInCupHandler.GolfClubItemIDs.Contains(args.Player.TPlayer.HeldItem.type)) { args.Handled = false; return; } - /// If the created projectile is a golf ball and the player is not holding a golf club item and neither a golf ball item and neither they have had a golf club projectile created recently. + // If the created projectile is a golf ball and the player is not holding a golf club item and neither a golf ball item and neither they have had a golf club projectile created recently. if (Handlers.LandGolfBallInCupHandler.GolfBallProjectileIDs.Contains(type) && !Handlers.LandGolfBallInCupHandler.GolfClubItemIDs.Contains(args.Player.TPlayer.HeldItem.type) && !Handlers.LandGolfBallInCupHandler.GolfBallItemIDs.Contains(args.Player.TPlayer.HeldItem.type) && !args.Player.RecentlyCreatedProjectiles.Any(p => p.Type == ProjectileID.GolfClubHelper)) { + return; TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile please report to tshock about this! normally this is a reject from {0} {1} (golf)", args.Player.Name, type)); } @@ -1352,11 +1423,13 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA || (type >= ProjectileID.MartianTurretBolt && type <= ProjectileID.RayGunnerLaser) || type == ProjectileID.CultistBossLightningOrb) { + return; TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from weird check from {0} {1}", args.Player.Name, type)); TShock.Log.Debug(GetString("Certain projectiles have been ignored for cheat detection.")); } else { + return; TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile please report to tshock about this! normally this is a reject from {0} {1}", args.Player.Name, type)); // args.Player.Disable(String.Format("Does not have projectile permission to update projectile. ({0})", type), DisableFlags.WriteToLogAndConsole); // args.Player.RemoveProjectile(ident, owner); @@ -1695,13 +1768,13 @@ internal void OnPlayerZone(object sender, GetDataHandlers.PlayerZoneEventArgs ar foreach (var npc in Main.npc) { - if (npc.netID == NPCID.LunarTowerSolar) + if (npc.type == NPCID.LunarTowerSolar) hasSolarTower = true; - else if (npc.netID == NPCID.LunarTowerVortex) + else if (npc.type == NPCID.LunarTowerVortex) hasVortexTower = true; - else if (npc.netID == NPCID.LunarTowerNebula) + else if (npc.type == NPCID.LunarTowerNebula) hasNebulaTower = true; - else if (npc.netID == NPCID.LunarTowerStardust) + else if (npc.type == NPCID.LunarTowerStardust) hasStardustTower = true; } @@ -2082,52 +2155,57 @@ internal void OnNPCAddBuff(object sender, GetDataHandlers.NPCAddBuffEventArgs ar return; } - if (!args.Player.HasPermission(Permissions.ignorenpcbuffdetection)) + // "TorchSlime" will add a super long burn buff + if (type == BuffID.OnFire && Main.getGoodWorld && npc.aiStyle == 1 && (short)npc.ai[1] == ItemID.Torch) { - bool detectedNPCBuffTimeCheat = false; + return; + } - if (NPCAddBuffTimeMax.ContainsKey(type)) - { - if (time > NPCAddBuffTimeMax[type]) - { - detectedNPCBuffTimeCheat = true; - } + bool detectedNPCBuffTimeCheat = false; - if (npc.townNPC) - { - if (type != BuffID.Poisoned - && type != BuffID.OnFire - && type != BuffID.Confused - && type != BuffID.CursedInferno - && type != BuffID.Ichor - && type != BuffID.Venom - && type != BuffID.Midas - && type != BuffID.Wet - && type != BuffID.Lovestruck - && type != BuffID.Stinky - && type != BuffID.Slimed - && type != BuffID.DryadsWard - && type != BuffID.GelBalloonBuff - && type != BuffID.OnFire3 - && type != BuffID.Frostburn2 - && type != BuffID.Shimmer) - { - detectedNPCBuffTimeCheat = true; - } - } - } - else + if (NPCAddBuffTimeMax.ContainsKey(type)) + { + if (time > NPCAddBuffTimeMax[type]) { detectedNPCBuffTimeCheat = true; } - if (detectedNPCBuffTimeCheat) - { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnNPCAddBuff rejected abnormal buff ({0}, last for {4}) added to {1} ({2}) from {3}.", type, npc.TypeName, npc.netID, args.Player.Name, time)); - args.Player.Kick(GetString($"Added buff to {npc.TypeName} NPC abnormally."), true); - args.Handled = true; + if (npc.townNPC) + { + if (type != BuffID.Poisoned + && type != BuffID.OnFire + && type != BuffID.Confused + && type != BuffID.CursedInferno + && type != BuffID.Ichor + && type != BuffID.Venom + && type != BuffID.Midas + && type != BuffID.Wet + && type != BuffID.Lovestruck + && type != BuffID.Stinky + && type != BuffID.Slimed + && type != BuffID.DryadsWard + && type != BuffID.GelBalloonBuff + && type != BuffID.OnFire3 + && type != BuffID.Frostburn2 + && type != BuffID.Shimmer) + { + detectedNPCBuffTimeCheat = true; + } } } + else + { + detectedNPCBuffTimeCheat = true; + } + + if (detectedNPCBuffTimeCheat) + { + TShock.Log.ConsoleDebug(GetString( + "Bouncer / OnNPCAddBuff rejected abnormal buff ({0}, last for {4}) added to {1} ({2}) from {3}.", + type, npc.TypeName, npc.type, args.Player.Name, time)); + args.Player.Kick($"Added buff {type} (last for {time}) to {npc.TypeName} NPC abnormally.", true); + args.Handled = true; + } } /// The Bouncer handler for when an NPC is rehomed. @@ -2389,6 +2467,16 @@ internal void OnPlaceObject(object sender, GetDataHandlers.PlaceObjectEventArgs return; } } + else if (type == TileID.GardenGnome) + { + if (style is > 4 or < 0) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlaceObject rejected {0} due to invalid garden gnome style {1}", args.Player.Name, style)); + args.Player.SendTileSquareCentered(x, y, 4); + args.Handled = true; + return; + } + } else { // This is necessary to check in order to prevent special tiles such as @@ -2404,8 +2492,11 @@ internal void OnPlaceObject(object sender, GetDataHandlers.PlaceObjectEventArgs if (args.Player.SelectedItem.placeStyle != style) { - var validTorch = args.Player.SelectedItem.createTile == TileID.Torches && args.Player.TPlayer.BiomeTorchPlaceStyle(args.Player.SelectedItem.placeStyle) == style; - var validCampfire = args.Player.SelectedItem.createTile == TileID.Campfire && args.Player.TPlayer.BiomeCampfirePlaceStyle(args.Player.SelectedItem.placeStyle) == style; + var uselessType = 114514; + args.Player.TPlayer.BiomeTorchPlaceStyle(ref uselessType, ref args.Player.SelectedItem.placeStyle); + + var validTorch = args.Player.SelectedItem.createTile == TileID.Torches && args.Player.SelectedItem.placeStyle == style; + var validCampfire = args.Player.SelectedItem.createTile == TileID.Campfire && args.Player.SelectedItem.placeStyle == style; if (!args.Player.TPlayer.unlockedBiomeTorches || (!validTorch && !validCampfire)) { TShock.Log.ConsoleError(GetString("Bouncer / OnPlaceObject rejected object placement with invalid style {1} (expected {2}) from {0}", args.Player.Name, style, args.Player.SelectedItem.placeStyle)); @@ -2815,7 +2906,7 @@ internal void OnKillMe(object sender, GetDataHandlers.KillMeEventArgs args) /// internal void OnFishOutNPC(object sender, GetDataHandlers.FishOutNPCEventArgs args) { - /// Getting recent projectiles of the player and selecting the first that is a bobber. + // Getting recent projectiles of the player and selecting the first that is a bobber. var projectile = args.Player.RecentlyCreatedProjectiles.FirstOrDefault(p => Main.projectile[p.Index].bobber); if (!FishingRodItemIDs.Contains(args.Player.SelectedItem.type)) @@ -2824,7 +2915,7 @@ internal void OnFishOutNPC(object sender, GetDataHandlers.FishOutNPCEventArgs ar args.Handled = true; return; } - if (projectile.Type == 0 || projectile.Killed) /// The bobber projectile is never killed when the NPC spawns. Type can only be 0 if no recent projectile is found that is named Bobber. + if (projectile.Type == 0 || projectile.Killed) // The bobber projectile is never killed when the NPC spawns. Type can only be 0 if no recent projectile is found that is named Bobber. { TShock.Log.ConsoleDebug(GetString("Bouncer / OnFishOutNPC rejected for not finding active bobber projectile! - From {0}", args.Player.Name)); args.Handled = true; @@ -2973,42 +3064,39 @@ internal static int GetMaxPlaceStyle(int tileID) // Moved to Projectile.StatusNPC(int i). private static Dictionary NPCAddBuffTimeMax = new Dictionary() { - { BuffID.Poisoned, 3600 }, // BuffID: 20 - { BuffID.OnFire, 1200 }, // BuffID: 24 - { BuffID.Confused, short.MaxValue }, // BuffID: 31 Brain of Confusion Internal Item ID: 3223 - { BuffID.CursedInferno, 600 }, // BuffID: 39 - { BuffID.Frostburn, 900 }, // BuffID: 44 - { BuffID.Ichor, 1200 }, // BuffID: 69 - { BuffID.Venom, 1800 }, // BuffID: 70 - { BuffID.Midas, 120 }, // BuffID: 72 - { BuffID.Wet, 1500 }, // BuffID: 103 - { BuffID.Lovestruck, 1800 }, // BuffID: 119 - { BuffID.Stinky, 1800 }, // BuffID: 120 - { BuffID.Slimed, 1500 }, // BuffID: 137 - { BuffID.SoulDrain, 30 }, // BuffID: 151 - { BuffID.ShadowFlame, 660 }, // BuffID: 153 - { BuffID.DryadsWard, 120 }, // BuffID: 165 - { BuffID.BoneJavelin, 900 }, // BuffID: 169 - { BuffID.StardustMinionBleed, 900 }, // BuffID: 183 - { BuffID.DryadsWardDebuff, 120 }, // BuffID: 186 - { BuffID.Daybreak, 300 }, // BuffID: 189 Solar Eruption Item ID: 3473, Daybreak Item ID: 3543 - { BuffID.BetsysCurse, 600 }, // BuffID: 203 - { BuffID.Oiled, 540 }, // BuffID: 204 - { BuffID.BlandWhipEnemyDebuff, 240 }, // BuffID: 307 - { BuffID.SwordWhipNPCDebuff, 240 }, // BuffID: 309 - { BuffID.ScytheWhipEnemyDebuff, 240 }, // BuffID: 310 - { BuffID.FlameWhipEnemyDebuff, 240 }, // BuffID: 313 - { BuffID.ThornWhipNPCDebuff, 240 }, // BuffID: 315 - { BuffID.RainbowWhipNPCDebuff, 240 }, // BuffID: 316 - { BuffID.MaceWhipNPCDebuff, 240 }, // BuffID: 319 - { BuffID.GelBalloonBuff, 1800 }, // BuffID: 320 - { BuffID.OnFire3, 1200 }, // BuffID: 323 - { BuffID.Frostburn2, 1200 }, // BuffID: 324 - { BuffID.BoneWhipNPCDebuff, 240 }, // BuffID: 326 - { BuffID.TentacleSpike, 540 }, // BuffID: 337 - { BuffID.CoolWhipNPCDebuff, 240 }, // BuffID: 340 - { BuffID.BloodButcherer, 540 }, // BuffID: 344 - { BuffID.Shimmer, 100 }, // BuffID: 353 + { BuffID.Shimmer, 100 }, + { BuffID.Venom, 1800 }, + { BuffID.CursedInferno, 600 }, + { BuffID.OnFire, 19392 }, // It's supposed to be 600, but it's actually 19392. And I can't find 19132 anywhere. + { BuffID.Ichor, 1140 }, + { BuffID.Confused, 16684 }, + { BuffID.Poisoned, 3600 }, + { BuffID.Midas, 120 }, + { BuffID.Bleeding, 720 }, + { BuffID.Frostburn2, 1200 }, + { BuffID.OnFire3, 1200 }, + { BuffID.Stinky, 1800 }, + { BuffID.Slimed, 180 }, + { BuffID.Hemorrhage, 720 }, + { BuffID.BrokenArmor, 1200 }, + { BuffID.BoneJavelin, 900 }, + { BuffID.Daybreak, 300 }, + { BuffID.TentacleSpike, 540 }, + { BuffID.BloodButcherer, 540 }, + { BuffID.BetsysCurse, 600 }, + { BuffID.StardustMinionBleed, 900 }, + { BuffID.ShadowFlame, 600 }, + { BuffID.Frostburn, 239 }, + { BuffID.Oiled, 510 }, + { BuffID.SoulDrain, 30 }, + { BuffID.EelWhipNPCDebuff, 240 }, + { BuffID.ScytheWhipEnemyDebuff, 240 }, + { BuffID.Wet, 1500 }, + { BuffID.DryadsWard, 120 }, + { BuffID.DryadsWardDebuff, 120 }, + { BuffID.Tipsy, 3659 }, + { BuffID.Lovestruck, 1800 }, + { BuffID.GelBalloonBuff, 1800 }, }; /// @@ -3057,7 +3145,7 @@ internal static int GetMaxPlaceStyle(int tileID) /// private static Dictionary directionalProjectiles = new Dictionary() { - ///Spears + //Spears { ProjectileID.DarkLance, ItemID.DarkLance}, { ProjectileID.Trident, ItemID.Trident}, { ProjectileID.Spear, ItemID.Spear}, @@ -3077,7 +3165,7 @@ internal static int GetMaxPlaceStyle(int tileID) { ProjectileID.MonkStaffT2, ItemID.MonkStaffT2}, { ProjectileID.ThunderSpear, ItemID.ThunderSpear}, { ProjectileID.GladiusStab, ItemID.Gladius}, - /// ShortSwords + // ShortSwords { ProjectileID.RulerStab, ItemID.Ruler }, { ProjectileID.CopperShortswordStab, ItemID.CopperShortsword }, { ProjectileID.TinShortswordStab, ItemID.TinShortsword }, diff --git a/TShockAPI/CLI/CommandLineParser.cs b/TShockAPI/CLI/CommandLineParser.cs index d62917fb6..6a80ff067 100644 --- a/TShockAPI/CLI/CommandLineParser.cs +++ b/TShockAPI/CLI/CommandLineParser.cs @@ -147,12 +147,11 @@ public CommandLineParser After(Action callback) /// public T Get(FlagSet flags) { - if (!_results.ContainsKey(flags)) + if (!_results.TryGetValue(flags, out object result)) { return default(T); } - object result = _results[flags]; Type t = typeof(T); if (t == typeof(string)) diff --git a/TShockAPI/CLI/FlagSet.cs b/TShockAPI/CLI/FlagSet.cs index ed559f3df..1a1c187f2 100644 --- a/TShockAPI/CLI/FlagSet.cs +++ b/TShockAPI/CLI/FlagSet.cs @@ -43,10 +43,7 @@ public class FlagSet : IEquatable /// Flags represented by this FlagSet public FlagSet(params string[] flags) { - if (flags == null) - { - throw new ArgumentNullException(nameof(flags)); - } + ArgumentNullException.ThrowIfNull(flags); _flags = flags.Select(f => f.ToLowerInvariant()); } diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 98cc639d1..8e1a594ec 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -35,7 +35,9 @@ You should have received a copy of the GNU General Public License using Microsoft.Xna.Framework; using TShockAPI.Localization; using System.Text.RegularExpressions; +using Terraria.Chat.Commands; using Terraria.DataStructures; +using Terraria.GameContent; using Terraria.GameContent.Creative; namespace TShockAPI @@ -113,8 +115,7 @@ public CommandDelegate CommandDelegate get { return commandDelegate; } set { - if (value == null) - throw new ArgumentNullException(); + ArgumentNullException.ThrowIfNull(value); commandDelegate = value; } @@ -134,10 +135,9 @@ public Command(string permissions, CommandDelegate cmd, params string[] names) public Command(CommandDelegate cmd, params string[] names) { - if (cmd == null) - throw new ArgumentNullException("cmd"); + ArgumentNullException.ThrowIfNull(cmd); if (names == null || names.Length < 1) - throw new ArgumentException("names"); + throw new ArgumentException("is null or empty", nameof(names)); AllowServer = true; CommandDelegate = cmd; @@ -647,6 +647,10 @@ public static void InitCommands() { HelpText = GetString("Shows the number of PVP deaths for all online players."), }); + add(new Command(BossDamage, "bossdamage") + { + HelpText = GetString("Shows recent boss kill contribution."), + }); TShockCommands = new ReadOnlyCollection(tshockCommands); } @@ -691,11 +695,10 @@ public static bool HandleCommand(TSPlayer player, string text) if (Hooks.PlayerHooks.OnPlayerCommand(player, cmdName, cmdText, args, ref cmds, cmdPrefix)) return true; - if (cmds.Count() == 0) + if (!cmds.Any()) { - if (player.AwaitingResponse.ContainsKey(cmdName)) + if (player.AwaitingResponse.TryGetValue(cmdName, out Action call)) { - Action call = player.AwaitingResponse[cmdName]; player.AwaitingResponse.Remove(cmdName); call(new CommandArgs(cmdText, player, args)); return true; @@ -743,7 +746,7 @@ public static bool HandleCommand(TSPlayer player, string text) /// /// /// - private static List ParseParameters(string str) + private static List ParseParameters(string str) { var ret = new List(); var sb = new StringBuilder(); @@ -860,7 +863,7 @@ private static void AttemptLogin(CommandArgs args) } else if (args.Parameters.Count == 2 && TShock.Config.Settings.AllowLoginAnyUsername) { - if (String.IsNullOrEmpty(args.Parameters[0])) + if (string.IsNullOrEmpty(args.Parameters[0])) { args.Player.SendErrorMessage(GetString("Bad login attempt.")); return; @@ -893,7 +896,7 @@ private static void AttemptLogin(CommandArgs args) } else if (account.VerifyPassword(password) || (usingUUID && account.UUID == args.Player.UUID && !TShock.Config.Settings.DisableUUIDLogin && - !String.IsNullOrWhiteSpace(args.Player.UUID))) + !string.IsNullOrWhiteSpace(args.Player.UUID))) { var group = TShock.Groups.GetGroupByName(account.Group); @@ -1265,7 +1268,7 @@ private static void WorldInfo(CommandArgs args) args.Player.SendInfoMessage(GetString($"Name: {(TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)}")); args.Player.SendInfoMessage(GetString("Size: {0}x{1}", Main.maxTilesX, Main.maxTilesY)); args.Player.SendInfoMessage(GetString($"ID: {Main.worldID}")); - args.Player.SendInfoMessage(GetString($"Seed: {WorldGen.currentWorldSeed}")); + args.Player.SendInfoMessage(GetString($"Seed: {Main.ActiveWorldFileData.SeedText}")); args.Player.SendInfoMessage(GetString($"Mode: {Main.GameMode}")); args.Player.SendInfoMessage(GetString($"Path: {Main.worldPathName}")); } @@ -1303,14 +1306,14 @@ private static void ViewAccountInfo(CommandArgs args) return; } - string username = String.Join(" ", args.Parameters); + string username = string.Join(" ", args.Parameters); if (!string.IsNullOrWhiteSpace(username)) { var account = TShock.UserAccounts.GetUserAccountByName(username); if (account != null) { DateTime LastSeen; - string Timezone = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Hours.ToString("+#;-#"); + string Timezone = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Hours.ToString("+#;-#"); if (DateTime.TryParse(account.LastAccessed, out LastSeen)) { @@ -1362,7 +1365,7 @@ private static void Kick(CommandArgs args) else { string reason = args.Parameters.Count > 1 - ? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) + ? string.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) : GetString("Misbehaviour."); if (!players[0].Kick(reason, !args.Player.RealPlayer, false, args.Player.Name)) { @@ -2035,7 +2038,7 @@ private static void SubstituteUserDo(CommandArgs args) return; } - string replacementCommand = String.Join(" ", args.Parameters.Select(p => p.Contains(" ") ? $"\"{p}\"" : p)); + string replacementCommand = string.Join(" ", args.Parameters.Select(p => p.Contains(' ') ? $"\"{p}\"" : p)); args.Player.tempGroup = new SuperAdminGroup(); HandleCommand(args.Player, replacementCommand); args.Player.tempGroup = null; @@ -2069,13 +2072,13 @@ private static void Off(CommandArgs args) } } - string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + String.Join(" ", args.Parameters) : GetString("Server shutting down!")); + string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + string.Join(" ", args.Parameters) : GetString("Server shutting down!")); TShock.Utils.StopServer(true, reason); } private static void OffNoSave(CommandArgs args) { - string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + String.Join(" ", args.Parameters) : GetString("Server shutting down.")); + string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + string.Join(" ", args.Parameters) : GetString("Server shutting down.")); Netplay.SaveOnServerExit = false; TShock.Utils.StopServer(false, reason); } @@ -2085,7 +2088,7 @@ private static void CheckUpdates(CommandArgs args) args.Player.SendInfoMessage(GetString("An update check has been queued. If an update is available, you will be notified shortly.")); try { - TShock.UpdateManager.UpdateCheckAsync(null); + _ = TShock.UpdateManager.UpdateCheckAsync(null); } catch (Exception) { @@ -2111,8 +2114,8 @@ private static void ManageRest(CommandArgs args) Dictionary restUsersTokens = new Dictionary(); foreach (Rests.SecureRest.TokenData tokenData in TShock.RestApi.Tokens.Values) { - if (restUsersTokens.ContainsKey(tokenData.Username)) - restUsersTokens[tokenData.Username]++; + if (restUsersTokens.TryGetValue(tokenData.Username, out int value)) + restUsersTokens[tokenData.Username] = ++value; else restUsersTokens.Add(tokenData.Username, 1); } @@ -2160,7 +2163,8 @@ private static void ManageRest(CommandArgs args) "invasion", "sandstorm", "rain", - "lanternsnight" + "lanternsnight", + "meteorshower" }; static readonly List _validInvasions = new List() { @@ -2177,8 +2181,8 @@ private static void ManageWorldEvent(CommandArgs args) if (args.Parameters.Count < 1) { args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}worldevent .", Specifier)); - args.Player.SendErrorMessage(GetString("Valid event types: {0}.", String.Join(", ", _validEvents))); - args.Player.SendErrorMessage(GetString("Valid invasion types if spawning an invasion: {0}.", String.Join(", ", _validInvasions))); + args.Player.SendErrorMessage(GetString("Valid event types: {0}.", string.Join(", ", _validEvents))); + args.Player.SendErrorMessage(GetString("Valid invasion types if spawning an invasion: {0}.", string.Join(", ", _validInvasions))); return; } @@ -2269,8 +2273,17 @@ void FailedPermissionCheck() LanternsNight(args); return; + case "meteorshower": + if (!args.Player.HasPermission(Permissions.managelanternsnightevent)) + { + FailedPermissionCheck(); + return; + } + MeteorShower(args); + return; + default: - args.Player.SendErrorMessage(GetString("Invalid event type. Valid event types: {0}.", String.Join(", ", _validEvents))); + args.Player.SendErrorMessage(GetString("Invalid event type. Valid event types: {0}.", string.Join(", ", _validEvents))); return; } } @@ -2363,7 +2376,7 @@ private static void Invade(CommandArgs args) if (args.Parameters.Count < 2) { args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}worldevent invasion [invasion type] [invasion wave].", Specifier)); - args.Player.SendErrorMessage(GetString("Valid invasion types: {0}.", String.Join(", ", _validInvasions))); + args.Player.SendErrorMessage(GetString("Valid invasion types: {0}.", string.Join(", ", _validInvasions))); return; } @@ -2431,7 +2444,7 @@ private static void Invade(CommandArgs args) break; default: - args.Player.SendErrorMessage(GetString("Invalid invasion type. Valid invasion types: {0}.", String.Join(", ", _validInvasions))); + args.Player.SendErrorMessage(GetString("Invalid invasion type. Valid invasion types: {0}.", string.Join(", ", _validInvasions))); break; } } @@ -2463,52 +2476,68 @@ private static void Sandstorm(CommandArgs args) private static void Rain(CommandArgs args) { - bool slime = false; - if (args.Parameters.Count > 1 && args.Parameters[1].ToLowerInvariant() == "slime") - { - slime = true; - } + var type = args.Parameters.Count > 1 ? args.Parameters[1].ToLowerInvariant() : "normal"; - if (!slime) + switch (type) { - args.Player.SendInfoMessage(GetString("Use \"{0}worldevent rain slime\" to start slime rain!", Specifier)); - } + case "slime": + if (Main.raining) + { + args.Player.SendErrorMessage(GetString( + "Slime rain cannot be activated during normal rain. Stop the normal rainstorm and try again.")); + return; + } - if (slime && Main.raining) //Slime rain cannot be activated during normal rain - { - args.Player.SendErrorMessage(GetString("Slime rain cannot be activated during normal rain. Stop the normal rainstorm and try again.")); - return; - } + if (Main.slimeRain) + { + Main.StopSlimeRain(false); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} ended the slime rain.", args.Player.Name)); + } + else + { + Main.StartSlimeRain(false); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} caused it to rain slime.", args.Player.Name)); + } - if (slime && Main.slimeRain) //Toggle slime rain off - { - Main.StopSlimeRain(false); - TSPlayer.All.SendData(PacketTypes.WorldInfo); - TSPlayer.All.SendInfoMessage(GetString("{0} ended the slime rain.", args.Player.Name)); - return; - } + break; - if (slime && !Main.slimeRain) //Toggle slime rain on - { - Main.StartSlimeRain(false); - TSPlayer.All.SendData(PacketTypes.WorldInfo); - TSPlayer.All.SendInfoMessage(GetString("{0} caused it to rain slime.", args.Player.Name)); - } + case "coin": + if (Main.coinRain != 0) + { + Main.StopRain(); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} ended the coin rain.", args.Player.Name)); + } + else + { + Main.StartRain(garenteeCoinRain: true); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} caused it to coin rain.", args.Player.Name)); + } - if (Main.raining && !slime) //Toggle rain off - { - Main.StopRain(); - TSPlayer.All.SendData(PacketTypes.WorldInfo); - TSPlayer.All.SendInfoMessage(GetString("{0} ended the rain.", args.Player.Name)); - return; - } + break; - if (!Main.raining && !slime) //Toggle rain on - { - Main.StartRain(); - TSPlayer.All.SendData(PacketTypes.WorldInfo); - TSPlayer.All.SendInfoMessage(GetString("{0} caused it to rain.", args.Player.Name)); - return; + default: + if (Main.raining) + { + Main.StopRain(); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} ended the rain.", args.Player.Name)); + } + else + { + Main.StartRain(); + TSPlayer.All.SendData(PacketTypes.WorldInfo); + TSPlayer.All.SendInfoMessage(GetString("{0} caused it to rain.", args.Player.Name)); + } + + args.Player.SendInfoMessage(GetString("Use \"{0}worldevent rain slime\" to start slime rain!", + Specifier)); + args.Player.SendInfoMessage(GetString("Use \"{0}worldevent coin slime\" to start coin rain!", + Specifier)); + break; } } @@ -2539,6 +2568,20 @@ private static void LanternsNight(CommandArgs args) } } + private static void MeteorShower(CommandArgs args) + { + if (WorldGen.meteorShowerCount > 0) + { + WorldGen.meteorShowerCount = 0; + TSPlayer.All.SendInfoMessage(GetString("{0} stopped the meteor shower.", args.Player.Name)); + } + else + { + WorldGen.StartMeteorShower(); + TSPlayer.All.SendInfoMessage(GetString("{0} started a meteor shower.", args.Player.Name)); + } + } + private static void ClearAnglerQuests(CommandArgs args) { if (args.Parameters.Count > 0) @@ -2579,7 +2622,7 @@ private static void ChangeWorldMode(CommandArgs args) if (args.Parameters.Count < 1) { args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}worldmode .", Specifier)); - args.Player.SendErrorMessage(GetString("Valid world modes: {0}", String.Join(", ", _worldModes.Keys))); + args.Player.SendErrorMessage(GetString("Valid world modes: {0}", string.Join(", ", _worldModes.Keys))); return; } @@ -2589,7 +2632,7 @@ private static void ChangeWorldMode(CommandArgs args) { if (mode < 0 || mode > 3) { - args.Player.SendErrorMessage(GetString("Invalid world mode. Valid world modes: {0}", String.Join(", ", _worldModes.Keys))); + args.Player.SendErrorMessage(GetString("Invalid world mode. Valid world modes: {0}", string.Join(", ", _worldModes.Keys))); return; } } @@ -2599,7 +2642,7 @@ private static void ChangeWorldMode(CommandArgs args) } else { - args.Player.SendErrorMessage(GetString("Invalid mode world mode. Valid modes: {0}", String.Join(", ", _worldModes.Keys))); + args.Player.SendErrorMessage(GetString("Invalid mode world mode. Valid modes: {0}", string.Join(", ", _worldModes.Keys))); return; } @@ -2901,7 +2944,7 @@ private static void SpawnMob(CommandArgs args) var npc = npcs[0]; if (npc.type >= 1 && npc.type < Terraria.ID.NPCID.Count && npc.type != 113) { - TSPlayer.Server.SpawnNPC(npc.netID, npc.FullName, amount, args.Player.TileX, args.Player.TileY, 50, 20); + TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY, 50, 20); if (args.Silent) { args.Player.SendSuccessMessage(GetPluralString("Spawned {0} {1} time.", "Spawned {0} {1} times.", amount, npc.FullName, amount)); @@ -3092,7 +3135,7 @@ private static void TPHere(CommandArgs args) return; } - string playerName = String.Join(" ", args.Parameters); + string playerName = string.Join(" ", args.Parameters); var players = TSPlayer.FindByNameOrID(playerName); if (players.Count == 0) { @@ -3141,7 +3184,7 @@ private static void TPNpc(CommandArgs args) var matches = new List(); foreach (var npc in Main.npc.Where(npc => npc.active)) { - var englishName = EnglishLanguage.GetNpcNameById(npc.netID); + var englishName = EnglishLanguage.GetNpcNameById(npc.type); if (string.Equals(npc.FullName, npcStr, StringComparison.InvariantCultureIgnoreCase) || string.Equals(englishName, npcStr, StringComparison.InvariantCultureIgnoreCase)) @@ -3149,7 +3192,7 @@ private static void TPNpc(CommandArgs args) matches = new List { npc }; break; } - if (npc.FullName.ToLowerInvariant().StartsWith(npcStr.ToLowerInvariant()) || + if (npc.FullName.StartsWith(npcStr, StringComparison.InvariantCultureIgnoreCase) || englishName?.StartsWith(npcStr, StringComparison.InvariantCultureIgnoreCase) == true) matches.Add(npc); } @@ -3175,7 +3218,7 @@ private static void GetPos(CommandArgs args) var player = args.Player.Name; if (args.Parameters.Count > 0) { - player = String.Join(" ", args.Parameters); + player = string.Join(" ", args.Parameters); } var players = TSPlayer.FindByNameOrID(player); @@ -3264,7 +3307,7 @@ private static void Warp(CommandArgs args) }); #endregion } - else if (args.Parameters[0].ToLower() == "add" && hasManageWarpPermission) + else if (args.Parameters[0].Equals("add", StringComparison.OrdinalIgnoreCase) && hasManageWarpPermission) { #region Add warp if (args.Parameters.Count == 2) @@ -3287,7 +3330,7 @@ private static void Warp(CommandArgs args) args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}warp add [name].", Specifier)); #endregion } - else if (args.Parameters[0].ToLower() == "del" && hasManageWarpPermission) + else if (args.Parameters[0].Equals("del", StringComparison.OrdinalIgnoreCase) && hasManageWarpPermission) { #region Del warp if (args.Parameters.Count == 2) @@ -3304,14 +3347,14 @@ private static void Warp(CommandArgs args) args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}warp del [name].", Specifier)); #endregion } - else if (args.Parameters[0].ToLower() == "hide" && hasManageWarpPermission) + else if (args.Parameters[0].Equals("hide", StringComparison.OrdinalIgnoreCase) && hasManageWarpPermission) { #region Hide warp if (args.Parameters.Count == 3) { string warpName = args.Parameters[1]; bool state = false; - if (Boolean.TryParse(args.Parameters[2], out state)) + if (bool.TryParse(args.Parameters[2], out state)) { if (TShock.Warps.Hide(args.Parameters[1], state)) { @@ -3330,7 +3373,7 @@ private static void Warp(CommandArgs args) args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}warp hide [name] .", Specifier)); #endregion } - else if (args.Parameters[0].ToLower() == "send" && args.Player.HasPermission(Permissions.tpothers)) + else if (args.Parameters[0].Equals("send", StringComparison.OrdinalIgnoreCase) && args.Player.HasPermission(Permissions.tpothers)) { #region Warp send if (args.Parameters.Count < 3) @@ -3370,7 +3413,7 @@ private static void Warp(CommandArgs args) } else { - string warpName = String.Join(" ", args.Parameters); + string warpName = string.Join(" ", args.Parameters); var warp = TShock.Warps.Find(warpName); if (warp != null) { @@ -3405,7 +3448,7 @@ private static void Group(CommandArgs args) string groupName = args.Parameters[1]; args.Parameters.RemoveRange(0, 2); - string permissions = String.Join(",", args.Parameters); + string permissions = string.Join(",", args.Parameters); try { @@ -3652,7 +3695,7 @@ private static void Group(CommandArgs args) { string newColor = args.Parameters[2]; - String[] parts = newColor.Split(','); + string[] parts = newColor.Split(','); byte r; byte g; byte b; @@ -3841,7 +3884,7 @@ private static void ItemBan(CommandArgs args) } else if (items.Count > 1) { - args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.type})")); } else { @@ -3892,7 +3935,7 @@ private static void ItemBan(CommandArgs args) } else if (items.Count > 1) { - args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.type})")); } else { @@ -3937,7 +3980,7 @@ private static void ItemBan(CommandArgs args) } else if (items.Count > 1) { - args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.type})")); } else { @@ -3963,7 +4006,7 @@ private static void ItemBan(CommandArgs args) } else if (items.Count > 1) { - args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.type})")); } else { @@ -4064,7 +4107,7 @@ private static void ProjectileBan(CommandArgs args) return; } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) { TShock.ProjectileBans.AddNewBan(id); args.Player.SendSuccessMessage(GetString("Banned projectile {0}.", id)); @@ -4084,7 +4127,7 @@ private static void ProjectileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) { if (!TShock.Groups.GroupExists(args.Parameters[2])) { @@ -4121,7 +4164,7 @@ private static void ProjectileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) { TShock.ProjectileBans.RemoveBan(id); args.Player.SendSuccessMessage(GetString("Unbanned projectile {0}.", id)); @@ -4142,7 +4185,7 @@ private static void ProjectileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id > 0 && id < Terraria.ID.ProjectileID.Count) { if (!TShock.Groups.GroupExists(args.Parameters[2])) { @@ -4202,7 +4245,7 @@ private static void ProjectileBan(CommandArgs args) int pageNumber; if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) return; - IEnumerable projectileIds = from projectileBan in TShock.ProjectileBans.ProjectileBans + IEnumerable projectileIds = from projectileBan in TShock.ProjectileBans.ProjectileBans select projectileBan.ID; PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(projectileIds), new PaginationTools.Settings @@ -4240,7 +4283,7 @@ private static void TileBan(CommandArgs args) return; } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) { TShock.TileBans.AddNewBan(id); args.Player.SendSuccessMessage(GetString("Banned tile {0}.", id)); @@ -4260,7 +4303,7 @@ private static void TileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) { if (!TShock.Groups.GroupExists(args.Parameters[2])) { @@ -4297,7 +4340,7 @@ private static void TileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) { TShock.TileBans.RemoveBan(id); args.Player.SendSuccessMessage(GetString("Unbanned tile {0}.", id)); @@ -4318,7 +4361,7 @@ private static void TileBan(CommandArgs args) } short id; - if (Int16.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) + if (short.TryParse(args.Parameters[1], out id) && id >= 0 && id < Terraria.ID.TileID.Count) { if (!TShock.Groups.GroupExists(args.Parameters[2])) { @@ -4378,7 +4421,7 @@ private static void TileBan(CommandArgs args) int pageNumber; if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) return; - IEnumerable tileIds = from tileBan in TShock.TileBans.TileBans + IEnumerable tileIds = from tileBan in TShock.TileBans.TileBans select tileBan.ID; PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(tileIds), new PaginationTools.Settings @@ -4468,7 +4511,7 @@ private static void MaxSpawns(CommandArgs args) return; } - if (String.Equals(args.Parameters[0], "default", StringComparison.CurrentCultureIgnoreCase)) + if (string.Equals(args.Parameters[0], "default", StringComparison.CurrentCultureIgnoreCase)) { TShock.Config.Settings.DefaultMaximumSpawns = NPC.defaultMaxSpawns = 5; if (args.Silent) @@ -4508,7 +4551,7 @@ private static void SpawnRate(CommandArgs args) return; } - if (String.Equals(args.Parameters[0], "default", StringComparison.CurrentCultureIgnoreCase)) + if (string.Equals(args.Parameters[0], "default", StringComparison.CurrentCultureIgnoreCase)) { TShock.Config.Settings.DefaultSpawnRate = NPC.defaultSpawnRate = 600; if (args.Silent) @@ -4717,7 +4760,7 @@ private static void Region(CommandArgs args) { if (!args.Player.TempPoints.Any(p => p == Point.Zero)) { - string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); + string regionName = string.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); var x = Math.Min(args.Player.TempPoints[0].X, args.Player.TempPoints[1].X); var y = Math.Min(args.Player.TempPoints[0].Y, args.Player.TempPoints[1].Y); var width = Math.Abs(args.Player.TempPoints[0].X - args.Player.TempPoints[1].X); @@ -4749,14 +4792,14 @@ private static void Region(CommandArgs args) if (args.Parameters.Count == 3) { string regionName = args.Parameters[1]; - if (args.Parameters[2].ToLower() == "true") + if (args.Parameters[2].Equals("true", StringComparison.OrdinalIgnoreCase)) { if (TShock.Regions.SetRegionState(regionName, true)) args.Player.SendInfoMessage(GetString("Marked region {0} as protected.", regionName)); else args.Player.SendErrorMessage(GetString($"Could not find the region {regionName}.")); } - else if (args.Parameters[2].ToLower() == "false") + else if (args.Parameters[2].Equals("false", StringComparison.OrdinalIgnoreCase)) { if (TShock.Regions.SetRegionState(regionName, false)) args.Player.SendInfoMessage(GetString("Marked region {0} as unprotected.", regionName)); @@ -4774,7 +4817,7 @@ private static void Region(CommandArgs args) { if (args.Parameters.Count > 1) { - string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); + string regionName = string.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); if (TShock.Regions.DeleteRegion(regionName)) { args.Player.SendInfoMessage(GetString("Deleted region \"{0}\".", regionName)); @@ -5432,7 +5475,7 @@ private static void SetupToken(CommandArgs args) } int givenCode; - if (!Int32.TryParse(args.Parameters[0], out givenCode) || givenCode != TShock.SetupToken) + if (!int.TryParse(args.Parameters[0], out givenCode) || givenCode != TShock.SetupToken) { args.Player.SendErrorMessage(GetString("Incorrect setup code. This incident has been logged.")); TShock.Log.Warn(args.Player.IP + " attempted to use an incorrect setup code."); @@ -5461,7 +5504,7 @@ private static void ThirdPerson(CommandArgs args) if (args.Player.mute) args.Player.SendErrorMessage(GetString("You are muted.")); else - TSPlayer.All.SendMessage(GetString("*{0} {1}", args.Player.Name, String.Join(" ", args.Parameters)), 205, 133, 63); + TSPlayer.All.SendMessage(GetString("*{0} {1}", args.Player.Name, string.Join(" ", args.Parameters)), 205, 133, 63); } private static void PartyChat(CommandArgs args) @@ -5477,7 +5520,7 @@ private static void PartyChat(CommandArgs args) args.Player.SendErrorMessage(GetString("You are muted.")); else if (playerTeam != 0) { - string msg = GetString("<{0}> {1}", args.Player.Name, String.Join(" ", args.Parameters)); + string msg = GetString("<{0}> {1}", args.Player.Name, string.Join(" ", args.Parameters)); foreach (TSPlayer player in TShock.Players) { if (player != null && player.Active && player.Team == playerTeam) @@ -5525,7 +5568,7 @@ private static void Mute(CommandArgs args) { string reason = GetString("No reason specified."); if (args.Parameters.Count > 1) - reason = String.Join(" ", args.Parameters.ToArray(), 1, args.Parameters.Count - 1); + reason = string.Join(" ", args.Parameters.ToArray(), 1, args.Parameters.Count - 1); var plr = players[0]; plr.mute = true; if (args.Silent) @@ -5875,6 +5918,27 @@ private static void ShowAllPVPDeath(CommandArgs args) args.Player.SendErrorMessage(string.Join('\n',deathsRank)); } + private static void BossDamage(CommandArgs args) + { + var attempts = NPCDamageTracker.RecentAttempts().ToList(); + + if (attempts.Count == 0) + { + args.Player.SendWarningMessage(GetString("No recent boss kill data found.")); + return; + } + foreach (var recentAttempt in attempts) + { + for (var playerId = 0; playerId < byte.MaxValue; ++playerId) + { + if (Main.player[playerId].active) + { + args.Player.SendSuccessMessage(recentAttempt.GetReport(Main.player[playerId]).ToString()); + } + } + } + } + #endregion General Commands @@ -5919,7 +5983,7 @@ private static void Clear(CommandArgs args) if (Main.item[i].active && dX * dX + dY * dY <= radius * radius * 256f) { - Main.item[i].active = false; + Main.item[i].TurnToAir(); everyone.SendData(PacketTypes.ItemDrop, "", i); cleared++; } @@ -5998,7 +6062,7 @@ private static void Kill(CommandArgs args) return; } - string targetName = String.Join(" ", args.Parameters); + string targetName = string.Join(" ", args.Parameters); var players = TSPlayer.FindByNameOrID(targetName); if (players.Count == 0) @@ -6042,7 +6106,7 @@ private static void Respawn(CommandArgs args) args.Player.SendErrorMessage(GetString("You do not have permission to respawn another player.")); return; } - string plStr = String.Join(" ", args.Parameters); + string plStr = string.Join(" ", args.Parameters); var players = TSPlayer.FindByNameOrID(plStr); if (players.Count == 0) { @@ -6109,13 +6173,13 @@ private static void Butcher(CommandArgs args) user.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})")); return; } - npcId = npcs[0].netID; + npcId = npcs[0].type; } int kills = 0; for (int i = 0; i < Main.npc.Length; i++) { - if (Main.npc[i].active && ((npcId == 0 && !Main.npc[i].townNPC && Main.npc[i].netID != NPCID.TargetDummy) || Main.npc[i].netID == npcId)) + if (Main.npc[i].active && ((npcId == 0 && !Main.npc[i].townNPC && Main.npc[i].type != NPCID.TargetDummy) || Main.npc[i].type == npcId)) { TSPlayer.Server.StrikeNPC(i, (int)(Main.npc[i].life + (Main.npc[i].defense * 0.6)), 0, 0); kills++; @@ -6162,7 +6226,7 @@ private static void Item(CommandArgs args) } else if (matchedItems.Count > 1) { - args.Player.SendMultipleMatchError(matchedItems.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(matchedItems.Select(i => $"{i.Name}({i.type})")); return; } else @@ -6255,13 +6319,13 @@ private static void RenameNPC(CommandArgs args) } else { - npcId = npcs[0].netID; + npcId = npcs[0].type; } } int done = 0; for (int i = 0; i < Main.npc.Length; i++) { - if (Main.npc[i].active && ((npcId == 0 && !Main.npc[i].townNPC) || (Main.npc[i].netID == npcId && Main.npc[i].townNPC))) + if (Main.npc[i].active && ((npcId == 0 && !Main.npc[i].townNPC) || (Main.npc[i].type == npcId && Main.npc[i].townNPC))) { Main.npc[i].GivenName = args.Parameters[1]; NetMessage.SendData(56, -1, -1, NetworkText.FromLiteral(args.Parameters[1]), i, 0f, 0f, 0f, 0); @@ -6310,7 +6374,7 @@ private static void Give(CommandArgs args) } else if (items.Count > 1) { - args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})")); + args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.type})")); } else { @@ -6788,7 +6852,7 @@ private static void ToggleGodMode(CommandArgs args) args.Player.SendErrorMessage(GetString("You do not have permission to god mode another player.")); return; } - string plStr = String.Join(" ", args.Parameters); + string plStr = string.Join(" ", args.Parameters); var players = TSPlayer.FindByNameOrID(plStr); if (players.Count == 0) { diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs index 4573c5bf3..b8d2bfd57 100644 --- a/TShockAPI/Configuration/TShockConfig.cs +++ b/TShockAPI/Configuration/TShockConfig.cs @@ -15,6 +15,10 @@ public class TShockSettings #region Server Settings + /// Disables update checking on server start and periodically while running. + [Description("Disables update checking on server start and periodically while running.")] + public bool DisabledUpdateCheck = true; + /// The server password required to join the server. [Description("The server password required to join the server.")] public string ServerPassword = ""; diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index bd57bf8fb..0a9b16837 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -107,6 +107,7 @@ public void TryConvertBans() SqlType.Mysql => database.QueryScalar("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName), SqlType.Sqlite => database.QueryScalar("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name = 'Bans'"), SqlType.Postgres => database.QueryScalar("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_name = 'Bans'"), + _ => 0, }; if (res != 0) @@ -347,9 +348,9 @@ public bool RemoveBan(int ticketNumber, bool fullDelete = false) /// public Ban GetBanById(int id) { - if (Bans.ContainsKey(id)) + if (Bans.TryGetValue(id, out Ban value)) { - return Bans[id]; + return value; } using var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE TicketNumber=@0", id); diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs index db14488c4..77427e1bf 100644 --- a/TShockAPI/DB/CharacterManager.cs +++ b/TShockAPI/DB/CharacterManager.cs @@ -71,7 +71,9 @@ public CharacterManager(IDbConnection db) new SqlColumn("unlockedSuperCart", MySqlDbType.Int32), new SqlColumn("enabledSuperCart", MySqlDbType.Int32), new SqlColumn("deathsPVE", MySqlDbType.Int32), - new SqlColumn("deathsPVP", MySqlDbType.Int32) + new SqlColumn("deathsPVP", MySqlDbType.Int32), + new SqlColumn("voiceVariant", MySqlDbType.Int32), + new SqlColumn("voicePitchOffset", MySqlDbType.Float) ); SqlTableCreator creator = new(db, db.GetSqlQueryBuilder()); @@ -136,6 +138,8 @@ public PlayerData GetPlayerData(TSPlayer player, int acctid) playerData.enabledSuperCart = reader.Get("enabledSuperCart"); playerData.deathsPVE = reader.Get("deathsPVE"); playerData.deathsPVP = reader.Get("deathsPVP"); + playerData.voiceVariant = reader.Get("voiceVariant"); + playerData.voicePitchOffset = reader.Get("voicePitchOffset"); return playerData; } } @@ -182,6 +186,7 @@ public bool SeedInitialData(UserAccount account) /// Inserts player data to the tsCharacter database table /// /// player to take data from + /// If , the permission will be checked /// true if inserted successfully public bool InsertPlayerData(TSPlayer player, bool fromCommand = false) { @@ -204,8 +209,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, 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); + "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, voiceVariant, voicePitchOffset) 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, @36, @37);", + 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, player.TPlayer.voiceVariant, player.TPlayer.voicePitchOffset); return true; } catch (Exception ex) @@ -218,8 +223,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, 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); + "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, voiceVariant = @36, voicePitchOffset= @37 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, player.TPlayer.voiceVariant, player.TPlayer.voicePitchOffset); return true; } catch (Exception ex) @@ -274,7 +279,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, @34, @35);", + "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, voiceVariant, voicePitchOffset) 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, @36, @37);", player.Account.ID, playerData.health, playerData.maxHealth, @@ -310,7 +315,9 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) playerData.unlockedSuperCart, playerData.enabledSuperCart, playerData.deathsPVE, - playerData.deathsPVP + playerData.deathsPVP, + player.TPlayer.voiceVariant, + player.TPlayer.voicePitchOffset ); return true; } @@ -324,7 +331,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, deathsPVE = @34, deathsPVP = @35 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, voiceVariant = @36, voicePitchOffset= @37 WHERE Account = @5;", playerData.health, playerData.maxHealth, playerData.mana, @@ -360,7 +367,9 @@ public bool InsertSpecificPlayerData(TSPlayer player, PlayerData data) playerData.unlockedSuperCart, playerData.enabledSuperCart, playerData.deathsPVE, - playerData.deathsPVP + playerData.deathsPVP, + player.TPlayer.voiceVariant, + player.TPlayer.voicePitchOffset ); return true; } diff --git a/TShockAPI/DB/DbBuilder.cs b/TShockAPI/DB/DbBuilder.cs index 98aedcdf9..643e5525d 100644 --- a/TShockAPI/DB/DbBuilder.cs +++ b/TShockAPI/DB/DbBuilder.cs @@ -35,7 +35,6 @@ public DbBuilder(TShock caller, TShockConfig config, string savePath) /// /// Builds a DB connection based on the provided configuration. /// - /// The TShock configuration. /// /// Default settings will result in a local sqlite database file named "tshock.db" in the current directory to be used as server DB. /// diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index 4d34098e5..28529ce3b 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -172,7 +172,6 @@ public GroupManager(IDbConnection db) Permissions.item, Permissions.give, Permissions.heal, - Permissions.immunetoban, Permissions.usebanneditem, Permissions.allowclientsideworldedit, Permissions.buff, diff --git a/TShockAPI/DB/Queries/MysqlQueryBuilder.cs b/TShockAPI/DB/Queries/MysqlQueryBuilder.cs index 851859608..dcda63d81 100644 --- a/TShockAPI/DB/Queries/MysqlQueryBuilder.cs +++ b/TShockAPI/DB/Queries/MysqlQueryBuilder.cs @@ -80,7 +80,7 @@ public override string DbTypeToString(MySqlDbType type, int? length) return ret + (length is not null ? "({0})".SFormat((int)length) : ""); } - throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); + throw new NotImplementedException(Enum.GetName(type)); } /// diff --git a/TShockAPI/DB/Queries/PostgresQueryBuilder.cs b/TShockAPI/DB/Queries/PostgresQueryBuilder.cs index dd92121e5..0c9a6377c 100644 --- a/TShockAPI/DB/Queries/PostgresQueryBuilder.cs +++ b/TShockAPI/DB/Queries/PostgresQueryBuilder.cs @@ -43,7 +43,7 @@ public class PostgresQueryBuilder : GenericQueryBuilder MySqlDbType.Int64 => "BIGINT", MySqlDbType.DateTime => "TIMESTAMP", - _ => throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)) + _ => throw new NotImplementedException(Enum.GetName(type)) }; /// @@ -79,7 +79,7 @@ public override string CreateTable(SqlTable table) .Where(c => c.Unique).Select(c => c.Name) .ToArray(); // No re-enumeration - return $"CREATE TABLE {EscapeTableName(table.Name)} ({string.Join(", ", columns)} {(uniques.Any() ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "")})"; + return $"CREATE TABLE {EscapeTableName(table.Name)} ({string.Join(", ", columns)} {(uniques.Length != 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "")})"; } /// diff --git a/TShockAPI/DB/Queries/SqliteQueryBuilder.cs b/TShockAPI/DB/Queries/SqliteQueryBuilder.cs index 72de17d51..4de64d8ba 100644 --- a/TShockAPI/DB/Queries/SqliteQueryBuilder.cs +++ b/TShockAPI/DB/Queries/SqliteQueryBuilder.cs @@ -48,7 +48,7 @@ public override string CreateTable(SqlTable table) var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns), - uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); + uniques.Any() ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); } /// @@ -91,7 +91,7 @@ public override string DbTypeToString(MySqlDbType type, int? length) return ret; } - throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); + throw new NotImplementedException(Enum.GetName(type)); } /// diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs index d657980ac..ad052f231 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/DB/RegionManager.cs @@ -84,7 +84,7 @@ public void Reload() string groups = reader.Get("Groups"); int z = reader.Get("Z"); - string[] splitids = mergedids.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + string[] splitids = mergedids.Split(',', StringSplitOptions.RemoveEmptyEntries); Region r = new Region(id, new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString(), z); r.SetAllowedGroups(groups); @@ -779,8 +779,7 @@ public void SetAllowedIDs(string ids) foreach (string id in idArr) { - int i = 0; - if (int.TryParse(id, out i) && i != 0) + if (int.TryParse(id, out var i) && i != 0) { idList.Add(i); } diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index e705e3fcb..3f12798d7 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -20,16 +20,15 @@ You should have received a copy of the GNU General Public License using System.Data; using System.Collections.Generic; using System.Linq; -using System.Text; using MySql.Data.MySqlClient; using System.Text.RegularExpressions; using BCrypt.Net; -using System.Security.Cryptography; -using TShockAPI.DB.Queries; using TShockAPI.Hooks; namespace TShockAPI.DB { + extern alias BCryptNext; + /// UserAccountManager - Methods for dealing with database user accounts and other related functionality within TShock. public class UserAccountManager { @@ -348,10 +347,10 @@ public List GetUserAccounts() } /// - /// Gets all user accounts from the database with a username that starts with or contains + /// Gets all user accounts from the database with a username that starts with or contains /// /// Rough username search. "n" will match "n", "na", "nam", "name", etc - /// If is not the first part of the username. If true then "name" would match "name", "username", "wordsnamewords", etc + /// If is not the first part of the username. If true then "name" would match "name", "username", "wordsnamewords", etc /// Matching users or null if exception is thrown public List GetUserAccountsByName(string username, bool notAtStart = false) { @@ -465,14 +464,14 @@ public bool VerifyPassword(string password) { try { - if (BCrypt.Net.BCrypt.Verify(password, Password)) + if (BCryptNext::BCrypt.Net.BCrypt.Verify(password, Password)) { // If necessary, perform an upgrade to the highest work factor. UpgradePasswordWorkFactor(password); return true; } } - catch (SaltParseException) + catch (BCryptNext::BCrypt.Net.SaltParseException) { TShock.Log.ConsoleError(GetString($"Unable to verify the password hash for user {Name} ({ID})")); return false; @@ -516,16 +515,16 @@ public void CreateBCryptHash(string password) if (password.Trim().Length < Math.Max(4, TShock.Config.Settings.MinimumPasswordLength)) { int minLength = TShock.Config.Settings.MinimumPasswordLength; - throw new ArgumentOutOfRangeException("password", GetString($"Password must be at least {minLength} characters.")); + throw new ArgumentOutOfRangeException(nameof(password), GetString($"Password must be at least {minLength} characters.")); } try { - Password = BCrypt.Net.BCrypt.HashPassword(password.Trim(), TShock.Config.Settings.BCryptWorkFactor); + Password = BCryptNext::BCrypt.Net.BCrypt.HashPassword(password.Trim(), TShock.Config.Settings.BCryptWorkFactor); } catch (ArgumentOutOfRangeException) { TShock.Log.ConsoleError(GetString("Invalid BCrypt work factor in config file! Creating new hash using default work factor.")); - Password = BCrypt.Net.BCrypt.HashPassword(password.Trim()); + Password = BCryptNext::BCrypt.Net.BCrypt.HashPassword(password.Trim()); } } @@ -537,9 +536,9 @@ public void CreateBCryptHash(string password, int workFactor) if (password.Trim().Length < Math.Max(4, TShock.Config.Settings.MinimumPasswordLength)) { int minLength = TShock.Config.Settings.MinimumPasswordLength; - throw new ArgumentOutOfRangeException("password", GetString($"Password must be at least {minLength} characters.")); + throw new ArgumentOutOfRangeException(nameof(password), GetString($"Password must be at least {minLength} characters.")); } - Password = BCrypt.Net.BCrypt.HashPassword(password.Trim(), workFactor); + Password = BCryptNext::BCrypt.Net.BCrypt.HashPassword(password.Trim(), workFactor); } #region IEquatable diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs index efbf68b78..a7f471a4f 100644 --- a/TShockAPI/Extensions/DbExt.cs +++ b/TShockAPI/Extensions/DbExt.cs @@ -184,27 +184,27 @@ public static IDbConnection CloneEx(this IDbConnection conn) (s, i) => s.IsDBNull(i) ? null : (object)s.GetByte(i) }, { - typeof (Int16), + typeof (short), (s, i) => s.GetInt16(i) }, { - typeof (Int16?), + typeof (short?), (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt16(i) }, { - typeof (Int32), + typeof (int), (s, i) => s.GetInt32(i) }, { - typeof (Int32?), + typeof (int?), (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i) }, { - typeof (Int64), + typeof (long), (s, i) => s.GetInt64(i) }, { - typeof (Int64?), + typeof (long?), (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i) }, { diff --git a/TShockAPI/Extensions/LinqExt.cs b/TShockAPI/Extensions/LinqExt.cs index 30c0db3c0..baa79fe7d 100644 --- a/TShockAPI/Extensions/LinqExt.cs +++ b/TShockAPI/Extensions/LinqExt.cs @@ -26,8 +26,8 @@ public static class LinqExt { public static void ForEach(this IEnumerable source, Action action) { - if (source == null) throw new ArgumentNullException("source"); - if (action == null) throw new ArgumentNullException("action"); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(action); foreach (T item in source) action(item); diff --git a/TShockAPI/GeoIPCountry.cs b/TShockAPI/GeoIPCountry.cs index 6df904308..05d9fbf52 100644 --- a/TShockAPI/GeoIPCountry.cs +++ b/TShockAPI/GeoIPCountry.cs @@ -293,7 +293,7 @@ private long FindCountryCode(long offset, long ipnum, int depth) throw new IOException("Cannot seek GeoIP database"); _geodata.Seek(6*offset, SeekOrigin.Begin); - _geodata.Read(buffer, 0, 6); + _geodata.ReadExactly(buffer, 0, 6); for (int i = 0; i < 2; i++) { diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 402270fde..d7d972f18 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -34,12 +34,15 @@ You should have received a copy of the GNU General Public License using Terraria.GameContent.Tile_Entities; using Terraria.Localization; using Microsoft.Xna.Framework; +using NuGet.Protocol.Plugins; using TShockAPI.Localization; using TShockAPI.Models; using TShockAPI.Models.PlayerUpdate; using TShockAPI.Models.Projectiles; using Terraria.Net; using Terraria.GameContent.NetModules; +using Terraria.GameContent; +using Terraria.GameContent.Items; namespace TShockAPI { @@ -110,6 +113,7 @@ public static void InitGetDataHandler() { PacketTypes.PlayerAnimation, HandlePlayerAnimation }, { PacketTypes.PlayerMana, HandlePlayerMana }, { PacketTypes.PlayerTeam, HandlePlayerTeam }, + { PacketTypes.TeamChangeFromUI, HandlePlayerTeam }, { PacketTypes.SignRead, HandleSignRead }, { PacketTypes.SignNew, HandleSign }, { PacketTypes.LiquidSet, HandleLiquidSet }, @@ -152,10 +156,31 @@ public static void InitGetDataHandler() { PacketTypes.FishOutNPC, HandleFishOutNPC }, { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing }, { PacketTypes.SyncCavernMonsterType, HandleSyncCavernMonsterType }, - { PacketTypes.SyncLoadout, HandleSyncLoadout } + { PacketTypes.SyncLoadout, HandleSyncLoadout }, + { PacketTypes.SyncItemCannotBeTakenByEnemies, HandleItemDrop }, + { PacketTypes.SyncItemsWithShimmer, HandleItemDrop }, + { PacketTypes.SpectatePlayer, HandleSyncPlayerSpectating } }; } - + [Conditional("FALSE")] + private static void NetModuleIDCheck() + { + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Liquid); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Text); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Ping); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Ambience); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Bestiary); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.CreativePowers); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.CreativeUnlocksPlayerReport); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.TeleportPylon); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Particles); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.CreativePowerPermissions); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.Banners); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.CraftingRequests); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.TagEffectState); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.LeashedEntity); + Debug.Assert(NetManager.Instance.GetId() == (int)NetModuleType.UnbreakableWallScan); + } public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data) { GetDataHandlerDelegate handler; @@ -182,6 +207,15 @@ public class PlayerInfoEventArgs : GetDataHandledEventArgs /// The Terraria playerID of the player /// public byte PlayerId { get; set; } + + /// + /// Voice variant + /// + public byte VoiceVariant { get; set; } + /// + /// Voice pitch offset + /// + public float VoicePitchOffset { get; set; } /// /// Hair color /// @@ -204,7 +238,8 @@ public class PlayerInfoEventArgs : GetDataHandledEventArgs /// If this is cancelled, the server will kick the player. If this should be changed in the future, let someone know. /// public static HandlerList PlayerInfo = new HandlerList(); - private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid, byte _hair, int _style, byte _difficulty, string _name) + private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid, byte _voiceVariant, float _voicePitchOffset, + byte _hair, int _style, byte _difficulty, string _name) { if (PlayerInfo == null) return false; @@ -217,6 +252,8 @@ private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid Hair = _hair, Style = _style, Difficulty = _difficulty, + VoiceVariant = _voiceVariant, + VoicePitchOffset = _voicePitchOffset, Name = _name, }; PlayerInfo.Invoke(null, args); @@ -280,10 +317,12 @@ public class GetSectionEventArgs : GetDataHandledEventArgs /// The Y position requested. Or -1 for spawn. public int Y { get; set; } + /// The team of the requesting player. + public byte Team { get; set; } } /// The hook for a GetSection event. public static HandlerList GetSection = new HandlerList(); - private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y) + private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y, byte team) { if (GetSection == null) return false; @@ -294,12 +333,12 @@ private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int Data = data, X = x, Y = y, + Team = team, }; GetSection.Invoke(null, args); return args.Handled; } - /// /// For use in a PlayerUpdate event /// @@ -867,13 +906,15 @@ public class SpawnEventArgs : GetDataHandledEventArgs /// /// Context of where the player is spawning from. /// + public int Team { get; set; } + public PlayerSpawnContext SpawnContext { get; set; } } /// /// PlayerSpawn - When a player spawns /// public static HandlerList PlayerSpawn = new HandlerList(); - private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY, int respawnTimer, int numberOfDeathsPVE, int numberOfDeathsPVP, PlayerSpawnContext spawnContext) + private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY, int respawnTimer, int numberOfDeathsPVE, int numberOfDeathsPVP, int team, PlayerSpawnContext spawnContext) { if (PlayerSpawn == null) return false; @@ -888,6 +929,7 @@ private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, RespawnTimer = respawnTimer, NumberOfDeathsPVE = numberOfDeathsPVE, NumberOfDeathsPVP = numberOfDeathsPVP, + Team = team, SpawnContext = spawnContext }; PlayerSpawn.Invoke(null, args); @@ -1048,12 +1090,18 @@ public class PlayerZoneEventArgs : GetDataHandledEventArgs /// 0 = The Aether /// public BitsByte Zone5 { get; set; } + + /// + /// The number of nearby town NPCs. + /// + public byte TownNPCs { get; set; } } /// /// PlayerZone - When the player sends it's zone/biome information to the server /// public static HandlerList PlayerZone = new HandlerList(); - private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4, BitsByte zone5) + private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, + BitsByte zone3, BitsByte zone4, BitsByte zone5, byte townNPCs) { if (PlayerZone == null) return false; @@ -1067,7 +1115,8 @@ private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, B Zone2 = zone2, Zone3 = zone3, Zone4 = zone4, - Zone5 = zone5 + Zone5 = zone5, + TownNPCs = townNPCs, }; PlayerZone.Invoke(null, args); return args.Handled; @@ -1526,11 +1575,11 @@ public class PaintTileEventArgs : GetDataHandledEventArgs /// /// X Location /// - public Int32 X { get; set; } + public int X { get; set; } /// /// Y Location /// - public Int32 Y { get; set; } + public int Y { get; set; } /// /// Type /// @@ -1544,7 +1593,7 @@ public class PaintTileEventArgs : GetDataHandledEventArgs /// NPCStrike - Called when an NPC is attacked /// public static HandlerList PaintTile = new HandlerList(); - private static bool OnPaintTile(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t, byte ct) + private static bool OnPaintTile(TSPlayer player, MemoryStream data, int x, int y, byte t, byte ct) { if (PaintTile == null) return false; @@ -1570,11 +1619,11 @@ public class PaintWallEventArgs : GetDataHandledEventArgs /// /// X Location /// - public Int32 X { get; set; } + public int X { get; set; } /// /// Y Location /// - public Int32 Y { get; set; } + public int Y { get; set; } /// /// Type /// @@ -1588,7 +1637,7 @@ public class PaintWallEventArgs : GetDataHandledEventArgs /// Called When a wall is painted /// public static HandlerList PaintWall = new HandlerList(); - private static bool OnPaintWall(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t, byte cw) + private static bool OnPaintWall(TSPlayer player, MemoryStream data, int x, int y, byte t, byte cw) { if (PaintWall == null) return false; @@ -1614,7 +1663,7 @@ public class TeleportEventArgs : GetDataHandledEventArgs /// /// ??? /// - public Int16 ID { get; set; } + public short ID { get; set; } /// /// Flag is a bit field /// if the first bit is set -> 0 = player, 1 = NPC @@ -1644,7 +1693,7 @@ public class TeleportEventArgs : GetDataHandledEventArgs /// NPCStrike - Called when an NPC is attacked /// public static HandlerList Teleport = new HandlerList(); - private static bool OnTeleport(TSPlayer player, MemoryStream data, Int16 id, byte f, float x, float y, byte style, int extraInfo) + private static bool OnTeleport(TSPlayer player, MemoryStream data, short id, byte f, float x, float y, byte style, int extraInfo) { if (Teleport == null) return false; @@ -2123,6 +2172,26 @@ private static bool OnEmoji(TSPlayer player, MemoryStream data, byte playerIndex return args.Handled; } + /// + /// Represents the ID of an inventory inside a . + /// + public enum DisplayDollInventoryID + { + /// + /// The ID of the inventory holding the equipment items. + /// + Equipment = 0, + + /// + /// The ID of the inventory holding the dyes. + /// + Dyes = 1, + + /// + /// The ID of the inventory holding the miscellaneous items (mounts, pets, etc.). + /// + Misc = 2, + } /// /// For use in a TileEntityDisplayDollItemSync event. /// @@ -2147,7 +2216,19 @@ public class DisplayDollItemSyncEventArgs : GetDataHandledEventArgs /// /// Whether or not the slot that is being modified is a Dye slot. /// - public bool IsDye { get; set; } + [Obsolete($"Use {nameof(InventoryID)} instead.")] + public bool IsDye + { + get => InventoryID == DisplayDollInventoryID.Dyes; + set => InventoryID = value + ? DisplayDollInventoryID.Dyes + : DisplayDollInventoryID.Equipment + ; + } + /// + /// The ID of the inventory that is being modified. + /// + public DisplayDollInventoryID InventoryID { get; set; } /// /// The current item that is present in the slot before the modification. /// @@ -2157,11 +2238,35 @@ public class DisplayDollItemSyncEventArgs : GetDataHandledEventArgs /// public Item NewItem { get; set; } } + + /// + /// For use in a event. + /// + public class DisplayDollPoseSyncEventArgs : GetDataHandledEventArgs + { + /// + /// The player index in the packet who modifies the DisplayDoll item slot. + /// + public byte PlayerIndex { get; set; } + /// + /// The ID of the TileEntity that is being modified. + /// + public int TileEntityID { get; set; } + /// + /// The TEDisplayDoll object that is being modified. + /// + public TEDisplayDoll DisplayDollEntity { get; set; } + /// + /// The incoming pose ID of the TEDisplayDoll. + /// + public byte Pose { get; set; } + } + /// /// Called when a player modifies a DisplayDoll (Mannequin) item slot. /// public static HandlerList DisplayDollItemSync = new HandlerList(); - private static bool OnDisplayDollItemSync(TSPlayer player, MemoryStream data, byte playerIndex, int tileEntityID, TEDisplayDoll displayDollEntity, int slot, bool isDye, Item oldItem, Item newItem) + private static bool OnDisplayDollItemSync(TSPlayer player, MemoryStream data, byte playerIndex, int tileEntityID, TEDisplayDoll displayDollEntity, int slot, DisplayDollInventoryID inventoryID, Item oldItem, Item newItem) { if (DisplayDollItemSync == null) return false; @@ -2174,7 +2279,7 @@ private static bool OnDisplayDollItemSync(TSPlayer player, MemoryStream data, by TileEntityID = tileEntityID, DisplayDollEntity = displayDollEntity, Slot = slot, - IsDye = isDye, + InventoryID = inventoryID, OldItem = oldItem, NewItem = newItem }; @@ -2182,6 +2287,29 @@ private static bool OnDisplayDollItemSync(TSPlayer player, MemoryStream data, by return args.Handled; } + /// + /// Called when a player modifies a DisplayDoll (Mannequin) pose. + /// + public static HandlerList DisplayDollPoseSync = new(); + private static bool OnDisplayDollPoseSync(TSPlayer player, MemoryStream data, byte playerIndex, int tileEntityID, TEDisplayDoll displayDollEntity, byte pose) + { + if (DisplayDollPoseSync == null) + return false; + + var args = new DisplayDollPoseSyncEventArgs + { + Player = player, + Data = data, + PlayerIndex = playerIndex, + TileEntityID = tileEntityID, + DisplayDollEntity = displayDollEntity, + Pose = pose, + }; + DisplayDollPoseSync.Invoke(null, args); + return args.Handled; + } + + /// /// For use in an OnRequestTileEntityInteraction event. /// @@ -2435,12 +2563,13 @@ private static bool HandlePlayerInfo(GetDataHandlerArgs args) byte playerid = args.Data.ReadInt8(); // 0-3 male; 4-7 female int skinVariant = args.Data.ReadByte(); + byte voiceVariant = args.Data.ReadInt8(); + float voicePitchOffset = args.Data.ReadSingle(); var hair = args.Data.ReadInt8(); string name = args.Data.ReadString(); byte hairDye = args.Data.ReadInt8(); - BitsByte hideVisual = args.Data.ReadInt8(); - BitsByte hideVisual2 = args.Data.ReadInt8(); + ushort hideVisualFlags = args.Data.ReadUInt16(); BitsByte hideMisc = args.Data.ReadInt8(); Color hairColor = new Color(args.Data.ReadInt8(), args.Data.ReadInt8(), args.Data.ReadInt8()); @@ -2481,7 +2610,7 @@ private static bool HandlePlayerInfo(GetDataHandlerArgs args) bool usedAmbrosia = bitsByte10[5]; bool ateArtisanBread = bitsByte10[6]; - if (OnPlayerInfo(args.Player, args.Data, playerid, hair, skinVariant, difficulty, name)) + if (OnPlayerInfo(args.Player, args.Data, playerid, voiceVariant, voicePitchOffset, hair, skinVariant, difficulty, name)) { TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePlayerInfo rejected plugin phase {0}", name)); args.Player.Kick(GetString("A plugin on this server stopped your login."), true, true); @@ -2509,20 +2638,16 @@ private static bool HandlePlayerInfo(GetDataHandlerArgs args) args.Player.TPlayer.hairColor = hairColor; args.Player.TPlayer.hairDye = hairDye; args.Player.TPlayer.skinVariant = skinVariant; + args.Player.TPlayer.voiceVariant = voiceVariant; + args.Player.TPlayer.voicePitchOffset = voicePitchOffset; args.Player.TPlayer.skinColor = skinColor; args.Player.TPlayer.eyeColor = eyeColor; args.Player.TPlayer.pantsColor = pantsColor; args.Player.TPlayer.shirtColor = shirtColor; args.Player.TPlayer.underShirtColor = underShirtColor; args.Player.TPlayer.shoeColor = shoeColor; - //@Olink: If you need to change bool[10], please make sure you also update the for loops below to account for it. - //There are two arrays from terraria that we only have a single array for. You will need to make sure that you are looking - //at the correct terraria array (hideVisual or hideVisual2). - args.Player.TPlayer.hideVisibleAccessory = new bool[10]; - for (int i = 0; i < 8; i++) - args.Player.TPlayer.hideVisibleAccessory[i] = hideVisual[i]; - for (int i = 0; i < 2; i++) - args.Player.TPlayer.hideVisibleAccessory[i + 8] = hideVisual2[i]; + for (var i = 0; i < args.Player.TPlayer.hideVisibleAccessory.Length; i++) + args.Player.TPlayer.hideVisibleAccessory[i] = (hideVisualFlags & (1 << i)) != 0; args.Player.TPlayer.hideMisc = hideMisc; args.Player.TPlayer.extraAccessory = extraSlot; args.Player.TPlayer.UsingBiomeTorches = usingBiomeTorches; @@ -2573,33 +2698,38 @@ private static bool HandlePlayerSlot(GetDataHandlerArgs args) short stack = args.Data.ReadInt16(); byte prefix = args.Data.ReadInt8(); short type = args.Data.ReadInt16(); + BitsByte slotFlags = args.Data.ReadInt8(); + bool favorited = slotFlags[0]; + bool blockedSlot = slotFlags[1]; // 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 == PlayerItemSlotID.Count - 1) { 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 > PlayerItemSlotID.Count - 1) return true; - if (args.Player.IgnoreSSCPackets) - { - TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePlayerSlot rejected ignore ssc packets")); - args.Player.SendData(PacketTypes.PlayerSlot, "", args.Player.Index, slot, prefix); - return true; - } + // if (args.Player.IgnoreSSCPackets) + // { + // TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePlayerSlot rejected ignore ssc packets")); + // args.Player.SendData(PacketTypes.PlayerSlot, "", args.Player.Index, slot, prefix); + // return true; + // } // Garabage? Or will it cause some internal initialization or whatever? var item = new Item(); item.netDefaults(type); item.Prefix(prefix); + item.favorited = favorited; + item.stack = stack; if (args.Player.IsLoggedIn) { - args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); + args.Player.PlayerData.StoreSlot(slot, type, prefix, stack, favorited); } else if (Main.ServerSideCharacter && TShock.Config.Settings.DisableLoginBeforeJoin && !bypassTrashCanCheck && args.Player.HasSentInventory && !args.Player.HasPermission(Permissions.bypassssc)) @@ -2610,7 +2740,6 @@ private static bool HandlePlayerSlot(GetDataHandlerArgs args) if (slot == 58) //this is the hand { - item.stack = stack; args.Player.ItemInHand = item; } @@ -2698,7 +2827,11 @@ private static bool HandleConnecting(GetDataHandlerArgs args) private static bool HandleGetSection(GetDataHandlerArgs args) { - if (OnGetSection(args.Player, args.Data, args.Data.ReadInt32(), args.Data.ReadInt32())) + int x = args.Data.ReadInt32(); + int y = args.Data.ReadInt32(); + byte team = args.Data.ReadInt8(); + + if (OnGetSection(args.Player, args.Data, x, y, team)) return true; if (TShock.Utils.GetActivePlayerCount() + 1 > TShock.Config.Settings.MaxSlots && @@ -2721,36 +2854,37 @@ private static bool HandleSpawn(GetDataHandlerArgs args) return true; } + byte player = args.Data.ReadInt8(); short spawnX = args.Data.ReadInt16(); short spawnY = args.Data.ReadInt16(); int respawnTimer = args.Data.ReadInt32(); short numberOfDeathsPVE = args.Data.ReadInt16(); short numberOfDeathsPVP = args.Data.ReadInt16(); + var team = args.Data.ReadByte(); PlayerSpawnContext context = (PlayerSpawnContext)args.Data.ReadByte(); if (args.Player.State >= (int)ConnectionState.RequestingWorldData && !args.Player.FinishedHandshake) args.Player.FinishedHandshake = true; //If the player has requested world data before sending spawn player, they should be at the obvious ClientRequestedWorldData state. Also only set this once to remove redundant updates. - if (OnPlayerSpawn(args.Player, args.Data, player, spawnX, spawnY, respawnTimer, numberOfDeathsPVE, numberOfDeathsPVP, context)) + if (OnPlayerSpawn(args.Player, args.Data, player, spawnX, spawnY, respawnTimer,numberOfDeathsPVE, numberOfDeathsPVP, team, context)) return true; if (!Main.ServerSideCharacter || context != PlayerSpawnContext.SpawningIntoWorld) { args.Player.Dead = respawnTimer > 0; } - if (Main.ServerSideCharacter) { // As long as the player has not changed his spawnpoint since initial connection, - // we should not use the client's spawnpoint value. This is because the spawnpoint - // value is not saved on the client when SSC is enabled. Hence, we have to assert - // the server-saved spawnpoint value until we can detect that the player has changed + // we should not use the client's spawnpoint value. This is because the spawnpoint + // value is not saved on the client when SSC is enabled. Hence, we have to assert + // the server-saved spawnpoint value until we can detect that the player has changed // his spawn. Once we detect the spawnpoint changed, the client's spawnpoint value // becomes the correct one to use. // - // Note that spawnpoint changes (right-clicking beds) are not broadcasted to the - // server. Hence, the only way to detect spawnpoint changes is from the + // Note that spawnpoint changes (right-clicking beds) are not broadcasted to the + // server. Hence, the only way to detect spawnpoint changes is from the // PlayerSpawn packet. // handle initial connection @@ -2760,19 +2894,20 @@ private static bool HandleSpawn(GetDataHandlerArgs args) args.Player.initialSpawn = true; args.Player.initialServerSpawnX = args.TPlayer.SpawnX; args.Player.initialServerSpawnY = args.TPlayer.SpawnY; + args.Player.TPlayer.dead = false; // initial client spawn point, do not use this to spawn the player // we only use it to detect if the spawnpoint has changed during this session args.Player.initialClientSpawnX = spawnX; args.Player.initialClientSpawnY = spawnY; - // we first let the game handle completing the connection (state 3 => 10), - // then we will spawn the player at the saved spawnpoint in the next second, + // we first let the game handle completing the connection (state 3 => 10), + // then we will spawn the player at the saved spawnpoint in the next second, // by reasserting the correct spawnpoint value return false; } - // once we detect the client has changed his spawnpoint in the current session, + // once we detect the client has changed his spawnpoint in the current session, // the client spawnpoint value will be correct for the rest of the session if (args.Player.spawnSynced || args.Player.initialClientSpawnX != spawnX || args.Player.initialClientSpawnY != spawnY) { @@ -2780,15 +2915,15 @@ private static bool HandleSpawn(GetDataHandlerArgs args) args.Player.spawnSynced = true; return false; } - + args.TPlayer.Spawn(context); // spawn the player before teleporting NetMessage.SendData((int)PacketTypes.PlayerSpawn, -1, args.Player.Index, null, args.Player.Index, (int)PlayerSpawnContext.ReviveFromDeath); - // the player has not changed his spawnpoint yet, so we assert the server-saved spawnpoint + // the player has not changed his spawnpoint yet, so we assert the server-saved spawnpoint // by teleporting the player instead of letting the game use the client's incorrect spawnpoint. TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleSpawn force ssc teleport for {0} at ({1},{2})", args.Player.Name, args.TPlayer.SpawnX, args.TPlayer.SpawnY)); args.Player.TeleportSpawnpoint(); - + args.TPlayer.respawnTimer = respawnTimer; args.TPlayer.numberOfDeathsPVE = numberOfDeathsPVE; args.TPlayer.numberOfDeathsPVP = numberOfDeathsPVP; @@ -2817,6 +2952,10 @@ private static bool HandlePlayerUpdate(GetDataHandlerArgs args) if (miscData1.HasVelocity) velocity = args.Data.ReadVector2(); + ushort mountType = 0; + if (miscData1.HasMount) + mountType = args.Data.ReadUInt16(); + Vector2? originalPosition = new Vector2?(); Vector2? homePosition = Vector2.Zero; if (miscData2.CanReturnWithPotionOfReturn) @@ -2961,8 +3100,9 @@ private static bool HandleItemOwner(GetDataHandlerArgs args) { var id = args.Data.ReadInt16(); var owner = args.Data.ReadInt8(); + //var position = args.Data.ReadVector2(); - if (id < 0 || id > 400) + if (id is < 0 or > Main.maxItems) return true; if (id == 400 && owner == 255) @@ -3229,8 +3369,9 @@ private static bool HandlePlayerZone(GetDataHandlerArgs args) BitsByte zone3 = args.Data.ReadInt8(); BitsByte zone4 = args.Data.ReadInt8(); BitsByte zone5 = args.Data.ReadInt8(); + byte townNPCs = args.Data.ReadInt8(); - if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4, zone5)) + if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4, zone5, townNPCs)) return true; return false; @@ -3466,10 +3607,10 @@ private static bool HandlePlayerBuffList(GetDataHandlerArgs args) if (OnPlayerBuffUpdate(args.Player, args.Data, id)) return true; - - for (int i = 0; i < Terraria.Player.maxBuffs; i++) + int buff; + int buffIndex = 0; + while ((buff = args.Data.ReadUInt16()) > 0 && buffIndex < Player.maxBuffs) { - var buff = args.Data.ReadUInt16(); if (buff == 10 && TShock.Config.Settings.DisableInvisPvP && args.TPlayer.hostile) buff = 0; @@ -3480,15 +3621,12 @@ private static bool HandlePlayerBuffList(GetDataHandlerArgs args) buff = 0; } - args.TPlayer.buffType[i] = buff; - if (args.TPlayer.buffType[i] > 0) - { - args.TPlayer.buffTime[i] = 60; - } - else - { - args.TPlayer.buffTime[i] = 0; - } + args.TPlayer.buffType[buffIndex] = buff; + args.TPlayer.buffTime[buffIndex] = 60; + buffIndex++; + // Clear remaining buff slots + Array.Clear(args.TPlayer.buffType, buffIndex, args.TPlayer.buffType.Length - buffIndex); + Array.Clear(args.TPlayer.buffTime, buffIndex, args.TPlayer.buffTime.Length - buffIndex); } TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePlayerBuffList handled event and sent data {0}", args.Player.Name)); @@ -3853,40 +3991,32 @@ private static bool HandleTeleport(GetDataHandlerArgs args) { BitsByte flag = (BitsByte)args.Data.ReadByte(); short id = args.Data.ReadInt16(); - var x = args.Data.ReadSingle(); - var y = args.Data.ReadSingle(); + Vector2 position = args.Data.ReadVector2(); byte style = args.Data.ReadInt8(); int type = 0; - bool isNPC = type == 1; int extraInfo = -1; bool getPositionFromTarget = false; if (flag[0]) - { - type = 1; - } + type += 1; if (flag[1]) - { - type = 2; - } + type += 2; if (flag[2]) - { getPositionFromTarget = true; - } if (flag[3]) - { extraInfo = args.Data.ReadInt32(); - } + if (getPositionFromTarget) + position = Main.player[id].position; - if (OnTeleport(args.Player, args.Data, id, flag, x, y, style, extraInfo)) + if (OnTeleport(args.Player, args.Data, id, flag, position.X, position.Y, style, extraInfo)) return true; //Rod of Discord teleport (usually (may be used by modded clients to teleport)) if (type == 0 && !args.Player.HasPermission(Permissions.rod)) { TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleTeleport rejected rod type {0} {1}", args.Player.Name, type)); - args.Player.SendErrorMessage(GetString("You do not have permission to teleport using items.")); // Was going to write using RoD but Hook of Disonnance and Potion of Return both use the same teleport packet as RoD. + args.Player.SendErrorMessage(GetString("You do not have permission to teleport using items.")); // Was going to write using RoD but Hook of Disonnance and Potion of Return both use the same teleport packet as RoD. args.Player.Teleport(args.TPlayer.position.X, args.TPlayer.position.Y); // Suggest renaming rod permission unless someone plans to add separate perms for the other 2 tp items. return true; } @@ -4350,9 +4480,9 @@ private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) } // Handle kicks/bans on mediumcore/hardcore deaths. - if (args.TPlayer.difficulty == 1 || args.TPlayer.difficulty == 2) // Player is not softcore + if (args.TPlayer.difficulty == PlayerDifficultyID.MediumCore || args.TPlayer.difficulty == PlayerDifficultyID.Hardcore) // Player is not softcore { - bool mediumcore = args.TPlayer.difficulty == 1; + bool mediumcore = args.TPlayer.difficulty == PlayerDifficultyID.MediumCore; bool shouldBan = mediumcore ? TShock.Config.Settings.BanOnMediumcoreDeath : TShock.Config.Settings.BanOnHardcoreDeath; bool shouldKick = mediumcore ? TShock.Config.Settings.KickOnMediumcoreDeath : TShock.Config.Settings.KickOnHardcoreDeath; string banReason = mediumcore ? TShock.Config.Settings.MediumcoreBanReason : TShock.Config.Settings.HardcoreBanReason; @@ -4373,7 +4503,7 @@ private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) } } - if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn) + if (args.TPlayer.difficulty == PlayerDifficultyID.Hardcore && Main.ServerSideCharacter && args.Player.IsLoggedIn) { if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID)) { @@ -4402,69 +4532,82 @@ private static bool HandleTileEntityDisplayDollItemSync(GetDataHandlerArgs args) byte playerIndex = args.Data.ReadInt8(); int tileEntityID = args.Data.ReadInt32(); int slot = args.Data.ReadByte(); - bool isDye = false; - if (slot >= 8) - { - isDye = true; - slot -= 8; - } - - Item newItem = new Item(); - Item oldItem = new Item(); + int subtype = args.Data.ReadByte(); - if (!TileEntity.ByID.TryGetValue(tileEntityID, out TileEntity tileEntity)) + if (!TileEntity.ByID.TryGetValue(tileEntityID, out TileEntity entity) || entity is not TEDisplayDoll displayDoll) return false; - TEDisplayDoll displayDoll = tileEntity as TEDisplayDoll; - if (displayDoll != null) + switch (subtype) + { + case 0: + return HandleItemSync(DisplayDollInventoryID.Equipment, displayDoll._equip); + case 1: + return HandleItemSync(DisplayDollInventoryID.Dyes, displayDoll._dyes); + case 3: + return HandleItemSync(DisplayDollInventoryID.Misc, displayDoll._misc); + + case 2: + { + byte pose = (byte)args.Data.ReadByte(); + return OnDisplayDollPoseSync(args.Player, args.Data, playerIndex, tileEntityID, displayDoll, pose); + } + + default: + return false; + } + bool HandleItemSync(DisplayDollInventoryID inventoryID, Item[] items) { - oldItem = displayDoll._items[slot]; - if (isDye) - oldItem = displayDoll._dyes[slot]; + Item oldItem = items[slot]; ushort itemType = args.Data.ReadUInt16(); ushort stack = args.Data.ReadUInt16(); int prefix = args.Data.ReadByte(); - if (oldItem.type == 0 && newItem.type == 0) + if (oldItem.type == 0 && itemType == 0) return false; + Item newItem = new Item(); newItem.SetDefaults(itemType); newItem.stack = stack; newItem.Prefix(prefix); - if (OnDisplayDollItemSync(args.Player, args.Data, playerIndex, tileEntityID, displayDoll, slot, isDye, oldItem, newItem)) + if (OnDisplayDollItemSync(args.Player, args.Data, playerIndex, tileEntityID, displayDoll, slot, inventoryID, oldItem, newItem)) return true; + + return false; } - return false; } - private static bool HandleRequestTileEntityInteraction(GetDataHandlerArgs args) - { - int tileEntityID = args.Data.ReadInt32(); - byte playerIndex = args.Data.ReadInt8(); - if (!TileEntity.ByID.TryGetValue(tileEntityID, out TileEntity tileEntity)) - return false; - if (OnRequestTileEntityInteraction(args.Player, args.Data, tileEntity, playerIndex)) - return true; + private static bool HandleRequestTileEntityInteraction(GetDataHandlerArgs args) + { + int tileEntityID = args.Data.ReadInt32(); + byte playerIndex = args.Data.ReadInt8(); + + if (!TileEntity.ByID.TryGetValue(tileEntityID, out TileEntity tileEntity)) return false; - } - private static bool HandleSyncTilePicking(GetDataHandlerArgs args) - { - byte playerIndex = args.Data.ReadInt8(); - short tileX = args.Data.ReadInt16(); - short tileY = args.Data.ReadInt16(); - byte damage = args.Data.ReadInt8(); + if (OnRequestTileEntityInteraction(args.Player, args.Data, tileEntity, playerIndex)) + return true; - if (OnSyncTilePicking(args.Player, args.Data, playerIndex, tileX, tileY, damage)) - return true; + return false; + } + + private static bool HandleSyncTilePicking(GetDataHandlerArgs args) + { + byte playerIndex = args.Data.ReadInt8(); + short tileX = args.Data.ReadInt16(); + short tileY = args.Data.ReadInt16(); + byte damage = args.Data.ReadInt8(); + + if (OnSyncTilePicking(args.Player, args.Data, playerIndex, tileX, tileY, damage)) + return true; + + return false; + } - return false; - } private static bool HandleSyncRevengeMarker(GetDataHandlerArgs args) { @@ -4564,23 +4707,25 @@ private static bool HandleSyncLoadout(GetDataHandlerArgs args) // 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) + (int, int) GetArmorSlotsForLoadoutIndex(int index) { return index switch { 0 => NetItem.Loadout1Armor, 1 => NetItem.Loadout2Armor, - 2 => NetItem.Loadout3Armor + 2 => NetItem.Loadout3Armor, + _ => throw new NotImplementedException($"invalid lodout index {index}") }; } - Tuple GetDyeSlotsForLoadoutIndex(int index) + (int, int) GetDyeSlotsForLoadoutIndex(int index) { return index switch { 0 => NetItem.Loadout1Dye, 1 => NetItem.Loadout2Dye, - 2 => NetItem.Loadout3Dye + 2 => NetItem.Loadout3Dye, + _ => throw new NotImplementedException($"invalid lodout index {index}") }; } @@ -4616,6 +4761,15 @@ Tuple GetDyeSlotsForLoadoutIndex(int index) return false; } + private static bool HandleSyncPlayerSpectating(GetDataHandlerArgs args) + { + // if (!args.Player.HasPermission(Permissions.playerspectating)) + // { + // return true; + // } + return false; + } + public enum DoorAction { OpenDoor = 0, @@ -4819,12 +4973,16 @@ public enum NetModuleType Ping, Ambience, Bestiary, - CreativeUnlocks, CreativePowers, CreativeUnlocksPlayerReport, TeleportPylon, Particles, - CreativePowerPermissions + CreativePowerPermissions, + Banners, + CraftingRequests, + TagEffectState, + LeashedEntity, + UnbreakableWallScan } public enum CreativePowerTypes diff --git a/TShockAPI/Group.cs b/TShockAPI/Group.cs index 45086eefb..387ee031c 100644 --- a/TShockAPI/Group.cs +++ b/TShockAPI/Group.cs @@ -208,7 +208,7 @@ public Group(string groupname, Group parentgroup = null, string chatcolor = "255 public virtual bool HasPermission(string permission) { bool negated = false; - if (String.IsNullOrEmpty(permission) || (RealHasPermission(permission, ref negated) && !negated)) + if (string.IsNullOrEmpty(permission) || (RealHasPermission(permission, ref negated) && !negated)) { return true; } @@ -220,7 +220,7 @@ public virtual bool HasPermission(string permission) for (int i = nodes.Length - 1; i >= 0; i--) { nodes[i] = "*"; - if (RealHasPermission(String.Join(".", nodes, 0, i + 1), ref negated)) + if (RealHasPermission(string.Join(".", nodes, 0, i + 1), ref negated)) { return !negated; } @@ -274,7 +274,7 @@ public virtual void NegatePermission(string permission) /// The permission to add. public virtual void AddPermission(string permission) { - if (permission.StartsWith("!")) + if (permission.StartsWith('!')) { NegatePermission(permission.Substring(1)); return; @@ -306,7 +306,7 @@ public virtual void SetPermission(List permission) /// public virtual void RemovePermission(string permission) { - if (permission.StartsWith("!")) + if (permission.StartsWith('!')) { negatedpermissions.Remove(permission.Substring(1)); return; diff --git a/TShockAPI/Handlers/DisplayDollItemSyncHandler.cs b/TShockAPI/Handlers/DisplayDollItemSyncHandler.cs index 156cc7fbf..4818c0c68 100644 --- a/TShockAPI/Handlers/DisplayDollItemSyncHandler.cs +++ b/TShockAPI/Handlers/DisplayDollItemSyncHandler.cs @@ -14,8 +14,8 @@ public class DisplayDollItemSyncHandler : IPacketHandler public static bool HasPermission(CreativePowerTypes powerType, TSPlayer player) { - if (!PowerToPermissionMap.ContainsKey(powerType)) + if (!PowerToPermissionMap.TryGetValue(powerType, out string permission)) { TShock.Log.ConsoleDebug(GetString("CreativePowerHandler received permission check request for unknown creative power")); return false; } - string permission = PowerToPermissionMap[powerType]; - //prevent being told about the spawnrate permission on join until relogic fixes if (!player.HasReceivedNPCPermissionError && powerType == CreativePowerTypes.SetSpawnRate) { diff --git a/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs b/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs index 92aff26b0..62d349d3e 100644 --- a/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs +++ b/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs @@ -15,10 +15,12 @@ public class CreativeUnlocksHandler : INetModuleHandler /// An unknown field. If this does not have a value of '0' the packet should be rejected. /// public byte UnknownField { get; set; } + /// /// ID of the item being sacrificed /// public ushort ItemId { get; set; } + /// /// Stack size of the item being sacrificed /// @@ -49,10 +51,11 @@ public void Deserialize(MemoryStream data) /// public void HandlePacket(TSPlayer player, out bool rejectPacket) { - if (!Main.GameModeInfo.IsJourneyMode) + if (!Main.IsJourneyMode) { TShock.Log.ConsoleDebug( - GetString($"NetModuleHandler received attempt to unlock sacrifice while not in journey mode from {player.Name}") + GetString( + $"NetModuleHandler received attempt to unlock sacrifice while not in journey mode from {player.Name}") ); rejectPacket = true; @@ -62,7 +65,8 @@ public void HandlePacket(TSPlayer player, out bool rejectPacket) if (UnknownField != 0) { TShock.Log.ConsoleDebug( - GetString($"CreativeUnlocksHandler received non-vanilla unlock request. Random field value: {UnknownField} but should be 0 from {player.Name}") + GetString( + $"CreativeUnlocksHandler received non-vanilla unlock request. Random field value: {UnknownField} but should be 0 from {player.Name}") ); rejectPacket = true; @@ -78,7 +82,8 @@ public void HandlePacket(TSPlayer player, out bool rejectPacket) var totalSacrificed = TShock.ResearchDatastore.SacrificeItem(ItemId, Amount, player); - var response = NetCreativeUnlocksModule.SerializeItemSacrifice(ItemId, totalSacrificed); + var response = + NetCreativeUnlocksPlayerReportModule.SerializeSacrificeRequest(player.Index, ItemId, totalSacrificed); NetManager.Instance.Broadcast(response); rejectPacket = false; diff --git a/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs index 16d58640a..bcc85b84a 100644 --- a/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs +++ b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Terraria; + using static TShockAPI.GetDataHandlers; namespace TShockAPI.Handlers.NetModules @@ -33,9 +33,9 @@ public void OnReceive(object sender, ReadNetModuleEventArgs args) { INetModuleHandler handler; - if (NetModulesToHandlersMap.ContainsKey(args.ModuleType)) + if (NetModulesToHandlersMap.TryGetValue(args.ModuleType, out Type type)) { - handler = (INetModuleHandler)Activator.CreateInstance(NetModulesToHandlersMap[args.ModuleType]); + handler = (INetModuleHandler)Activator.CreateInstance(type); } else { diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 7e998a2c1..e57ca47b5 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -369,7 +369,7 @@ private MatchResult MatchRemoval(TSPlayer player, TileRect rect) private static readonly TileRectMatch[] Matches = new TileRectMatch[] { TileRectMatch.Placement(2, 3, TileID.TargetDummy, 54, 36, 18, 18), - TileRectMatch.Placement(3, 4, TileID.TeleportationPylon, 468, 54, 18, 18), + TileRectMatch.Placement(3, 4, TileID.TeleportationPylon, 522, 54, 18, 18), TileRectMatch.Placement(2, 3, TileID.DisplayDoll, 126, 36, 18, 18), TileRectMatch.Placement(3, 4, TileID.HatRack, 90, 54, 18, 18), TileRectMatch.Placement(2, 2, TileID.ItemFrame, 162, 18, 18, 18), @@ -398,6 +398,7 @@ private MatchResult MatchRemoval(TSPlayer player, TileRect rect) TileRectMatch.StateChangeX(1, 1, TileID.ShadowCandle, 18, 18), TileRectMatch.StateChange(1, 1, TileID.Traps, 90, 90, 18, 18), + TileRectMatch.StateChange(1, 1, TileID.Torches, 0, 198, 0, 0), TileRectMatch.StateChangeX(1, 1, TileID.WirePipe, 36, 18), TileRectMatch.StateChangeX(1, 1, TileID.ProjectilePressurePad, 66, 22), @@ -526,6 +527,7 @@ public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) return; } + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from matches from {args.Player.Name}")); // send correcting data diff --git a/TShockAPI/ILog.cs b/TShockAPI/ILog.cs index fc0248652..f1ba40853 100644 --- a/TShockAPI/ILog.cs +++ b/TShockAPI/ILog.cs @@ -145,6 +145,7 @@ public interface ILog /// Writes a debug string to the log file. Only works if the DebugLogs config option is set to true. /// /// The message to be written. + /// format args void ConsoleDebug(string message, params object[] args); /// diff --git a/TShockAPI/ItemBans.cs b/TShockAPI/ItemBans.cs index df0d36b05..ffa358457 100644 --- a/TShockAPI/ItemBans.cs +++ b/TShockAPI/ItemBans.cs @@ -88,7 +88,7 @@ internal void OnSecondlyUpdate(EventArgs args) UnTaint(player); // No matter the player type, we do a check when a player is holding an item that's banned. - if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].netID), player)) + if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].type), player)) { string itemName = player.TPlayer.inventory[player.TPlayer.selectedItem].Name; player.Disable(GetString($"holding banned item: {itemName}"), disableFlags); @@ -157,7 +157,7 @@ internal void OnPlayerUpdate(object sender, PlayerUpdateEventArgs args) TSPlayer player = args.Player; string itemName = player.TPlayer.inventory[args.SelectedItem].Name; - if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[args.SelectedItem].netID), args.Player)) + if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[args.SelectedItem].type), args.Player)) { player.TPlayer.controlUseItem = false; player.Disable(GetString($"holding banned item: {itemName}"), disableFlags); @@ -204,7 +204,7 @@ internal void OnTileEdit(object sender, TileEditEventArgs args) return; } - if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(args.Player.SelectedItem.netID), args.Player)) + if (DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(args.Player.SelectedItem.type), args.Player)) { args.Player.SendTileSquareCentered(args.X, args.Y, 4); args.Handled = true; diff --git a/TShockAPI/Localization/EnglishLanguage.cs b/TShockAPI/Localization/EnglishLanguage.cs index 334c47804..a7a3ffe84 100644 --- a/TShockAPI/Localization/EnglishLanguage.cs +++ b/TShockAPI/Localization/EnglishLanguage.cs @@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Linq; using Terraria; +using Terraria.ID; using Terraria.Initializers; using Terraria.Localization; using Terraria.UI.Chat; @@ -54,22 +55,22 @@ internal static void Initialize() LanguageManager.Instance.SetLanguage(GameCulture.FromCultureName(GameCulture.CultureName.English)); } - for (var i = -48; i < Terraria.ID.ItemID.Count; i++) + for (var i = -48; i < ItemID.Count; i++) { ItemNames.Add(i, Lang.GetItemNameValue(i)); } - for (var i = -17; i < Terraria.ID.NPCID.Count; i++) + for (var i = -17; i < NPCID.Count; i++) { NpcNames.Add(i, Lang.GetNPCNameValue(i)); } - for (var i = 0; i < Terraria.ID.BuffID.Count; i++) + for (var i = 0; i < BuffID.Count; i++) { Buffs.Add(i, Lang.GetBuffName(i)); } - foreach (var field in typeof(Main).Assembly.GetType("Terraria.ID.PrefixID") + foreach (var field in typeof(PrefixID) .GetFields().Where(f => !f.Name.Equals("Count", StringComparison.Ordinal))) { var i = (int)field.GetValue(null); diff --git a/TShockAPI/Models/PlayerUpdate/MiscDataSet1.cs b/TShockAPI/Models/PlayerUpdate/MiscDataSet1.cs index 480353a1a..86e3e1e84 100644 --- a/TShockAPI/Models/PlayerUpdate/MiscDataSet1.cs +++ b/TShockAPI/Models/PlayerUpdate/MiscDataSet1.cs @@ -75,6 +75,15 @@ public bool IsGhosted set => bitsbyte[6] = value; } + /// + /// Gets or Sets the Has Mount flag on the backing field (1.4.5+) + /// + public bool HasMount + { + get => bitsbyte[7]; + set => bitsbyte[7] = value; + } + /// /// Constructs a new instance of MiscDataSet1 with the given backing BitsByte /// diff --git a/TShockAPI/Models/PlayerUpdate/MiscDataSet3.cs b/TShockAPI/Models/PlayerUpdate/MiscDataSet3.cs index 04a45b6a2..94cdaa2f9 100644 --- a/TShockAPI/Models/PlayerUpdate/MiscDataSet3.cs +++ b/TShockAPI/Models/PlayerUpdate/MiscDataSet3.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Terraria; +using Terraria; namespace TShockAPI.Models.PlayerUpdate { @@ -23,6 +18,60 @@ public bool IsSleeping set => bitsbyte[0] = value; } + /// + /// Gets or Sets the Auto Reuse All Weapons flag on the backing field + /// + public bool AutoReuseAllWeapons + { + get => bitsbyte[1]; + set => bitsbyte[1] = value; + } + + /// + /// Gets or Sets the Control Down Hold flag on the backing field + /// + public bool ControlDownHold + { + get => bitsbyte[2]; + set => bitsbyte[2] = value; + } + + /// + /// Gets or Sets the Is Operating Another Entity flag on the backing field + /// + public bool IsOperatingAnotherEntity + { + get => bitsbyte[3]; + set => bitsbyte[3] = value; + } + + /// + /// Gets or Sets the Control Use Tile flag on the backing field + /// + public bool ControlUseTile + { + get => bitsbyte[4]; + set => bitsbyte[4] = value; + } + + /// + /// Gets or Sets the Has Camera Target flag on the backing field + /// + public bool HasCameraTarget + { + get => bitsbyte[5]; + set => bitsbyte[5] = value; + } + + /// + /// Gets or Sets the Last Item Use Attempt Success flag on the backing field + /// + public bool LastItemUseAttemptSuccess + { + get => bitsbyte[6]; + set => bitsbyte[6] = value; + } + /// /// Constructs a new instance of MiscDataSet3 with the given backing BitsByte /// diff --git a/TShockAPI/Modules/ModuleManager.cs b/TShockAPI/Modules/ModuleManager.cs index 679e40bca..dc345a7fc 100644 --- a/TShockAPI/Modules/ModuleManager.cs +++ b/TShockAPI/Modules/ModuleManager.cs @@ -75,7 +75,7 @@ public void InitialiseModule(Type moduleType, object[] parameters) } } - if (args.Count() == ctorParams.Length) + if (args.Count == ctorParams.Length) constructor = ctor; } diff --git a/TShockAPI/Modules/ReduceConsoleSpam.cs b/TShockAPI/Modules/ReduceConsoleSpam.cs index 854536dcc..c5ca335af 100644 --- a/TShockAPI/Modules/ReduceConsoleSpam.cs +++ b/TShockAPI/Modules/ReduceConsoleSpam.cs @@ -85,7 +85,7 @@ bool replace(string text) { var mprogress = e.Value.Substring(0, master + 1/*%*/); var sprogress = e.Value.Substring(sub + FindSub.Length); - if (mprogress.EndsWith("%") && sprogress.EndsWith("%")) + if (mprogress.EndsWith('%') && sprogress.EndsWith('%')) { var text = e.Value.Substring(master + FindMaster.Length, sub - master - FindMaster.Length).Trim(); diff --git a/TShockAPI/Net/SpawnMsg.cs b/TShockAPI/Net/SpawnMsg.cs index 241a42fa4..fca38c1b4 100644 --- a/TShockAPI/Net/SpawnMsg.cs +++ b/TShockAPI/Net/SpawnMsg.cs @@ -35,6 +35,7 @@ public override PacketTypes ID public int RespawnTimer { get; set; } public short NumberOfDeathsPVE { get; set; } public short NumberOfDeathsPVP { get; set; } + public int Team { get; set; } public PlayerSpawnContext PlayerSpawnContext { get; set; } public override void Pack(Stream stream) @@ -45,6 +46,7 @@ public override void Pack(Stream stream) stream.WriteInt32(RespawnTimer); stream.WriteInt16(NumberOfDeathsPVE); stream.WriteInt16(NumberOfDeathsPVP); + stream.WriteByte((byte)Team); stream.WriteByte((byte) PlayerSpawnContext); } } diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 0f0dc6e35..5dcdb1cd4 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -94,37 +94,71 @@ public struct NetItem /// /// 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 + TrashSlots + (LoadoutArmorSlots * 3) + + 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); - 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); + public static readonly (int, int) InventoryIndex = (0, InventorySlots); - 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 (int, int) ArmorIndex = + (InventoryIndex.Item2, InventoryIndex.Item2 + ArmorSlots); - 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 (int, int) DyeIndex = + (ArmorIndex.Item2, ArmorIndex.Item2 + DyeSlots); - public static readonly Tuple Loadout3Armor = new Tuple(Loadout2Dye.Item2, Loadout2Dye.Item2 + LoadoutArmorSlots); - public static readonly Tuple Loadout3Dye = new Tuple(Loadout3Armor.Item2, Loadout3Armor.Item2 + LoadoutDyeSlots); + public static readonly (int, int) MiscEquipIndex = + (DyeIndex.Item2, DyeIndex.Item2 + MiscEquipSlots); + + public static readonly (int, int) MiscDyeIndex = + (MiscEquipIndex.Item2, MiscEquipIndex.Item2 + MiscDyeSlots); + + public static readonly (int, int) PiggyIndex = + (MiscDyeIndex.Item2, MiscDyeIndex.Item2 + PiggySlots); + + public static readonly (int, int) SafeIndex = + (PiggyIndex.Item2, PiggyIndex.Item2 + SafeSlots); + + public static readonly (int, int) TrashIndex = + (SafeIndex.Item2, SafeIndex.Item2 + TrashSlots); + + public static readonly (int, int) ForgeIndex = + (TrashIndex.Item2, TrashIndex.Item2 + ForgeSlots); + + public static readonly (int, int) VoidIndex = + (ForgeIndex.Item2, ForgeIndex.Item2 + VoidSlots); + + public static readonly (int, int) Loadout1Armor = + (VoidIndex.Item2, VoidIndex.Item2 + LoadoutArmorSlots); + + public static readonly (int, int) Loadout1Dye = + (Loadout1Armor.Item2, Loadout1Armor.Item2 + LoadoutDyeSlots); + + public static readonly (int, int) Loadout2Armor = + (Loadout1Dye.Item2, Loadout1Dye.Item2 + LoadoutArmorSlots); + + public static readonly (int, int) Loadout2Dye = + (Loadout2Armor.Item2, Loadout2Armor.Item2 + LoadoutDyeSlots); + + public static readonly (int, int) Loadout3Armor = + (Loadout2Dye.Item2, Loadout2Dye.Item2 + LoadoutArmorSlots); + + public static readonly (int, int) Loadout3Dye = + (Loadout3Armor.Item2, Loadout3Armor.Item2 + LoadoutDyeSlots); [JsonProperty("netID")] private int _netId; + [JsonProperty("prefix")] private byte _prefixId; + [JsonProperty("stack")] private int _stack; + [JsonProperty("favorited")] + private bool _favorited; + /// /// Gets the net ID. /// @@ -149,17 +183,28 @@ public int Stack get { return _stack; } } + /// + /// Gets the favorited. + /// + public bool Favorited + { + get { return _favorited; } + } + + /// /// Creates a new . /// /// The net ID. /// The stack. /// The prefix ID. - public NetItem(int netId, int stack = 1, byte prefixId = 0) + /// The favorited. + public NetItem(int netId, int stack = 1, byte prefixId = 0, bool favorited = false) { _netId = netId; _stack = stack; _prefixId = prefixId; + _favorited = favorited; } /// @@ -168,9 +213,10 @@ public NetItem(int netId, int stack = 1, byte prefixId = 0) /// Item in the game. public NetItem(Item item) { - _netId = item.netID; + _netId = item.type; _stack = item.stack; _prefixId = item.prefix; + _favorited = item.favorited; } /// @@ -184,6 +230,7 @@ public Item ToItem() item.netDefaults(_netId); item.stack = _stack; item.prefix = _prefixId; + item.favorited = _favorited; return item; } @@ -194,7 +241,7 @@ public Item ToItem() /// public override string ToString() { - return String.Format("{0},{1},{2}", _netId, _stack, _prefixId); + return string.Format("{0},{1},{2},{3}", _netId, _stack, _prefixId, _favorited ? 1 : 0); } /// @@ -206,18 +253,18 @@ public override string ToString() /// public static NetItem Parse(string str) { - if (str == null) - throw new ArgumentNullException("str"); + ArgumentNullException.ThrowIfNull(str); string[] comp = str.Split(','); - if (comp.Length != 3) - throw new FormatException("String does not contain three sections."); + if (comp.Length != 4) + throw new FormatException("String does not contain 4 sections."); - int netId = Int32.Parse(comp[0]); - int stack = Int32.Parse(comp[1]); - byte prefixId = Byte.Parse(comp[2]); + int netId = int.Parse(comp[0]); + int stack = int.Parse(comp[1]); + byte prefixId = byte.Parse(comp[2]); + bool favorited = int.Parse(comp[3]) == 1; - return new NetItem(netId, stack, prefixId); + return new NetItem(netId, stack, prefixId, favorited); } /// @@ -229,7 +276,7 @@ public static explicit operator NetItem(Item item) { return item == null ? new NetItem() - : new NetItem(item.netID, item.stack, item.prefix); + : new NetItem(item.type, item.stack, item.prefix, item.favorited); } } } diff --git a/TShockAPI/PaginationTools.cs b/TShockAPI/PaginationTools.cs index 1f9b457ef..e74a56afb 100644 --- a/TShockAPI/PaginationTools.cs +++ b/TShockAPI/PaginationTools.cs @@ -42,8 +42,7 @@ public string HeaderFormat get { return this.headerFormat; } set { - if (value == null) - throw new ArgumentNullException(); + ArgumentNullException.ThrowIfNull(value); this.headerFormat = value; } @@ -58,8 +57,7 @@ public string FooterFormat get { return this.footerFormat; } set { - if (value == null) - throw new ArgumentNullException(); + ArgumentNullException.ThrowIfNull(value); this.footerFormat = value; } diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index 59df7182b..4a18b0368 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -24,7 +24,6 @@ You should have received a copy of the GNU General Public License using System.Text; // Since the permission nodes have annotations that say what they are, we don't need XML comments. -#pragma warning disable 1591 namespace TShockAPI { @@ -313,6 +312,9 @@ public static class Permissions [Description("User can use the 'lanternsnight' subcommand of the 'worldevent' command")] public static readonly string managelanternsnightevent = "tshock.world.events.lanternsnight"; + [Description("User can use the 'meteorshower' subcommand of the 'worldevent' command")] + public static readonly string managemeteorshowerevent = "tshock.world.events.meteorshower"; + [Description("User can change expert state.")] public static readonly string toggleexpert = "tshock.world.toggleexpert"; @@ -509,6 +511,9 @@ public static class Permissions [Description("Player can send emotes.")] public static readonly string sendemoji = "tshock.sendemoji"; + + [Description("Player can send emotes.")] + public static readonly string playerspectating = "tshock.spectating"; #endregion /// /// Lists all commands associated with a given permission @@ -542,7 +547,7 @@ public static void DumpDescriptions() sb.AppendLine($"## {name}"); sb.AppendLine($"{desc}"); - sb.AppendLine(GetString("* **Commands**: `{0}`", strs.Count() > 0 ? string.Join(", ", strs) : GetString("No associated commands."))); + sb.AppendLine(GetString("* **Commands**: `{0}`", strs.Any() ? string.Join(", ", strs) : GetString("No associated commands."))); sb.AppendLine(); } diff --git a/TShockAPI/PlayerData.cs b/TShockAPI/PlayerData.cs index 1ab5430fa..ab457007b 100644 --- a/TShockAPI/PlayerData.cs +++ b/TShockAPI/PlayerData.cs @@ -39,6 +39,8 @@ public class PlayerData public int spawnY = -1; public int? extraSlot; public int? skinVariant; + public int? voiceVariant; + public float? voicePitchOffset; public int? hair; public byte hairDye; public Color? hairColor; @@ -85,7 +87,7 @@ public PlayerData(bool includingStarterInventory = true) for (int i = 0; i < TShock.ServerSideCharacterConfig.Settings.StartingInventory.Count; i++) { var item = TShock.ServerSideCharacterConfig.Settings.StartingInventory[i]; - StoreSlot(i, item.NetId, item.PrefixId, item.Stack); + StoreSlot(i, item.NetId, item.PrefixId, item.Stack, item.Favorited); } } @@ -96,9 +98,10 @@ public PlayerData(bool includingStarterInventory = true) /// /// /// - public void StoreSlot(int slot, int netID, byte prefix, int stack) + /// + public void StoreSlot(int slot, int netID, byte prefix, int stack, bool favorited) { - StoreSlot(slot, new NetItem(netID, stack, prefix)); + StoreSlot(slot, new NetItem(netID, stack, prefix, favorited)); } /// @@ -130,6 +133,8 @@ public void CopyCharacter(TSPlayer player) this.spawnY = player.TPlayer.SpawnY; extraSlot = player.TPlayer.extraAccessory ? 1 : 0; this.skinVariant = player.TPlayer.skinVariant; + this.voiceVariant = player.TPlayer.voiceVariant; + this.voicePitchOffset = player.TPlayer.voicePitchOffset; this.hair = player.TPlayer.hair; this.hairDye = player.TPlayer.hairDye; this.hairColor = player.TPlayer.hairColor; @@ -301,9 +306,13 @@ public void RestoreCharacter(TSPlayer player) player.sscDeathsPVP = this.deathsPVP; if (extraSlot != null) - player.TPlayer.extraAccessory = extraSlot.Value == 1 ? true : false; + player.TPlayer.extraAccessory = extraSlot.Value == 1; if (this.skinVariant != null) player.TPlayer.skinVariant = this.skinVariant.Value; + if (this.voiceVariant != null) + player.TPlayer.voiceVariant = this.voiceVariant.Value; + if (this.voicePitchOffset != null) + player.TPlayer.voicePitchOffset = this.voicePitchOffset.Value; if (this.hair != null) player.TPlayer.hair = this.hair.Value; if (this.hairColor != null) @@ -333,10 +342,11 @@ public void RestoreCharacter(TSPlayer player) //0-58 player.TPlayer.inventory[i].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.inventory[i].netID != 0) + if (player.TPlayer.inventory[i].type!= 0) { player.TPlayer.inventory[i].stack = this.inventory[i].Stack; player.TPlayer.inventory[i].prefix = this.inventory[i].PrefixId; + player.TPlayer.inventory[i].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.ArmorIndex.Item2) @@ -345,10 +355,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.ArmorIndex.Item1; player.TPlayer.armor[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.armor[index].netID != 0) + if (player.TPlayer.armor[index].type != 0) { player.TPlayer.armor[index].stack = this.inventory[i].Stack; player.TPlayer.armor[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.armor[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.DyeIndex.Item2) @@ -357,10 +368,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.DyeIndex.Item1; player.TPlayer.dye[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.dye[index].netID != 0) + if (player.TPlayer.dye[index].type != 0) { player.TPlayer.dye[index].stack = this.inventory[i].Stack; player.TPlayer.dye[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.dye[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.MiscEquipIndex.Item2) @@ -369,10 +381,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.MiscEquipIndex.Item1; player.TPlayer.miscEquips[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.miscEquips[index].netID != 0) + if (player.TPlayer.miscEquips[index].type != 0) { player.TPlayer.miscEquips[index].stack = this.inventory[i].Stack; player.TPlayer.miscEquips[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.miscEquips[index].favorited =this.inventory[i].Favorited; } } else if (i < NetItem.MiscDyeIndex.Item2) @@ -381,10 +394,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.MiscDyeIndex.Item1; player.TPlayer.miscDyes[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.miscDyes[index].netID != 0) + if (player.TPlayer.miscDyes[index].type != 0) { player.TPlayer.miscDyes[index].stack = this.inventory[i].Stack; player.TPlayer.miscDyes[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.miscDyes[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.PiggyIndex.Item2) @@ -393,10 +407,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.PiggyIndex.Item1; player.TPlayer.bank.item[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.bank.item[index].netID != 0) + if (player.TPlayer.bank.item[index].type != 0) { player.TPlayer.bank.item[index].stack = this.inventory[i].Stack; player.TPlayer.bank.item[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.bank.item[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.SafeIndex.Item2) @@ -405,10 +420,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.SafeIndex.Item1; player.TPlayer.bank2.item[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.bank2.item[index].netID != 0) + if (player.TPlayer.bank2.item[index].type != 0) { player.TPlayer.bank2.item[index].stack = this.inventory[i].Stack; player.TPlayer.bank2.item[index].prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.bank2.item[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.TrashIndex.Item2) @@ -417,10 +433,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.TrashIndex.Item1; player.TPlayer.trashItem.netDefaults(this.inventory[i].NetId); - if (player.TPlayer.trashItem.netID != 0) + if (player.TPlayer.trashItem.type != 0) { player.TPlayer.trashItem.stack = this.inventory[i].Stack; player.TPlayer.trashItem.prefix = (byte)this.inventory[i].PrefixId; + player.TPlayer.trashItem.favorited = this.inventory[i].Favorited; } } else if (i < NetItem.ForgeIndex.Item2) @@ -429,10 +446,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.ForgeIndex.Item1; player.TPlayer.bank3.item[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.bank3.item[index].netID != 0) + if (player.TPlayer.bank3.item[index].type != 0) { player.TPlayer.bank3.item[index].stack = this.inventory[i].Stack; player.TPlayer.bank3.item[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.bank3.item[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.VoidIndex.Item2) @@ -441,10 +459,11 @@ public void RestoreCharacter(TSPlayer player) var index = i - NetItem.VoidIndex.Item1; player.TPlayer.bank4.item[index].netDefaults(this.inventory[i].NetId); - if (player.TPlayer.bank4.item[index].netID != 0) + if (player.TPlayer.bank4.item[index].type != 0) { player.TPlayer.bank4.item[index].stack = this.inventory[i].Stack; player.TPlayer.bank4.item[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.bank4.item[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout1Armor.Item2) @@ -452,10 +471,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[0].Armor[index].type != 0) { player.TPlayer.Loadouts[0].Armor[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[0].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[0].Armor[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout1Dye.Item2) @@ -463,10 +483,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[0].Dye[index].type != 0) { player.TPlayer.Loadouts[0].Dye[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[0].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[0].Dye[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout2Armor.Item2) @@ -474,10 +495,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[1].Armor[index].type != 0) { player.TPlayer.Loadouts[1].Armor[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[1].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[1].Armor[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout2Dye.Item2) @@ -485,10 +507,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[1].Dye[index].type != 0) { player.TPlayer.Loadouts[1].Dye[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[1].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[1].Dye[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout3Armor.Item2) @@ -496,10 +519,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[2].Armor[index].type != 0) { player.TPlayer.Loadouts[2].Armor[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[2].Armor[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[2].Armor[index].favorited = this.inventory[i].Favorited; } } else if (i < NetItem.Loadout3Dye.Item2) @@ -507,10 +531,11 @@ public void RestoreCharacter(TSPlayer player) 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) + if (player.TPlayer.Loadouts[2].Dye[index].type != 0) { player.TPlayer.Loadouts[2].Dye[index].stack = this.inventory[i].Stack; player.TPlayer.Loadouts[2].Dye[index].Prefix((byte)this.inventory[i].PrefixId); + player.TPlayer.Loadouts[2].Dye[index].favorited = this.inventory[i].Favorited; } } } @@ -519,177 +544,73 @@ public void RestoreCharacter(TSPlayer player) // 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++) - { - 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++; - } - 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); - 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++; - } - 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++; - } - - - - 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 < Player.maxBuffs; k++) + 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); + + + for (var i = 0; i < NetItem.InventorySlots; ++i) + NetMessage.TrySendData((int)PacketTypes.PlayerSlot, number: player.Index, + number2: PlayerItemSlotID.Inventory0 + i); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.armor, PlayerItemSlotID.Armor0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.dye, PlayerItemSlotID.Dye0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.miscEquips, PlayerItemSlotID.Misc0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.miscDyes, PlayerItemSlotID.MiscDye0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.bank.item, PlayerItemSlotID.Bank1_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.bank2.item, PlayerItemSlotID.Bank2_0); + NetMessage.TrySendData((int)PacketTypes.PlayerSlot, number: player.Index, + number2: PlayerItemSlotID.TrashItem); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.bank3.item, PlayerItemSlotID.Bank3_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.bank4.item, PlayerItemSlotID.Bank4_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[0].Armor, + PlayerItemSlotID.Loadout1_Armor_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[0].Dye, PlayerItemSlotID.Loadout1_Dye_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[1].Armor, + PlayerItemSlotID.Loadout2_Armor_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[1].Dye, PlayerItemSlotID.Loadout2_Dye_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[2].Armor, + PlayerItemSlotID.Loadout3_Armor_0); + TrySendingItemArray(player.Index, -1, -1, player.TPlayer.Loadouts[2].Dye, PlayerItemSlotID.Loadout3_Dye_0); + + + NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(player.Name), + player.Index); + NetMessage.SendData((int)PacketTypes.PlayerMana, -1, -1, NetworkText.Empty, player.Index); + NetMessage.SendData((int)PacketTypes.PlayerHp, -1, -1, NetworkText.Empty, player.Index); + + for (var i = 0; i < NetItem.InventorySlots; ++i) + NetMessage.TrySendData((int)PacketTypes.PlayerSlot, remoteClient: player.Index, number: player.Index, + number2: PlayerItemSlotID.Inventory0 + i); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.armor, PlayerItemSlotID.Armor0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.dye, PlayerItemSlotID.Dye0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.miscEquips, PlayerItemSlotID.Misc0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.miscDyes, PlayerItemSlotID.MiscDye0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.bank.item, PlayerItemSlotID.Bank1_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.bank2.item, PlayerItemSlotID.Bank2_0); + NetMessage.TrySendData((int)PacketTypes.PlayerSlot, remoteClient: player.Index, number: player.Index, + number2: PlayerItemSlotID.TrashItem); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.bank3.item, PlayerItemSlotID.Bank3_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.bank4.item, PlayerItemSlotID.Bank4_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[0].Armor, + PlayerItemSlotID.Loadout1_Armor_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[0].Dye, + PlayerItemSlotID.Loadout1_Dye_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[1].Armor, + PlayerItemSlotID.Loadout2_Armor_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[1].Dye, + PlayerItemSlotID.Loadout2_Dye_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[2].Armor, + PlayerItemSlotID.Loadout3_Armor_0); + TrySendingItemArray(player.Index, player.Index, -1, player.TPlayer.Loadouts[2].Dye, + PlayerItemSlotID.Loadout3_Dye_0); + + NetMessage.SendData((int)PacketTypes.PlayerInfo, player.Index, -1, NetworkText.FromLiteral(player.Name), + player.Index); + NetMessage.SendData((int)PacketTypes.PlayerMana, player.Index, -1, NetworkText.Empty, player.Index); + NetMessage.SendData((int)PacketTypes.PlayerHp, player.Index, -1, NetworkText.Empty, player.Index); + + for (var k = 0; k < Player.maxBuffs; k++) { player.TPlayer.buffType[k] = 0; } @@ -700,29 +621,39 @@ public void RestoreCharacter(TSPlayer player) * 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((int)PacketTypes.PlayerBuff, -1, -1, NetworkText.Empty, player.Index); + NetMessage.SendData((int)PacketTypes.PlayerBuff, player.Index, -1, NetworkText.Empty, player.Index); - NetMessage.SendData(76, player.Index, -1, NetworkText.Empty, player.Index); - NetMessage.SendData(76, -1, -1, NetworkText.Empty, player.Index); + 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(39, player.Index, -1, NetworkText.Empty, 400); + // NetMessage.SendData((int)PacketTypes.RemoveItemOwner, player.Index, -1, NetworkText.Empty, 400); - if (Main.GameModeInfo.IsJourneyMode) + if (Main.IsJourneyMode) { var sacrificedItems = TShock.ResearchDatastore.GetSacrificedItems(); - for(int i = 0; i < ItemID.Count; i++) + for (int i = 0; i < ItemID.Count; i++) { - var amount = 0; - if (sacrificedItems.ContainsKey(i)) - { - amount = sacrificedItems[i]; - } + sacrificedItems.TryGetValue(i, out int amount); - var response = NetCreativeUnlocksModule.SerializeItemSacrifice(i, amount); + var response = + NetCreativeUnlocksPlayerReportModule.SerializeSacrificeRequest(player.Index, i, amount); NetManager.Instance.SendToClient(response, player.Index); } } + + // 别删了,ItemOwner那边应该是依赖于原版的一个行为会发包恢复,现在好像没了,先用这个 + player.IgnoreSSCPackets = false; + } + + private static void TrySendingItemArray(int plr,int remoteClient, int ignoreClient, Item[] array, int slotStartIndex) + { + for (var index = 0; index < array.Length; ++index) + { + NetMessage.TrySendData(5, number: plr, number2: slotStartIndex + index, remoteClient: remoteClient, ignoreClient: ignoreClient); + } } } } diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index a6e681e2a..67fa1eed8 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -221,7 +221,7 @@ public Rest(IPAddress ip, int port) Ip = ip; Port = port; AssemblyName assembly = this.GetType().Assembly.GetName(); - serverHeader = new StringHeader("Server", String.Format("{0}/{1}", assembly.Name, assembly.Version)); + serverHeader = new StringHeader("Server", string.Format("{0}/{1}", assembly.Name, assembly.Version)); } /// @@ -378,10 +378,10 @@ protected virtual object ProcessRequest(object sender, RequestEventArgs e) uri = uri.TrimEnd('/'); string upgrade = null; - if (redirects.ContainsKey(uri)) + if (redirects.TryGetValue(uri, out var value)) { - upgrade = redirects[uri].Item2; - uri = redirects[uri].Item1; + upgrade = value.Item2; + uri = value.Item1; } foreach (var com in commands) @@ -398,7 +398,7 @@ protected virtual object ProcessRequest(object sender, RequestEventArgs e) for (int i = 0; i < com.UriVerbs.Length; i++) verbs.Add(com.UriVerbs[i], match.Groups[i + 1].Value); } - else if (com.UriTemplate.ToLower() != uri.ToLower()) + else if (!string.Equals(com.UriTemplate, uri, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 5dd2ff201..2b0d3ccd5 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -155,7 +155,7 @@ public class Token : Noun /// /// Creates a new instance of /// - public Token() : base("token", true, GetString("The REST authentication token."), typeof(String)) { } + public Token() : base("token", true, GetString("The REST authentication token."), typeof(string)) { } } /// @@ -300,7 +300,7 @@ public void RegisterRestfulCommands() [Description("Executes a remote command on the server, and returns the output of the command.")] [RouteAttribute("/v3/server/rawcmd")] [Permission(RestPermissions.restrawcommand)] - [Noun("cmd", true, "The command and arguments to execute.", typeof(String))] + [Noun("cmd", true, "The command and arguments to execute.", typeof(string))] [Token] private object ServerCommandV3(RestRequestArgs args) { @@ -321,7 +321,7 @@ private object ServerCommandV3(RestRequestArgs args) [Route("/v2/server/off")] [Permission(RestPermissions.restmaintenance)] [Noun("confirm", true, "Required to confirm that actually want to turn the server off.", typeof(bool))] - [Noun("message", false, "The shutdown message.", typeof(String))] + [Noun("message", false, "The shutdown message.", typeof(string))] [Noun("nosave", false, "Shutdown without saving.", typeof(bool))] [Token] private object ServerOff(RestRequestArgs args) @@ -350,7 +350,7 @@ private object ServerReload(RestRequestArgs args) [Description("Broadcast a server wide message.")] [Route("/v2/server/broadcast")] - [Noun("msg", true, "The message to broadcast.", typeof(String))] + [Noun("msg", true, "The message to broadcast.", typeof(string))] [Token] private object ServerBroadcast(RestRequestArgs args) { @@ -484,9 +484,9 @@ private object UserListV2(RestRequestArgs args) [Description("Create a new TShock user account.")] [Route("/v2/users/create")] [Permission(RestPermissions.restmanageusers)] - [Noun("user", true, "The user account name for the new account.", typeof(String))] - [Noun("group", false, "The group the new account should be assigned.", typeof(String))] - [Noun("password", true, "The password for the new account.", typeof(String))] + [Noun("user", true, "The user account name for the new account.", typeof(string))] + [Noun("group", false, "The group the new account should be assigned.", typeof(string))] + [Noun("password", true, "The password for the new account.", typeof(string))] [Token] private object UserCreateV2(RestRequestArgs args) { @@ -520,10 +520,10 @@ private object UserCreateV2(RestRequestArgs args) [Description("Update a users information.")] [Route("/v2/users/update")] [Permission(RestPermissions.restmanageusers)] - [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(String))] - [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(String))] - [Noun("password", false, "The users new password, and at least this or group must be defined.", typeof(String))] - [Noun("group", false, "The new group for the user, at least this or password must be defined.", typeof(String))] + [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(string))] + [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(string))] + [Noun("password", false, "The users new password, and at least this or group must be defined.", typeof(string))] + [Noun("group", false, "The new group for the user, at least this or password must be defined.", typeof(string))] [Token] private object UserUpdateV2(RestRequestArgs args) { @@ -571,8 +571,8 @@ private object UserUpdateV2(RestRequestArgs args) [Description("Destroy a TShock user account.")] [Route("/v2/users/destroy")] [Permission(RestPermissions.restmanageusers)] - [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(String))] - [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(String))] + [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(string))] + [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(string))] [Token] private object UserDestroyV2(RestRequestArgs args) { @@ -595,8 +595,8 @@ private object UserDestroyV2(RestRequestArgs args) [Description("List detailed information for a user account.")] [Route("/v2/users/read")] [Permission(RestPermissions.restviewusers)] - [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(String))] - [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(String))] + [Noun("user", true, "The search criteria (name or id of account to lookup).", typeof(string))] + [Noun("type", true, "The search criteria type (name for name lookup, id for id lookup).", typeof(string))] [Token] private object UserInfoV2(RestRequestArgs args) { @@ -615,10 +615,10 @@ private object UserInfoV2(RestRequestArgs args) [Description("Create a new ban entry.")] [Route("/v3/bans/create")] [Permission(RestPermissions.restmanagebans)] - [Noun("identifier", true, "The identifier to ban.", typeof(String))] - [Noun("reason", false, "The reason to assign to the ban.", typeof(String))] - [Noun("start", false, "The datetime at which the ban should start.", typeof(String))] - [Noun("end", false, "The datetime at which the ban should end.", typeof(String))] + [Noun("identifier", true, "The identifier to ban.", typeof(string))] + [Noun("reason", false, "The reason to assign to the ban.", typeof(string))] + [Noun("start", false, "The datetime at which the ban should start.", typeof(string))] + [Noun("end", false, "The datetime at which the ban should end.", typeof(string))] [Token] private object BanCreateV3(RestRequestArgs args) { @@ -675,7 +675,7 @@ private object BanCreateV3(RestRequestArgs args) [Description("Delete an existing ban entry.")] [Route("/v3/bans/destroy")] [Permission(RestPermissions.restmanagebans)] - [Noun("ticketNumber", true, "The ticket number of the ban to delete.", typeof(String))] + [Noun("ticketNumber", true, "The ticket number of the ban to delete.", typeof(string))] [Noun("fullDelete", false, "Whether or not to completely remove the ban from the system.", typeof(bool))] [Token] private object BanDestroyV3(RestRequestArgs args) @@ -702,7 +702,7 @@ private object BanDestroyV3(RestRequestArgs args) [Description("View the details of a specific ban.")] [Route("/v3/bans/read")] [Permission(RestPermissions.restviewbans)] - [Noun("ticketNumber", true, "The ticket number to search for.", typeof(String))] + [Noun("ticketNumber", true, "The ticket number to search for.", typeof(string))] [Token] private object BanInfoV3(RestRequestArgs args) { @@ -923,7 +923,7 @@ private object WorldBloodmoonV3(RestRequestArgs args) [Description("Unmute a player.")] [Route("/v2/players/unmute")] [Permission(RestPermissions.restmute)] - [Noun("player", true, "The player to mute.", typeof(String))] + [Noun("player", true, "The player to mute.", typeof(string))] [Token] private object PlayerUnMute(RestRequestArgs args) { @@ -933,7 +933,7 @@ private object PlayerUnMute(RestRequestArgs args) [Description("Mute a player.")] [Route("/v2/players/mute")] [Permission(RestPermissions.restmute)] - [Noun("player", true, "The player to mute.", typeof(String))] + [Noun("player", true, "The player to mute.", typeof(string))] [Token] private object PlayerMute(RestRequestArgs args) { @@ -967,7 +967,7 @@ private object PlayerListV2(RestRequestArgs args) [Description("Get information for a user.")] [Route("/v3/players/read")] [Permission(RestPermissions.restuserinfo)] - [Noun("player", true, "The player to lookup", typeof(String))] + [Noun("player", true, "The player to lookup", typeof(string))] [Token] private object PlayerReadV3(RestRequestArgs args) { @@ -989,7 +989,7 @@ private object PlayerReadV3(RestRequestArgs args) {"muted", player.mute }, {"position", player.TileX + "," + player.TileY}, {"inventory", string.Join(", ", inventory.Select(p => (p.Name + ":" + p.stack)))}, - {"armor", string.Join(", ", equipment.Select(p => (p.netID + ":" + p.prefix)))}, + {"armor", string.Join(", ", equipment.Select(p => (p.type + ":" + p.prefix)))}, {"dyes", string.Join(", ", dyes.Select(p => (p.Name)))}, {"buffs", string.Join(", ", player.TPlayer.buffType)} }; @@ -998,7 +998,7 @@ private object PlayerReadV3(RestRequestArgs args) [Description("Get information for a user.")] [Route("/v4/players/read")] [Permission(RestPermissions.restuserinfo)] - [Noun("player", true, "The player to lookup", typeof(String))] + [Noun("player", true, "The player to lookup", typeof(string))] [Token] private object PlayerReadV4(RestRequestArgs args) { @@ -1037,8 +1037,8 @@ private object PlayerReadV4(RestRequestArgs args) [Description("Kick a player off the server.")] [Route("/v2/players/kick")] [Permission(RestPermissions.restkick)] - [Noun("player", true, "The player to kick.", typeof(String))] - [Noun("reason", false, "The reason the player was kicked.", typeof(String))] + [Noun("player", true, "The player to kick.", typeof(string))] + [Noun("reason", false, "The reason the player was kicked.", typeof(string))] [Token] private object PlayerKickV2(RestRequestArgs args) { @@ -1054,8 +1054,8 @@ private object PlayerKickV2(RestRequestArgs args) [Description("Kill a player.")] [Route("/v2/players/kill")] [Permission(RestPermissions.restkill)] - [Noun("player", true, "The player to kick.", typeof(String))] - [Noun("from", false, "Who killed the player.", typeof(String))] + [Noun("player", true, "The player to kick.", typeof(string))] + [Noun("from", false, "Who killed the player.", typeof(string))] [Token] private object PlayerKill(RestRequestArgs args) { @@ -1091,7 +1091,7 @@ private object GroupList(RestRequestArgs args) [Description("Display information of a group.")] [Route("/v2/groups/read")] [Permission(RestPermissions.restviewgroups)] - [Noun("group", true, "The group name to get information on.", typeof(String))] + [Noun("group", true, "The group name to get information on.", typeof(string))] [Token] private object GroupInfo(RestRequestArgs args) { @@ -1113,7 +1113,7 @@ private object GroupInfo(RestRequestArgs args) [Description("Delete a group.")] [Route("/v2/groups/destroy")] [Permission(RestPermissions.restmanagegroups)] - [Noun("group", true, "The group name to delete.", typeof(String))] + [Noun("group", true, "The group name to delete.", typeof(string))] [Token] private object GroupDestroy(RestRequestArgs args) { @@ -1137,10 +1137,10 @@ private object GroupDestroy(RestRequestArgs args) [Description("Create a new group.")] [Route("/v2/groups/create")] [Permission(RestPermissions.restmanagegroups)] - [Noun("group", true, "The name of the new group.", typeof(String))] - [Noun("parent", false, "The name of the parent group.", typeof(String))] - [Noun("permissions", false, "A comma separated list of permissions for the new group.", typeof(String))] - [Noun("chatcolor", false, "A r,g,b string representing the color for this groups chat.", typeof(String))] + [Noun("group", true, "The name of the new group.", typeof(string))] + [Noun("parent", false, "The name of the parent group.", typeof(string))] + [Noun("permissions", false, "A comma separated list of permissions for the new group.", typeof(string))] + [Noun("chatcolor", false, "A r,g,b string representing the color for this groups chat.", typeof(string))] [Token] private object GroupCreate(RestRequestArgs args) { @@ -1161,10 +1161,10 @@ private object GroupCreate(RestRequestArgs args) [Route("/v2/groups/update")] [Permission(RestPermissions.restmanagegroups)] - [Noun("group", true, "The name of the group to modify.", typeof(String))] - [Noun("parent", false, "The name of the new parent for this group.", typeof(String))] - [Noun("chatcolor", false, "The new chat color r,g,b.", typeof(String))] - [Noun("permissions", false, "The new comma separated list of permissions.", typeof(String))] + [Noun("group", true, "The name of the group to modify.", typeof(string))] + [Noun("parent", false, "The name of the new parent for this group.", typeof(string))] + [Noun("chatcolor", false, "The new chat color r,g,b.", typeof(string))] + [Noun("permissions", false, "The new comma separated list of permissions.", typeof(string))] [Token] private object GroupUpdate(RestRequestArgs args) { @@ -1216,17 +1216,17 @@ public static void DumpDescriptions() sb.AppendLine("{0}".SFormat(descattr.Description)); var permission = method.GetCustomAttributes(false).Where(o => o is Permission); - if (permission.Count() > 0) + if (permission.Any()) { - sb.AppendLine(GetString("* **Permissions**: `{0}`", String.Join(", ", permission.Select(p => ((Permission)p).Name)))); + sb.AppendLine(GetString("* **Permissions**: `{0}`", string.Join(", ", permission.Select(p => ((Permission)p).Name)))); } else { sb.AppendLine(GetString("No special permissions are required for this route.")); } sb.AppendLine(); - var verbs = method.GetCustomAttributes(false).Where(o => o is Verb); - if (verbs.Count() > 0) + var verbs = method.GetCustomAttributes(false).Where(o => o is Verb).Cast(); + if (verbs.Any()) { sb.AppendLine(GetString("**Verbs**:")); foreach (Verb verb in verbs) @@ -1238,8 +1238,8 @@ public static void DumpDescriptions() } } sb.AppendLine(); - var nouns = method.GetCustomAttributes(false).Where(o => o is Noun); - if (nouns.Count() > 0) + var nouns = method.GetCustomAttributes(false).Where(o => o is Noun).Cast(); + if (nouns.Any()) { sb.AppendLine(GetString("**Nouns**:")); foreach (Noun noun in nouns) @@ -1252,7 +1252,7 @@ public static void DumpDescriptions() } sb.AppendLine(); sb.AppendLine(GetString("**Example Usage**: `{0}?{1}`", routeattr.Route, - string.Join("&", nouns.Select(n => String.Format("{0}={0}", ((Noun)n).Name))))); + string.Join("&", nouns.Select(n => string.Format("{0}={0}", ((Noun)n).Name))))); sb.AppendLine(); } } @@ -1375,7 +1375,7 @@ private Dictionary PlayerFilter(TSPlayer tsPlayer, EscapedParame } foreach (EscapedParameter filter in parameters) { - if (player.ContainsKey(filter.Name) && !player[filter.Name].Equals(filter.Value)) + if (player.TryGetValue(filter.Name, out object value) && !value.Equals(filter.Value)) return null; } return player; diff --git a/TShockAPI/Rest/RestObject.cs b/TShockAPI/Rest/RestObject.cs index 99459b519..6142333c3 100644 --- a/TShockAPI/Rest/RestObject.cs +++ b/TShockAPI/Rest/RestObject.cs @@ -63,8 +63,7 @@ public RestObject(string status = "200") { get { - object ret; - if (TryGetValue(key, out ret)) + if (TryGetValue(key, out object ret)) return ret; return null; } diff --git a/TShockAPI/Rest/RestVerbs.cs b/TShockAPI/Rest/RestVerbs.cs index 1ef4addff..0525295c3 100644 --- a/TShockAPI/Rest/RestVerbs.cs +++ b/TShockAPI/Rest/RestVerbs.cs @@ -36,8 +36,7 @@ public class RestVerbs : Dictionary { get { - string ret; - if (TryGetValue(key, out ret)) + if (TryGetValue(key, out string ret)) { return Uri.UnescapeDataString(ret); } diff --git a/TShockAPI/Rest/SecureRest.cs b/TShockAPI/Rest/SecureRest.cs index 5d785219e..c65cfa3a5 100644 --- a/TShockAPI/Rest/SecureRest.cs +++ b/TShockAPI/Rest/SecureRest.cs @@ -42,7 +42,7 @@ public struct TokenData public Dictionary Tokens { get; protected set; } public Dictionary AppTokens { get; protected set; } - private RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); + private RandomNumberGenerator _rng = RandomNumberGenerator.Create(); public SecureRest(IPAddress ip, int port) : base(ip, port) @@ -67,14 +67,10 @@ public SecureRest(IPAddress ip, int port) private void AddTokenToBucket(string ip) { - if (tokenBucket.ContainsKey(ip)) + if (!tokenBucket.TryAdd(ip, 1)) { tokenBucket[ip] += 1; } - else - { - tokenBucket.Add(ip, 1); - } } private object DestroyToken(RestRequestArgs args) diff --git a/TShockAPI/SaveManager.cs b/TShockAPI/SaveManager.cs index 76fe294fa..5ed2497fd 100644 --- a/TShockAPI/SaveManager.cs +++ b/TShockAPI/SaveManager.cs @@ -38,8 +38,8 @@ private SaveManager() public static SaveManager Instance { get { return instance; } } // Producer Consumer - private EventWaitHandle _wh = new AutoResetEvent(false); - private Object _saveLock = new Object(); + private AutoResetEvent _wh = new AutoResetEvent(false); + private object _saveLock = new object(); private Queue _saveQueue = new Queue(); private Thread _saveThread; private int saveQueueCount { get { lock (_saveLock) return _saveQueue.Count; } } diff --git a/TShockAPI/Sockets/LinuxTcpSocket.cs b/TShockAPI/Sockets/LinuxTcpSocket.cs index 5a95794be..d2a0acafa 100644 --- a/TShockAPI/Sockets/LinuxTcpSocket.cs +++ b/TShockAPI/Sockets/LinuxTcpSocket.cs @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; @@ -123,10 +123,6 @@ private void SendCallback(IAsyncResult result) } } - void ISocket.SendQueuedPackets() - { - } - void ISocket.AsyncSend(byte[] data, int offset, int size, SocketSendCallback callback, object state) { byte[] array = LegacyNetBufferPool.RequestBuffer(data, offset, size); diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 52d319fcc..e29d495d7 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -38,7 +38,7 @@ You should have received a copy of the GNU General Public License namespace TShockAPI { /// - /// Bitflags used with the method + /// Bitflags used with the method /// [Flags] public enum DisableFlags @@ -121,7 +121,7 @@ public class TSPlayer /// If the string comes with tsi: or tsn:, we'll only return a list with one element, /// either the player with the matching ID or name, respectively. /// - /// Player name or ID + /// Player name or ID /// A list of matching players public static List FindByNameOrID(string search) { @@ -155,14 +155,13 @@ public static List FindByNameOrID(string search) } } - string searchLower = search.ToLower(); foreach (TSPlayer player in TShock.Players) { if (player != null) { if ((search == player.Name) && exactNameOnly) return new List { player }; - if (player.Name.ToLower().StartsWith(searchLower)) + if (player.Name.StartsWith(search, StringComparison.CurrentCultureIgnoreCase)) found.Add(player); } } @@ -494,9 +493,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // From above: this is slots 0-58 in the inventory. // 0-58 Item item = new Item(); - if (inventory[i] != null && inventory[i].netID != 0) + if (inventory[i] != null && inventory[i].type != 0) { - item.netDefaults(inventory[i].netID); + item.netDefaults(inventory[i].type); item.Prefix(inventory[i].prefix); item.AffixName(); if (inventory[i].stack > item.maxStack || inventory[i].stack < 0) @@ -514,9 +513,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 59-78 var index = i - NetItem.ArmorIndex.Item1; Item item = new Item(); - if (armor[index] != null && armor[index].netID != 0) + if (armor[index] != null && armor[index].type != 0) { - item.netDefaults(armor[index].netID); + item.netDefaults(armor[index].type); item.Prefix(armor[index].prefix); item.AffixName(); if (armor[index].stack > item.maxStack || armor[index].stack < 0) @@ -534,9 +533,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 79-88 var index = i - NetItem.DyeIndex.Item1; Item item = new Item(); - if (dye[index] != null && dye[index].netID != 0) + if (dye[index] != null && dye[index].type != 0) { - item.netDefaults(dye[index].netID); + item.netDefaults(dye[index].type); item.Prefix(dye[index].prefix); item.AffixName(); if (dye[index].stack > item.maxStack || dye[index].stack < 0) @@ -554,9 +553,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 89-93 var index = i - NetItem.MiscEquipIndex.Item1; Item item = new Item(); - if (miscEquips[index] != null && miscEquips[index].netID != 0) + if (miscEquips[index] != null && miscEquips[index].type != 0) { - item.netDefaults(miscEquips[index].netID); + item.netDefaults(miscEquips[index].type); item.Prefix(miscEquips[index].prefix); item.AffixName(); if (miscEquips[index].stack > item.maxStack || miscEquips[index].stack < 0) @@ -574,9 +573,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 93-98 var index = i - NetItem.MiscDyeIndex.Item1; Item item = new Item(); - if (miscDyes[index] != null && miscDyes[index].netID != 0) + if (miscDyes[index] != null && miscDyes[index].type != 0) { - item.netDefaults(miscDyes[index].netID); + item.netDefaults(miscDyes[index].type); item.Prefix(miscDyes[index].prefix); item.AffixName(); if (miscDyes[index].stack > item.maxStack || miscDyes[index].stack < 0) @@ -594,9 +593,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 98-138 var index = i - NetItem.PiggyIndex.Item1; Item item = new Item(); - if (piggy[index] != null && piggy[index].netID != 0) + if (piggy[index] != null && piggy[index].type != 0) { - item.netDefaults(piggy[index].netID); + item.netDefaults(piggy[index].type); item.Prefix(piggy[index].prefix); item.AffixName(); @@ -615,9 +614,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 138-178 var index = i - NetItem.SafeIndex.Item1; Item item = new Item(); - if (safe[index] != null && safe[index].netID != 0) + if (safe[index] != null && safe[index].type != 0) { - item.netDefaults(safe[index].netID); + item.netDefaults(safe[index].type); item.Prefix(safe[index].prefix); item.AffixName(); @@ -635,9 +634,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { // 178-179 Item item = new Item(); - if (trash != null && trash.netID != 0) + if (trash != null && trash.type != 0) { - item.netDefaults(trash.netID); + item.netDefaults(trash.type); item.Prefix(trash.prefix); item.AffixName(); @@ -656,9 +655,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 179-220 var index = i - NetItem.ForgeIndex.Item1; Item item = new Item(); - if (forge[index] != null && forge[index].netID != 0) + if (forge[index] != null && forge[index].type != 0) { - item.netDefaults(forge[index].netID); + item.netDefaults(forge[index].type); item.Prefix(forge[index].prefix); item.AffixName(); @@ -677,9 +676,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) // 220-260 var index = i - NetItem.VoidIndex.Item1; Item item = new Item(); - if (voidVault[index] != null && voidVault[index].netID != 0) + if (voidVault[index] != null && voidVault[index].type != 0) { - item.netDefaults(voidVault[index].netID); + item.netDefaults(voidVault[index].type); item.Prefix(voidVault[index].prefix); item.AffixName(); @@ -697,9 +696,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout1Armor.Item1; Item item = new Item(); - if (loadout1Armor[index] != null && loadout1Armor[index].netID != 0) + if (loadout1Armor[index] != null && loadout1Armor[index].type != 0) { - item.netDefaults(loadout1Armor[index].netID); + item.netDefaults(loadout1Armor[index].type); item.Prefix(loadout1Armor[index].prefix); item.AffixName(); @@ -717,9 +716,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout1Dye.Item1; Item item = new Item(); - if (loadout1Dye[index] != null && loadout1Dye[index].netID != 0) + if (loadout1Dye[index] != null && loadout1Dye[index].type != 0) { - item.netDefaults(loadout1Dye[index].netID); + item.netDefaults(loadout1Dye[index].type); item.Prefix(loadout1Dye[index].prefix); item.AffixName(); @@ -737,9 +736,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout2Armor.Item1; Item item = new Item(); - if (loadout2Armor[index] != null && loadout2Armor[index].netID != 0) + if (loadout2Armor[index] != null && loadout2Armor[index].type != 0) { - item.netDefaults(loadout2Armor[index].netID); + item.netDefaults(loadout2Armor[index].type); item.Prefix(loadout2Armor[index].prefix); item.AffixName(); @@ -757,9 +756,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout2Dye.Item1; Item item = new Item(); - if (loadout2Dye[index] != null && loadout2Dye[index].netID != 0) + if (loadout2Dye[index] != null && loadout2Dye[index].type != 0) { - item.netDefaults(loadout2Dye[index].netID); + item.netDefaults(loadout2Dye[index].type); item.Prefix(loadout2Dye[index].prefix); item.AffixName(); @@ -777,9 +776,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout3Armor.Item1; Item item = new Item(); - if (loadout3Armor[index] != null && loadout3Armor[index].netID != 0) + if (loadout3Armor[index] != null && loadout3Armor[index].type != 0) { - item.netDefaults(loadout3Armor[index].netID); + item.netDefaults(loadout3Armor[index].type); item.Prefix(loadout3Armor[index].prefix); item.AffixName(); @@ -797,9 +796,9 @@ public bool HasHackedItemStacks(bool shouldWarnPlayer = false) { var index = i - NetItem.Loadout3Dye.Item1; Item item = new Item(); - if (loadout3Dye[index] != null && loadout3Dye[index].netID != 0) + if (loadout3Dye[index] != null && loadout3Dye[index].type != 0) { - item.netDefaults(loadout3Dye[index].netID); + item.netDefaults(loadout3Dye[index].type); item.Prefix(loadout3Dye[index].prefix); item.AffixName(); @@ -1405,7 +1404,7 @@ public TSPlayer(int index) /// Initializes a new instance of the class. /// /// The player's name. - protected TSPlayer(String playerName) + protected TSPlayer(string playerName) { TilesDestroyed = new Dictionary(); TilesCreated = new Dictionary(); @@ -1476,7 +1475,7 @@ public bool Teleport(float x, float y, byte style = 1) } /// - /// Teleports the player to their spawnpoint. + /// Teleports the player to their spawnpoint. /// Teleports to main spawnpoint if their bed is not active. /// Supports SSC. /// @@ -1485,7 +1484,7 @@ public bool TeleportSpawnpoint() // NOTE: it is vanilla behaviour to not permanently override the spawnpoint if the bed spawn is broken/invalid int x = TPlayer.SpawnX; int y = TPlayer.SpawnY; - if ((x == -1 && y == -1) || + if ((x == -1 && y == -1) || !Main.tile[x, y - 1].active() || Main.tile[x, y - 1].type != TileID.Beds || !WorldGen.StartRoomCheck(x, y - 1)) { x = Main.spawnTileX; @@ -1520,7 +1519,9 @@ public void Spawn(PlayerSpawnContext context, int? respawnTimer = null) /// The respawn timer, will be Player.respawnTimer if parameter is null. /// The number of deaths PVE, will be TPlayer.numberOfDeathsPVE if parameter is null. /// The number of deaths PVP, will be TPlayer.numberOfDeathsPVP if parameter is null. - public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawnTimer = null, short? numberOfDeathsPVE = null, short? numberOfDeathsPVP = null) + /// The team after player spawn. + public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawnTimer = null, + short? numberOfDeathsPVE = null, short? numberOfDeathsPVP = null, int team = -1) { using (var ms = new MemoryStream()) { @@ -1532,6 +1533,7 @@ public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawn RespawnTimer = respawnTimer ?? TShock.Players[Index].RespawnTimer * 60, NumberOfDeathsPVE = numberOfDeathsPVE ?? (short)TPlayer.numberOfDeathsPVE, NumberOfDeathsPVP = numberOfDeathsPVP ?? (short)TPlayer.numberOfDeathsPVP, + Team = team == -1 ? TPlayer.team : team, PlayerSpawnContext = context, }; msg.PackFull(ms); @@ -1632,7 +1634,7 @@ public void UpdateSection(Rectangle? rectangle = null, bool isLoaded = false) { Client.TileSections[i, j] = isLoaded; } - } + } } else { @@ -1759,7 +1761,7 @@ private Item GiveItem_FillAmmo(Item item) private Item GiveItemDirectly_FillIntoOccupiedSlot(Item item, int slot) { var inv = this.TPlayer.inventory; - if (inv[slot].type <= 0 || inv[slot].stack >= inv[slot].maxStack || !item.IsTheSameAs(inv[slot])) + if (inv[slot].type <= 0 || inv[slot].stack >= inv[slot].maxStack || item.IsNotTheSameAs(inv[slot])) return item; if (item.stack + inv[slot].stack <= inv[slot].maxStack) @@ -1895,7 +1897,7 @@ public virtual void SendMessage(string msg, Color color) /// The amount of blue color to factor in. Max: 255 public virtual void SendMessage(string msg, byte red, byte green, byte blue) { - if (msg.Contains("\n")) + if (msg.Contains('\n')) { string[] msgs = msg.Split('\n'); foreach (string message in msgs) @@ -1925,7 +1927,7 @@ public virtual void SendMessage(string msg, byte red, byte green, byte blue) /// The player who receives the message. public virtual void SendMessageFromPlayer(string msg, byte red, byte green, byte blue, int ply) { - if (msg.Contains("\n")) + if (msg.Contains('\n')) { string[] msgs = msg.Split('\n'); foreach (string message in msgs) @@ -1944,7 +1946,6 @@ public virtual void SendMessageFromPlayer(string msg, byte red, byte green, byte public void SendFileTextAsMessage(string file) { string foo = ""; - bool containsOldFormat = false; using (var tr = new StreamReader(file)) { Color lineColor; @@ -1967,7 +1968,7 @@ public void SendFileTextAsMessage(string file) } foo = foo.Replace("%map%", (TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)); - foo = foo.Replace("%players%", String.Join(", ", players)); + foo = foo.Replace("%players%", string.Join(", ", players)); foo = foo.Replace("%specifier%", TShock.Config.Settings.CommandSpecifier); foo = foo.Replace("%onlineplayers%", TShock.Utils.GetActivePlayerCount().ToString()); foo = foo.Replace("%serverslots%", TShock.Config.Settings.MaxSlots.ToString()); @@ -2291,10 +2292,7 @@ public virtual void SendRawData(byte[] data) /// The method that will be executed on confirmation ie user accepts public void AddResponse(string name, Action callback) { - if (AwaitingResponse.ContainsKey(name)) - { - AwaitingResponse.Remove(name); - } + AwaitingResponse.Remove(name); AwaitingResponse.Add(name, callback); } diff --git a/TShockAPI/TSServerPlayer.cs b/TShockAPI/TSServerPlayer.cs index 6c95de78e..457eb5cf7 100644 --- a/TShockAPI/TSServerPlayer.cs +++ b/TShockAPI/TSServerPlayer.cs @@ -76,14 +76,7 @@ public void SendConsoleMessage(string msg, byte red, byte green, byte blue) foreach (var snippet in snippets) { - if (snippet.Color != null) - { - Console.ForegroundColor = PickNearbyConsoleColor(snippet.Color); - } - else - { - Console.ForegroundColor = ConsoleColor.Gray; - } + Console.ForegroundColor = PickNearbyConsoleColor(snippet.Color); Console.Write(snippet.Text); } diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 276ad3385..8070b153c 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -45,7 +45,6 @@ You should have received a copy of the GNU General Public License using TShockAPI.Configuration; using Terraria.GameContent.Creative; using System.Runtime.InteropServices; -using MonoMod.Cil; using Terraria.Achievements; using Terraria.Initializers; using Terraria.UI.Chat; @@ -63,7 +62,7 @@ public class TShock : TerrariaPlugin /// VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info. public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; /// VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions. - public static readonly string VersionCodename = "Hopefully SSC works somewhat correctly now edition"; + public static readonly string VersionCodename = "!!! You are using a very unofficial dangerous testing version !!!"; /// SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins). public static string SavePath = "tshock"; @@ -225,7 +224,7 @@ static IntPtr ResolveNativeDep(string libraryName, Assembly assembly, DllImportS matches = Directory.GetFiles(x64, "*" + libraryName + "*", SearchOption.AllDirectories); } - if (matches.Count() == 0) + if (!matches.Any()) { matches = Directory.GetFiles(Environment.CurrentDirectory, "*" + libraryName + "*"); } @@ -330,7 +329,7 @@ public override void Initialize() File.Delete(Path.Combine(SavePath, "tshock.pid")); } File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), - Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); + Environment.ProcessId.ToString(CultureInfo.InvariantCulture)); CliParser.Reset(); HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); @@ -538,10 +537,10 @@ protected override void Dispose(bool disposing) /// args - The PlayerPostLoginEventArgs object. private void OnPlayerLogin(PlayerPostLoginEventArgs args) { - List KnownIps = new List(); + List KnownIps = new List(); if (!string.IsNullOrWhiteSpace(args.Player.Account.KnownIps)) { - KnownIps = JsonConvert.DeserializeObject>(args.Player.Account.KnownIps); + KnownIps = JsonConvert.DeserializeObject>(args.Player.Account.KnownIps); } if (KnownIps.Count == 0) @@ -993,7 +992,7 @@ private void OnPostInit(EventArgs args) } // Disable the auth system if "setup.lock" is present or a user account already exists - if (File.Exists(Path.Combine(SavePath, "setup.lock")) || (UserAccounts.GetUserAccounts().Count() > 0)) + if (File.Exists(Path.Combine(SavePath, "setup.lock")) || (UserAccounts.GetUserAccounts().Count > 0)) { SetupToken = 0; @@ -1029,7 +1028,7 @@ private void OnPostInit(EventArgs args) Console.WriteLine(GetString("This token will display until disabled by verification. ({0}setup)", Commands.Specifier)); Console.ResetColor(); } - + Log.ConsoleError(GetString("!!! You are using a very unofficial dangerous testing version !!!")); Regions.Reload(); Warps.ReloadWarps(); @@ -1359,7 +1358,7 @@ private void OnJoin(JoinEventArgs args) return; } - if (Config.Settings.KickEmptyUUID && String.IsNullOrWhiteSpace(player.UUID)) + if (Config.Settings.KickEmptyUUID && string.IsNullOrWhiteSpace(player.UUID)) { player.Kick(GetString("Your client sent a blank UUID. Configure it to send one or use a different client."), true, true, null, false); args.Handled = true; @@ -1482,7 +1481,7 @@ private void OnChat(ServerChatEventArgs args) { if (item.Value._name == args.CommandId._name) { - if (!String.IsNullOrEmpty(text)) + if (!string.IsNullOrEmpty(text)) { text = EnglishLanguage.GetCommandPrefixByName(item.Value._name) + ' ' + text; } @@ -1526,7 +1525,7 @@ private void OnChat(ServerChatEventArgs args) } else if (!TShock.Config.Settings.EnableChatAboveHeads) { - text = String.Format(Config.Settings.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, + text = string.Format(Config.Settings.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, args.Text); //Invoke the PlayerChat hook. If this hook event is handled then we need to prevent sending the chat message @@ -1544,7 +1543,7 @@ private void OnChat(ServerChatEventArgs args) { Player ply = Main.player[args.Who]; string name = ply.name; - ply.name = String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix); + ply.name = string.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix); //Update the player's name to format text nicely. This needs to be done because Terraria automatically formats messages against our will NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(ply.name), args.Who, 0, 0, 0, 0); @@ -1569,8 +1568,8 @@ private void OnChat(ServerChatEventArgs args) //Reset their name NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(name), args.Who, 0, 0, 0, 0); - string msg = String.Format("<{0}> {1}", - String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix), + string msg = string.Format("<{0}> {1}", + string.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix), text ); @@ -1645,7 +1644,7 @@ private void OnGetData(GetDataEventArgs e) return; } - if ((player.State < (int)ConnectionState.Complete || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 && + if ((player.State < (int)ConnectionState.Complete) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 && (int)type != 38 && (int)type != 21 && (int)type != 22 && type != PacketTypes.SyncLoadout) { e.Handled = true; diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index b20f11a4f..07f86b59c 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -1,61 +1,66 @@ - - Library - net9.0 - true - True - + 6.0.1 + TShock for Terraria + Pryaxis & TShock Contributors + TShockAPI + Copyright © Pryaxis & TShock Contributors 2011-2025 + + + GPL-3.0-or-later + Pryaxis & TShock Contributors + TShock is a toolbox for Terraria servers and communities. + UnrealMultiple.TShock-Beta + $(NoWarn);CS1591 + - Major Version - Minor Version - Build Number - Starting in version 4.2.5, we are no longer including the fourth decimal - location, which previously held the date and time. + + + BCryptNext + + + + + + + - 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.9.9 - TShock for Terraria - Pryaxis & TShock Contributors - TShockAPI - Copyright © Pryaxis & TShock Contributors 2011-2025 - - True - GPL-3.0-or-later - Pryaxis & TShock Contributors - TShock is a toolbox for Terraria servers and communities. - TShock - + + + - - - - - - - - - - - - - - - ..\prebuilts\HttpServer.dll - - - - - - HttpServer.dll - PreserveNewest - true - true - - + + + ..\prebuilts\HttpServer.dll + + + + + HttpServer.dll + PreserveNewest + true + true + + + + diff --git a/TShockAPI/UpdateManager.cs b/TShockAPI/UpdateManager.cs index b01d92a21..9d206df8c 100644 --- a/TShockAPI/UpdateManager.cs +++ b/TShockAPI/UpdateManager.cs @@ -70,6 +70,12 @@ private async Task CheckForUpdatesAsync(object state) { try { + // Check whether update checking is disabled + if (TShock.Config.Settings.DisabledUpdateCheck) + { + return; + } + CheckXMinutes = 30; await UpdateCheckAsync(state); } @@ -134,7 +140,7 @@ private async Task> ServerIsOutOfDateAsync() private void NotifyAdministrators(Dictionary update) { - var changes = update["changes"].Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + var changes = update["changes"].Split('\n', StringSplitOptions.RemoveEmptyEntries); NotifyAdministrator(TSPlayer.Server, changes); foreach (TSPlayer player in TShock.Players) { @@ -154,4 +160,4 @@ private void NotifyAdministrator(TSPlayer player, string[] changes) } } } -} \ No newline at end of file +} diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index 7a3cad6ce..945e98ce0 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -249,7 +249,7 @@ public bool TileSolid(int tileX, int tileY) public List GetItemByIdOrName(string text) { int type = -1; - if (Int32.TryParse(text, out type)) + if (int.TryParse(text, out type)) { if (type >= Terraria.ID.ItemID.Count) return new List(); @@ -335,11 +335,11 @@ public Item GetItemFromTag(string tag) if (!match.Success) return null; Item item = new Item(); - item.netDefaults(Int32.Parse(match.Groups["NetID"].Value)); - if (!String.IsNullOrWhiteSpace(match.Groups["Stack"].Value)) - item.stack = Int32.Parse(match.Groups["Stack"].Value); - if (!String.IsNullOrWhiteSpace(match.Groups["Prefix"].Value)) - item.prefix = Byte.Parse(match.Groups["Prefix"].Value); + item.netDefaults(int.Parse(match.Groups["NetID"].Value)); + if (!string.IsNullOrWhiteSpace(match.Groups["Stack"].Value)) + item.stack = int.Parse(match.Groups["Stack"].Value); + if (!string.IsNullOrWhiteSpace(match.Groups["Prefix"].Value)) + item.prefix = byte.Parse(match.Groups["Prefix"].Value); return item; } @@ -921,7 +921,7 @@ public HttpWebResponse GetResponseNoException(HttpWebRequest req) /// The , surrounded by the color tag with the appropriated hex code. public string ColorTag(string text, Color color) { - return String.Format("[c/{0}:{1}]", color.Hex3(), text); + return string.Format("[c/{0}:{1}]", color.Hex3(), text); } /// @@ -931,11 +931,11 @@ public string ColorTag(string text, Color color) /// The NetID surrounded by the item tag with proper stack/prefix data. public string ItemTag(Item item) { - int netID = item.netID; + int netID = item.type; int stack = item.stack; int prefix = item.prefix; string options = stack > 1 ? "/s" + stack : prefix != 0 ? "/p" + prefix : ""; - return String.Format("[i{0}:{1}]", options, netID); + return string.Format("[i{0}:{1}]", options, netID); } /// @@ -1065,7 +1065,7 @@ internal void DumpPermissionMatrix(string path) output.Append("[["); output.Append(g.Name); output.Append("]]"); - output.Append("|"); + output.Append('|'); } output.AppendLine(); @@ -1091,7 +1091,7 @@ internal void DumpPermissionMatrix(string path) } else { - output.Append("|"); + output.Append('|'); } } output.AppendLine(); @@ -1192,13 +1192,11 @@ internal void ComputeMaxStyles() item.netDefaults(i); if (item.placeStyle >= 0) { - if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile)) + if (!GetDataHandlers.MaxPlaceStyles.TryAdd(item.createTile, item.placeStyle)) { if (item.placeStyle > GetDataHandlers.MaxPlaceStyles[item.createTile]) GetDataHandlers.MaxPlaceStyles[item.createTile] = item.placeStyle; } - else - GetDataHandlers.MaxPlaceStyles.Add(item.createTile, item.placeStyle); } } } diff --git a/TShockLauncher.Tests/ChatTests.cs b/TShockLauncher.Tests/ChatTests.cs deleted file mode 100644 index 718698188..000000000 --- a/TShockLauncher.Tests/ChatTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Xna.Framework; -using NUnit.Framework; -using Terraria.UI.Chat; - -namespace TShockLauncher.Tests; - -public class ChatTests -{ - /// - /// Ensures that the does not cause exceptions when used on the server. - /// - /// - /// The behaviour of TShock regarding the achievement tag handler changes depending on if TShock has - /// a instance or not. Therefore, we do not check the correctness of the parsed message, but only if it - /// throws an exception. - /// - [TestCase] - public void TestChatAchievementTagHandler() - { - Assert.That(() => - { - ChatManager.ParseMessage("No achievement tags", Color.White); - ChatManager.ParseMessage("One achievement tag: [a:KILL_THE_SUN]", Color.White); - ChatManager.ParseMessage("One achievement tag, using the longer variant: [achievement:KILL_THE_SUN]", Color.White); - ChatManager.ParseMessage("Multiple achievement tags: [a:KILL_THE_SUN] and [a:TOPPED_OFF]", Color.White); - ChatManager.ParseMessage("One achievement tag, referring to a non-existent achievement: [a:_THIS_WILL_NEVER_EXIST_]", Color.White); - ChatManager.ParseMessage("Both valid and invalid achievement tags: [a:KILL_THE_SUN] and [a:_THIS_WILL_NEVER_EXIST_]", Color.White); - }, Throws.Nothing); - } -} diff --git a/TShockLauncher.Tests/GroupTests.cs b/TShockLauncher.Tests/GroupTests.cs deleted file mode 100644 index 01a058ca9..000000000 --- a/TShockLauncher.Tests/GroupTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using NUnit.Framework; -using TShockAPI; -using TShockAPI.DB; - -namespace TShockLauncher.Tests; - -public class GroupTests -{ - /// - /// This tests to ensure the group commands work. - /// - /// Due to the switch to Microsoft.Data.Sqlite, nulls have to be replaced with DBNull for the query to complete - [TestCase] - public void TestPermissions() - { - var groups = TShock.Groups = new GroupManager(TShock.DB); - - if (!groups.GroupExists("test")) - groups.AddGroup("test", null, "test", Group.defaultChatColor); - - groups.AddPermissions("test", new() { "abc" }); - - var hasperm = groups.GetGroupByName("test").Permissions.Contains("abc"); - Assert.That(hasperm, Is.True); - - groups.DeletePermissions("test", new() { "abc" }); - - hasperm = groups.GetGroupByName("test").Permissions.Contains("abc"); - Assert.That(hasperm, Is.False); - - groups.DeleteGroup("test"); - - var g = groups.GetGroupByName("test"); - Assert.That(g, Is.Null); - } -} - diff --git a/TShockLauncher.Tests/ServerInitTests.cs b/TShockLauncher.Tests/ServerInitTests.cs deleted file mode 100644 index 2ab72bd11..000000000 --- a/TShockLauncher.Tests/ServerInitTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using NUnit.Framework; -using System; -using System.Diagnostics; -using System.Threading; - -namespace TShockLauncher.Tests; - -public class ServerInitTests -{ - /// - /// This test will ensure that the TSAPI binary boots up as expected - /// - [TestCase] - public void EnsureBoots() - { - var are = new AutoResetEvent(false); - HookEvents.HookDelegate cb = (instance, args) => - { - args.ContinueExecution = false; - are.Set(); - Debug.WriteLine("Server init process successful"); - }; - HookEvents.Terraria.Main.DedServ += cb; - - new Thread(() => TerrariaApi.Server.Program.Main([])).Start(); - - var hit = are.WaitOne(TimeSpan.FromSeconds(10)); - - HookEvents.Terraria.Main.DedServ -= cb; - - Assert.That(hit, Is.True); - } -} - diff --git a/TShockLauncher.Tests/TShockLauncher.Tests.csproj b/TShockLauncher.Tests/TShockLauncher.Tests.csproj deleted file mode 100644 index 9be3968af..000000000 --- a/TShockLauncher.Tests/TShockLauncher.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net9.0 - enable - false - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/TShockLauncher.Tests/TestSetup.cs b/TShockLauncher.Tests/TestSetup.cs deleted file mode 100644 index 016f26790..000000000 --- a/TShockLauncher.Tests/TestSetup.cs +++ /dev/null @@ -1,28 +0,0 @@ -using NUnit.Framework; -using Terraria; -using Terraria.Initializers; -using Terraria.Localization; -using TShockAPI; - -namespace TShockLauncher.Tests; - -[SetUpFixture] -public class TestSetup -{ - /// - /// This will be called automatically by NUnit before the first test. - /// It serves to initialise the bare minimum variables needed for TShock to be testable without booting up an actual server. - /// - [OneTimeSetUp] - public static void SetupTShock() - { - ChatInitializer.Load(); - - Program.SavePath = ""; // 1.4.4.2 staticness introduced this where by default it is null, and any touch to Terraria.Main will use it and cause a crash. - LanguageManager.Instance.SetLanguage(GameCulture.DefaultCulture); // TShockAPI.Localization will fail without ActiveCulture set - Lang.InitializeLegacyLocalization(); // TShockAPI.Localization will fail without preparing NPC names etc - - var ts = new TShock(null); // prepares configs etc - ts.Initialize(); // used to prepare tshocks own static variables, such as the TShock.DB instance - } -} diff --git a/TShockLauncher/TShockLauncher.csproj b/TShockLauncher/TShockLauncher.csproj index 95b85859e..9dbdc6471 100644 --- a/TShockLauncher/TShockLauncher.csproj +++ b/TShockLauncher/TShockLauncher.csproj @@ -34,11 +34,11 @@ - + - + PreserveNewest true @@ -51,6 +51,7 @@ PreserveNewest true + diff --git a/TShockPluginManager/NugetCLI.cs b/TShockPluginManager/NugetCLI.cs index 66fcea183..5744667f6 100644 --- a/TShockPluginManager/NugetCLI.cs +++ b/TShockPluginManager/NugetCLI.cs @@ -181,7 +181,7 @@ static public void DumpOne(SourcePackageDependencyInfo pkg, IEnumerable data, IEnumerable builtins, int level) { - var indent = new String('\t', level); + var indent = new string('\t', level); Console.Write(indent); CLIHelpers.WriteLine(C.GetString($"{from.Id} from {from.Source.PackageSource.Name} [{from.Source.PackageSource.Source}]")); diff --git a/TerrariaServerAPI b/TerrariaServerAPI index 2b81ac745..fbfdde877 160000 --- a/TerrariaServerAPI +++ b/TerrariaServerAPI @@ -1 +1 @@ -Subproject commit 2b81ac74594d2ca56be9831d31c277201b003257 +Subproject commit fbfdde8779256468c2bb41257ef4ad6ec6a47864