From fffc046126d6e0afebd5b13541da6274eebae612 Mon Sep 17 00:00:00 2001 From: anth64 Date: Wed, 15 Jul 2026 18:44:50 +0200 Subject: [PATCH] refactor: normalize claim link membership into flat entry storage --- ClaimLink/ClaimLink.cs | 23 +++--- ClaimLink/ClaimLinkChatCommand.cs | 60 ++++++--------- ClaimLink/ClaimLinkModSystem.cs | 7 -- ClaimLink/ClaimLinkRegistry.cs | 120 ++++++++++++++++++++++++++++-- 4 files changed, 145 insertions(+), 65 deletions(-) diff --git a/ClaimLink/ClaimLink.cs b/ClaimLink/ClaimLink.cs index 3c4d196..0c15fbf 100644 --- a/ClaimLink/ClaimLink.cs +++ b/ClaimLink/ClaimLink.cs @@ -1,24 +1,23 @@ -using System.Collections.Generic; using ProtoBuf; namespace ClaimLink; -[ProtoContract] -public class ClaimLinkMember -{ - [ProtoMember(1)] - public string OwnerPlayerUid = ""; - - [ProtoMember(2)] - public List LocalClaimIndices = new(); -} - [ProtoContract] public class ClaimLink { [ProtoMember(1)] public int GroupId; +} + +[ProtoContract] +public class ClaimLinkEntry +{ + [ProtoMember(1)] + public string OwnerPlayerUid = ""; [ProtoMember(2)] - public List Members = new(); + public int ClaimIndex; + + [ProtoMember(3)] + public int GroupId; } diff --git a/ClaimLink/ClaimLinkChatCommand.cs b/ClaimLink/ClaimLinkChatCommand.cs index 7a450b8..b49af47 100644 --- a/ClaimLink/ClaimLinkChatCommand.cs +++ b/ClaimLink/ClaimLinkChatCommand.cs @@ -328,15 +328,7 @@ public static class ClaimLinkChatCommand $"{claimDesc} will be linked into '{groupName}'.", () => { - ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == playerUid); - if (member == null) - { - member = new ClaimLinkMember { OwnerPlayerUid = playerUid }; - link.Members.Add(member); - } - - member.LocalClaimIndices.Add(claimIndex); - ClaimLinkModSystem.Registry.Save(); + ClaimLinkModSystem.Registry.AddEntry(playerUid, link.GroupId, claimIndex); return TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'."); } @@ -362,12 +354,7 @@ public static class ClaimLinkChatCommand $"{claimDesc} will be unlinked from '{groupName}'.", () => { - ClaimLinkMember member = link.Members.Find(m => m.OwnerPlayerUid == playerUid)!; - member.LocalClaimIndices.Remove(claimIndex); - if (member.LocalClaimIndices.Count == 0) - link.Members.Remove(member); - - ClaimLinkModSystem.Registry.Save(); + ClaimLinkModSystem.Registry.RemoveEntry(playerUid, link.GroupId, claimIndex); return TextCommandResult.Success($"Unlinked {claimDesc} from '{groupName}'."); } ); @@ -382,7 +369,7 @@ public static class ClaimLinkChatCommand if (err != null) return err; - err = TryResolveClaimLink(group, out ClaimLink link); + err = TryResolveClaimLink(group, out _); if (err != null) return err; @@ -393,8 +380,7 @@ public static class ClaimLinkChatCommand string targetUid = target.PlayerUID; string playerUid = args.Caller.Player.PlayerUID; - ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == targetUid); - if (member == null) + if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid)) return TextCommandResult.Error( $"{target.PlayerName} has no claims linked in '{groupName}'." ); @@ -404,9 +390,8 @@ public static class ClaimLinkChatCommand $"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.", () => { - link.Members.Remove(member); + ClaimLinkModSystem.Registry.RemoveAllForPlayerInGroup(targetUid, group.Uid); RemovePending(targetUid); - ClaimLinkModSystem.Registry.Save(); return TextCommandResult.Success( $"Unlinked all claims of {target.PlayerName} from '{groupName}'." @@ -424,7 +409,7 @@ public static class ClaimLinkChatCommand if (err != null) return err; - err = TryResolveClaimLink(group, out ClaimLink link); + err = TryResolveClaimLink(group, out _); if (err != null) return err; @@ -438,8 +423,8 @@ public static class ClaimLinkChatCommand $"'{groupName}' will be deleted as a claim link.", () => { - foreach (ClaimLinkMember member in link.Members) - RemovePending(member.OwnerPlayerUid); + foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId)) + RemovePending(uid); ClaimLinkModSystem.Registry.Remove(groupId); return TextCommandResult.Success($"'{groupName}' is no longer a claim link."); @@ -531,7 +516,7 @@ public static class ClaimLinkChatCommand string notice = $"'{group.Name}' is now owned by {target.PlayerName}."; - HashSet notifyUids = link.Members.Select(m => m.OwnerPlayerUid).ToHashSet(); + HashSet notifyUids = ClaimLinkModSystem.Registry.MemberUidsForGroup(link.GroupId).ToHashSet(); notifyUids.Add(oldOwnerUid); notifyUids.Add(target.PlayerUID); @@ -565,7 +550,7 @@ public static class ClaimLinkChatCommand public static TextCommandResult List(TextCommandCallingArgs args) { List links = ClaimLinkModSystem - .Registry.All.OrderByDescending(l => l.Members.Count) + .Registry.All.OrderByDescending(l => ClaimLinkModSystem.Registry.MemberCountForGroup(l.GroupId)) .ToList(); if (links.Count == 0) @@ -577,9 +562,8 @@ public static class ClaimLinkChatCommand foreach (ClaimLink link in links) { string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[link.GroupId].Name; - sb.AppendLine( - $" {groupName}: {link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}" - ); + int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(link.GroupId); + sb.AppendLine($" {groupName}: {memberCount} member{(memberCount == 1 ? "" : "s")}"); } return TextCommandResult.Success(sb.ToString()); @@ -603,19 +587,17 @@ public static class ClaimLinkChatCommand private static string FormatInfo(string groupName, ClaimLink link) { - StringBuilder sb = new(); - sb.AppendLine( - $"Claim link '{groupName}' ({link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}):" - ); + int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(link.GroupId); - foreach (ClaimLinkMember member in link.Members) + StringBuilder sb = new(); + sb.AppendLine($"Claim link '{groupName}' ({memberCount} member{(memberCount == 1 ? "" : "s")}):"); + + foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(link.GroupId)) { - string name = - ClaimLinkModSystem.World.PlayerByUid(member.OwnerPlayerUid)?.PlayerName - ?? member.OwnerPlayerUid; - IEnumerable claims = member.LocalClaimIndices.Select(i => - DescribeClaim(member.OwnerPlayerUid, i) - ); + string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid; + IEnumerable claims = ClaimLinkModSystem + .Registry.ClaimsForPlayerInGroup(uid, link.GroupId) + .Select(i => DescribeClaim(uid, i)); sb.AppendLine($" {name}: [{string.Join(", ", claims)}]"); } diff --git a/ClaimLink/ClaimLinkModSystem.cs b/ClaimLink/ClaimLinkModSystem.cs index 0a3bde5..33957bc 100644 --- a/ClaimLink/ClaimLinkModSystem.cs +++ b/ClaimLink/ClaimLinkModSystem.cs @@ -29,13 +29,6 @@ public class ClaimLinkModSystem : ModSystem ClaimLinkChatCommand.Register(api); api.Event.PlayerDisconnect += ClaimLinkCommandListener.OnPlayerDisconnect; - - // TODO remove debug logging once transfer ownership persistence is confirmed tested - foreach (ClaimLink link in Registry.All) - { - PlayerGroup group = Groups.PlayerGroupsById[link.GroupId]; - Logger.Notification($"claimlink: startup state '{group.Name}' (uid {group.Uid}) OwnerUID={group.OwnerUID}"); - } } public override void Dispose() diff --git a/ClaimLink/ClaimLinkRegistry.cs b/ClaimLink/ClaimLinkRegistry.cs index 35b02ac..757b601 100644 --- a/ClaimLink/ClaimLinkRegistry.cs +++ b/ClaimLink/ClaimLinkRegistry.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using ProtoBuf; using Vintagestory.API.Server; @@ -9,6 +10,9 @@ public class ClaimLinkData { [ProtoMember(1)] public List Links = new(); + + [ProtoMember(2)] + public List Entries = new(); } public class ClaimLinkRegistry @@ -18,6 +22,8 @@ public class ClaimLinkRegistry private readonly ISaveGame saveGame; private readonly Dictionary byGroupId = new(); + private readonly Dictionary>> byPlayer = new(); + public ClaimLinkRegistry(ISaveGame saveGame) { this.saveGame = saveGame; @@ -28,9 +34,13 @@ public class ClaimLinkRegistry foreach (ClaimLink link in data.Links) byGroupId[link.GroupId] = link; + + foreach (ClaimLinkEntry entry in data.Entries) + GetOrCreateIndices(entry.OwnerPlayerUid, entry.GroupId).Add(entry.ClaimIndex); } - public ClaimLink? Get(int groupId) => byGroupId.TryGetValue(groupId, out ClaimLink? link) ? link : null; + public ClaimLink? Get(int groupId) => + byGroupId.TryGetValue(groupId, out ClaimLink? link) ? link : null; public IReadOnlyCollection All => byGroupId.Values; @@ -43,23 +53,119 @@ public class ClaimLinkRegistry public void Remove(int groupId) { byGroupId.Remove(groupId); + + List emptyPlayerUids = new(); + foreach (KeyValuePair>> playerEntry in byPlayer) + { + playerEntry.Value.Remove(groupId); + if (playerEntry.Value.Count == 0) + emptyPlayerUids.Add(playerEntry.Key); + } + + foreach (string uid in emptyPlayerUids) + byPlayer.Remove(uid); + Save(); } - public bool IsClaimLinked(string ownerPlayerUid, int localIndex) => FindLinkContaining(ownerPlayerUid, localIndex) != null; + public bool IsClaimLinked(string ownerPlayerUid, int localIndex) => + FindLinkContaining(ownerPlayerUid, localIndex) != null; public ClaimLink? FindLinkContaining(string ownerPlayerUid, int localIndex) { - foreach (ClaimLink link in byGroupId.Values) - foreach (ClaimLinkMember member in link.Members) - if (member.OwnerPlayerUid == ownerPlayerUid && member.LocalClaimIndices.Contains(localIndex)) - return link; + if (!byPlayer.TryGetValue(ownerPlayerUid, out Dictionary>? perGroup)) + return null; + + foreach (KeyValuePair> entry in perGroup) + if (entry.Value.Contains(localIndex)) + return Get(entry.Key); return null; } + public void AddEntry(string ownerPlayerUid, int groupId, int localClaimIndex) + { + GetOrCreateIndices(ownerPlayerUid, groupId).Add(localClaimIndex); + Save(); + } + + public void RemoveEntry(string ownerPlayerUid, int groupId, int localClaimIndex) + { + if ( + byPlayer.TryGetValue(ownerPlayerUid, out Dictionary>? perGroup) + && perGroup.TryGetValue(groupId, out List? indices) + ) + { + indices.Remove(localClaimIndex); + if (indices.Count == 0) + perGroup.Remove(groupId); + + if (perGroup.Count == 0) + byPlayer.Remove(ownerPlayerUid); + } + + Save(); + } + + public bool HasAnyEntry(string ownerPlayerUid, int groupId) => + byPlayer.TryGetValue(ownerPlayerUid, out Dictionary>? perGroup) + && perGroup.TryGetValue(groupId, out List? indices) + && indices.Count > 0; + + public void RemoveAllForPlayerInGroup(string ownerPlayerUid, int groupId) + { + if (byPlayer.TryGetValue(ownerPlayerUid, out Dictionary>? perGroup)) + { + perGroup.Remove(groupId); + if (perGroup.Count == 0) + byPlayer.Remove(ownerPlayerUid); + } + + Save(); + } + + public IEnumerable MemberUidsForGroup(int groupId) => + byPlayer + .Where(kv => kv.Value.TryGetValue(groupId, out List? indices) && indices.Count > 0) + .Select(kv => kv.Key); + + public int MemberCountForGroup(int groupId) => MemberUidsForGroup(groupId).Count(); + + public IReadOnlyList ClaimsForPlayerInGroup(string ownerPlayerUid, int groupId) => + byPlayer.TryGetValue(ownerPlayerUid, out Dictionary>? perGroup) + && perGroup.TryGetValue(groupId, out List? indices) + ? indices + : System.Array.Empty(); + + private List GetOrCreateIndices(string playerUid, int groupId) + { + if (!byPlayer.TryGetValue(playerUid, out Dictionary>? perGroup)) + byPlayer[playerUid] = perGroup = new Dictionary>(); + + if (!perGroup.TryGetValue(groupId, out List? indices)) + perGroup[groupId] = indices = new List(); + + return indices; + } + public void Save() { - saveGame.StoreData(SaveKey, new ClaimLinkData { Links = new List(byGroupId.Values) }); + List entries = new(); + foreach (KeyValuePair>> playerEntry in byPlayer) + foreach (KeyValuePair> groupEntry in playerEntry.Value) + foreach (int index in groupEntry.Value) + entries.Add( + new ClaimLinkEntry + { + OwnerPlayerUid = playerEntry.Key, + GroupId = groupEntry.Key, + ClaimIndex = index, + } + ); + + saveGame.StoreData( + SaveKey, + new ClaimLinkData { Links = new List(byGroupId.Values), Entries = entries } + ); } }