diff --git a/ClaimLink/ClaimLinkBufferIndex.cs b/ClaimLink/ClaimLinkBufferIndex.cs index 5f56d15..dbcd063 100644 --- a/ClaimLink/ClaimLinkBufferIndex.cs +++ b/ClaimLink/ClaimLinkBufferIndex.cs @@ -1,25 +1,32 @@ +using System; using System.Collections.Generic; using Vintagestory.API.MathTools; namespace ClaimLink; -internal static class ClaimLinkBufferIndex +internal class ClaimLinkBufferIndex { - private static readonly Dictionary> groupIdsByRegion = new(); - private static readonly Dictionary> buffersByGroupId = new(); + private readonly Func regionIndex; + private readonly Dictionary> groupIdsByRegion = new(); + private readonly Dictionary> buffersByGroupId = new(); + private int regionSize; - internal static void Rebuild() + internal ClaimLinkBufferIndex(Func regionIndex) { + this.regionIndex = regionIndex; + } + + internal void Rebuild(IEnumerable<(int groupId, List buffers)> source, int regionSize) + { + this.regionSize = regionSize; groupIdsByRegion.Clear(); buffersByGroupId.Clear(); - int regionSize = ClaimLinkModSystem.WorldManager.RegionSize; if (regionSize <= 0) return; - foreach (int groupId in ClaimLinkModSystem.Registry.All) + foreach ((int groupId, List buffers) in source) { - List buffers = ClaimLinkBuffer.GetBuffer(groupId); buffersByGroupId[groupId] = buffers; HashSet regionKeys = new(); @@ -32,7 +39,7 @@ internal static class ClaimLinkBufferIndex for (int rx = minRegionX; rx <= maxRegionX; rx++) for (int rz = minRegionZ; rz <= maxRegionZ; rz++) - regionKeys.Add(ClaimLinkModSystem.WorldManager.MapRegionIndex2D(rx, rz)); + regionKeys.Add(regionIndex(rx, rz)); } foreach (long regionKey in regionKeys) @@ -44,23 +51,19 @@ internal static class ClaimLinkBufferIndex } } - internal static IEnumerable CandidateGroupIds(BlockPos pos) + internal IEnumerable CandidateGroupIds(BlockPos pos) { - int regionSize = ClaimLinkModSystem.WorldManager.RegionSize; if (regionSize <= 0) - return System.Array.Empty(); + return Array.Empty(); - long regionKey = ClaimLinkModSystem.WorldManager.MapRegionIndex2D( - pos.X / regionSize, - pos.Z / regionSize - ); + long regionKey = regionIndex(pos.X / regionSize, pos.Z / regionSize); return groupIdsByRegion.TryGetValue(regionKey, out HashSet? groupIds) ? groupIds - : System.Array.Empty(); + : Array.Empty(); } - internal static List CachedBuffer(int groupId) => + internal List CachedBuffer(int groupId) => buffersByGroupId.TryGetValue(groupId, out List? buffers) ? buffers : new List(); diff --git a/ClaimLink/ClaimLinkBufferProtection.cs b/ClaimLink/ClaimLinkBufferProtection.cs index ede7d99..11b199e 100644 --- a/ClaimLink/ClaimLinkBufferProtection.cs +++ b/ClaimLink/ClaimLinkBufferProtection.cs @@ -34,12 +34,12 @@ internal static class ClaimLinkBufferProtection { bool allowed = true; - foreach (int groupId in ClaimLinkBufferIndex.CandidateGroupIds(pos)) + foreach (int groupId in ClaimLinkModSystem.BufferIndex.CandidateGroupIds(pos)) { if (IsMember(byPlayer, groupId)) continue; - foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId)) + foreach (Cuboidi buffer in ClaimLinkModSystem.BufferIndex.CachedBuffer(groupId)) if ( buffer.Contains(pos) && !ClaimLinkModSystem.Registry.HasPermission(groupId, byPlayer, accessFlag) diff --git a/ClaimLink/ClaimLinkModSystem.cs b/ClaimLink/ClaimLinkModSystem.cs index 8e6f965..2a24b1d 100644 --- a/ClaimLink/ClaimLinkModSystem.cs +++ b/ClaimLink/ClaimLinkModSystem.cs @@ -15,6 +15,7 @@ public class ClaimLinkModSystem : ModSystem internal static IWorldManagerAPI WorldManager = null!; internal static IPlayerDataManager PlayerData = null!; internal static ClaimLinkConfig Config = null!; + internal static ClaimLinkBufferIndex BufferIndex = null!; private object? cmdListenerProxy; private object? groupCmdListenerProxy; @@ -29,8 +30,9 @@ public class ClaimLinkModSystem : ModSystem 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; + BufferIndex = new ClaimLinkBufferIndex(WorldManager.MapRegionIndex2D); + RebuildBufferIndex(); + api.Event.SaveGameLoaded += RebuildBufferIndex; if (api.ModLoader.IsModEnabled("commandhook")) { @@ -75,6 +77,14 @@ public class ClaimLinkModSystem : ModSystem api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect; } + internal static void RebuildBufferIndex() + { + List<(int groupId, List buffers)> source = new(); + foreach (int groupId in Registry.All) + source.Add((groupId, ClaimLinkBuffer.GetBuffer(groupId))); + BufferIndex.Rebuild(source, WorldManager.RegionSize); + } + public override void StartClientSide(ICoreClientAPI api) => ClaimLinkVisualizerClient.Start(api); public override void Dispose() diff --git a/ClaimLink/ClaimLinkRegistry.cs b/ClaimLink/ClaimLinkRegistry.cs index 2319900..f13cbad 100644 --- a/ClaimLink/ClaimLinkRegistry.cs +++ b/ClaimLink/ClaimLinkRegistry.cs @@ -58,7 +58,7 @@ public class ClaimLinkRegistry public void Add(int groupId) { claimLinks.Add(groupId); - Save(); + Save(new[] { groupId }); } public void Remove(int groupId) @@ -69,7 +69,7 @@ public class ClaimLinkRegistry permittedPlayerUids.Remove(groupId); permittedPlayerGroupIds.Remove(groupId); Prune(); - Save(); + Save(new[] { groupId }); } public void GrantPlayer(int groupId, string uid, EnumBlockAccessFlags flags) @@ -77,7 +77,7 @@ public class ClaimLinkRegistry if (!permittedPlayerUids.TryGetValue(groupId, out var uids)) permittedPlayerUids[groupId] = uids = new(); uids[uid] = flags; - Save(); + Save(new[] { groupId }); } public bool RevokePlayer(int groupId, string uid) @@ -87,7 +87,7 @@ public class ClaimLinkRegistry return false; if (uids!.Count == 0) permittedPlayerUids.Remove(groupId); - Save(); + Save(new[] { groupId }); return true; } @@ -96,7 +96,7 @@ public class ClaimLinkRegistry if (!permittedPlayerGroupIds.TryGetValue(groupId, out var groups)) permittedPlayerGroupIds[groupId] = groups = new(); groups[targetGroupId] = flags; - Save(); + Save(new[] { groupId }); } public bool RevokeGroup(int groupId, int targetGroupId) @@ -108,7 +108,7 @@ public class ClaimLinkRegistry return false; if (groups!.Count == 0) permittedPlayerGroupIds.Remove(groupId); - Save(); + Save(new[] { groupId }); return true; } @@ -138,12 +138,12 @@ public class ClaimLinkRegistry public void RemoveGroupFromAllPermissions(int targetGroupId) { - bool changed = false; - foreach (var groups in permittedPlayerGroupIds.Values) + List? changedGroupIds = null; + foreach (var (groupId, groups) in permittedPlayerGroupIds) if (groups.Remove(targetGroupId)) - changed = true; + (changedGroupIds ??= new()).Add(groupId); - if (!changed) + if (changedGroupIds == null) return; List? emptyGroupIds = null; @@ -154,7 +154,7 @@ public class ClaimLinkRegistry foreach (int groupId in emptyGroupIds) permittedPlayerGroupIds.Remove(groupId); - Save(); + Save(changedGroupIds); } public bool IsClaimLinked(string uid, int claimIndex) => @@ -181,7 +181,7 @@ public class ClaimLinkRegistry if (!groups.TryGetValue(groupId, out var claims)) groups[groupId] = claims = new(); claims.Add(claimIndex); - Save(); + Save(new[] { groupId }); return true; } @@ -190,7 +190,7 @@ public class ClaimLinkRegistry if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims)) claims.Remove(claimIndex); Prune(); - Save(); + Save(new[] { groupId }); } public void ClaimRemoved(string uid, int removedIndex) @@ -198,6 +198,8 @@ public class ClaimLinkRegistry if (!entries.TryGetValue(uid, out var groups)) return; + int[] affectedGroupIds = new List(groups.Keys).ToArray(); + foreach (var claims in groups.Values) { claims.Remove(removedIndex); @@ -207,7 +209,7 @@ public class ClaimLinkRegistry } Prune(); - Save(); + Save(affectedGroupIds); } public void ClaimSaved(string uid, int claimIndex) @@ -215,6 +217,8 @@ public class ClaimLinkRegistry if (!entries.TryGetValue(uid, out var groups)) return; + int[] affectedGroupIds = new List(groups.Keys).ToArray(); + int count = 0; foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All) if (c.OwnedByPlayerUid == uid) @@ -233,7 +237,7 @@ public class ClaimLinkRegistry claims[i] = newIndex; } - Save(); + Save(affectedGroupIds); } public bool HasAnyEntry(string uid, int groupId) => @@ -246,13 +250,17 @@ public class ClaimLinkRegistry if (entries.TryGetValue(uid, out var groups)) groups.Remove(groupId); Prune(); - Save(); + Save(new[] { groupId }); } public void ClearAllForPlayer(string uid) { + int[] affectedGroupIds = entries.TryGetValue(uid, out var groups) + ? new List(groups.Keys).ToArray() + : System.Array.Empty(); + entries.Remove(uid); - Save(); + Save(affectedGroupIds); } public IEnumerable MemberUidsForGroup(int groupId) @@ -286,7 +294,7 @@ public class ClaimLinkRegistry entries.Remove(uid); } - public void Save() + private void Save(IReadOnlyCollection affectedGroupIds) { var entryList = new List(); foreach (var (uid, groups) in entries) @@ -331,6 +339,7 @@ public class ClaimLinkRegistry } ); - ClaimLinkBufferIndex.Rebuild(); + ClaimLinkModSystem.RebuildBufferIndex(); + ClaimLinkVisualizerNetwork.BroadcastDelta(affectedGroupIds); } } diff --git a/ClaimLink/ClaimLinkVisualizerClient.cs b/ClaimLink/ClaimLinkVisualizerClient.cs index aa71c8b..e8a4bc7 100644 --- a/ClaimLink/ClaimLinkVisualizerClient.cs +++ b/ClaimLink/ClaimLinkVisualizerClient.cs @@ -8,10 +8,14 @@ namespace ClaimLink; internal static class ClaimLinkVisualizerClient { private const int HighlightSlotId = 8341; + private const int ClientRegionSize = 512; + private static ICoreClientAPI capi = null!; private static bool visible; private static ClaimLinkVisualizerConfig config = null!; private static IClientNetworkChannel channel = null!; + private static readonly Dictionary knownEntries = new(); + private static readonly ClaimLinkBufferIndex bufferIndex = new(ClientRegionKey); internal static void Start(ICoreClientAPI api) { @@ -20,9 +24,8 @@ internal static class ClaimLinkVisualizerClient api.StoreModConfig(config, "claimlinkvisualizer.json"); channel = api.Network.RegisterChannel(ClaimLinkVisualizerNetwork.ChannelName) - .RegisterMessageType() .RegisterMessageType() - .SetMessageHandler(OnBufferResponse); + .SetMessageHandler(OnBuffersPushed); api.Input.RegisterHotKey( "claimlinkvisualizertoggle", @@ -32,35 +35,134 @@ internal static class ClaimLinkVisualizerClient ctrlPressed: true ); api.Input.SetHotKeyHandler("claimlinkvisualizertoggle", OnToggle); + + api.Event.TestBlockAccess += OnTestBlockAccess; } + private static long ClientRegionKey(int regionX, int regionZ) => ((long)regionX << 32) | (uint)regionZ; + private static bool OnToggle(KeyCombination comb) { visible = !visible; if (visible) - { - if (channel.Connected) - channel.SendPacket(new ClaimLinkBufferRequest()); - } + RenderHighlights(); else - { Clear(); - } return true; } - private static void OnBufferResponse(ClaimLinkBufferResponse response) + private static void OnBuffersPushed(ClaimLinkBufferResponse response) { - if (!visible) - return; - - List positions = new(); - List colors = new(); + if (response.IsFullSync) + knownEntries.Clear(); foreach (ClaimLinkBufferEntryDto entry in response.Entries) { - int color = HashToColor(entry.GroupId); + if (entry.Cuboids.Count == 0) + knownEntries.Remove(entry.GroupId); + else + knownEntries[entry.GroupId] = entry; + } + + RebuildIndex(); + + if (visible) + RenderHighlights(); + } + + private static void RebuildIndex() + { + List<(int groupId, List buffers)> source = new(); + + foreach ((int groupId, ClaimLinkBufferEntryDto entry) in knownEntries) + { + List cuboids = new(); + foreach (ClaimLinkBufferCuboidDto c in entry.Cuboids) + cuboids.Add(new Cuboidi(c.MinX, c.MinY, c.MinZ, c.MaxX, c.MaxY, c.MaxZ)); + source.Add((groupId, cuboids)); + } + + bufferIndex.Rebuild(source, ClientRegionSize); + } + + private static EnumWorldAccessResponse OnTestBlockAccess( + IPlayer player, + BlockSelection blockSel, + EnumBlockAccessFlags accessType, + ref string claimant, + EnumWorldAccessResponse response + ) + { + if (response != EnumWorldAccessResponse.Granted) + return response; + + BlockPos pos = blockSel.Position; + + foreach (int groupId in bufferIndex.CandidateGroupIds(pos)) + { + if (IsMember(groupId)) + continue; + + bool inBuffer = false; + foreach (Cuboidi buffer in bufferIndex.CachedBuffer(groupId)) + if (buffer.Contains(pos)) + { + inBuffer = true; + break; + } + if (!inBuffer) + continue; + + EnumBlockAccessFlags granted = knownEntries.TryGetValue(groupId, out var entry) + ? (EnumBlockAccessFlags)entry.GrantedAccess + : EnumBlockAccessFlags.None; + if ((granted & accessType) != 0) + continue; + + if (HasClaimAccess(pos, accessType)) + continue; + + claimant = "the claim link's buffer zone"; + return EnumWorldAccessResponse.DeniedByMod; + } + + return response; + } + + private static bool IsMember(int groupId) + { + foreach (PlayerGroupMembership membership in capi.World.Player.Groups) + if (membership.GroupUid == groupId) + return true; + return false; + } + + private static bool HasClaimAccess(BlockPos pos, EnumBlockAccessFlags accessFlag) + { + LandClaim[]? claims = capi.World.Claims.Get(pos); + if (claims == null) + return false; + + foreach (LandClaim claim in claims) + { + if (claim.TestPlayerAccess(capi.World.Player, accessFlag) != EnumPlayerAccessResult.Denied) + return true; + if (accessFlag == EnumBlockAccessFlags.Use && claim.AllowUseEveryone) + return true; + } + + return false; + } + + private static void RenderHighlights() + { + List positions = new(); + List colors = new(); + + foreach ((int groupId, ClaimLinkBufferEntryDto entry) in knownEntries) + { + int color = HashToColor(groupId); foreach (ClaimLinkBufferCuboidDto cuboid in entry.Cuboids) { positions.Add(new BlockPos(cuboid.MinX, cuboid.MinY, cuboid.MinZ)); diff --git a/ClaimLink/ClaimLinkVisualizerNetwork.cs b/ClaimLink/ClaimLinkVisualizerNetwork.cs index a6dd693..2959c3d 100644 --- a/ClaimLink/ClaimLinkVisualizerNetwork.cs +++ b/ClaimLink/ClaimLinkVisualizerNetwork.cs @@ -1,13 +1,11 @@ using System.Collections.Generic; using ProtoBuf; +using Vintagestory.API.Common; using Vintagestory.API.MathTools; using Vintagestory.API.Server; namespace ClaimLink; -[ProtoContract] -public class ClaimLinkBufferRequest { } - [ProtoContract] public class ClaimLinkBufferCuboidDto { @@ -38,6 +36,9 @@ public class ClaimLinkBufferEntryDto [ProtoMember(2)] public List Cuboids = new(); + + [ProtoMember(3)] + public int GrantedAccess; } [ProtoContract] @@ -45,62 +46,80 @@ public class ClaimLinkBufferResponse { [ProtoMember(1)] public List Entries = new(); + + [ProtoMember(2)] + public bool IsFullSync; } internal static class ClaimLinkVisualizerNetwork { internal const string ChannelName = "claimlinkvisualizer"; + private static IServerNetworkChannel channel = null!; internal static void Start(ICoreServerAPI api) { - api.Network.RegisterChannel(ChannelName) - .RegisterMessageType() - .RegisterMessageType() - .SetMessageHandler( - (fromPlayer, _) => SendBuffers(api, fromPlayer) - ); + channel = api.Network.RegisterChannel(ChannelName).RegisterMessageType(); + + api.Event.PlayerNowPlaying += SendFullSync; } - private static void SendBuffers(ICoreServerAPI api, IServerPlayer toPlayer) + private static void SendFullSync(IServerPlayer toPlayer) { - ClaimLinkBufferResponse response = new(); - - BlockPos playerPos = toPlayer.Entity.Pos.AsBlockPos; - int radius = toPlayer.WorldData.LastApprovedViewDistance; + ClaimLinkBufferResponse response = new() { IsFullSync = true }; foreach (int groupId in ClaimLinkModSystem.Registry.All) { - ClaimLinkBufferEntryDto entry = new() { GroupId = groupId }; - foreach (Cuboidi cuboid in ClaimLinkBuffer.GetBuffer(groupId)) - { - if (!IsNearby(cuboid, playerPos, radius)) - continue; - - entry.Cuboids.Add( - new ClaimLinkBufferCuboidDto - { - MinX = cuboid.MinX, - MinY = cuboid.MinY, - MinZ = cuboid.MinZ, - MaxX = cuboid.MaxX, - MaxY = cuboid.MaxY, - MaxZ = cuboid.MaxZ, - } - ); - } - + ClaimLinkBufferEntryDto entry = BuildEntry(groupId, toPlayer); if (entry.Cuboids.Count > 0) response.Entries.Add(entry); } - api.Network.GetChannel(ChannelName).SendPacket(response, toPlayer); + channel.SendPacket(response, toPlayer); } - private static bool IsNearby(Cuboidi cuboid, BlockPos playerPos, int radius) => - cuboid.MinX <= playerPos.X + radius - && cuboid.MaxX >= playerPos.X - radius - && cuboid.MinY <= playerPos.Y + radius - && cuboid.MaxY >= playerPos.Y - radius - && cuboid.MinZ <= playerPos.Z + radius - && cuboid.MaxZ >= playerPos.Z - radius; + internal static void BroadcastDelta(IReadOnlyCollection affectedGroupIds) + { + if (affectedGroupIds.Count == 0 || channel == null) + return; + + foreach (IPlayer onlinePlayer in ClaimLinkModSystem.World.AllOnlinePlayers) + { + if (onlinePlayer is not IServerPlayer serverPlayer) + continue; + + ClaimLinkBufferResponse response = new() { IsFullSync = false }; + foreach (int groupId in affectedGroupIds) + response.Entries.Add(BuildEntry(groupId, serverPlayer)); + + channel.SendPacket(response, serverPlayer); + } + } + + private static ClaimLinkBufferEntryDto BuildEntry(int groupId, IServerPlayer forPlayer) + { + EnumBlockAccessFlags granted = EnumBlockAccessFlags.None; + if (ClaimLinkModSystem.Registry.HasPermission(groupId, forPlayer, EnumBlockAccessFlags.BuildOrBreak)) + granted |= EnumBlockAccessFlags.BuildOrBreak; + if (ClaimLinkModSystem.Registry.HasPermission(groupId, forPlayer, EnumBlockAccessFlags.Use)) + granted |= EnumBlockAccessFlags.Use; + + ClaimLinkBufferEntryDto entry = new() { GroupId = groupId, GrantedAccess = (int)granted }; + + foreach (Cuboidi cuboid in ClaimLinkBuffer.GetBuffer(groupId)) + { + entry.Cuboids.Add( + new ClaimLinkBufferCuboidDto + { + MinX = cuboid.MinX, + MinY = cuboid.MinY, + MinZ = cuboid.MinZ, + MaxX = cuboid.MaxX, + MaxY = cuboid.MaxY, + MaxZ = cuboid.MaxZ, + } + ); + } + + return entry; + } } diff --git a/ClaimLink/CommandListener.cs b/ClaimLink/CommandListener.cs index 07d1944..58f64af 100644 --- a/ClaimLink/CommandListener.cs +++ b/ClaimLink/CommandListener.cs @@ -111,7 +111,7 @@ public class CommandListener if (!violatesClaimLink && !violatesClaim) { - ClaimLinkBufferIndex.Rebuild(); + ClaimLinkModSystem.RebuildBufferIndex(); return; }