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进入 | 无 |