811 lines
26 KiB
C#
811 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Vintagestory.API.Common;
|
|
using Vintagestory.API.Config;
|
|
using Vintagestory.API.Server;
|
|
|
|
namespace ClaimLink;
|
|
|
|
public static class ClaimLinkChatCommand
|
|
{
|
|
private readonly struct CommandSpec
|
|
{
|
|
public readonly string[] Names;
|
|
public readonly string Description;
|
|
public readonly ICommandArgumentParser[] Args;
|
|
public readonly bool IsPlayerOnly;
|
|
public readonly OnCommandDelegate Handler;
|
|
|
|
public CommandSpec(
|
|
string[] names,
|
|
string description,
|
|
ICommandArgumentParser[] args,
|
|
bool isPlayerOnly,
|
|
OnCommandDelegate handler
|
|
)
|
|
{
|
|
Names = names;
|
|
Description = description;
|
|
Args = args;
|
|
IsPlayerOnly = isPlayerOnly;
|
|
Handler = handler;
|
|
}
|
|
}
|
|
|
|
private static void BuildSubCommand(IChatCommand parent, CommandSpec spec)
|
|
{
|
|
var sub =
|
|
spec.Names.Length > 1
|
|
? parent.BeginSubCommands(spec.Names)
|
|
: parent.BeginSubCommand(spec.Names[0]);
|
|
|
|
sub.WithDescription(spec.Description);
|
|
|
|
if (spec.Args.Length > 0)
|
|
sub.WithArgs(spec.Args);
|
|
|
|
if (spec.IsPlayerOnly)
|
|
sub.RequiresPlayer();
|
|
else
|
|
sub.RequiresPrivilege(Privilege.controlserver);
|
|
|
|
sub.HandleWith(spec.Handler).EndSubCommand();
|
|
}
|
|
|
|
public static void Register(ICoreServerAPI api)
|
|
{
|
|
var p = api.ChatCommands.Parsers;
|
|
|
|
var root = api
|
|
.ChatCommands.Create("claimlink")
|
|
.WithAlias("clink", "claiml", "cl")
|
|
.WithDescription("Link vanilla land claims together via group.")
|
|
.RequiresPrivilege(Privilege.chat);
|
|
|
|
CommandSpec[] playerCommands =
|
|
{
|
|
new(
|
|
new[] { "new", "n" },
|
|
"Promote a group you own to a claim link.",
|
|
new ICommandArgumentParser[] { p.OptionalWord("groupname") },
|
|
true,
|
|
New
|
|
),
|
|
new(
|
|
new[] { "link", "l" },
|
|
"Link a claim you own to a claim link.",
|
|
new ICommandArgumentParser[]
|
|
{
|
|
p.IntRange("claim", 0, 999),
|
|
p.OptionalWord("groupname"),
|
|
},
|
|
true,
|
|
Link
|
|
),
|
|
new(
|
|
new[] { "confirm", "c" },
|
|
"Confirm pending action.",
|
|
Array.Empty<ICommandArgumentParser>(),
|
|
true,
|
|
Confirm
|
|
),
|
|
new(
|
|
new[] { "cancel" },
|
|
"Cancel pending action.",
|
|
Array.Empty<ICommandArgumentParser>(),
|
|
true,
|
|
Cancel
|
|
),
|
|
new(
|
|
new[] { "unlink", "ul" },
|
|
"Remove a claim you own from its claim link.",
|
|
new ICommandArgumentParser[] { p.IntRange("claim", 0, 999) },
|
|
true,
|
|
Unlink
|
|
),
|
|
new(
|
|
new[] { "kick" },
|
|
"Force-unlink all of a player's claims from the claim link.",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
|
|
true,
|
|
Kick
|
|
),
|
|
new(
|
|
new[] { "delete" },
|
|
"Delete a claim link.",
|
|
new ICommandArgumentParser[] { p.OptionalWord("groupname") },
|
|
true,
|
|
Delete
|
|
),
|
|
new(
|
|
new[] { "transferownership", "transfer", "to" },
|
|
"Transfer ownership of the claim link to another player.",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
|
|
true,
|
|
TransferOwnership
|
|
),
|
|
new(
|
|
new[] { "info", "i" },
|
|
"Show a claim link's members and linked claims.",
|
|
new ICommandArgumentParser[] { p.OptionalWord("groupname") },
|
|
true,
|
|
Info
|
|
),
|
|
new(
|
|
new[] { "list", "ls" },
|
|
"List all claim links.",
|
|
Array.Empty<ICommandArgumentParser>(),
|
|
true,
|
|
List
|
|
),
|
|
};
|
|
|
|
foreach (var spec in playerCommands)
|
|
BuildSubCommand(root, spec);
|
|
|
|
var admin = root.BeginSubCommands("admin", "a")
|
|
.WithDescription("Admin commands for claim link.")
|
|
.RequiresPrivilege(Privilege.controlserver);
|
|
|
|
CommandSpec[] adminCommands =
|
|
{
|
|
new(
|
|
new[] { "delete", "del" },
|
|
"Delete a claim link.",
|
|
new ICommandArgumentParser[] { p.Word("groupname") },
|
|
false,
|
|
AdminDelete
|
|
),
|
|
new(
|
|
new[] { "unlink" },
|
|
"Unlink a claim.",
|
|
new ICommandArgumentParser[] { p.OnlinePlayer("playername"), p.Word("claim") },
|
|
false,
|
|
AdminUnlink
|
|
),
|
|
new(
|
|
new[] { "kick" },
|
|
"Force-unlink all of a player's claims.",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") },
|
|
false,
|
|
AdminKick
|
|
),
|
|
new(
|
|
new[] { "transferownership", "transfer", "to" },
|
|
"Transfer ownership of any claim link to another player.",
|
|
new ICommandArgumentParser[]
|
|
{
|
|
p.PlayerUids("playername"),
|
|
p.OptionalWord("groupname"),
|
|
},
|
|
false,
|
|
AdminTransferOwnership
|
|
),
|
|
};
|
|
|
|
foreach (var spec in adminCommands)
|
|
BuildSubCommand(admin, spec);
|
|
|
|
admin.EndSubCommand();
|
|
}
|
|
|
|
internal static readonly Dictionary<
|
|
string,
|
|
(int GroupId, Func<TextCommandResult> Action)
|
|
> PendingActions = new();
|
|
|
|
internal static readonly Dictionary<string, string> LastCancelReason = new();
|
|
|
|
private static TextCommandResult Stage(
|
|
string playerUid,
|
|
int groupId,
|
|
string prompt,
|
|
Func<TextCommandResult> action
|
|
)
|
|
{
|
|
PendingActions[playerUid] = (groupId, action);
|
|
LastCancelReason.Remove(playerUid);
|
|
return TextCommandResult.Success(
|
|
$"{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)
|
|
{
|
|
group = ClaimLinkModSystem.Groups.GetPlayerGroupByName(groupName)!;
|
|
return group == null
|
|
? TextCommandResult.Error($"No group named '{groupName}' exists.")
|
|
: 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)
|
|
{
|
|
return !ClaimLinkModSystem.Registry.Exists(group.Uid)
|
|
? TextCommandResult.Error($"'{group.Name}' is not a claim link.")
|
|
: null;
|
|
}
|
|
|
|
private static TextCommandResult? RequireMember(IPlayer player, PlayerGroup group)
|
|
{
|
|
return player.GetGroup(group.Uid) == null
|
|
? TextCommandResult.Error($"'{player.PlayerName}' is not a member of '{group.Name}'.")
|
|
: null;
|
|
}
|
|
|
|
private static TextCommandResult? RequireOwner(IPlayer player, PlayerGroup group)
|
|
{
|
|
return group.OwnerUID != player.PlayerUID
|
|
? TextCommandResult.Error($"You do not own '{group.Name}'.")
|
|
: null;
|
|
}
|
|
|
|
private static TextCommandResult? RequireOpOrOwner(IPlayer player, PlayerGroup group)
|
|
{
|
|
if (group.OwnerUID == player.PlayerUID)
|
|
return null;
|
|
|
|
PlayerGroupMembership? membership = player.GetGroup(group.Uid);
|
|
return membership == null || membership.Level < EnumPlayerGroupMemberShip.Op
|
|
? TextCommandResult.Error($"You must be an operator of '{group.Name}' to do that.")
|
|
: null;
|
|
}
|
|
|
|
public static TextCommandResult New(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireOwner(args.Caller.Player, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
if (ClaimLinkModSystem.Registry.Exists(group.Uid))
|
|
return TextCommandResult.Error($"'{group.Name}' is already a claim link.");
|
|
|
|
int groupId = group.Uid;
|
|
string groupName = group.Name;
|
|
return Stage(
|
|
playerUid,
|
|
groupId,
|
|
$"'{groupName}' will become a claim link.",
|
|
() =>
|
|
{
|
|
if (!ClaimLinkModSystem.Groups.PlayerGroupsById.ContainsKey(groupId))
|
|
return TextCommandResult.Error($"'{groupName}' no longer exists.");
|
|
|
|
ClaimLinkModSystem.Registry.Add(groupId);
|
|
return TextCommandResult.Success($"'{groupName}' is now a claim link.");
|
|
}
|
|
);
|
|
}
|
|
|
|
public static TextCommandResult Confirm(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
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.");
|
|
}
|
|
|
|
PendingActions.Remove(playerUid);
|
|
LastCancelReason.Remove(playerUid);
|
|
return pending.Action();
|
|
}
|
|
|
|
public static TextCommandResult Cancel(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
if (!PendingActions.Remove(playerUid))
|
|
return TextCommandResult.Error("You do not have a pending action.");
|
|
|
|
return TextCommandResult.Success("Pending action cancelled.");
|
|
}
|
|
|
|
public static TextCommandResult Link(TextCommandCallingArgs args)
|
|
{
|
|
int claimIndex = (int)args[0];
|
|
|
|
TextCommandResult? err = TryResolveGroupArg(args, 1, out PlayerGroup group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveClaimLink(group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
IPlayer player = args.Caller.Player;
|
|
err = RequireMember(player, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
string playerUid = player.PlayerUID;
|
|
int groupId = group.Uid;
|
|
string groupName = group.Name;
|
|
|
|
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _))
|
|
return TextCommandResult.Error("You do not own that claim.");
|
|
|
|
if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex))
|
|
return TextCommandResult.Error("That claim is already part of a claim link.");
|
|
|
|
string claimDesc = DescribeClaim(playerUid, claimIndex);
|
|
return Stage(
|
|
playerUid,
|
|
groupId,
|
|
$"{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);
|
|
return success
|
|
? TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.")
|
|
: TextCommandResult.Error(
|
|
$"Unable to link {claimIndex} to '{groupName}' it is currently being modified."
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
public static TextCommandResult Unlink(TextCommandCallingArgs args)
|
|
{
|
|
int claimIndex = (int)args[0];
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
int? groupId = ClaimLinkModSystem.Registry.FindGroupContaining(playerUid, claimIndex);
|
|
if (groupId == null)
|
|
return TextCommandResult.Error(
|
|
$"Claim {claimIndex} is not linked into any claim link by you."
|
|
);
|
|
|
|
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[(int)groupId].Name;
|
|
string claimDesc = DescribeClaim(playerUid, claimIndex);
|
|
|
|
return Stage(
|
|
playerUid,
|
|
(int)groupId,
|
|
$"{claimDesc} will be unlinked from '{groupName}'.",
|
|
() =>
|
|
{
|
|
ClaimLinkModSystem.Registry.RemoveEntry(playerUid, (int)groupId, claimIndex);
|
|
return TextCommandResult.Success($"Unlinked {claimDesc} from '{groupName}'.");
|
|
}
|
|
);
|
|
}
|
|
|
|
public static TextCommandResult Kick(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;
|
|
|
|
linkErr = RequireOpOrOwner(args.Caller.Player, group);
|
|
if (linkErr != null)
|
|
return linkErr;
|
|
|
|
string? targetUid = ClaimLinkModSystem
|
|
.PlayerData.GetPlayerDataByLastKnownName(targetName)
|
|
?.PlayerUID;
|
|
if (targetUid == null)
|
|
return TextCommandResult.Error($"No such player '{targetName}'.");
|
|
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
string groupName = group.Name;
|
|
|
|
if (!ClaimLinkModSystem.Registry.HasAnyEntry(targetUid, group.Uid))
|
|
return TextCommandResult.Error($"{targetName} has no claims linked in '{groupName}'.");
|
|
|
|
return Stage(
|
|
playerUid,
|
|
group.Uid,
|
|
$"All of {targetName}'s claims will be unlinked from '{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 Delete(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveClaimLink(group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireOwner(args.Caller.Player, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
int groupId = group.Uid;
|
|
string groupName = group.Name;
|
|
return Stage(
|
|
playerUid,
|
|
groupId,
|
|
$"'{groupName}' will be deleted as a claim link.",
|
|
() =>
|
|
{
|
|
RemovePendingActionsForGroup(groupId, "claim link was deleted");
|
|
ClaimLinkModSystem.Registry.Remove(groupId);
|
|
return TextCommandResult.Success($"'{groupName}' is no longer a claim link.");
|
|
}
|
|
);
|
|
}
|
|
|
|
public static TextCommandResult TransferOwnership(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? 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;
|
|
string playerUid = player.PlayerUID;
|
|
|
|
TextCommandResult? err = TryResolveOnlinePlayerByName(targetName, out IPlayer target);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveClaimLink(group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireOwner(player, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireMember(target, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireNotAlreadyOwner(group, target);
|
|
if (err != null)
|
|
return err;
|
|
|
|
string groupName = group.Name;
|
|
return Stage(
|
|
playerUid,
|
|
group.Uid,
|
|
$"Ownership of '{groupName}' will be transferred to {target.PlayerName}.",
|
|
() => 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(
|
|
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)
|
|
{
|
|
return target.PlayerUID == group.OwnerUID
|
|
? TextCommandResult.Error($"{target.PlayerName} already owns '{group.Name}'.")
|
|
: null;
|
|
}
|
|
|
|
private static TextCommandResult ExecuteTransferOwnership(PlayerGroup group, IPlayer target)
|
|
{
|
|
string oldOwnerUid = group.OwnerUID;
|
|
IPlayer? oldOwner = ClaimLinkModSystem.World.PlayerByUid(oldOwnerUid);
|
|
PlayerGroupMembership? oldOwnerMembership = oldOwner?.GetGroup(group.Uid);
|
|
PlayerGroupMembership targetMembership = target.GetGroup(group.Uid)!;
|
|
|
|
group.OwnerUID = target.PlayerUID;
|
|
targetMembership.Level = EnumPlayerGroupMemberShip.Owner;
|
|
|
|
if (oldOwnerMembership != null)
|
|
oldOwnerMembership.Level = EnumPlayerGroupMemberShip.Op;
|
|
|
|
PendingActions.Remove(oldOwnerUid);
|
|
|
|
string notice = $"'{group.Name}' is now owned by {target.PlayerName}.";
|
|
|
|
HashSet<string> notifyUids = ClaimLinkModSystem
|
|
.Registry.MemberUidsForGroup(group.Uid)
|
|
.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)
|
|
{
|
|
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveClaimLink(group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
IPlayer player = args.Caller.Player;
|
|
bool showDetails =
|
|
player.HasPrivilege(Privilege.controlserver) || player.GetGroup(group.Uid) != null;
|
|
|
|
return TextCommandResult.Success(FormatInfo(group.Name, group.Uid, showDetails));
|
|
}
|
|
|
|
public static TextCommandResult List(TextCommandCallingArgs args)
|
|
{
|
|
List<int> groupIds = new(ClaimLinkModSystem.Registry.All);
|
|
groupIds.Sort((a, b) => GroupMemberCount(b).CompareTo(GroupMemberCount(a)));
|
|
|
|
if (groupIds.Count == 0)
|
|
return TextCommandResult.Success("There are no claim links.");
|
|
|
|
StringBuilder sb = new();
|
|
int shown = 0;
|
|
|
|
foreach (int groupId in groupIds)
|
|
{
|
|
if (
|
|
!ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
|
|
groupId,
|
|
out PlayerGroup? group
|
|
)
|
|
)
|
|
{
|
|
RemovePendingActionsForGroup(groupId, "group no longer exists");
|
|
ClaimLinkModSystem.Registry.Remove(groupId);
|
|
continue;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (
|
|
!ClaimLinkModSystem.TryResolveOwnedClaim(
|
|
ownerPlayerUid,
|
|
claimIndex,
|
|
out LandClaim? claim
|
|
)
|
|
|| claim == null
|
|
)
|
|
return $"claim {claimIndex}";
|
|
|
|
return string.IsNullOrEmpty(claim.Description) ? $"claim {claimIndex}" : claim.Description;
|
|
}
|
|
|
|
private static string FormatInfo(string groupName, int groupId, bool showDetails)
|
|
{
|
|
int memberCount = GroupMemberCount(groupId);
|
|
|
|
StringBuilder sb = new();
|
|
sb.AppendLine(
|
|
$"Claim link '{groupName}' ({memberCount} member{(memberCount == 1 ? "" : "s")}):"
|
|
);
|
|
|
|
foreach (string uid in GroupMemberUids(groupId))
|
|
{
|
|
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
|
|
|
|
if (!showDetails)
|
|
{
|
|
sb.AppendLine($" {name}");
|
|
continue;
|
|
}
|
|
|
|
IEnumerable<string> claims = ClaimLinkModSystem
|
|
.Registry.ClaimsForPlayerInGroup(uid, groupId)
|
|
.Select(i => DescribeClaim(uid, i));
|
|
sb.AppendLine($" {name}: [{string.Join(", ", claims)}]");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static TextCommandResult AdminDelete(TextCommandCallingArgs args) =>
|
|
TextCommandResult.Success("stub: claimlink admin delete");
|
|
|
|
public static TextCommandResult AdminUnlink(TextCommandCallingArgs args) =>
|
|
TextCommandResult.Success("stub: claimlink admin unlink");
|
|
|
|
public static TextCommandResult AdminKick(TextCommandCallingArgs args) =>
|
|
TextCommandResult.Success("stub: claimlink admin kick");
|
|
|
|
public static TextCommandResult AdminTransferOwnership(TextCommandCallingArgs args)
|
|
{
|
|
TextCommandResult? err = TryResolveTargetPlayer(
|
|
(PlayerUidName[])args[0],
|
|
out IPlayer target
|
|
);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveGroupArg(args, 1, out PlayerGroup group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = TryResolveClaimLink(group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireMember(target, group);
|
|
if (err != null)
|
|
return err;
|
|
|
|
err = RequireNotAlreadyOwner(group, target);
|
|
if (err != null)
|
|
return err;
|
|
|
|
return ExecuteTransferOwnership(group, target);
|
|
}
|
|
|
|
internal static void OnPlayerDisconnect(IServerPlayer player)
|
|
{
|
|
PendingActions.Remove(player.PlayerUID);
|
|
}
|
|
}
|