From 59f9440d174504b69ec81f3368c1b501f0148fb4 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Mon, 12 Dec 2022 17:15:34 -0800 Subject: [PATCH 01/77] Use same TSPlayer.Active check --- TShockAPI/Bouncer.cs | 6 +++--- TShockAPI/Commands.cs | 8 ++++---- TShockAPI/Rest/RestManager.cs | 6 +++--- TShockAPI/Utils.cs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index affa1609..e86501b1 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1878,7 +1878,7 @@ namespace TShockAPI 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 +2081,7 @@ namespace TShockAPI 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 +2589,7 @@ namespace TShockAPI 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; diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 2214f252..74fa4f42 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -3070,12 +3070,12 @@ namespace TShockAPI 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.")); diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index c41e7767..b6efd6c5 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -402,7 +402,7 @@ namespace TShockAPI {"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")}, @@ -944,8 +944,8 @@ namespace TShockAPI [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/Utils.cs b/TShockAPI/Utils.cs index efe17130..a7291e73 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -172,7 +172,7 @@ namespace TShockAPI 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 @@ namespace TShockAPI /// 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 From 10aca8573dc2c3dd8ca7d3866bf6e5f0e676ff9a Mon Sep 17 00:00:00 2001 From: Stargazing Koishi Date: Tue, 7 Mar 2023 18:39:38 -0800 Subject: [PATCH 02/77] Remove Connection: Close for REST api fix #2923 --- TShockAPI/Rest/Rest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index 58cc23d8..a6e681e2 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -351,7 +351,6 @@ namespace Rests { 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); From 1948ad3d2cf960619bd9954bec87072d6aa207b1 Mon Sep 17 00:00:00 2001 From: Stargazing Koishi Date: Thu, 9 Mar 2023 21:01:09 -0800 Subject: [PATCH 03/77] Update changelog.md --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index be96f88c..8af0bada 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -106,6 +106,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Allowed multiple test cases to be in TShock's test suite. (@drunderscore) * Fixed unable to use Purification/Evil Powder in jungle. (@sgkoishi) * Set the `GetDataHandledEventArgs.Player` property for the `SyncTilePicking` data handler. (@drunderscore) +* Fixed unable to transfer long response body for REST API. (@sgkoishi, #2925) ## TShock 5.1.3 * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) From b714ab18a548ff38fea565fbdb35a9ef24eb80e7 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 4 Apr 2023 05:08:17 +0200 Subject: [PATCH 04/77] Completely rewrite STR handling --- TShockAPI/Bouncer.cs | 4 +- .../Handlers/SendTileRectHandlerRefactor.cs | 841 ++++++++++++++++++ 2 files changed, 843 insertions(+), 2 deletions(-) create mode 100644 TShockAPI/Handlers/SendTileRectHandlerRefactor.cs diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 6ff7fd1a..c7c72f96 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -35,7 +35,7 @@ namespace TShockAPI /// Bouncer is the TShock anti-hack and anti-cheat system. internal sealed class Bouncer { - internal Handlers.SendTileRectHandler STSHandler { get; private set; } + internal Handlers.SendTileRectHandlerRefactor STSHandler { get; private set; } internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; private set; } internal Handlers.EmojiHandler EmojiHandler { get; private set; } internal Handlers.IllegalPerSe.EmojiPlayerMismatch EmojiPlayerMismatch { get; private set; } @@ -83,7 +83,7 @@ namespace TShockAPI /// A new Bouncer. internal Bouncer() { - STSHandler = new Handlers.SendTileRectHandler(); + STSHandler = new Handlers.SendTileRectHandlerRefactor(); GetDataHandlers.SendTileRect += STSHandler.OnReceive; NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler(); diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs new file mode 100644 index 00000000..86048193 --- /dev/null +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -0,0 +1,841 @@ +using System.Collections.Generic; +using System.IO; + +using Terraria; +using Terraria.ID; + +using TShockAPI.Net; + +namespace TShockAPI.Handlers +{ + /// + /// 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 sealed class SendTileRectHandlerRefactor : IPacketHandler + { + /// + /// Represents a tile rectangle sent through the packet. + /// + private sealed class TileRect + { + 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) + { + _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) + { + 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); + } + } + + /// + /// Represents a common tile rect operation (Placement, State Change, Removal). + /// + private readonly struct TileRectMatch + { + public const short IGNORE_FRAME = -1; + + private enum MatchType + { + Placement, + StateChange, + Removal, + } + + private readonly int Width; + private readonly int Height; + + private readonly ushort TileType; + private readonly short MaxFrameX; + private readonly short MaxFrameY; + + private readonly MatchType Type; + + private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY) + { + Type = type; + Width = width; + Height = height; + TileType = tileType; + MaxFrameX = maxFrameX; + MaxFrameY = maxFrameY; + } + + /// + /// 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, or if this operation does not change frameX. + /// The maximum allowed frameY of the placement, or if this operation does not change frameY. + /// The resulting operation match. + public static TileRectMatch Placement(int width, int height, ushort tileType, short maxFrameX, short maxFrameY) + { + return new TileRectMatch(MatchType.Placement, width, height, tileType, maxFrameX, maxFrameY); + } + + /// + /// 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, or if this operation does not change frameX. + /// The maximum allowed frameY of the state change, or if this operation does not change frameY. + /// The resulting operation match. + public static TileRectMatch StateChange(int width, int height, ushort tileType, short maxFrameX, short maxFrameY) + { + return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrameX, maxFrameY); + } + + /// + /// 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); + } + + /// + /// 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) + { + if (rect.Width != Width || rect.Height != Height) + { + return false; + } + + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + 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) + { + return false; + } + } + if (MaxFrameY != IGNORE_FRAME) + { + if (tile.FrameY < 0 || tile.FrameY > MaxFrameY) + { + return false; + } + } + } + if (Type == MatchType.Removal) + { + if (tile.Active) + { + return false; + } + } + } + } + + for (int x = rect.X; x < rect.X + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) + { + 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; + } + + 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++) + { + if (Main.tile[x, y].active() && !(Main.tile[x, y].type != TileID.RollingCactus && (Main.tileCut[Main.tile[x, y].type] || TileID.Sets.BreakableWhenPlacing[Main.tile[x, y].type]))) + { + 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; + } + + 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; + if (MaxFrameX != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; + } + if (MaxFrameY != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; + } + } + } + + return true; + } + + 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++) + { + if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType) + { + return false; + } + } + } + + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + if (MaxFrameX != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; + } + if (MaxFrameY != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; + } + } + } + + return true; + } + + 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) + { + return false; + } + } + } + + 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; + } + } + + /// + /// Contains the complete list of valid tile rect operations the game currently performs. + /// + private static readonly TileRectMatch[] Matches = new TileRectMatch[] + { + TileRectMatch.Placement(2, 3, TileID.TargetDummy, 54, 36), + TileRectMatch.Placement(3, 4, TileID.TeleportationPylon, 468, 54), + TileRectMatch.Placement(2, 3, TileID.DisplayDoll, 126, 36), + TileRectMatch.Placement(2, 3, TileID.HatRack, 90, 54), + TileRectMatch.Placement(2, 2, TileID.ItemFrame, 162, 18), + TileRectMatch.Placement(3, 3, TileID.WeaponsRack2, 90, 36), + TileRectMatch.Placement(1, 1, TileID.FoodPlatter, 18, 0), + TileRectMatch.Placement(1, 1, TileID.LogicSensor, 108, 0), + + TileRectMatch.StateChange(3, 2, TileID.Campfire, TileRectMatch.IGNORE_FRAME, 54), + TileRectMatch.StateChange(4, 3, TileID.Cannon, TileRectMatch.IGNORE_FRAME, 468), + TileRectMatch.StateChange(2, 2, TileID.ArrowSign, TileRectMatch.IGNORE_FRAME, 270), + TileRectMatch.StateChange(2, 2, TileID.PaintedArrowSign, TileRectMatch.IGNORE_FRAME, 270), + TileRectMatch.StateChange(2, 2, TileID.MusicBoxes, 54, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(2, 3, TileID.LunarMonolith, TileRectMatch.IGNORE_FRAME, 92), + TileRectMatch.StateChange(2, 3, TileID.BloodMoonMonolith, TileRectMatch.IGNORE_FRAME, 90), + TileRectMatch.StateChange(2, 3, TileID.VoidMonolith, TileRectMatch.IGNORE_FRAME, 90), + TileRectMatch.StateChange(2, 3, TileID.EchoMonolith, TileRectMatch.IGNORE_FRAME, 90), + TileRectMatch.StateChange(2, 3, TileID.ShimmerMonolith, TileRectMatch.IGNORE_FRAME, 144), + TileRectMatch.StateChange(2, 4, TileID.WaterFountain, TileRectMatch.IGNORE_FRAME, 126), + TileRectMatch.StateChange(1, 1, TileID.Candles, 18, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.PeaceCandle, 18, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.WaterCandle, 18, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.PlatinumCandle, 18, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.ShadowCandle, 18, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.Traps, 90, 90), + TileRectMatch.StateChange(1, 1, TileID.WirePipe, 36, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.ProjectilePressurePad, 66, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.Plants, 792, TileRectMatch.IGNORE_FRAME), + TileRectMatch.StateChange(1, 1, TileID.MinecartTrack, 36, TileRectMatch.IGNORE_FRAME), + + TileRectMatch.Removal(1, 2, TileID.Firework), + TileRectMatch.Removal(1, 1, TileID.LandMine), + }; + + + /// + /// Handles a packet receive event. + /// + public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) + { + // this permission bypasses all checks for direct access to the world + if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) + { + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}")); + + // use vanilla handling + args.Handled = false; + return; + } + + // this handler handles the entire logic of this packet + args.Handled = true; + + // 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; + } + + // player disabled? + if (args.Player.IsBeingDisabled()) + { + 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; + } + + // 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 from non-vanilla tilemod from {args.Player.Name}")); + + // definitely invalid; do not send any correcting data + return; + } + + // 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)) + { + 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; + } + + // 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; + } + + // check if the distance is valid + if (!IsRectDistanceValid(args.Player, rect)) + { + 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; + } + + // a very special case, due to the flower seed check otherwise hijacking this + if (MatchesFlowerBoots(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; + } + + // check if the rect matches any valid operation + foreach (TileRectMatch match in Matches) + { + if (match.Matches(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; + } + } + + // a few special cases + if ( + MatchesConversionSpread(args.Player, rect) || + MatchesGrassMow(args.Player, rect) || + MatchesChristmasTree(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; + } + + 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; + } + + /// + /// Checks whether the tile rect is at a valid position for the given player. + /// + /// 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) + { + for (int x = 0; x < rect.Width; x++) + { + 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; + } + } + } + + return true; + } + + /// + /// Checks whether the tile rect is at a valid distance to the given player. + /// + /// 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) + { + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + int realX = rect.X + x; + int realY = rect.Y + y; + + if (!player.IsInRange(realX, realY)) + { + return false; + } + } + } + + return true; + } + + + /// + /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.) + /// + /// 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) + { + if (rect.Width != 1 || rect.Height != 1) + { + return false; + } + + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; + + bool matchedTileOrWall = false; + + if (oldTile.active()) + { + if ( + ( + (TileID.Sets.Conversion.Stone[oldTile.type] || Main.tileMoss[oldTile.type]) && + (TileID.Sets.Conversion.Stone[newTile.Type] || Main.tileMoss[newTile.Type]) + ) || + ( + (oldTile.type == TileID.Dirt || oldTile.type == TileID.Mud) && + (newTile.Type == TileID.Dirt || newTile.Type == TileID.Mud) + ) || + TileID.Sets.Conversion.Grass[oldTile.type] && TileID.Sets.Conversion.Grass[newTile.Type] || + TileID.Sets.Conversion.Ice[oldTile.type] && TileID.Sets.Conversion.Ice[newTile.Type] || + TileID.Sets.Conversion.Sand[oldTile.type] && TileID.Sets.Conversion.Sand[newTile.Type] || + TileID.Sets.Conversion.Sandstone[oldTile.type] && TileID.Sets.Conversion.Sandstone[newTile.Type] || + TileID.Sets.Conversion.HardenedSand[oldTile.type] && TileID.Sets.Conversion.HardenedSand[newTile.Type] || + TileID.Sets.Conversion.Thorn[oldTile.type] && TileID.Sets.Conversion.Thorn[newTile.Type] || + TileID.Sets.Conversion.Moss[oldTile.type] && TileID.Sets.Conversion.Moss[newTile.Type] || + TileID.Sets.Conversion.MossBrick[oldTile.type] && TileID.Sets.Conversion.MossBrick[newTile.Type] + ) + { + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + matchedTileOrWall = true; + } + else if (!player.HasBuildPermission(rect.X, rect.Y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + matchedTileOrWall = 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; + + matchedTileOrWall = true; + } + } + } + + if (oldTile.wall != 0) + { + if ( + WallID.Sets.Conversion.Stone[oldTile.wall] && WallID.Sets.Conversion.Stone[newTile.Wall] || + WallID.Sets.Conversion.Grass[oldTile.wall] && WallID.Sets.Conversion.Grass[newTile.Wall] || + WallID.Sets.Conversion.Sandstone[oldTile.wall] && WallID.Sets.Conversion.Sandstone[newTile.Wall] || + WallID.Sets.Conversion.HardenedSand[oldTile.wall] && WallID.Sets.Conversion.HardenedSand[newTile.Wall] || + WallID.Sets.Conversion.PureSand[oldTile.wall] && WallID.Sets.Conversion.PureSand[newTile.Wall] || + WallID.Sets.Conversion.NewWall1[oldTile.wall] && WallID.Sets.Conversion.NewWall1[newTile.Wall] || + WallID.Sets.Conversion.NewWall2[oldTile.wall] && WallID.Sets.Conversion.NewWall2[newTile.Wall] || + WallID.Sets.Conversion.NewWall3[oldTile.wall] && WallID.Sets.Conversion.NewWall3[newTile.Wall] || + WallID.Sets.Conversion.NewWall4[oldTile.wall] && WallID.Sets.Conversion.NewWall4[newTile.Wall] + ) + { + // wallbans when? + + if (!player.HasBuildPermission(rect.X, rect.Y)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + matchedTileOrWall = true; + } + else + { + Main.tile[rect.X, rect.Y].wall = newTile.Wall; + + matchedTileOrWall = true; + } + } + } + + return matchedTileOrWall; + } + + + private static readonly Dictionary> PlantToGrassMap = new Dictionary> + { + { 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() + { + TileID.JungleGrass + } }, + { TileID.AshPlants, new HashSet() + { + TileID.AshGrass + } }, + }; + + private static readonly Dictionary> GrassToStyleMap = new Dictionary>() + { + { TileID.Plants, new HashSet() + { + 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, + } }, + }; + + /// + /// 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) + { + return false; + } + + if (!player.TPlayer.flowerBoots) + { + return false; + } + + 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)) + ) + { + 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 }, + }; + + /// + /// Checks whether the tile rect is a valid grass mow. + /// + /// 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) + { + if (rect.Width != 1 || rect.Height != 1) + { + 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) + { + 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; + } + + + /// + /// Checks whether the tile rect is a valid christmas tree modification. + /// This also required significant reverse engineering effort. + /// + /// 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) + { + if (rect.Width != 1 || rect.Height != 1) + { + 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)) + { + // 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 false; + } + } +} From 2fdf096ce8104a1a77bbc0a7394fddb1634f75ff Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 4 Apr 2023 05:10:37 +0200 Subject: [PATCH 05/77] Update changelog --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index c4a95d14..48c965d5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -109,7 +109,7 @@ 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) +* Re-wrote tile rect handling from scratch, fixing a certain exploitable flaw in the old code and significantly reducing the potential exploit surface, potentially even down to zero. (@punchready) ## TShock 5.1.3 * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) From 3302b4653ea986bcd6a8d7f813430fb1e6514031 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 4 Apr 2023 05:51:53 +0200 Subject: [PATCH 06/77] Update STR checks to be even more strict --- .../Handlers/SendTileRectHandlerRefactor.cs | 125 ++++++++++++------ 1 file changed, 85 insertions(+), 40 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs index 86048193..9b8b0096 100644 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -69,7 +69,7 @@ namespace TShockAPI.Handlers /// private readonly struct TileRectMatch { - public const short IGNORE_FRAME = -1; + private const short IGNORE_FRAME = -1; private enum MatchType { @@ -84,10 +84,12 @@ namespace TShockAPI.Handlers private readonly ushort TileType; private readonly short MaxFrameX; private readonly short MaxFrameY; + private readonly short FrameXStep; + private readonly short FrameYStep; private readonly MatchType Type; - private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY) + private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) { Type = type; Width = width; @@ -95,6 +97,8 @@ namespace TShockAPI.Handlers TileType = tileType; MaxFrameX = maxFrameX; MaxFrameY = maxFrameY; + FrameXStep = frameXStep; + FrameYStep = frameYStep; } /// @@ -105,10 +109,12 @@ namespace TShockAPI.Handlers /// The tile type of the placement. /// The maximum allowed frameX of the placement, or if this operation does not change frameX. /// The maximum allowed frameY of the placement, or if this operation does not change frameY. + /// 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) + 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); + return new TileRectMatch(MatchType.Placement, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep); } /// @@ -117,12 +123,42 @@ namespace TShockAPI.Handlers /// 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, or if this operation does not change frameX. - /// The maximum allowed frameY of the state change, or if this operation does not change frameY. + /// 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) + 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); + return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep); + } + + /// + /// 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); + } + + /// + /// 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) + { + return new TileRectMatch(MatchType.StateChange, width, height, tileType, IGNORE_FRAME, maxFrame, 0, frameStep); } /// @@ -134,7 +170,7 @@ namespace TShockAPI.Handlers /// The resulting operation match. public static TileRectMatch Removal(int width, int height, ushort tileType) { - return new TileRectMatch(MatchType.Removal, width, height, tileType, 0, 0); + return new TileRectMatch(MatchType.Removal, width, height, tileType, 0, 0, 0, 0); } /// @@ -166,14 +202,14 @@ namespace TShockAPI.Handlers { if (MaxFrameX != IGNORE_FRAME) { - if (tile.FrameX < 0 || tile.FrameX > MaxFrameX) + if (tile.FrameX < 0 || tile.FrameX > MaxFrameX || tile.FrameX % FrameXStep != 0) { return false; } } if (MaxFrameY != IGNORE_FRAME) { - if (tile.FrameY < 0 || tile.FrameY > MaxFrameY) + if (tile.FrameY < 0 || tile.FrameY > MaxFrameY || tile.FrameY % FrameYStep != 0) { return false; } @@ -321,38 +357,47 @@ namespace TShockAPI.Handlers /// /// 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), - TileRectMatch.Placement(3, 4, TileID.TeleportationPylon, 468, 54), - TileRectMatch.Placement(2, 3, TileID.DisplayDoll, 126, 36), - TileRectMatch.Placement(2, 3, TileID.HatRack, 90, 54), - TileRectMatch.Placement(2, 2, TileID.ItemFrame, 162, 18), - TileRectMatch.Placement(3, 3, TileID.WeaponsRack2, 90, 36), - TileRectMatch.Placement(1, 1, TileID.FoodPlatter, 18, 0), - TileRectMatch.Placement(1, 1, TileID.LogicSensor, 108, 0), + 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.StateChange(3, 2, TileID.Campfire, TileRectMatch.IGNORE_FRAME, 54), - TileRectMatch.StateChange(4, 3, TileID.Cannon, TileRectMatch.IGNORE_FRAME, 468), - TileRectMatch.StateChange(2, 2, TileID.ArrowSign, TileRectMatch.IGNORE_FRAME, 270), - TileRectMatch.StateChange(2, 2, TileID.PaintedArrowSign, TileRectMatch.IGNORE_FRAME, 270), - TileRectMatch.StateChange(2, 2, TileID.MusicBoxes, 54, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(2, 3, TileID.LunarMonolith, TileRectMatch.IGNORE_FRAME, 92), - TileRectMatch.StateChange(2, 3, TileID.BloodMoonMonolith, TileRectMatch.IGNORE_FRAME, 90), - TileRectMatch.StateChange(2, 3, TileID.VoidMonolith, TileRectMatch.IGNORE_FRAME, 90), - TileRectMatch.StateChange(2, 3, TileID.EchoMonolith, TileRectMatch.IGNORE_FRAME, 90), - TileRectMatch.StateChange(2, 3, TileID.ShimmerMonolith, TileRectMatch.IGNORE_FRAME, 144), - TileRectMatch.StateChange(2, 4, TileID.WaterFountain, TileRectMatch.IGNORE_FRAME, 126), - TileRectMatch.StateChange(1, 1, TileID.Candles, 18, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.PeaceCandle, 18, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.WaterCandle, 18, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.PlatinumCandle, 18, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.ShadowCandle, 18, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.Traps, 90, 90), - TileRectMatch.StateChange(1, 1, TileID.WirePipe, 36, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.ProjectilePressurePad, 66, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.Plants, 792, TileRectMatch.IGNORE_FRAME), - TileRectMatch.StateChange(1, 1, TileID.MinecartTrack, 36, TileRectMatch.IGNORE_FRAME), + 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), From 26482da23f70c8b22fa00b5ccdc853bc4e0e0f68 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 4 Apr 2023 06:02:55 +0200 Subject: [PATCH 07/77] Remove frame ignoring from tile rect placement operations --- TShockAPI/Handlers/SendTileRectHandlerRefactor.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs index 9b8b0096..a3c87100 100644 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -107,8 +107,8 @@ namespace TShockAPI.Handlers /// The width of the placement. /// The height of the placement. /// The tile type of the placement. - /// The maximum allowed frameX of the placement, or if this operation does not change frameX. - /// The maximum allowed frameY of the placement, or if this operation does not change frameY. + /// 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. @@ -282,14 +282,8 @@ namespace TShockAPI.Handlers { Main.tile[x + rect.X, y + rect.Y].active(active: true); Main.tile[x + rect.X, y + rect.Y].type = rect[x, y].Type; - if (MaxFrameX != IGNORE_FRAME) - { - Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; - } - if (MaxFrameY != IGNORE_FRAME) - { - Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; - } + 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; } } From d0409db5fb0774a9787627e92d14037e7b696712 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 4 Apr 2023 06:16:44 +0200 Subject: [PATCH 08/77] Never send back too large tile rects in handling --- .../Handlers/SendTileRectHandlerRefactor.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs index a3c87100..1472423f 100644 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -416,6 +416,16 @@ namespace TShockAPI.Handlers // 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 from size from {args.Player.Name}")); + + // definitely invalid; do not send any correcting data + return; + } + // player throttled? if (args.Player.IsBouncerThrottled()) { @@ -436,16 +446,6 @@ namespace TShockAPI.Handlers return; } - // 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 from non-vanilla tilemod from {args.Player.Name}")); - - // definitely invalid; do not send any correcting data - return; - } - // read the tile rectangle TileRect rect = TileRect.Read(args.Data, args.TileX, args.TileY, args.Width, args.Length); From c309990f9488af07939111e5bc153cdcd571e25b Mon Sep 17 00:00:00 2001 From: punchready Date: Wed, 5 Apr 2023 06:43:47 +0200 Subject: [PATCH 09/77] Fix LunarMonolith toggling --- TShockAPI/Handlers/SendTileRectHandlerRefactor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs index 1472423f..b3a2d1e7 100644 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -211,10 +211,14 @@ namespace TShockAPI.Handlers { 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) From b57eb91230c9cff20c2249236355953a99339ba7 Mon Sep 17 00:00:00 2001 From: punchready Date: Wed, 5 Apr 2023 06:44:21 +0200 Subject: [PATCH 10/77] Rewrite conversion spread handling to be much more accurate --- .../Handlers/SendTileRectHandlerRefactor.cs | 576 ++++++++++++++++-- 1 file changed, 511 insertions(+), 65 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs index b3a2d1e7..34f8a495 100644 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs @@ -214,11 +214,11 @@ namespace TShockAPI.Handlers // 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; + return false; + } } } } - } if (Type == MatchType.Removal) { if (tile.Active) @@ -578,7 +578,7 @@ namespace TShockAPI.Handlers /// - /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.) + /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.). /// /// The player the operation originates from. /// The tile rectangle of the operation. @@ -593,81 +593,48 @@ namespace TShockAPI.Handlers ITile oldTile = Main.tile[rect.X, rect.Y]; NetTile newTile = rect[0, 0]; - bool matchedTileOrWall = false; + WorldGenMock.SimulateConversionChange(rect.X, rect.Y, out HashSet validTiles, out HashSet validWalls); - if (oldTile.active()) + if (newTile.Type != oldTile.type && validTiles.Contains(newTile.Type)) { - if ( - ( - (TileID.Sets.Conversion.Stone[oldTile.type] || Main.tileMoss[oldTile.type]) && - (TileID.Sets.Conversion.Stone[newTile.Type] || Main.tileMoss[newTile.Type]) - ) || - ( - (oldTile.type == TileID.Dirt || oldTile.type == TileID.Mud) && - (newTile.Type == TileID.Dirt || newTile.Type == TileID.Mud) - ) || - TileID.Sets.Conversion.Grass[oldTile.type] && TileID.Sets.Conversion.Grass[newTile.Type] || - TileID.Sets.Conversion.Ice[oldTile.type] && TileID.Sets.Conversion.Ice[newTile.Type] || - TileID.Sets.Conversion.Sand[oldTile.type] && TileID.Sets.Conversion.Sand[newTile.Type] || - TileID.Sets.Conversion.Sandstone[oldTile.type] && TileID.Sets.Conversion.Sandstone[newTile.Type] || - TileID.Sets.Conversion.HardenedSand[oldTile.type] && TileID.Sets.Conversion.HardenedSand[newTile.Type] || - TileID.Sets.Conversion.Thorn[oldTile.type] && TileID.Sets.Conversion.Thorn[newTile.Type] || - TileID.Sets.Conversion.Moss[oldTile.type] && TileID.Sets.Conversion.Moss[newTile.Type] || - TileID.Sets.Conversion.MossBrick[oldTile.type] && TileID.Sets.Conversion.MossBrick[newTile.Type] - ) + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) { - if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) - { - // for simplicity, let's pretend that the edit was valid, but do not execute it - matchedTileOrWall = true; - } - else if (!player.HasBuildPermission(rect.X, rect.Y)) - { - // for simplicity, let's pretend that the edit was valid, but do not execute it - matchedTileOrWall = 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; + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + else 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].type = newTile.Type; + Main.tile[rect.X, rect.Y].frameX = newTile.FrameX; + Main.tile[rect.X, rect.Y].frameY = newTile.FrameY; - matchedTileOrWall = true; - } + return true; } } - if (oldTile.wall != 0) + if (newTile.Wall != oldTile.wall && validWalls.Contains(newTile.Wall)) { - if ( - WallID.Sets.Conversion.Stone[oldTile.wall] && WallID.Sets.Conversion.Stone[newTile.Wall] || - WallID.Sets.Conversion.Grass[oldTile.wall] && WallID.Sets.Conversion.Grass[newTile.Wall] || - WallID.Sets.Conversion.Sandstone[oldTile.wall] && WallID.Sets.Conversion.Sandstone[newTile.Wall] || - WallID.Sets.Conversion.HardenedSand[oldTile.wall] && WallID.Sets.Conversion.HardenedSand[newTile.Wall] || - WallID.Sets.Conversion.PureSand[oldTile.wall] && WallID.Sets.Conversion.PureSand[newTile.Wall] || - WallID.Sets.Conversion.NewWall1[oldTile.wall] && WallID.Sets.Conversion.NewWall1[newTile.Wall] || - WallID.Sets.Conversion.NewWall2[oldTile.wall] && WallID.Sets.Conversion.NewWall2[newTile.Wall] || - WallID.Sets.Conversion.NewWall3[oldTile.wall] && WallID.Sets.Conversion.NewWall3[newTile.Wall] || - WallID.Sets.Conversion.NewWall4[oldTile.wall] && WallID.Sets.Conversion.NewWall4[newTile.Wall] - ) + // wallbans when? + + if (!player.HasBuildPermission(rect.X, rect.Y)) { - // wallbans when? + // 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; - if (!player.HasBuildPermission(rect.X, rect.Y)) - { - // for simplicity, let's pretend that the edit was valid, but do not execute it - matchedTileOrWall = true; - } - else - { - Main.tile[rect.X, rect.Y].wall = newTile.Wall; - - matchedTileOrWall = true; - } + return true; } } - return matchedTileOrWall; + return false; } @@ -881,4 +848,483 @@ namespace TShockAPI.Handlers return false; } } + + /// + /// This helper class allows simulating a `WorldGen.Convert` call and retrieving all valid changes for a given tile. + /// + internal static class WorldGenMock + { + /// + /// This is a mock tile which collects all possible changes the `WorldGen.Convert` code could make in its property setters. + /// + private sealed class MockTile + { + private readonly HashSet _setTypes; + private readonly HashSet _setWalls; + + private ushort _type; + private ushort _wall; + + public MockTile(ushort type, ushort wall, HashSet setTypes, HashSet setWalls) + { + _setTypes = setTypes; + _setWalls = setWalls; + _type = type; + _wall = wall; + } + +#pragma warning disable IDE1006 + + public ushort type + { + get => _type; + set + { + _setTypes.Add(value); + _type = value; + } + } + + public ushort wall + { + get => _wall; + set + { + _setWalls.Add(value); + _wall = value; + } + } + +#pragma warning restore IDE1006 + } + + /// + /// 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. + /// + public static void SimulateConversionChange(int x, int y, out HashSet validTiles, out HashSet validWalls) + { + validTiles = new HashSet(); + validWalls = new HashSet(); + + // 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); + } + } + + /* + * 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) + { + int type = tile.type; + int wall = tile.wall; + switch (conversionType) + { + case 4: + if (WallID.Sets.Conversion.Grass[wall] && wall != 81) + { + tile.wall = 81; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 83) + { + 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)) + { + num = 397; + } + 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)) + { + num2 = 2; + } + tile.type = (ushort)num2; + } + break; + } + if (tile.wall == 69 || tile.wall == 70 || tile.wall == 81) + { + if (l < Main.worldSurface) + { + tile.wall = 65; + tile.wall = 63; + } + 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; + } + } + } } From 823d942b4728b34dcb380572600452f8ddfde888 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Fri, 28 Apr 2023 15:55:14 -0700 Subject: [PATCH 11/77] Update changelog --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index c4a95d14..88a8c518 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -110,6 +110,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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) +* Changed the use of `Player.active` to `TSPlayer.Active` for consistency. (@sgkoishi, #2939) ## TShock 5.1.3 * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) From 0dd15277e471a00e2a714bf1710698ecfc514fee Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 9 May 2023 12:41:54 +0200 Subject: [PATCH 12/77] Remove old STR handler --- TShockAPI/Bouncer.cs | 4 +- TShockAPI/Handlers/SendTileRectHandler.cs | 1858 +++++++++++------ .../Handlers/SendTileRectHandlerRefactor.cs | 1330 ------------ 3 files changed, 1226 insertions(+), 1966 deletions(-) delete mode 100644 TShockAPI/Handlers/SendTileRectHandlerRefactor.cs diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index c7c72f96..6ff7fd1a 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -35,7 +35,7 @@ namespace TShockAPI /// Bouncer is the TShock anti-hack and anti-cheat system. internal sealed class Bouncer { - internal Handlers.SendTileRectHandlerRefactor STSHandler { get; private set; } + internal Handlers.SendTileRectHandler STSHandler { get; private set; } internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; private set; } internal Handlers.EmojiHandler EmojiHandler { get; private set; } internal Handlers.IllegalPerSe.EmojiPlayerMismatch EmojiPlayerMismatch { get; private set; } @@ -83,7 +83,7 @@ namespace TShockAPI /// A new Bouncer. internal Bouncer() { - STSHandler = new Handlers.SendTileRectHandlerRefactor(); + STSHandler = new Handlers.SendTileRectHandler(); GetDataHandlers.SendTileRect += STSHandler.OnReceive; NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler(); diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 80d5a47c..64d9d8a7 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -1,26 +1,644 @@ -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 + { + 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) + { + _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) + { + 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); + } + } + + /// + /// Represents a common tile rect operation (Placement, State Change, Removal). + /// + private readonly struct TileRectMatch + { + private const short IGNORE_FRAME = -1; + + private enum MatchType + { + Placement, + StateChange, + Removal, + } + + private readonly int Width; + private readonly int Height; + + private readonly ushort TileType; + private readonly short MaxFrameX; + private readonly short MaxFrameY; + private readonly short FrameXStep; + private readonly short FrameYStep; + + private readonly MatchType Type; + + private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) + { + Type = type; + Width = width; + Height = height; + TileType = tileType; + MaxFrameX = maxFrameX; + MaxFrameY = maxFrameY; + FrameXStep = frameXStep; + FrameYStep = frameYStep; + } + + /// + /// 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); + } + + /// + /// 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); + } + + /// + /// 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); + } + + /// + /// 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) + { + return new TileRectMatch(MatchType.StateChange, width, height, tileType, IGNORE_FRAME, maxFrame, 0, frameStep); + } + + /// + /// 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); + } + + /// + /// 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) + { + if (rect.Width != Width || rect.Height != Height) + { + return false; + } + + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + 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; + } + } + } + } + + for (int x = rect.X; x < rect.X + rect.Width; x++) + { + for (int y = rect.Y; y < rect.Y + rect.Height; y++) + { + 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; + } + + 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++) + { + if (Main.tile[x, y].active() && !(Main.tile[x, y].type != TileID.RollingCactus && (Main.tileCut[Main.tile[x, y].type] || TileID.Sets.BreakableWhenPlacing[Main.tile[x, y].type]))) + { + 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; + } + + 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; + } + } + + return true; + } + + 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++) + { + if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType) + { + return false; + } + } + } + + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + if (MaxFrameX != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; + } + if (MaxFrameY != IGNORE_FRAME) + { + Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; + } + } + } + + return true; + } + + 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) + { + return false; + } + } + } + + 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; + } + } + + /// + /// 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. + /// + public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) + { + // this permission bypasses all checks for direct access to the world + if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) + { + TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}")); + + // use vanilla handling + args.Handled = false; + return; + } + + // 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 from size from {args.Player.Name}")); + + // definitely invalid; do not send any correcting data + return; + } + + // 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; + } + + // player disabled? + if (args.Player.IsBeingDisabled()) + { + 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; + } + + // 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)) + { + 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; + } + + // 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; + } + + // check if the distance is valid + if (!IsRectDistanceValid(args.Player, rect)) + { + 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; + } + + // a very special case, due to the flower seed check otherwise hijacking this + if (MatchesFlowerBoots(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; + } + + // check if the rect matches any valid operation + foreach (TileRectMatch match in Matches) + { + if (match.Matches(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; + } + } + + // a few special cases + if ( + MatchesConversionSpread(args.Player, rect) || + MatchesGrassMow(args.Player, rect) || + MatchesChristmasTree(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; + } + + 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; + } + + /// + /// Checks whether the tile rect is at a valid position for the given player. + /// + /// 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) + { + for (int x = 0; x < rect.Width; x++) + { + 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; + } + } + } + + return true; + } + + /// + /// Checks whether the tile rect is at a valid distance to the given player. + /// + /// 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) + { + for (int x = 0; x < rect.Width; x++) + { + for (int y = 0; y < rect.Height; y++) + { + int realX = rect.X + x; + int realY = rect.Y + y; + + if (!player.IsInRange(realX, realY)) + { + return false; + } + } + } + + return true; + } + + + /// + /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.). + /// + /// 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) + { + if (rect.Width != 1 || rect.Height != 1) + { + return false; + } + + 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)) + { + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; + } + else 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].type = newTile.Type; + Main.tile[rect.X, rect.Y].frameX = newTile.FrameX; + Main.tile[rect.X, rect.Y].frameY = newTile.FrameY; + + return true; + } + } + + if (newTile.Wall != oldTile.wall && validWalls.Contains(newTile.Wall)) + { + // 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; + } + } + + return false; + } + + + private static readonly Dictionary> PlantToGrassMap = new Dictionary> { { TileID.Plants, new HashSet() { @@ -44,27 +662,19 @@ namespace TShockAPI.Handlers } }, }; - /// - /// 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` - /// - private static readonly Dictionary> FlowerBootPlantToStyleMap = new Dictionary>() + private static readonly Dictionary> GrassToStyleMap = new Dictionary>() { { 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() @@ -78,662 +688,642 @@ namespace TShockAPI.Handlers }; /// - /// Item IDs that can spawn flowers while you walk + /// Checks whether the tile rect is a valid Flower Boots placement. /// - public static List FlowerBootItems = new List + /// 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) { - ItemID.FlowerBoots, - ItemID.FairyBoots - }; - - /// - /// 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 } - }; - - /// - /// Invoked when a SendTileRect packet is received - /// - /// - /// - public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) - { - // By default, we'll handle everything - args.Handled = true; - - if (ShouldSkipProcessing(args)) + if (rect.Width != 1 || rect.Height != 1) { - return; + return false; } - 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); - - IterateTileRect(tiles, processed, args); - - // 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); - - // 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) + if (!player.TPlayer.flowerBoots) { - TSPlayer.All.SendTileRect(args.TileX, args.TileY, args.Width, args.Length); - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from carbonara from {args.Player.Name}")); - } - } - - /// - /// 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; - - for (int x = 0; x < width; x++) - { - for (int y = 0; y < length; y++) - { - // Do not process already processed tiles - if (processed[x, y]) - { - continue; - } - - 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)) - { - processed[x, y] = true; - continue; - } - - // Do not process tiles that the player cannot update - if (!args.Player.HasBuildPermission(realX, realY) || - !args.Player.IsInRange(realX, realY)) - { - processed[x, y] = true; - continue; - } - - NetTile newTile = tiles[x, y]; - - TileObjectData data; - - // 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) - { - // 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) - { - 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; - } - } - } - else - { - // 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; - } - } - } - - 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)) - { - continue; - } - - newTiles = new NetTile[objWidth, objHeight]; - - for (int i = 0; i < objWidth; i++) - { - for (int j = 0; j < objHeight; j++) - { - newTiles[i, j] = tiles[x + i, y + j]; - processed[x + i, y + j] = true; - } - } - 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; - } - } - } - - /// - /// Processes a tile object consisting of multiple tiles from the tile rect packet - /// - /// 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) - { - // 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)) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from no permission for tile object from {args.Player.Name}")); - return; + return false; } - if (TShock.TileBans.TileIsBanned((short)tileType)) - { - TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRect rejected for banned tile")); - return; - } + ITile oldTile = Main.tile[rect.X, rect.Y]; + NetTile newTile = rect[0, 0]; - // Update all tiles in the tile object. These will be sent back to the player later - UpdateMultipleServerTileStates(realX, realY, width, height, newTiles); - - // Tile entities have special placements that we should let the game deal with - if (TileEntityIdToTileIdMap.ContainsKey(tileType)) - { - TileEntity.PlaceEntityNet(realX, realY, TileEntityIdToTileIdMap[tileType]); - } - } - - /// - /// 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))) - { - ProcessFlowerBoots(realX, realY, newTile); - return; - } - - ITile tile = Main.tile[realX, realY]; - - // Triggering a single land mine tile - if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active) - { - UpdateServerTileState(tile, newTile, TileDataType.Tile); - } - - // Hammering a single junction box - if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe) - { - UpdateServerTileState(tile, newTile, TileDataType.Tile); - } - - // 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 - )) - { - 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 - { - WorldGen.KillTile(realX, realY + 1); - } - } - - // 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) - { - ProcessConversionSpreads(tile, newTile); - } - - // All other single tile updates should not be processed. - } - - /// - /// Applies changes to a tile if a tile rect for flower-growing boots is valid - /// - /// 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) - { - 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)) + 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)) ) { - UpdateServerTileState(tile, newTile, TileDataType.Tile); - } - } - - // 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(); - - /// - /// 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) - /// - /// The tile to update - /// The NetTile containing new tile properties - internal void ProcessConversionSpreads(ITile tile, NetTile newTile) - { - 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 - { - foreach (var tileType in _convertibleTiles) + if (TShock.TileBans.TileIsBanned((short)newTile.Type, player)) { - if (tileType[tile.type] && tileType[newTile.Type]) - { - allowTile = true; - break; - } + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; } - } - if (allowTile) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a tile conversion update - [{tile.type}] -> [{newTile.Type}]")); - UpdateServerTileState(tile, newTile, TileDataType.Tile); - } - - foreach (var wallType in _convertibleWalls) - { - if (wallType[tile.wall] && wallType[newTile.Wall]) + if (!player.HasBuildPermission(rect.X, rect.Y)) { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a wall conversion update - [{tile.wall}] -> [{newTile.Wall}]")); - UpdateServerTileState(tile, newTile, TileDataType.Wall); - break; + // for simplicity, let's pretend that the edit was valid, but do not execute it + return true; } - } - } - /// - /// Updates a single tile's world state with a set of changes from the networked tile state - /// - /// 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) - { - //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. + 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; - if ((updateType & TileDataType.Tile) != 0) - { - tile.active(newTile.Active); - tile.type = newTile.Type; - - if (newTile.FrameImportant) - { - tile.frameX = newTile.FrameX; - tile.frameY = newTile.FrameY; - } - else if (tile.type != newTile.Type || !tile.active()) - { - //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; - } - } - - if ((updateType & TileDataType.Wall) != 0) - { - tile.wall = newTile.Wall; - } - - if ((updateType & TileDataType.TilePaint) != 0) - { - tile.color(newTile.TileColor); - tile.fullbrightBlock(newTile.FullbrightBlock); - tile.invisibleBlock(newTile.InvisibleBlock); - } - - if ((updateType & TileDataType.WallPaint) != 0) - { - tile.wallColor(newTile.WallColor); - tile.fullbrightWall(newTile.FullbrightWall); - tile.invisibleWall(newTile.InvisibleWall); - } - - if ((updateType & TileDataType.Liquid) != 0) - { - tile.liquid = newTile.Liquid; - tile.liquidType(newTile.LiquidType); - } - - if ((updateType & TileDataType.Slope) != 0) - { - tile.halfBrick(newTile.IsHalf); - tile.slope(newTile.Slope); - } - - if ((updateType & TileDataType.Wiring) != 0) - { - tile.wire(newTile.Wire); - tile.wire2(newTile.Wire2); - tile.wire3(newTile.Wire3); - tile.wire4(newTile.Wire4); - } - - if ((updateType & TileDataType.Actuator) != 0) - { - tile.actuator(newTile.IsActuator); - tile.inActive(newTile.Inactive); - } - } - - /// - /// Performs on multiple tiles - /// - /// - /// - /// - /// - /// - public static void UpdateMultipleServerTileStates(int x, int y, int width, int height, NetTile[,] newTiles) - { - for (int i = 0; i < width; i++) - { - for (int j = 0; j < height; j++) - { - UpdateServerTileState(Main.tile[x + i, y + j], newTiles[i, j], TileDataType.Tile); - } - } - } - - /// - /// Reads a set of NetTiles from a memory stream - /// - /// - /// - /// - /// - static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, byte width, byte length) - { - NetTile[,] tiles = new NetTile[width, length]; - for (int x = 0; x < width; x++) - { - for (int y = 0; y < length; y++) - { - tiles[x, y] = new NetTile(stream); - } - } - - return tiles; - } - - /// - /// Determines whether or not the tile rect should be immediately accepted or rejected - /// - /// - /// - static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args) - { - 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; - } - - 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 - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from non-vanilla tilemod from {args.Player.Name}")); - return true; - } - - if (args.Player.IsBouncerThrottled()) - { - 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; - } - - if (args.Player.IsBeingDisabled()) - { - 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; } return false; } - /// - /// Checks if a tile object fits inside the dimensions of a tile rectangle - /// - /// - /// - /// - /// - /// - /// - /// - /// - static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, bool[,] processed) - { - 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; - } - } - TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (excessive size)")); + private static readonly Dictionary GrassToMowedMap = new Dictionary + { + { TileID.Grass, TileID.GolfGrass }, + { TileID.HallowedGrass, TileID.GolfGrassHallowed }, + }; + + /// + /// Checks whether the tile rect is a valid grass mow. + /// + /// 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) + { + if (rect.Width != 1 || rect.Height != 1) + { return false; } - return true; + 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) + { + 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; } - class Debug + + /// + /// Checks whether the tile rect is a valid christmas tree modification. + /// This also required significant reverse engineering effort. + /// + /// 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) { - /// - /// 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) + if (rect.Width != 1 || rect.Height != 1) { - if (TShock.Config.Settings.DebugLogs) + 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) { - char pad = '0'; - for (int y = 0; y < height; y++) - { - int realY = y + tileY; - for (int x = 0; x < width; x++) - { - 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" : "-") + " "); - } - Console.Write(" -> "); - for (int x = 0; x < width; x++) - { - 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" : "-") + " "); - } - Console.Write("\n"); - } + 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)) + { + // 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 false; + } + } + + /// + /// This helper class allows simulating a `WorldGen.Convert` call and retrieving all valid changes for a given tile. + /// + internal static class WorldGenMock + { + /// + /// This is a mock tile which collects all possible changes the `WorldGen.Convert` code could make in its property setters. + /// + private sealed class MockTile + { + private readonly HashSet _setTypes; + private readonly HashSet _setWalls; + + private ushort _type; + private ushort _wall; + + public MockTile(ushort type, ushort wall, HashSet setTypes, HashSet setWalls) + { + _setTypes = setTypes; + _setWalls = setWalls; + _type = type; + _wall = wall; + } + +#pragma warning disable IDE1006 + + public ushort type + { + get => _type; + set + { + _setTypes.Add(value); + _type = value; } } - /// - /// 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) + public ushort wall { - for (int x = 0; x < width; x++) + get => _wall; + set { - 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); + _setWalls.Add(value); + _wall = value; } + } - player.SendTileRect(tileX, tileY, width, height); +#pragma warning restore IDE1006 + } + + /// + /// 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. + /// + public static void SimulateConversionChange(int x, int y, out HashSet validTiles, out HashSet validWalls) + { + validTiles = new HashSet(); + validWalls = new HashSet(); + + // 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); + } + } + + /* + * 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) + { + int type = tile.type; + int wall = tile.wall; + switch (conversionType) + { + case 4: + if (WallID.Sets.Conversion.Grass[wall] && wall != 81) + { + tile.wall = 81; + } + else if (WallID.Sets.Conversion.Stone[wall] && wall != 83) + { + 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)) + { + num = 397; + } + 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)) + { + num2 = 2; + } + tile.type = (ushort)num2; + } + break; + } + if (tile.wall == 69 || tile.wall == 70 || tile.wall == 81) + { + if (l < Main.worldSurface) + { + tile.wall = 65; + tile.wall = 63; + } + 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/Handlers/SendTileRectHandlerRefactor.cs b/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs deleted file mode 100644 index 34f8a495..00000000 --- a/TShockAPI/Handlers/SendTileRectHandlerRefactor.cs +++ /dev/null @@ -1,1330 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -using Terraria; -using Terraria.ID; - -using TShockAPI.Net; - -namespace TShockAPI.Handlers -{ - /// - /// 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 sealed class SendTileRectHandlerRefactor : IPacketHandler - { - /// - /// Represents a tile rectangle sent through the packet. - /// - private sealed class TileRect - { - 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) - { - _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) - { - 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); - } - } - - /// - /// Represents a common tile rect operation (Placement, State Change, Removal). - /// - private readonly struct TileRectMatch - { - private const short IGNORE_FRAME = -1; - - private enum MatchType - { - Placement, - StateChange, - Removal, - } - - private readonly int Width; - private readonly int Height; - - private readonly ushort TileType; - private readonly short MaxFrameX; - private readonly short MaxFrameY; - private readonly short FrameXStep; - private readonly short FrameYStep; - - private readonly MatchType Type; - - private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep) - { - Type = type; - Width = width; - Height = height; - TileType = tileType; - MaxFrameX = maxFrameX; - MaxFrameY = maxFrameY; - FrameXStep = frameXStep; - FrameYStep = frameYStep; - } - - /// - /// 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); - } - - /// - /// 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); - } - - /// - /// 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); - } - - /// - /// 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) - { - return new TileRectMatch(MatchType.StateChange, width, height, tileType, IGNORE_FRAME, maxFrame, 0, frameStep); - } - - /// - /// 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); - } - - /// - /// 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) - { - if (rect.Width != Width || rect.Height != Height) - { - return false; - } - - for (int x = 0; x < rect.Width; x++) - { - for (int y = 0; y < rect.Height; y++) - { - 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; - } - } - } - } - - for (int x = rect.X; x < rect.X + rect.Width; x++) - { - for (int y = rect.Y; y < rect.Y + rect.Height; y++) - { - 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; - } - - 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++) - { - if (Main.tile[x, y].active() && !(Main.tile[x, y].type != TileID.RollingCactus && (Main.tileCut[Main.tile[x, y].type] || TileID.Sets.BreakableWhenPlacing[Main.tile[x, y].type]))) - { - 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; - } - - 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; - } - } - - return true; - } - - 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++) - { - if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType) - { - return false; - } - } - } - - for (int x = 0; x < rect.Width; x++) - { - for (int y = 0; y < rect.Height; y++) - { - if (MaxFrameX != IGNORE_FRAME) - { - Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX; - } - if (MaxFrameY != IGNORE_FRAME) - { - Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY; - } - } - } - - return true; - } - - 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) - { - return false; - } - } - } - - 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; - } - } - - /// - /// 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. - /// - public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) - { - // this permission bypasses all checks for direct access to the world - if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) - { - TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}")); - - // use vanilla handling - args.Handled = false; - return; - } - - // 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 from size from {args.Player.Name}")); - - // definitely invalid; do not send any correcting data - return; - } - - // 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; - } - - // player disabled? - if (args.Player.IsBeingDisabled()) - { - 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; - } - - // 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)) - { - 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; - } - - // 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; - } - - // check if the distance is valid - if (!IsRectDistanceValid(args.Player, rect)) - { - 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; - } - - // a very special case, due to the flower seed check otherwise hijacking this - if (MatchesFlowerBoots(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; - } - - // check if the rect matches any valid operation - foreach (TileRectMatch match in Matches) - { - if (match.Matches(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; - } - } - - // a few special cases - if ( - MatchesConversionSpread(args.Player, rect) || - MatchesGrassMow(args.Player, rect) || - MatchesChristmasTree(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; - } - - 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; - } - - /// - /// Checks whether the tile rect is at a valid position for the given player. - /// - /// 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) - { - for (int x = 0; x < rect.Width; x++) - { - 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; - } - } - } - - return true; - } - - /// - /// Checks whether the tile rect is at a valid distance to the given player. - /// - /// 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) - { - for (int x = 0; x < rect.Width; x++) - { - for (int y = 0; y < rect.Height; y++) - { - int realX = rect.X + x; - int realY = rect.Y + y; - - if (!player.IsInRange(realX, realY)) - { - return false; - } - } - } - - return true; - } - - - /// - /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.). - /// - /// 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) - { - if (rect.Width != 1 || rect.Height != 1) - { - return false; - } - - 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)) - { - // for simplicity, let's pretend that the edit was valid, but do not execute it - return true; - } - else 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].type = newTile.Type; - Main.tile[rect.X, rect.Y].frameX = newTile.FrameX; - Main.tile[rect.X, rect.Y].frameY = newTile.FrameY; - - return true; - } - } - - if (newTile.Wall != oldTile.wall && validWalls.Contains(newTile.Wall)) - { - // 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; - } - } - - return false; - } - - - private static readonly Dictionary> PlantToGrassMap = new Dictionary> - { - { 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() - { - TileID.JungleGrass - } }, - { TileID.AshPlants, new HashSet() - { - TileID.AshGrass - } }, - }; - - private static readonly Dictionary> GrassToStyleMap = new Dictionary>() - { - { TileID.Plants, new HashSet() - { - 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, - } }, - }; - - /// - /// 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) - { - return false; - } - - if (!player.TPlayer.flowerBoots) - { - return false; - } - - 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)) - ) - { - 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 }, - }; - - /// - /// Checks whether the tile rect is a valid grass mow. - /// - /// 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) - { - if (rect.Width != 1 || rect.Height != 1) - { - 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) - { - 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; - } - - - /// - /// Checks whether the tile rect is a valid christmas tree modification. - /// This also required significant reverse engineering effort. - /// - /// 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) - { - if (rect.Width != 1 || rect.Height != 1) - { - 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)) - { - // 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 false; - } - } - - /// - /// This helper class allows simulating a `WorldGen.Convert` call and retrieving all valid changes for a given tile. - /// - internal static class WorldGenMock - { - /// - /// This is a mock tile which collects all possible changes the `WorldGen.Convert` code could make in its property setters. - /// - private sealed class MockTile - { - private readonly HashSet _setTypes; - private readonly HashSet _setWalls; - - private ushort _type; - private ushort _wall; - - public MockTile(ushort type, ushort wall, HashSet setTypes, HashSet setWalls) - { - _setTypes = setTypes; - _setWalls = setWalls; - _type = type; - _wall = wall; - } - -#pragma warning disable IDE1006 - - public ushort type - { - get => _type; - set - { - _setTypes.Add(value); - _type = value; - } - } - - public ushort wall - { - get => _wall; - set - { - _setWalls.Add(value); - _wall = value; - } - } - -#pragma warning restore IDE1006 - } - - /// - /// 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. - /// - public static void SimulateConversionChange(int x, int y, out HashSet validTiles, out HashSet validWalls) - { - validTiles = new HashSet(); - validWalls = new HashSet(); - - // 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); - } - } - - /* - * 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) - { - int type = tile.type; - int wall = tile.wall; - switch (conversionType) - { - case 4: - if (WallID.Sets.Conversion.Grass[wall] && wall != 81) - { - tile.wall = 81; - } - else if (WallID.Sets.Conversion.Stone[wall] && wall != 83) - { - 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)) - { - num = 397; - } - 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)) - { - num2 = 2; - } - tile.type = (ushort)num2; - } - break; - } - if (tile.wall == 69 || tile.wall == 70 || tile.wall == 81) - { - if (l < Main.worldSurface) - { - tile.wall = 65; - tile.wall = 63; - } - 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; - } - } - } -} From 3d11d84d7339ffe7f165cf2f15316a1d6660dd48 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Sat, 13 May 2023 03:56:07 -0700 Subject: [PATCH 13/77] Fix dump-reference-data mutate command name --- TShockAPI/Permissions.cs | 15 +++------------ docs/changelog.md | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index d7ec7166..debc8ea5 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -529,18 +529,9 @@ namespace TShockAPI 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).Select(n => $"/{n}"))})" + : c.Name); sb.AppendLine($"## {name}"); sb.AppendLine($"{desc}"); diff --git a/docs/changelog.md b/docs/changelog.md index 3f64f355..d7b1d19f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -78,7 +78,7 @@ 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 -Your changes could be here! +* Fixed `/dump-reference-data` mutate the command names. (#2943, @sgkoishi) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 51ecef73a16dd1df10bf093ef529be57c5bd1b64 Mon Sep 17 00:00:00 2001 From: Stargazing Koishi Date: Sat, 13 May 2023 04:09:15 -0700 Subject: [PATCH 14/77] Optimize Linq Co-authored-by: Arthri <41360489+Arthri@users.noreply.github.com> --- TShockAPI/Permissions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index debc8ea5..99ac5d7d 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -530,8 +530,8 @@ namespace TShockAPI var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : GetString("No description available."); var strs = GetCommands(name).Select(c => c.Names.Count > 1 - ? $"{c.Name} ({string.Join(" ", c.Names.Skip(1).Select(n => $"/{n}"))})" - : c.Name); + ? $"/{c.Name} (/{string.Join(" /", c.Names.Skip(1))})" + : $"/{c.Name}"); sb.AppendLine($"## {name}"); sb.AppendLine($"{desc}"); From c3f5994451b02a0b93230b41519f34fa97ac9a9a Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 10:08:02 +0700 Subject: [PATCH 15/77] Added optional arguments `stack` and `prefixId` to the `NetItem` constructor --- TShockAPI/NetItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 278fda1c..03596a1f 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -155,7 +155,7 @@ namespace TShockAPI /// 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; From 62b8e5067ce3d97055ff7ed8063d9994909816c4 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 10:13:25 +0700 Subject: [PATCH 16/77] Added the `NetItem.Build' method. The method will create a Terraria.Item instance based on the data from the structure. --- TShockAPI/NetItem.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 03596a1f..095d3f1c 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -162,6 +162,24 @@ namespace TShockAPI _prefixId = prefixId; } + /// + /// Creates based on data from this structure. + /// + /// A copy of the item. + /// If the item ID is 0. + public Item Build() + { + if (_netId == 0) + throw new Exception("It is impossible to create an item whose ID is 0."); + Item item = new Item(); + + item.netDefaults(_netId); + item.stack = _stack; + item.prefix = _prefixId; + + return item; + } + /// /// Converts the to a string. /// From 86be1738ccc2f6d8930d4fe3dea411acc310dd49 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 10:13:59 +0700 Subject: [PATCH 17/77] Added a constructor with arguments taking `Terraria.Item`. --- TShockAPI/NetItem.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 095d3f1c..86fe82a7 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -162,6 +162,17 @@ namespace TShockAPI _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. /// From 7ab05707869366b884cce0680efee356cfc6ea7e Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 10:22:39 +0700 Subject: [PATCH 18/77] Update changelog.md --- docs/changelog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 3f64f355..54e6ff66 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -78,7 +78,10 @@ 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 -Your changes could be here! +* Updated `TShockAPI.NetItem` (@AgaSpace): + * Added constructor overload with parameter `Terraria.Item`. + * Added the `Build` method to get a copy of `Terraria.Item`. + * In the constructor I added optional parameters `stack` and `prefix`. ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 22d8575e01bad823c7e6fac60e24413d77bde990 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 12:04:17 +0700 Subject: [PATCH 19/77] Corrected the `UserAccountNotExistException` documentation. --- TShockAPI/DB/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 0354989e..7b8fe4a2 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -619,7 +619,7 @@ namespace TShockAPI.DB 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")) From 8a0920b6eabadb40bc43cb65a4eb565366ff0f75 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 12:08:11 +0700 Subject: [PATCH 20/77] Added a hook `AccountHooks.AccountGroupUpdate`. --- TShockAPI/Hooks/AccountHooks.cs | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/TShockAPI/Hooks/AccountHooks.cs b/TShockAPI/Hooks/AccountHooks.cs index 9c08b26d..e224b467 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,37 @@ namespace TShockAPI.Hooks } } + public abstract 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 AccountGroupUpdateByPluginEventArgs : AccountGroupUpdateEventArgs + { + public AccountGroupUpdateByPluginEventArgs(string accountName, Group group) : base(accountName, 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 +94,25 @@ namespace TShockAPI.Hooks 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 AccountGroupUpdateByPluginEventArgs(account.Name, group); + AccountGroupUpdate?.Invoke(args); + group = args.Group; + + return args.Handled; + } } } From 1e037748c1d511fdc3449ff6261a55a049b4e96d Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 12:13:56 +0700 Subject: [PATCH 21/77] Updated the `UserManager.SetUserGroup`. Added an exception `UserGroupUpdateLockedException`, which appears when a hook locks a group change. Added an overload for `UserManager.SetUserGroup`, with the `TSPlayer` parameter (author) --- TShockAPI/DB/UserManager.cs | 51 ++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 7b8fe4a2..6411b289 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -25,6 +25,7 @@ using MySql.Data.MySqlClient; using System.Text.RegularExpressions; using BCrypt.Net; using System.Security.Cryptography; +using TShockAPI.Hooks; namespace TShockAPI.DB { @@ -166,7 +167,41 @@ namespace TShockAPI.DB 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 @@ -627,6 +662,20 @@ namespace TShockAPI.DB } } + /// 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($"The user {name} group could not be updated")) + { + } + } + + /// A GroupNotExistsException, used when a group does not exist. [Serializable] public class GroupNotExistsException : UserAccountManagerException From 230d9b094591b196b40aff2905e90d8bf88ec9f8 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 12:17:47 +0700 Subject: [PATCH 22/77] Updated the `SetUserGroup` in the commands. --- TShockAPI/Commands.cs | 6 +++++- TShockAPI/Rest/RestManager.cs | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 2214f252..164bda89 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -1176,7 +1176,7 @@ namespace TShockAPI 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 @@ namespace TShockAPI { 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.")); diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index c41e7767..0a96b004 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -555,7 +555,8 @@ namespace TShockAPI { 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) From 4e85c5ddac099fad4ecfb4ae472854d526729d2a Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sun, 14 May 2023 12:19:05 +0700 Subject: [PATCH 23/77] Update changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 3f64f355..ebfcd895 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -78,7 +78,7 @@ 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 -Your changes could be here! +* Added a hook `AccountHooks.AccountGroupUpdate`, which is called when you change the user group. (@AgaSpace) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From cc753cf1dac747db6dc613d9c0b7faed9045fdda Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:00:18 +0700 Subject: [PATCH 24/77] Removed unnecessary abstraction. --- TShockAPI/Hooks/AccountHooks.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/TShockAPI/Hooks/AccountHooks.cs b/TShockAPI/Hooks/AccountHooks.cs index e224b467..ae9fff24 100644 --- a/TShockAPI/Hooks/AccountHooks.cs +++ b/TShockAPI/Hooks/AccountHooks.cs @@ -40,7 +40,7 @@ namespace TShockAPI.Hooks } } - public abstract class AccountGroupUpdateEventArgs : HandledEventArgs + public class AccountGroupUpdateEventArgs : HandledEventArgs { public string AccountName { get; private set; } public Group Group { get; set; } @@ -52,12 +52,6 @@ namespace TShockAPI.Hooks } } - public class AccountGroupUpdateByPluginEventArgs : AccountGroupUpdateEventArgs - { - public AccountGroupUpdateByPluginEventArgs(string accountName, Group group) : base(accountName, group) - { - } - } public class AccountGroupUpdateByPlayerEventArgs : AccountGroupUpdateEventArgs { /// @@ -108,7 +102,7 @@ namespace TShockAPI.Hooks } public static bool OnAccountGroupUpdate(UserAccount account, ref Group group) { - AccountGroupUpdateEventArgs args = new AccountGroupUpdateByPluginEventArgs(account.Name, group); + AccountGroupUpdateEventArgs args = new AccountGroupUpdateEventArgs(account.Name, group); AccountGroupUpdate?.Invoke(args); group = args.Group; From 07bf66f072aef44c43559ac424ecb3990c4d4beb Mon Sep 17 00:00:00 2001 From: punchready <22683812+punchready@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:25:03 +0200 Subject: [PATCH 25/77] Fix MatchPlacement allowing auto breakable tiles --- TShockAPI/Handlers/SendTileRectHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 64d9d8a7..3aa4b14c 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -266,7 +266,7 @@ namespace TShockAPI.Handlers { for (int y = rect.Y; y < rect.Y + rect.Height; y++) { - if (Main.tile[x, y].active() && !(Main.tile[x, y].type != TileID.RollingCactus && (Main.tileCut[Main.tile[x, y].type] || TileID.Sets.BreakableWhenPlacing[Main.tile[x, y].type]))) + if (Main.tile[x, y].active()) // the client will kill tiles that auto break before placing the object { return false; } From 914d7432646080ab7eae8d0ccebd4c375f7c7439 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Tue, 6 Jun 2023 19:15:58 +0700 Subject: [PATCH 26/77] Changed the method from `NetItem.Build` to `NetItem.ToItem`. --- TShockAPI/NetItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 86fe82a7..0a3e0c99 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; @@ -178,7 +178,7 @@ namespace TShockAPI /// /// A copy of the item. /// If the item ID is 0. - public Item Build() + public Item ToItem() { if (_netId == 0) throw new Exception("It is impossible to create an item whose ID is 0."); From 149ca8a70cdc7b2994ed1584c25230ff7378b0ba Mon Sep 17 00:00:00 2001 From: punchready <22683812+punchready@users.noreply.github.com> Date: Wed, 7 Jun 2023 03:05:10 +0200 Subject: [PATCH 27/77] Remove duplicate conversion spread matching --- TShockAPI/Handlers/SendTileRectHandler.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 3aa4b14c..d85185b8 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -506,11 +506,7 @@ namespace TShockAPI.Handlers } // a few special cases - if ( - MatchesConversionSpread(args.Player, rect) || - MatchesGrassMow(args.Player, rect) || - MatchesChristmasTree(args.Player, rect) - ) + if (MatchesGrassMow(args.Player, rect) || MatchesChristmasTree(args.Player, rect)) { TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}")); From 3a90029d5199e724baeaf04a1aea010b9be3e103 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:33:32 +0700 Subject: [PATCH 28/77] Allowed to receive an item if it is 0. --- TShockAPI/NetItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 0a3e0c99..13f3f49d 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -180,8 +180,6 @@ namespace TShockAPI /// If the item ID is 0. public Item ToItem() { - if (_netId == 0) - throw new Exception("It is impossible to create an item whose ID is 0."); Item item = new Item(); item.netDefaults(_netId); From b3f314985070afc450da441524698c1640d6dccc Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:34:06 +0700 Subject: [PATCH 29/77] Update changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 54e6ff66..e02dcbdc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -80,7 +80,7 @@ Use past tense when adding new entries; sign your name off when you add or chang ## Upcoming changes * Updated `TShockAPI.NetItem` (@AgaSpace): * Added constructor overload with parameter `Terraria.Item`. - * Added the `Build` method to get a copy of `Terraria.Item`. + * Added the `ToItem` method to get a copy of `Terraria.Item`. * In the constructor I added optional parameters `stack` and `prefix`. ## TShock 5.2 From 4eb2aa49de40e5c64b4dc09acacdf554634737c6 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Thu, 8 Jun 2023 18:11:26 +0700 Subject: [PATCH 30/77] Removed unnecessary documentation --- TShockAPI/NetItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs index 13f3f49d..0f0dc6e3 100644 --- a/TShockAPI/NetItem.cs +++ b/TShockAPI/NetItem.cs @@ -177,7 +177,6 @@ namespace TShockAPI /// Creates based on data from this structure. /// /// A copy of the item. - /// If the item ID is 0. public Item ToItem() { Item item = new Item(); From 5498f84942c5487b651214db20a331c7f7b87dc6 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Thu, 8 Jun 2023 18:12:25 +0700 Subject: [PATCH 31/77] Update changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index e02dcbdc..d1f486b2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -81,7 +81,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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 I added optional parameters `stack` and `prefix`. + * In the constructor `stack` and `prefix` are now optional parameters. ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 72ecc472187dde708ea2a2d1176e3b9872cd27f7 Mon Sep 17 00:00:00 2001 From: James Puleo Date: Fri, 9 Jun 2023 05:52:19 -0400 Subject: [PATCH 32/77] Update `docs/changelog.md` (#2953) --- docs/changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index f02867f8..89e4d39c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -82,12 +82,12 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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. +* Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From a6666ff21abf1d40133a98eb5dfe2e5e0c6b8c58 Mon Sep 17 00:00:00 2001 From: AkjaHAsLk1IALk0MasH <46046453+AgaSpace@users.noreply.github.com> Date: Sat, 10 Jun 2023 13:59:38 +0700 Subject: [PATCH 33/77] Updated the message --- TShockAPI/DB/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 6411b289..6fc50d98 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -670,7 +670,7 @@ namespace TShockAPI.DB /// 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($"The user {name} group could not be updated")) + base(GetString($"Unable to update group of user {name}.")) { } } From 5bcde689d4262ae18ca639e4a9895d078ba31cc4 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Sun, 11 Jun 2023 22:11:20 -0700 Subject: [PATCH 34/77] Fix a typo in gbuff --- TShockAPI/Commands.cs | 2 +- docs/changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 3d1767ea..a4669b04 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -6462,7 +6462,7 @@ namespace TShockAPI 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 {user.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/docs/changelog.md b/docs/changelog.md index 89e4d39c..d4ab3cfe 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -88,6 +88,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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. (@AgaSpace) +* Fixed typo in `/gbuff`. (@sgkoishi, #2955) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From f66ad72e2caeec3de8c6a64cc007afdc83d1637a Mon Sep 17 00:00:00 2001 From: Stargazing Koishi Date: Sun, 11 Jun 2023 22:20:48 -0700 Subject: [PATCH 35/77] Update TShockAPI/Commands.cs Co-authored-by: punchready <22683812+punchready@users.noreply.github.com> --- TShockAPI/Commands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index a4669b04..d39b84e0 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -6462,7 +6462,7 @@ namespace TShockAPI if (target == user) user.SendSuccessMessage(GetString($"You buffed yourself with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds.")); else - user.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!")); } From b1a054472fcabd14182c9dd1ec7fa3871ab9eb39 Mon Sep 17 00:00:00 2001 From: Cardinal System Date: Tue, 15 Aug 2023 09:53:09 +0000 Subject: [PATCH 36/77] =?UTF-8?q?Update=20translation=20template=20?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/template.pot | 600 +++++++++++++++++++++++----------------------- 1 file changed, 296 insertions(+), 304 deletions(-) diff --git a/i18n/template.pot b/i18n/template.pot index a4fe57ab..9da0b2e0 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 -#, csharp-format -msgid "" -"Bouncer / SendTileRect processing a wall conversion update - [{0}] -> [{1}]" -msgstr "" - -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:129 -#, csharp-format -msgid "Bouncer / SendTileRect reimplemented from carbonara from {0}" -msgstr "" - -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:299 -msgid "Bouncer / SendTileRect rejected for banned tile" -msgstr "" - -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:578 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:446 #, csharp-format msgid "Bouncer / SendTileRect rejected from being disabled from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:293 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:518 +#, csharp-format +msgid "Bouncer / SendTileRect rejected from matches from {0}" +msgstr "" + +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:459 #, csharp-format msgid "" -"Bouncer / SendTileRect rejected from no permission for tile object from {0}" +"Bouncer / SendTileRect rejected from out of bounds / build permission from " +"{0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:565 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:478 #, csharp-format -msgid "Bouncer / SendTileRect rejected from non-vanilla tilemod from {0}" +msgid "Bouncer / SendTileRect rejected from out of range from {0}" msgstr "" -#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:571 +#: ../../TShockAPI/Handlers/SendTileRectHandler.cs:427 +#, csharp-format +msgid "Bouncer / SendTileRect rejected from size from {0}" +msgstr "" + +#: ../../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 "" From 60599ef5b58ef8f548ec36742a091f11ddda9d8b Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Fri, 22 Sep 2023 22:30:18 +0200 Subject: [PATCH 37/77] Rewrite the `.dockerignore` file into a denylist This should help with not forgetting to add any new directories (such as TShockInstaller and TShockPluginManager, which have been missing until now). --- .dockerignore | 33 +++++++++++++++++++-------------- docs/changelog.md | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.dockerignore b/.dockerignore index b20ac5b6..234117c0 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/docs/changelog.md b/docs/changelog.md index 6a159e8a..035df8ad 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -89,6 +89,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added a method `TSPlayer.GiveItem`, which has `TShockAPI.NetItem` structure in its arguments. (@AgaSpace) * Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) * Fixed typo in `/gbuff`. (@sgkoishi, #2955) +* Rewrote the `.dockerignore` file into a denylist. (@timschumi) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 93749be8bf57520d764264dbef007252883fe9a6 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Fri, 29 Sep 2023 17:36:58 +0200 Subject: [PATCH 38/77] Clear default Dockerfile assignments for `*PLATFORM` These kept Docker buildx from automatically building for multiple platforms. --- Dockerfile | 10 ++++++---- docs/docker.md | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 186bee70..87024fb9 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/docs/docker.md b/docs/docker.md index 0b89681b..ca018510 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -14,15 +14,15 @@ 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 . +# Building the image using buildx and loading it into docker +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-amd64 \ + --rm -it tshock:latest \ -world /worlds/backflip.wld -motd "OMFG DOCKER" ``` @@ -33,7 +33,7 @@ Using `docker buildx`, you could build [multi-platform images](https://docs.dock 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:linux-arm64 --platform linux/arm64 --load . # Running the image docker run -p 7777:7777 -p 7878:7878 \ From 3ffe8e1274597151dee42a42f8df64a2f7b9a076 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Fri, 29 Sep 2023 17:13:00 +0200 Subject: [PATCH 39/77] Add CI for Docker images --- .github/workflows/ci-docker.yml | 25 +++++++++++++++++++++++++ docs/changelog.md | 1 + 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/ci-docker.yml diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 00000000..deb811a2 --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,25 @@ +name: CI (Docker image) + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + 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: Build image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64 + push: false + pull: true + cache-from: type=gha, scope=${{ github.workflow }} + cache-to: type=gha, scope=${{ github.workflow }} diff --git a/docs/changelog.md b/docs/changelog.md index 035df8ad..3cb52f80 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -90,6 +90,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) * Fixed typo in `/gbuff`. (@sgkoishi, #2955) * Rewrote the `.dockerignore` file into a denylist. (@timschumi) +* Added CI for Docker images. (@timschumi) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From b6041a738e61f9c995e2b3bbcd3c101f658f3ff5 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Thu, 21 Dec 2023 20:39:43 -0800 Subject: [PATCH 40/77] Whitelist the Striking Moment (`ParryDamageBuff`) buff --- TShockAPI/Bouncer.cs | 6 ++++++ docs/changelog.md | 1 + 2 files changed, 7 insertions(+) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 6ff7fd1a..4a41a42c 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -434,6 +434,12 @@ namespace TShockAPI CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; + PlayerAddBuffWhitelist[BuffID.WindPushed] = new BuffLimit + { + MaxTicks = 2, + CanBeAddedWithoutHostile = true, + CanOnlyBeAppliedToSender = true + }; #endregion Whitelist } diff --git a/docs/changelog.md b/docs/changelog.md index 035df8ad..235b5ec9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -90,6 +90,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) * Fixed typo in `/gbuff`. (@sgkoishi, #2955) * Rewrote the `.dockerignore` file into a denylist. (@timschumi) +* Added `ParryDamageBuff` (Striking Moment with Brand of the Inferno and shield) to the `PlayerAddBuffWhitelist` (@sgkoishi, #3005) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 3c2a3f3e53e2eb25ba3d6eeb2ebf066136dcdaa8 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Thu, 21 Dec 2023 20:43:41 -0800 Subject: [PATCH 41/77] Add unstaged changes --- TShockAPI/Bouncer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 4a41a42c..594d76ff 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -434,9 +434,9 @@ namespace TShockAPI CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; - PlayerAddBuffWhitelist[BuffID.WindPushed] = new BuffLimit + PlayerAddBuffWhitelist[BuffID.ParryDamageBuff] = new BuffLimit { - MaxTicks = 2, + MaxTicks = 300, CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; From cf726368c51647f6189729bead08ef658644951d Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Fri, 22 Dec 2023 13:19:58 -0800 Subject: [PATCH 42/77] Use seconds as the time unit --- TShockAPI/Bouncer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 594d76ff..22d6461a 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -424,7 +424,7 @@ namespace TShockAPI }; PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit { - MaxTicks = 240, + MaxTicks = 60 * 4, CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; @@ -436,7 +436,7 @@ namespace TShockAPI }; PlayerAddBuffWhitelist[BuffID.ParryDamageBuff] = new BuffLimit { - MaxTicks = 300, + MaxTicks = 60 * 5, CanBeAddedWithoutHostile = true, CanOnlyBeAppliedToSender = true }; From dbca317639625fe3e317d05f521fb04b6cb37d96 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Wed, 27 Dec 2023 13:01:55 -0800 Subject: [PATCH 43/77] Update CursedInferno time limit --- TShockAPI/Bouncer.cs | 2 +- docs/changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 22d6461a..aed7ac04 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -2860,7 +2860,7 @@ namespace TShockAPI { 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/docs/changelog.md b/docs/changelog.md index 235b5ec9..2a4fbfcd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -90,7 +90,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) * Fixed typo in `/gbuff`. (@sgkoishi, #2955) * Rewrote the `.dockerignore` file into a denylist. (@timschumi) -* Added `ParryDamageBuff` (Striking Moment with Brand of the Inferno and shield) to the `PlayerAddBuffWhitelist` (@sgkoishi, #3005) +* Added `ParryDamageBuff` (Striking Moment with Brand of the Inferno and shield) for player, updated `CursedInferno` buff for NPC (@sgkoishi, #3005) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From aa526223a6a941933cf7a46fb2177ef58d5eb5cc Mon Sep 17 00:00:00 2001 From: Johannes Grimm Date: Tue, 30 Jan 2024 17:55:39 +0100 Subject: [PATCH 44/77] fix: off no save command Added seting SaveOnSeverExit befor shutting down the server Fixes #2938 --- TShockAPI/Commands.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index d39b84e0..496669bb 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -2044,6 +2044,7 @@ namespace TShockAPI 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); } From a19ac72e0db7f37728f1ebaa1c4aaa4713642357 Mon Sep 17 00:00:00 2001 From: ZakFahey Date: Sun, 18 Feb 2024 11:36:26 -0800 Subject: [PATCH 45/77] Lazy initialize TextLog._logWriter There is an instance of this in SqlLog that's used as a backup, but since the StreamWriter is created in the constructor, the file is created too. This means that you'll get an empty text log created, every time a server starts up, even if you have text logs disabled. This is especially problematic when you have a multi-server setup with file write conflicts. --- TShockAPI/TextLog.cs | 14 +++++++++++--- docs/changelog.md | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/TShockAPI/TextLog.cs b/TShockAPI/TextLog.cs index 070c6c88..ae3bb29e 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 @@ namespace TShockAPI public TextLog(string filename, bool clear) { FileName = filename; - _logWriter = new StreamWriter(filename, !clear); + ClearFile = clear; } public bool MayWriteType(TraceLevel type) @@ -247,6 +248,10 @@ namespace TShockAPI { if (!MayWriteType(level)) return; + if (_logWriter is null) + { + _logWriter = new StreamWriter(FileName, !ClearFile); + } var caller = "TShock"; @@ -278,7 +283,10 @@ namespace TShockAPI public void Dispose() { - _logWriter.Dispose(); + if (_logWriter != null) + { + _logWriter.Dispose(); + } } } } diff --git a/docs/changelog.md b/docs/changelog.md index 035df8ad..ebb335cc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -88,6 +88,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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. (@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) From 5c4db00f1e997bce7122f9da3937560d77ddfc08 Mon Sep 17 00:00:00 2001 From: Arthri <41360489+a@users.noreply.github.com> Date: Wed, 24 Apr 2024 05:31:49 +0000 Subject: [PATCH 46/77] Fix Cursed Flare --- TShockAPI/Bouncer.cs | 2 +- docs/changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 6ff7fd1a..58c14b1c 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -2854,7 +2854,7 @@ namespace TShockAPI { 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/docs/changelog.md b/docs/changelog.md index 27e037d6..b1974546 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -92,6 +92,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * 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) From 8df09cb4a8a8707524390265744e61e3dbe690c6 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Wed, 31 Jul 2024 19:11:50 +0800 Subject: [PATCH 47/77] fix: /help, /me, and /p commands can't work in non-English languages --- TShockAPI/Localization/EnglishLanguage.cs | 26 +++++++++++++++++++++++ TShockAPI/TShock.cs | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Localization/EnglishLanguage.cs b/TShockAPI/Localization/EnglishLanguage.cs index 1ec4ecea..97507141 100644 --- a/TShockAPI/Localization/EnglishLanguage.cs +++ b/TShockAPI/Localization/EnglishLanguage.cs @@ -20,7 +20,9 @@ using System; 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 @@ namespace TShockAPI.Localization private static readonly Dictionary Buffs = new Dictionary(); + private static readonly Dictionary VanillaCommands = new Dictionary(); + internal static void Initialize() { var culture = Language.ActiveCulture; @@ -71,6 +75,15 @@ namespace TShockAPI.Localization var i = (int)field.GetValue(null); Prefixs.Add(i, Lang.prefix[i].Value); } + + ChatInitializer.Load(); + foreach (var command in ChatManager.Commands._localizedCommands) + { + if (VanillaCommands.ContainsKey(command.Value._name)) + continue; + VanillaCommands.Add(command.Value._name,command.Key.Value); + } + ChatManager.Commands._localizedCommands.Clear(); } finally { @@ -136,5 +149,18 @@ namespace TShockAPI.Localization return null; } + + /// + /// Get vanilla command text in English + /// + /// vanilla command name + /// vanilla command text English + public static string GetCommandTextByName(string name) + { + string commandText; + if (VanillaCommands.TryGetValue(name, out commandText)) + return commandText; + return null; + } } } diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 054e31df..48fab88d 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1497,11 +1497,11 @@ namespace TShockAPI { if (!String.IsNullOrEmpty(text)) { - text = item.Key.Value + ' ' + text; + text = EnglishLanguage.GetCommandTextByName(item.Value._name) + ' ' + text; } else { - text = item.Key.Value; + text = EnglishLanguage.GetCommandTextByName(item.Value._name); } break; } From b90182f1e5a8cc47057fbb809bd2ee3311c13714 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Wed, 31 Jul 2024 19:16:23 +0800 Subject: [PATCH 48/77] update changelog --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index b1974546..d2e1444e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -93,6 +93,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Rewrote the `.dockerignore` file into a denylist. (@timschumi) * Added CI for Docker images. (@timschumi) * Fixed Cursed Flares kicking players for invalid buff. (@Arthri) +* Fixed /help, /me, and /p commands can't work in non-English languages (@ACaiCat) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From a8624a816344dd39a63a701e98fe57c086613a34 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Wed, 31 Jul 2024 19:16:23 +0800 Subject: [PATCH 49/77] update changelog --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index b1974546..d2e1444e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -93,6 +93,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Rewrote the `.dockerignore` file into a denylist. (@timschumi) * Added CI for Docker images. (@timschumi) * Fixed Cursed Flares kicking players for invalid buff. (@Arthri) +* Fixed /help, /me, and /p commands can't work in non-English languages (@ACaiCat) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From 7419205548fb9779f83bc924222c1df9741ddff5 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Wed, 31 Jul 2024 19:24:53 +0800 Subject: [PATCH 50/77] rename --- TShockAPI/Localization/EnglishLanguage.cs | 14 +++++++------- TShockAPI/TShock.cs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/TShockAPI/Localization/EnglishLanguage.cs b/TShockAPI/Localization/EnglishLanguage.cs index 97507141..334c4780 100644 --- a/TShockAPI/Localization/EnglishLanguage.cs +++ b/TShockAPI/Localization/EnglishLanguage.cs @@ -39,7 +39,7 @@ namespace TShockAPI.Localization private static readonly Dictionary Buffs = new Dictionary(); - private static readonly Dictionary VanillaCommands = new Dictionary(); + private static readonly Dictionary VanillaCommandsPrefixs = new Dictionary(); internal static void Initialize() { @@ -79,9 +79,9 @@ namespace TShockAPI.Localization ChatInitializer.Load(); foreach (var command in ChatManager.Commands._localizedCommands) { - if (VanillaCommands.ContainsKey(command.Value._name)) + if (VanillaCommandsPrefixs.ContainsKey(command.Value._name)) continue; - VanillaCommands.Add(command.Value._name,command.Key.Value); + VanillaCommandsPrefixs.Add(command.Value._name,command.Key.Value); } ChatManager.Commands._localizedCommands.Clear(); } @@ -151,14 +151,14 @@ namespace TShockAPI.Localization } /// - /// Get vanilla command text in English + /// Get vanilla command prefix in English /// /// vanilla command name - /// vanilla command text English - public static string GetCommandTextByName(string name) + /// vanilla command prefix in English + public static string GetCommandPrefixByName(string name) { string commandText; - if (VanillaCommands.TryGetValue(name, out commandText)) + if (VanillaCommandsPrefixs.TryGetValue(name, out commandText)) return commandText; return null; } diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 48fab88d..9c1015bc 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1497,11 +1497,11 @@ namespace TShockAPI { if (!String.IsNullOrEmpty(text)) { - text = EnglishLanguage.GetCommandTextByName(item.Value._name) + ' ' + text; + text = EnglishLanguage.GetCommandPrefixByName(item.Value._name) + ' ' + text; } else { - text = EnglishLanguage.GetCommandTextByName(item.Value._name); + text = EnglishLanguage.GetCommandPrefixByName(item.Value._name); } break; } From 5075997264b48e27960e3446a948ecb0ea0f5a03 Mon Sep 17 00:00:00 2001 From: quicm <2648373+quicm@users.noreply.github.com> Date: Tue, 17 Dec 2024 01:13:01 +1030 Subject: [PATCH 51/77] Add bandaid fix to RemoteClient.Reset for SecAd GHSA-hvm9-wc8j-mgrc --- TShockAPI/TShock.cs | 8 ++++++++ TShockAPI/TShockAPI.csproj | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 054e31df..bcd72aa3 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -428,6 +428,8 @@ namespace TShockAPI Hooks.AccountHooks.AccountDelete += OnAccountDelete; Hooks.AccountHooks.AccountCreate += OnAccountCreate; + On.Terraria.RemoteClient.Reset += RemoteClient_Reset; + GetDataHandlers.InitGetDataHandler(); Commands.InitCommands(); @@ -496,6 +498,12 @@ namespace TShockAPI } } + 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) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index a3731709..9da8cd08 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 From 3da4bf4b45ad77f96afb261aaf92c82056c7bf4c Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Fri, 3 Jan 2025 00:05:25 +0900 Subject: [PATCH 52/77] Remove inactive people from sponsors --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8275735a..75b69d48 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] From 99701f9a8415382c8a48ecc324d988a8a579d3f0 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 4 Jan 2025 05:31:04 +0900 Subject: [PATCH 53/77] Update logo url in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5559b37..0369df53 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- TShock for Terraria
+ TShock for Terraria
AppVeyor Build Status From 1b31665a888769be8747637c5be51e81ff8cbf26 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 4 Jan 2025 05:31:27 +0900 Subject: [PATCH 54/77] Update logo in readme --- README_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_cn.md b/README_cn.md index e038817e..65c5eba6 100644 --- a/README_cn.md +++ b/README_cn.md @@ -1,5 +1,5 @@

- TShock for Terraria
+ TShock for Terraria
AppVeyor Build Status From af969898b1e54f684e48cd30bc019393725c4a00 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:17:29 +0000 Subject: [PATCH 55/77] Update dependency NuGet.Packaging to 6.3.4 [SECURITY] --- TShockPluginManager/TShockPluginManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockPluginManager/TShockPluginManager.csproj b/TShockPluginManager/TShockPluginManager.csproj index 33c503fd..774f9819 100644 --- a/TShockPluginManager/TShockPluginManager.csproj +++ b/TShockPluginManager/TShockPluginManager.csproj @@ -7,7 +7,7 @@ - + From 68bb381ece3fca103ddb6f5c2197046de70be498 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 13:24:14 +0100 Subject: [PATCH 56/77] docker: Bump docker/build-push-action to v6 --- .github/workflows/ci-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index deb811a2..41454ae4 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -15,7 +15,7 @@ jobs: - name: Set up buildx uses: docker/setup-buildx-action@v3 - name: Build image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64 From a82881b7d3a01f002c2cc15151f6c50ed5cdbc92 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 13:33:23 +0100 Subject: [PATCH 57/77] docker: Tag and push images to GHCR automatically Co-authored-by: BrailleBennett Co-authored-by: TheSuperGamer20578 --- .github/workflows/ci-docker.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 41454ae4..691c8e81 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -5,6 +5,8 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest + permissions: + packages: write steps: - name: Checkout uses: actions/checkout@v4 @@ -14,12 +16,25 @@ jobs: 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 }} - name: Build image uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64 - push: false + push: true + 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 }} From cc3d2af15f100d6b3a23a4cb252a40b67fe6e006 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 13:45:19 +0100 Subject: [PATCH 58/77] docker: Generate build provenance attestations Co-authored-by: BrailleBennett Co-authored-by: TheSuperGamer20578 --- .github/workflows/ci-docker.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 691c8e81..82e152a9 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -6,6 +6,8 @@ jobs: build: runs-on: ubuntu-latest permissions: + attestations: write + id-token: write packages: write steps: - name: Checkout @@ -28,6 +30,7 @@ jobs: with: images: ghcr.io/${{ github.repository }} - name: Build image + id: build uses: docker/build-push-action@v6 with: context: . @@ -38,3 +41,9 @@ jobs: pull: true cache-from: type=gha, scope=${{ github.workflow }} cache-to: type=gha, scope=${{ github.workflow }} + - name: Generate build provenance attestation + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true From ca3dfcdf17947b09b525fe593e2ac8bf17602a4c Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 14:04:57 +0100 Subject: [PATCH 59/77] docker: Push semver tags if a version tag is pushed --- .github/workflows/ci-docker.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 82e152a9..cb09fc93 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -29,6 +29,14 @@ jobs: 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') }} - name: Build image id: build uses: docker/build-push-action@v6 From 660ec5bc601f075860f77d4330cf2c37fa2180c2 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 14:14:38 +0100 Subject: [PATCH 60/77] docker: Restrict tagging 'latest' to version tags --- .github/workflows/ci-docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index cb09fc93..6965c773 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -37,6 +37,8 @@ jobs: 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 From cbdae5b15eedd5237199f0539ffc6d6b7d92e185 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 14:29:40 +0100 Subject: [PATCH 61/77] Add a changelog entry for publishing to GHCR --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index b1974546..b5303c5f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -93,6 +93,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Rewrote the `.dockerignore` file into a denylist. (@timschumi) * Added CI for Docker images. (@timschumi) * Fixed Cursed Flares kicking players for invalid buff. (@Arthri) +* Added automatic publishing of Docker images to GHCR. (@timschumi) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK) From d54711651b9f2732f8200db238c710561e126e2d Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 4 Jan 2025 14:36:58 +0100 Subject: [PATCH 62/77] docker: Update documentation for officially provided Docker images --- docs/docker.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index ca018510..afc4bdfc 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 using buildx and loading it into docker -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:latest \ + --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 -docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --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" +docker buildx build -t tshock:latest --load . +``` + +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 . ``` From 02ce8fdf0deac5c52a98070d9970fbd1b5654490 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sun, 5 Jan 2025 01:30:28 +0100 Subject: [PATCH 63/77] docker: Don't push to the registry for Pull Requests This requires more thought on how to handle secrets, and whether to just omit it permanently. --- .github/workflows/ci-docker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 6965c773..3d47d3c6 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -45,13 +45,14 @@ jobs: with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64 - push: true + 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 }} From d53517104ced20ce5f7b17b8351b200d20769463 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 10:45:58 +0000 Subject: [PATCH 64/77] Update dependency NuGet.Protocol to 6.3.3 [SECURITY] --- TShockPluginManager/TShockPluginManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockPluginManager/TShockPluginManager.csproj b/TShockPluginManager/TShockPluginManager.csproj index 774f9819..1d53f320 100644 --- a/TShockPluginManager/TShockPluginManager.csproj +++ b/TShockPluginManager/TShockPluginManager.csproj @@ -8,7 +8,7 @@ - + From e21322b86beb0850d4302aa9c622d5c825ea3dec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 10:58:00 +0000 Subject: [PATCH 65/77] Update dependency SharpZipLib to 1.4.2 --- TShockInstaller/TShockInstaller.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockInstaller/TShockInstaller.csproj b/TShockInstaller/TShockInstaller.csproj index 9b73d66a..ac3bfff4 100644 --- a/TShockInstaller/TShockInstaller.csproj +++ b/TShockInstaller/TShockInstaller.csproj @@ -12,6 +12,6 @@ - + From 78dd32a8374c9d8a0bc5b5a80b37c38708fb21f1 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 25 Jan 2025 21:28:24 +0900 Subject: [PATCH 66/77] Fix changelog versioning --- docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index b1974546..54b23a8a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -78,6 +78,9 @@ 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 +* You know the drill + +## 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) From b4968adb7d964f851633f840d407654e6e90b9aa Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 25 Jan 2025 22:27:48 +0900 Subject: [PATCH 67/77] Fix typo in config file Closes https://github.com/Pryaxis/TShock/pull/3052 --- TShockAPI/Configuration/TShockConfig.cs | 4 ++-- docs/changelog.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs index 79c374b2..c91466fe 100644 --- a/TShockAPI/Configuration/TShockConfig.cs +++ b/TShockAPI/Configuration/TShockConfig.cs @@ -320,8 +320,8 @@ namespace TShockAPI.Configuration [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/docs/changelog.md b/docs/changelog.md index 683cddcb..00aec595 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -79,6 +79,8 @@ Use past tense when adding new entries; sign your name off when you add or chang ## Upcoming changes * Fixed `/dump-reference-data` mutate the command names. (#2943, @sgkoishi) +* You know the drill +* Fix typo in config for IP bans. (@redchess64) ## TShock 5.2.1 * Updated `TSPlayer.GodMode`. (@AgaSpace) From 9aba57dab6d6886f61f76a4a0597116f5efe1cb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:31:02 +0000 Subject: [PATCH 68/77] Update dependency MySql.Data to 8.4.0 --- TShockAPI/TShockAPI.csproj | 2 +- TShockLauncher/TShockLauncher.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 9da8cd08..34df3c74 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -34,7 +34,7 @@ - + diff --git a/TShockLauncher/TShockLauncher.csproj b/TShockLauncher/TShockLauncher.csproj index e3c4ac32..fbe428bb 100644 --- a/TShockLauncher/TShockLauncher.csproj +++ b/TShockLauncher/TShockLauncher.csproj @@ -30,7 +30,7 @@ - + From 8246b95739b0a3e8e0e20491be7b1c7a6ae13275 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 25 Jan 2025 22:50:15 +0900 Subject: [PATCH 69/77] Disable stupid semantic commits --- renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 159ec1e2..4292e678 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,8 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base" + "config:base", + ":semanticCommitsDisabled" ], "git-submodules": { "enabled": true From 7e9b97be29849720f815eecc6daa1fa523d872a6 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sat, 25 Jan 2025 22:51:42 +0900 Subject: [PATCH 70/77] Disable renovate This is really irritating --- renovate.json | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 renovate.json diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 4292e678..00000000 --- a/renovate.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", - ":semanticCommitsDisabled" - ], - "git-submodules": { - "enabled": true - }, - "packageRules": [ - { - "matchPackageNames": ["OTAPI.Upcoming", "ModFramework", "TerrariaServerAPI"], - "ignoreUnstable": "false", - "bumpVersion": "prerelease", - "groupName": "OTAPI things" - } - ] -} From 61a81bb4ff8d0a07f9870cc776045fe3a741448b Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Sun, 26 Jan 2025 01:00:17 +0900 Subject: [PATCH 71/77] Fix merge conflict --- docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 75e8d251..841bba3b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -130,6 +130,10 @@ Use past tense when adding new entries; sign your name off when you add or chang * Allowed multiple test cases to be in TShock's test suite. (@drunderscore) * Fixed unable to use Purification/Evil Powder in jungle. (@sgkoishi) * Set the `GetDataHandledEventArgs.Player` property for the `SyncTilePicking` data handler. (@drunderscore) +* 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) +* Re-wrote tile rect handling from scratch, fixing a certain exploitable flaw in the old code and significantly reducing the potential exploit surface, potentially even down to zero. (@punchready) ## TShock 5.1.3 * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) From b984ff8b65433e54478459421dba9247df2c62bb Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sun, 26 Jan 2025 08:45:11 +0900 Subject: [PATCH 72/77] Apply patch from https://github.com/Pryaxis/TShock/pull/3018 This applies https://github.com/Pryaxis/TShock/pull/3018 as a patch per @punchready because they deleted the head repository so that we can't merge it and none of the git objects have valid refs anymore on the command line and we can't merge it on the gui. --- TShockAPI/Commands.cs | 11 ++++++----- docs/changelog.md | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index e7491632..1006b50f 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -4622,21 +4622,22 @@ namespace TShockAPI { 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 diff --git a/docs/changelog.md b/docs/changelog.md index 4866e01c..41fa2853 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -83,6 +83,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Changed the use of `Player.active` to `TSPlayer.Active` for consistency. (@sgkoishi, #2939) * Fix typo in config for IP bans. (@redchess64) * Fixed unable to transfer long response body for REST API. (@sgkoishi, #2925) +* Fixed the `/wind` command not being very helpful. (@punchready) ## TShock 5.2.1 * Updated `TSPlayer.GodMode`. (@AgaSpace) From fdee582dc74266b9e5231414c78ee34a13baf7e8 Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sun, 26 Jan 2025 09:00:41 +0900 Subject: [PATCH 73/77] Redirect ikebukuro to wiki This redirects the hosted docs on github pages to the wiki, which is easier for normal people to edit. --- docs/index.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/index.html b/docs/index.html index 2ae1df82..0cfccf11 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,12 +11,7 @@
From 52ed275be2393a496edf26430b1f7d05a8fe72b5 Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Sun, 26 Jan 2025 13:00:06 +0900 Subject: [PATCH 74/77] Fix #2895 --- TShockPluginManager/Nuget.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/TShockPluginManager/Nuget.cs b/TShockPluginManager/Nuget.cs index 9fdefb6b..1d00fe1c 100644 --- a/TShockPluginManager/Nuget.cs +++ b/TShockPluginManager/Nuget.cs @@ -30,7 +30,6 @@ using NuGet.Versioning; 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 @@ namespace TShockPluginManager // 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 @@ namespace TShockPluginManager 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); + } } } } From 74a07150f82eefb4a3359bb7f3b0911f91d8f5ae Mon Sep 17 00:00:00 2001 From: Lucas Nicodemus Date: Sun, 26 Jan 2025 13:37:38 +0900 Subject: [PATCH 75/77] Update submodule --- TerrariaServerAPI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TerrariaServerAPI b/TerrariaServerAPI index 8a3fffd7..d4bb7e3a 160000 --- a/TerrariaServerAPI +++ b/TerrariaServerAPI @@ -1 +1 @@ -Subproject commit 8a3fffd71db401736ea80619122c70c449c10ff3 +Subproject commit d4bb7e3a21e875cfeb23bcf5cf847c85d9470ccf From c4a141308e58d4c7cc9ac07fac8143434597b13d Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Sun, 26 Jan 2025 15:11:15 +0900 Subject: [PATCH 76/77] Automatic language detection --- TShockAPI/I18n.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TShockAPI/I18n.cs b/TShockAPI/I18n.cs index e8f5eedb..ac2985d0 100644 --- a/TShockAPI/I18n.cs +++ b/TShockAPI/I18n.cs @@ -60,6 +60,12 @@ namespace TShockAPI } } + 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; } } From 605be8f813dcc01cd24e3c422d545dea720f5b0c Mon Sep 17 00:00:00 2001 From: SGKoishi Date: Mon, 27 Jan 2025 08:11:36 +0900 Subject: [PATCH 77/77] Detect xterm compatibility to avoid console spam --- TShockAPI/Utils.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index 23b1e224..3b9c0286 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -1149,11 +1149,15 @@ namespace TShockAPI /// 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.