Compare commits

...
8 Commits
6 changed files with 631 additions and 259 deletions
+310 -86
View File
@@ -69,14 +69,18 @@ public static class ClaimLinkChatCommand
new( new(
new[] { "new", "n" }, new[] { "new", "n" },
"Promote a group you own to a claim link.", "Promote a group you own to a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, new ICommandArgumentParser[] { p.OptionalWord("groupname") },
true, true,
New New
), ),
new( new(
new[] { "link", "l" }, new[] { "link", "l" },
"Link a claim you own to a claim link.", "Link a claim you own to a claim link.",
new ICommandArgumentParser[] { p.Word("groupname"), p.IntRange("claim", 0, 999) }, new ICommandArgumentParser[]
{
p.IntRange("claim", 0, 999),
p.OptionalWord("groupname"),
},
true, true,
Link Link
), ),
@@ -104,28 +108,28 @@ public static class ClaimLinkChatCommand
new( new(
new[] { "kick" }, new[] { "kick" },
"Force-unlink all of a player's claims from the claim link.", "Force-unlink all of a player's claims from the claim link.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
true, true,
Kick Kick
), ),
new( new(
new[] { "delete" }, new[] { "delete" },
"Delete a claim link.", "Delete a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, new ICommandArgumentParser[] { p.OptionalWord("groupname") },
true, true,
Delete Delete
), ),
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.PlayerUids("playername") }, new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
true, true,
TransferOwnership TransferOwnership
), ),
new( new(
new[] { "info", "i" }, new[] { "info", "i" },
"Show a claim link's members and linked claims.", "Show a claim link's members and linked claims.",
new ICommandArgumentParser[] { p.Word("groupname") }, new ICommandArgumentParser[] { p.OptionalWord("groupname") },
true, true,
Info Info
), ),
@@ -150,38 +154,35 @@ public static class ClaimLinkChatCommand
new( new(
new[] { "delete", "del" }, new[] { "delete", "del" },
"Delete a claim link.", "Delete a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, new ICommandArgumentParser[] { p.OptionalWord("groupname") },
false, false,
AdminDelete AdminDelete
), ),
new( new(
new[] { "unlink" }, new[] { "unlink" },
"Unlink a claim.", "Unlink a claim.",
new ICommandArgumentParser[] { p.OnlinePlayer("playername"), p.Word("claim") }, new ICommandArgumentParser[] { p.Word("playername"), p.IntRange("claim", 0, 999) },
false, false,
AdminUnlink AdminUnlink
), ),
new( new(
new[] { "kick" }, new[] { "kick" },
"Force-unlink all of a player's claims.", "Force-unlink all of a player's claims.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
false, false,
AdminKick AdminKick
), ),
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.PlayerUids("playername") }, new ICommandArgumentParser[]
{
p.PlayerUids("playername"),
p.OptionalWord("groupname"),
},
false, false,
AdminTransferOwnership AdminTransferOwnership
), ),
new(
new[] { "info", "i" },
"Show a claim link's members and linked claims.",
new ICommandArgumentParser[] { p.Word("groupname") },
false,
AdminInfo
),
}; };
foreach (var spec in adminCommands) foreach (var spec in adminCommands)
@@ -190,20 +191,53 @@ 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();
internal static readonly Dictionary<string, string> LastCancelReason = 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);
LastCancelReason.Remove(playerUid);
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, string reason)
{
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);
LastCancelReason[uid] = reason;
}
}
internal static void CancelPendingAction(string uid, int groupId, string reason)
{
if (!PendingActions.TryGetValue(uid, out var pending) || pending.GroupId != groupId)
return;
PendingActions.Remove(uid);
LastCancelReason[uid] = reason;
}
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)!;
@@ -212,6 +246,24 @@ public static class ClaimLinkChatCommand
: null; : null;
} }
private static TextCommandResult? TryResolveGroupArg(
TextCommandCallingArgs args,
int argIndex,
out PlayerGroup group
)
{
if (!args.Parsers[argIndex].IsMissing)
return TryResolveGroup((string)args[argIndex], out group);
int chatGroupId = args.Caller.FromChatGroupId;
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(chatGroupId, out group!))
return TextCommandResult.Error(
"No group specified and you are not sending this from a group chat channel."
);
return null;
}
private static TextCommandResult? TryResolveClaimLink(PlayerGroup group) private static TextCommandResult? TryResolveClaimLink(PlayerGroup group)
{ {
return !ClaimLinkModSystem.Registry.Exists(group.Uid) return !ClaimLinkModSystem.Registry.Exists(group.Uid)
@@ -246,10 +298,9 @@ public static class ClaimLinkChatCommand
public static TextCommandResult New(TextCommandCallingArgs args) public static TextCommandResult New(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0];
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -258,14 +309,19 @@ public static class ClaimLinkChatCommand
return err; return err;
if (ClaimLinkModSystem.Registry.Exists(group.Uid)) if (ClaimLinkModSystem.Registry.Exists(group.Uid))
return TextCommandResult.Error($"'{groupName}' is already a claim link."); return TextCommandResult.Error($"'{group.Name}' is already a claim link.");
int groupId = group.Uid; int groupId = group.Uid;
string groupName = group.Name;
return Stage( return Stage(
playerUid, playerUid,
groupId,
$"'{groupName}' will become a claim link.", $"'{groupName}' will become a claim link.",
() => () =>
{ {
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.ContainsKey(groupId))
return TextCommandResult.Error($"'{groupName}' no longer exists.");
ClaimLinkModSystem.Registry.Add(groupId); ClaimLinkModSystem.Registry.Add(groupId);
return TextCommandResult.Success($"'{groupName}' is now a claim link."); return TextCommandResult.Success($"'{groupName}' is now a claim link.");
} }
@@ -276,11 +332,16 @@ 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))
{
if (LastCancelReason.Remove(playerUid, out string? reason))
return TextCommandResult.Error($"You do not have a pending action ({reason}).");
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(); LastCancelReason.Remove(playerUid);
return pending.Action();
} }
public static TextCommandResult Cancel(TextCommandCallingArgs args) public static TextCommandResult Cancel(TextCommandCallingArgs args)
@@ -295,10 +356,9 @@ public static class ClaimLinkChatCommand
public static TextCommandResult Link(TextCommandCallingArgs args) public static TextCommandResult Link(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; int claimIndex = (int)args[0];
int claimIndex = (int)args[1];
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); TextCommandResult? err = TryResolveGroupArg(args, 1, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -313,6 +373,7 @@ public static class ClaimLinkChatCommand
string playerUid = player.PlayerUID; string playerUid = player.PlayerUID;
int groupId = group.Uid; int groupId = group.Uid;
string groupName = group.Name;
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, 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.");
@@ -323,9 +384,17 @@ 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}'.",
() => () =>
{ {
bool stillMember =
ClaimLinkModSystem
.PlayerData.GetPlayerDataByUid(playerUid)
?.PlayerGroupMemberships.ContainsKey(groupId) == true;
if (!stillMember)
return TextCommandResult.Error($"You are no longer a member of '{groupName}'.");
bool success = ClaimLinkModSystem.Registry.AddEntry(playerUid, groupId, claimIndex); bool success = ClaimLinkModSystem.Registry.AddEntry(playerUid, groupId, claimIndex);
return success return success
? TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.") ? TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.")
@@ -352,6 +421,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}'.",
() => () =>
{ {
@@ -363,40 +433,60 @@ public static class ClaimLinkChatCommand
public static TextCommandResult Kick(TextCommandCallingArgs args) public static TextCommandResult Kick(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; string word1 = (string)args[0];
IPlayer target = (IPlayer)args[1]; string? word2 = args.Parsers[1].IsMissing ? null : (string)args[1];
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); string targetName;
PlayerGroup group;
if (word2 != null)
{
TextCommandResult? err = TryResolveGroup(word1, out group);
if (err != null) if (err != null)
return err; return err;
targetName = word2;
}
else
{
int chatGroupId = args.Caller.FromChatGroupId;
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(chatGroupId, out group!))
return TextCommandResult.Error(
"No group specified and you are not sending this from a group chat channel."
);
targetName = word1;
}
err = TryResolveClaimLink(group); TextCommandResult? linkErr = TryResolveClaimLink(group);
if (err != null) if (linkErr != null)
return err; return linkErr;
err = RequireOpOrOwner(args.Caller.Player, group); linkErr = RequireOpOrOwner(args.Caller.Player, group);
if (err != null) if (linkErr != null)
return err; return linkErr;
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(targetName)
?.PlayerUID;
if (targetUid == null)
return TextCommandResult.Error($"No such player '{targetName}'.");
string targetUid = target.PlayerUID;
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
string groupName = group.Name;
if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid)) if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid))
return TextCommandResult.Error( return TextCommandResult.Error($"{targetName} has no claims linked in '{groupName}'.");
$"{target.PlayerName} has no claims linked in '{groupName}'."
);
return Stage( return Stage(
playerUid, playerUid,
$"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.", group.Uid,
$"All of {targetName}'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(
$"Unlinked all claims of {target.PlayerName} from '{groupName}'." $"Unlinked all claims of {targetName} from '{groupName}'."
); );
} }
); );
@@ -404,10 +494,9 @@ public static class ClaimLinkChatCommand
public static TextCommandResult Delete(TextCommandCallingArgs args) public static TextCommandResult Delete(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0];
string playerUid = args.Caller.Player.PlayerUID; string playerUid = args.Caller.Player.PlayerUID;
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group); TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -420,14 +509,14 @@ public static class ClaimLinkChatCommand
return err; return err;
int groupId = group.Uid; int groupId = group.Uid;
string groupName = group.Name;
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, "claim link was deleted");
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.");
} }
@@ -436,18 +525,32 @@ public static class ClaimLinkChatCommand
public static TextCommandResult TransferOwnership(TextCommandCallingArgs args) public static TextCommandResult TransferOwnership(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; string word1 = (string)args[0];
string? word2 = args.Parsers[1].IsMissing ? null : (string)args[1];
string targetName;
PlayerGroup group;
if (word2 != null)
{
TextCommandResult? groupErr = TryResolveGroup(word1, out group);
if (groupErr != null)
return groupErr;
targetName = word2;
}
else
{
int chatGroupId = args.Caller.FromChatGroupId;
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(chatGroupId, out group!))
return TextCommandResult.Error(
"No group specified and you are not sending this from a group chat channel."
);
targetName = word1;
}
IPlayer player = args.Caller.Player; IPlayer player = args.Caller.Player;
string playerUid = player.PlayerUID; string playerUid = player.PlayerUID;
TextCommandResult? err = TryResolveTargetPlayer( TextCommandResult? err = TryResolveOnlinePlayerByName(targetName, out IPlayer target);
(PlayerUidName[])args[1],
out IPlayer target
);
if (err != null)
return err;
err = TryResolveGroup(groupName, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -467,13 +570,31 @@ public static class ClaimLinkChatCommand
if (err != null) if (err != null)
return err; return err;
string groupName = group.Name;
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)
); );
} }
private static TextCommandResult? TryResolveOnlinePlayerByName(string name, out IPlayer target)
{
target = null!;
foreach (IPlayer online in ClaimLinkModSystem.World.AllOnlinePlayers)
{
if (string.Equals(online.PlayerName, name, StringComparison.OrdinalIgnoreCase))
{
target = online;
return null;
}
}
return TextCommandResult.Error($"No such player '{name}' online.");
}
private static TextCommandResult? TryResolveTargetPlayer( private static TextCommandResult? TryResolveTargetPlayer(
PlayerUidName[] matches, PlayerUidName[] matches,
out IPlayer target out IPlayer target
@@ -536,9 +657,7 @@ public static class ClaimLinkChatCommand
public static TextCommandResult Info(TextCommandCallingArgs args) public static TextCommandResult Info(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0]; TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -550,31 +669,58 @@ public static class ClaimLinkChatCommand
bool showDetails = bool showDetails =
player.HasPrivilege(Privilege.controlserver) || player.GetGroup(group.Uid) != null; player.HasPrivilege(Privilege.controlserver) || player.GetGroup(group.Uid) != null;
return TextCommandResult.Success(FormatInfo(groupName, group.Uid, showDetails)); return TextCommandResult.Success(FormatInfo(group.Name, group.Uid, showDetails));
} }
public static TextCommandResult List(TextCommandCallingArgs args) public static TextCommandResult List(TextCommandCallingArgs args)
{ {
List<int> groupIds = ClaimLinkModSystem List<int> groupIds = new(ClaimLinkModSystem.Registry.All);
.Registry.All.OrderByDescending(id => groupIds.Sort((a, b) => GroupMemberCount(b).CompareTo(GroupMemberCount(a)));
ClaimLinkModSystem.Registry.MemberCountForGroup(id)
)
.ToList();
if (groupIds.Count == 0) if (groupIds.Count == 0)
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 (
int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(groupId); !ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
sb.AppendLine($" {groupName}: {memberCount} member{(memberCount == 1 ? "" : "s")}"); groupId,
out PlayerGroup? group
)
)
{
RemovePendingActionsForGroup(groupId, "group no longer exists");
ClaimLinkModSystem.Registry.Remove(groupId);
continue;
} }
return TextCommandResult.Success(sb.ToString()); int memberCount = GroupMemberCount(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 IEnumerable<string> GroupMemberUids(int groupId)
{
foreach (var playerData in ClaimLinkModSystem.PlayerData.PlayerDataByUid.Values)
if (playerData.PlayerGroupMemberships.ContainsKey(groupId))
yield return playerData.PlayerUID;
}
private static int GroupMemberCount(int groupId)
{
int count = 0;
foreach (string _ in GroupMemberUids(groupId))
count++;
return count;
} }
private static string DescribeClaim(string ownerPlayerUid, int claimIndex) private static string DescribeClaim(string ownerPlayerUid, int claimIndex)
@@ -594,14 +740,14 @@ public static class ClaimLinkChatCommand
private static string FormatInfo(string groupName, int groupId, bool showDetails) private static string FormatInfo(string groupName, int groupId, bool showDetails)
{ {
int memberCount = ClaimLinkModSystem.Registry.MemberCountForGroup(groupId); int memberCount = GroupMemberCount(groupId);
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine( sb.AppendLine(
$"Claim link '{groupName}' ({memberCount} member{(memberCount == 1 ? "" : "s")}):" $"Claim link '{groupName}' ({memberCount} member{(memberCount == 1 ? "" : "s")}):"
); );
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId)) foreach (string uid in GroupMemberUids(groupId))
{ {
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid; string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
@@ -620,27 +766,108 @@ public static class ClaimLinkChatCommand
return sb.ToString(); return sb.ToString();
} }
public static TextCommandResult AdminDelete(TextCommandCallingArgs args) => public static TextCommandResult AdminDelete(TextCommandCallingArgs args)
TextCommandResult.Success("stub: claimlink admin delete"); {
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
if (err != null)
return err;
public static TextCommandResult AdminUnlink(TextCommandCallingArgs args) => err = TryResolveClaimLink(group);
TextCommandResult.Success("stub: claimlink admin unlink"); if (err != null)
return err;
public static TextCommandResult AdminKick(TextCommandCallingArgs args) => int groupId = group.Uid;
TextCommandResult.Success("stub: claimlink admin kick"); string groupName = group.Name;
RemovePendingActionsForGroup(groupId, "claim link was deleted");
ClaimLinkModSystem.Registry.Remove(groupId);
return TextCommandResult.Success($"'{groupName}' is no longer a claim link.");
}
public static TextCommandResult AdminUnlink(TextCommandCallingArgs args)
{
string playerName = (string)args[0];
int claimIndex = (int)args[1];
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(playerName)
?.PlayerUID;
if (targetUid == null)
return TextCommandResult.Error($"No such player '{playerName}'.");
int? groupId = ClaimLinkModSystem.Registry.FindGroupContaining(targetUid, claimIndex);
if (groupId == null)
return TextCommandResult.Error(
$"Claim {claimIndex} is not linked into any claim link by {playerName}."
);
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[(int)groupId].Name;
string claimDesc = DescribeClaim(targetUid, claimIndex);
ClaimLinkModSystem.Registry.RemoveEntry(targetUid, (int)groupId, claimIndex);
return TextCommandResult.Success(
$"Unlinked {claimDesc} from '{groupName}' (owned by {playerName})."
);
}
public static TextCommandResult AdminKick(TextCommandCallingArgs args)
{
string word1 = (string)args[0];
string? word2 = args.Parsers[1].IsMissing ? null : (string)args[1];
string targetName;
PlayerGroup group;
if (word2 != null)
{
TextCommandResult? err = TryResolveGroup(word1, out group);
if (err != null)
return err;
targetName = word2;
}
else
{
int chatGroupId = args.Caller.FromChatGroupId;
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(chatGroupId, out group!))
return TextCommandResult.Error(
"No group specified and you are not sending this from a group chat channel."
);
targetName = word1;
}
TextCommandResult? linkErr = TryResolveClaimLink(group);
if (linkErr != null)
return linkErr;
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(targetName)
?.PlayerUID;
if (targetUid == null)
return TextCommandResult.Error($"No such player '{targetName}'.");
string groupName = group.Name;
if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid))
return TextCommandResult.Error($"{targetName} has no claims linked in '{groupName}'.");
ClaimLinkModSystem.Registry.RemoveAllForPlayerInGroup(targetUid, group.Uid);
CommandListener.PendingLoads.Remove(targetUid);
PendingActions.Remove(targetUid);
return TextCommandResult.Success(
$"Unlinked all claims of {targetName} from '{groupName}'."
);
}
public static TextCommandResult AdminTransferOwnership(TextCommandCallingArgs args) public static TextCommandResult AdminTransferOwnership(TextCommandCallingArgs args)
{ {
string groupName = (string)args[0];
TextCommandResult? err = TryResolveTargetPlayer( TextCommandResult? err = TryResolveTargetPlayer(
(PlayerUidName[])args[1], (PlayerUidName[])args[0],
out IPlayer target out IPlayer target
); );
if (err != null) if (err != null)
return err; return err;
err = TryResolveGroup(groupName, out PlayerGroup group); err = TryResolveGroupArg(args, 1, out PlayerGroup group);
if (err != null) if (err != null)
return err; return err;
@@ -659,9 +886,6 @@ public static class ClaimLinkChatCommand
return ExecuteTransferOwnership(group, target); return ExecuteTransferOwnership(group, target);
} }
public static TextCommandResult AdminInfo(TextCommandCallingArgs args) =>
TextCommandResult.Success("stub: claimlink admin info");
internal static void OnPlayerDisconnect(IServerPlayer player) internal static void OnPlayerDisconnect(IServerPlayer player)
{ {
PendingActions.Remove(player.PlayerUID); PendingActions.Remove(player.PlayerUID);
+9 -3
View File
@@ -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(
+1 -1
View File
@@ -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;
+307
View File
@@ -0,0 +1,307 @@
using System.Collections.Generic;
using CommandHook;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
namespace ClaimLink;
public class CommandListener : ICommandHookListener
{
public string ModId => "claimlink";
public IReadOnlyList<string> Commands => new[] { "land" };
public CommandRegistration Registration => new(Before, After);
internal static Dictionary<string, int> PendingLoads = new Dictionary<string, int>();
private delegate void SubHandler(Caller caller, CmdArgs args);
private static readonly Dictionary<string, SubHandler> topLevel = new()
{
["claim"] = Claim,
["free"] = Free,
["adminfree"] = AdminFree,
["adminfreehere"] = AdminFreeHere,
};
private static readonly Dictionary<string, SubHandler> claimSub = new()
{
["load"] = ClaimLoad,
["save"] = ClaimSave,
["cancel"] = ClaimCancel,
};
private static void Dispatch(TextCommandCallingArgs args)
{
CmdArgs rawArgs = args.RawArgs.Clone();
while (rawArgs.Length > 0)
{
string word = rawArgs.PeekWord();
if (topLevel.TryGetValue(word, out SubHandler? handler))
{
rawArgs.PopWord();
handler(args.Caller, rawArgs);
}
else
{
rawArgs.PopWord();
}
}
}
private TextCommandResult? Before(TextCommandCallingArgs args)
{
Dispatch(args);
return null;
}
private void After(TextCommandCallingArgs args, TextCommandResult result) { }
private static void Claim(Caller caller, CmdArgs args)
{
string? word = args.PeekWord();
if (word == null)
return;
if (claimSub.TryGetValue(word, out SubHandler? handler))
{
args.PopWord();
handler(caller, args);
}
}
private static void ClaimSave(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
if (!PendingLoads.TryGetValue(uid, out int pendingIndex))
return;
ClaimLinkModSystem.Registry.ClaimSaved(uid, pendingIndex);
PendingLoads.Remove(uid);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void ClaimLoad(Caller caller, CmdArgs args)
{
int? claimIndex = args.PopInt();
if (caller.Player == null || claimIndex == null)
return;
if (
ClaimLinkModSystem.TryResolveOwnedClaim(caller.Player.PlayerUID, (int)claimIndex, out _)
)
PendingLoads[caller.Player.PlayerUID] = (int)claimIndex;
}
private static void ClaimCancel(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
PendingLoads.Remove(caller.Player.PlayerUID);
}
private static void Free(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
int? claimIndex = args.PopInt();
if (claimIndex == null)
return;
if (args.PopWord() != "confirm")
return;
ClaimLinkModSystem.Registry.ClaimRemoved(uid, (int)claimIndex);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void AdminFree(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string? name = args.PopWord();
if (name == null)
return;
string? uid = ClaimLinkModSystem.PlayerData.GetPlayerDataByLastKnownName(name)?.PlayerUID;
if (uid == null)
return;
ClaimLinkModSystem.Registry.ClearAllForPlayer(uid);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void AdminFreeHere(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
LandClaim[] claims = ClaimLinkModSystem.LandClaimAPI.Get(
caller.Player.Entity.Pos.AsBlockPos
);
foreach (LandClaim c in claims)
if (ClaimLinkModSystem.TryResolveClaimIndex(c.OwnedByPlayerUid, c, out int claimIndex))
{
ClaimLinkModSystem.Registry.ClaimRemoved(c.OwnedByPlayerUid, claimIndex);
ClaimLinkChatCommand.PendingActions.Remove(c.OwnedByPlayerUid);
}
}
internal static void OnPlayerDisconnect(IServerPlayer player)
{
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>();
internal static Dictionary<string, (string AffectedUid, int GroupId)> PendingMembershipChecks =
new Dictionary<string, (string, int)>();
private TextCommandResult? Before(TextCommandCallingArgs args)
{
if (args.Caller.Player == null)
return null;
string callerUid = args.Caller.Player.PlayerUID;
CmdArgs rawArgs = args.RawArgs.Clone();
string? word = rawArgs.PopWord();
switch (word)
{
case "confirmdisband":
ConfirmDisband(args, callerUid, rawArgs);
break;
case "leave":
Leave(args, callerUid, rawArgs);
break;
case "kick":
Kick(args, callerUid, rawArgs);
break;
}
return null;
}
private static void ConfirmDisband(
TextCommandCallingArgs args,
string callerUid,
CmdArgs rawArgs
)
{
string? name = rawArgs.PopWord();
int? groupId =
name != null
? ClaimLinkModSystem.Groups.GetPlayerGroupByName(name)?.Uid
: args.Caller.FromChatGroupId;
if (groupId != null)
PendingDisbands[callerUid] = (int)groupId;
}
private static void Leave(TextCommandCallingArgs args, string callerUid, CmdArgs rawArgs)
{
string? name = rawArgs.PopWord();
int? groupId =
name != null
? ClaimLinkModSystem.Groups.GetPlayerGroupByName(name)?.Uid
: args.Caller.FromChatGroupId;
if (groupId != null)
PendingMembershipChecks[callerUid] = (callerUid, (int)groupId);
}
private static void Kick(TextCommandCallingArgs args, string callerUid, CmdArgs rawArgs)
{
string? word1 = rawArgs.PopWord();
string? word2 = rawArgs.PopWord();
string? targetName;
int? groupId;
if (word2 != null)
{
groupId =
word1 != null ? ClaimLinkModSystem.Groups.GetPlayerGroupByName(word1)?.Uid : null;
targetName = word2;
}
else
{
targetName = word1;
groupId = args.Caller.FromChatGroupId;
}
if (targetName == null || groupId == null)
return;
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(targetName)
?.PlayerUID;
if (targetUid != null)
PendingMembershipChecks[callerUid] = (targetUid, (int)groupId);
}
private void After(TextCommandCallingArgs args, TextCommandResult result)
{
if (args.Caller.Player == null)
return;
string callerUid = args.Caller.Player.PlayerUID;
if (PendingDisbands.TryGetValue(callerUid, out int disbandedGroupId))
{
PendingDisbands.Remove(callerUid);
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.ContainsKey(disbandedGroupId))
{
ClaimLinkChatCommand.RemovePendingActionsForGroup(
disbandedGroupId,
"group no longer exists"
);
if (ClaimLinkModSystem.Registry.Exists(disbandedGroupId))
ClaimLinkModSystem.Registry.Remove(disbandedGroupId);
}
}
if (PendingMembershipChecks.TryGetValue(callerUid, out var check))
{
PendingMembershipChecks.Remove(callerUid);
bool stillMember =
ClaimLinkModSystem
.PlayerData.GetPlayerDataByUid(check.AffectedUid)
?.PlayerGroupMemberships.ContainsKey(check.GroupId) == true;
if (!stillMember)
ClaimLinkChatCommand.CancelPendingAction(
check.AffectedUid,
check.GroupId,
"you are no longer a member of the group"
);
}
}
internal static void OnPlayerDisconnect(IServerPlayer player)
{
PendingDisbands.Remove(player.PlayerUID);
PendingMembershipChecks.Remove(player.PlayerUID);
}
}
-165
View File
@@ -1,165 +0,0 @@
using System.Collections.Generic;
using CommandHook;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
namespace ClaimLink;
public class ClaimLinkCommandListener : ICommandHookListener
{
public string ModId => "claimlink";
public IReadOnlyList<string> Commands => new[] { "land" };
public CommandRegistration Registration => new(Before, After);
internal static Dictionary<string, int> PendingLoads = new Dictionary<string, int>();
private delegate void SubHandler(Caller caller, CmdArgs args);
private static readonly Dictionary<string, SubHandler> topLevel = new()
{
["claim"] = Claim,
["free"] = Free,
["adminfree"] = AdminFree,
["adminfreehere"] = AdminFreeHere,
};
private static readonly Dictionary<string, SubHandler> claimSub = new()
{
["load"] = ClaimLoad,
["save"] = ClaimSave,
["cancel"] = ClaimCancel,
};
private static void Dispatch(TextCommandCallingArgs args)
{
CmdArgs rawArgs = args.RawArgs.Clone();
while (rawArgs.Length > 0)
{
string word = rawArgs.PeekWord();
if (topLevel.TryGetValue(word, out SubHandler? handler))
{
rawArgs.PopWord();
handler(args.Caller, rawArgs);
}
else
{
rawArgs.PopWord();
}
}
}
private TextCommandResult? Before(TextCommandCallingArgs args)
{
Dispatch(args);
return null;
}
private void After(TextCommandCallingArgs args, TextCommandResult result) { }
private static void Claim(Caller caller, CmdArgs args)
{
string? word = args.PeekWord();
if (word == null)
return;
if (claimSub.TryGetValue(word, out SubHandler? handler))
{
args.PopWord();
handler(caller, args);
}
}
private static void ClaimSave(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
if (!PendingLoads.TryGetValue(uid, out int pendingIndex))
return;
ClaimLinkModSystem.Registry.ClaimSaved(uid, pendingIndex);
PendingLoads.Remove(uid);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void ClaimLoad(Caller caller, CmdArgs args)
{
int? claimIndex = args.PopInt();
if (caller.Player == null || claimIndex == null)
return;
if (
ClaimLinkModSystem.TryResolveOwnedClaim(caller.Player.PlayerUID, (int)claimIndex, out _)
)
PendingLoads[caller.Player.PlayerUID] = (int)claimIndex;
}
private static void ClaimCancel(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
PendingLoads.Remove(caller.Player.PlayerUID);
}
private static void Free(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string uid = caller.Player.PlayerUID;
int? claimIndex = args.PopInt();
if (claimIndex == null)
return;
if (args.PopWord() != "confirm")
return;
ClaimLinkModSystem.Registry.ClaimRemoved(uid, (int)claimIndex);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void AdminFree(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
string? name = args.PopWord();
if (name == null)
return;
string? uid = ClaimLinkModSystem.PlayerData.GetPlayerDataByLastKnownName(name)?.PlayerUID;
if (uid == null)
return;
ClaimLinkModSystem.Registry.ClearAllForPlayer(uid);
ClaimLinkChatCommand.PendingActions.Remove(uid);
}
private static void AdminFreeHere(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
LandClaim[] claims = ClaimLinkModSystem.LandClaimAPI.Get(
caller.Player.Entity.Pos.AsBlockPos
);
foreach (LandClaim c in claims)
if (ClaimLinkModSystem.TryResolveClaimIndex(c.OwnedByPlayerUid, c, out int claimIndex))
{
ClaimLinkModSystem.Registry.ClaimRemoved(c.OwnedByPlayerUid, claimIndex);
ClaimLinkChatCommand.PendingActions.Remove(c.OwnedByPlayerUid);
}
}
internal static void OnPlayerDisconnect(IServerPlayer player)
{
PendingLoads.Remove(player.PlayerUID);
}
}
+2 -2
View File
@@ -8,9 +8,9 @@
"anth64" "anth64"
], ],
"description": "Link claims together with groups.", "description": "Link claims together with groups.",
"version": "0.0.6", "version": "0.1.0",
"dependencies": { "dependencies": {
"game": "1.22.3", "game": "1.22.5",
"commandhook": "2.1.0" "commandhook": "2.1.0"
} }
} }