Compare commits

...
5 Commits
5 changed files with 368 additions and 153 deletions
+11 -12
View File
@@ -1,24 +1,23 @@
using System.Collections.Generic;
using ProtoBuf; using ProtoBuf;
namespace ClaimLink; namespace ClaimLink;
[ProtoContract]
public class ClaimLinkMember
{
[ProtoMember(1)]
public string OwnerPlayerUid = "";
[ProtoMember(2)]
public List<int> LocalClaimIndices = new();
}
[ProtoContract] [ProtoContract]
public class ClaimLink public class ClaimLink
{ {
[ProtoMember(1)] [ProtoMember(1)]
public int GroupId; public int GroupId;
}
[ProtoContract]
public class ClaimLinkEntry
{
[ProtoMember(1)]
public string OwnerPlayerUid = "";
[ProtoMember(2)] [ProtoMember(2)]
public List<ClaimLinkMember> Members = new(); public int ClaimIndex;
[ProtoMember(3)]
public int GroupId;
} }
+101 -77
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.API.Server; using Vintagestory.API.Server;
namespace ClaimLink; namespace ClaimLink;
@@ -117,7 +118,7 @@ public static class ClaimLinkChatCommand
new( new(
new[] { "transferownership", "transfer", "to" }, new[] { "transferownership", "transfer", "to" },
"Transfer ownership of the claim link to another player.", "Transfer ownership of the claim link to another player.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, new ICommandArgumentParser[] { p.Word("groupname"), p.PlayerUids("playername") },
true, true,
TransferOwnership TransferOwnership
), ),
@@ -170,7 +171,7 @@ public static class ClaimLinkChatCommand
new( new(
new[] { "transferownership", "transfer", "to" }, new[] { "transferownership", "transfer", "to" },
"Transfer ownership of any claim link to another player.", "Transfer ownership of any claim link to another player.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, new ICommandArgumentParser[] { p.Word("groupname"), p.PlayerUids("playername") },
false, false,
AdminTransferOwnership AdminTransferOwnership
), ),
@@ -189,9 +190,7 @@ public static class ClaimLinkChatCommand
admin.EndSubCommand(); admin.EndSubCommand();
} }
private static readonly Dictionary<string, Func<TextCommandResult>> pendingActions = new(); internal static readonly Dictionary<string, Func<TextCommandResult>> PendingActions = new();
internal static void RemovePending(string playerUid) => pendingActions.Remove(playerUid);
private static TextCommandResult Stage( private static TextCommandResult Stage(
string playerUid, string playerUid,
@@ -199,7 +198,7 @@ public static class ClaimLinkChatCommand
Func<TextCommandResult> action Func<TextCommandResult> action
) )
{ {
pendingActions[playerUid] = action; PendingActions[playerUid] = action;
return TextCommandResult.Success( return TextCommandResult.Success(
$"{prompt} Use /claimlink confirm to proceed, or /claimlink cancel to cancel." $"{prompt} Use /claimlink confirm to proceed, or /claimlink cancel to cancel."
); );
@@ -278,10 +277,10 @@ public static class ClaimLinkChatCommand
{ {
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
if (!pendingActions.TryGetValue(playerUid, out Func<TextCommandResult>? action)) if (!PendingActions.TryGetValue(playerUid, out Func<TextCommandResult>? action))
return TextCommandResult.Error("You do not have a pending action."); return TextCommandResult.Error("You do not have a pending action.");
pendingActions.Remove(playerUid); PendingActions.Remove(playerUid);
return action(); return action();
} }
@@ -289,7 +288,7 @@ public static class ClaimLinkChatCommand
{ {
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
if (!pendingActions.Remove(playerUid)) if (!PendingActions.Remove(playerUid))
return TextCommandResult.Error("You do not have a pending action."); return TextCommandResult.Error("You do not have a pending action.");
return TextCommandResult.Success("Pending action cancelled."); return TextCommandResult.Success("Pending action cancelled.");
@@ -315,7 +314,7 @@ public static class ClaimLinkChatCommand
string playerUid = player.PlayerUID; string playerUid = player.PlayerUID;
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _, out _)) if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _))
return TextCommandResult.Error("You do not own that claim."); return TextCommandResult.Error("You do not own that claim.");
if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex)) if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex))
@@ -327,15 +326,7 @@ public static class ClaimLinkChatCommand
$"{claimDesc} will be linked into '{groupName}'.", $"{claimDesc} will be linked into '{groupName}'.",
() => () =>
{ {
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == playerUid); ClaimLinkModSystem.Registry.AddEntry(playerUid, link.GroupId, claimIndex);
if (member == null)
{
member = new ClaimLinkMember { OwnerPlayerUid = playerUid };
link.Members.Add(member);
}
member.LocalClaimIndices.Add(claimIndex);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'."); return TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.");
} }
@@ -361,12 +352,7 @@ public static class ClaimLinkChatCommand
$"{claimDesc} will be unlinked from '{groupName}'.", $"{claimDesc} will be unlinked from '{groupName}'.",
() => () =>
{ {
ClaimLinkMember member = link.Members.Find(m => m.OwnerPlayerUid == playerUid)!; ClaimLinkModSystem.Registry.RemoveEntry(playerUid, link.GroupId, claimIndex);
member.LocalClaimIndices.Remove(claimIndex);
if (member.LocalClaimIndices.Count == 0)
link.Members.Remove(member);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success($"Unlinked {claimDesc} from '{groupName}'."); return TextCommandResult.Success($"Unlinked {claimDesc} from '{groupName}'.");
} }
); );
@@ -381,7 +367,7 @@ public static class ClaimLinkChatCommand
if (err != null) if (err != null)
return err; return err;
err = TryResolveClaimLink(group, out ClaimLink link); err = TryResolveClaimLink(group, out _);
if (err != null) if (err != null)
return err; return err;
@@ -392,8 +378,7 @@ public static class ClaimLinkChatCommand
string targetUid = target.PlayerUID; string targetUid = target.PlayerUID;
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == targetUid); if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid))
if (member == null)
return TextCommandResult.Error( return TextCommandResult.Error(
$"{target.PlayerName} has no claims linked in '{groupName}'." $"{target.PlayerName} has no claims linked in '{groupName}'."
); );
@@ -403,9 +388,8 @@ public static class ClaimLinkChatCommand
$"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.", $"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.",
() => () =>
{ {
link.Members.Remove(member); ClaimLinkModSystem.Registry.RemoveAllForPlayerInGroup(targetUid, group.Uid);
RemovePending(targetUid); ClaimLinkCommandListener.PendingLoads.Remove(targetUid);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success( return TextCommandResult.Success(
$"Unlinked all claims of {target.PlayerName} from '{groupName}'." $"Unlinked all claims of {target.PlayerName} from '{groupName}'."
@@ -423,7 +407,7 @@ public static class ClaimLinkChatCommand
if (err != null) if (err != null)
return err; return err;
err = TryResolveClaimLink(group, out ClaimLink link); err = TryResolveClaimLink(group, out _);
if (err != null) if (err != null)
return err; return err;
@@ -437,8 +421,8 @@ public static class ClaimLinkChatCommand
$"'{groupName}' will be deleted as a claim link.", $"'{groupName}' will be deleted as a claim link.",
() => () =>
{ {
foreach (ClaimLinkMember member in link.Members) foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
RemovePending(member.OwnerPlayerUid); PendingActions.Remove(uid);
ClaimLinkModSystem.Registry.Remove(groupId); ClaimLinkModSystem.Registry.Remove(groupId);
return TextCommandResult.Success($"'{groupName}' is no longer a claim link."); return TextCommandResult.Success($"'{groupName}' is no longer a claim link.");
@@ -449,15 +433,21 @@ public static class ClaimLinkChatCommand
public static TextCommandResult TransferOwnership(TextCommandCallingArgs args) public static TextCommandResult TransferOwnership(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; string groupName = (string)args[0];
IPlayer target = (IPlayer)args[1];
IPlayer player = args.Caller.Player; IPlayer player = args.Caller.Player;
string playerUid = player.PlayerUID; string playerUid = player.PlayerUID;
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); TextCommandResult? err = TryResolveTargetPlayer(
(PlayerUidName[])args[1],
out IPlayer target
);
if (err != null) if (err != null)
return err; return err;
err = TryResolveClaimLink(group, out _); err = TryResolveGroup(groupName, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group, out ClaimLink link);
if (err != null) if (err != null)
return err; return err;
@@ -476,10 +466,28 @@ public static class ClaimLinkChatCommand
return Stage( return Stage(
playerUid, playerUid,
$"Ownership of '{groupName}' will be transferred to {target.PlayerName}.", $"Ownership of '{groupName}' will be transferred to {target.PlayerName}.",
() => ExecuteTransferOwnership(group, target) () => ExecuteTransferOwnership(group, link, target)
); );
} }
private static TextCommandResult? TryResolveTargetPlayer(
PlayerUidName[] matches,
out IPlayer target
)
{
target = null!;
if (matches.Length != 1)
return TextCommandResult.Error("Name a single player.");
IPlayer? resolved = ClaimLinkModSystem.World.PlayerByUid(matches[0].Uid);
if (resolved == null)
return TextCommandResult.Error($"No such player '{matches[0].Name}'.");
target = resolved;
return null;
}
private static TextCommandResult? RequireNotAlreadyOwner(PlayerGroup group, IPlayer target) private static TextCommandResult? RequireNotAlreadyOwner(PlayerGroup group, IPlayer target)
{ {
return target.PlayerUID == group.OwnerUID return target.PlayerUID == group.OwnerUID
@@ -487,27 +495,41 @@ public static class ClaimLinkChatCommand
: null; : null;
} }
private static TextCommandResult ExecuteTransferOwnership(PlayerGroup group, IPlayer target) private static TextCommandResult ExecuteTransferOwnership(
PlayerGroup group,
ClaimLink link,
IPlayer target
)
{ {
string oldOwnerUid = group.OwnerUID; string oldOwnerUid = group.OwnerUID;
string targetUid = target.PlayerUID;
IPlayer? oldOwner = ClaimLinkModSystem.World.PlayerByUid(oldOwnerUid); IPlayer? oldOwner = ClaimLinkModSystem.World.PlayerByUid(oldOwnerUid);
EnumPlayerGroupMemberShip? oldOwnerLevel = oldOwner?.GetGroup(group.Uid)?.Level; PlayerGroupMembership? oldOwnerMembership = oldOwner?.GetGroup(group.Uid);
EnumPlayerGroupMemberShip targetLevel = target.GetGroup(group.Uid)!.Level; PlayerGroupMembership targetMembership = target.GetGroup(group.Uid)!;
// TODO remove debug logging once transfer ownership persistence is confirmed tested group.OwnerUID = target.PlayerUID;
ClaimLinkModSystem.Logger.Notification( targetMembership.Level = EnumPlayerGroupMemberShip.Owner;
$"claimlink: transferownership '{group.Name}' (uid {group.Uid}) BEFORE OwnerUID={oldOwnerUid} "
+ $"(old owner {oldOwner?.PlayerName ?? oldOwnerUid} level={oldOwnerLevel}, new owner {target.PlayerName}/{targetUid} level={targetLevel})"
);
group.OwnerUID = targetUid; if (oldOwnerMembership != null)
oldOwnerMembership.Level = EnumPlayerGroupMemberShip.Op;
ClaimLinkModSystem.Logger.Notification( string notice = $"'{group.Name}' is now owned by {target.PlayerName}.";
$"claimlink: transferownership '{group.Name}' (uid {group.Uid}) AFTER OwnerUID={group.OwnerUID}"
);
return TextCommandResult.Success($"'{group.Name}' is now owned by {target.PlayerName}."); HashSet<string> notifyUids = ClaimLinkModSystem
.Registry.MemberUidsForGroup(link.GroupId)
.ToHashSet();
notifyUids.Add(oldOwnerUid);
notifyUids.Add(target.PlayerUID);
foreach (string uid in notifyUids)
NotifyIfOnline(ClaimLinkModSystem.World.PlayerByUid(uid), notice);
return TextCommandResult.Success(notice);
}
private static void NotifyIfOnline(IPlayer? player, string message)
{
if (player is IServerPlayer sp && sp.ConnectionState == EnumClientState.Playing)
sp.SendMessage(GlobalConstants.GeneralChatGroup, message, EnumChatType.Notification);
} }
public static TextCommandResult Info(TextCommandCallingArgs args) public static TextCommandResult Info(TextCommandCallingArgs args)
@@ -528,7 +550,9 @@ public static class ClaimLinkChatCommand
public static TextCommandResult List(TextCommandCallingArgs args) public static TextCommandResult List(TextCommandCallingArgs args)
{ {
List<ClaimLink> links = ClaimLinkModSystem List<ClaimLink> links = ClaimLinkModSystem
.Registry.All.OrderByDescending(l => l.Members.Count) .Registry.All.OrderByDescending(l =>
ClaimLinkModSystem.Registry.MemberCountForGroup(l.GroupId)
)
.ToList(); .ToList();
if (links.Count == 0) if (links.Count == 0)
@@ -540,45 +564,39 @@ public static class ClaimLinkChatCommand
foreach (ClaimLink link in links) foreach (ClaimLink link in links)
{ {
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[link.GroupId].Name; string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[link.GroupId].Name;
sb.AppendLine( int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(link.GroupId);
$" {groupName}: {link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}" sb.AppendLine($" {groupName}: {memberCount} member{(memberCount == 1 ? "" : "s")}");
);
} }
return TextCommandResult.Success(sb.ToString()); return TextCommandResult.Success(sb.ToString());
} }
private static string DescribeClaim(string ownerPlayerUid, int localIndex) private static string DescribeClaim(string ownerPlayerUid, int claimIndex)
{ {
if ( if (
!ClaimLinkModSystem.TryResolveOwnedClaim( !ClaimLinkModSystem.TryResolveOwnedClaim(ownerPlayerUid, claimIndex, out LandClaim? claim)
ownerPlayerUid,
localIndex,
out _,
out LandClaim? claim
)
|| claim == null || claim == null
) )
return $"claim {localIndex}"; return $"claim {claimIndex}";
return string.IsNullOrEmpty(claim.Description) ? $"claim {localIndex}" : claim.Description; return string.IsNullOrEmpty(claim.Description) ? $"claim {claimIndex}" : claim.Description;
} }
private static string FormatInfo(string groupName, ClaimLink link) private static string FormatInfo(string groupName, ClaimLink link)
{ {
int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(link.GroupId);
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine( sb.AppendLine(
$"Claim link '{groupName}' ({link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}):" $"Claim link '{groupName}' ({memberCount} member{(memberCount == 1 ? "" : "s")}):"
); );
foreach (ClaimLinkMember member in link.Members) foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(link.GroupId))
{ {
string name = string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
ClaimLinkModSystem.World.PlayerByUid(member.OwnerPlayerUid)?.PlayerName IEnumerable<string> claims = ClaimLinkModSystem
?? member.OwnerPlayerUid; .Registry.ClaimsForPlayerInGroup(uid, link.GroupId)
IEnumerable<string> claims = member.LocalClaimIndices.Select(i => .Select(i => DescribeClaim(uid, i));
DescribeClaim(member.OwnerPlayerUid, i)
);
sb.AppendLine($" {name}: [{string.Join(", ", claims)}]"); sb.AppendLine($" {name}: [{string.Join(", ", claims)}]");
} }
@@ -597,13 +615,19 @@ public static class ClaimLinkChatCommand
public static TextCommandResult AdminTransferOwnership(TextCommandCallingArgs args) public static TextCommandResult AdminTransferOwnership(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; string groupName = (string)args[0];
IPlayer target = (IPlayer)args[1];
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); TextCommandResult? err = TryResolveTargetPlayer(
(PlayerUidName[])args[1],
out IPlayer target
);
if (err != null) if (err != null)
return err; return err;
err = TryResolveClaimLink(group, out _); err = TryResolveGroup(groupName, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group, out ClaimLink link);
if (err != null) if (err != null)
return err; return err;
@@ -615,7 +639,7 @@ public static class ClaimLinkChatCommand
if (err != null) if (err != null)
return err; return err;
return ExecuteTransferOwnership(group, target); return ExecuteTransferOwnership(group, link, target);
} }
public static TextCommandResult AdminInfo(TextCommandCallingArgs args) => public static TextCommandResult AdminInfo(TextCommandCallingArgs args) =>
+33 -20
View File
@@ -29,13 +29,6 @@ public class ClaimLinkModSystem : ModSystem
ClaimLinkChatCommand.Register(api); ClaimLinkChatCommand.Register(api);
api.Event.PlayerDisconnect += ClaimLinkCommandListener.OnPlayerDisconnect; 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() public override void Dispose()
@@ -44,28 +37,48 @@ public class ClaimLinkModSystem : ModSystem
CommandHookModSystem.Unregister(cmdListener); CommandHookModSystem.Unregister(cmdListener);
} }
internal static bool TryResolveOwnedClaim(string ownerPlayerUid, int localIndex, out int globalIndex, out LandClaim? claim) private static IEnumerable<(int claimIndex, LandClaim claim)> EnumerateOwnedClaims(
string ownerPlayerUid
)
{ {
globalIndex = -1; int index = 0;
claim = null; foreach (LandClaim c in LandClaimAPI.All)
List<LandClaim> claims = LandClaimAPI.All;
int count = 0;
for (int i = 0; i < claims.Count; ++i)
{ {
if (claims[i].OwnedByPlayerUid != ownerPlayerUid) if (c.OwnedByPlayerUid != ownerPlayerUid)
continue; continue;
if (count == localIndex) yield return (index, c);
index++;
}
}
internal static bool TryResolveOwnedClaim(string ownerPlayerUid, int claimIndex, out LandClaim? claim)
{
foreach ((int index, LandClaim c) in EnumerateOwnedClaims(ownerPlayerUid))
{
if (index == claimIndex)
{ {
globalIndex = i; claim = c;
claim = claims[i];
return true; return true;
} }
count++;
} }
claim = null;
return false;
}
internal static bool TryResolveClaimIndex(string ownerPlayerUid, LandClaim target, out int claimIndex)
{
foreach ((int index, LandClaim c) in EnumerateOwnedClaims(ownerPlayerUid))
{
if (ReferenceEquals(c, target))
{
claimIndex = index;
return true;
}
}
claimIndex = -1;
return false; return false;
} }
} }
+114 -8
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using ProtoBuf; using ProtoBuf;
using Vintagestory.API.Server; using Vintagestory.API.Server;
@@ -9,6 +10,9 @@ public class ClaimLinkData
{ {
[ProtoMember(1)] [ProtoMember(1)]
public List<ClaimLink> Links = new(); public List<ClaimLink> Links = new();
[ProtoMember(2)]
public List<ClaimLinkEntry> Entries = new();
} }
public class ClaimLinkRegistry public class ClaimLinkRegistry
@@ -18,6 +22,8 @@ public class ClaimLinkRegistry
private readonly ISaveGame saveGame; private readonly ISaveGame saveGame;
private readonly Dictionary<int, ClaimLink> byGroupId = new(); private readonly Dictionary<int, ClaimLink> byGroupId = new();
private readonly Dictionary<string, Dictionary<int, List<int>>> byPlayer = new();
public ClaimLinkRegistry(ISaveGame saveGame) public ClaimLinkRegistry(ISaveGame saveGame)
{ {
this.saveGame = saveGame; this.saveGame = saveGame;
@@ -28,9 +34,13 @@ public class ClaimLinkRegistry
foreach (ClaimLink link in data.Links) foreach (ClaimLink link in data.Links)
byGroupId[link.GroupId] = link; 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<ClaimLink> All => byGroupId.Values; public IReadOnlyCollection<ClaimLink> All => byGroupId.Values;
@@ -43,23 +53,119 @@ public class ClaimLinkRegistry
public void Remove(int groupId) public void Remove(int groupId)
{ {
byGroupId.Remove(groupId); byGroupId.Remove(groupId);
List<string> emptyPlayerUids = new();
foreach (KeyValuePair<string, Dictionary<int, List<int>>> 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(); Save();
} }
public bool IsClaimLinked(string ownerPlayerUid, int localIndex) => FindLinkContaining(ownerPlayerUid, localIndex) != null; public bool IsClaimLinked(string ownerPlayerUid, int claimIndex) =>
FindLinkContaining(ownerPlayerUid, claimIndex) != null;
public ClaimLink? FindLinkContaining(string ownerPlayerUid, int localIndex) public ClaimLink? FindLinkContaining(string ownerPlayerUid, int claimIndex)
{ {
foreach (ClaimLink link in byGroupId.Values) if (!byPlayer.TryGetValue(ownerPlayerUid, out Dictionary<int, List<int>>? perGroup))
foreach (ClaimLinkMember member in link.Members) return null;
if (member.OwnerPlayerUid == ownerPlayerUid && member.LocalClaimIndices.Contains(localIndex))
return link; foreach (KeyValuePair<int, List<int>> entry in perGroup)
if (entry.Value.Contains(claimIndex))
return Get(entry.Key);
return null; return null;
} }
public void AddEntry(string ownerPlayerUid, int groupId, int claimIndex)
{
GetOrCreateIndices(ownerPlayerUid, groupId).Add(claimIndex);
Save();
}
public void RemoveEntry(string ownerPlayerUid, int groupId, int claimIndex)
{
if (
byPlayer.TryGetValue(ownerPlayerUid, out Dictionary<int, List<int>>? perGroup)
&& perGroup.TryGetValue(groupId, out List<int>? indices)
)
{
indices.Remove(claimIndex);
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<int, List<int>>? perGroup)
&& perGroup.TryGetValue(groupId, out List<int>? indices)
&& indices.Count > 0;
public void RemoveAllForPlayerInGroup(string ownerPlayerUid, int groupId)
{
if (byPlayer.TryGetValue(ownerPlayerUid, out Dictionary<int, List<int>>? perGroup))
{
perGroup.Remove(groupId);
if (perGroup.Count == 0)
byPlayer.Remove(ownerPlayerUid);
}
Save();
}
public IEnumerable<string> MemberUidsForGroup(int groupId) =>
byPlayer
.Where(kv => kv.Value.TryGetValue(groupId, out List<int>? indices) && indices.Count > 0)
.Select(kv => kv.Key);
public int MemberCountForGroup(int groupId) => MemberUidsForGroup(groupId).Count();
public IReadOnlyList<int> ClaimsForPlayerInGroup(string ownerPlayerUid, int groupId) =>
byPlayer.TryGetValue(ownerPlayerUid, out Dictionary<int, List<int>>? perGroup)
&& perGroup.TryGetValue(groupId, out List<int>? indices)
? indices
: System.Array.Empty<int>();
private List<int> GetOrCreateIndices(string playerUid, int groupId)
{
if (!byPlayer.TryGetValue(playerUid, out Dictionary<int, List<int>>? perGroup))
byPlayer[playerUid] = perGroup = new Dictionary<int, List<int>>();
if (!perGroup.TryGetValue(groupId, out List<int>? indices))
perGroup[groupId] = indices = new List<int>();
return indices;
}
public void Save() public void Save()
{ {
saveGame.StoreData(SaveKey, new ClaimLinkData { Links = new List<ClaimLink>(byGroupId.Values) }); List<ClaimLinkEntry> entries = new();
foreach (KeyValuePair<string, Dictionary<int, List<int>>> playerEntry in byPlayer)
foreach (KeyValuePair<int, List<int>> 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<ClaimLink>(byGroupId.Values), Entries = entries }
);
} }
} }
+109 -36
View File
@@ -1,6 +1,6 @@
using Vintagestory.API.Common;
using System.Collections.Generic; using System.Collections.Generic;
using CommandHook; using CommandHook;
using Vintagestory.API.Common;
using Vintagestory.API.Server; using Vintagestory.API.Server;
namespace ClaimLink; namespace ClaimLink;
@@ -13,9 +13,9 @@ public class ClaimLinkCommandListener : ICommandHookListener
public CommandRegistration Registration => new(Before, After); public CommandRegistration Registration => new(Before, After);
private static Dictionary<string, int> pendingLoads = new Dictionary<string, int>(); internal static Dictionary<string, int> PendingLoads = new Dictionary<string, int>();
private delegate void SubHandler(Caller caller, CmdArgs args); private delegate void SubHandler(Caller caller, CmdArgs args, TextCommandResult? result);
private static readonly Dictionary<string, SubHandler> topLevel = new() private static readonly Dictionary<string, SubHandler> topLevel = new()
{ {
@@ -47,7 +47,7 @@ public class ClaimLinkCommandListener : ICommandHookListener
["download"] = ClaimDownload, ["download"] = ClaimDownload,
}; };
private TextCommandResult? Before(TextCommandCallingArgs args) private static void Dispatch(TextCommandCallingArgs args, TextCommandResult? result)
{ {
CmdArgs rawArgs = args.RawArgs.Clone(); CmdArgs rawArgs = args.RawArgs.Clone();
@@ -58,79 +58,152 @@ public class ClaimLinkCommandListener : ICommandHookListener
if (topLevel.TryGetValue(word, out SubHandler? handler)) if (topLevel.TryGetValue(word, out SubHandler? handler))
{ {
rawArgs.PopWord(); rawArgs.PopWord();
handler(args.Caller, rawArgs); handler(args.Caller, rawArgs, result);
} }
else else
{ {
rawArgs.PopWord(); rawArgs.PopWord();
} }
} }
}
private TextCommandResult? Before(TextCommandCallingArgs args)
{
Dispatch(args, null);
return null; return null;
} }
private void After(TextCommandCallingArgs args, TextCommandResult result) private void After(TextCommandCallingArgs args, TextCommandResult result)
{ {
Dispatch(args, result);
} }
private static void Claim(Caller caller, CmdArgs args) private static void Claim(Caller caller, CmdArgs args, TextCommandResult? result)
{ {
string word = args.PeekWord(); string word = args.PeekWord();
if (claimSub.TryGetValue(word, out SubHandler? handler)) if (claimSub.TryGetValue(word, out SubHandler? handler))
{ {
args.PopWord(); args.PopWord();
handler(caller, args); handler(caller, args, result);
} }
} }
private static void ClaimLoad(Caller caller, CmdArgs args) private static void ClaimLoad(Caller caller, CmdArgs args, TextCommandResult? result)
{ {
if (caller.Player == null) if (result == null || result.Status != EnumCommandStatus.Success || caller.Player == null)
return; return;
int? claimIndex = args.PopInt(); int? claimIndex = args.PopInt();
if (claimIndex == null || claimIndex < 0 || claimIndex > 999) if (claimIndex == null || claimIndex < 0 || claimIndex > 999)
return; return;
if (!ClaimLinkModSystem.TryResolveOwnedClaim(caller.Player.PlayerUID, (int)claimIndex, out int globalIndex, out _)) ClaimLinkModSystem.Logger.Notification($"Claim Index Loaded = {claimIndex}");
return; PendingLoads[caller.Player.PlayerUID] = (int)claimIndex;
pendingLoads[caller.Player.PlayerUID] = globalIndex;
} }
private static void ClaimCancel(Caller caller, CmdArgs args) private static void ClaimCancel(Caller caller, CmdArgs args, TextCommandResult? result)
{
if (result != null || caller.Player == null)
return;
PendingLoads.Remove(caller.Player.PlayerUID);
}
private static void Free(Caller caller, CmdArgs args, TextCommandResult? result)
{
if (result == null || result.Status != EnumCommandStatus.Success || caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
int? claimIndex = args.PopInt();
if (claimIndex == null)
return;
if (args.PopWord() != "confirm")
return;
ClaimLink? link = ClaimLinkModSystem.Registry.FindLinkContaining(uid, (int)claimIndex);
ClaimLinkModSystem.Logger.Notification(
link != null
? $"claimlink: freed claim {claimIndex} for {uid} was linked to group {link.GroupId}"
: $"claimlink: freed claim {claimIndex} for {uid} was not linked"
);
}
private static void Info(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void List(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void AdminFree(Caller caller, CmdArgs args, TextCommandResult? result)
{ {
if (caller.Player == null) if (caller.Player == null)
return; return;
pendingLoads.Remove(caller.Player.PlayerUID); LandClaim[] claims = ClaimLinkModSystem.LandClaimAPI.Get(
caller.Player.Entity.Pos.AsBlockPos
);
string phase = result == null ? "before" : $"after (status={result.Status})";
ClaimLinkModSystem.Logger.Notification(
$"claimlink: adminfree {phase}: {claims.Length} claim(s) at position"
);
foreach (LandClaim c in claims)
ClaimLinkModSystem.Logger.Notification($"claimlink: owned by {c.OwnedByPlayerUid}");
} }
private static void Free(Caller caller, CmdArgs args) { } private static void ClaimNew(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void Info(Caller caller, CmdArgs args) { }
private static void List(Caller caller, CmdArgs args) { }
private static void AdminFree(Caller caller, CmdArgs args) { }
private static void ClaimNew(Caller caller, CmdArgs args) { } private static void ClaimGrant(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimGrant(Caller caller, CmdArgs args) { }
private static void ClaimRevoke(Caller caller, CmdArgs args) { } private static void ClaimRevoke(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimGrantGroup(Caller caller, CmdArgs args) { }
private static void ClaimRevokeGroup(Caller caller, CmdArgs args) { } private static void ClaimGrantGroup(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimStart(Caller caller, CmdArgs args) { }
private static void ClaimEnd(Caller caller, CmdArgs args) { } private static void ClaimRevokeGroup(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimGrow(Caller caller, CmdArgs args) { }
private static void ClaimShrink(Caller caller, CmdArgs args) { } private static void ClaimStart(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimAdd(Caller caller, CmdArgs args) { }
private static void ClaimAllowUseEveryone(Caller caller, CmdArgs args) { } private static void ClaimEnd(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimPLevel(Caller caller, CmdArgs args) { }
private static void ClaimFullHeight(Caller caller, CmdArgs args) { } private static void ClaimGrow(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimSave(Caller caller, CmdArgs args) { }
private static void ClaimDownload(Caller caller, CmdArgs args) { } private static void ClaimShrink(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimAdd(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimAllowUseEveryone(
Caller caller,
CmdArgs args,
TextCommandResult? result
) { }
private static void ClaimPLevel(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimFullHeight(Caller caller, CmdArgs args, TextCommandResult? result) { }
private static void ClaimSave(Caller caller, CmdArgs args, TextCommandResult? result)
{
if (result == null || result.Status != EnumCommandStatus.Success || caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
int? claimIndex = args.PopInt();
if (PendingLoads.TryGetValue(uid, out int pendingIndex) && claimIndex != pendingIndex)
ClaimLinkModSystem.Logger.Error(
$"claimlink: claim save index mismatch for {uid}: pending={pendingIndex} saved={claimIndex}"
);
PendingLoads.Remove(uid);
}
private static void ClaimDownload(Caller caller, CmdArgs args, TextCommandResult? result) { }
internal static void OnPlayerDisconnect(IServerPlayer player) internal static void OnPlayerDisconnect(IServerPlayer player)
{ {
pendingLoads.Remove(player.PlayerUID); PendingLoads.Remove(player.PlayerUID);
ClaimLinkChatCommand.RemovePending(player.PlayerUID); ClaimLinkChatCommand.PendingActions.Remove(player.PlayerUID);
} }
} }