From 095b826178b8befe01c556f9d21f52a5c9281d68 Mon Sep 17 00:00:00 2001 From: anth64 Date: Sun, 9 Aug 2026 20:05:15 +0200 Subject: [PATCH] feat: deny non-members from modifying claim link buffer zones, bump to 0.4.0 --- ClaimLink/ClaimLink.csproj | 6 ++ ClaimLink/ClaimLinkBufferIndex.cs | 67 +++++++++++++++++++++ ClaimLink/ClaimLinkBufferProtection.cs | 78 +++++++++++++++++++++++++ ClaimLink/ClaimLinkModSystem.cs | 5 ++ ClaimLink/ClaimLinkRegistry.cs | 2 + ClaimLink/CommandListener.cs | 3 + ClaimLink/assets/claimlink/lang/en.json | 4 ++ ClaimLink/modinfo.json | 2 +- 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 ClaimLink/ClaimLinkBufferIndex.cs create mode 100644 ClaimLink/ClaimLinkBufferProtection.cs create mode 100644 ClaimLink/assets/claimlink/lang/en.json diff --git a/ClaimLink/ClaimLink.csproj b/ClaimLink/ClaimLink.csproj index ae0fee9..9f65537 100644 --- a/ClaimLink/ClaimLink.csproj +++ b/ClaimLink/ClaimLink.csproj @@ -34,4 +34,10 @@ + + + PreserveNewest + + + diff --git a/ClaimLink/ClaimLinkBufferIndex.cs b/ClaimLink/ClaimLinkBufferIndex.cs new file mode 100644 index 0000000..5f56d15 --- /dev/null +++ b/ClaimLink/ClaimLinkBufferIndex.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Vintagestory.API.MathTools; + +namespace ClaimLink; + +internal static class ClaimLinkBufferIndex +{ + private static readonly Dictionary> groupIdsByRegion = new(); + private static readonly Dictionary> buffersByGroupId = new(); + + internal static void Rebuild() + { + groupIdsByRegion.Clear(); + buffersByGroupId.Clear(); + + int regionSize = ClaimLinkModSystem.WorldManager.RegionSize; + if (regionSize <= 0) + return; + + foreach (int groupId in ClaimLinkModSystem.Registry.All) + { + List buffers = ClaimLinkBuffer.GetBuffer(groupId); + buffersByGroupId[groupId] = buffers; + + HashSet regionKeys = new(); + foreach (Cuboidi buffer in buffers) + { + int minRegionX = buffer.MinX / regionSize; + int maxRegionX = buffer.MaxX / regionSize; + int minRegionZ = buffer.MinZ / regionSize; + int maxRegionZ = buffer.MaxZ / regionSize; + + for (int rx = minRegionX; rx <= maxRegionX; rx++) + for (int rz = minRegionZ; rz <= maxRegionZ; rz++) + regionKeys.Add(ClaimLinkModSystem.WorldManager.MapRegionIndex2D(rx, rz)); + } + + foreach (long regionKey in regionKeys) + { + if (!groupIdsByRegion.TryGetValue(regionKey, out HashSet? groupIds)) + groupIdsByRegion[regionKey] = groupIds = new(); + groupIds.Add(groupId); + } + } + } + + internal static IEnumerable CandidateGroupIds(BlockPos pos) + { + int regionSize = ClaimLinkModSystem.WorldManager.RegionSize; + if (regionSize <= 0) + return System.Array.Empty(); + + long regionKey = ClaimLinkModSystem.WorldManager.MapRegionIndex2D( + pos.X / regionSize, + pos.Z / regionSize + ); + + return groupIdsByRegion.TryGetValue(regionKey, out HashSet? groupIds) + ? groupIds + : System.Array.Empty(); + } + + internal static List CachedBuffer(int groupId) => + buffersByGroupId.TryGetValue(groupId, out List? buffers) + ? buffers + : new List(); +} diff --git a/ClaimLink/ClaimLinkBufferProtection.cs b/ClaimLink/ClaimLinkBufferProtection.cs new file mode 100644 index 0000000..9e5a201 --- /dev/null +++ b/ClaimLink/ClaimLinkBufferProtection.cs @@ -0,0 +1,78 @@ +using Vintagestory.API.Common; +using Vintagestory.API.MathTools; +using Vintagestory.API.Server; + +namespace ClaimLink; + +internal static class ClaimLinkBufferProtection +{ + internal static void Register(ICoreServerAPI api) + { + api.Event.CanPlaceOrBreakBlock += OnCanPlaceOrBreakBlock; + api.Event.CanUseBlock += OnCanUseBlock; + } + + private static bool OnCanUseBlock(IServerPlayer byPlayer, BlockSelection blockSel) => + IsAllowed(byPlayer, blockSel.Position, EnumBlockAccessFlags.Use); + + private static bool OnCanPlaceOrBreakBlock( + IServerPlayer byPlayer, + BlockSelection blockSel, + out string claimant + ) + { + bool allowed = IsAllowed(byPlayer, blockSel.Position, EnumBlockAccessFlags.BuildOrBreak); + claimant = allowed ? null! : "custommessage-claimlink-buffer-zone"; + return allowed; + } + + private static bool IsAllowed( + IServerPlayer byPlayer, + BlockPos pos, + EnumBlockAccessFlags accessFlag + ) + { + bool allowed = true; + + foreach (int groupId in ClaimLinkBufferIndex.CandidateGroupIds(pos)) + { + if (IsMember(byPlayer, groupId)) + continue; + + foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId)) + if (buffer.Contains(pos)) + allowed = false; + } + + return allowed || HasClaimAccess(byPlayer, pos, accessFlag); + } + + private static bool IsMember(IServerPlayer player, int groupId) + { + foreach (PlayerGroupMembership membership in player.Groups) + if (membership.GroupUid == groupId) + return true; + return false; + } + + private static bool HasClaimAccess( + IServerPlayer player, + BlockPos pos, + EnumBlockAccessFlags accessFlag + ) + { + LandClaim[]? claims = ClaimLinkModSystem.LandClaimAPI.Get(pos); + if (claims == null) + return false; + + foreach (LandClaim claim in claims) + { + if (claim.TestPlayerAccess(player, accessFlag) != EnumPlayerAccessResult.Denied) + return true; + if (accessFlag == EnumBlockAccessFlags.Use && claim.AllowUseEveryone) + return true; + } + + return false; + } +} diff --git a/ClaimLink/ClaimLinkModSystem.cs b/ClaimLink/ClaimLinkModSystem.cs index aa1f5a2..01a2ff2 100644 --- a/ClaimLink/ClaimLinkModSystem.cs +++ b/ClaimLink/ClaimLinkModSystem.cs @@ -12,6 +12,7 @@ public class ClaimLinkModSystem : ModSystem internal static ClaimLinkRegistry Registry = null!; internal static IGroupManager Groups = null!; internal static IWorldAccessor World = null!; + internal static IWorldManagerAPI WorldManager = null!; internal static IPlayerDataManager PlayerData = null!; internal static ClaimLinkConfig Config = null!; internal CommandListener? CmdListener; @@ -23,12 +24,15 @@ public class ClaimLinkModSystem : ModSystem { LandClaimAPI = api.World.Claims; World = api.World; + WorldManager = api.WorldManager; Groups = api.Groups; Logger = api.Logger; PlayerData = api.PlayerData; Config = api.LoadModConfig("claimlink.json") ?? new ClaimLinkConfig(); api.StoreModConfig(Config, "claimlink.json"); Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame); + ClaimLinkBufferIndex.Rebuild(); + api.Event.SaveGameLoaded += ClaimLinkBufferIndex.Rebuild; CmdListener = new CommandListener(); CommandHookModSystem.Register(CmdListener); @@ -36,6 +40,7 @@ public class ClaimLinkModSystem : ModSystem CommandHookModSystem.Register(GroupCmdListener); ClaimLinkChatCommand.Register(api); ClaimLinkVisualizerNetwork.Start(api); + ClaimLinkBufferProtection.Register(api); api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect; api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect; diff --git a/ClaimLink/ClaimLinkRegistry.cs b/ClaimLink/ClaimLinkRegistry.cs index 03bfc33..3c73296 100644 --- a/ClaimLink/ClaimLinkRegistry.cs +++ b/ClaimLink/ClaimLinkRegistry.cs @@ -197,5 +197,7 @@ public class ClaimLinkRegistry SaveKey, new ClaimLinkSaveData { Links = claimLinks, Entries = entryList } ); + + ClaimLinkBufferIndex.Rebuild(); } } diff --git a/ClaimLink/CommandListener.cs b/ClaimLink/CommandListener.cs index 40cd2f0..be9232a 100644 --- a/ClaimLink/CommandListener.cs +++ b/ClaimLink/CommandListener.cs @@ -113,7 +113,10 @@ public class CommandListener : ICommandHookListener ); if (!violatesClaimLink && !violatesClaim) + { + ClaimLinkBufferIndex.Rebuild(); return; + } ClaimLinkModSystem.LandClaimAPI.Remove(savedClaim); diff --git a/ClaimLink/assets/claimlink/lang/en.json b/ClaimLink/assets/claimlink/lang/en.json new file mode 100644 index 0000000..0fdd435 --- /dev/null +++ b/ClaimLink/assets/claimlink/lang/en.json @@ -0,0 +1,4 @@ +{ + "custommessage-claimlink-buffer-zone": "This area is a claim link buffer zone.", + "ingameerror-noprivilege-buildbreak-claimlink-buffer-zone": "This area is a claim link buffer zone." +} diff --git a/ClaimLink/modinfo.json b/ClaimLink/modinfo.json index 65ce703..3df6d2c 100644 --- a/ClaimLink/modinfo.json +++ b/ClaimLink/modinfo.json @@ -8,7 +8,7 @@ "anth64" ], "description": "Link claims together with groups.", - "version": "0.3.0", + "version": "0.4.0", "dependencies": { "game": "1.22.5", "commandhook": "2.1.0"