diff --git a/.dockerignore b/.dockerignore index b20ac5b68..234117c02 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,15 +1,20 @@ -# ignore every file -* +# Ignore all Git metadata and build output (in all directories). +**/.git* +**/bin/ +**/obj/ -# except for the ones required for building -!i18n/ -!prebuilts/ -!TerrariaServerAPI/ -!TShockAPI/ -!TShockLauncher/ -!TShockLauncher.Tests/ -!TShock.sln - -# but exclude build artifacts -*/bin/ -*/obj/ +# Ignore other specific files that aren't needed for the build itself. +/.all-contributorsrc +/.dockerignore +/.editorconfig +/.vscode +/appveyor.yml +/COPYING +/crowdin.yml +/Dockerfile +/docs +/README.md +/README_cn.md +/renovate.json +/scripts +/SECURITY.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8275735a2..75b69d48c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ # These are supported funding model platforms -github: [SignatureBeef, hakusaro, Stealownz, QuiCM] +github: [SignatureBeef, QuiCM] diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 000000000..3d47d3c67 --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,60 @@ +name: CI (Docker image) + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + attestations: write + id-token: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up buildx + uses: docker/setup-buildx-action@v3 + - name: Login to ghcr.io + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Generate version information + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag,enable=${{ !startsWith(github.ref, 'refs/tags/v') }} + type=ref,event=pr + type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + flavor: | + latest=${{ startsWith(github.ref, 'refs/tags/v') }} + - name: Build image + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + pull: true + cache-from: type=gha, scope=${{ github.workflow }} + cache-to: type=gha, scope=${{ github.workflow }} + - name: Generate build provenance attestation + if: ${{ github.event_name != 'pull_request' }} + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/Dockerfile b/Dockerfile index 186bee70e..87024fb9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,8 @@ -ARG TARGETPLATFORM=linux/amd64 -ARG BUILDPLATFORM=${TARGETPLATFORM} +# TARGETPLATFORM and BUILDPLATFORM are automatically filled in by Docker buildx. +# They should not be set in the global scope manually. FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0 AS builder -ARG TARGETPLATFORM - # Copy build context WORKDIR /TShock COPY . ./ @@ -12,6 +10,10 @@ COPY . ./ # Build and package release based on target architecture RUN dotnet build -v m WORKDIR /TShock/TShockLauncher + +# Make TARGETPLATFORM available to the container. +ARG TARGETPLATFORM + RUN \ case "${TARGETPLATFORM}" in \ "linux/amd64") export ARCH="linux-x64" \ diff --git a/README.md b/README.md index b5559b37f..0369df531 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- TShock for Terraria
+ TShock for Terraria
AppVeyor Build Status diff --git a/README_cn.md b/README_cn.md index e038817e4..65c5eba61 100644 --- a/README_cn.md +++ b/README_cn.md @@ -1,5 +1,5 @@

- TShock for Terraria
+ TShock for Terraria
AppVeyor Build Status diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 6ff7fd1aa..0f5f6e3c8 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -424,7 +424,7 @@ internal Bouncer() }; PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit { - MaxTicks = 240, + MaxTicks = 60 * 4, CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; @@ -434,6 +434,12 @@ internal Bouncer() CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; + PlayerAddBuffWhitelist[BuffID.ParryDamageBuff] = new BuffLimit + { + MaxTicks = 60 * 5, + CanBeAddedWithoutHostile = true, + CanOnlyBeAppliedToSender = true + }; #endregion Whitelist } @@ -1878,7 +1884,7 @@ void Reject(bool shouldResync = true) return; } - if (TShock.Players[id] == null) + if (TShock.Players[id] == null || !TShock.Players[id].Active) { TShock.Log.ConsoleDebug(GetString( "Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: target is null", args.Player.Name, @@ -2081,7 +2087,7 @@ internal void OnHealOtherPlayer(object sender, GetDataHandlers.HealOtherPlayerEv short amount = args.Amount; byte plr = args.TargetPlayerIndex; - if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active) + if (amount <= 0 || TShock.Players[plr] == null || !TShock.Players[plr].Active) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnHealOtherPlayer rejected null checks")); args.Handled = true; @@ -2589,7 +2595,7 @@ internal void OnPlayerDamage(object sender, GetDataHandlers.PlayerDamageEventArg byte direction = args.Direction; PlayerDeathReason reason = args.PlayerDeathReason; - if (id >= Main.maxPlayers || TShock.Players[id] == null) + if (id >= Main.maxPlayers || TShock.Players[id] == null || !TShock.Players[id].Active) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerDamage rejected null check")); args.Handled = true; @@ -2854,7 +2860,7 @@ internal static int GetMaxPlaceStyle(int tileID) { 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, 420 }, // BuffID: 39 + { BuffID.CursedInferno, 600 }, // BuffID: 39 { BuffID.Frostburn, 900 }, // BuffID: 44 { BuffID.Ichor, 1200 }, // BuffID: 69 { BuffID.Venom, 1800 }, // BuffID: 70 diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 3d1767eab..e6bc1b592 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -1176,7 +1176,7 @@ private static void ManageUsers(CommandArgs args) try { - TShock.UserAccounts.SetUserGroup(account, args.Parameters[2]); + TShock.UserAccounts.SetUserGroup(args.Player, account, args.Parameters[2]); TShock.Log.ConsoleInfo(GetString("{0} changed account {1} to group {2}.", args.Player.Name, account.Name, args.Parameters[2])); args.Player.SendSuccessMessage(GetString("Account {0} has been changed to group {1}.", account.Name, args.Parameters[2])); @@ -1193,6 +1193,10 @@ private static void ManageUsers(CommandArgs args) { args.Player.SendErrorMessage(GetString($"User {account.Name} does not exist.")); } + catch (UserGroupUpdateLockedException) + { + args.Player.SendErrorMessage(GetString("Hook blocked the attempt to change the user group.")); + } catch (UserAccountManagerException e) { args.Player.SendErrorMessage(GetString($"User {account.Name} could not be added. Check console for details.")); @@ -2044,6 +2048,7 @@ private static void Off(CommandArgs args) private static void OffNoSave(CommandArgs args) { 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); } @@ -3070,12 +3075,12 @@ private static void TPHere(CommandArgs args) args.Player.SendErrorMessage(GetString("You do not have permission to teleport all other players.")); return; } - for (int i = 0; i < Main.maxPlayers; i++) + foreach (var player in TShock.Players) { - if (Main.player[i].active && (Main.player[i] != args.TPlayer)) + if (player != null && player.Active && player.Index != args.Player.Index) { - if (TShock.Players[i].Teleport(args.TPlayer.position.X, args.TPlayer.position.Y)) - TShock.Players[i].SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name)); + if (player.Teleport(args.TPlayer.position.X, args.TPlayer.position.Y)) + player.SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name)); } } args.Player.SendSuccessMessage(GetString("Teleported everyone to yourself.")); @@ -4622,21 +4627,22 @@ private static void Wind(CommandArgs args) { if (args.Parameters.Count != 1) { - args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind .", Specifier)); + args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind .", Specifier)); return; } - int speed; - if (!int.TryParse(args.Parameters[0], out speed) || speed * 100 < 0) + float mph; + if (!float.TryParse(args.Parameters[0], out mph) || mph is < -40f or > 40f) { - args.Player.SendErrorMessage(GetString("Invalid wind speed.")); + args.Player.SendErrorMessage(GetString("Invalid wind speed (must be between -40 and 40).")); return; } + float speed = mph / 50f; // -40 to 40 mph -> -0.8 to 0.8 Main.windSpeedCurrent = speed; Main.windSpeedTarget = speed; TSPlayer.All.SendData(PacketTypes.WorldInfo); - TSPlayer.All.SendInfoMessage(GetString("{0} changed the wind speed to {1}.", args.Player.Name, speed)); + TSPlayer.All.SendInfoMessage(GetString("{0} changed the wind speed to {1}mph.", args.Player.Name, mph)); } #endregion Time/PvpFun Commands @@ -6462,7 +6468,7 @@ private static void GBuff(CommandArgs args) if (target == user) user.SendSuccessMessage(GetString($"You buffed yourself with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds.")); else - target.SendSuccessMessage(GetString($"You have buffed {user.Name} with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!")); + user.SendSuccessMessage(GetString($"You have buffed {target.Name} with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!")); if (!args.Silent && target != user) target.SendSuccessMessage(GetString($"{user.Name} has buffed you with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!")); } diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs index 79c374b25..c91466fec 100644 --- a/TShockAPI/Configuration/TShockConfig.cs +++ b/TShockAPI/Configuration/TShockConfig.cs @@ -320,8 +320,8 @@ public class TShockSettings [Description("The reason given if banning a mediumcore player on death.")] public string MediumcoreBanReason = GetString("Death results in a ban"); - ///

Disbales IP bans by default, if no arguments are passed to the ban command. - [Description("Disbales IP bans by default, if no arguments are passed to the ban command.")] + /// Disables IP bans by default, if no arguments are passed to the ban command. + [Description("Disables IP bans by default, if no arguments are passed to the ban command.")] public bool DisableDefaultIPBan; /// Enable or disable the whitelist based on IP addresses in the whitelist.txt file. diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 0354989e7..6fc50d98d 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License using System.Text.RegularExpressions; using BCrypt.Net; using System.Security.Cryptography; +using TShockAPI.Hooks; namespace TShockAPI.DB { @@ -166,7 +167,41 @@ public void SetUserGroup(UserAccount account, string group) if (null == grp) throw new GroupNotExistsException(group); - if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, account.Name) == 0) + if (AccountHooks.OnAccountGroupUpdate(account, ref grp)) + throw new UserGroupUpdateLockedException(account.Name); + + if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", grp.Name, account.Name) == 0) + throw new UserAccountNotExistException(account.Name); + + try + { + // Update player group reference for any logged in player + foreach (var player in TShock.Players.Where(p => p != null && p.Account != null && p.Account.Name == account.Name)) + { + player.Group = grp; + } + } + catch (Exception ex) + { + throw new UserAccountManagerException(GetString("SetUserGroup SQL returned an error"), ex); + } + } + /// + /// Sets the group for a given username + /// + /// Who changes the group + /// The user account + /// The user account group to be set + public void SetUserGroup(TSPlayer author, UserAccount account, string group) + { + Group grp = TShock.Groups.GetGroupByName(group); + if (null == grp) + throw new GroupNotExistsException(group); + + if (AccountHooks.OnAccountGroupUpdate(account, author, ref grp)) + throw new UserGroupUpdateLockedException(account.Name); + + if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", grp.Name, account.Name) == 0) throw new UserAccountNotExistException(account.Name); try @@ -619,7 +654,7 @@ public UserAccountExistsException(string name) public class UserAccountNotExistException : UserAccountManagerException { /// Creates a new UserAccountNotExistException object, with the user account name in the message. - /// The user account name to be pasesd in the message. + /// The user account name to be passed in the message. /// A new UserAccountNotExistException object with a message containing the user account name that does not exist. public UserAccountNotExistException(string name) : base(GetString($"User account {name} does not exist")) @@ -627,6 +662,20 @@ public UserAccountNotExistException(string name) } } + /// The UserGroupUpdateLockedException used when the user group update failed and the request failed as a result.. + [Serializable] + public class UserGroupUpdateLockedException : UserAccountManagerException + { + /// Creates a new UserGroupUpdateLockedException object. + /// The name of the user who failed to change the group. + /// New UserGroupUpdateLockedException object with a message containing the name of the user account that failed to change the group. + public UserGroupUpdateLockedException(string name) : + base(GetString($"Unable to update group of user {name}.")) + { + } + } + + /// A GroupNotExistsException, used when a group does not exist. [Serializable] public class GroupNotExistsException : UserAccountManagerException diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 80d5a47c4..d85185b86 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -1,739 +1,1325 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; +using System.IO; using Terraria; -using Terraria.DataStructures; -using Terraria.GameContent.Tile_Entities; using Terraria.ID; -using Terraria.ObjectData; using TShockAPI.Net; namespace TShockAPI.Handlers { /// - /// Provides processors for handling Tile Rect packets + /// Provides processors for handling tile rect packets. + /// This required many hours of reverse engineering work, and is kindly provided to TShock for free by @punchready. /// - public class SendTileRectHandler : IPacketHandler + public sealed class SendTileRectHandler : IPacketHandler { /// - /// Maps plant tile types to their valid grass ground tiles when using flower boots + /// Represents a tile rectangle sent through the packet. /// - private static readonly Dictionary> FlowerBootPlantToGrassMap = new Dictionary> + private sealed class TileRect { - { TileID.Plants, new HashSet() - { - TileID.Grass, TileID.GolfGrass - } }, - { TileID.HallowedPlants, new HashSet() - { - TileID.HallowedGrass, TileID.GolfGrassHallowed - } }, - { TileID.HallowedPlants2, new HashSet() - { - TileID.HallowedGrass, TileID.GolfGrassHallowed - } }, - { TileID.JunglePlants2, new HashSet() + private readonly NetTile[,] _tiles; + public readonly int X; + public readonly int Y; + public readonly int Width; + public readonly int Height; + + /// + /// Accesses the tiles contained in this rect. + /// + /// The X coordinate within the rect. + /// The Y coordinate within the rect. + /// The tile at the given position within the rect. + public NetTile this[int x, int y] => _tiles[x, y]; + + /// + /// Constructs a new tile rect based on the given information. + /// + public TileRect(NetTile[,] tiles, int x, int y, int width, int height) { - TileID.JungleGrass - } }, - { TileID.AshPlants, new HashSet() + _tiles = tiles; + X = x; + Y = y; + Width = width; + Height = height; + } + + /// + /// Reads a tile rect from the given stream. + /// + /// The resulting tile rect. + public static TileRect Read(MemoryStream stream, int tileX, int tileY, int width, int height) { - TileID.AshGrass - } }, - }; + NetTile[,] tiles = new NetTile[width, height]; + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + tiles[x, y] = new NetTile(); + tiles[x, y].Unpack(stream); // explicit > implicit + } + } + return new TileRect(tiles, tileX, tileY, width, height); + } + } /// - /// Maps plant tile types to a list of valid styles, which are used to determine the FrameX value of the plant tile - /// See `Player.DoBootsEffect_PlaceFlowersOnTile` + /// Represents a common tile rect operation (Placement, State Change, Removal). /// - private static readonly Dictionary> FlowerBootPlantToStyleMap = new Dictionary>() + private readonly struct TileRectMatch { - { TileID.Plants, new HashSet() - { - // The upper line is from a `NextFromList` call - // The lower line is from an additional switch which will add the listed options by adding a random value to a select set of styles - 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42, - 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, - } }, - { TileID.HallowedPlants, new HashSet() - { - // 5 is intentionally missing here because it is being skipped by vanilla - 4, 6, - } }, - { TileID.HallowedPlants2, new HashSet() - { - // 5 is intentionally missing here because it is being skipped by vanilla - 2, 3, 4, 6, 7, - } }, - { TileID.JunglePlants2, new HashSet() - { - 9, 10, 11, 12, 13, 14, 15, 16, - } }, - { TileID.AshPlants, new HashSet() + private const short IGNORE_FRAME = -1; + + private enum MatchType { - 6, 7, 8, 9, 10, - } }, - }; + Placement, + StateChange, + Removal, + } - /// - /// Item IDs that can spawn flowers while you walk - /// - public static List FlowerBootItems = new List - { - ItemID.FlowerBoots, - ItemID.FairyBoots - }; + private readonly int Width; + private readonly int Height; - /// - /// Maps TileIDs to Tile Entity IDs. - /// Note: is empty at the time of writing, but entities are dynamically assigned their ID at initialize time - /// which is why we can use the _myEntityId field on each entity type - /// - public static Dictionary TileEntityIdToTileIdMap = new Dictionary - { - { TileID.TargetDummy, TETrainingDummy._myEntityID }, - { TileID.ItemFrame, TEItemFrame._myEntityID }, - { TileID.LogicSensor, TELogicSensor._myEntityID }, - { TileID.DisplayDoll, TEDisplayDoll._myEntityID }, - { TileID.WeaponsRack2, TEWeaponsRack._myEntityID }, - { TileID.HatRack, TEHatRack._myEntityID }, - { TileID.FoodPlatter, TEFoodPlatter._myEntityID }, - { TileID.TeleportationPylon, TETeleportationPylon._myEntityID } - }; + private readonly ushort TileType; + private readonly short MaxFrameX; + private readonly short MaxFrameY; + private readonly short FrameXStep; + private readonly short FrameYStep; - /// - /// Invoked when a SendTileRect packet is received - /// - /// - /// - public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) - { - // By default, we'll handle everything - args.Handled = true; + private readonly MatchType Type; - if (ShouldSkipProcessing(args)) + private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) { - return; + Type = type; + Width = width; + Height = height; + TileType = tileType; + MaxFrameX = maxFrameX; + MaxFrameY = maxFrameY; + FrameXStep = frameXStep; + FrameYStep = frameYStep; } - bool[,] processed = new bool[args.Width, args.Length]; - NetTile[,] tiles = ReadNetTilesFromStream(args.Data, args.Width, args.Length); - - Debug.VisualiseTileSetDiff(args.TileX, args.TileY, args.Width, args.Length, tiles); + /// + /// Creates a new placement operation. + /// + /// The width of the placement. + /// The height of the placement. + /// The tile type of the placement. + /// The maximum allowed frameX of the placement. + /// The maximum allowed frameY of the placement. + /// The step size in which frameX changes for this placement, or 1 if any value is allowed. + /// The step size in which frameX changes for this placement, or 1 if any value is allowed. + /// The resulting operation match. + public static TileRectMatch Placement(int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) + { + return new TileRectMatch(MatchType.Placement, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep); + } - IterateTileRect(tiles, processed, args); + /// + /// Creates a new state change operation. + /// + /// The width of the state change. + /// The height of the state change. + /// The target tile type of the state change. + /// The maximum allowed frameX of the state change. + /// The maximum allowed frameY of the state change. + /// The step size in which frameX changes for this placement, or 1 if any value is allowed. + /// The step size in which frameY changes for this placement, or 1 if any value is allowed. + /// The resulting operation match. + public static TileRectMatch StateChange(int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) + { + return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep); + } - // Uncommenting this function will send the same tile rect 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks. - // Use in test worlds only. - //Debug.DisplayTileSetInGame(args.TileX, (short)(args.TileY - 10), args.Width, args.Length, tiles, args.Player); + /// + /// Creates a new state change operation which only changes frameX. + /// + /// The width of the state change. + /// The height of the state change. + /// The target tile type of the state change. + /// The maximum allowed frameX of the state change. + /// The step size in which frameX changes for this placement, or 1 if any value is allowed. + /// The resulting operation match. + public static TileRectMatch StateChangeX(int width, int height, ushort tileType, short maxFrame, short frameStep) + { + return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrame, IGNORE_FRAME, frameStep, 0); + } - // If we are handling this event then we have updated the server's Main.tile state the way we want it. - // At this point we should send our state back to the client so they remain in sync with the server - if (args.Handled == true) + /// + /// Creates a new state change operation which only changes frameY. + /// + /// The width of the state change. + /// The height of the state change. + /// The target tile type of the state change. + /// The maximum allowed frameY of the state change. + /// The step size in which frameY changes for this placement, or 1 if any value is allowed. + /// The resulting operation match. + public static TileRectMatch StateChangeY(int width, int height, ushort tileType, short maxFrame, short frameStep) { - TSPlayer.All.SendTileRect(args.TileX, args.TileY, args.Width, args.Length); - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from carbonara from {args.Player.Name}")); + return new TileRectMatch(MatchType.StateChange, width, height, tileType, IGNORE_FRAME, maxFrame, 0, frameStep); } - } - /// - /// Iterates over each tile in the tile rectangle and performs processing on individual tiles or multi-tile Tile Objects - /// - /// - /// - /// - internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandlers.SendTileRectEventArgs args) - { - int tileX = args.TileX; - int tileY = args.TileY; - byte width = args.Width; - byte length = args.Length; + /// + /// Creates a new removal operation. + /// + /// The width of the removal. + /// The height of the removal. + /// The target tile type of the removal. + /// The resulting operation match. + public static TileRectMatch Removal(int width, int height, ushort tileType) + { + return new TileRectMatch(MatchType.Removal, width, height, tileType, 0, 0, 0, 0); + } - for (int x = 0; x < width; x++) + /// + /// Determines whether the given tile rectangle matches this operation, and if so, applies it to the world. + /// + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect matches this operation and the changes have been applied, otherwise . + public bool Matches(TSPlayer player, TileRect rect) { - for (int y = 0; y < length; y++) + if (rect.Width != Width || rect.Height != Height) + { + return false; + } + + for (int x = 0; x < rect.Width; x++) { - // Do not process already processed tiles - if (processed[x, y]) + for (int y = 0; y < rect.Height; y++) { - continue; + NetTile tile = rect[x, y]; + if (Type is MatchType.Placement or MatchType.StateChange) + { + if (tile.Type != TileType) + { + return false; + } + } + if (Type is MatchType.Placement or MatchType.StateChange) + { + if (MaxFrameX != IGNORE_FRAME) + { + if (tile.FrameX < 0 || tile.FrameX > MaxFrameX || tile.FrameX % FrameXStep != 0) + { + return false; + } + } + if (MaxFrameY != IGNORE_FRAME) + { + if (tile.FrameY < 0 || tile.FrameY > MaxFrameY || tile.FrameY % FrameYStep != 0) + { + // this is the only tile type sent in a tile rect where the frame have a different pattern (56, 74, 92 instead of 54, 72, 90) + if (!(TileType == TileID.LunarMonolith && tile.FrameY % FrameYStep == 2)) + { + return false; + } + } + } + } + if (Type == MatchType.Removal) + { + if (tile.Active) + { + return false; + } + } } + } - int realX = tileX + x; - int realY = tileY + y; - - // Do not process tiles outside of the world boundaries - if ((realX < 0 || realX >= Main.maxTilesX) - || (realY < 0 || realY > Main.maxTilesY)) + for (int x = rect.X; x < rect.X + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) { - processed[x, y] = true; - continue; + if (!player.HasBuildPermission(x, y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } } + } + + switch (Type) + { + case MatchType.Placement: + { + return MatchPlacement(player, rect); + } + case MatchType.StateChange: + { + return MatchStateChange(player, rect); + } + case MatchType.Removal: + { + return MatchRemoval(player, rect); + } + } + + return false; + } - // Do not process tiles that the player cannot update - if (!args.Player.HasBuildPermission(realX, realY) || - !args.Player.IsInRange(realX, realY)) + private bool MatchPlacement(TSPlayer player, TileRect rect) + { + for (int x = rect.X; x < rect.Y + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) { - processed[x, y] = true; - continue; + if (Main.tile[x, y].active()) // the client will kill tiles that auto break before placing the object + { + return false; + } } + } + + // let's hope tile types never go out of short range (they use ushort in terraria's code) + if (TShock.TileBans.TileIsBanned((short)TileType, player)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } - NetTile newTile = tiles[x, y]; + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + Main.tile[x + rect.X, y + rect.Y].active(active: true); + Main.tile[x + rect.X, y + rect.Y].type = rect[x, y].Type; + Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; + Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; + } + } - TileObjectData data; + return true; + } - // If the new tile has an associated TileObjectData object, we take the tile and the surrounding tiles that make up the tile object - // and process them as a tile object - if (newTile.Type < TileObjectData._data.Count && TileObjectData._data[newTile.Type] != null) + private bool MatchStateChange(TSPlayer player, TileRect rect) + { + for (int x = rect.X; x < rect.Y + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) { - // Verify that the changes are actually valid conceptually - // Many tiles that are never placed or modified using this packet are valid TileObjectData entries, which is the main attack vector for most exploits using this packet - if (Main.tile[realX, realY].type == newTile.Type) + if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType) { - switch (newTile.Type) - { - // Some individual cases might still allow crashing exploits, as the actual framing is not being checked here - // Doing so requires hard-coding the individual valid framing values and is a lot of effort - case TileID.ProjectilePressurePad: - case TileID.WirePipe: - case TileID.Traps: - case TileID.Candles: - case TileID.PeaceCandle: - case TileID.WaterCandle: - case TileID.PlatinumCandle: - case TileID.Firework: - case TileID.WaterFountain: - case TileID.BloodMoonMonolith: - case TileID.VoidMonolith: - case TileID.LunarMonolith: - case TileID.MusicBoxes: - case TileID.ArrowSign: - case TileID.PaintedArrowSign: - case TileID.Cannon: - case TileID.Campfire: - case TileID.Plants: - case TileID.MinecartTrack: - case TileID.ChristmasTree: - case TileID.ShimmerMonolith: - { - // Allowed changes - - // Based on empirical tests, these should be some conservative upper bounds for framing values - if (newTile.FrameX != -1 || newTile.FrameY != -1) - { - if (newTile.FrameX is < 0 or > 1000) - { - processed[x, y] = true; - continue; - } - if (newTile.FrameY is < 0 or > 5000) - { - processed[x, y] = true; - continue; - } - } - } - break; - default: - { - processed[x, y] = true; - continue; - } - } + return false; } - else + } + } + + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + if (MaxFrameX != IGNORE_FRAME) { - // Together with Flower Boots and Land Mine destruction, these are the only cases where a tile type is allowed to be modified - switch (newTile.Type) - { - case TileID.LogicSensor: - case TileID.FoodPlatter: - case TileID.WeaponsRack2: - case TileID.ItemFrame: - case TileID.HatRack: - case TileID.DisplayDoll: - case TileID.TeleportationPylon: - case TileID.TargetDummy: - { - // Allowed placements - - // Based on empirical tests, these should be some conservative upper bounds for framing values - if (newTile.FrameX != -1 || newTile.FrameY != -1) - { - if (newTile.FrameX is < 0 or > 1000) - { - processed[x, y] = true; - continue; - } - if (newTile.FrameY is < 0 or > 500) - { - processed[x, y] = true; - continue; - } - } - } - break; - default: - { - processed[x, y] = true; - continue; - } - } + Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; } - - data = TileObjectData._data[newTile.Type]; - NetTile[,] newTiles; - int objWidth = data.Width; - int objHeight = data.Height; - - // Ensure the tile object fits inside the rect before processing it - if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, processed)) + if (MaxFrameY != IGNORE_FRAME) { - continue; + Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; } + } + } - newTiles = new NetTile[objWidth, objHeight]; + return true; + } - for (int i = 0; i < objWidth; i++) + private bool MatchRemoval(TSPlayer player, TileRect rect) + { + for (int x = rect.X; x < rect.Y + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) + { + if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType) { - for (int j = 0; j < objHeight; j++) - { - newTiles[i, j] = tiles[x + i, y + j]; - processed[x + i, y + j] = true; - } + return false; } - ProcessTileObject(newTile.Type, realX, realY, objWidth, objHeight, newTiles, args); - continue; } + } - // If the new tile does not have an associated tile object, process it as an individual tile - ProcessSingleTile(realX, realY, newTile, width, length, args); - processed[x, y] = true; + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + Main.tile[x + rect.X, y + rect.Y].active(active: false); + Main.tile[x + rect.X, y + rect.Y].frameX = -1; + Main.tile[x + rect.X, y + rect.Y].frameY = -1; + } } + + return true; } } /// - /// Processes a tile object consisting of multiple tiles from the tile rect packet + /// Contains the complete list of valid tile rect operations the game currently performs. + /// + // The matches restrict the tile rects to only place one kind of tile, and only with the given maximum values and step sizes for frameX and frameY. This performs pretty much perfect checks on the data, allowing only valid placements. + // For TileID.MinecartTrack, the data is taken from `Minecart._trackSwitchOptions`, allowing any framing value in this array (currently 0-36). + // For TileID.Plants, it is taken from `ItemID.Sets.flowerPacketInfo[n].stylesOnPurity`, allowing every style multiplied by 18. + // The other operations are based on code analysis and manual observation. + 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(2, 3, TileID.DisplayDoll, 126, 36, 18, 18), + TileRectMatch.Placement(2, 3, TileID.HatRack, 90, 54, 18, 18), + TileRectMatch.Placement(2, 2, TileID.ItemFrame, 162, 18, 18, 18), + TileRectMatch.Placement(3, 3, TileID.WeaponsRack2, 90, 36, 18, 18), + TileRectMatch.Placement(1, 1, TileID.FoodPlatter, 18, 0, 18, 18), + TileRectMatch.Placement(1, 1, TileID.LogicSensor, 108, 0, 18, 18), + + TileRectMatch.StateChangeY(3, 2, TileID.Campfire, 54, 18), + TileRectMatch.StateChangeY(4, 3, TileID.Cannon, 468, 18), + TileRectMatch.StateChangeY(2, 2, TileID.ArrowSign, 270, 18), + TileRectMatch.StateChangeY(2, 2, TileID.PaintedArrowSign, 270, 18), + + TileRectMatch.StateChangeX(2, 2, TileID.MusicBoxes, 54, 18), + + TileRectMatch.StateChangeY(2, 3, TileID.LunarMonolith, 92, 18), + TileRectMatch.StateChangeY(2, 3, TileID.BloodMoonMonolith, 90, 18), + TileRectMatch.StateChangeY(2, 3, TileID.VoidMonolith, 90, 18), + TileRectMatch.StateChangeY(2, 3, TileID.EchoMonolith, 90, 18), + TileRectMatch.StateChangeY(2, 3, TileID.ShimmerMonolith, 144, 18), + TileRectMatch.StateChangeY(2, 4, TileID.WaterFountain, 126, 18), + + TileRectMatch.StateChangeX(1, 1, TileID.Candles, 18, 18), + TileRectMatch.StateChangeX(1, 1, TileID.PeaceCandle, 18, 18), + TileRectMatch.StateChangeX(1, 1, TileID.WaterCandle, 18, 18), + TileRectMatch.StateChangeX(1, 1, TileID.PlatinumCandle, 18, 18), + TileRectMatch.StateChangeX(1, 1, TileID.ShadowCandle, 18, 18), + + TileRectMatch.StateChange(1, 1, TileID.Traps, 90, 90, 18, 18), + + TileRectMatch.StateChangeX(1, 1, TileID.WirePipe, 36, 18), + TileRectMatch.StateChangeX(1, 1, TileID.ProjectilePressurePad, 66, 22), + TileRectMatch.StateChangeX(1, 1, TileID.Plants, 792, 18), + TileRectMatch.StateChangeX(1, 1, TileID.MinecartTrack, 36, 1), + + TileRectMatch.Removal(1, 2, TileID.Firework), + TileRectMatch.Removal(1, 1, TileID.LandMine), + }; + + + /// + /// Handles a packet receive event. /// - /// The tile type the object is comprised of - /// 2D array of NetTile containing the new tiles properties - /// X position at the top left of the object - /// Y position at the top left of the object - /// Width of the tile object - /// Height of the tile object - /// SendTileRectEventArgs containing event information - internal void ProcessTileObject(int tileType, int realX, int realY, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileRectEventArgs args) + public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) { - // As long as the player has permission to build, we should allow a tile object to be placed - // More in depth checks should take place in handlers for the Place Object (79), Update Tile Entity (86), and Place Tile Entity (87) packets - if (!args.Player.HasBuildPermissionForTileObject(realX, realY, width, height)) + // this permission bypasses all checks for direct access to the world + if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from no permission for tile object from {args.Player.Name}")); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}")); + + // use vanilla handling + args.Handled = false; return; } - if (TShock.TileBans.TileIsBanned((short)tileType)) + // this handler handles the entire logic of this packet + args.Handled = true; + + // as of 1.4 this is the biggest size the client will send in any case, determined by full code analysis + // see default matches above and special cases below + if (args.Width > 4 || args.Length > 4) { - TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRect rejected for banned tile")); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from size from {args.Player.Name}")); + + // definitely invalid; do not send any correcting data return; } - // Update all tiles in the tile object. These will be sent back to the player later - UpdateMultipleServerTileStates(realX, realY, width, height, newTiles); + // player throttled? + if (args.Player.IsBouncerThrottled()) + { + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from throttle from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; + } - // Tile entities have special placements that we should let the game deal with - if (TileEntityIdToTileIdMap.ContainsKey(tileType)) + // player disabled? + if (args.Player.IsBeingDisabled()) { - TileEntity.PlaceEntityNet(realX, realY, TileEntityIdToTileIdMap[tileType]); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from being disabled from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } - } - /// - /// Processes a single tile from the tile rect packet - /// - /// X position at the top left of the object - /// Y position at the top left of the object - /// The NetTile containing new tile properties - /// The width of the rectangle being processed - /// The length of the rectangle being processed - /// SendTileRectEventArgs containing event information - internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rectWidth, byte rectLength, GetDataHandlers.SendTileRectEventArgs args) - { - // Some boots allow growing flowers on grass. This process sends a 1x1 tile rect to grow the flowers - // The rect size must be 1 and the player must have an accessory that allows growing flowers in order for this rect to be valid - if (rectWidth == 1 && rectLength == 1 && WorldGen.InWorld(realX, realY + 1) && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) + // read the tile rectangle + TileRect rect = TileRect.Read(args.Data, args.TileX, args.TileY, args.Width, args.Length); + + // check if the positioning is valid + if (!IsRectPositionValid(args.Player, rect)) { - ProcessFlowerBoots(realX, realY, newTile); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from out of bounds / build permission from {args.Player.Name}")); + + // send nothing due to out of bounds return; } - ITile tile = Main.tile[realX, realY]; + // a very special case, due to the clentaminator having a larger range than TSPlayer.IsInRange() allows + if (MatchesConversionSpread(args.Player, rect)) + { + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; + } - // Triggering a single land mine tile - if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active) + // check if the distance is valid + if (!IsRectDistanceValid(args.Player, rect)) { - UpdateServerTileState(tile, newTile, TileDataType.Tile); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from out of range from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } - // Hammering a single junction box - if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe) + // a very special case, due to the flower seed check otherwise hijacking this + if (MatchesFlowerBoots(args.Player, rect)) { - UpdateServerTileState(tile, newTile, TileDataType.Tile); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } - // Mowing a single grass tile: Grass -> GolfGrass OR HallowedGrass -> GolfGrassHallowed - if (rectWidth == 1 && rectLength == 1 && - ( - tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass || - tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed - )) + // check if the rect matches any valid operation + foreach (TileRectMatch match in Matches) { - UpdateServerTileState(tile, newTile, TileDataType.Tile); - if (WorldGen.InWorld(realX, realY + 1) && TileID.Sets.IsVine[Main.tile[realX, realY + 1].type]) // vanilla does in theory break the vines on its own, but we can't trust that + if (match.Matches(args.Player, rect)) { - WorldGen.KillTile(realX, realY + 1); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } } - // Conversion: only sends a 1x1 rect; has to happen AFTER grass mowing as it would otherwise also let mowing through, but without fixing vines - if (rectWidth == 1 && rectLength == 1) + // a few special cases + if (MatchesGrassMow(args.Player, rect) || MatchesChristmasTree(args.Player, rect)) { - ProcessConversionSpreads(tile, newTile); + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } - // All other single tile updates should not be processed. + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from matches from {args.Player.Name}")); + + // send correcting data + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); + return; } /// - /// Applies changes to a tile if a tile rect for flower-growing boots is valid + /// Checks whether the tile rect is at a valid position for the given player. /// - /// The tile x position of the tile rect packet - this is where the flowers are intending to grow - /// The tile y position of the tile rect packet - this is where the flowers are intending to grow - /// The NetTile containing information about the flowers that are being grown - internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile) + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect at a valid position, otherwise . + private static bool IsRectPositionValid(TSPlayer player, TileRect rect) { - ITile tile = Main.tile[realX, realY]; - // Ensure that: - // - the placed plant is valid for the grass below - // - the target tile is empty - // - and the placed plant has valid framing (style * 18 = FrameX) - if ( - FlowerBootPlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) && - !tile.active() && - grassTiles.Contains(Main.tile[realX, realY + 1].type) && - FlowerBootPlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18)) - ) + for (int x = 0; x < rect.Width; x++) { - UpdateServerTileState(tile, newTile, TileDataType.Tile); + for (int y = 0; y < rect.Height; y++) + { + int realX = rect.X + x; + int realY = rect.Y + y; + + if (realX < 0 || realX >= Main.maxTilesX || realY < 0 || realY >= Main.maxTilesY) + { + return false; + } + } } - } - // Moss and MossBrick are not used in conversion - private static List _convertibleTiles = typeof(TileID.Sets.Conversion) - .GetFields() - .ExceptBy(new[] { nameof(TileID.Sets.Conversion.Moss), nameof(TileID.Sets.Conversion.MossBrick) }, f => f.Name) - .Select(f => (bool[])f.GetValue(null)) - .ToList(); - // PureSand is only used in WorldGen.SpreadDesertWalls, which is server side - private static List _convertibleWalls = typeof(WallID.Sets.Conversion) - .GetFields() - .ExceptBy(new[] { nameof(WallID.Sets.Conversion.PureSand) }, f => f.Name) - .Select(f => (bool[])f.GetValue(null)) - .ToList(); + return true; + } /// - /// Updates a single tile on the server if it is a valid conversion from one tile or wall type to another (eg stone -> corrupt stone) + /// Checks whether the tile rect is at a valid distance to the given player. /// - /// The tile to update - /// The NetTile containing new tile properties - internal void ProcessConversionSpreads(ITile tile, NetTile newTile) + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect at a valid distance, otherwise . + private static bool IsRectDistanceValid(TSPlayer player, TileRect rect) { - var allowTile = false; - if (Main.tileMoss[tile.type] && TileID.Sets.Conversion.Stone[newTile.Type]) - { - allowTile = true; - } - else if ((Main.tileMoss[tile.type] || TileID.Sets.Conversion.Stone[tile.type] || TileID.Sets.Conversion.Ice[tile.type] || TileID.Sets.Conversion.Sandstone[tile.type]) && - (newTile.Type == TileID.Sandstone || newTile.Type == TileID.IceBlock)) - { - // ProjectileID.SandSpray and ProjectileID.SnowSpray - allowTile = true; - } - else + for (int x = 0; x < rect.Width; x++) { - foreach (var tileType in _convertibleTiles) + for (int y = 0; y < rect.Height; y++) { - if (tileType[tile.type] && tileType[newTile.Type]) + int realX = rect.X + x; + int realY = rect.Y + y; + + if (!player.IsInRange(realX, realY)) { - allowTile = true; - break; + return false; } } } - if (allowTile) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a tile conversion update - [{tile.type}] -> [{newTile.Type}]")); - UpdateServerTileState(tile, newTile, TileDataType.Tile); - } + return true; + } - foreach (var wallType in _convertibleWalls) - { - if (wallType[tile.wall] && wallType[newTile.Wall]) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a wall conversion update - [{tile.wall}] -> [{newTile.Wall}]")); - UpdateServerTileState(tile, newTile, TileDataType.Wall); - break; - } - } - } /// - /// Updates a single tile's world state with a set of changes from the networked tile state + /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.). /// - /// The tile to update - /// The NetTile containing the change - /// The type of data to merge into world state - public static void UpdateServerTileState(ITile tile, NetTile newTile, TileDataType updateType) + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect matches a conversion spread operation, otherwise . + private static bool MatchesConversionSpread(TSPlayer player, TileRect rect) { - //This logic (updateType & TDT.Tile) != 0 is the way Terraria does it (see: Tile.cs/Clear(TileDataType)) - //& is not a typo - we're performing a binary AND test to see if a given flag is set. - - if ((updateType & TileDataType.Tile) != 0) + if (rect.Width != 1 || rect.Height != 1) { - tile.active(newTile.Active); - tile.type = newTile.Type; + return false; + } - if (newTile.FrameImportant) + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; + + WorldGenMock.SimulateConversionChange(rect.X, rect.Y, out HashSet validTiles, out HashSet validWalls); + + if (newTile.Type != oldTile.type && validTiles.Contains(newTile.Type)) + { + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) { - tile.frameX = newTile.FrameX; - tile.frameY = newTile.FrameY; + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; } - else if (tile.type != newTile.Type || !tile.active()) + else if (!player.HasBuildPermission(rect.X, rect.Y)) { - //This is vanilla logic - if the tile changed types (or wasn't active) the frame values might not be valid - so we reset them to -1. - tile.frameX = -1; - tile.frameY = -1; + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; } - } + else + { + Main.tile[rect.X, rect.Y].type = newTile.Type; + Main.tile[rect.X, rect.Y].frameX = newTile.FrameX; + Main.tile[rect.X, rect.Y].frameY = newTile.FrameY; - if ((updateType & TileDataType.Wall) != 0) - { - tile.wall = newTile.Wall; + return true; + } } - if ((updateType & TileDataType.TilePaint) != 0) + if (newTile.Wall != oldTile.wall && validWalls.Contains(newTile.Wall)) { - tile.color(newTile.TileColor); - tile.fullbrightBlock(newTile.FullbrightBlock); - tile.invisibleBlock(newTile.InvisibleBlock); + // wallbans when? + + if (!player.HasBuildPermission(rect.X, rect.Y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + else + { + Main.tile[rect.X, rect.Y].wall = newTile.Wall; + + return true; + } } - if ((updateType & TileDataType.WallPaint) != 0) + return false; + } + + + private static readonly Dictionary> PlantToGrassMap = new Dictionary> + { + { TileID.Plants, new HashSet() { - tile.wallColor(newTile.WallColor); - tile.fullbrightWall(newTile.FullbrightWall); - tile.invisibleWall(newTile.InvisibleWall); - } + TileID.Grass, TileID.GolfGrass + } }, + { TileID.HallowedPlants, new HashSet() + { + TileID.HallowedGrass, TileID.GolfGrassHallowed + } }, + { TileID.HallowedPlants2, new HashSet() + { + TileID.HallowedGrass, TileID.GolfGrassHallowed + } }, + { TileID.JunglePlants2, new HashSet() + { + TileID.JungleGrass + } }, + { TileID.AshPlants, new HashSet() + { + TileID.AshGrass + } }, + }; - if ((updateType & TileDataType.Liquid) != 0) + private static readonly Dictionary> GrassToStyleMap = new Dictionary>() + { + { TileID.Plants, new HashSet() { - tile.liquid = newTile.Liquid; - tile.liquidType(newTile.LiquidType); - } + 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + } }, + { TileID.HallowedPlants, new HashSet() + { + 4, 6, + } }, + { TileID.HallowedPlants2, new HashSet() + { + 2, 3, 4, 6, 7, + } }, + { TileID.JunglePlants2, new HashSet() + { + 9, 10, 11, 12, 13, 14, 15, 16, + } }, + { TileID.AshPlants, new HashSet() + { + 6, 7, 8, 9, 10, + } }, + }; - if ((updateType & TileDataType.Slope) != 0) + /// + /// Checks whether the tile rect is a valid Flower Boots placement. + /// + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect matches a Flower Boots placement, otherwise . + private static bool MatchesFlowerBoots(TSPlayer player, TileRect rect) + { + if (rect.Width != 1 || rect.Height != 1) { - tile.halfBrick(newTile.IsHalf); - tile.slope(newTile.Slope); + return false; } - if ((updateType & TileDataType.Wiring) != 0) + if (!player.TPlayer.flowerBoots) { - tile.wire(newTile.Wire); - tile.wire2(newTile.Wire2); - tile.wire3(newTile.Wire3); - tile.wire4(newTile.Wire4); + return false; } - if ((updateType & TileDataType.Actuator) != 0) + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; + + if ( + PlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) && + !oldTile.active() && grassTiles.Contains(Main.tile[rect.X, rect.Y + 1].type) && + GrassToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18)) + ) { - tile.actuator(newTile.IsActuator); - tile.inActive(newTile.Inactive); + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + + if (!player.HasBuildPermission(rect.X, rect.Y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + + Main.tile[rect.X, rect.Y].active(active: true); + Main.tile[rect.X, rect.Y].type = newTile.Type; + Main.tile[rect.X, rect.Y].frameX = newTile.FrameX; + Main.tile[rect.X, rect.Y].frameY = 0; + + return true; } + + return false; } + + private static readonly Dictionary GrassToMowedMap = new Dictionary + { + { TileID.Grass, TileID.GolfGrass }, + { TileID.HallowedGrass, TileID.GolfGrassHallowed }, + }; + /// - /// Performs on multiple tiles + /// Checks whether the tile rect is a valid grass mow. /// - /// - /// - /// - /// - /// - public static void UpdateMultipleServerTileStates(int x, int y, int width, int height, NetTile[,] newTiles) + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect matches a grass mowing operation, otherwise . + private static bool MatchesGrassMow(TSPlayer player, TileRect rect) { - for (int i = 0; i < width; i++) + if (rect.Width != 1 || rect.Height != 1) { - for (int j = 0; j < height; j++) + return false; + } + + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; + + if (GrassToMowedMap.TryGetValue(oldTile.type, out ushort mowed) && newTile.Type == mowed) + { + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + + if (!player.HasBuildPermission(rect.X, rect.Y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + + Main.tile[rect.X, rect.Y].type = newTile.Type; + if (!newTile.FrameImportant) { - UpdateServerTileState(Main.tile[x + i, y + j], newTiles[i, j], TileDataType.Tile); + Main.tile[rect.X, rect.Y].frameX = -1; + Main.tile[rect.X, rect.Y].frameY = -1; } + + // prevent a common crash when the game checks all vines in an unlimited horizontal length + if (TileID.Sets.IsVine[Main.tile[rect.X, rect.Y + 1].type]) + { + WorldGen.KillTile(rect.X, rect.Y + 1); + } + + return true; } + + return false; } + /// - /// Reads a set of NetTiles from a memory stream + /// Checks whether the tile rect is a valid christmas tree modification. + /// This also required significant reverse engineering effort. /// - /// - /// - /// - /// - static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, byte width, byte length) + /// The player the operation originates from. + /// The tile rectangle of the operation. + /// , if the rect matches a christmas tree operation, otherwise . + private static bool MatchesChristmasTree(TSPlayer player, TileRect rect) { - NetTile[,] tiles = new NetTile[width, length]; - for (int x = 0; x < width; x++) + if (rect.Width != 1 || rect.Height != 1) { - for (int y = 0; y < length; y++) + return false; + } + + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; + + if (oldTile.type == TileID.ChristmasTree && newTile.Type == TileID.ChristmasTree) + { + if (newTile.FrameX != 10) + { + return false; + } + + int obj_0 = (newTile.FrameY & 0b0000000000000111); + int obj_1 = (newTile.FrameY & 0b0000000000111000) >> 3; + int obj_2 = (newTile.FrameY & 0b0000001111000000) >> 6; + int obj_3 = (newTile.FrameY & 0b0011110000000000) >> 10; + int obj_x = (newTile.FrameY & 0b1100000000000000) >> 14; + + if (obj_x != 0) + { + return false; + } + + if (obj_0 is < 0 or > 4 || obj_1 is < 0 or > 6 || obj_2 is < 0 or > 11 || obj_3 is < 0 or > 11) + { + return false; + } + + if (!player.HasBuildPermission(rect.X, rect.Y)) { - tiles[x, y] = new NetTile(stream); + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; } + + Main.tile[rect.X, rect.Y].frameY = newTile.FrameY; + + return true; } - return tiles; + return false; } + } + /// + /// This helper class allows simulating a `WorldGen.Convert` call and retrieving all valid changes for a given tile. + /// + internal static class WorldGenMock + { /// - /// Determines whether or not the tile rect should be immediately accepted or rejected + /// This is a mock tile which collects all possible changes the `WorldGen.Convert` code could make in its property setters. /// - /// - /// - static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args) + private sealed class MockTile { - if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}")); - args.Handled = false; - return true; - } + private readonly HashSet _setTypes; + private readonly HashSet _setWalls; - if (args.Width > 4 || args.Length > 4) // as of 1.4.3.6 this is the biggest size the client will send in any case + private ushort _type; + private ushort _wall; + + public MockTile(ushort type, ushort wall, HashSet setTypes, HashSet setWalls) { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from non-vanilla tilemod from {args.Player.Name}")); - return true; + _setTypes = setTypes; + _setWalls = setWalls; + _type = type; + _wall = wall; } - if (args.Player.IsBouncerThrottled()) +#pragma warning disable IDE1006 + + public ushort type { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from throttle from {args.Player.Name}")); - args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); - return true; + get => _type; + set + { + _setTypes.Add(value); + _type = value; + } } - if (args.Player.IsBeingDisabled()) + public ushort wall { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from being disabled from {args.Player.Name}")); - args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); - return true; + get => _wall; + set + { + _setWalls.Add(value); + _wall = value; + } } - return false; +#pragma warning restore IDE1006 } /// - /// Checks if a tile object fits inside the dimensions of a tile rectangle + /// Simulates what would happen if `WorldGen.Convert` was called on the given coordinates and returns two sets with the possible tile type and wall types that the conversion could change the tile to. /// - /// - /// - /// - /// - /// - /// - /// - /// - static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, bool[,] processed) + public static void SimulateConversionChange(int x, int y, out HashSet validTiles, out HashSet validWalls) { - if (x + width > rectWidth || y + height > rectLength) - { - // This is ugly, but we want to mark all these tiles as processed so that we're not hitting this check multiple times for one dodgy tile object - for (int i = x; i < rectWidth; i++) - { - for (int j = y; j < rectLength; j++) - { - processed[i, j] = true; - } - } + validTiles = new HashSet(); + validWalls = new HashSet(); - TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (excessive size)")); - return false; + // all the conversion types used in the code, most apparent in Projectile ai 31 + foreach (int conversionType in new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }) + { + MockTile mock = new(Main.tile[x, y].type, Main.tile[x, y].wall, validTiles, validWalls); + Convert(mock, x, y, conversionType); } - - return true; } - class Debug + /* + * This is a copy of the `WorldGen.Convert` method with the following precise changes: + * - Added a `MockTile tile` parameter + * - Changed the `i` and `j` parameters to `k` and `l` + * - Removed the size parameter + * - Removed the area loop and `Tile tile = Main.tile[k, l]` access in favor of using the tile parameter + * - Removed all calls to `WorldGen.SquareWallFrame`, `NetMessage.SendTileSquare`, `WorldGen.TryKillingTreesAboveIfTheyWouldBecomeInvalid` + * - Changed all `continue` statements to `break` statements + * - Removed the ifs checking the bounds of the tile and wall types + * - Removed branches that would call `WorldGen.KillTile` + * - Changed branches depending on randomness to instead set the property to both values after one another + * + * This overall leads to a method that can be called on a MockTile and real-world coordinates and will spit out the proper conversion changes into the MockTile. + */ + + private static void Convert(MockTile tile, int k, int l, int conversionType) { - /// - /// Displays the difference in IDs between existing tiles and a set of NetTiles to the console - /// - /// X position at the top left of the rect - /// Y position at the top left of the rect - /// Width of the NetTile set - /// Height of the NetTile set - /// New tiles to be visualised - public static void VisualiseTileSetDiff(int tileX, int tileY, int width, int height, NetTile[,] newTiles) + int type = tile.type; + int wall = tile.wall; + switch (conversionType) { - if (TShock.Config.Settings.DebugLogs) - { - char pad = '0'; - for (int y = 0; y < height; y++) + case 4: + if (WallID.Sets.Conversion.Grass[wall] && wall != 81) + { + tile.wall = 81; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 83) { - int realY = y + tileY; - for (int x = 0; x < width; x++) + tile.wall = 83; + } + else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 218) + { + tile.wall = 218; + } + else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 221) + { + tile.wall = 221; + } + else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 192) + { + tile.wall = 192; + } + else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 193) + { + tile.wall = 193; + } + else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 194) + { + tile.wall = 194; + } + else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 195) + { + tile.wall = 195; + } + if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 203) + { + tile.type = 203; + } + else if (TileID.Sets.Conversion.JungleGrass[type] && type != 662) + { + tile.type = 662; + } + else if (TileID.Sets.Conversion.Grass[type] && type != 199) + { + tile.type = 199; + } + else if (TileID.Sets.Conversion.Ice[type] && type != 200) + { + tile.type = 200; + } + else if (TileID.Sets.Conversion.Sand[type] && type != 234) + { + tile.type = 234; + } + else if (TileID.Sets.Conversion.HardenedSand[type] && type != 399) + { + tile.type = 399; + } + else if (TileID.Sets.Conversion.Sandstone[type] && type != 401) + { + tile.type = 401; + } + else if (TileID.Sets.Conversion.Thorn[type] && type != 352) + { + tile.type = 352; + } + break; + case 2: + if (WallID.Sets.Conversion.Grass[wall] && wall != 70) + { + tile.wall = 70; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 28) + { + tile.wall = 28; + } + else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 219) + { + tile.wall = 219; + } + else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 222) + { + tile.wall = 222; + } + else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 200) + { + tile.wall = 200; + } + else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 201) + { + tile.wall = 201; + } + else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 202) + { + tile.wall = 202; + } + else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 203) + { + tile.wall = 203; + } + if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 117) + { + tile.type = 117; + } + else if (TileID.Sets.Conversion.GolfGrass[type] && type != 492) + { + tile.type = 492; + } + else if (TileID.Sets.Conversion.Grass[type] && type != 109 && type != 492) + { + tile.type = 109; + } + else if (TileID.Sets.Conversion.Ice[type] && type != 164) + { + tile.type = 164; + } + else if (TileID.Sets.Conversion.Sand[type] && type != 116) + { + tile.type = 116; + } + else if (TileID.Sets.Conversion.HardenedSand[type] && type != 402) + { + tile.type = 402; + } + else if (TileID.Sets.Conversion.Sandstone[type] && type != 403) + { + tile.type = 403; + } + if (type == 59 && (Main.tile[k - 1, l].type == 109 || Main.tile[k + 1, l].type == 109 || Main.tile[k, l - 1].type == 109 || Main.tile[k, l + 1].type == 109)) + { + tile.type = 0; + } + break; + case 1: + if (WallID.Sets.Conversion.Grass[wall] && wall != 69) + { + tile.wall = 69; + } + else if (TileID.Sets.Conversion.JungleGrass[type] && type != 661) + { + tile.type = 661; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 3) + { + tile.wall = 3; + } + else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 217) + { + tile.wall = 217; + } + else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 220) + { + tile.wall = 220; + } + else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 188) + { + tile.wall = 188; + } + else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 189) + { + tile.wall = 189; + } + else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 190) + { + tile.wall = 190; + } + else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 191) + { + tile.wall = 191; + } + if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 25) + { + tile.type = 25; + } + else if (TileID.Sets.Conversion.Grass[type] && type != 23) + { + tile.type = 23; + } + else if (TileID.Sets.Conversion.Ice[type] && type != 163) + { + tile.type = 163; + } + else if (TileID.Sets.Conversion.Sand[type] && type != 112) + { + tile.type = 112; + } + else if (TileID.Sets.Conversion.HardenedSand[type] && type != 398) + { + tile.type = 398; + } + else if (TileID.Sets.Conversion.Sandstone[type] && type != 400) + { + tile.type = 400; + } + else if (TileID.Sets.Conversion.Thorn[type] && type != 32) + { + tile.type = 32; + } + break; + case 3: + if (WallID.Sets.CanBeConvertedToGlowingMushroom[wall]) + { + tile.wall = 80; + } + if (tile.type == 60) + { + tile.type = 70; + } + break; + case 5: + if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.NewWall1[wall] || WallID.Sets.Conversion.NewWall2[wall] || WallID.Sets.Conversion.NewWall3[wall] || WallID.Sets.Conversion.NewWall4[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 187) + { + tile.wall = 187; + } + else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Dirt[wall] || WallID.Sets.Conversion.Snow[wall]) && wall != 216) + { + tile.wall = 216; + } + if ((TileID.Sets.Conversion.Grass[type] || TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 53) + { + int num = 53; + if (WorldGen.BlockBelowMakesSandConvertIntoHardenedSand(k, l)) { - int realX = x + tileX; - ushort type = Main.tile[realX, realY].type; - string type2 = type.ToString(); - Console.Write((type2.ToString()).PadLeft(3, pad) + (Main.tile[realX, realY].active() ? "a" : "-") + " "); + num = 397; } - Console.Write(" -> "); - for (int x = 0; x < width; x++) + tile.type = (ushort)num; + } + else if (TileID.Sets.Conversion.HardenedSand[type] && type != 397) + { + tile.type = 397; + } + else if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 396) + { + tile.type = 396; + } + break; + case 6: + if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.NewWall1[wall] || WallID.Sets.Conversion.NewWall2[wall] || WallID.Sets.Conversion.NewWall3[wall] || WallID.Sets.Conversion.NewWall4[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 71) + { + tile.wall = 71; + } + else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Dirt[wall] || WallID.Sets.Conversion.Snow[wall]) && wall != 40) + { + tile.wall = 40; + } + if ((TileID.Sets.Conversion.Grass[type] || TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.HardenedSand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 147) + { + tile.type = 147; + } + else if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 161) + { + tile.type = 161; + } + break; + case 7: + if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 1) + { + tile.wall = 1; + } + else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Snow[wall] || WallID.Sets.Conversion.Dirt[wall]) && wall != 2) + { + tile.wall = 2; + } + else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 196) + { + tile.wall = 196; + } + else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 197) + { + tile.wall = 197; + } + else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 198) + { + tile.wall = 198; + } + else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 199) + { + tile.wall = 199; + } + if ((TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 1) + { + tile.type = 1; + } + else if (TileID.Sets.Conversion.GolfGrass[type] && type != 477) + { + tile.type = 477; + } + else if (TileID.Sets.Conversion.Grass[type] && type != 2 && type != 477) + { + tile.type = 2; + } + else if ((TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.HardenedSand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 0) + { + int num2 = 0; + if (WorldGen.TileIsExposedToAir(k, l)) { - int realX = x + tileX; - ushort type = newTiles[x, y].Type; - string type2 = type.ToString(); - Console.Write((type2.ToString()).PadLeft(3, pad) + (newTiles[x, y].Active ? "a" : "-") + " "); + num2 = 2; } - Console.Write("\n"); + tile.type = (ushort)num2; } - } + break; } - - /// - /// Sends a tile rect at the given (tileX, tileY) coordinate, using the given set of NetTiles information to update the tile rect - /// - /// X position at the top left of the rect - /// Y position at the top left of the rect - /// Width of the NetTile set - /// Height of the NetTile set - /// New tiles to place in the rect - /// Player to send the debug display to - public static void DisplayTileSetInGame(short tileX, short tileY, byte width, byte height, NetTile[,] newTiles, TSPlayer player) + if (tile.wall == 69 || tile.wall == 70 || tile.wall == 81) { - for (int x = 0; x < width; x++) + if (l < Main.worldSurface) { - for (int y = 0; y < height; y++) - { - UpdateServerTileState(Main.tile[tileX + x, tileY + y], newTiles[x, y], TileDataType.All); - } - //Add a line of dirt blocks at the bottom for safety - UpdateServerTileState(Main.tile[tileX + x, tileY + height], new NetTile { Active = true, Type = 0 }, TileDataType.All); + tile.wall = 65; + tile.wall = 63; } - - player.SendTileRect(tileX, tileY, width, height); + else + { + tile.wall = 64; + } + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 1 && wall != 262 && wall != 274 && wall != 61 && wall != 185) + { + tile.wall = 1; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall == 262) + { + tile.wall = 61; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall == 274) + { + tile.wall = 185; + } + if (WallID.Sets.Conversion.NewWall1[wall] && wall != 212) + { + tile.wall = 212; + } + else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 213) + { + tile.wall = 213; + } + else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 214) + { + tile.wall = 214; + } + else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 215) + { + tile.wall = 215; + } + else if (tile.wall == 80) + { + tile.wall = 15; + tile.wall = 64; + } + else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 216) + { + tile.wall = 216; + } + else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 187) + { + tile.wall = 187; + } + if (tile.type == 492) + { + tile.type = 477; + } + else if (TileID.Sets.Conversion.JungleGrass[type] && type != 60) + { + tile.type = 60; + } + else if (TileID.Sets.Conversion.Grass[type] && type != 2 && type != 477) + { + tile.type = 2; + } + else if (TileID.Sets.Conversion.Stone[type] && type != 1) + { + tile.type = 1; + } + else if (TileID.Sets.Conversion.Sand[type] && type != 53) + { + tile.type = 53; + } + else if (TileID.Sets.Conversion.HardenedSand[type] && type != 397) + { + tile.type = 397; + } + else if (TileID.Sets.Conversion.Sandstone[type] && type != 396) + { + tile.type = 396; + } + else if (TileID.Sets.Conversion.Ice[type] && type != 161) + { + tile.type = 161; + } + else if (TileID.Sets.Conversion.MushroomGrass[type]) + { + tile.type = 60; } } } diff --git a/TShockAPI/Hooks/AccountHooks.cs b/TShockAPI/Hooks/AccountHooks.cs index 9c08b26dc..ae9fff244 100644 --- a/TShockAPI/Hooks/AccountHooks.cs +++ b/TShockAPI/Hooks/AccountHooks.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +using System.ComponentModel; using TShockAPI.DB; namespace TShockAPI.Hooks { @@ -39,6 +40,31 @@ public AccountCreateEventArgs(UserAccount account) } } + public class AccountGroupUpdateEventArgs : HandledEventArgs + { + public string AccountName { get; private set; } + public Group Group { get; set; } + + public AccountGroupUpdateEventArgs(string accountName, Group group) + { + this.AccountName = accountName; + this.Group = group; + } + } + + public class AccountGroupUpdateByPlayerEventArgs : AccountGroupUpdateEventArgs + { + /// + /// The player who updated the user's group + /// + public TSPlayer Player { get; private set; } + + public AccountGroupUpdateByPlayerEventArgs(TSPlayer player, string accountName, Group group) : base(accountName, group) + { + this.Player = player; + } + } + public class AccountHooks { public delegate void AccountCreateD(AccountCreateEventArgs e); @@ -62,5 +88,25 @@ public static void OnAccountDelete(UserAccount u) AccountDelete(new AccountDeleteEventArgs(u)); } + + public delegate void AccountGroupUpdateD(AccountGroupUpdateEventArgs e); + public static event AccountGroupUpdateD AccountGroupUpdate; + + public static bool OnAccountGroupUpdate(UserAccount account, TSPlayer author, ref Group group) + { + AccountGroupUpdateEventArgs args = new AccountGroupUpdateByPlayerEventArgs(author, account.Name, group); + AccountGroupUpdate?.Invoke(args); + group = args.Group; + + return args.Handled; + } + public static bool OnAccountGroupUpdate(UserAccount account, ref Group group) + { + AccountGroupUpdateEventArgs args = new AccountGroupUpdateEventArgs(account.Name, group); + AccountGroupUpdate?.Invoke(args); + group = args.Group; + + return args.Handled; + } } } diff --git a/TShockAPI/I18n.cs b/TShockAPI/I18n.cs index e8f5eedbb..ac2985d09 100644 --- a/TShockAPI/I18n.cs +++ b/TShockAPI/I18n.cs @@ -60,6 +60,12 @@ static CultureInfo Redirect(CultureInfo cultureInfo) } } + if (LanguageManager.Instance.ActiveCulture == GameCulture.DefaultCulture) + { + var bf = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static; + // LanguageManager.SetLanguage will change this so we need to reset it back to null + typeof(CultureInfo).GetField("s_currentThreadUICulture", bf)?.SetValue(null, null); + } return CultureInfo.CurrentUICulture; } } diff --git a/TShockAPI/Localization/EnglishLanguage.cs b/TShockAPI/Localization/EnglishLanguage.cs index 1ec4ecea8..334c47804 100644 --- a/TShockAPI/Localization/EnglishLanguage.cs +++ b/TShockAPI/Localization/EnglishLanguage.cs @@ -20,7 +20,9 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Linq; using Terraria; +using Terraria.Initializers; using Terraria.Localization; +using Terraria.UI.Chat; namespace TShockAPI.Localization { @@ -37,6 +39,8 @@ public static class EnglishLanguage private static readonly Dictionary Buffs = new Dictionary(); + private static readonly Dictionary VanillaCommandsPrefixs = new Dictionary(); + internal static void Initialize() { var culture = Language.ActiveCulture; @@ -71,6 +75,15 @@ internal static void Initialize() var i = (int)field.GetValue(null); Prefixs.Add(i, Lang.prefix[i].Value); } + + ChatInitializer.Load(); + foreach (var command in ChatManager.Commands._localizedCommands) + { + if (VanillaCommandsPrefixs.ContainsKey(command.Value._name)) + continue; + VanillaCommandsPrefixs.Add(command.Value._name,command.Key.Value); + } + ChatManager.Commands._localizedCommands.Clear(); } finally { @@ -136,5 +149,18 @@ public static string GetBuffNameById(int id) return null; } + + /// + /// Get vanilla command prefix in English + /// + /// vanilla command name + /// vanilla command prefix in English + public static string GetCommandPrefixByName(string name) + { + string commandText; + if (VanillaCommandsPrefixs.TryGetValue(name, out commandText)) + return commandText; + return null; + } } } diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 278fda1cd..0f0dc6e35 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.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.Linq; using System.Text; @@ -155,13 +155,39 @@ public int Stack /// The net ID. /// The stack. /// The prefix ID. - public NetItem(int netId, int stack, byte prefixId) + public NetItem(int netId, int stack = 1, byte prefixId = 0) { _netId = netId; _stack = stack; _prefixId = prefixId; } + /// + /// Creates a new . + /// + /// Item in the game. + public NetItem(Item item) + { + _netId = item.netID; + _stack = item.stack; + _prefixId = item.prefix; + } + + /// + /// Creates based on data from this structure. + /// + /// A copy of the item. + public Item ToItem() + { + Item item = new Item(); + + item.netDefaults(_netId); + item.stack = _stack; + item.prefix = _prefixId; + + return item; + } + /// /// Converts the to a string. /// diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index d7ec7166d..99ac5d7d2 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -529,18 +529,9 @@ public static void DumpDescriptions() field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute; var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : GetString("No description available."); - var commands = GetCommands(name); - foreach (var c in commands) - { - for (var i = 0; i < c.Names.Count; i++) - { - c.Names[i] = "/" + c.Names[i]; - } - } - var strs = - commands.Select( - c => - c.Name + (c.Names.Count > 1 ? " ({0})".SFormat(string.Join(" ", c.Names.ToArray(), 1, c.Names.Count - 1)) : "")); + var strs = GetCommands(name).Select(c => c.Names.Count > 1 + ? $"/{c.Name} (/{string.Join(" /", c.Names.Skip(1))})" + : $"/{c.Name}"); sb.AppendLine($"## {name}"); sb.AppendLine($"{desc}"); diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index 58cc23d8f..a6e681e2a 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -351,7 +351,6 @@ protected virtual void OnRequest(object sender, RequestEventArgs e) { str = string.Format("{0}({1});", jsonp, str); } - e.Response.Connection.Type = ConnectionType.Close; e.Response.ContentType = new ContentTypeHeader("application/json; charset=utf-8"); e.Response.Add(serverHeader); var bytes = Encoding.UTF8.GetBytes(str); diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index c41e7767b..5dd2ff201 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -402,7 +402,7 @@ private object ServerStatusV2(RestRequestArgs args) {"serverversion", Main.versionNumber}, {"tshockversion", TShock.VersionNum}, {"port", TShock.Config.Settings.ServerPort}, - {"playercount", Main.player.Where(p => null != p && p.active).Count()}, + {"playercount", TShock.Utils.GetActivePlayerCount()}, {"maxplayers", TShock.Config.Settings.MaxSlots}, {"world", (TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)}, {"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")}, @@ -555,7 +555,8 @@ private object UserUpdateV2(RestRequestArgs args) { try { - TShock.UserAccounts.SetUserGroup(account, group); + TShock.UserAccounts.SetUserGroup(new TSRestPlayer(args.TokenData.Username, TShock.Groups.GetGroupByName(args.TokenData.UserGroupName)), + account, group); response.Add("group-response", "Group updated successfully"); } catch (Exception e) @@ -944,8 +945,8 @@ private object PlayerMute(RestRequestArgs args) [Token] private object PlayerList(RestRequestArgs args) { - var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); - return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; + var activeplayers = TShock.Players.Where(p => null != p && p.Active).Select(p => p.Name); + return new RestObject() { { "players", string.Join(", ", activeplayers) } }; } [Description("Fetches detailed user information on all connected users, and can be filtered by specifying a key value pair filter users where the key is a field and the value is a users field value.")] diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 054e31dfa..365c531d5 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -428,6 +428,8 @@ public override void Initialize() Hooks.AccountHooks.AccountDelete += OnAccountDelete; Hooks.AccountHooks.AccountCreate += OnAccountCreate; + On.Terraria.RemoteClient.Reset += RemoteClient_Reset; + GetDataHandlers.InitGetDataHandler(); Commands.InitCommands(); @@ -496,6 +498,12 @@ void SafeError(string message) } } + private static void RemoteClient_Reset(On.Terraria.RemoteClient.orig_Reset orig, RemoteClient client) + { + client.ClientUUID = null; + orig(client); + } + private static void OnAchievementInitializerLoad(ILContext il) { // Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions) @@ -1497,11 +1505,11 @@ private void OnChat(ServerChatEventArgs args) { if (!String.IsNullOrEmpty(text)) { - text = item.Key.Value + ' ' + text; + text = EnglishLanguage.GetCommandPrefixByName(item.Value._name) + ' ' + text; } else { - text = item.Key.Value; + text = EnglishLanguage.GetCommandPrefixByName(item.Value._name); } break; } diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index a37317092..34df3c74c 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -18,7 +18,7 @@ Also, be sure to release on github with the exact assembly version tag as below so that the update manager works correctly (via the Github releases api and mimic) --> - 5.2.0 + 5.2.1 TShock for Terraria Pryaxis & TShock Contributors TShockAPI @@ -34,7 +34,7 @@ - + diff --git a/TShockAPI/TextLog.cs b/TShockAPI/TextLog.cs index 070c6c888..ae3bb29e5 100644 --- a/TShockAPI/TextLog.cs +++ b/TShockAPI/TextLog.cs @@ -29,7 +29,8 @@ namespace TShockAPI /// public class TextLog : ILog, IDisposable { - private readonly StreamWriter _logWriter; + private readonly bool ClearFile; + private StreamWriter _logWriter; /// /// File name of the Text log @@ -44,7 +45,7 @@ public class TextLog : ILog, IDisposable public TextLog(string filename, bool clear) { FileName = filename; - _logWriter = new StreamWriter(filename, !clear); + ClearFile = clear; } public bool MayWriteType(TraceLevel type) @@ -247,6 +248,10 @@ public void Write(string message, TraceLevel level) { if (!MayWriteType(level)) return; + if (_logWriter is null) + { + _logWriter = new StreamWriter(FileName, !ClearFile); + } var caller = "TShock"; @@ -278,7 +283,10 @@ public void Write(string message, TraceLevel level) public void Dispose() { - _logWriter.Dispose(); + if (_logWriter != null) + { + _logWriter.Dispose(); + } } } } diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index 4ee41c012..3b9c0286b 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -172,7 +172,7 @@ public void SendLogs(string log, Color color, TSPlayer excludedPlayer = null) foreach (TSPlayer player in TShock.Players) { if (player != null && player != excludedPlayer && player.Active && player.HasPermission(Permissions.logs) && - player.DisplayLogs && TShock.Config.Settings.DisableSpewLogs == false) + player.DisplayLogs && !TShock.Config.Settings.DisableSpewLogs) player.SendMessage(log, color); } } @@ -183,7 +183,7 @@ public void SendLogs(string log, Color color, TSPlayer excludedPlayer = null) /// The number of active players on the server. public int GetActivePlayerCount() { - return Main.player.Where(p => null != p && p.active).Count(); + return TShock.Players.Count(p => null != p && p.Active); } //Random should not be generated in a method @@ -1149,11 +1149,15 @@ internal void FixChestStacks() /// If the server is empty; determines if we should use Utils.GetActivePlayerCount() for player count or 0. internal void SetConsoleTitle(bool empty) { + if (ShouldSkipTitle) + return; Console.Title = GetString("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})", !string.IsNullOrWhiteSpace(TShock.Config.Settings.ServerName) ? TShock.Config.Settings.ServerName + " - " : "", empty ? 0 : GetActivePlayerCount(), TShock.Config.Settings.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, TShock.VersionNum); } + // Some terminals doesn't supports XTerm escape sequences for setting the title + private static bool ShouldSkipTitle = !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) && !(Environment.GetEnvironmentVariable("TERM")?.Contains("xterm") ?? false); /// Determines the distance between two vectors. /// The first vector location. diff --git a/TShockInstaller/TShockInstaller.csproj b/TShockInstaller/TShockInstaller.csproj index 9b73d66a1..ac3bfff40 100644 --- a/TShockInstaller/TShockInstaller.csproj +++ b/TShockInstaller/TShockInstaller.csproj @@ -12,6 +12,6 @@ - + diff --git a/TShockLauncher/TShockLauncher.csproj b/TShockLauncher/TShockLauncher.csproj index e3c4ac327..fbe428bb1 100644 --- a/TShockLauncher/TShockLauncher.csproj +++ b/TShockLauncher/TShockLauncher.csproj @@ -30,7 +30,7 @@ - + diff --git a/TShockPluginManager/Nuget.cs b/TShockPluginManager/Nuget.cs index 9fdefb6b1..1d00fe1c9 100644 --- a/TShockPluginManager/Nuget.cs +++ b/TShockPluginManager/Nuget.cs @@ -30,7 +30,6 @@ You should have received a copy of the GNU General Public License namespace TShockPluginManager { - public class Nugetter { // this object can figure out the right framework folders to use from a set of packages @@ -82,15 +81,13 @@ async Task GetPackageDependencies( // make sure the source repository can actually tell us about dependencies var dependencyInfoResource = await sourceRepository.GetResourceAsync(); // get the try and dependencies - // (the above function returns a nullable value, but doesn't properly indicate it as such) - #pragma warning disable CS8602 - var dependencyInfo = await dependencyInfoResource?.ResolvePackage( + if (dependencyInfoResource is null) continue; + var dependencyInfo = await dependencyInfoResource.ResolvePackage( package, framework, cacheContext, logger, CancellationToken.None); - #pragma warning restore CS8602 // oop, we don't have the ability to get dependency info from this repository, or // it wasn't found. let's try the next source repository! - if (dependencyInfo == null) continue; + if (dependencyInfo is null) continue; availablePackages.Add(dependencyInfo); foreach (var dependency in dependencyInfo.Dependencies) @@ -302,8 +299,11 @@ public void InstallPackage(SourcePackageDependencyInfo pkg, string pkgPath, Pack var relativeFolder = Path.GetDirectoryName(packageRelativeFilePath); var targetFolder = Path.Join(isPlugin ? "./ServerPlugins" : "./bin", relativeFolder); - Directory.CreateDirectory(targetFolder); - File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true); + if (File.Exists(filePath)) + { + Directory.CreateDirectory(targetFolder); + File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true); + } } } } diff --git a/TShockPluginManager/TShockPluginManager.csproj b/TShockPluginManager/TShockPluginManager.csproj index 33c503fde..1d53f320b 100644 --- a/TShockPluginManager/TShockPluginManager.csproj +++ b/TShockPluginManager/TShockPluginManager.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/TerrariaServerAPI b/TerrariaServerAPI index 8a3fffd71..d4bb7e3a2 160000 --- a/TerrariaServerAPI +++ b/TerrariaServerAPI @@ -1 +1 @@ -Subproject commit 8a3fffd71db401736ea80619122c70c449c10ff3 +Subproject commit d4bb7e3a21e875cfeb23bcf5cf847c85d9470ccf diff --git a/docs/changelog.md b/docs/changelog.md index d329a6dfb..a96b47a7f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -78,17 +78,37 @@ Use past tense when adding new entries; sign your name off when you add or chang * If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change. --> ## Upcoming changes +* Fixed `/dump-reference-data` mutate the command names. (#2943, @sgkoishi) +* Added `ParryDamageBuff` (Striking Moment with Brand of the Inferno and shield) for player, updated `CursedInferno` buff for NPC (@sgkoishi, #3005) +* Changed the use of `Player.active` to `TSPlayer.Active` for consistency. (@sgkoishi, #2939) +* Fix typo in config for IP bans. (@redchess64) +* Updated `TShockAPI.NetItem` (@AgaSpace): + * Added constructor overload with parameter `Terraria.Item`. + * Added the `ToItem` method to get a copy of `Terraria.Item`. + * In the constructor `stack` and `prefix` are now optional parameters. +* Fixed unable to transfer long response body for REST API. (@sgkoishi, #2925) +* Fixed the `/wind` command not being very helpful. (@punchready) +* Fixed /help, /me, and /p commands can't work in non-English languages. (@ACaiCat) +* Added a hook `AccountHooks.AccountGroupUpdate`, which is called when you change the user group. (@AgaSpace) +* * Ensured `TSPlayer.PlayerData` is non-null whilst syncing loadouts. (@drunderscore) + +## TShock 5.2.1 * Updated `TSPlayer.GodMode`. (@AgaSpace) * Previously the field was used as some kind of dataset changed by /godmode command, but now it is a property that receives/changes data in journey mode. * Added the `TSPlayer.Client` property. It allows the developer to get the `RemoteClient` player, without an additional call to `Terraria.Netplay.Clients`. (@AgaSpace) * Updated the documentation for the `TSPlayer.SetPvP` method. The `sendMsg` parameter, which is responsible for sending a pvp mode change message, was not documented earlier. (@AgaSpace) -* Added methods `TSPlayer.KillPlayer` and `TSPlayer.DamagePlayer` for which you can specify the cause (`PlayerDeathReason`) in parameters. +* Added methods `TSPlayer.KillPlayer` and `TSPlayer.DamagePlayer` for which you can specify the cause (`PlayerDeathReason`) in parameters. (@AgaSpace) * Added an error when trying to change a `TSPlayer` team to, say, 9, when there are only 6. (@AgaSpace) * Added an error when trying to call the `TSPlayer.SetTeam` method with an argument (team) greater than 5 or less than 0. (@AgaSpace) * Added a method `TSPlayer.UpdateSection` with arguments `rectangle` and `isLoaded`, which will load some area from the server to the player. (@AgaSpace) * Added a method `TSPlayer.GiveItem`, which has `TShockAPI.NetItem` structure in its arguments. (@AgaSpace) -* Added a property `TSPlayer.Hostile`, which gets pvp player mode. -* Ensured `TSPlayer.PlayerData` is non-null whilst syncing loadouts. (@drunderscore) +* Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) +* Fixed bug where when the `UseSqlLogs` config property is true, an empty log file would still get created. (@ZakFahey) +* Fixed typo in `/gbuff`. (@sgkoishi, #2955) +* Rewrote the `.dockerignore` file into a denylist. (@timschumi) +* Added CI for Docker images. (@timschumi) +* Fixed Cursed Flares kicking players for invalid buff. (@Arthri) + ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) @@ -122,7 +142,6 @@ Use past tense when adding new entries; sign your name off when you add or chang * Relaxed custom death message restrictions to allow Inferno potions in PvP. (@drunderscore) * Allowed Flower Boots to place Ash Flowers on Ash Grass blocks. (@punchready) * Removed unnecessary range check that artifically shortened quick stack reach. (@boddyn, #2885, @bcat) -* Improved the exploit protection in tile rect handling. (@punchready) ## TShock 5.1.3 * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) diff --git a/docs/docker.md b/docs/docker.md index 0b89681bb..afc4bdfc9 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -14,32 +14,27 @@ Open ports can also be passed through using `-p :`. For Example: ```bash -# Building the image -docker build -t tshock:linux-amd64 --build-arg TARGETPLATFORM=linux/amd64 . - -# Running the image docker run -p 7777:7777 -p 7878:7878 \ -v /home/cider/tshock/:/tshock \ -v /home/cider/.local/share/Terraria/Worlds:/worlds \ -v /home/cider/tshock/plugins:/plugins \ - --rm -it tshock:linux-amd64 \ + --rm -it ghcr.io/pryaxis/tshock:latest \ -world /worlds/backflip.wld -motd "OMFG DOCKER" ``` -## Building for Other Platforms +## Building custom images -Using `docker buildx`, you could build [multi-platform images](https://docs.docker.com/build/building/multi-platform/) for TShock. +Occasionally, it may be necessary to adjust TShock with customizations that are not included in the upstream project. +Therefore, these changes are also not available in the officially provided Docker images. + +To build and load a Docker image from your local checkout, use the following `buildx` command: -For Example: ```bash -# Building the image using buildx and loading it into docker -sudo docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load . +docker buildx build -t tshock:latest --load . +``` -# Running the image -docker run -p 7777:7777 -p 7878:7878 \ - -v /home/cider/tshock/:/tshock \ - -v /home/cider/.local/share/Terraria/Worlds:/worlds \ - -v /home/cider/tshock/plugins:/plugins \ - --rm -it tshock:linux-arm64 \ - -world /worlds/backflip.wld -motd "ARM64 ftw" +It is also possible to build [multi-platform images](https://docs.docker.com/build/building/multi-platform/) for TShock (e.g. an image targeting `arm64`, on a host that is not `arm64`): + +```bash +docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load . ``` diff --git a/docs/index.html b/docs/index.html index 2ae1df82e..0cfccf115 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,12 +11,7 @@
diff --git a/i18n/template.pot b/i18n/template.pot index a4fe57ab9..9da0b2e07 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: TShock\n" -"POT-Creation-Date: 2022-12-06 05:43:49+0000\n" -"PO-Revision-Date: 2022-12-06 05:43:50+0000\n" +"POT-Creation-Date: 2023-08-15 09:53:07+0000\n" +"PO-Revision-Date: 2023-08-15 09:53:09+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -10,8 +10,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: GetText.NET Extractor\n" -#: ../../TShockAPI/DB/CharacterManager.cs:194 -#: ../../TShockAPI/DB/CharacterManager.cs:264 +#: ../../TShockAPI/DB/CharacterManager.cs:197 +#: ../../TShockAPI/DB/CharacterManager.cs:267 #, csharp-format msgctxt "{0} is a player name" msgid "Skipping SSC save (due to tshock.ignore.ssc) for {0}" @@ -161,31 +161,31 @@ msgstr "" msgid "- Palm trees :" msgstr "" -#: ../../TShockAPI/TShock.cs:963 +#: ../../TShockAPI/TShock.cs:999 msgid "" "!!! > Set DisableLoginBeforeJoin to true in the config file and /reload if " "this is a problem." msgstr "" -#: ../../TShockAPI/TShock.cs:957 +#: ../../TShockAPI/TShock.cs:993 msgid "" "!!! > Set DisableUUIDLogin to true in the config file and /reload if this is " "a problem." msgstr "" -#: ../../TShockAPI/TShock.cs:962 +#: ../../TShockAPI/TShock.cs:998 msgid "" "!!! Login before join is enabled. Existing accounts can login & the server " "password will be bypassed." msgstr "" -#: ../../TShockAPI/TShock.cs:951 +#: ../../TShockAPI/TShock.cs:987 msgid "" "!!! The server password in config.json was overridden by the interactive " "prompt and will be ignored." msgstr "" -#: ../../TShockAPI/TShock.cs:956 +#: ../../TShockAPI/TShock.cs:992 msgid "" "!!! UUID login is enabled. If a user's UUID matches an account, the server " "password will be bypassed." @@ -249,12 +249,12 @@ msgstr "" msgid "{0} ({1}) failed to change the password for account {2}." msgstr "" -#: ../../TShockAPI/TShock.cs:1659 +#: ../../TShockAPI/TShock.cs:1695 #, csharp-format msgid "{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})" msgstr "" -#: ../../TShockAPI/TShock.cs:1667 +#: ../../TShockAPI/TShock.cs:1703 #, csharp-format msgid "{0} ({1}) from '{2}' group joined. ({3}/{4})" msgstr "" @@ -266,7 +266,7 @@ msgid "" "automatically." msgstr "" -#: ../../TShockAPI/TShock.cs:1663 +#: ../../TShockAPI/TShock.cs:1699 #, csharp-format msgid "{0} ({1}) has joined." msgstr "" @@ -367,17 +367,17 @@ msgstr "" msgid "{0} added account {1} to group {2}." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3546 +#: ../../TShockAPI/GetDataHandlers.cs:3587 #, csharp-format msgid "{0} applied advanced combat techniques volume 2!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3564 +#: ../../TShockAPI/GetDataHandlers.cs:3605 #, csharp-format msgid "{0} applied advanced combat techniques!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3543 +#: ../../TShockAPI/GetDataHandlers.cs:3584 #, csharp-format msgid "{0} applied traveling merchant's satchel!" msgstr "" @@ -387,8 +387,8 @@ msgstr "" msgid "{0} attempted to register for the account {1} but it was already taken." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2637 -#: ../../TShockAPI/GetDataHandlers.cs:3212 +#: ../../TShockAPI/GetDataHandlers.cs:2672 +#: ../../TShockAPI/GetDataHandlers.cs:3252 #, csharp-format msgid "{0} authenticated successfully as user {1}." msgstr "" @@ -398,7 +398,7 @@ msgstr "" msgid "{0} authenticated successfully as user: {1}." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1955 +#: ../../TShockAPI/TSPlayer.cs:2042 #, csharp-format msgid "{0} banned {1} for '{2}'." msgstr "" @@ -486,7 +486,7 @@ msgstr "" msgid "{0} disabled xmas mode." msgstr "" -#: ../../TShockAPI/TShock.cs:1398 +#: ../../TShockAPI/TShock.cs:1434 #, csharp-format msgid "{0} disconnected." msgstr "" @@ -590,12 +590,12 @@ msgstr "" msgid "{0} has ended the Old One's Army event." msgstr "" -#: ../../TShockAPI/TShock.cs:1670 +#: ../../TShockAPI/TShock.cs:1706 #, csharp-format msgid "{0} has joined." msgstr "" -#: ../../TShockAPI/TShock.cs:1674 +#: ../../TShockAPI/TShock.cs:1710 #, csharp-format msgid "{0} has joined. IP: {1}" msgstr "" @@ -615,7 +615,7 @@ msgstr "" msgid "{0} has launched himself into space." msgstr "" -#: ../../TShockAPI/TShock.cs:1397 +#: ../../TShockAPI/TShock.cs:1433 #, csharp-format msgid "{0} has left." msgstr "" @@ -630,22 +630,22 @@ msgstr "" msgid "{0} has respawned you." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3555 +#: ../../TShockAPI/GetDataHandlers.cs:3596 #, csharp-format msgid "{0} has sent a request to the bunny delivery service!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3561 +#: ../../TShockAPI/GetDataHandlers.cs:3602 #, csharp-format msgid "{0} has sent a request to the cat delivery service!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3558 +#: ../../TShockAPI/GetDataHandlers.cs:3599 #, csharp-format msgid "{0} has sent a request to the dog delivery service!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3552 +#: ../../TShockAPI/GetDataHandlers.cs:3593 #, csharp-format msgid "{0} has sent a request to the slime delivery service!" msgstr "" @@ -662,7 +662,7 @@ msgstr[1] "" msgid "{0} has spawned a Wall of Flesh." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2620 +#: ../../TShockAPI/GetDataHandlers.cs:2655 #, csharp-format msgid "" "{0} has SSC data in the database, but has the tshock.ignore.ssc permission. " @@ -749,7 +749,7 @@ msgstr "" msgid "{0} is banned! Remove it!" msgstr "" -#: ../../TShockAPI/Commands.cs:6751 +#: ../../TShockAPI/Commands.cs:6747 #, csharp-format msgid "{0} is no longer in god mode." msgstr "" @@ -771,7 +771,7 @@ msgstr "" msgid "{0} is not dead!" msgstr "" -#: ../../TShockAPI/Commands.cs:6750 +#: ../../TShockAPI/Commands.cs:6746 #, csharp-format msgid "{0} is now in god mode." msgstr "" @@ -787,7 +787,7 @@ msgstr "" msgid "{0} just killed you!" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1926 +#: ../../TShockAPI/TSPlayer.cs:2013 #, csharp-format msgid "{0} kicked {1} for '{2}'" msgstr "" @@ -887,8 +887,8 @@ msgstr "" msgid "{0} started the frost moon at wave {1}!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4150 -#: ../../TShockAPI/GetDataHandlers.cs:4152 +#: ../../TShockAPI/GetDataHandlers.cs:4194 +#: ../../TShockAPI/GetDataHandlers.cs:4196 #, csharp-format msgid "{0} started the Old One's Army event!" msgstr "" @@ -923,63 +923,63 @@ msgstr "" msgid "{0} successfully deleted account: {1}." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3567 +#: ../../TShockAPI/GetDataHandlers.cs:3608 #, csharp-format msgid "{0} summoned a Blood Moon!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3579 +#: ../../TShockAPI/GetDataHandlers.cs:3620 #, csharp-format msgid "{0} summoned a frost moon!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3591 +#: ../../TShockAPI/GetDataHandlers.cs:3632 #, csharp-format msgid "{0} summoned a Goblin Invasion!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3573 +#: ../../TShockAPI/GetDataHandlers.cs:3614 #, csharp-format msgid "{0} summoned a Martian invasion!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3549 +#: ../../TShockAPI/GetDataHandlers.cs:3590 #, csharp-format msgid "{0} summoned a Mechdusa!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3570 +#: ../../TShockAPI/GetDataHandlers.cs:3611 #, csharp-format msgid "{0} summoned a Moon Lord!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3582 +#: ../../TShockAPI/GetDataHandlers.cs:3623 #, csharp-format msgid "{0} summoned a pumpkin moon!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3576 +#: ../../TShockAPI/GetDataHandlers.cs:3617 #, csharp-format msgid "{0} summoned an eclipse!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3598 +#: ../../TShockAPI/GetDataHandlers.cs:3639 #, csharp-format msgid "{0} summoned the {1}!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2977 -#: ../../TShockAPI/GetDataHandlers.cs:2980 +#: ../../TShockAPI/GetDataHandlers.cs:3016 +#: ../../TShockAPI/GetDataHandlers.cs:3019 #, csharp-format msgid "{0} summoned the Empress of Light!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3585 +#: ../../TShockAPI/GetDataHandlers.cs:3626 #, csharp-format msgid "{0} summoned the Pirates!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3588 +#: ../../TShockAPI/GetDataHandlers.cs:3629 #, csharp-format msgid "{0} summoned the Snow Legion!" msgstr "" @@ -1021,12 +1021,12 @@ msgstr "" msgid "{0} warped you to {1}." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1953 +#: ../../TShockAPI/TSPlayer.cs:2040 #, csharp-format msgid "{0} was banned for '{1}'." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1924 +#: ../../TShockAPI/TSPlayer.cs:2011 #, csharp-format msgid "{0} was kicked for '{1}'" msgstr "" @@ -1216,7 +1216,7 @@ msgid "" "re-create the allowed field." msgstr "" -#: ../../TShockAPI/TShock.cs:986 +#: ../../TShockAPI/TShock.cs:1022 msgid "" "A malicious server can easily steal a user's UUID. You may consider turning " "this option off if you run a public server." @@ -1234,7 +1234,7 @@ msgstr "" msgid "a Nebula Pillar" msgstr "" -#: ../../TShockAPI/TShock.cs:974 +#: ../../TShockAPI/TShock.cs:1010 msgid "A password for this server was set in config.json and is being used." msgstr "" @@ -1242,7 +1242,7 @@ msgstr "" msgid "A player name must be provided to kick a player. Please provide one." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2451 +#: ../../TShockAPI/GetDataHandlers.cs:2486 msgid "A plugin on this server stopped your login." msgstr "" @@ -1398,7 +1398,7 @@ msgstr "" msgid "Amethyst Gemtree" msgstr "" -#: ../../TShockAPI/TShock.cs:996 +#: ../../TShockAPI/TShock.cs:1032 msgid "" "An account has been detected in the user database, but setup-code.txt is " "still present." @@ -1414,7 +1414,7 @@ msgstr "" msgid "An exception has occurred during database transaction: {0}" msgstr "" -#: ../../TShockAPI/TShock.cs:1489 +#: ../../TShockAPI/TShock.cs:1525 msgid "An exception occurred executing a command." msgstr "" @@ -1477,19 +1477,19 @@ msgid "" msgstr "" #: ../../TShockAPI/Commands.cs:903 -#: ../../TShockAPI/GetDataHandlers.cs:2636 -#: ../../TShockAPI/GetDataHandlers.cs:3211 +#: ../../TShockAPI/GetDataHandlers.cs:2671 +#: ../../TShockAPI/GetDataHandlers.cs:3251 #, csharp-format msgid "Authenticated as {0} successfully." msgstr "" -#: ../../TShockAPI/TShock.cs:440 -#: ../../TShockAPI/TShock.cs:1590 +#: ../../TShockAPI/TShock.cs:471 +#: ../../TShockAPI/TShock.cs:1626 msgid "AutoSave Disabled" msgstr "" -#: ../../TShockAPI/TShock.cs:438 -#: ../../TShockAPI/TShock.cs:1588 +#: ../../TShockAPI/TShock.cs:469 +#: ../../TShockAPI/TShock.cs:1624 msgid "AutoSave Enabled" msgstr "" @@ -1538,11 +1538,11 @@ msgstr "" msgid "Backup Thread" msgstr "" -#: ../../TShockAPI/TShock.cs:444 +#: ../../TShockAPI/TShock.cs:475 msgid "Backups Disabled" msgstr "" -#: ../../TShockAPI/TShock.cs:442 +#: ../../TShockAPI/TShock.cs:473 msgid "Backups Enabled" msgstr "" @@ -1625,7 +1625,7 @@ msgstr "" msgid "Banned tile {0}." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1950 +#: ../../TShockAPI/TSPlayer.cs:2037 #, csharp-format msgid "Banned: {0}" msgstr "" @@ -1660,12 +1660,12 @@ msgstr "" msgid "Boreal Tree" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3264 +#: ../../TShockAPI/GetDataHandlers.cs:3304 #, csharp-format msgid "Bouncer / HandleNpcTalk rejected from bouncer out of bounds from {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3257 +#: ../../TShockAPI/GetDataHandlers.cs:3297 #, csharp-format msgid "Bouncer / HandleNpcTalk rejected from bouncer throttle from {0}" msgstr "" @@ -1705,57 +1705,57 @@ msgstr "" msgid "Bouncer / OnChestOpen rejected from region check from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2735 +#: ../../TShockAPI/Bouncer.cs:2738 #, csharp-format msgid "" "Bouncer / OnFishOutNPC rejected for not finding active bobber projectile! - " "From {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2729 +#: ../../TShockAPI/Bouncer.cs:2732 #, csharp-format msgid "Bouncer / OnFishOutNPC rejected for not using a fishing rod! - From {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2741 +#: ../../TShockAPI/Bouncer.cs:2744 #, csharp-format msgid "" "Bouncer / OnFishOutNPC rejected for the NPC not being on the fishable NPCs " "list! - From {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2753 +#: ../../TShockAPI/Bouncer.cs:2756 #, csharp-format msgid "Bouncer / OnFishOutNPC rejected range checks from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2747 +#: ../../TShockAPI/Bouncer.cs:2750 #, csharp-format msgid "Bouncer / OnFishOutNPC rejected summon boss permissions from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2781 +#: ../../TShockAPI/Bouncer.cs:2784 #, csharp-format msgid "Bouncer / OnFoodPlatterTryPlacing rejected disabled from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2774 +#: ../../TShockAPI/Bouncer.cs:2777 #, csharp-format msgid "" "Bouncer / OnFoodPlatterTryPlacing rejected item not placed by hand from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2792 +#: ../../TShockAPI/Bouncer.cs:2795 #, csharp-format msgid "Bouncer / OnFoodPlatterTryPlacing rejected permissions from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2803 +#: ../../TShockAPI/Bouncer.cs:2806 #, csharp-format msgid "Bouncer / OnFoodPlatterTryPlacing rejected range checks from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2767 +#: ../../TShockAPI/Bouncer.cs:2770 #, csharp-format msgid "" "Bouncer / OnFoodPlatterTryPlacing rejected tile placement valid from {0}" @@ -1852,22 +1852,22 @@ msgstr "" msgid "Bouncer / OnItemDrop rejected from sneaky from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2703 +#: ../../TShockAPI/Bouncer.cs:2706 #, csharp-format msgid "Bouncer / OnKillMe rejected bad length death text from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2710 +#: ../../TShockAPI/Bouncer.cs:2713 #, csharp-format msgid "Bouncer / OnKillMe rejected custom death message from {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2684 +#: ../../TShockAPI/Bouncer.cs:2687 #, csharp-format msgid "Bouncer / OnKillMe rejected high damage from {0} {1}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2693 +#: ../../TShockAPI/Bouncer.cs:2696 #, csharp-format msgid "Bouncer / OnKillMe rejected index check from {0}" msgstr "" @@ -2280,7 +2280,7 @@ msgid "" "ticks: target is null" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2667 +#: ../../TShockAPI/Bouncer.cs:2670 #, csharp-format msgid "Bouncer / OnPlayerDamage rejected custom death message from {0}" msgstr "" @@ -2596,59 +2596,51 @@ msgstr "" msgid "Bouncer / OnUpdateNPCHome rejected range checks from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:558 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:413 #, csharp-format msgid "Bouncer / SendTileRect accepted clientside world edit from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:414 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:468 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:488 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:500 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:511 #, csharp-format -msgid "" -"Bouncer / SendTileRect processing a tile conversion update - [{0}] -> [{1}]" +msgid "Bouncer / SendTileRect reimplemented from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:429 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:446 #, csharp-format -msgid "" -"Bouncer / SendTileRect processing a wall conversion update - [{0}] -> [{1}]" +msgid "Bouncer / SendTileRect rejected from being disabled from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:129 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:518 #, csharp-format -msgid "Bouncer / SendTileRect reimplemented from carbonara from {0}" -msgstr "" - -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:299 -msgid "Bouncer / SendTileRect rejected for banned tile" +msgid "Bouncer / SendTileRect rejected from matches from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:578 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:459 #, csharp-format -msgid "Bouncer / SendTileRect rejected from being disabled from {0}" +msgid "" +"Bouncer / SendTileRect rejected from out of bounds / build permission from " +"{0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:293 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:478 #, csharp-format -msgid "" -"Bouncer / SendTileRect rejected from no permission for tile object from {0}" +msgid "Bouncer / SendTileRect rejected from out of range from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:565 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:427 #, csharp-format -msgid "Bouncer / SendTileRect rejected from non-vanilla tilemod from {0}" +msgid "Bouncer / SendTileRect rejected from size from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:571 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:436 #, csharp-format msgid "Bouncer / SendTileRect rejected from throttle from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:610 -msgid "" -"Bouncer / SendTileRectHandler - rejected tile object because object " -"dimensions fall outside the tile rect (excessive size)" -msgstr "" - #: ../../TShockAPI/Utils.cs:136 #, csharp-format msgid "Broadcast: {0}" @@ -2671,7 +2663,7 @@ msgstr "" msgid "Butcher Syntax and Example" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2619 +#: ../../TShockAPI/GetDataHandlers.cs:2654 msgid "" "Bypass SSC is enabled for your account. SSC data will not be loaded or saved." msgstr "" @@ -2780,7 +2772,7 @@ msgstr "" msgid "Commands: add, del, hide, list, send, [warpname]." msgstr "" -#: ../../TShockAPI/TShock.cs:765 +#: ../../TShockAPI/TShock.cs:801 #, csharp-format msgid "Config path has been set to {0}" msgstr "" @@ -2795,7 +2787,7 @@ msgstr "" msgid "Connect to the internet to figure out what to download?" msgstr "" -#: ../../TShockAPI/TShock.cs:1329 +#: ../../TShockAPI/TShock.cs:1365 msgid "Connecting via a proxy is not allowed." msgstr "" @@ -2808,7 +2800,7 @@ msgstr "" msgid "Corruption Palm" msgstr "" -#: ../../TShockAPI/TShock.cs:296 +#: ../../TShockAPI/TShock.cs:300 #, csharp-format msgid "" "Could not apply the given log path / log format, defaults will be used. " @@ -2886,7 +2878,7 @@ msgstr "" msgid "Could not rename {0}!" msgstr "" -#: ../../TShockAPI/TShock.cs:1448 +#: ../../TShockAPI/TShock.cs:1484 msgid "Crash attempt via long chat packet." msgstr "" @@ -2928,12 +2920,12 @@ msgstr "" msgid "Current spawn rate: {0}." msgstr "" -#: ../../TShockAPI/Bouncer.cs:2686 +#: ../../TShockAPI/Bouncer.cs:2689 #, csharp-format msgid "Death Exploit Attempt: Damage {0}" msgstr "" -#: ../../TShockAPI/Bouncer.cs:2704 +#: ../../TShockAPI/Bouncer.cs:2707 msgid "Death reason outside of normal bounds." msgstr "" @@ -3134,7 +3126,7 @@ msgstr "" msgid "Executes a command as the super admin." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4371 +#: ../../TShockAPI/GetDataHandlers.cs:4416 msgid "Exploit attempt detected!" msgstr "" @@ -3177,7 +3169,7 @@ msgstr "" msgid "Failed to rename the region." msgstr "" -#: ../../TShockAPI/Bouncer.cs:2685 +#: ../../TShockAPI/Bouncer.cs:2688 msgid "Failed to shade polygon normals." msgstr "" @@ -3239,402 +3231,402 @@ msgid_plural "Gave {0} {1}s." msgstr[0] "" msgstr[1] "" -#: ../../TShockAPI/GetDataHandlers.cs:3796 +#: ../../TShockAPI/GetDataHandlers.cs:3839 #, csharp-format msgid "GetDataHandlers / HandleCatchNpc catch zero {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3804 +#: ../../TShockAPI/GetDataHandlers.cs:3847 #, csharp-format msgid "GetDataHandlers / HandleCatchNpc rejected catch npc {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3119 +#: ../../TShockAPI/GetDataHandlers.cs:3158 #, csharp-format msgid "" "GetDataHandlers / HandleChestActive rejected build permission and region " "check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3094 +#: ../../TShockAPI/GetDataHandlers.cs:3133 #, csharp-format msgid "GetDataHandlers / HandleChestItem rejected max stacks {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2844 +#: ../../TShockAPI/GetDataHandlers.cs:2881 #, csharp-format msgid "GetDataHandlers / HandleDoorUse rejected door gap check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2829 +#: ../../TShockAPI/GetDataHandlers.cs:2866 #, csharp-format msgid "GetDataHandlers / HandleDoorUse rejected out of range door {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2835 +#: ../../TShockAPI/GetDataHandlers.cs:2872 #, csharp-format msgid "GetDataHandlers / HandleDoorUse rejected type 0 5 check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2669 +#: ../../TShockAPI/GetDataHandlers.cs:2704 msgid "GetDataHandlers / HandleGetSection rejected reserve slot" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4042 +#: ../../TShockAPI/GetDataHandlers.cs:4086 #, csharp-format msgid "GetDataHandlers / HandleKillPortal rejected owner mismatch check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2989 +#: ../../TShockAPI/GetDataHandlers.cs:3028 #, csharp-format msgid "GetDataHandlers / HandleNpcStrike rejected Cultist summon from {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2972 +#: ../../TShockAPI/GetDataHandlers.cs:3011 #, csharp-format msgid "GetDataHandlers / HandleNpcStrike rejected EoL summon from {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2962 +#: ../../TShockAPI/GetDataHandlers.cs:3001 #, csharp-format msgid "GetDataHandlers / HandleNpcStrike rejected npc strike {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3250 +#: ../../TShockAPI/GetDataHandlers.cs:3290 #, csharp-format msgid "GetDataHandlers / HandleNpcTalk rejected npc talk {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4086 +#: ../../TShockAPI/GetDataHandlers.cs:4130 #, csharp-format msgid "" "GetDataHandlers / HandleNpcTeleportPortal rejected not thinking with portals " "{0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4079 +#: ../../TShockAPI/GetDataHandlers.cs:4123 #, csharp-format msgid "GetDataHandlers / HandleNpcTeleportPortal rejected null check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3920 +#: ../../TShockAPI/GetDataHandlers.cs:3963 #, csharp-format msgid "" "GetDataHandlers / HandleNumberOfAnglerQuestsCompleted surprise packet! " "Someone tell the TShock team! {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4144 +#: ../../TShockAPI/GetDataHandlers.cs:4188 #, csharp-format msgid "GetDataHandlers / HandleOldOnesArmy rejected permissions {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4138 +#: ../../TShockAPI/GetDataHandlers.cs:4182 #, csharp-format msgid "GetDataHandlers / HandleOldOnesArmy rejected throttled {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3622 +#: ../../TShockAPI/GetDataHandlers.cs:3664 #, csharp-format msgid "GetDataHandlers / HandlePaintTile rejected range check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3640 +#: ../../TShockAPI/GetDataHandlers.cs:3682 #, csharp-format msgid "GetDataHandlers / HandlePaintTile rejected select consistency {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3649 +#: ../../TShockAPI/GetDataHandlers.cs:3691 #, csharp-format msgid "" "GetDataHandlers / HandlePaintTile rejected throttle/permission/range check " "{0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3669 +#: ../../TShockAPI/GetDataHandlers.cs:3712 #, csharp-format msgid "GetDataHandlers / HandlePaintWall rejected range check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3687 +#: ../../TShockAPI/GetDataHandlers.cs:3730 #, csharp-format msgid "GetDataHandlers / HandlePaintWall rejected selector consistency {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3696 +#: ../../TShockAPI/GetDataHandlers.cs:3739 #, csharp-format msgid "" "GetDataHandlers / HandlePaintWall rejected throttle/permission/range {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3411 +#: ../../TShockAPI/GetDataHandlers.cs:3452 #, csharp-format msgid "GetDataHandlers / HandlePlayerBuffList handled event and sent data {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3396 +#: ../../TShockAPI/GetDataHandlers.cs:3437 #, csharp-format msgid "" "GetDataHandlers / HandlePlayerBuffList zeroed player buff due to below state " "2 {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2777 +#: ../../TShockAPI/GetDataHandlers.cs:2814 #, csharp-format msgid "GetDataHandlers / HandlePlayerHp rejected over max hp {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2522 +#: ../../TShockAPI/GetDataHandlers.cs:2557 msgid "GetDataHandlers / HandlePlayerInfo rejected hardcore required" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2516 +#: ../../TShockAPI/GetDataHandlers.cs:2551 msgid "GetDataHandlers / HandlePlayerInfo rejected mediumcore required" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2457 +#: ../../TShockAPI/GetDataHandlers.cs:2492 msgid "GetDataHandlers / HandlePlayerInfo rejected name length 0" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2450 +#: ../../TShockAPI/GetDataHandlers.cs:2485 #, csharp-format msgid "GetDataHandlers / HandlePlayerInfo rejected plugin phase {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2510 +#: ../../TShockAPI/GetDataHandlers.cs:2545 msgid "GetDataHandlers / HandlePlayerInfo rejected softcore required" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4210 -#: ../../TShockAPI/GetDataHandlers.cs:4216 +#: ../../TShockAPI/GetDataHandlers.cs:4255 +#: ../../TShockAPI/GetDataHandlers.cs:4261 #, csharp-format msgid "GetDataHandlers / HandlePlayerKillMeV2 kicked with difficulty {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4225 +#: ../../TShockAPI/GetDataHandlers.cs:4270 #, csharp-format msgid "GetDataHandlers / HandlePlayerKillMeV2 ssc delete {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3289 +#: ../../TShockAPI/GetDataHandlers.cs:3329 #, csharp-format msgid "GetDataHandlers / HandlePlayerMana rejected max mana {0} {1}/{2}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2554 +#: ../../TShockAPI/GetDataHandlers.cs:2589 msgid "GetDataHandlers / HandlePlayerSlot rejected ignore ssc packets" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3316 +#: ../../TShockAPI/GetDataHandlers.cs:3357 #, csharp-format msgid "GetDataHandlers / HandlePlayerTeam rejected team fastswitch {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2757 +#: ../../TShockAPI/GetDataHandlers.cs:2794 #, csharp-format msgid "GetDataHandlers / HandlePlayerUpdate home position delta {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3144 +#: ../../TShockAPI/GetDataHandlers.cs:3183 msgid "GetDataHandlers / HandlePlayerZone rejected null check" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3025 +#: ../../TShockAPI/GetDataHandlers.cs:3064 #, csharp-format msgid "" "GetDataHandlers / HandleProjectileKill permitted skeletron prime exemption " "{0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3029 +#: ../../TShockAPI/GetDataHandlers.cs:3068 #, csharp-format msgid "GetDataHandlers / HandleProjectileKill rejected banned projectile {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3014 +#: ../../TShockAPI/GetDataHandlers.cs:3053 #, csharp-format msgid "GetDataHandlers / HandleProjectileKill rejected tombstone {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3354 +#: ../../TShockAPI/GetDataHandlers.cs:3395 #, csharp-format msgid "GetDataHandlers / HandleSign rejected sign on build permission {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3361 +#: ../../TShockAPI/GetDataHandlers.cs:3402 #, csharp-format msgid "GetDataHandlers / HandleSign rejected sign range check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3334 +#: ../../TShockAPI/GetDataHandlers.cs:3375 #, csharp-format msgid "GetDataHandlers / HandleSignRead rejected out of bounds {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2700 +#: ../../TShockAPI/GetDataHandlers.cs:2737 #, csharp-format msgid "GetDataHandlers / HandleSpawn force teleport 'vanilla spawn' {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2711 +#: ../../TShockAPI/GetDataHandlers.cs:2748 #, csharp-format msgid "GetDataHandlers / HandleSpawn force teleport phase 1 {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2720 +#: ../../TShockAPI/GetDataHandlers.cs:2757 #, csharp-format msgid "GetDataHandlers / HandleSpawn force teleport phase 2 {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2682 +#: ../../TShockAPI/GetDataHandlers.cs:2717 #, csharp-format msgid "GetDataHandlers / HandleSpawn rejected dead player spawn request {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3517 +#: ../../TShockAPI/GetDataHandlers.cs:3558 #, csharp-format msgid "GetDataHandlers / HandleSpawnBoss rejected boss {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3507 +#: ../../TShockAPI/GetDataHandlers.cs:3548 #, csharp-format msgid "GetDataHandlers / HandleSpawnBoss rejected bouner throttled {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3524 +#: ../../TShockAPI/GetDataHandlers.cs:3565 #, csharp-format msgid "GetDataHandlers / HandleSpawnBoss rejected invasion {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3531 +#: ../../TShockAPI/GetDataHandlers.cs:3572 #, csharp-format msgid "GetDataHandlers / HandleSpawnBoss rejected pet {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3442 +#: ../../TShockAPI/GetDataHandlers.cs:3483 #, csharp-format msgid "" "GetDataHandlers / HandleSpecial rejected enchanted sundial permission " "(ForceTime) {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3436 +#: ../../TShockAPI/GetDataHandlers.cs:3477 #, csharp-format msgid "" "GetDataHandlers / HandleSpecial rejected enchanted sundial permission {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3426 +#: ../../TShockAPI/GetDataHandlers.cs:3467 #, csharp-format msgid "GetDataHandlers / HandleSpecial rejected type 1 for {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4005 +#: ../../TShockAPI/GetDataHandlers.cs:4049 #, csharp-format msgid "" "GetDataHandlers / HandleSyncExtraValue rejected expert/master mode check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3999 +#: ../../TShockAPI/GetDataHandlers.cs:4043 #, csharp-format msgid "GetDataHandlers / HandleSyncExtraValue rejected extents check {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4011 +#: ../../TShockAPI/GetDataHandlers.cs:4055 #, csharp-format msgid "" "GetDataHandlers / HandleSyncExtraValue rejected npc id out of bounds check - " "NPC ID: {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4018 +#: ../../TShockAPI/GetDataHandlers.cs:4062 #, csharp-format msgid "" "GetDataHandlers / HandleSyncExtraValue rejected npc is null - NPC ID: {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4025 +#: ../../TShockAPI/GetDataHandlers.cs:4069 #, csharp-format msgid "" "GetDataHandlers / HandleSyncExtraValue rejected range check {0},{1} vs " "{2},{3} which is {4}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4397 +#: ../../TShockAPI/GetDataHandlers.cs:4442 #, csharp-format msgid "GetDataHandlers / HandleSyncLoadout rejected loadout index sync {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4388 +#: ../../TShockAPI/GetDataHandlers.cs:4433 #, csharp-format msgid "" "GetDataHandlers / HandleSyncLoadout rejected loadout index sync out of bounds " "{0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3753 +#: ../../TShockAPI/GetDataHandlers.cs:3796 #, csharp-format msgid "GetDataHandlers / HandleTeleport rejected npc teleport {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3762 +#: ../../TShockAPI/GetDataHandlers.cs:3805 #, csharp-format msgid "GetDataHandlers / HandleTeleport rejected p2p extents {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3768 +#: ../../TShockAPI/GetDataHandlers.cs:3811 #, csharp-format msgid "" "GetDataHandlers / HandleTeleport rejected p2p wormhole permission {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3744 +#: ../../TShockAPI/GetDataHandlers.cs:3787 #, csharp-format msgid "GetDataHandlers / HandleTeleport rejected rod type {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3842 -#: ../../TShockAPI/GetDataHandlers.cs:3858 -#: ../../TShockAPI/GetDataHandlers.cs:3881 +#: ../../TShockAPI/GetDataHandlers.cs:3885 #: ../../TShockAPI/GetDataHandlers.cs:3901 +#: ../../TShockAPI/GetDataHandlers.cs:3924 +#: ../../TShockAPI/GetDataHandlers.cs:3944 #, csharp-format msgid "" "GetDataHandlers / HandleTeleportationPotion rejected not holding the correct " "item {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3832 +#: ../../TShockAPI/GetDataHandlers.cs:3875 #, csharp-format msgid "" "GetDataHandlers / HandleTeleportationPotion rejected permissions {0} {1}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4126 +#: ../../TShockAPI/GetDataHandlers.cs:4170 #, csharp-format msgid "GetDataHandlers / HandleToggleParty rejected no party {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3059 +#: ../../TShockAPI/GetDataHandlers.cs:3098 #, csharp-format msgid "GetDataHandlers / HandleTogglePvp rejected fastswitch {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3052 +#: ../../TShockAPI/GetDataHandlers.cs:3091 #, csharp-format msgid "GetDataHandlers / HandleTogglePvp rejected index mismatch {0}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2735 +#: ../../TShockAPI/GetDataHandlers.cs:2772 msgid "GetDataHandlers / OnPlayerUpdate rejected from null player." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2464 +#: ../../TShockAPI/GetDataHandlers.cs:2499 msgid "" "GetDataHandlers / rejecting player for name prefix starting with tsi: or " "tsn:." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3492 +#: ../../TShockAPI/GetDataHandlers.cs:3533 #, csharp-format msgid "GetDataHandlers / UpdateNPCHome rejected no permission {0}" msgstr "" @@ -3801,7 +3793,7 @@ msgstr "" msgid "Hallow Palm" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4372 +#: ../../TShockAPI/GetDataHandlers.cs:4417 #, csharp-format msgid "" "HandleSyncCavernMonsterType: Player is trying to modify NPC " @@ -3903,7 +3895,7 @@ msgid "" "were disabled for to TShock so we can improve this!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3026 +#: ../../TShockAPI/GetDataHandlers.cs:3065 msgid "" "If this was not skeletron prime related, please report to TShock what " "happened." @@ -3964,7 +3956,7 @@ msgstr "" msgid "Ignoring shrapnel per config.." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2465 +#: ../../TShockAPI/GetDataHandlers.cs:2500 msgid "Illegal name: prefixes tsi: and tsn: are forbidden." msgstr "" @@ -4197,7 +4189,7 @@ msgid "" "Press any key to exit." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3231 +#: ../../TShockAPI/GetDataHandlers.cs:3271 msgid "Invalid server password." msgstr "" @@ -4660,7 +4652,7 @@ msgstr "" msgid "Item bans ({{0}}/{{1}}):" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1920 +#: ../../TShockAPI/TSPlayer.cs:2007 #, csharp-format msgid "Kicked {0} for : '{1}'" msgstr "" @@ -4669,7 +4661,7 @@ msgstr "" msgid "Kicked via web" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1919 +#: ../../TShockAPI/TSPlayer.cs:2006 #, csharp-format msgid "Kicked: {0}" msgstr "" @@ -4753,12 +4745,12 @@ msgstr "" msgid "List Online Players Syntax" msgstr "" -#: ../../TShockAPI/TShock.cs:828 +#: ../../TShockAPI/TShock.cs:864 #, csharp-format msgid "Listening on IP {0}." msgstr "" -#: ../../TShockAPI/TShock.cs:809 +#: ../../TShockAPI/TShock.cs:845 #, csharp-format msgid "Listening on port {0}." msgstr "" @@ -4775,7 +4767,7 @@ msgstr "" msgid "listusers - Lists all REST users and their current active tokens." msgstr "" -#: ../../TShockAPI/TShock.cs:798 +#: ../../TShockAPI/TShock.cs:834 #, csharp-format msgid "Loading dedicated config file: {0}" msgstr "" @@ -4793,7 +4785,7 @@ msgstr "" msgid "Log display enabled." msgstr "" -#: ../../TShockAPI/TShock.cs:785 +#: ../../TShockAPI/TShock.cs:821 #, csharp-format msgid "Log path has been set to {0}" msgstr "" @@ -4802,13 +4794,13 @@ msgstr "" msgid "Login attempt failed - see the message above." msgstr "" -#: ../../TShockAPI/TShock.cs:980 +#: ../../TShockAPI/TShock.cs:1016 msgid "" "Login before join enabled. Users may be prompted for an account specific " "password instead of a server password on connect." msgstr "" -#: ../../TShockAPI/TShock.cs:985 +#: ../../TShockAPI/TShock.cs:1021 msgid "Login using UUID enabled. Users automatically login via UUID." msgstr "" @@ -4928,7 +4920,7 @@ msgstr "" msgid "Mode: {0}" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1966 +#: ../../TShockAPI/TSPlayer.cs:2053 msgid "More than one match found -- unable to decide which is correct: " msgstr "" @@ -4977,7 +4969,7 @@ msgstr "" msgid "New name is too large!" msgstr "" -#: ../../TShockAPI/TShock.cs:864 +#: ../../TShockAPI/TShock.cs:900 #, csharp-format msgid "New worlds will be generated with the {0} world evil type!" msgstr "" @@ -5055,9 +5047,9 @@ msgid "" "token was not valid." msgstr "" +#: ../../TShockAPI/Rest/SecureRest.cs:180 #: ../../TShockAPI/Rest/RestCommand.cs:95 #: ../../TShockAPI/Rest/RestCommand.cs:101 -#: ../../TShockAPI/Rest/SecureRest.cs:180 msgid "Not authorized. The specified API endpoint requires a token." msgstr "" @@ -5197,8 +5189,8 @@ msgstr "" msgid "Player \"{0}\" is already logged in." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1883 -#: ../../TShockAPI/TSPlayer.cs:1887 +#: ../../TShockAPI/TSPlayer.cs:1970 +#: ../../TShockAPI/TSPlayer.cs:1974 #, csharp-format msgid "Player {0} has been disabled for {1}." msgstr "" @@ -5260,7 +5252,7 @@ msgstr "" msgid "Player not found. Unable to kick the player." msgstr "" -#: ../../TShockAPI/TShock.cs:1696 +#: ../../TShockAPI/TShock.cs:1732 #, csharp-format msgid "Please {0}register or {0}login to play!" msgstr "" @@ -5286,7 +5278,7 @@ msgstr "" msgid "Please use the following to create a permanent account for you." msgstr "" -#: ../../TShockAPI/TShock.cs:895 +#: ../../TShockAPI/TShock.cs:931 #, csharp-format msgid "Port overridden by startup argument. Set to {0}" msgstr "" @@ -5363,12 +5355,12 @@ msgstr "" msgid "Quick usage: {0} {1} \"Griefing\"" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:758 +#: ../../TShockAPI/TSPlayer.cs:759 #, csharp-format msgid "Rangecheck failed for {0} ({1}, {2}) (rg: {3}/{5}, {4}/{5})" msgstr "" -#: ../../TShockAPI/TShock.cs:1196 +#: ../../TShockAPI/TShock.cs:1232 msgid "Reached HealOtherPlayer threshold" msgstr "" @@ -5376,7 +5368,7 @@ msgstr "" msgid "Reached HealOtherPlayer threshold." msgstr "" -#: ../../TShockAPI/TShock.cs:1187 +#: ../../TShockAPI/TShock.cs:1223 msgid "Reached paint threshold" msgstr "" @@ -5384,16 +5376,16 @@ msgstr "" msgid "Reached projectile create threshold." msgstr "" -#: ../../TShockAPI/TShock.cs:1178 +#: ../../TShockAPI/TShock.cs:1214 msgid "Reached projectile threshold" msgstr "" +#: ../../TShockAPI/TShock.cs:1144 #: ../../TShockAPI/Bouncer.cs:922 -#: ../../TShockAPI/TShock.cs:1108 msgid "Reached TileKill threshold." msgstr "" -#: ../../TShockAPI/TShock.cs:1169 +#: ../../TShockAPI/TShock.cs:1205 msgid "Reached TileLiquid threshold" msgstr "" @@ -5406,7 +5398,7 @@ msgstr "" msgid "Reached TileLiquid threshold." msgstr "" -#: ../../TShockAPI/TShock.cs:1125 +#: ../../TShockAPI/TShock.cs:1161 msgid "Reached TilePlace threshold" msgstr "" @@ -5670,7 +5662,7 @@ msgstr "" msgid "Sends you to your spawn point." msgstr "" -#: ../../TShockAPI/TShock.cs:738 +#: ../../TShockAPI/TShock.cs:774 msgid "Server console interrupted!" msgstr "" @@ -5682,7 +5674,7 @@ msgstr "" msgid "Server is full. No reserved slots open." msgstr "" -#: ../../TShockAPI/TShock.cs:1300 +#: ../../TShockAPI/TShock.cs:1336 msgid "Server is shutting down..." msgstr "" @@ -5712,7 +5704,7 @@ msgstr "" msgid "Server side characters are enabled. You need to be logged-in to play." msgstr "" -#: ../../TShockAPI/TShock.cs:1691 +#: ../../TShockAPI/TShock.cs:1727 #, csharp-format msgid "" "Server side characters is enabled! Please {0}register or {0}login to play!" @@ -5841,7 +5833,7 @@ msgstr "" msgid "Shuts down the server without saving." msgstr "" -#: ../../TShockAPI/TShock.cs:735 +#: ../../TShockAPI/TShock.cs:771 msgid "Shutting down safely. To force shutdown, send SIGINT (CTRL + C) again." msgstr "" @@ -5858,7 +5850,7 @@ msgstr "" msgid "Skeletron Prime" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1069 +#: ../../TShockAPI/TSPlayer.cs:1076 #, csharp-format msgid "Skipping SSC save (due to tshock.ignore.ssc) for {0}" msgstr "" @@ -5939,67 +5931,67 @@ msgstr "" msgid "SSC must be enabled to use this command." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:434 +#: ../../TShockAPI/TSPlayer.cs:435 #, csharp-format msgid "Stack cheat detected. Remove armor {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:577 +#: ../../TShockAPI/TSPlayer.cs:578 #, csharp-format msgid "" "Stack cheat detected. Remove Defender's Forge item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:454 +#: ../../TShockAPI/TSPlayer.cs:455 #, csharp-format msgid "Stack cheat detected. Remove dye {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:414 -#: ../../TShockAPI/TSPlayer.cs:474 +#: ../../TShockAPI/TSPlayer.cs:415 +#: ../../TShockAPI/TSPlayer.cs:475 #, csharp-format msgid "Stack cheat detected. Remove item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:494 +#: ../../TShockAPI/TSPlayer.cs:495 #, csharp-format msgid "Stack cheat detected. Remove item dye {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:618 -#: ../../TShockAPI/TSPlayer.cs:638 +#: ../../TShockAPI/TSPlayer.cs:619 +#: ../../TShockAPI/TSPlayer.cs:639 #, csharp-format msgid "Stack cheat detected. Remove Loadout 1 item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:658 -#: ../../TShockAPI/TSPlayer.cs:678 +#: ../../TShockAPI/TSPlayer.cs:659 +#: ../../TShockAPI/TSPlayer.cs:679 #, csharp-format msgid "Stack cheat detected. Remove Loadout 2 item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:698 -#: ../../TShockAPI/TSPlayer.cs:718 +#: ../../TShockAPI/TSPlayer.cs:699 +#: ../../TShockAPI/TSPlayer.cs:719 #, csharp-format msgid "Stack cheat detected. Remove Loadout 3 item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:515 +#: ../../TShockAPI/TSPlayer.cs:516 #, csharp-format msgid "Stack cheat detected. Remove piggy-bank item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:536 +#: ../../TShockAPI/TSPlayer.cs:537 #, csharp-format msgid "Stack cheat detected. Remove safe item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:556 +#: ../../TShockAPI/TSPlayer.cs:557 #, csharp-format msgid "Stack cheat detected. Remove trash item {0} ({1}) and then rejoin." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:598 +#: ../../TShockAPI/TSPlayer.cs:599 #, csharp-format msgid "Stack cheat detected. Remove Void Vault item {0} ({1}) and then rejoin." msgstr "" @@ -6016,19 +6008,19 @@ msgstr "" msgid "Started an eclipse." msgstr "" -#: ../../TShockAPI/TShock.cs:927 +#: ../../TShockAPI/TShock.cs:963 msgid "Startup parameter overrode maximum player slot configuration value." msgstr "" -#: ../../TShockAPI/TShock.cs:909 +#: ../../TShockAPI/TShock.cs:945 msgid "Startup parameter overrode REST enable." msgstr "" -#: ../../TShockAPI/TShock.cs:918 +#: ../../TShockAPI/TShock.cs:954 msgid "Startup parameter overrode REST port." msgstr "" -#: ../../TShockAPI/TShock.cs:901 +#: ../../TShockAPI/TShock.cs:937 msgid "Startup parameter overrode REST token." msgstr "" @@ -6197,7 +6189,7 @@ msgstr "" msgid "the Destroyer" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3427 +#: ../../TShockAPI/GetDataHandlers.cs:3468 msgid "The Dungeon Guardian returned you to your spawn point." msgstr "" @@ -6431,8 +6423,8 @@ msgid_plural "These are the plugins you requested to install" msgstr[0] "" msgstr[1] "" -#: ../../TShockAPI/TShock.cs:1013 -#: ../../TShockAPI/TShock.cs:1023 +#: ../../TShockAPI/TShock.cs:1049 +#: ../../TShockAPI/TShock.cs:1059 #, csharp-format msgid "This token will display until disabled by verification. ({0}setup)" msgstr "" @@ -6486,8 +6478,8 @@ msgid "" "instead of {1}" msgstr "" -#: ../../TShockAPI/TShock.cs:1012 -#: ../../TShockAPI/TShock.cs:1022 +#: ../../TShockAPI/TShock.cs:1048 +#: ../../TShockAPI/TShock.cs:1058 #, csharp-format msgid "To setup the server, join the game and type {0}setup {1}" msgstr "" @@ -6554,7 +6546,7 @@ msgstr "" msgid "Tried to grow a {0}." msgstr "" -#: ../../TShockAPI/TShock.cs:399 +#: ../../TShockAPI/TShock.cs:403 #, csharp-format msgid "TShock {0} ({1}) now running." msgstr "" @@ -6563,29 +6555,29 @@ msgstr "" msgid "TShock Ban Help" msgstr "" -#: ../../TShockAPI/TShock.cs:449 +#: ../../TShockAPI/TShock.cs:480 msgid "TShock comes with no warranty & is free software." msgstr "" -#: ../../TShockAPI/TShock.cs:461 +#: ../../TShockAPI/TShock.cs:492 msgid "" "TShock encountered a problem from which it cannot recover. The following " "message may help diagnose the problem." msgstr "" -#: ../../TShockAPI/TShock.cs:1021 +#: ../../TShockAPI/TShock.cs:1057 msgid "" "TShock Notice: setup-code.txt is still present, and the code located in that " "file will be used." msgstr "" -#: ../../TShockAPI/TShock.cs:353 +#: ../../TShockAPI/TShock.cs:357 msgid "" "TShock was improperly shut down. Please use the exit command in the future to " "prevent this." msgstr "" -#: ../../TShockAPI/TShock.cs:997 +#: ../../TShockAPI/TShock.cs:1033 msgid "" "TShock will now disable the initial setup system and remove setup-code.txt as " "it is no longer needed." @@ -6740,11 +6732,11 @@ msgstr "" msgid "Unable to launch {0} because she is not logged in." msgstr "" -#: ../../TShockAPI/TShock.cs:1484 +#: ../../TShockAPI/TShock.cs:1520 msgid "Unable to parse command '{0}' from player {1}." msgstr "" -#: ../../TShockAPI/TShock.cs:1483 +#: ../../TShockAPI/TShock.cs:1519 msgid "" "Unable to parse command. Please contact an administrator for assistance." msgstr "" @@ -6788,7 +6780,7 @@ msgstr "" msgid "Unrecognized player direction" msgstr "" -#: ../../TShockAPI/TShock.cs:462 +#: ../../TShockAPI/TShock.cs:493 msgid "" "Until the problem is resolved, TShock will not be able to start (and will " "crash on startup)." @@ -6835,11 +6827,11 @@ msgstr "" msgid "Use \"{0}worldevent rain slime\" to start slime rain!" msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1971 +#: ../../TShockAPI/TSPlayer.cs:2058 msgid "Use \"my query\" for items with spaces." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1972 +#: ../../TShockAPI/TSPlayer.cs:2059 msgid "" "Use tsi:[number] or tsn:[username] to distinguish between user IDs and " "usernames." @@ -6897,8 +6889,8 @@ msgid "" "privileges." msgstr "" -#: ../../TShockAPI/TShock.cs:390 #: ../../TShockAPI/TShock.cs:394 +#: ../../TShockAPI/TShock.cs:398 #, csharp-format msgid "Using {0} for tile implementation" msgstr "" @@ -7012,7 +7004,7 @@ msgstr "" msgid "Warps ({{0}}/{{1}}):" msgstr "" -#: ../../TShockAPI/TShock.cs:448 +#: ../../TShockAPI/TShock.cs:479 msgid "Welcome to TShock for Terraria!" msgstr "" @@ -7042,12 +7034,12 @@ msgstr "" msgid "World mode set to {0}." msgstr "" -#: ../../TShockAPI/TShock.cs:818 +#: ../../TShockAPI/TShock.cs:854 #, csharp-format msgid "World name will be overridden by: {0}" msgstr "" -#: ../../TShockAPI/TShock.cs:775 +#: ../../TShockAPI/TShock.cs:811 #, csharp-format msgid "World path has been set to {0}" msgstr "" @@ -7078,7 +7070,7 @@ msgstr "" msgid "You are dead. Dead players can't go home." msgstr "" -#: ../../TShockAPI/TShock.cs:1501 +#: ../../TShockAPI/TShock.cs:1537 msgid "You are muted!" msgstr "" @@ -7089,7 +7081,7 @@ msgstr "" msgid "You are muted." msgstr "" -#: ../../TShockAPI/Commands.cs:6758 +#: ../../TShockAPI/Commands.cs:6754 #, csharp-format msgid "You are no longer in god mode." msgstr "" @@ -7114,7 +7106,7 @@ msgstr "" msgid "You are now being annoyed." msgstr "" -#: ../../TShockAPI/Commands.cs:6757 +#: ../../TShockAPI/Commands.cs:6753 #, csharp-format msgid "You are now in god mode." msgstr "" @@ -7132,7 +7124,7 @@ msgid_plural "You butchered {0} NPCs." msgstr[0] "" msgstr[1] "" -#: ../../TShockAPI/TShock.cs:450 +#: ../../TShockAPI/TShock.cs:481 msgid "You can modify & distribute it under the terms of the GNU GPLv3." msgstr "" @@ -7210,7 +7202,7 @@ msgstr "" msgid "You cannot spawn banned items." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3445 +#: ../../TShockAPI/GetDataHandlers.cs:3486 msgid "You cannot use the Enchanted Sundial because time is stopped." msgstr "" @@ -7243,7 +7235,7 @@ msgstr[1] "" msgid "You didn't put a player name." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4211 +#: ../../TShockAPI/GetDataHandlers.cs:4256 msgid "You died! Normally, you'd be banned." msgstr "" @@ -7252,15 +7244,15 @@ msgstr "" msgid "You do not have access to this command." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:828 +#: ../../TShockAPI/TSPlayer.cs:829 msgid "You do not have permission to build in the spawn point." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:831 +#: ../../TShockAPI/TSPlayer.cs:832 msgid "You do not have permission to build in this region." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:825 +#: ../../TShockAPI/TSPlayer.cs:826 msgid "You do not have permission to build on this server." msgstr "" @@ -7296,7 +7288,7 @@ msgstr "" msgid "You do not have permission to grow this tree type" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2960 +#: ../../TShockAPI/GetDataHandlers.cs:2999 msgid "You do not have permission to hurt Town NPCs." msgstr "" @@ -7357,7 +7349,7 @@ msgstr "" msgid "You do not have permission to place actuators." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3967 +#: ../../TShockAPI/GetDataHandlers.cs:4011 msgid "You do not have permission to place Logic Sensors." msgstr "" @@ -7366,7 +7358,7 @@ msgstr "" msgid "You do not have permission to place this tile." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3493 +#: ../../TShockAPI/GetDataHandlers.cs:3534 msgid "You do not have permission to relocate Town NPCs." msgstr "" @@ -7382,15 +7374,15 @@ msgstr "" msgid "You do not have permission to send emotes!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3532 +#: ../../TShockAPI/GetDataHandlers.cs:3573 msgid "You do not have permission to spawn pets." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4127 +#: ../../TShockAPI/GetDataHandlers.cs:4171 msgid "You do not have permission to start a party." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3525 +#: ../../TShockAPI/GetDataHandlers.cs:3566 msgid "You do not have permission to start invasions." msgstr "" @@ -7399,19 +7391,19 @@ msgstr "" msgid "You do not have permission to start the {0} event." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4145 +#: ../../TShockAPI/GetDataHandlers.cs:4189 msgid "You do not have permission to start the Old One's Army." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3518 +#: ../../TShockAPI/GetDataHandlers.cs:3559 msgid "You do not have permission to summon bosses." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2970 +#: ../../TShockAPI/GetDataHandlers.cs:3009 msgid "You do not have permission to summon the Empress of Light." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2987 +#: ../../TShockAPI/GetDataHandlers.cs:3026 msgid "You do not have permission to summon the Lunatic Cultist!" msgstr "" @@ -7427,12 +7419,12 @@ msgstr "" msgid "You do not have permission to teleport other players." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3833 +#: ../../TShockAPI/GetDataHandlers.cs:3876 #, csharp-format msgid "You do not have permission to teleport using {0}." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3745 +#: ../../TShockAPI/GetDataHandlers.cs:3788 msgid "You do not have permission to teleport using items." msgstr "" @@ -7440,7 +7432,7 @@ msgstr "" msgid "You do not have permission to teleport using pylons." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3769 +#: ../../TShockAPI/GetDataHandlers.cs:3812 msgid "You do not have permission to teleport using Wormhole Potions." msgstr "" @@ -7458,7 +7450,7 @@ msgid "" "server-side-character data." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3437 +#: ../../TShockAPI/GetDataHandlers.cs:3478 msgid "You do not have permission to use the Enchanted Sundial." msgstr "" @@ -7473,7 +7465,7 @@ msgstr "" msgid "You failed to change your password." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2458 +#: ../../TShockAPI/GetDataHandlers.cs:2493 msgid "You have been Bounced." msgstr "" @@ -7504,7 +7496,7 @@ msgstr "" msgid "You have changed {0}'s group to {1} for {2}" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:4226 +#: ../../TShockAPI/GetDataHandlers.cs:4271 msgid "" "You have fallen in hardcore mode, and your items have been lost forever." msgstr "" @@ -7573,11 +7565,11 @@ msgstr "" msgid "You launched fireworks on yourself." msgstr "" -#: ../../TShockAPI/TShock.cs:608 +#: ../../TShockAPI/TShock.cs:648 msgid "You logged in from another location." msgstr "" -#: ../../TShockAPI/TShock.cs:599 +#: ../../TShockAPI/TShock.cs:639 msgid "You logged in from the same IP." msgstr "" @@ -7585,7 +7577,7 @@ msgstr "" msgid "You may now receive whispers from other players." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2621 +#: ../../TShockAPI/GetDataHandlers.cs:2656 msgid "" "You may wish to consider removing the tshock.ignore.ssc permission or " "negating it for this player." @@ -7599,7 +7591,7 @@ msgstr "" msgid "You must provide a setup code!" msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3448 +#: ../../TShockAPI/GetDataHandlers.cs:3489 msgid "" "You must set ForceTime to normal via config to use the Enchanted Sundial." msgstr "" @@ -7612,15 +7604,15 @@ msgstr "" msgid "You must use this command in-game." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2523 +#: ../../TShockAPI/GetDataHandlers.cs:2558 msgid "You need to join with a hardcore player." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2517 +#: ../../TShockAPI/GetDataHandlers.cs:2552 msgid "You need to join with a mediumcore player or higher." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:2511 +#: ../../TShockAPI/GetDataHandlers.cs:2546 msgid "You need to join with a softcore player." msgstr "" @@ -7648,7 +7640,7 @@ msgstr "" msgid "You were teleported to {0}." msgstr "" -#: ../../TShockAPI/TShock.cs:1706 +#: ../../TShockAPI/TShock.cs:1742 msgid "You will be teleported to your last known location..." msgstr "" @@ -7684,7 +7676,7 @@ msgstr "" msgid "Your client sent a blank character name." msgstr "" -#: ../../TShockAPI/TShock.cs:1351 +#: ../../TShockAPI/TShock.cs:1387 msgid "" "Your client sent a blank UUID. Configure it to send one or use a different " "client." @@ -7721,7 +7713,7 @@ msgid "" "off." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3217 +#: ../../TShockAPI/GetDataHandlers.cs:3257 msgid "Your password did not match this character's password." msgstr "" @@ -7742,7 +7734,7 @@ msgstr "" msgid "Your server-side character data has been saved." msgstr "" -#: ../../TShockAPI/TSPlayer.cs:1325 +#: ../../TShockAPI/TSPlayer.cs:1342 msgid "Your temporary group access has expired." msgstr "" @@ -7750,7 +7742,7 @@ msgstr "" msgid "z <#> - Sets the z-order of the region." msgstr "" -#: ../../TShockAPI/GetDataHandlers.cs:3235 +#: ../../TShockAPI/GetDataHandlers.cs:3275 msgctxt "Likely non-vanilla client send zero-length password" msgid "You have been Bounced for invalid password." msgstr "" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 159ec1e22..000000000 --- a/renovate.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ], - "git-submodules": { - "enabled": true - }, - "packageRules": [ - { - "matchPackageNames": ["OTAPI.Upcoming", "ModFramework", "TerrariaServerAPI"], - "ignoreUnstable": "false", - "bumpVersion": "prerelease", - "groupName": "OTAPI things" - } - ] -}