The server now pushes buffer and permission state to clients continuously: a full sync when a player joins, and an incremental delta on every registry mutation, mirroring vanilla's own land claim sync model. ClaimLinkBufferIndex is now instantiable, so the client and server each get their own region-indexed lookup built from the same algorithm.
346 lines
11 KiB
C#
346 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using Vintagestory.API.Common;
|
|
using Vintagestory.API.Server;
|
|
|
|
namespace ClaimLink;
|
|
|
|
public class ClaimLinkRegistry
|
|
{
|
|
private const string SaveKey = "claimlink:claimlinkdata";
|
|
|
|
private static readonly Dictionary<string, EnumBlockAccessFlags> EmptyPlayerFlags = new();
|
|
private static readonly Dictionary<int, EnumBlockAccessFlags> EmptyGroupFlags = new();
|
|
|
|
private readonly ISaveGame saveGame;
|
|
|
|
private readonly List<int> claimLinks = new();
|
|
private readonly Dictionary<string, Dictionary<int, List<int>>> entries = new();
|
|
private readonly Dictionary<int, Dictionary<string, EnumBlockAccessFlags>> permittedPlayerUids =
|
|
new();
|
|
private readonly Dictionary<
|
|
int,
|
|
Dictionary<int, EnumBlockAccessFlags>
|
|
> permittedPlayerGroupIds = new();
|
|
|
|
public ClaimLinkRegistry(ISaveGame saveGame)
|
|
{
|
|
this.saveGame = saveGame;
|
|
|
|
ClaimLinkSaveData? data = saveGame.GetData<ClaimLinkSaveData?>(SaveKey, null);
|
|
if (data == null)
|
|
return;
|
|
|
|
claimLinks.AddRange(data.Links);
|
|
|
|
foreach (var entry in data.Entries)
|
|
{
|
|
if (!entries.ContainsKey(entry.OwnerPlayerUid))
|
|
entries[entry.OwnerPlayerUid] = new Dictionary<int, List<int>>();
|
|
|
|
entries[entry.OwnerPlayerUid][entry.GroupId] = new(entry.ClaimIndicies);
|
|
}
|
|
|
|
foreach (var permission in data.Permissions)
|
|
{
|
|
if (permission.PermittedPlayerUids.Count > 0)
|
|
permittedPlayerUids[permission.GroupId] = new(permission.PermittedPlayerUids);
|
|
if (permission.PermittedPlayerGroupIds.Count > 0)
|
|
permittedPlayerGroupIds[permission.GroupId] = new(
|
|
permission.PermittedPlayerGroupIds
|
|
);
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<int> All => claimLinks;
|
|
|
|
public bool Exists(int groupId) => claimLinks.Contains(groupId);
|
|
|
|
public void Add(int groupId)
|
|
{
|
|
claimLinks.Add(groupId);
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public void Remove(int groupId)
|
|
{
|
|
claimLinks.Remove(groupId);
|
|
foreach (var groups in entries.Values)
|
|
groups.Remove(groupId);
|
|
permittedPlayerUids.Remove(groupId);
|
|
permittedPlayerGroupIds.Remove(groupId);
|
|
Prune();
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public void GrantPlayer(int groupId, string uid, EnumBlockAccessFlags flags)
|
|
{
|
|
if (!permittedPlayerUids.TryGetValue(groupId, out var uids))
|
|
permittedPlayerUids[groupId] = uids = new();
|
|
uids[uid] = flags;
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public bool RevokePlayer(int groupId, string uid)
|
|
{
|
|
bool removed = permittedPlayerUids.TryGetValue(groupId, out var uids) && uids.Remove(uid);
|
|
if (!removed)
|
|
return false;
|
|
if (uids!.Count == 0)
|
|
permittedPlayerUids.Remove(groupId);
|
|
Save(new[] { groupId });
|
|
return true;
|
|
}
|
|
|
|
public void GrantGroup(int groupId, int targetGroupId, EnumBlockAccessFlags flags)
|
|
{
|
|
if (!permittedPlayerGroupIds.TryGetValue(groupId, out var groups))
|
|
permittedPlayerGroupIds[groupId] = groups = new();
|
|
groups[targetGroupId] = flags;
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public bool RevokeGroup(int groupId, int targetGroupId)
|
|
{
|
|
bool removed =
|
|
permittedPlayerGroupIds.TryGetValue(groupId, out var groups)
|
|
&& groups.Remove(targetGroupId);
|
|
if (!removed)
|
|
return false;
|
|
if (groups!.Count == 0)
|
|
permittedPlayerGroupIds.Remove(groupId);
|
|
Save(new[] { groupId });
|
|
return true;
|
|
}
|
|
|
|
public bool HasPermission(int groupId, IPlayer player, EnumBlockAccessFlags accessFlag)
|
|
{
|
|
if (
|
|
permittedPlayerUids.TryGetValue(groupId, out var uids)
|
|
&& uids.TryGetValue(player.PlayerUID, out EnumBlockAccessFlags flags)
|
|
&& (flags & accessFlag) != 0
|
|
)
|
|
return true;
|
|
|
|
if (permittedPlayerGroupIds.TryGetValue(groupId, out var groups))
|
|
foreach (PlayerGroupMembership membership in player.Groups)
|
|
if (groups.TryGetValue(membership.GroupUid, out flags) && (flags & accessFlag) != 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public IReadOnlyDictionary<string, EnumBlockAccessFlags> PermittedPlayersForGroup(
|
|
int groupId
|
|
) => permittedPlayerUids.TryGetValue(groupId, out var uids) ? uids : EmptyPlayerFlags;
|
|
|
|
public IReadOnlyDictionary<int, EnumBlockAccessFlags> PermittedGroupsForGroup(int groupId) =>
|
|
permittedPlayerGroupIds.TryGetValue(groupId, out var groups) ? groups : EmptyGroupFlags;
|
|
|
|
public void RemoveGroupFromAllPermissions(int targetGroupId)
|
|
{
|
|
List<int>? changedGroupIds = null;
|
|
foreach (var (groupId, groups) in permittedPlayerGroupIds)
|
|
if (groups.Remove(targetGroupId))
|
|
(changedGroupIds ??= new()).Add(groupId);
|
|
|
|
if (changedGroupIds == null)
|
|
return;
|
|
|
|
List<int>? emptyGroupIds = null;
|
|
foreach (var (groupId, groups) in permittedPlayerGroupIds)
|
|
if (groups.Count == 0)
|
|
(emptyGroupIds ??= new()).Add(groupId);
|
|
if (emptyGroupIds != null)
|
|
foreach (int groupId in emptyGroupIds)
|
|
permittedPlayerGroupIds.Remove(groupId);
|
|
|
|
Save(changedGroupIds);
|
|
}
|
|
|
|
public bool IsClaimLinked(string uid, int claimIndex) =>
|
|
FindGroupContaining(uid, claimIndex) != null;
|
|
|
|
public int? FindGroupContaining(string uid, int claimIndex)
|
|
{
|
|
if (!entries.TryGetValue(uid, out var groups))
|
|
return null;
|
|
foreach (var (groupId, claims) in groups)
|
|
if (claims.Contains(claimIndex))
|
|
return groupId;
|
|
return null;
|
|
}
|
|
|
|
public bool AddEntry(string uid, int groupId, int claimIndex)
|
|
{
|
|
if (CommandListener.PendingLoads.ContainsKey(uid))
|
|
return false;
|
|
if (!Exists(groupId))
|
|
return false;
|
|
if (!entries.TryGetValue(uid, out var groups))
|
|
entries[uid] = groups = new();
|
|
if (!groups.TryGetValue(groupId, out var claims))
|
|
groups[groupId] = claims = new();
|
|
claims.Add(claimIndex);
|
|
Save(new[] { groupId });
|
|
return true;
|
|
}
|
|
|
|
public void RemoveEntry(string uid, int groupId, int claimIndex)
|
|
{
|
|
if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims))
|
|
claims.Remove(claimIndex);
|
|
Prune();
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public void ClaimRemoved(string uid, int removedIndex)
|
|
{
|
|
if (!entries.TryGetValue(uid, out var groups))
|
|
return;
|
|
|
|
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
|
|
|
foreach (var claims in groups.Values)
|
|
{
|
|
claims.Remove(removedIndex);
|
|
for (int i = 0; i < claims.Count; i++)
|
|
if (claims[i] > removedIndex)
|
|
claims[i]--;
|
|
}
|
|
|
|
Prune();
|
|
Save(affectedGroupIds);
|
|
}
|
|
|
|
public void ClaimSaved(string uid, int claimIndex)
|
|
{
|
|
if (!entries.TryGetValue(uid, out var groups))
|
|
return;
|
|
|
|
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
|
|
|
int count = 0;
|
|
foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All)
|
|
if (c.OwnedByPlayerUid == uid)
|
|
count++;
|
|
int newIndex = count - 1;
|
|
|
|
foreach (var claims in groups.Values)
|
|
{
|
|
int i = claims.IndexOf(claimIndex);
|
|
|
|
for (int j = 0; j < claims.Count; j++)
|
|
if (j != i && claims[j] > claimIndex)
|
|
claims[j]--;
|
|
|
|
if (i >= 0)
|
|
claims[i] = newIndex;
|
|
}
|
|
|
|
Save(affectedGroupIds);
|
|
}
|
|
|
|
public bool HasAnyEntry(string uid, int groupId) =>
|
|
entries.TryGetValue(uid, out var groups)
|
|
&& groups.TryGetValue(groupId, out var claims)
|
|
&& claims.Count > 0;
|
|
|
|
public void RemoveAllForPlayerInGroup(string uid, int groupId)
|
|
{
|
|
if (entries.TryGetValue(uid, out var groups))
|
|
groups.Remove(groupId);
|
|
Prune();
|
|
Save(new[] { groupId });
|
|
}
|
|
|
|
public void ClearAllForPlayer(string uid)
|
|
{
|
|
int[] affectedGroupIds = entries.TryGetValue(uid, out var groups)
|
|
? new List<int>(groups.Keys).ToArray()
|
|
: System.Array.Empty<int>();
|
|
|
|
entries.Remove(uid);
|
|
Save(affectedGroupIds);
|
|
}
|
|
|
|
public IEnumerable<string> MemberUidsForGroup(int groupId)
|
|
{
|
|
foreach (var (uid, groups) in entries)
|
|
if (groups.ContainsKey(groupId))
|
|
yield return uid;
|
|
}
|
|
|
|
public int MemberCountForGroup(int groupId)
|
|
{
|
|
int count = 0;
|
|
foreach (var uid in MemberUidsForGroup(groupId))
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
public IReadOnlyList<int> ClaimsForPlayerInGroup(string uid, int groupId) =>
|
|
entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims)
|
|
? claims
|
|
: System.Array.Empty<int>();
|
|
|
|
private void Prune()
|
|
{
|
|
List<string>? empty = null;
|
|
foreach (var (uid, groups) in entries)
|
|
if (groups.Count == 0)
|
|
(empty ??= new()).Add(uid);
|
|
if (empty != null)
|
|
foreach (var uid in empty)
|
|
entries.Remove(uid);
|
|
}
|
|
|
|
private void Save(IReadOnlyCollection<int> affectedGroupIds)
|
|
{
|
|
var entryList = new List<ClaimLinkEntrySaveData>();
|
|
foreach (var (uid, groups) in entries)
|
|
foreach (var (groupId, claims) in groups)
|
|
entryList.Add(
|
|
new ClaimLinkEntrySaveData
|
|
{
|
|
OwnerPlayerUid = uid,
|
|
GroupId = groupId,
|
|
ClaimIndicies = new(claims),
|
|
}
|
|
);
|
|
|
|
var permissionGroupIds = new HashSet<int>();
|
|
foreach (int groupId in permittedPlayerUids.Keys)
|
|
permissionGroupIds.Add(groupId);
|
|
foreach (int groupId in permittedPlayerGroupIds.Keys)
|
|
permissionGroupIds.Add(groupId);
|
|
|
|
var permissionList = new List<ClaimLinkPermissionSaveData>();
|
|
foreach (int groupId in permissionGroupIds)
|
|
{
|
|
permittedPlayerUids.TryGetValue(groupId, out var uids);
|
|
permittedPlayerGroupIds.TryGetValue(groupId, out var groups);
|
|
permissionList.Add(
|
|
new ClaimLinkPermissionSaveData
|
|
{
|
|
GroupId = groupId,
|
|
PermittedPlayerUids = uids != null ? new(uids) : new(),
|
|
PermittedPlayerGroupIds = groups != null ? new(groups) : new(),
|
|
}
|
|
);
|
|
}
|
|
|
|
saveGame.StoreData(
|
|
SaveKey,
|
|
new ClaimLinkSaveData
|
|
{
|
|
Links = claimLinks,
|
|
Entries = entryList,
|
|
Permissions = permissionList,
|
|
}
|
|
);
|
|
|
|
ClaimLinkModSystem.RebuildBufferIndex();
|
|
ClaimLinkVisualizerNetwork.BroadcastDelta(affectedGroupIds);
|
|
}
|
|
}
|