diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 1cb76148..ed2b2e71 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -43,12 +43,12 @@ namespace TShockAPI internal Bouncer() { STSHandler = new Handlers.SendTileSquareHandler(); + GetDataHandlers.SendTileSquare += STSHandler.OnReceiveSendTileSquare; // Setup hooks GetDataHandlers.GetSection += OnGetSection; GetDataHandlers.PlayerUpdate += OnPlayerUpdate; GetDataHandlers.TileEdit += OnTileEdit; - GetDataHandlers.SendTileSquare += STSHandler.OnReceiveSendTileSquare; GetDataHandlers.ItemDrop += OnItemDrop; GetDataHandlers.NewProjectile += OnNewProjectile; GetDataHandlers.NPCStrike += OnNPCStrike; diff --git a/TShockAPI/Handlers/SendTileSquareHandler.cs b/TShockAPI/Handlers/SendTileSquareHandler.cs index 15732007..48462a03 100644 --- a/TShockAPI/Handlers/SendTileSquareHandler.cs +++ b/TShockAPI/Handlers/SendTileSquareHandler.cs @@ -12,7 +12,7 @@ using TShockAPI.Net; namespace TShockAPI.Handlers { /// - /// Provides processors and handling for Tile Square packets + /// Provides processors for handling Tile Square packets /// public class SendTileSquareHandler { @@ -35,6 +35,11 @@ namespace TShockAPI.Handlers 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 + /// Dictionary _tileEntityIdToTileIdMap = new Dictionary { { TileID.TargetDummy, TETrainingDummy._myEntityID }, @@ -48,11 +53,257 @@ namespace TShockAPI.Handlers }; /// - /// Syncs a single tile on the server with a tile from the tile square packet + /// Invoked when a SendTileSquare packet is received + /// + /// + /// + public void OnReceiveSendTileSquare(object sender, GetDataHandlers.SendTileSquareEventArgs args) + { + // By default, we'll handle everything + args.Handled = true; + + if (ShouldSkipProcessing(args)) + { + return; + } + + bool[,] processed = new bool[args.Size, args.Size]; + NetTile[,] tiles = ReadNetTilesFromStream(args.Data, args.Size); + + Debug.VisualiseTileSetDiff(args.TileX, args.TileY, args.Size, args.Size, tiles); + + IterateTileSquare(tiles, processed, args); + + // Uncommenting this function will send the same tile square 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks. + // Use in test worlds only. + //Debug.DisplayTileSetInGame(tileX, tileY - 10, size, size, 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) + { + args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from carbonara from {0}", args.Player.Name); + } + } + + /// + /// Iterates over each tile in the tile square and performs processing on individual tiles or multi-tile Tile Objects + /// + /// + /// + /// + internal void IterateTileSquare(NetTile[,] tiles, bool[,] processed, GetDataHandlers.SendTileSquareEventArgs args) + { + short size = args.Size; + int tileX = args.TileX; + int tileY = args.TileY; + + for (int x = 0; x < size; x++) + { + for (int y = 0; y < size; 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) + { + data = TileObjectData._data[newTile.Type]; + NetTile[,] newTiles; + int width = data.Width; + int height = data.Height; + int offset = 0; + + if (newTile.Type == TileID.TrapdoorClosed) + { + // Trapdoors can modify a 2x3 space. When it closes it will have leftover tiles either on top or bottom. + // If we don't update these tiles, the trapdoor gets confused and disappears. + // So we capture all 6 possible tiles and offset ourselves 1 tile above the closed trapdoor to capture the entire 2x3 area + width = 2; + height = 3; + offset = -1; + } + + newTiles = new NetTile[width, height]; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + newTiles[i, j] = tiles[x + i, y + j + offset]; + processed[x + i, y + j] = true; + } + } + ProcessTileObject(newTile.Type, realX, realY + offset, width, height, newTiles, args); + continue; + } + + // If the new tile does not have an associated tile object, process it as an individual tile + ProcessSingleTile(realX, realY, newTile, size, args); + processed[x, y] = true; + } + } + } + + /// + /// Processes a tile object consisting of multiple tiles from the tile square 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 + /// SendTileSquareEventArgs containing event information + internal void ProcessTileObject(int tileType, int realX, int realY, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileSquareEventArgs 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("Bouncer / SendTileSquare rejected from no permission for tile object from {0}", args.Player.Name); + return; + } + + // 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 square 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 size of the tile square being received + /// SendTileSquareEventArgs containing event information + internal void ProcessSingleTile(int realX, int realY, NetTile newTile, int squareSize, GetDataHandlers.SendTileSquareEventArgs args) + { + // Some boots allow growing flowers on grass. This process sends a 1x1 tile square to grow the flowers + // The square size must be 1 and the player must have an accessory that allows growing flowers in order for this square to be valid + if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && _flowerBootItems.Contains(a.type))) + { + ProcessFlowerBoots(realX, realY, newTile, args); + return; + } + + ITile tile = Main.tile[realX, realY]; + + if (tile.type == TileID.LandMine && !newTile.Active) + { + UpdateServerTileState(tile, newTile); + } + + if (tile.type == TileID.WirePipe) + { + UpdateServerTileState(tile, newTile); + } + + ProcessConversionSpreads(Main.tile[realX, realY], newTile); + + // All other single tile updates should not be processed. + } + + /// + /// Applies changes to a tile if a tile square for flower-growing boots is valid + /// + /// The tile x position of the tile square packet - this is where the flowers are intending to grow + /// The tile y position of the tile square packet - this is where the flowers are intending to grow + /// The NetTile containing information about the flowers that are being grown + /// SendTileSquareEventArgs containing event information + internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile, GetDataHandlers.SendTileSquareEventArgs args) + { + // We need to get the tile below the tile square to determine what grass types are allowed + if (!WorldGen.InWorld(realX, realY + 1)) + { + // If the tile below the tile square isn't valid, we return here and don't update the server tile state + return; + } + + ITile tile = Main.tile[realX, realY + 1]; + if (!_grassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) + { + // If the tile below the tile square isn't a valid plant tile (eg grass) then we don't update the server tile state + return; + } + + UpdateServerTileState(Main.tile[realX, realY], newTile); + } + + /// + /// 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 - public static void UpdateTile(ITile tile, NetTile newTile) + internal void ProcessConversionSpreads(ITile tile, NetTile newTile) + { + // Update if the existing tile or wall is convertible and the new tile or wall is a valid conversion + if ( + ((TileID.Sets.Conversion.Stone[tile.type] || Main.tileMoss[tile.type]) && (TileID.Sets.Conversion.Stone[newTile.Type] || Main.tileMoss[newTile.Type])) || + ((tile.type == 0 || tile.type == 59) && (newTile.Type == 0 || newTile.Type == 59)) || + TileID.Sets.Conversion.Grass[tile.type] && TileID.Sets.Conversion.Grass[newTile.Type] || + TileID.Sets.Conversion.Ice[tile.type] && TileID.Sets.Conversion.Ice[newTile.Type] || + TileID.Sets.Conversion.Sand[tile.type] && TileID.Sets.Conversion.Sand[newTile.Type] || + TileID.Sets.Conversion.Sandstone[tile.type] && TileID.Sets.Conversion.Sandstone[newTile.Type] || + TileID.Sets.Conversion.HardenedSand[tile.type] && TileID.Sets.Conversion.HardenedSand[newTile.Type] || + TileID.Sets.Conversion.Thorn[tile.type] && TileID.Sets.Conversion.Thorn[newTile.Type] || + TileID.Sets.Conversion.Moss[tile.type] && TileID.Sets.Conversion.Moss[newTile.Type] || + TileID.Sets.Conversion.MossBrick[tile.type] && TileID.Sets.Conversion.MossBrick[newTile.Type] || + WallID.Sets.Conversion.Stone[tile.wall] && WallID.Sets.Conversion.Stone[newTile.Wall] || + WallID.Sets.Conversion.Grass[tile.wall] && WallID.Sets.Conversion.Grass[newTile.Wall] || + WallID.Sets.Conversion.Sandstone[tile.wall] && WallID.Sets.Conversion.Sandstone[newTile.Wall] || + WallID.Sets.Conversion.HardenedSand[tile.wall] && WallID.Sets.Conversion.HardenedSand[newTile.Wall] || + WallID.Sets.Conversion.PureSand[tile.wall] && WallID.Sets.Conversion.PureSand[newTile.Wall] || + WallID.Sets.Conversion.NewWall1[tile.wall] && WallID.Sets.Conversion.NewWall1[newTile.Wall] || + WallID.Sets.Conversion.NewWall2[tile.wall] && WallID.Sets.Conversion.NewWall2[newTile.Wall] || + WallID.Sets.Conversion.NewWall3[tile.wall] && WallID.Sets.Conversion.NewWall3[newTile.Wall] || + WallID.Sets.Conversion.NewWall4[tile.wall] && WallID.Sets.Conversion.NewWall4[newTile.Wall] + ) + { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing a conversion update - [{0}|{1}] -> [{2}|{3}]", tile.type, tile.wall, newTile.Type, newTile.Wall); + UpdateServerTileState(tile, newTile); + } + } + + /// + /// Updates a single tile's world state with a change from the tile square packet + /// + /// The tile to update + /// The NetTile containing the change + public static void UpdateServerTileState(ITile tile, NetTile newTile) { tile.active(newTile.Active); tile.type = newTile.Type; @@ -111,293 +362,80 @@ namespace TShockAPI.Handlers } /// - /// Determines if a Tile Square for flower-growing boots should be accepted or not + /// Performs on multiple tiles /// - /// The tile x position of the tile square packet - this is where the flowers are intending to grow - /// The tile y position of the tile square packet - this is where the flowers are intending to grow - /// The NetTile containing information about the flowers that are being grown - /// SendTileSquareEventArgs containing event information - internal void HandleFlowerBoots(int realx, int realy, NetTile newTile, GetDataHandlers.SendTileSquareEventArgs args) + /// + /// + /// + /// + /// + public static void UpdateMultipleServerTileStates(int x, int y, int width, int height, NetTile[,] newTiles) { - // We need to get the tile below the tile square to determine what grass types are allowed - if (!WorldGen.InWorld(realx, realy + 1)) + for (int i = 0; i < width; i++) { - // If the tile below the tile square isn't valid, we return here and don't update the server tile state - return; - } - - ITile tile = Main.tile[realx, realy + 1]; - if (!_grassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) - { - // If the tile below the tile square isn't a valid plant tile (eg grass) then we don't update the server tile state - return; - } - - UpdateTile(Main.tile[realx, realy], newTile); - } - - /// - /// 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 HandleConversionSpreads(ITile tile, NetTile newTile) - { - // Update if the existing tile or wall is convertible and the new tile or wall is a valid conversion - if ( - ((TileID.Sets.Conversion.Stone[tile.type] || Main.tileMoss[tile.type]) && (TileID.Sets.Conversion.Stone[newTile.Type] || Main.tileMoss[newTile.Type])) || - ((tile.type == 0 || tile.type == 59) && (newTile.Type == 0 || newTile.Type == 59)) || - TileID.Sets.Conversion.Grass[tile.type] && TileID.Sets.Conversion.Grass[newTile.Type] || - TileID.Sets.Conversion.Ice[tile.type] && TileID.Sets.Conversion.Ice[newTile.Type] || - TileID.Sets.Conversion.Sand[tile.type] && TileID.Sets.Conversion.Sand[newTile.Type] || - TileID.Sets.Conversion.Sandstone[tile.type] && TileID.Sets.Conversion.Sandstone[newTile.Type] || - TileID.Sets.Conversion.HardenedSand[tile.type] && TileID.Sets.Conversion.HardenedSand[newTile.Type] || - TileID.Sets.Conversion.Thorn[tile.type] && TileID.Sets.Conversion.Thorn[newTile.Type] || - TileID.Sets.Conversion.Moss[tile.type] && TileID.Sets.Conversion.Moss[newTile.Type] || - TileID.Sets.Conversion.MossBrick[tile.type] && TileID.Sets.Conversion.MossBrick[newTile.Type] || - WallID.Sets.Conversion.Stone[tile.wall] && WallID.Sets.Conversion.Stone[newTile.Wall] || - WallID.Sets.Conversion.Grass[tile.wall] && WallID.Sets.Conversion.Grass[newTile.Wall] || - WallID.Sets.Conversion.Sandstone[tile.wall] && WallID.Sets.Conversion.Sandstone[newTile.Wall] || - WallID.Sets.Conversion.HardenedSand[tile.wall] && WallID.Sets.Conversion.HardenedSand[newTile.Wall] || - WallID.Sets.Conversion.PureSand[tile.wall] && WallID.Sets.Conversion.PureSand[newTile.Wall] || - WallID.Sets.Conversion.NewWall1[tile.wall] && WallID.Sets.Conversion.NewWall1[newTile.Wall] || - WallID.Sets.Conversion.NewWall2[tile.wall] && WallID.Sets.Conversion.NewWall2[newTile.Wall] || - WallID.Sets.Conversion.NewWall3[tile.wall] && WallID.Sets.Conversion.NewWall3[newTile.Wall] || - WallID.Sets.Conversion.NewWall4[tile.wall] && WallID.Sets.Conversion.NewWall4[newTile.Wall] - ) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing a conversion update - [{0}|{1}] -> [{2}|{3}]", tile.type, tile.wall, newTile.Type, newTile.Wall); - UpdateTile(tile, newTile); - } - } - - /// - /// Processes a single tile from the tile square 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 size of the tile square being received - /// SendTileSquareEventArgs containing event information - internal void ProcessSingleTile(int realx, int realy, NetTile newTile, int squareSize, GetDataHandlers.SendTileSquareEventArgs args) - { - // Some boots allow growing flowers on grass. This process sends a 1x1 tile square to grow the flowers - // The square size must be 1 and the player must have an accessory that allows growing flowers in order for this square to be valid - if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && _flowerBootItems.Contains(a.type))) - { - HandleFlowerBoots(realx, realy, newTile, args); - return; - } - - ITile tile = Main.tile[realx, realy]; - - if (tile.type == TileID.LandMine && !newTile.Active) - { - UpdateTile(tile, newTile); - } - - if (tile.type == TileID.WirePipe) - { - UpdateTile(tile, newTile); - } - - HandleConversionSpreads(Main.tile[realx, realy], newTile); - - // All other single tile updates should not be processed. - } - - /// - /// Processes a tile object consisting of multiple tiles from the tile square 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 - /// SendTileSquareEventArgs containing event information - public void ProcessTileObject(int tileType, int realx, int realy, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileSquareEventArgs 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("Bouncer / SendTileSquare rejected from no permission for tile object from {0}", args.Player.Name); - return; - } - - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) + for (int j = 0; j < height; j++) { - // Update all tiles in the tile object. These will be synced back to the player later - UpdateTile(Main.tile[realx + x, realy + y], newTiles[x, y]); - } - } - - // Tile entities have special placements that we should let the game deal with - if (_tileEntityIdToTileIdMap.ContainsKey(tileType)) - { - TileEntity.PlaceEntityNet(realx, realy, _tileEntityIdToTileIdMap[tileType]); - } - } - - private void ProcessClosedTrapDoor(int realx, int realy, NetTile[,] newTiles) - { - for (int x = 0; x < 2; x++) - { - for (int y = 0; y < 3; y++) - { - UpdateTile(Main.tile[realx + x, realy + y], newTiles[x, y]); + UpdateServerTileState(Main.tile[x + i, y + j], newTiles[i, j]); } } } /// - /// Invoked when a SendTileSquare packet is received + /// Reads a set of NetTiles from a memory stream /// - /// - /// - public void OnReceiveSendTileSquare(object sender, GetDataHandlers.SendTileSquareEventArgs args) + /// + /// + /// + static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, int size) { - short size = args.Size; - int tileX = args.TileX; - int tileY = args.TileY; - - // By default, we'll handle everything - args.Handled = true; - - if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare accepted clientside world edit from {0}", args.Player.Name); - args.Handled = false; - return; - } - - // 5x5 is the largest vanilla-sized tile square. Anything larger than this should not be seen in the vanilla game and should be rejected - if (size > 5) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from non-vanilla tilemod from {0}", args.Player.Name); - return; - } - - if (args.Player.IsBouncerThrottled()) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from throttle from {0}", args.Player.Name); - args.Player.SendTileSquare(tileX, tileY, size); - return; - } - - if (args.Player.IsBeingDisabled()) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from being disabled from {0}", args.Player.Name); - args.Player.SendTileSquare(tileX, tileY, size); - return; - } - - bool[,] processed = new bool[size, size]; NetTile[,] tiles = new NetTile[size, size]; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { - tiles[x, y] = new NetTile(args.Data); + tiles[x, y] = new NetTile(stream); } } - Debug.VisualiseTileSetDiff(tileX, tileY, size, size, tiles); + return tiles; + } - for (int x = 0; x < size; x++) + /// + /// Determines whether or not the tile square should be immediately accepted or rejected + /// + /// + /// + static bool ShouldSkipProcessing(GetDataHandlers.SendTileSquareEventArgs args) + { + if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) { - for (int y = 0; y < size; 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)) - { - 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 && (data = TileObjectData._data[newTile.Type]) != null) - { - NetTile[,] newTiles; - - if (newTile.Type == TileID.TrapdoorClosed) - { - // Trapdoors can modify a 2x3 space. When it closes it will have leftover tiles either on top or bottom. - // If we don't update these tiles, the trapdoor gets confused and disappears. - // So we capture all 6 possible tiles and process them all - newTiles = new NetTile[2, 3]; - - for (int i = 0; i < 2; i++) - { - for (int j = 0; j < 3; j++) - { - newTiles[i, j] = tiles[x + i, y + j - 1]; // -1 will give us the tile above the closed door, the closed door, the tile below the closed door - processed[x + i, y + j] = true; - } - } - // Use realy - 1 to target the correct tile, otherwise we will write a closed trapdoor a block below where it should be - ProcessTileObject(newTile.Type, realx, realy - 1, width: 2, height: 3, newTiles, args); - } - else - { - newTiles = new NetTile[data.Width, data.Height]; - - for (int i = 0; i < data.Width; i++) - { - for (int j = 0; j < data.Height; j++) - { - newTiles[i, j] = tiles[x + i, y + j]; - processed[x + i, y + j] = true; - } - } - ProcessTileObject(newTile.Type, realx, realy, data.Width, data.Height, newTiles, args); - } - - continue; - } - - // If the new tile does not have an associated tile object, process it as an individual tile - ProcessSingleTile(realx, realy, newTile, size, args); - processed[x, y] = true; - } + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare accepted clientside world edit from {0}", args.Player.Name); + args.Handled = false; + return true; } - // Uncommenting this function will send the same tile square 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks. - // Use in test worlds only. - //Debug.DisplayTileSetInGame(tileX, tileY - 10, size, size, tiles, args.Player); - Debug.VisualiseTileSetDiff(tileX, tileY, size, size, tiles); - - // 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) + // 5x5 is the largest vanilla-sized tile square. Anything larger than this should not be seen in the vanilla game and should be rejected + if (args.Size > 5) { - args.Player.SendTileSquare(tileX, tileY, size + 1); - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from spaghetti from {0}", args.Player.Name); + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from non-vanilla tilemod from {0}", args.Player.Name); + return true; } + + if (args.Player.IsBouncerThrottled()) + { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from throttle from {0}", args.Player.Name); + args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); + return true; + } + + if (args.Player.IsBeingDisabled()) + { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from being disabled from {0}", args.Player.Name); + args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); + return true; + } + + return false; } class Debug @@ -453,10 +491,10 @@ namespace TShockAPI.Handlers { for (int y = 0; y < height; y++) { - UpdateTile(Main.tile[tileX + x, tileY + y], newTiles[x, y]); + UpdateServerTileState(Main.tile[tileX + x, tileY + y], newTiles[x, y]); } //Add a line of dirt blocks at the bottom for safety - UpdateTile(Main.tile[tileX + x, tileY + height], new NetTile { Active = true, Type = 0 }); + UpdateServerTileState(Main.tile[tileX + x, tileY + height], new NetTile { Active = true, Type = 0 }); } player.SendTileSquare(tileX, tileY, Math.Max(width, height) + 1); diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 6751c0c2..4a85f590 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -681,7 +681,8 @@ namespace TShockAPI } /// - /// Determines if the player can build a tile object on a given point. + /// Determines if the player can build a multi-block tile object on a given point. + /// Tile objects include things like Doors, Trap Doors, Item Frames, Beds, and Dressers. /// /// The x coordinate they want to build at. /// The y coordinate they want to build at.