From 7178a2179e2f494fe09c98329cfdbbe926a76fd3 Mon Sep 17 00:00:00 2001 From: xien <2383759126@qq.com> Date: Sat, 13 Apr 2024 16:17:25 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=EF=BC=9ARegionView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugin.sln | 10 + RegionView/README.md | 28 ++ RegionView/Region.cs | 222 +++++++++++++ RegionView/RegionPlayer.cs | 27 ++ RegionView/RegionView.cs | 607 +++++++++++++++++++++++++++++++++++ RegionView/RegionView.csproj | 3 + 6 files changed, 897 insertions(+) create mode 100644 RegionView/README.md create mode 100644 RegionView/Region.cs create mode 100644 RegionView/RegionPlayer.cs create mode 100644 RegionView/RegionView.cs create mode 100644 RegionView/RegionView.csproj diff --git a/Plugin.sln b/Plugin.sln index 8daf95306..22f393901 100644 --- a/Plugin.sln +++ b/Plugin.sln @@ -60,6 +60,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecipesBrowser", "RecipesBr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TownNPCHomes", "TownNPCHomes\TownNPCHomes.csproj", "{6FAF2ED2-38B1-46C7-83F1-B909D260D70E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegionView", "RegionView\RegionView.csproj", "{41B3DE8D-F7B2-44E0-AD35-971140F49E81}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -276,6 +278,14 @@ Global {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|Any CPU.Build.0 = Release|Any CPU {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|x64.ActiveCfg = Release|Any CPU {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|x64.Build.0 = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|x64.ActiveCfg = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|x64.Build.0 = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|Any CPU.Build.0 = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|x64.ActiveCfg = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RegionView/README.md b/RegionView/README.md new file mode 100644 index 000000000..821b81315 --- /dev/null +++ b/RegionView/README.md @@ -0,0 +1,28 @@ +# RegionView 区域显示 + +- 作者: TBC开发者团队,肝帝熙恩 +- 出处: github +- 为区域添加边界显示,仅自己视角可见 + +## 更新日志 + +``` +暂无 +``` + +## 指令 + +| 语法 | 权限 | 说明 | +| -------------- | :-----------------: | :------: | +| /regionview <区域名称> 或 /rv <区域名称> | regionvision.regionview | 显示指定区域边界| +| /regionclear 或 /rc | regionvision.regionview | 取消显示所有区域边界| +| /regionviewnear 或 /rvn | regionvision.regionviewnear | 显示周围所有区域边界| + +## 配置 + +```json +暂无 +``` +## 反馈 +- 共同维护的插件库:https://github.com/THEXN/TShockPlugin/ +- 国内社区trhub.cn 或 TShock官方群等 \ No newline at end of file diff --git a/RegionView/Region.cs b/RegionView/Region.cs new file mode 100644 index 000000000..96dd13fdc --- /dev/null +++ b/RegionView/Region.cs @@ -0,0 +1,222 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Utilities; +using TShockAPI; + +namespace RegionView +{ + public class Region + { + private Tile[]? RealTiles; + public const int MaximumSize = 256; + + public Rectangle Area; + public Rectangle ShowArea; + + public string Name { get; } + + public byte Color { get; } + + public bool Command { get; set; } + + public Region(string name, Rectangle area, bool command = true) + { + Name = name; + Area = area; + ShowArea = area; + Command = command; + + var total = 0; + for (var i = 0; i < name.Length; i++) total += name[i]; + Color = (byte)(total % 12 + 13); + } + + public void CalculateArea(TSPlayer tPlayer) + { + ShowArea = Area; + + // If the region is large, only part of its border will be shown. + if (ShowArea.Width >= MaximumSize) + { + ShowArea.X = (int)(tPlayer.X / 16) - MaximumSize / 2; + ShowArea.Width = MaximumSize - 1; + + if (ShowArea.Left < Area.Left) + ShowArea.X = Area.Left; + else if (ShowArea.Right > Area.Right) + ShowArea.X = Area.Right - (MaximumSize - 1); + } + if (ShowArea.Height >= MaximumSize) + { + ShowArea.Y = (int)(tPlayer.Y / 16) - MaximumSize / 2; + ShowArea.Height = MaximumSize - 1; + + if (ShowArea.Top < Area.Top) + ShowArea.Y = Area.Top; + else if (ShowArea.Bottom > Area.Bottom) + ShowArea.Y = Area.Bottom - (MaximumSize - 1); + } + + // Ensure the region boundary is within the world. + if (ShowArea.Left < 1) + ShowArea.X = 1; + else if (ShowArea.Left >= Main.maxTilesX - 1) + ShowArea.X = Main.maxTilesX - 1; + + if (ShowArea.Top < 1) + ShowArea.Y = 1; + else if (ShowArea.Top >= Main.maxTilesY - 1) + ShowArea.Y = Main.maxTilesY - 1; + + if (ShowArea.Right >= Main.maxTilesX - 1) + ShowArea.Width = Main.maxTilesX - ShowArea.X - 2; + + if (ShowArea.Bottom >= Main.maxTilesY - 1) + ShowArea.Height = Main.maxTilesY - ShowArea.Y - 2; + } + + /// Spawns fake tiles for the region border. + /// Fake tiles have already been set, which would cause a desync. + public void SetFakeTiles() + { + int d; var index = 0; + + if (RealTiles != null) throw new InvalidOperationException("该区域已设置虚拟图块。"); + + // Initialise the temporary tile array. + if (ShowArea.Width == 0) + RealTiles = new Tile[ShowArea.Height + 1]; + else if (ShowArea.Height == 0) + RealTiles = new Tile[ShowArea.Width + 1]; + else + RealTiles = new Tile[(ShowArea.Width + ShowArea.Height) * 2]; + + // Top boundary + if (ShowArea.Top == Area.Top) + for (d = 0; d <= ShowArea.Width; d++) + SetFakeTile(index++, ShowArea.Left + d, ShowArea.Top); + // East boundary + if (ShowArea.Right == Area.Right) + for (d = 1; d <= ShowArea.Height; d++) + SetFakeTile(index++, ShowArea.Right, ShowArea.Top + d); + // West boundary + if (ShowArea.Width > 0 && ShowArea.Left == Area.Left) + for (d = 1; d <= ShowArea.Height; d++) + SetFakeTile(index++, ShowArea.Left, ShowArea.Top + d); + // Bottom boundary + if (ShowArea.Height > 0 && ShowArea.Bottom == Area.Bottom) + for (d = 1; d < ShowArea.Width; d++) + SetFakeTile(index++, ShowArea.Left + d, ShowArea.Bottom); + } + + /// Removes fake tiles for the region, reverting to the real tiles. + /// Fake tiles have not been set. + public void UnsetFakeTiles() + { + int d; var index = 0; + + if (RealTiles == null) + throw new InvalidOperationException("区域未设置虚拟图块。"); + + // Top boundary + if (ShowArea.Top == Area.Top) + for (d = 0; d <= ShowArea.Width; d++) + UnsetFakeTile(index++, ShowArea.Left + d, ShowArea.Top); + // East boundary + if (ShowArea.Right == Area.Right) + for (d = 1; d <= ShowArea.Height; d++) + UnsetFakeTile(index++, ShowArea.Right, ShowArea.Top + d); + // West boundary + if (ShowArea.Width > 0 && ShowArea.Left == Area.Left) + for (d = 1; d <= ShowArea.Height; d++) + UnsetFakeTile(index++, ShowArea.Left, ShowArea.Top + d); + // Bottom boundary + if (ShowArea.Height > 0 && ShowArea.Bottom == Area.Bottom) + for (d = 1; d < ShowArea.Width; d++) + UnsetFakeTile(index++, ShowArea.Left + d, ShowArea.Bottom); + + RealTiles = null; + } + + /// Adds a single fake tile. If a tile exists, this will replace it with a painted clone. Otherwise, this will place an inactive magical ice tile with the same paint. + /// The index in the realTile array into which to store the existing tile + /// The x coordinate of the tile position + /// The y coordinate of the tile position + public void SetFakeTile(int index, int x, int y) + { + if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY) + return; + + if (RealTiles == null) + throw new InvalidOperationException("区域尚未设置虚拟图块。"); + + ITile fakeTile; + if (Main.tile[x, y] == null) + { + fakeTile = new Tile(); + } + else + { + // As of API version 1.22, Main.tile.get now only returns a link to the tile data heap, and the tile was getting lost at Main.tile[x, y] = fakeTile. + // This is why we actually have to copy the tile now. + RealTiles[index] = new Tile(Main.tile[x, y]); + fakeTile = Main.tile[x, y]; + } + + if (RealTiles[index] != null && RealTiles[index].active()) + { + // There's already a tile there; apply paint. + if (fakeTile.type == Terraria.ID.TileID.RainbowBrick) fakeTile.type = Terraria.ID.TileID.GrayBrick; + + fakeTile.color(Color); + } + else + { + // There isn't a tile there; place an ice block. + if (Main.rand == null) Main.rand = new UnifiedRandom(); + fakeTile.active(true); + fakeTile.inActive(true); + fakeTile.type = Terraria.ID.TileID.MagicalIceBlock; + fakeTile.frameX = (short)(162 + Main.rand.Next(0, 2) * 18); + fakeTile.frameY = 54; + fakeTile.color(Color); + } + } + + public void UnsetFakeTile(int index, int x, int y) + { + if (RealTiles == null) + throw new InvalidOperationException("区域尚未设置虚拟图块。"); + + if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY) + return; + + Main.tile[x, y] = RealTiles[index]; + } + + public void Refresh(TSPlayer player) + { + // Due to the way the Rectangle class works, the Width and Height values are one tile less than the actual dimensions of the region. + if (ShowArea.Width <= 3 || ShowArea.Height <= 3) + { + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top - 1, ShowArea.Width + 3, ShowArea.Height + 3, 0); + } + else + { + if (ShowArea.Top == Area.Top) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top - 1, ShowArea.Width + 3, 3, 0); + + if (ShowArea.Left == Area.Left) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top + 2, 3, ShowArea.Height, 0); + + if (ShowArea.Right == Area.Right) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Right - 1, ShowArea.Top + 2, 3, ShowArea.Height, 0); + + if (ShowArea.Bottom == Area.Bottom) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left + 2, ShowArea.Bottom - 1, ShowArea.Width - 3, 3, 0); + } + + player.SendData(PacketTypes.TileFrameSection, "", (ShowArea.Left / 200), (ShowArea.Top / 150), (ShowArea.Right / 200), (ShowArea.Bottom / 150), 0); + } + } +} diff --git a/RegionView/RegionPlayer.cs b/RegionView/RegionPlayer.cs new file mode 100644 index 000000000..49e1a5d79 --- /dev/null +++ b/RegionView/RegionPlayer.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TShockAPI; + +namespace RegionView +{ + public class RegionPlayer + { + public int Index { get; } + + public TSPlayer TSPlayer + { + get + => TShock.Players[Index]; + } + + public List Regions { get; } = new(); + + public bool IsViewingNearby { get; set; } + + public RegionPlayer(int index) + => Index = index; + } +} diff --git a/RegionView/RegionView.cs b/RegionView/RegionView.cs new file mode 100644 index 000000000..426342571 --- /dev/null +++ b/RegionView/RegionView.cs @@ -0,0 +1,607 @@ +using Microsoft.Xna.Framework; +using System.Timers; +using Terraria; +using TerrariaApi.Server; +using TShockAPI; +using TShockAPI.DB; +using TShockAPI.Hooks; + +namespace RegionView +{ + [ApiVersion(2, 1)] + public class RegionView : TerrariaPlugin + { + public const int NearRange = 100; + + public List Players { get; } = new(); + + public static Color[] TextColors { get; } = new[] + { + new Color(244, 93, 93), + new Color(244, 169, 93), + new Color(244, 244, 93), + new Color(169, 244, 93), + new Color( 93, 244, 93), + new Color( 93, 244, 169), + new Color( 93, 244, 244), + new Color( 93, 169, 244), + new Color( 93, 93, 244), + new Color(169, 93, 244), + new Color(244, 93, 244), + new Color(244, 93, 169) + }; + + public override string Author + => "TBC开发者团队,肝帝熙恩汉化"; + + public override string Description + => "为地区添加区域边界视图。"; + + public override string Name + => "区域显示"; + + public override Version Version + => new(1, 1); + + private readonly System.Timers.Timer _refreshTimer = new(5000); + + public RegionView(Main game) + : base(game) + { + Order = 1; + } + + public override void Initialize() + { + Commands.ChatCommands.Add(new Command("regionvision.regionview", CommandView, "regionview", "rv") + { + AllowServer = false, + HelpText = "显示指定区域的边界" + }); + + Commands.ChatCommands.Add(new Command("regionvision.regionview", CommandClear, "regionclear", "rc") + { + AllowServer = false, + HelpDesc = new string[] { "用法: /rc", "从您的视图中移除所有区域显示" } + }); + + Commands.ChatCommands.Add(new Command("regionvision.regionviewnear", CommandViewNearby, "regionviewnear", "rvn") + { + AllowServer = false, + HelpText = "开启或关闭自动显示您附近的区域" + }); + + GetDataHandlers.TileEdit += HandlerList.Create(OnTileEdit!, HandlerPriority.High, false); + + ServerApi.Hooks.ServerJoin.Register(this, OnPlayerJoin); + ServerApi.Hooks.ServerLeave.Register(this, OnPlayerLeave); + + PlayerHooks.PlayerCommand += OnPlayerCommand; + RegionHooks.RegionCreated += RegionCreated; + RegionHooks.RegionDeleted += RegionDeleted; + + _refreshTimer.AutoReset = false; + + _refreshTimer.Elapsed += (x, _) => RefreshRegions(); + } + + private void RegionDeleted(RegionHooks.RegionDeletedEventArgs args) + { + if (args.Region.WorldID != Main.worldID.ToString()) return; + + // If any players were viewing this region, clear its border. + lock (Players) + { + foreach (var player in Players) + { + for (var i = 0; i < player.Regions.Count; i++) + { + var region = player.Regions[i]; + if (region.Name.Equals(args.Region.Name)) + { + player.TSPlayer.SendMessage("区域显示 " + region.Name + " 已被删除。", TextColors[region.Color - 13]); + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i); + + foreach (var region2 in player.Regions) + region2.SetFakeTiles(); + foreach (var region2 in player.Regions) + region2.Refresh(player.TSPlayer); + foreach (var region2 in player.Regions.Reverse()) + region2.UnsetFakeTiles(); + + break; + } + } + } + } + } + + private void RegionCreated(RegionHooks.RegionCreatedEventArgs args) + { + _refreshTimer.Stop(); + RefreshRegions(); + } + + public RegionPlayer? FindPlayer(int index) + => Players.FirstOrDefault(p => p.Index == index); + + private void CommandView(CommandArgs args) + { + TShockAPI.DB.Region? tRegion = null; + var matches = new List(); + + if (args.Parameters.Count < 1) + { + args.Player.SendErrorMessage("用法: /regionview <区域名称>"); + return; + } + + // Find the specified region. + for (var pass = 1; pass <= 3 && tRegion == null && matches.Count == 0; pass++) + { + foreach (var _tRegion in TShock.Regions.Regions) + { + switch (pass) + { + case 1: // Pass 1: exact match + if (_tRegion.Name == args.Parameters[0]) + { + tRegion = _tRegion; + break; + } + else if (_tRegion.Name.Equals(args.Parameters[0], StringComparison.OrdinalIgnoreCase)) + matches.Add(_tRegion); + break; + case 2: // Pass 2: case-sensitive partial match + if (_tRegion.Name.StartsWith(args.Parameters[0])) + matches.Add(_tRegion); + break; + case 3: // Pass 3: case-insensitive partial match + if (_tRegion.Name.StartsWith(args.Parameters[0], StringComparison.OrdinalIgnoreCase)) + matches.Add(_tRegion); + break; + } + if (tRegion != null) + break; + } + } + + if (tRegion == null) + { + if (matches.Count == 1) + { + tRegion = matches[0]; + } + else if (matches.Count == 0) + { + args.Player.SendErrorMessage("没有找到这样的区域。"); + return; + } + else if (matches.Count > 5) + { + args.Player.SendErrorMessage("找到了多个匹配的区域:{0} 等{1}个更多。请更具体一些。", string.Join(", ", matches.Take(5).Select(r => r.Name)), matches.Count - 5); + return; + } + else if (matches.Count > 1) + { + args.Player.SendErrorMessage("找到了多个匹配的区域:{0}。请更具体一些。", string.Join(", ", matches.Select(r => r.Name))); + return; + } + } + + if (tRegion!.Area.Width < 0 || tRegion.Area.Height < 0) + { + args.Player.SendErrorMessage("区域 {0} 不包含任何图块。 (找到的尺寸: {1} × {2})\n使用 [c/FF8080:/region resize] 来修复它。", tRegion.Name, tRegion.Area.Width, tRegion.Area.Height); + return; + } + + lock (Players) + { + var player = FindPlayer(args.Player.Index); + + if (player == null) + return; + + // Register this region. + var region = player.Regions.FirstOrDefault(r => r.Name == tRegion.Name); + + if (region == null) + region = new Region(tRegion.Name, tRegion.Area); + else + player.Regions.Remove(region); + + foreach (var _region in player.Regions) + _region.SetFakeTiles(); + + if (region.ShowArea != region.Area) + region.Refresh(player.TSPlayer); + + player.Regions.Add(region); + + region.CalculateArea(args.Player); + region.SetFakeTiles(); + region.Refresh(player.TSPlayer); + + foreach (var _region in player.Regions.Reverse()) + _region.UnsetFakeTiles(); + + var message = "您现在正在查看 " + region.Name + " 区域。"; + // 如果区域很大,显示区域的大小。 + if (tRegion.Area.Width >= Region.MaximumSize || tRegion.Area.Height >= Region.MaximumSize) + { + int num; int num2; + if (tRegion.Area.Bottom < args.Player.TileY) + { + num = args.Player.TileY - tRegion.Area.Bottom; + message += " 边界位于您上方 " + num + (num == 1 ? " 个图块" : " 个图块"); + } + else if (tRegion.Area.Top > args.Player.TileY) + { + num = tRegion.Area.Top - args.Player.TileY; + message += " 边界位于您下方 " + (tRegion.Area.Top - args.Player.TileY) + (num == 1 ? " 个图块" : " 个图块"); + } + else + { + num = args.Player.TileY - tRegion.Area.Top; + num2 = tRegion.Area.Bottom - args.Player.TileY; + message += " 边界位于您上方 " + (args.Player.TileY - tRegion.Area.Top) + (num == 1 ? " 个图块" : " 个图块") + + ",下方 " + (tRegion.Area.Bottom - args.Player.TileY) + (num2 == 1 ? " 个图块" : " 个图块"); + } + if (tRegion.Area.Right < args.Player.TileX) + { + num = args.Player.TileX - tRegion.Area.Right; + message += ",位于您右侧 " + (args.Player.TileX - tRegion.Area.Right) + (num == 1 ? " 个图块。" : " 个图块。"); + } + else if (tRegion.Area.Left > args.Player.TileX) + { + num = tRegion.Area.Left - args.Player.TileX; + message += ",位于您左侧 " + (tRegion.Area.Left - args.Player.TileX) + (num == 1 ? " 个图块。" : " 个图块。"); + } + else + { + num = args.Player.TileX - tRegion.Area.Left; + num2 = tRegion.Area.Right - args.Player.TileX; + message += ",位于您右侧 " + (args.Player.TileX - tRegion.Area.Left) + (num == 1 ? " 个图块" : " 个图块") + + ",左侧 " + (tRegion.Area.Right - args.Player.TileX) + (num2 == 1 ? " 个图块。" : " 个图块。"); + } + } + args.Player.SendMessage(message, TextColors[region.Color - 13]); + + _refreshTimer.Interval = 7000; + _refreshTimer.Enabled = true; + } + } + + private void CommandClear(CommandArgs args) + { + lock (Players) + { + var player = FindPlayer(args.Player.Index); + if (player == null) + return; + + player.IsViewingNearby = false; + ClearRegions(player); + } + } + + private void CommandViewNearby(CommandArgs args) + { + lock (Players) + { + var player = FindPlayer(args.Player.Index); + + if (player == null) + return; + + if (player.IsViewingNearby) + { + player.IsViewingNearby = false; + args.Player.SendInfoMessage("您不再查看您附近的区域。"); + } + else + { + player.IsViewingNearby = true; + args.Player.SendInfoMessage("您现在正在查看您附近的区域。"); + + _refreshTimer.Interval = 1500; + _refreshTimer.Enabled = true; + } + } + } + + public static void ClearRegions(RegionPlayer player) + { + foreach (var region in player.Regions) + region.Refresh(player.TSPlayer); + + player.Regions.Clear(); + } + + private void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs e) + { + if (e.Action is > GetDataHandlers.EditAction.KillTileNoItem or GetDataHandlers.EditAction.KillWall) + return; + + if (e.Action == GetDataHandlers.EditAction.PlaceTile && e.EditData == Terraria.ID.TileID.MagicalIceBlock) + return; + + lock (Players) + { + var player = this.FindPlayer(e.Player.Index); + if (player == null) + return; + + if (player.Regions.Count == 0) + return; + + // Stop the edit if a phantom tile is the only thing making it possible. + foreach (var region in player.Regions) + { + // Clear the region borders if they break one of the phantom ice blocks. + if ((e.Action == GetDataHandlers.EditAction.KillTile || e.Action == GetDataHandlers.EditAction.KillTileNoItem) && (Main.tile[e.X, e.Y] == null || !Main.tile[e.X, e.Y].active()) && + e.X >= region.ShowArea.Left - 1 && e.X <= region.ShowArea.Right + 1 && e.Y >= region.ShowArea.Top - 1 && e.Y <= region.ShowArea.Bottom + 1 && + !(e.X >= region.ShowArea.Left + 2 && e.X <= region.ShowArea.Right - 2 && e.Y >= region.ShowArea.Top + 2 && e.Y <= region.ShowArea.Bottom - 2)) + { + e.Handled = true; + //clearRegions(player); + break; + } + if ((e.Action == GetDataHandlers.EditAction.PlaceTile || e.Action == GetDataHandlers.EditAction.PlaceWall) && !TileValidityCheck(region, e.X, e.Y, e.Action)) + { + e.Handled = true; + player.TSPlayer.SendData(PacketTypes.TileSendSquare, "", 1, e.X, e.Y, 0, 0); + if (e.Action == GetDataHandlers.EditAction.PlaceTile) GiveTile(player, e); + if (e.Action == GetDataHandlers.EditAction.PlaceWall) GiveWall(player, e); + break; + } + } + + if (e.Handled) + ClearRegions(player); + } + } + + private void OnPlayerJoin(JoinEventArgs e) + { + lock (Players) + Players.Add(new(e.Who)); + } + + private void OnPlayerLeave(LeaveEventArgs e) + { + lock (Players) + for (var i = 0; i < Players.Count; i++) + { + if (Players[i].Index == e.Who) + { + Players.RemoveAt(i); + break; + } + } + } + + private void OnPlayerCommand(PlayerCommandEventArgs e) + { + if (e.Parameters.Count >= 2 && e.CommandName.ToLower() == "region" && new[] { "delete", "resize", "expand" }.Contains(e.Parameters[0].ToLower())) + { + if (Commands.ChatCommands.Any(c => c.HasAlias("region") && c.CanRun(e.Player))) + _refreshTimer.Interval = 1500; + } + } + + private void Tick(object sender, ElapsedEventArgs e) + => this.RefreshRegions(); + + private void RefreshRegions() + { + var anyRegions = false; + + // Check for regions that have changed. + lock (Players) + { + foreach (var player in Players) + { + var refreshFlag = false; + + for (var i = 0; i < player.Regions.Count; i++) + { + var region = player.Regions[i]; + var tRegion = TShock.Regions.GetRegionByName(region.Name); + + if (tRegion == null) + { + // The region was removed. + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + { + var newArea = tRegion.Area; + if (!region.Command && (!player.IsViewingNearby || !IsPlayerNearby(player.TSPlayer, region.Area))) + { + // The player is no longer near the region. + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + if (newArea != region.Area) + { + // The region was resized. + if (newArea.Width < 0 || newArea.Height < 0) + { + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + { + anyRegions = true; + refreshFlag = true; + region.Refresh(player.TSPlayer); + region.Area = newArea; + region.CalculateArea(player.TSPlayer); + } + } + else + { + anyRegions = true; + } + } + } + + if (player.IsViewingNearby) + { + anyRegions = true; + + // Search for nearby regions + foreach (var tRegion in TShock.Regions.Regions) + { + if (tRegion.WorldID == Main.worldID.ToString() && tRegion.Area.Width >= 0 && tRegion.Area.Height >= 0) + { + if (IsPlayerNearby(player.TSPlayer, tRegion.Area)) + { + if (!player.Regions.Any(r => r.Name == tRegion.Name)) + { + refreshFlag = true; + var region = new Region(tRegion.Name, tRegion.Area, false); + region.CalculateArea(player.TSPlayer); + player.Regions.Add(region); + player.TSPlayer.SendMessage("你正在看区域 " + region.Name + ".", TextColors[region.Color - 13]); + } + } + } + } + } + + if (refreshFlag) + { + foreach (var region in player.Regions) + region.SetFakeTiles(); + foreach (var region in player.Regions) + region.Refresh(player.TSPlayer); + foreach (var region in player.Regions.Reverse()) + region.UnsetFakeTiles(); + } + } + } + + if (anyRegions) + { + _refreshTimer.Interval = 7000; + _refreshTimer.Enabled = true; + } + } + + public static bool IsPlayerNearby(TSPlayer tPlayer, Rectangle area) + { + var playerX = (int)(tPlayer.X / 16); + var playerY = (int)(tPlayer.Y / 16); + + return playerX >= area.Left - NearRange && + playerX <= area.Right + NearRange && + playerY >= area.Top - NearRange && + playerY <= area.Bottom + NearRange; + } + + public static bool TileValidityCheck(Region region, int x, int y, GetDataHandlers.EditAction editType) + { + // Check if there's a wall or another tile next to this tile. + if (editType == GetDataHandlers.EditAction.PlaceWall) + { + if (Main.tile[x, y] != null && Main.tile[x, y].active()) + return true; + + if (Main.tile[x - 1, y] != null && ((Main.tile[x - 1, y].active() && !Main.tileNoAttach[Main.tile[x - 1, y].type]) || Main.tile[x - 1, y].wall > 0)) + return true; + + if (Main.tile[x + 1, y] != null && ((Main.tile[x + 1, y].active() && !Main.tileNoAttach[Main.tile[x + 1, y].type]) || Main.tile[x + 1, y].wall > 0)) + return true; + + if (Main.tile[x, y - 1] != null && ((Main.tile[x, y - 1].active() && !Main.tileNoAttach[Main.tile[x, y - 1].type]) || Main.tile[x, y - 1].wall > 0)) + return true; + + if (Main.tile[x, y + 1] != null && ((Main.tile[x, y + 1].active() && !Main.tileNoAttach[Main.tile[x, y + 1].type]) || Main.tile[x, y + 1].wall > 0)) + return true; + } + else + { + if (Main.tile[x, y] != null && Main.tile[x, y].wall > 0) + return true; + + if (Main.tile[x - 1, y] != null && Main.tile[x - 1, y].wall > 0) + return true; + + if (Main.tile[x + 1, y] != null && Main.tile[x + 1, y].wall > 0) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y - 1].wall > 0) + return true; + + if (Main.tile[x, y + 1] != null && Main.tile[x, y + 1].wall > 0) + return true; + + if (Main.tile[x - 1, y] != null && Main.tile[x - 1, y].active() && !Main.tileNoAttach[Main.tile[x - 1, y].type]) + return true; + + if (Main.tile[x + 1, y] != null && Main.tile[x + 1, y].active() && !Main.tileNoAttach[Main.tile[x + 1, y].type]) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y - 1].active() && !Main.tileNoAttach[Main.tile[x, y - 1].type]) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y + 1].active() && !Main.tileNoAttach[Main.tile[x, y + 1].type]) + return true; + } + + // Check if this tile is next to a region boundary. + return x < region.ShowArea.Left - 1 || x > region.ShowArea.Right + 1 || y < region.ShowArea.Top - 1 || y > region.ShowArea.Bottom + 1 || + x >= region.ShowArea.Left + 2 && x <= region.ShowArea.Right - 2 && y >= region.ShowArea.Top + 2 && y <= region.ShowArea.Bottom - 2; + } + + public static void GiveTile(RegionPlayer player, GetDataHandlers.TileEditEventArgs e) + { + var item = new Item(); + var found = false; + + for (var i = 1; i <= Terraria.ID.ItemID.Count; i++) + { + item.SetDefaults(i, true); + if (item.createTile == e.EditData && item.placeStyle == e.Style) + { + if (item.tileWand != -1) item.SetDefaults(item.tileWand, true); + found = true; + break; + } + } + + if (found) + GiveItem(player, item); + } + + public static void GiveWall(RegionPlayer player, GetDataHandlers.TileEditEventArgs e) + { + var item = new Item(); var found = false; + for (var i = 1; i <= Terraria.ID.ItemID.Count; i++) + { + item.SetDefaults(i, true); + if (item.createWall == e.EditData) + { + found = true; + break; + } + } + if (found) + { + item.stack = 1; + GiveItem(player, item); + } + } + + public static void GiveItem(RegionPlayer player, Item item) + => player.TSPlayer.GiveItem(item.type, 1); + } +} \ No newline at end of file diff --git a/RegionView/RegionView.csproj b/RegionView/RegionView.csproj new file mode 100644 index 000000000..04c81dc33 --- /dev/null +++ b/RegionView/RegionView.csproj @@ -0,0 +1,3 @@ + + + \ No newline at end of file From d6191164a5d7e58fd814a3cac72b35a323d999cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=9D=E5=B8=9D=E7=86=99=E6=81=A9?= <111548550+THEXN@users.noreply.github.com> Date: Sat, 13 Apr 2024 16:22:47 +0800 Subject: [PATCH 2/4] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 647d1c7e8..973495c4f 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,10 @@ | [RandReSpawn](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RandRespawn) | 随机出生点 | 无 | | [CGive](https://github.com/Controllerdestiny/TShockPlugin/tree/master/CGive) | 离线命令 | 无 | | [RainbowChat](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RainbowChat) | 每次说话颜色不一样 | 无 | -| [NormalDropsBags](https://github.com/Controllerdestiny/TShockPlugin/tree/master/NormalDropsBags) | 普通难度宝藏袋 | 无 | -| [CheckBag](https://github.com/Controllerdestiny/TShockPlugin/tree/master/CheckBag) | 检查背包(超进度物品检测) | 无 | -| [DisableSurfaceProjectiles](https://github.com/Controllerdestiny/TShockPlugin/tree/master/DisableSurfaceProjectiles) | 禁地表弹幕 | 无 | -| [RecipesBrowser](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RecipesBrowser) | 合成表 | 无 | -| [DisableGodMod](https://github.com/Controllerdestiny/TShockPlugin/tree/master/DisableGodMod) | 阻止玩家无敌 | 无 | -| [TownNPCHomes](https://github.com/Controllerdestiny/TShockPlugin/tree/master/TownNPCHomes) | NPC快速回家 | 无 | +| [NormalDropsBags](https://github.com/Controllerdestiny/TShockPlugin/tree/master/NormalDropsBags) | 普通难度宝藏袋 | 无 | +| [CheckBag](https://github.com/Controllerdestiny/TShockPlugin/tree/master/CheckBag) | 检查背包(超进度物品检测) | 无 | +| [DisableSurfaceProjectiles](https://github.com/Controllerdestiny/TShockPlugin/tree/master/DisableSurfaceProjectiles) | 禁地表弹幕 | 无 | +| [RecipesBrowser](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RecipesBrowser) | 合成表 | 无 | +| [DisableGodMod](https://github.com/Controllerdestiny/TShockPlugin/tree/master/DisableGodMod) | 阻止玩家无敌 | 无 | +| [TownNPCHomes](https://github.com/Controllerdestiny/TShockPlugin/tree/master/TownNPCHomes) | NPC快速回家 | 无 | +| [RegionView](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RegionView) | 显示区域边界 | 无 | From 8fd6775f5d6c80c87649fd1f6aff2d3834afa138 Mon Sep 17 00:00:00 2001 From: xien <2383759126@qq.com> Date: Sat, 13 Apr 2024 16:17:25 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=EF=BC=9ANoagent=20=E5=8F=8D=E4=BB=A3=E7=90=86=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Noagent/Noagent.cs | 135 ++++++++ Noagent/Noagent.csproj | 5 + Noagent/README.md | 26 ++ Plugin.sln | 20 ++ RegionView/README.md | 28 ++ RegionView/Region.cs | 222 +++++++++++++ RegionView/RegionPlayer.cs | 27 ++ RegionView/RegionView.cs | 607 +++++++++++++++++++++++++++++++++++ RegionView/RegionView.csproj | 3 + 9 files changed, 1073 insertions(+) create mode 100644 Noagent/Noagent.cs create mode 100644 Noagent/Noagent.csproj create mode 100644 Noagent/README.md create mode 100644 RegionView/README.md create mode 100644 RegionView/Region.cs create mode 100644 RegionView/RegionPlayer.cs create mode 100644 RegionView/RegionView.cs create mode 100644 RegionView/RegionView.csproj diff --git a/Noagent/Noagent.cs b/Noagent/Noagent.cs new file mode 100644 index 000000000..28bb59a63 --- /dev/null +++ b/Noagent/Noagent.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Terraria; +using TerrariaApi.Server; +using TShockAPI; + +namespace Noagent; + +[ApiVersion(2, 1)] +public class Noagent : TerrariaPlugin +{ + public override string Author => "[星迹]Jonesn,肝帝熙恩更新适配1449"; + public override string Description => "禁止代理登录"; + public override string Name => "[禁止代理登录]Noagent"; + public override Version Version => new Version(1, 0, 1); + + public Noagent(Main game) + : base(game) + { + } + + public override void Initialize() + { + ServerApi.Hooks.ServerJoin.Register(this, OnJoin); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + ServerApi.Hooks.ServerJoin.Deregister(this, OnJoin); + } + base.Dispose(disposing); + } + + public class Message + { + public TSPlayer Player { get; set; } + public TaskCompletionSource Result { get; set; } + + public async void ThreadMain() + { + try + { + await Task.Delay(3000); + string ipAddress = Player.IP; + string apiUrl = $"https://blackbox.ipinfo.app/lookup/{ipAddress}"; + + using HttpClient client = new HttpClient(); + HttpResponseMessage response = await client.GetAsync(apiUrl); + response.EnsureSuccessStatusCode(); + + string responseContent = await response.Content.ReadAsStringAsync(); + Result.SetResult(responseContent); + } + catch (Exception ex) + { + Result.SetException(ex); + } + } + } + + private async void OnJoin(JoinEventArgs args) + { + TSPlayer player = TShock.Players[args.Who]; + + // 忽略内网IP和回环地址 + if (IsPrivateOrLoopbackAddress(player.IP)) + { + Console.WriteLine($"{player.Name} 使用内网或回环地址,跳过代理检测"); + return; + } + Message message = new Message + { + Player = player, + Result = new TaskCompletionSource() + }; + + Thread thread = new Thread(message.ThreadMain); + thread.Start(); + + try + { + string result = await message.Result.Task; + if (result == "Y") // 假设新的IP查询服务返回 "Y" 表示代理IP + { + Console.WriteLine($"检测到{player.Name}为代理,已踢出"); + player.Disconnect("本服务禁止代理IP登录"); + } + else + { + Console.WriteLine($"{player.Name} IP无异常"); + } + } + catch (Exception ex) + { + Console.WriteLine($"检测玩家{player.Name} IP时发生错误: {ex.Message}"); + } + } + + private bool IsPrivateOrLoopbackAddress(string ipAddress) + { + IPAddress ip; + if (!IPAddress.TryParse(ipAddress, out ip)) + { + return false; + } + + // 判断是否为内网IP + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + byte[] bytes = ip.GetAddressBytes(); + if ((bytes[0] == 10) || // 10.0.0.0/8 + (bytes[0] == 172 && (bytes[1] >= 16 && bytes[1] <= 31)) || // 172.16.0.0/12 + (bytes[0] == 192 && bytes[1] == 168)) // 192.168.0.0/16 + { + return true; + } + } + + // 判断是否为回环地址 + if (IPAddress.IsLoopback(ip)) + { + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/Noagent/Noagent.csproj b/Noagent/Noagent.csproj new file mode 100644 index 000000000..639adc026 --- /dev/null +++ b/Noagent/Noagent.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Noagent/README.md b/Noagent/README.md new file mode 100644 index 000000000..e51dcc3d4 --- /dev/null +++ b/Noagent/README.md @@ -0,0 +1,26 @@ +# Noagent 反代理插件 + +- 作者: Jonesn,肝帝熙恩 +- 出处: TShock中文官方群 +- 调用`https://blackbox.ipinfo.app/lookup/`进行检测,实测效果还行 + +## 更新日志 + +``` +暂无 +``` + +## 指令 + +``` +暂无 +``` + +## 配置 + +```json +暂无 +``` +## 反馈 +- 共同维护的插件库:https://github.com/THEXN/TShockPlugin/ +- 国内社区trhub.cn 或 TShock官方群等 \ No newline at end of file diff --git a/Plugin.sln b/Plugin.sln index 8daf95306..9198f5298 100644 --- a/Plugin.sln +++ b/Plugin.sln @@ -60,6 +60,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecipesBrowser", "RecipesBr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TownNPCHomes", "TownNPCHomes\TownNPCHomes.csproj", "{6FAF2ED2-38B1-46C7-83F1-B909D260D70E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegionView", "RegionView\RegionView.csproj", "{41B3DE8D-F7B2-44E0-AD35-971140F49E81}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Noagent", "Noagent\Noagent.csproj", "{AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -276,6 +280,22 @@ Global {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|Any CPU.Build.0 = Release|Any CPU {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|x64.ActiveCfg = Release|Any CPU {6FAF2ED2-38B1-46C7-83F1-B909D260D70E}.Release|x64.Build.0 = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|x64.ActiveCfg = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Debug|x64.Build.0 = Debug|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|Any CPU.Build.0 = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|x64.ActiveCfg = Release|Any CPU + {41B3DE8D-F7B2-44E0-AD35-971140F49E81}.Release|x64.Build.0 = Release|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Debug|x64.Build.0 = Debug|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Release|Any CPU.Build.0 = Release|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Release|x64.ActiveCfg = Release|Any CPU + {AFB08FA2-C6C6-4759-8117-E1CD44E1BCD9}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RegionView/README.md b/RegionView/README.md new file mode 100644 index 000000000..821b81315 --- /dev/null +++ b/RegionView/README.md @@ -0,0 +1,28 @@ +# RegionView 区域显示 + +- 作者: TBC开发者团队,肝帝熙恩 +- 出处: github +- 为区域添加边界显示,仅自己视角可见 + +## 更新日志 + +``` +暂无 +``` + +## 指令 + +| 语法 | 权限 | 说明 | +| -------------- | :-----------------: | :------: | +| /regionview <区域名称> 或 /rv <区域名称> | regionvision.regionview | 显示指定区域边界| +| /regionclear 或 /rc | regionvision.regionview | 取消显示所有区域边界| +| /regionviewnear 或 /rvn | regionvision.regionviewnear | 显示周围所有区域边界| + +## 配置 + +```json +暂无 +``` +## 反馈 +- 共同维护的插件库:https://github.com/THEXN/TShockPlugin/ +- 国内社区trhub.cn 或 TShock官方群等 \ No newline at end of file diff --git a/RegionView/Region.cs b/RegionView/Region.cs new file mode 100644 index 000000000..96dd13fdc --- /dev/null +++ b/RegionView/Region.cs @@ -0,0 +1,222 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Utilities; +using TShockAPI; + +namespace RegionView +{ + public class Region + { + private Tile[]? RealTiles; + public const int MaximumSize = 256; + + public Rectangle Area; + public Rectangle ShowArea; + + public string Name { get; } + + public byte Color { get; } + + public bool Command { get; set; } + + public Region(string name, Rectangle area, bool command = true) + { + Name = name; + Area = area; + ShowArea = area; + Command = command; + + var total = 0; + for (var i = 0; i < name.Length; i++) total += name[i]; + Color = (byte)(total % 12 + 13); + } + + public void CalculateArea(TSPlayer tPlayer) + { + ShowArea = Area; + + // If the region is large, only part of its border will be shown. + if (ShowArea.Width >= MaximumSize) + { + ShowArea.X = (int)(tPlayer.X / 16) - MaximumSize / 2; + ShowArea.Width = MaximumSize - 1; + + if (ShowArea.Left < Area.Left) + ShowArea.X = Area.Left; + else if (ShowArea.Right > Area.Right) + ShowArea.X = Area.Right - (MaximumSize - 1); + } + if (ShowArea.Height >= MaximumSize) + { + ShowArea.Y = (int)(tPlayer.Y / 16) - MaximumSize / 2; + ShowArea.Height = MaximumSize - 1; + + if (ShowArea.Top < Area.Top) + ShowArea.Y = Area.Top; + else if (ShowArea.Bottom > Area.Bottom) + ShowArea.Y = Area.Bottom - (MaximumSize - 1); + } + + // Ensure the region boundary is within the world. + if (ShowArea.Left < 1) + ShowArea.X = 1; + else if (ShowArea.Left >= Main.maxTilesX - 1) + ShowArea.X = Main.maxTilesX - 1; + + if (ShowArea.Top < 1) + ShowArea.Y = 1; + else if (ShowArea.Top >= Main.maxTilesY - 1) + ShowArea.Y = Main.maxTilesY - 1; + + if (ShowArea.Right >= Main.maxTilesX - 1) + ShowArea.Width = Main.maxTilesX - ShowArea.X - 2; + + if (ShowArea.Bottom >= Main.maxTilesY - 1) + ShowArea.Height = Main.maxTilesY - ShowArea.Y - 2; + } + + /// Spawns fake tiles for the region border. + /// Fake tiles have already been set, which would cause a desync. + public void SetFakeTiles() + { + int d; var index = 0; + + if (RealTiles != null) throw new InvalidOperationException("该区域已设置虚拟图块。"); + + // Initialise the temporary tile array. + if (ShowArea.Width == 0) + RealTiles = new Tile[ShowArea.Height + 1]; + else if (ShowArea.Height == 0) + RealTiles = new Tile[ShowArea.Width + 1]; + else + RealTiles = new Tile[(ShowArea.Width + ShowArea.Height) * 2]; + + // Top boundary + if (ShowArea.Top == Area.Top) + for (d = 0; d <= ShowArea.Width; d++) + SetFakeTile(index++, ShowArea.Left + d, ShowArea.Top); + // East boundary + if (ShowArea.Right == Area.Right) + for (d = 1; d <= ShowArea.Height; d++) + SetFakeTile(index++, ShowArea.Right, ShowArea.Top + d); + // West boundary + if (ShowArea.Width > 0 && ShowArea.Left == Area.Left) + for (d = 1; d <= ShowArea.Height; d++) + SetFakeTile(index++, ShowArea.Left, ShowArea.Top + d); + // Bottom boundary + if (ShowArea.Height > 0 && ShowArea.Bottom == Area.Bottom) + for (d = 1; d < ShowArea.Width; d++) + SetFakeTile(index++, ShowArea.Left + d, ShowArea.Bottom); + } + + /// Removes fake tiles for the region, reverting to the real tiles. + /// Fake tiles have not been set. + public void UnsetFakeTiles() + { + int d; var index = 0; + + if (RealTiles == null) + throw new InvalidOperationException("区域未设置虚拟图块。"); + + // Top boundary + if (ShowArea.Top == Area.Top) + for (d = 0; d <= ShowArea.Width; d++) + UnsetFakeTile(index++, ShowArea.Left + d, ShowArea.Top); + // East boundary + if (ShowArea.Right == Area.Right) + for (d = 1; d <= ShowArea.Height; d++) + UnsetFakeTile(index++, ShowArea.Right, ShowArea.Top + d); + // West boundary + if (ShowArea.Width > 0 && ShowArea.Left == Area.Left) + for (d = 1; d <= ShowArea.Height; d++) + UnsetFakeTile(index++, ShowArea.Left, ShowArea.Top + d); + // Bottom boundary + if (ShowArea.Height > 0 && ShowArea.Bottom == Area.Bottom) + for (d = 1; d < ShowArea.Width; d++) + UnsetFakeTile(index++, ShowArea.Left + d, ShowArea.Bottom); + + RealTiles = null; + } + + /// Adds a single fake tile. If a tile exists, this will replace it with a painted clone. Otherwise, this will place an inactive magical ice tile with the same paint. + /// The index in the realTile array into which to store the existing tile + /// The x coordinate of the tile position + /// The y coordinate of the tile position + public void SetFakeTile(int index, int x, int y) + { + if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY) + return; + + if (RealTiles == null) + throw new InvalidOperationException("区域尚未设置虚拟图块。"); + + ITile fakeTile; + if (Main.tile[x, y] == null) + { + fakeTile = new Tile(); + } + else + { + // As of API version 1.22, Main.tile.get now only returns a link to the tile data heap, and the tile was getting lost at Main.tile[x, y] = fakeTile. + // This is why we actually have to copy the tile now. + RealTiles[index] = new Tile(Main.tile[x, y]); + fakeTile = Main.tile[x, y]; + } + + if (RealTiles[index] != null && RealTiles[index].active()) + { + // There's already a tile there; apply paint. + if (fakeTile.type == Terraria.ID.TileID.RainbowBrick) fakeTile.type = Terraria.ID.TileID.GrayBrick; + + fakeTile.color(Color); + } + else + { + // There isn't a tile there; place an ice block. + if (Main.rand == null) Main.rand = new UnifiedRandom(); + fakeTile.active(true); + fakeTile.inActive(true); + fakeTile.type = Terraria.ID.TileID.MagicalIceBlock; + fakeTile.frameX = (short)(162 + Main.rand.Next(0, 2) * 18); + fakeTile.frameY = 54; + fakeTile.color(Color); + } + } + + public void UnsetFakeTile(int index, int x, int y) + { + if (RealTiles == null) + throw new InvalidOperationException("区域尚未设置虚拟图块。"); + + if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY) + return; + + Main.tile[x, y] = RealTiles[index]; + } + + public void Refresh(TSPlayer player) + { + // Due to the way the Rectangle class works, the Width and Height values are one tile less than the actual dimensions of the region. + if (ShowArea.Width <= 3 || ShowArea.Height <= 3) + { + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top - 1, ShowArea.Width + 3, ShowArea.Height + 3, 0); + } + else + { + if (ShowArea.Top == Area.Top) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top - 1, ShowArea.Width + 3, 3, 0); + + if (ShowArea.Left == Area.Left) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left - 1, ShowArea.Top + 2, 3, ShowArea.Height, 0); + + if (ShowArea.Right == Area.Right) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Right - 1, ShowArea.Top + 2, 3, ShowArea.Height, 0); + + if (ShowArea.Bottom == Area.Bottom) + player.SendData(PacketTypes.TileSendSection, "", ShowArea.Left + 2, ShowArea.Bottom - 1, ShowArea.Width - 3, 3, 0); + } + + player.SendData(PacketTypes.TileFrameSection, "", (ShowArea.Left / 200), (ShowArea.Top / 150), (ShowArea.Right / 200), (ShowArea.Bottom / 150), 0); + } + } +} diff --git a/RegionView/RegionPlayer.cs b/RegionView/RegionPlayer.cs new file mode 100644 index 000000000..49e1a5d79 --- /dev/null +++ b/RegionView/RegionPlayer.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TShockAPI; + +namespace RegionView +{ + public class RegionPlayer + { + public int Index { get; } + + public TSPlayer TSPlayer + { + get + => TShock.Players[Index]; + } + + public List Regions { get; } = new(); + + public bool IsViewingNearby { get; set; } + + public RegionPlayer(int index) + => Index = index; + } +} diff --git a/RegionView/RegionView.cs b/RegionView/RegionView.cs new file mode 100644 index 000000000..426342571 --- /dev/null +++ b/RegionView/RegionView.cs @@ -0,0 +1,607 @@ +using Microsoft.Xna.Framework; +using System.Timers; +using Terraria; +using TerrariaApi.Server; +using TShockAPI; +using TShockAPI.DB; +using TShockAPI.Hooks; + +namespace RegionView +{ + [ApiVersion(2, 1)] + public class RegionView : TerrariaPlugin + { + public const int NearRange = 100; + + public List Players { get; } = new(); + + public static Color[] TextColors { get; } = new[] + { + new Color(244, 93, 93), + new Color(244, 169, 93), + new Color(244, 244, 93), + new Color(169, 244, 93), + new Color( 93, 244, 93), + new Color( 93, 244, 169), + new Color( 93, 244, 244), + new Color( 93, 169, 244), + new Color( 93, 93, 244), + new Color(169, 93, 244), + new Color(244, 93, 244), + new Color(244, 93, 169) + }; + + public override string Author + => "TBC开发者团队,肝帝熙恩汉化"; + + public override string Description + => "为地区添加区域边界视图。"; + + public override string Name + => "区域显示"; + + public override Version Version + => new(1, 1); + + private readonly System.Timers.Timer _refreshTimer = new(5000); + + public RegionView(Main game) + : base(game) + { + Order = 1; + } + + public override void Initialize() + { + Commands.ChatCommands.Add(new Command("regionvision.regionview", CommandView, "regionview", "rv") + { + AllowServer = false, + HelpText = "显示指定区域的边界" + }); + + Commands.ChatCommands.Add(new Command("regionvision.regionview", CommandClear, "regionclear", "rc") + { + AllowServer = false, + HelpDesc = new string[] { "用法: /rc", "从您的视图中移除所有区域显示" } + }); + + Commands.ChatCommands.Add(new Command("regionvision.regionviewnear", CommandViewNearby, "regionviewnear", "rvn") + { + AllowServer = false, + HelpText = "开启或关闭自动显示您附近的区域" + }); + + GetDataHandlers.TileEdit += HandlerList.Create(OnTileEdit!, HandlerPriority.High, false); + + ServerApi.Hooks.ServerJoin.Register(this, OnPlayerJoin); + ServerApi.Hooks.ServerLeave.Register(this, OnPlayerLeave); + + PlayerHooks.PlayerCommand += OnPlayerCommand; + RegionHooks.RegionCreated += RegionCreated; + RegionHooks.RegionDeleted += RegionDeleted; + + _refreshTimer.AutoReset = false; + + _refreshTimer.Elapsed += (x, _) => RefreshRegions(); + } + + private void RegionDeleted(RegionHooks.RegionDeletedEventArgs args) + { + if (args.Region.WorldID != Main.worldID.ToString()) return; + + // If any players were viewing this region, clear its border. + lock (Players) + { + foreach (var player in Players) + { + for (var i = 0; i < player.Regions.Count; i++) + { + var region = player.Regions[i]; + if (region.Name.Equals(args.Region.Name)) + { + player.TSPlayer.SendMessage("区域显示 " + region.Name + " 已被删除。", TextColors[region.Color - 13]); + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i); + + foreach (var region2 in player.Regions) + region2.SetFakeTiles(); + foreach (var region2 in player.Regions) + region2.Refresh(player.TSPlayer); + foreach (var region2 in player.Regions.Reverse()) + region2.UnsetFakeTiles(); + + break; + } + } + } + } + } + + private void RegionCreated(RegionHooks.RegionCreatedEventArgs args) + { + _refreshTimer.Stop(); + RefreshRegions(); + } + + public RegionPlayer? FindPlayer(int index) + => Players.FirstOrDefault(p => p.Index == index); + + private void CommandView(CommandArgs args) + { + TShockAPI.DB.Region? tRegion = null; + var matches = new List(); + + if (args.Parameters.Count < 1) + { + args.Player.SendErrorMessage("用法: /regionview <区域名称>"); + return; + } + + // Find the specified region. + for (var pass = 1; pass <= 3 && tRegion == null && matches.Count == 0; pass++) + { + foreach (var _tRegion in TShock.Regions.Regions) + { + switch (pass) + { + case 1: // Pass 1: exact match + if (_tRegion.Name == args.Parameters[0]) + { + tRegion = _tRegion; + break; + } + else if (_tRegion.Name.Equals(args.Parameters[0], StringComparison.OrdinalIgnoreCase)) + matches.Add(_tRegion); + break; + case 2: // Pass 2: case-sensitive partial match + if (_tRegion.Name.StartsWith(args.Parameters[0])) + matches.Add(_tRegion); + break; + case 3: // Pass 3: case-insensitive partial match + if (_tRegion.Name.StartsWith(args.Parameters[0], StringComparison.OrdinalIgnoreCase)) + matches.Add(_tRegion); + break; + } + if (tRegion != null) + break; + } + } + + if (tRegion == null) + { + if (matches.Count == 1) + { + tRegion = matches[0]; + } + else if (matches.Count == 0) + { + args.Player.SendErrorMessage("没有找到这样的区域。"); + return; + } + else if (matches.Count > 5) + { + args.Player.SendErrorMessage("找到了多个匹配的区域:{0} 等{1}个更多。请更具体一些。", string.Join(", ", matches.Take(5).Select(r => r.Name)), matches.Count - 5); + return; + } + else if (matches.Count > 1) + { + args.Player.SendErrorMessage("找到了多个匹配的区域:{0}。请更具体一些。", string.Join(", ", matches.Select(r => r.Name))); + return; + } + } + + if (tRegion!.Area.Width < 0 || tRegion.Area.Height < 0) + { + args.Player.SendErrorMessage("区域 {0} 不包含任何图块。 (找到的尺寸: {1} × {2})\n使用 [c/FF8080:/region resize] 来修复它。", tRegion.Name, tRegion.Area.Width, tRegion.Area.Height); + return; + } + + lock (Players) + { + var player = FindPlayer(args.Player.Index); + + if (player == null) + return; + + // Register this region. + var region = player.Regions.FirstOrDefault(r => r.Name == tRegion.Name); + + if (region == null) + region = new Region(tRegion.Name, tRegion.Area); + else + player.Regions.Remove(region); + + foreach (var _region in player.Regions) + _region.SetFakeTiles(); + + if (region.ShowArea != region.Area) + region.Refresh(player.TSPlayer); + + player.Regions.Add(region); + + region.CalculateArea(args.Player); + region.SetFakeTiles(); + region.Refresh(player.TSPlayer); + + foreach (var _region in player.Regions.Reverse()) + _region.UnsetFakeTiles(); + + var message = "您现在正在查看 " + region.Name + " 区域。"; + // 如果区域很大,显示区域的大小。 + if (tRegion.Area.Width >= Region.MaximumSize || tRegion.Area.Height >= Region.MaximumSize) + { + int num; int num2; + if (tRegion.Area.Bottom < args.Player.TileY) + { + num = args.Player.TileY - tRegion.Area.Bottom; + message += " 边界位于您上方 " + num + (num == 1 ? " 个图块" : " 个图块"); + } + else if (tRegion.Area.Top > args.Player.TileY) + { + num = tRegion.Area.Top - args.Player.TileY; + message += " 边界位于您下方 " + (tRegion.Area.Top - args.Player.TileY) + (num == 1 ? " 个图块" : " 个图块"); + } + else + { + num = args.Player.TileY - tRegion.Area.Top; + num2 = tRegion.Area.Bottom - args.Player.TileY; + message += " 边界位于您上方 " + (args.Player.TileY - tRegion.Area.Top) + (num == 1 ? " 个图块" : " 个图块") + + ",下方 " + (tRegion.Area.Bottom - args.Player.TileY) + (num2 == 1 ? " 个图块" : " 个图块"); + } + if (tRegion.Area.Right < args.Player.TileX) + { + num = args.Player.TileX - tRegion.Area.Right; + message += ",位于您右侧 " + (args.Player.TileX - tRegion.Area.Right) + (num == 1 ? " 个图块。" : " 个图块。"); + } + else if (tRegion.Area.Left > args.Player.TileX) + { + num = tRegion.Area.Left - args.Player.TileX; + message += ",位于您左侧 " + (tRegion.Area.Left - args.Player.TileX) + (num == 1 ? " 个图块。" : " 个图块。"); + } + else + { + num = args.Player.TileX - tRegion.Area.Left; + num2 = tRegion.Area.Right - args.Player.TileX; + message += ",位于您右侧 " + (args.Player.TileX - tRegion.Area.Left) + (num == 1 ? " 个图块" : " 个图块") + + ",左侧 " + (tRegion.Area.Right - args.Player.TileX) + (num2 == 1 ? " 个图块。" : " 个图块。"); + } + } + args.Player.SendMessage(message, TextColors[region.Color - 13]); + + _refreshTimer.Interval = 7000; + _refreshTimer.Enabled = true; + } + } + + private void CommandClear(CommandArgs args) + { + lock (Players) + { + var player = FindPlayer(args.Player.Index); + if (player == null) + return; + + player.IsViewingNearby = false; + ClearRegions(player); + } + } + + private void CommandViewNearby(CommandArgs args) + { + lock (Players) + { + var player = FindPlayer(args.Player.Index); + + if (player == null) + return; + + if (player.IsViewingNearby) + { + player.IsViewingNearby = false; + args.Player.SendInfoMessage("您不再查看您附近的区域。"); + } + else + { + player.IsViewingNearby = true; + args.Player.SendInfoMessage("您现在正在查看您附近的区域。"); + + _refreshTimer.Interval = 1500; + _refreshTimer.Enabled = true; + } + } + } + + public static void ClearRegions(RegionPlayer player) + { + foreach (var region in player.Regions) + region.Refresh(player.TSPlayer); + + player.Regions.Clear(); + } + + private void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs e) + { + if (e.Action is > GetDataHandlers.EditAction.KillTileNoItem or GetDataHandlers.EditAction.KillWall) + return; + + if (e.Action == GetDataHandlers.EditAction.PlaceTile && e.EditData == Terraria.ID.TileID.MagicalIceBlock) + return; + + lock (Players) + { + var player = this.FindPlayer(e.Player.Index); + if (player == null) + return; + + if (player.Regions.Count == 0) + return; + + // Stop the edit if a phantom tile is the only thing making it possible. + foreach (var region in player.Regions) + { + // Clear the region borders if they break one of the phantom ice blocks. + if ((e.Action == GetDataHandlers.EditAction.KillTile || e.Action == GetDataHandlers.EditAction.KillTileNoItem) && (Main.tile[e.X, e.Y] == null || !Main.tile[e.X, e.Y].active()) && + e.X >= region.ShowArea.Left - 1 && e.X <= region.ShowArea.Right + 1 && e.Y >= region.ShowArea.Top - 1 && e.Y <= region.ShowArea.Bottom + 1 && + !(e.X >= region.ShowArea.Left + 2 && e.X <= region.ShowArea.Right - 2 && e.Y >= region.ShowArea.Top + 2 && e.Y <= region.ShowArea.Bottom - 2)) + { + e.Handled = true; + //clearRegions(player); + break; + } + if ((e.Action == GetDataHandlers.EditAction.PlaceTile || e.Action == GetDataHandlers.EditAction.PlaceWall) && !TileValidityCheck(region, e.X, e.Y, e.Action)) + { + e.Handled = true; + player.TSPlayer.SendData(PacketTypes.TileSendSquare, "", 1, e.X, e.Y, 0, 0); + if (e.Action == GetDataHandlers.EditAction.PlaceTile) GiveTile(player, e); + if (e.Action == GetDataHandlers.EditAction.PlaceWall) GiveWall(player, e); + break; + } + } + + if (e.Handled) + ClearRegions(player); + } + } + + private void OnPlayerJoin(JoinEventArgs e) + { + lock (Players) + Players.Add(new(e.Who)); + } + + private void OnPlayerLeave(LeaveEventArgs e) + { + lock (Players) + for (var i = 0; i < Players.Count; i++) + { + if (Players[i].Index == e.Who) + { + Players.RemoveAt(i); + break; + } + } + } + + private void OnPlayerCommand(PlayerCommandEventArgs e) + { + if (e.Parameters.Count >= 2 && e.CommandName.ToLower() == "region" && new[] { "delete", "resize", "expand" }.Contains(e.Parameters[0].ToLower())) + { + if (Commands.ChatCommands.Any(c => c.HasAlias("region") && c.CanRun(e.Player))) + _refreshTimer.Interval = 1500; + } + } + + private void Tick(object sender, ElapsedEventArgs e) + => this.RefreshRegions(); + + private void RefreshRegions() + { + var anyRegions = false; + + // Check for regions that have changed. + lock (Players) + { + foreach (var player in Players) + { + var refreshFlag = false; + + for (var i = 0; i < player.Regions.Count; i++) + { + var region = player.Regions[i]; + var tRegion = TShock.Regions.GetRegionByName(region.Name); + + if (tRegion == null) + { + // The region was removed. + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + { + var newArea = tRegion.Area; + if (!region.Command && (!player.IsViewingNearby || !IsPlayerNearby(player.TSPlayer, region.Area))) + { + // The player is no longer near the region. + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + if (newArea != region.Area) + { + // The region was resized. + if (newArea.Width < 0 || newArea.Height < 0) + { + refreshFlag = true; + region.Refresh(player.TSPlayer); + player.Regions.RemoveAt(i--); + } + else + { + anyRegions = true; + refreshFlag = true; + region.Refresh(player.TSPlayer); + region.Area = newArea; + region.CalculateArea(player.TSPlayer); + } + } + else + { + anyRegions = true; + } + } + } + + if (player.IsViewingNearby) + { + anyRegions = true; + + // Search for nearby regions + foreach (var tRegion in TShock.Regions.Regions) + { + if (tRegion.WorldID == Main.worldID.ToString() && tRegion.Area.Width >= 0 && tRegion.Area.Height >= 0) + { + if (IsPlayerNearby(player.TSPlayer, tRegion.Area)) + { + if (!player.Regions.Any(r => r.Name == tRegion.Name)) + { + refreshFlag = true; + var region = new Region(tRegion.Name, tRegion.Area, false); + region.CalculateArea(player.TSPlayer); + player.Regions.Add(region); + player.TSPlayer.SendMessage("你正在看区域 " + region.Name + ".", TextColors[region.Color - 13]); + } + } + } + } + } + + if (refreshFlag) + { + foreach (var region in player.Regions) + region.SetFakeTiles(); + foreach (var region in player.Regions) + region.Refresh(player.TSPlayer); + foreach (var region in player.Regions.Reverse()) + region.UnsetFakeTiles(); + } + } + } + + if (anyRegions) + { + _refreshTimer.Interval = 7000; + _refreshTimer.Enabled = true; + } + } + + public static bool IsPlayerNearby(TSPlayer tPlayer, Rectangle area) + { + var playerX = (int)(tPlayer.X / 16); + var playerY = (int)(tPlayer.Y / 16); + + return playerX >= area.Left - NearRange && + playerX <= area.Right + NearRange && + playerY >= area.Top - NearRange && + playerY <= area.Bottom + NearRange; + } + + public static bool TileValidityCheck(Region region, int x, int y, GetDataHandlers.EditAction editType) + { + // Check if there's a wall or another tile next to this tile. + if (editType == GetDataHandlers.EditAction.PlaceWall) + { + if (Main.tile[x, y] != null && Main.tile[x, y].active()) + return true; + + if (Main.tile[x - 1, y] != null && ((Main.tile[x - 1, y].active() && !Main.tileNoAttach[Main.tile[x - 1, y].type]) || Main.tile[x - 1, y].wall > 0)) + return true; + + if (Main.tile[x + 1, y] != null && ((Main.tile[x + 1, y].active() && !Main.tileNoAttach[Main.tile[x + 1, y].type]) || Main.tile[x + 1, y].wall > 0)) + return true; + + if (Main.tile[x, y - 1] != null && ((Main.tile[x, y - 1].active() && !Main.tileNoAttach[Main.tile[x, y - 1].type]) || Main.tile[x, y - 1].wall > 0)) + return true; + + if (Main.tile[x, y + 1] != null && ((Main.tile[x, y + 1].active() && !Main.tileNoAttach[Main.tile[x, y + 1].type]) || Main.tile[x, y + 1].wall > 0)) + return true; + } + else + { + if (Main.tile[x, y] != null && Main.tile[x, y].wall > 0) + return true; + + if (Main.tile[x - 1, y] != null && Main.tile[x - 1, y].wall > 0) + return true; + + if (Main.tile[x + 1, y] != null && Main.tile[x + 1, y].wall > 0) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y - 1].wall > 0) + return true; + + if (Main.tile[x, y + 1] != null && Main.tile[x, y + 1].wall > 0) + return true; + + if (Main.tile[x - 1, y] != null && Main.tile[x - 1, y].active() && !Main.tileNoAttach[Main.tile[x - 1, y].type]) + return true; + + if (Main.tile[x + 1, y] != null && Main.tile[x + 1, y].active() && !Main.tileNoAttach[Main.tile[x + 1, y].type]) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y - 1].active() && !Main.tileNoAttach[Main.tile[x, y - 1].type]) + return true; + + if (Main.tile[x, y - 1] != null && Main.tile[x, y + 1].active() && !Main.tileNoAttach[Main.tile[x, y + 1].type]) + return true; + } + + // Check if this tile is next to a region boundary. + return x < region.ShowArea.Left - 1 || x > region.ShowArea.Right + 1 || y < region.ShowArea.Top - 1 || y > region.ShowArea.Bottom + 1 || + x >= region.ShowArea.Left + 2 && x <= region.ShowArea.Right - 2 && y >= region.ShowArea.Top + 2 && y <= region.ShowArea.Bottom - 2; + } + + public static void GiveTile(RegionPlayer player, GetDataHandlers.TileEditEventArgs e) + { + var item = new Item(); + var found = false; + + for (var i = 1; i <= Terraria.ID.ItemID.Count; i++) + { + item.SetDefaults(i, true); + if (item.createTile == e.EditData && item.placeStyle == e.Style) + { + if (item.tileWand != -1) item.SetDefaults(item.tileWand, true); + found = true; + break; + } + } + + if (found) + GiveItem(player, item); + } + + public static void GiveWall(RegionPlayer player, GetDataHandlers.TileEditEventArgs e) + { + var item = new Item(); var found = false; + for (var i = 1; i <= Terraria.ID.ItemID.Count; i++) + { + item.SetDefaults(i, true); + if (item.createWall == e.EditData) + { + found = true; + break; + } + } + if (found) + { + item.stack = 1; + GiveItem(player, item); + } + } + + public static void GiveItem(RegionPlayer player, Item item) + => player.TSPlayer.GiveItem(item.type, 1); + } +} \ No newline at end of file diff --git a/RegionView/RegionView.csproj b/RegionView/RegionView.csproj new file mode 100644 index 000000000..04c81dc33 --- /dev/null +++ b/RegionView/RegionView.csproj @@ -0,0 +1,3 @@ + + + \ No newline at end of file From d24d4690ee48a8ff8f371d7fb74c74aad28a0b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=9D=E5=B8=9D=E7=86=99=E6=81=A9?= <111548550+THEXN@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:11:43 +0800 Subject: [PATCH 4/4] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 973495c4f..8dea10c66 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,4 @@ | [DisableGodMod](https://github.com/Controllerdestiny/TShockPlugin/tree/master/DisableGodMod) | 阻止玩家无敌 | 无 | | [TownNPCHomes](https://github.com/Controllerdestiny/TShockPlugin/tree/master/TownNPCHomes) | NPC快速回家 | 无 | | [RegionView](https://github.com/Controllerdestiny/TShockPlugin/tree/master/RegionView) | 显示区域边界 | 无 | +| [Noagent](https://github.com/Controllerdestiny/TShockPlugin/tree/master/Noagent) | 禁止代理ip进入 | 无 |