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"