fix: track groupId per pending action for precise cleanup on delete/disband; split group hook into its own listener; self-heal orphaned registry entries in list

This commit is contained in:
2026-07-26 08:51:54 +02:00
parent c55787ce40
commit b7ca9fa53b
5 changed files with 123 additions and 68 deletions
+50 -13
View File
@@ -190,20 +190,38 @@ public static class ClaimLinkChatCommand
admin.EndSubCommand();
}
internal static readonly Dictionary<string, Func<TextCommandResult>> PendingActions = new();
internal static readonly Dictionary<
string,
(int GroupId, Func<TextCommandResult> Action)
> PendingActions = new();
private static TextCommandResult Stage(
string playerUid,
int groupId,
string prompt,
Func<TextCommandResult> action
)
{
PendingActions[playerUid] = action;
PendingActions[playerUid] = (groupId, action);
return TextCommandResult.Success(
$"{prompt} Use /claimlink confirm to proceed, or /claimlink cancel to cancel."
);
}
internal static void RemovePendingActionsForGroup(int groupId)
{
List<string>? toRemove = null;
foreach (var (uid, entry) in PendingActions)
if (entry.GroupId == groupId)
(toRemove ??= new()).Add(uid);
if (toRemove == null)
return;
foreach (string uid in toRemove)
PendingActions.Remove(uid);
}
private static TextCommandResult? TryResolveGroup(string groupName, out PlayerGroup group)
{
group = ClaimLinkModSystem.Groups.GetPlayerGroupByName(groupName)!;
@@ -263,6 +281,7 @@ public static class ClaimLinkChatCommand
int groupId = group.Uid;
return Stage(
playerUid,
groupId,
$"'{groupName}' will become a claim link.",
() =>
{
@@ -276,11 +295,11 @@ public static class ClaimLinkChatCommand
{
string playerUid = args.Caller.Player.PlayerUID;
if (!PendingActions.TryGetValue(playerUid, out Func<TextCommandResult>? action))
if (!PendingActions.TryGetValue(playerUid, out var pending))
return TextCommandResult.Error("You do not have a pending action.");
PendingActions.Remove(playerUid);
return action();
return pending.Action();
}
public static TextCommandResult Cancel(TextCommandCallingArgs args)
@@ -323,6 +342,7 @@ public static class ClaimLinkChatCommand
string claimDesc = DescribeClaim(playerUid, claimIndex);
return Stage(
playerUid,
groupId,
$"{claimDesc} will be linked into '{groupName}'.",
() =>
{
@@ -359,6 +379,7 @@ public static class ClaimLinkChatCommand
return Stage(
playerUid,
(int)groupId,
$"{claimDesc} will be unlinked from '{groupName}'.",
() =>
{
@@ -395,11 +416,12 @@ public static class ClaimLinkChatCommand
return Stage(
playerUid,
group.Uid,
$"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.",
() =>
{
ClaimLinkModSystem.Registry.RemoveAllForPlayerInGroup(targetUid, group.Uid);
ClaimLinkCommandListener.PendingLoads.Remove(targetUid);
CommandListener.PendingLoads.Remove(targetUid);
PendingActions.Remove(targetUid);
return TextCommandResult.Success(
@@ -429,12 +451,11 @@ public static class ClaimLinkChatCommand
int groupId = group.Uid;
return Stage(
playerUid,
groupId,
$"'{groupName}' will be deleted as a claim link.",
() =>
{
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
PendingActions.Remove(uid);
RemovePendingActionsForGroup(groupId);
ClaimLinkModSystem.Registry.Remove(groupId);
return TextCommandResult.Success($"'{groupName}' is no longer a claim link.");
}
@@ -476,6 +497,7 @@ public static class ClaimLinkChatCommand
return Stage(
playerUid,
group.Uid,
$"Ownership of '{groupName}' will be transferred to {target.PlayerName}.",
() => ExecuteTransferOwnership(group, target)
);
@@ -572,16 +594,31 @@ public static class ClaimLinkChatCommand
return TextCommandResult.Success("There are no claim links.");
StringBuilder sb = new();
sb.AppendLine($"Claim links ({groupIds.Count}):");
int shown = 0;
foreach (int groupId in groupIds)
{
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[groupId].Name;
int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(groupId);
sb.AppendLine($" {groupName}: {memberCount} member{(memberCount == 1 ? "" : "s")}");
if (
!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
groupId,
out PlayerGroup? group
)
)
{
RemovePendingActionsForGroup(groupId);
ClaimLinkModSystem.Registry.Remove(groupId);
continue;
}
return TextCommandResult.Success(sb.ToString());
int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(groupId);
sb.AppendLine($" {group.Name}: {memberCount} member{(memberCount == 1 ? "" : "s")}");
shown++;
}
if (shown == 0)
return TextCommandResult.Success("There are no claim links.");
return TextCommandResult.Success($"Claim links ({shown}):\n{sb}");
}
private static string DescribeClaim(string ownerPlayerUid, int claimIndex)
+9 -3
View File
@@ -13,7 +13,8 @@ public class ClaimLinkModSystem : ModSystem
internal static IGroupManager Groups = null!;
internal static IWorldAccessor World = null!;
internal static IPlayerDataManager PlayerData = null!;
internal ClaimLinkCommandListener? CmdListener;
internal CommandListener? CmdListener;
internal GroupCommandListener? GroupCmdListener;
public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server;
@@ -26,11 +27,14 @@ public class ClaimLinkModSystem : ModSystem
PlayerData = api.PlayerData;
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
CmdListener = new ClaimLinkCommandListener();
CmdListener = new CommandListener();
CommandHookModSystem.Register(CmdListener);
GroupCmdListener = new GroupCommandListener();
CommandHookModSystem.Register(GroupCmdListener);
ClaimLinkChatCommand.Register(api);
api.Event.PlayerDisconnect += ClaimLinkCommandListener.OnPlayerDisconnect;
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect;
api.Event.PlayerDisconnect += ClaimLinkChatCommand.OnPlayerDisconnect;
}
@@ -38,6 +42,8 @@ public class ClaimLinkModSystem : ModSystem
{
if (CmdListener != null)
CommandHookModSystem.Unregister(CmdListener);
if (GroupCmdListener != null)
CommandHookModSystem.Unregister(GroupCmdListener);
}
private static IEnumerable<(int claimIndex, LandClaim claim)> EnumerateOwnedClaims(
+1 -1
View File
@@ -65,7 +65,7 @@ public class ClaimLinkRegistry
public bool AddEntry(string uid, int groupId, int claimIndex)
{
if (ClaimLinkCommandListener.PendingLoads.ContainsKey(uid))
if (CommandListener.PendingLoads.ContainsKey(uid))
return false;
if (!Exists(groupId))
return false;
+63 -51
View File
@@ -5,16 +5,15 @@ using Vintagestory.API.Server;
namespace ClaimLink;
public class ClaimLinkCommandListener : ICommandHookListener
public class CommandListener : ICommandHookListener
{
public string ModId => "claimlink";
public IReadOnlyList<string> Commands => new[] { "land", "group" };
public IReadOnlyList<string> Commands => new[] { "land" };
public CommandRegistration Registration => new(Before, After);
internal static Dictionary<string, int> PendingLoads = new Dictionary<string, int>();
internal static Dictionary<string, int> PendingDisbands = new Dictionary<string, int>();
private delegate void SubHandler(Caller caller, CmdArgs args);
@@ -33,23 +32,15 @@ public class ClaimLinkCommandListener : ICommandHookListener
["cancel"] = ClaimCancel,
};
private static readonly Dictionary<string, SubHandler> groupTopLevel = new()
{
["confirmdisband"] = GroupConfirmDisband,
};
private static void Dispatch(TextCommandCallingArgs args)
{
Dictionary<string, SubHandler> table =
args.Command?.Name == "group" ? groupTopLevel : topLevel;
CmdArgs rawArgs = args.RawArgs.Clone();
while (rawArgs.Length > 0)
{
string word = rawArgs.PeekWord();
if (table.TryGetValue(word, out SubHandler? handler))
if (topLevel.TryGetValue(word, out SubHandler? handler))
{
rawArgs.PopWord();
handler(args.Caller, rawArgs);
@@ -67,45 +58,7 @@ public class ClaimLinkCommandListener : ICommandHookListener
return null;
}
private void After(TextCommandCallingArgs args, TextCommandResult result)
{
if (args.Caller.Player == null)
return;
string uid = args.Caller.Player.PlayerUID;
if (!PendingDisbands.TryGetValue(uid, out int groupId))
return;
PendingDisbands.Remove(uid);
if (ClaimLinkModSystem.Groups.PlayerGroupsById.ContainsKey(groupId))
return;
if (!ClaimLinkModSystem.Registry.Exists(groupId))
return;
foreach (string memberUid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
ClaimLinkChatCommand.PendingActions.Remove(memberUid);
ClaimLinkModSystem.Registry.Remove(groupId);
}
private static void GroupConfirmDisband(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string? name = args.PopWord();
int? groupId =
name != null
? ClaimLinkModSystem.Groups.GetPlayerGroupByName(name)?.Uid
: caller.FromChatGroupId;
if (groupId == null)
return;
PendingDisbands[caller.Player.PlayerUID] = (int)groupId;
}
private void After(TextCommandCallingArgs args, TextCommandResult result) { }
private static void Claim(Caller caller, CmdArgs args)
{
@@ -210,3 +163,62 @@ public class ClaimLinkCommandListener : ICommandHookListener
PendingLoads.Remove(player.PlayerUID);
}
}
public class GroupCommandListener : ICommandHookListener
{
public string ModId => "claimlink.group";
public IReadOnlyList<string> Commands => new[] { "group" };
public CommandRegistration Registration => new(Before, After);
internal static Dictionary<string, int> PendingDisbands = new Dictionary<string, int>();
private TextCommandResult? Before(TextCommandCallingArgs args)
{
if (args.Caller.Player == null)
return null;
CmdArgs rawArgs = args.RawArgs.Clone();
if (rawArgs.PopWord() != "confirmdisband")
return null;
string? name = rawArgs.PopWord();
int? groupId =
name != null
? ClaimLinkModSystem.Groups.GetPlayerGroupByName(name)?.Uid
: args.Caller.FromChatGroupId;
if (groupId == null)
return null;
PendingDisbands[args.Caller.Player.PlayerUID] = (int)groupId;
return null;
}
private void After(TextCommandCallingArgs args, TextCommandResult result)
{
if (args.Caller.Player == null)
return;
string uid = args.Caller.Player.PlayerUID;
if (!PendingDisbands.TryGetValue(uid, out int groupId))
return;
PendingDisbands.Remove(uid);
if (ClaimLinkModSystem.Groups.PlayerGroupsById.ContainsKey(groupId))
return;
if (!ClaimLinkModSystem.Registry.Exists(groupId))
return;
ClaimLinkChatCommand.RemovePendingActionsForGroup(groupId);
ClaimLinkModSystem.Registry.Remove(groupId);
}
internal static void OnPlayerDisconnect(IServerPlayer player)
{
PendingDisbands.Remove(player.PlayerUID);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
"description": "Link claims together with groups.",
"version": "0.0.6",
"dependencies": {
"game": "1.22.3",
"game": "1.22.5",
"commandhook": "2.1.0"
}
}