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