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.