diff --git a/TShockAPI/Handlers/SendTileSquareHandler.cs b/TShockAPI/Handlers/SendTileSquareHandler.cs index 273c4d35..c5c90eb2 100644 --- a/TShockAPI/Handlers/SendTileSquareHandler.cs +++ b/TShockAPI/Handlers/SendTileSquareHandler.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Terraria; +using Terraria.DataStructures; +using Terraria.GameContent.Tile_Entities; using Terraria.ID; using Terraria.ObjectData; using TShockAPI.Net; @@ -15,9 +17,9 @@ namespace TShockAPI.Handlers { Dictionary> _grassToPlantMap = new Dictionary> { - { TileID.Grass, new List { TileID.Plants, TileID.Plants2 } }, - { TileID.HallowedGrass, new List { TileID.HallowedPlants, TileID.HallowedPlants2 } }, - { TileID.JungleGrass, new List { TileID.JunglePlants, TileID.JunglePlants2 } } + { TileID.Grass, new List { TileID.Plants, TileID.Plants2 } }, + { TileID.HallowedGrass, new List { TileID.HallowedPlants, TileID.HallowedPlants2 } }, + { TileID.JungleGrass, new List { TileID.JunglePlants, TileID.JunglePlants2 } } }; List _flowerBootItems = new List @@ -26,6 +28,18 @@ namespace TShockAPI.Handlers ItemID.FairyBoots }; + 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 } + }; + /// /// Updates a single tile /// @@ -89,6 +103,8 @@ namespace TShockAPI.Handlers } tile.slope(slope); + + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare updated a tile from type {0} to {1}", tile.type, newTile.Type); } /// @@ -97,30 +113,65 @@ namespace TShockAPI.Handlers /// /// /// - /// + /// /// - internal bool HandleFlowerBoots(int realx, int realy, NetTile newTile, TSPlayer player) + internal void HandleFlowerBoots(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)) { - return true; + return; } ITile tile = Main.tile[realx, realy + 1]; if (!_grassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) { - return true; + return; } - return false; + UpdateTile(Main.tile[realx, realy], newTile); + } + + /// + /// + /// + /// + /// + 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); + } } internal void ProcessSingleTile(int realx, int realy, NetTile newTile, int squareSize, GetDataHandlers.SendTileSquareEventArgs args) { if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && _flowerBootItems.Contains(a.type))) { - args.Handled = HandleFlowerBoots(realx, realy, newTile, args.Player); + HandleFlowerBoots(realx, realy, newTile, args); return; } @@ -136,19 +187,86 @@ namespace TShockAPI.Handlers UpdateTile(tile, newTile); } + HandleConversionSpreads(Main.tile[realx, realy], newTile); } /// /// /// + /// /// /// /// /// - /// - public void ProcessTileObject(TileObjectData data, NetTile[,] newTiles, int realx, int realy, TSPlayer player) + /// + public void ProcessTileObject(int tileType, TileObjectData data, NetTile[,] newTiles, int realx, int realy, 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, data.Width, data.Height)) + { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from no permission for tile object from {0}", args.Player.Name); + return; + } + if (tileType == TileID.ClosedDoor || tileType == TileID.OpenDoor) + { + ProcessDoor(realx, realy, newTiles, args); + return; + } + + for (int x = 0; x < data.Width; x++) + { + for (int y = 0; y < data.Height; y++) + { + // Update all tiles in the tile object + UpdateTile(Main.tile[realx + x, realy + y], newTiles[x, y]); + } + } + + args.Handled = false; + } + + private void ProcessDoor(int realx, int realy, NetTile[,] newTiles, GetDataHandlers.SendTileSquareEventArgs args) + { + //If we handle this then doors disappear... We only send back the tile square if args.Handled == true, so somewhere we're either breaking or not implementing the tile square properly + ///Calling UpdateTile on all tiles in newTiles doesn't help either. + //Need to figure out what's differing from Vanilla in this regard + args.Handled = false; + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing door from {0}", args.Player.Name); + } + + static List symbols = new List { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y' }; + void VisualiseTileSquare(NetTile[,] tiles, int size) + { + Dictionary tileToCharMap = new Dictionary(); + int lastCharIndex = 0; + + StringBuilder sb = new StringBuilder("\n"); + + for (int y = 0; y < size; y++) + { + for (int x = 0; x < size; x++) + { + NetTile tile = tiles[x, y]; + char symbol; + if (tileToCharMap.ContainsKey(tile.Type)) + { + symbol = tileToCharMap[tile.Type]; + } + else + { + symbol = symbols[lastCharIndex++]; + tileToCharMap.Add(tile.Type, symbol); + } + + sb.Append(symbol); + } + sb.Append("\n"); + } + sb.Append("Key: " + String.Join(", ", tileToCharMap.Select(kvp => $"{kvp.Value} = {kvp.Key}"))); + + TShock.Log.ConsoleDebug(sb.ToString()); } /// @@ -162,6 +280,9 @@ namespace TShockAPI.Handlers 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); @@ -174,7 +295,6 @@ namespace TShockAPI.Handlers if (size > 5) { TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from non-vanilla tilemod from {0}", args.Player.Name); - args.Handled = true; return; } @@ -182,7 +302,6 @@ namespace TShockAPI.Handlers { TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from throttle from {0}", args.Player.Name); args.Player.SendTileSquare(tileX, tileY, size); - args.Handled = true; return; } @@ -190,7 +309,6 @@ namespace TShockAPI.Handlers { TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from being disabled from {0}", args.Player.Name); args.Player.SendTileSquare(tileX, tileY, size); - args.Handled = true; return; } @@ -204,13 +322,18 @@ namespace TShockAPI.Handlers } } + VisualiseTileSquare(tiles, size); + for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare tile ({0}, {1})", x, y); + // Do not handle already processed tiles if (processed[x, y]) { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare skipping processed tile from {0}", args.Player.Name); continue; } @@ -218,7 +341,7 @@ namespace TShockAPI.Handlers int realy = tileY + y; if ((realx < 0 || realx >= Main.maxTilesX) - || (realy < 0 || realy < Main.maxTilesY)) + || (realy < 0 || realy > Main.maxTilesY)) { processed[x, y] = true; continue; @@ -245,17 +368,38 @@ namespace TShockAPI.Handlers } } - ProcessTileObject(data, newTiles, realx, realy, args.Player); + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing tile object ({1}) from {0}", args.Player.Name, newTile.Type); + ProcessTileObject(newTile.Type, data, newTiles, realx, realy, args); continue; } + if (Main.tile[realx, realy].type == TileID.ClosedDoor || Main.tile[realx, realy].type == TileID.OpenDoor) + { + NetTile[,] 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]; + processed[x + i, y + j] = true; + } + } + + ProcessDoor(realx, realy, newTiles, args); + continue; + } + + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing single tile ({1}->{2}) from {0}", args.Player.Name, Main.tile[realx, realy].type, newTile.Type); ProcessSingleTile(realx, realy, newTile, size, args); processed[x, y] = true; } } - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from spaghetti from {0}", args.Player.Name); - args.Handled = true; + if (args.Handled == true) + { + args.Player.SendTileSquare(tileX, tileY, size); + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from spaghetti from {0}", args.Player.Name); + } } }