334 lines
14 KiB
C#
334 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using Vintagestory.API.Common;
|
|
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 into shared, derived protective territory.")
|
|
.RequiresPrivilege(Privilege.chat);
|
|
|
|
CommandSpec[] playerCommands =
|
|
{
|
|
new(new[] { "new", "n" }, "Promote a group you own into a claim link (enters pending state).",
|
|
new ICommandArgumentParser[] { p.Word("groupname") }, true, New),
|
|
|
|
new(new[] { "link", "l" }, "Link a claim you own into a claim link.",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.IntRange("claim", 0, 999) }, true, Link),
|
|
|
|
new(new[] { "confirm", "c" }, "Commit your pending claim link.",
|
|
Array.Empty<ICommandArgumentParser>(), true, Confirm),
|
|
|
|
new(new[] { "cancel" }, "Discard your pending claim link.",
|
|
Array.Empty<ICommandArgumentParser>(), true, Cancel),
|
|
|
|
new(new[] { "unlink", "ul" }, "Remove a claim you own from its claim link.",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.IntRange("claim", 0, 999) }, true, Unlink),
|
|
|
|
new(new[] { "kick" }, "Force-unlink all of a player's claims from the link (Owner/Op).",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, true, Kick),
|
|
|
|
new(new[] { "delete" }, "Delete the claim link entirely (Owner).",
|
|
new ICommandArgumentParser[] { p.Word("groupname") }, true, Delete),
|
|
|
|
new(new[] { "transferownership", "transfer", "to" }, "Transfer ownership of the claim link to another player (Owner).",
|
|
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, true, TransferOwnership),
|
|
|
|
new(new[] { "info", "i" }, "Show a claim link's linked claims, members, and territory (Owner/Op or admin).",
|
|
new ICommandArgumentParser[] { p.Word("groupname") }, true, Info),
|
|
};
|
|
|
|
foreach (var spec in playerCommands)
|
|
BuildSubCommand(root, spec);
|
|
|
|
var admin = root.BeginSubCommands("admin", "a")
|
|
.WithDescription("Admin management for any claim link (op or console).")
|
|
.RequiresPrivilege(Privilege.controlserver);
|
|
|
|
CommandSpec[] adminCommands =
|
|
{
|
|
new(new[] { "delete", "del" }, "Delete any claim link entirely (claims and group untouched).",
|
|
new ICommandArgumentParser[] { p.Word("groupname") }, false, AdminDelete),
|
|
|
|
new(new[] { "unlink" }, "Force a single claim out of its link by player and claim (claim untouched).",
|
|
new ICommandArgumentParser[] { p.OnlinePlayer("playername"), p.Word("claim") }, false, AdminUnlink),
|
|
|
|
new(new[] { "kick" }, "Force-unlink all of a player's claims from a link (claims untouched).",
|
|
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.Word("groupname"), p.OnlinePlayer("playername") }, false, AdminTransferOwnership),
|
|
|
|
new(new[] { "info", "i" }, "Show any claim link's linked claims, members, and territory.",
|
|
new ICommandArgumentParser[] { p.Word("groupname") }, false, AdminInfo),
|
|
};
|
|
|
|
foreach (var spec in adminCommands)
|
|
BuildSubCommand(admin, spec);
|
|
|
|
admin.EndSubCommand();
|
|
}
|
|
|
|
private static readonly Dictionary<string, ClaimLink> pendingLinks = new();
|
|
|
|
internal static void RemovePending(string playerUid) => pendingLinks.Remove(playerUid);
|
|
|
|
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? TryResolveClaimLink(PlayerGroup group, out ClaimLink link)
|
|
{
|
|
link = ClaimLinkModSystem.Registry.Get(group.Uid)!;
|
|
return link == null ? TextCommandResult.Error($"'{group.Name}' is not a claim link.") : null;
|
|
}
|
|
|
|
private static TextCommandResult? TryResolveClaimLinkForEdit(string playerUid, PlayerGroup group, out ClaimLink link, out bool isPending)
|
|
{
|
|
if (pendingLinks.TryGetValue(playerUid, out ClaimLink? pending) && pending.GroupId == group.Uid)
|
|
{
|
|
link = pending;
|
|
isPending = true;
|
|
return null;
|
|
}
|
|
|
|
isPending = false;
|
|
return TryResolveClaimLink(group, out link);
|
|
}
|
|
|
|
private static TextCommandResult? RequireMember(IPlayer player, PlayerGroup group)
|
|
{
|
|
return player.GetGroup(group.Uid) == null ? TextCommandResult.Error($"You are 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 the group '{group.Name}'.") : null;
|
|
}
|
|
|
|
private static TextCommandResult? RequireOpOrOwner(IPlayer player, PlayerGroup group)
|
|
{
|
|
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 groupName = (string)args[0];
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
|
|
if (err != null) return err;
|
|
|
|
err = RequireOwner(args.Caller.Player, group);
|
|
if (err != null) return err;
|
|
|
|
if (pendingLinks.ContainsKey(playerUid))
|
|
return TextCommandResult.Error("You already have a pending claim link. Confirm or cancel it first.");
|
|
|
|
if (ClaimLinkModSystem.Registry.Get(group.Uid) != null)
|
|
return TextCommandResult.Error($"'{groupName}' is already a claim link.");
|
|
|
|
pendingLinks[playerUid] = new ClaimLink { GroupId = group.Uid };
|
|
return TextCommandResult.Success($"'{groupName}' is now a pending claim link. Use /claimlink link to add claims, then confirm.");
|
|
}
|
|
|
|
public static TextCommandResult Confirm(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
if (!pendingLinks.TryGetValue(playerUid, out ClaimLink? pending))
|
|
return TextCommandResult.Error("You do not have a pending claim link.");
|
|
|
|
ClaimLinkModSystem.Registry.Add(pending);
|
|
pendingLinks.Remove(playerUid);
|
|
|
|
return TextCommandResult.Success("Claim link confirmed.");
|
|
}
|
|
|
|
public static TextCommandResult Cancel(TextCommandCallingArgs args)
|
|
{
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
if (!pendingLinks.Remove(playerUid))
|
|
return TextCommandResult.Error("You do not have a pending claim link.");
|
|
|
|
return TextCommandResult.Success("Pending claim link discarded.");
|
|
}
|
|
|
|
public static TextCommandResult Link(TextCommandCallingArgs args)
|
|
{
|
|
string groupName = (string)args[0];
|
|
int claimIndex = (int)args[1];
|
|
|
|
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
|
|
if (err != null) return err;
|
|
|
|
IPlayer player = args.Caller.Player;
|
|
string playerUid = player.PlayerUID;
|
|
|
|
err = TryResolveClaimLinkForEdit(playerUid, group, out ClaimLink link, out bool isPending);
|
|
if (err != null) return err;
|
|
|
|
err = RequireMember(player, group);
|
|
if (err != null) return err;
|
|
|
|
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _, out _))
|
|
return TextCommandResult.Error("You do not own a claim with that index.");
|
|
|
|
if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex))
|
|
return TextCommandResult.Error("That claim is already part of a claim link.");
|
|
|
|
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == playerUid);
|
|
if (member == null)
|
|
{
|
|
member = new ClaimLinkMember { OwnerPlayerUid = playerUid };
|
|
link.Members.Add(member);
|
|
}
|
|
|
|
member.LocalClaimIndices.Add(claimIndex);
|
|
if (!isPending)
|
|
ClaimLinkModSystem.Registry.Save();
|
|
|
|
return TextCommandResult.Success($"Linked claim {claimIndex} into '{groupName}'.");
|
|
}
|
|
|
|
public static TextCommandResult Unlink(TextCommandCallingArgs args)
|
|
{
|
|
string groupName = (string)args[0];
|
|
int claimIndex = (int)args[1];
|
|
|
|
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
|
|
if (err != null) return err;
|
|
|
|
string playerUid = args.Caller.Player.PlayerUID;
|
|
|
|
err = TryResolveClaimLinkForEdit(playerUid, group, out ClaimLink link, out bool isPending);
|
|
if (err != null) return err;
|
|
|
|
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == playerUid);
|
|
if (member == null || !member.LocalClaimIndices.Remove(claimIndex))
|
|
return TextCommandResult.Error($"Claim {claimIndex} is not linked into '{groupName}' by you.");
|
|
|
|
if (member.LocalClaimIndices.Count == 0)
|
|
link.Members.Remove(member);
|
|
|
|
if (!isPending)
|
|
ClaimLinkModSystem.Registry.Save();
|
|
|
|
return TextCommandResult.Success($"Unlinked claim {claimIndex} from '{groupName}'.");
|
|
}
|
|
public static TextCommandResult Kick(TextCommandCallingArgs args)
|
|
{
|
|
string groupName = (string)args[0];
|
|
IPlayer target = (IPlayer)args[1];
|
|
|
|
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
|
|
if (err != null) return err;
|
|
|
|
err = TryResolveClaimLink(group, out ClaimLink link);
|
|
if (err != null) return err;
|
|
|
|
err = RequireOpOrOwner(args.Caller.Player, group);
|
|
if (err != null) return err;
|
|
|
|
string targetUid = target.PlayerUID;
|
|
|
|
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == targetUid);
|
|
if (member == null)
|
|
return TextCommandResult.Error($"{target.PlayerName} has no claims linked into '{groupName}'.");
|
|
|
|
link.Members.Remove(member);
|
|
ClaimLinkModSystem.Registry.Save();
|
|
|
|
return TextCommandResult.Success($"Kicked all of {target.PlayerName}'s claims from '{groupName}'.");
|
|
}
|
|
public static TextCommandResult Delete(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink delete");
|
|
public static TextCommandResult TransferOwnership(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink transferownership");
|
|
public static TextCommandResult Info(TextCommandCallingArgs args)
|
|
{
|
|
string groupName = (string)args[0];
|
|
|
|
TextCommandResult? err = TryResolveGroup(groupName, out PlayerGroup group);
|
|
if (err != null) return err;
|
|
|
|
err = TryResolveClaimLink(group, out ClaimLink link);
|
|
if (err != null) return err;
|
|
|
|
err = RequireOpOrOwner(args.Caller.Player, group);
|
|
if (err != null) return err;
|
|
|
|
return TextCommandResult.Success(FormatInfo(groupName, link));
|
|
}
|
|
|
|
private static string FormatInfo(string groupName, ClaimLink link)
|
|
{
|
|
StringBuilder sb = new();
|
|
sb.AppendLine($"Claim link '{groupName}' ({link.Members.Count} member(s)):");
|
|
|
|
foreach (ClaimLinkMember member in link.Members)
|
|
{
|
|
string name = ClaimLinkModSystem.World.PlayerByUid(member.OwnerPlayerUid)?.PlayerName ?? member.OwnerPlayerUid;
|
|
sb.AppendLine($" {name}: claims [{string.Join(", ", member.LocalClaimIndices)}]");
|
|
}
|
|
|
|
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.Success("stub: claimlink admin transferownership");
|
|
public static TextCommandResult AdminInfo(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink admin info");
|
|
}
|