Compare commits

...
16 Commits
Author SHA1 Message Date
anth64 979f02e7f8 feat: open info to any player, add claimlink list, bump to 0.0.6
Modified command messages.
2026-07-12 21:19:24 +02:00
anth64 acbc45483e feat: implement claimlink delete 2026-07-12 19:54:13 +02:00
anth64 4e97e25b38 refactor: unlink no longer needs a groupname arg
A claim can only belong to one claim link at a time, so it resolves
via the new Registry.FindLinkContaining(playerUid, claimIndex).
2026-07-12 19:52:23 +02:00
anth64 1803aaaa37 fix: clear kicked player's pending action on kick 2026-07-12 19:46:08 +02:00
anth64 c035bf1a73 refactor: replace pending-link buffer with generic pending-action confirm/cancel
new/link/unlink/kick now stage a validated action and require confirm
to execute, instead of new alone gating a multi-edit draft. Bump to
0.0.5.
2026-07-12 19:43:00 +02:00
anth64 565f91408b feat: implement claimlink info, bump to 0.0.4 2026-07-12 19:27:31 +02:00
anth64 e7118e259b feat: implement claimlink kick 2026-07-12 19:23:54 +02:00
anth64 65e00e2f57 feat: stage claimlink new behind confirm/cancel pending state 2026-07-12 19:09:40 +02:00
anth64 c7e3f97a85 feat: implement claimlink unlink 2026-07-12 19:06:43 +02:00
anth64 0914d55d9a refactor: extract guard-clause helpers for group/link resolution 2026-07-12 19:05:27 +02:00
anth64 ac79d2ac94 feat: implement claimlink link 2026-07-12 19:00:10 +02:00
anth64 3b6a44a779 chore: bump version to 0.0.3 2026-07-12 18:50:20 +02:00
anth64 deafd98f40 feat: implement claimlink new 2026-07-12 18:49:54 +02:00
anth64 3b63c5b4af refactor: group member's claim indices into one list per player 2026-07-12 18:41:51 +02:00
anth64 cf17697a5e feat: add member claims to the ClaimLink model
Each ClaimLinkMember stores an owner player uid plus that player's
local claim index (resolved fresh at use, not trusted as stable) so
a claim link can reference existing vanilla claims without copying
their data. Buffer claim is intentionally not referenced here; it
resolves on demand via group ownership.
2026-07-12 18:38:09 +02:00
anth64 02c9a22bf1 feat: add ClaimLink persistence via savegame-backed registry
Adds a ClaimLink data model and ClaimLinkRegistry that loads from
and writes to the savegame's keyed data store (protobuf-net, via
WorldManager.SaveGame), so claim link state survives restarts
without touching vanilla claim data. Also silences nullable
warnings on statics that are guaranteed set by StartServerSide
before any use, and on TryGetValue out-params guarded by the if.
2026-07-12 18:31:21 +02:00
7 changed files with 416 additions and 44 deletions
+24
View File
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using ProtoBuf;
namespace ClaimLink;
[ProtoContract]
public class ClaimLinkMember
{
[ProtoMember(1)]
public string OwnerPlayerUid = "";
[ProtoMember(2)]
public List<int> LocalClaimIndices = new();
}
[ProtoContract]
public class ClaimLink
{
[ProtoMember(1)]
public int GroupId;
[ProtoMember(2)]
public List<ClaimLinkMember> Members = new();
}
+4
View File
@@ -16,6 +16,10 @@
<HintPath>$(COMMANDHOOK)/CommandHook.dll</HintPath> <HintPath>$(COMMANDHOOK)/CommandHook.dll</HintPath>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<Reference Include="protobuf-net">
<HintPath>$(VINTAGE_STORY)/Lib/protobuf-net.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+282 -25
View File
@@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Server; using Vintagestory.API.Server;
@@ -49,61 +52,64 @@ public static class ClaimLinkChatCommand
var root = api.ChatCommands.Create("claimlink") var root = api.ChatCommands.Create("claimlink")
.WithAlias("clink", "claiml", "cl") .WithAlias("clink", "claiml", "cl")
.WithDescription("Link vanilla land claims into shared, derived protective territory.") .WithDescription("Link vanilla land claims together via group.")
.RequiresPrivilege(Privilege.chat); .RequiresPrivilege(Privilege.chat);
CommandSpec[] playerCommands = CommandSpec[] playerCommands =
{ {
new(new[] { "new", "n" }, "Promote a group you own into a claim link (enters pending state).", new(new[] { "new", "n" }, "Promote a group you own to a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, true, New), new ICommandArgumentParser[] { p.Word("groupname") }, true, New),
new(new[] { "link", "l" }, "Link a claim you own into the pending or committed claim link.", new(new[] { "link", "l" }, "Link a claim you own to a claim link.",
new ICommandArgumentParser[] { p.Word("claim") }, true, Link), new ICommandArgumentParser[] { p.Word("groupname"), p.IntRange("claim", 0, 999) }, true, Link),
new(new[] { "confirm", "c" }, "Commit your pending claim link.", new(new[] { "confirm", "c" }, "Confirm pending action.",
Array.Empty<ICommandArgumentParser>(), true, Confirm), Array.Empty<ICommandArgumentParser>(), true, Confirm),
new(new[] { "cancel" }, "Discard your pending claim link.", new(new[] { "cancel" }, "Cancel pending action.",
Array.Empty<ICommandArgumentParser>(), true, Cancel), Array.Empty<ICommandArgumentParser>(), true, Cancel),
new(new[] { "unlink", "ul" }, "Remove a claim you own from its claim link.", new(new[] { "unlink", "ul" }, "Remove a claim you own from its claim link.",
new ICommandArgumentParser[] { p.Word("claim") }, true, Unlink), new ICommandArgumentParser[] { p.IntRange("claim", 0, 999) }, true, Unlink),
new(new[] { "kick" }, "Force-unlink another player's claim from the link (Owner/Op).", new(new[] { "kick" }, "Force-unlink all of a player's claims from the claim link.",
new ICommandArgumentParser[] { p.OnlinePlayer("playername"), p.Word("claim") }, true, Kick), new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, true, Kick),
new(new[] { "delete" }, "Delete the claim link entirely (Owner).", new(new[] { "delete" }, "Delete a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, true, Delete), new ICommandArgumentParser[] { p.Word("groupname") }, true, Delete),
new(new[] { "transferownership", "transfer", "to" }, "Transfer ownership of the claim link to another player (Owner).", new(new[] { "transferownership", "transfer", "to" }, "Transfer ownership of the claim link to another player.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, true, TransferOwnership), 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(new[] { "info", "i" }, "Show a claim link's members and linked claims.",
new ICommandArgumentParser[] { p.Word("groupname") }, true, Info), new ICommandArgumentParser[] { p.Word("groupname") }, true, Info),
new(new[] { "list", "ls" }, "List all claim links.",
Array.Empty<ICommandArgumentParser>(), true, List),
}; };
foreach (var spec in playerCommands) foreach (var spec in playerCommands)
BuildSubCommand(root, spec); BuildSubCommand(root, spec);
var admin = root.BeginSubCommands("admin", "a") var admin = root.BeginSubCommands("admin", "a")
.WithDescription("Admin management for any claim link (op or console).") .WithDescription("Admin commands for claim link.")
.RequiresPrivilege(Privilege.controlserver); .RequiresPrivilege(Privilege.controlserver);
CommandSpec[] adminCommands = CommandSpec[] adminCommands =
{ {
new(new[] { "delete", "del" }, "Delete any claim link entirely (claims and group untouched).", new(new[] { "delete", "del" }, "Delete a claim link.",
new ICommandArgumentParser[] { p.Word("groupname") }, false, AdminDelete), 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(new[] { "unlink" }, "Unlink a claim.",
new ICommandArgumentParser[] { p.OnlinePlayer("playername"), p.Word("claim") }, false, AdminUnlink), 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(new[] { "kick" }, "Force-unlink all of a player's claims.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, false, AdminKick), 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(new[] { "transferownership", "transfer", "to" }, "Transfer ownership of any claim link to another player.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OnlinePlayer("playername") }, false, AdminTransferOwnership), 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(new[] { "info", "i" }, "Show a claim link's members and linked claims.",
new ICommandArgumentParser[] { p.Word("groupname") }, false, AdminInfo), new ICommandArgumentParser[] { p.Word("groupname") }, false, AdminInfo),
}; };
@@ -113,15 +119,266 @@ public static class ClaimLinkChatCommand
admin.EndSubCommand(); admin.EndSubCommand();
} }
public static TextCommandResult New(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlinknew"); private static readonly Dictionary<string, Func<TextCommandResult>> pendingActions = new();
public static TextCommandResult Link(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink link");
public static TextCommandResult Confirm(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink confirm"); internal static void RemovePending(string playerUid) => pendingActions.Remove(playerUid);
public static TextCommandResult Cancel(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink cancel");
public static TextCommandResult Unlink(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink unlink"); private static TextCommandResult Stage(string playerUid, string prompt, Func<TextCommandResult> action)
public static TextCommandResult Kick(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink kick"); {
public static TextCommandResult Delete(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink delete"); pendingActions[playerUid] = action;
return TextCommandResult.Success($"{prompt} Use /claimlink confirm to proceed, or /claimlink cancel to cancel.");
}
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? 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 '{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 (ClaimLinkModSystem.Registry.Get(group.Uid) != null)
return TextCommandResult.Error($"'{groupName}' is already a claim link.");
int groupId = group.Uid;
return Stage(playerUid, $"'{groupName}' will become a claim link.", () =>
{
ClaimLinkModSystem.Registry.Add(new ClaimLink { GroupId = 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 Func<TextCommandResult>? action))
return TextCommandResult.Error("You do not have a pending action.");
pendingActions.Remove(playerUid);
return 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)
{
string groupName = (string)args[0];
int claimIndex = (int)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;
IPlayer player = args.Caller.Player;
err = RequireMember(player, group);
if (err != null) return err;
string playerUid = player.PlayerUID;
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _, 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, $"{claimDesc} will be linked into '{groupName}'.", () =>
{
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);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.");
});
}
public static TextCommandResult Unlink(TextCommandCallingArgs args)
{
int claimIndex = (int)args[0];
string playerUid = args.Caller.Player.PlayerUID;
ClaimLink? link = ClaimLinkModSystem.Registry.FindLinkContaining(playerUid, claimIndex);
if (link == null)
return TextCommandResult.Error($"Claim {claimIndex} is not linked into any claim link by you.");
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[link.GroupId].Name;
string claimDesc = DescribeClaim(playerUid, claimIndex);
return Stage(playerUid, $"{claimDesc} will be unlinked from '{groupName}'.", () =>
{
ClaimLinkMember member = link.Members.Find(m => m.OwnerPlayerUid == playerUid)!;
member.LocalClaimIndices.Remove(claimIndex);
if (member.LocalClaimIndices.Count == 0)
link.Members.Remove(member);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success($"Unlinked {claimDesc} 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;
string playerUid = args.Caller.Player.PlayerUID;
ClaimLinkMember? member = link.Members.Find(m => m.OwnerPlayerUid == targetUid);
if (member == null)
return TextCommandResult.Error($"{target.PlayerName} has no claims linked in '{groupName}'.");
return Stage(playerUid, $"All of {target.PlayerName}'s claims will be unlinked from '{groupName}'.", () =>
{
link.Members.Remove(member);
RemovePending(targetUid);
ClaimLinkModSystem.Registry.Save();
return TextCommandResult.Success($"Unlinked all claims of {target.PlayerName} from '{groupName}'.");
});
}
public static TextCommandResult Delete(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 = TryResolveClaimLink(group, out ClaimLink link);
if (err != null) return err;
err = RequireOwner(args.Caller.Player, group);
if (err != null) return err;
int groupId = group.Uid;
return Stage(playerUid, $"'{groupName}' will be deleted as a claim link.", () =>
{
foreach (ClaimLinkMember member in link.Members)
RemovePending(member.OwnerPlayerUid);
ClaimLinkModSystem.Registry.Remove(groupId);
return TextCommandResult.Success($"'{groupName}' is no longer a claim link.");
});
}
public static TextCommandResult TransferOwnership(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink transferownership"); public static TextCommandResult TransferOwnership(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink transferownership");
public static TextCommandResult Info(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink info");
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;
return TextCommandResult.Success(FormatInfo(groupName, link));
}
public static TextCommandResult List(TextCommandCallingArgs args)
{
List<ClaimLink> links = ClaimLinkModSystem.Registry.All
.OrderByDescending(l => l.Members.Count)
.ToList();
if (links.Count == 0)
return TextCommandResult.Success("There are no claim links.");
StringBuilder sb = new();
sb.AppendLine($"Claim links ({links.Count}):");
foreach (ClaimLink link in links)
{
string groupName = ClaimLinkModSystem.Groups.PlayerGroupsById[link.GroupId].Name;
sb.AppendLine($" {groupName}: {link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}");
}
return TextCommandResult.Success(sb.ToString());
}
private static string DescribeClaim(string ownerPlayerUid, int localIndex)
{
if (!ClaimLinkModSystem.TryResolveOwnedClaim(ownerPlayerUid, localIndex, out _, out LandClaim? claim) || claim == null)
return $"claim {localIndex}";
return string.IsNullOrEmpty(claim.Description) ? $"claim {localIndex}" : claim.Description;
}
private static string FormatInfo(string groupName, ClaimLink link)
{
StringBuilder sb = new();
sb.AppendLine($"Claim link '{groupName}' ({link.Members.Count} member{(link.Members.Count == 1 ? "" : "s")}):");
foreach (ClaimLinkMember member in link.Members)
{
string name = ClaimLinkModSystem.World.PlayerByUid(member.OwnerPlayerUid)?.PlayerName ?? member.OwnerPlayerUid;
IEnumerable<string> claims = member.LocalClaimIndices.Select(i => DescribeClaim(member.OwnerPlayerUid, i));
sb.AppendLine($" {name}: claims [{string.Join(", ", claims)}]");
}
return sb.ToString();
}
public static TextCommandResult AdminDelete(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink admin delete"); 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 AdminUnlink(TextCommandCallingArgs args) => TextCommandResult.Success("stub: claimlink admin unlink");
+35 -3
View File
@@ -1,4 +1,5 @@
using CommandHook; using System.Collections.Generic;
using CommandHook;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Server; using Vintagestory.API.Server;
@@ -6,8 +7,11 @@ namespace ClaimLink;
public class ClaimLinkModSystem : ModSystem public class ClaimLinkModSystem : ModSystem
{ {
internal static ILandClaimAPI LandClaimAPI; internal static ILandClaimAPI LandClaimAPI = null!;
internal static ILogger Logger; internal static ILogger Logger = null!;
internal static ClaimLinkRegistry Registry = null!;
internal static IGroupManager Groups = null!;
internal static IWorldAccessor World = null!;
internal ClaimLinkCommandListener? cmdListener; internal ClaimLinkCommandListener? cmdListener;
public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server; public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server;
@@ -16,6 +20,9 @@ public class ClaimLinkModSystem : ModSystem
{ {
LandClaimAPI = api.World.Claims; LandClaimAPI = api.World.Claims;
Logger = api.Logger; Logger = api.Logger;
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
Groups = api.Groups;
World = api.World;
cmdListener = new ClaimLinkCommandListener(); cmdListener = new ClaimLinkCommandListener();
CommandHookModSystem.Register(cmdListener); CommandHookModSystem.Register(cmdListener);
@@ -29,4 +36,29 @@ public class ClaimLinkModSystem : ModSystem
if (cmdListener != null) if (cmdListener != null)
CommandHookModSystem.Unregister(cmdListener); CommandHookModSystem.Unregister(cmdListener);
} }
internal static bool TryResolveOwnedClaim(string ownerPlayerUid, int localIndex, out int globalIndex, out LandClaim? claim)
{
globalIndex = -1;
claim = null;
List<LandClaim> claims = LandClaimAPI.All;
int count = 0;
for (int i = 0; i < claims.Count; ++i)
{
if (claims[i].OwnedByPlayerUid != ownerPlayerUid)
continue;
if (count == localIndex)
{
globalIndex = i;
claim = claims[i];
return true;
}
count++;
}
return false;
}
} }
+65
View File
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using ProtoBuf;
using Vintagestory.API.Server;
namespace ClaimLink;
[ProtoContract]
public class ClaimLinkData
{
[ProtoMember(1)]
public List<ClaimLink> Links = new();
}
public class ClaimLinkRegistry
{
private const string SaveKey = "claimlink:links";
private readonly ISaveGame saveGame;
private readonly Dictionary<int, ClaimLink> byGroupId = new();
public ClaimLinkRegistry(ISaveGame saveGame)
{
this.saveGame = saveGame;
ClaimLinkData? data = saveGame.GetData<ClaimLinkData?>(SaveKey, null);
if (data == null)
return;
foreach (ClaimLink link in data.Links)
byGroupId[link.GroupId] = link;
}
public ClaimLink? Get(int groupId) => byGroupId.TryGetValue(groupId, out ClaimLink? link) ? link : null;
public IReadOnlyCollection<ClaimLink> All => byGroupId.Values;
public void Add(ClaimLink link)
{
byGroupId[link.GroupId] = link;
Save();
}
public void Remove(int groupId)
{
byGroupId.Remove(groupId);
Save();
}
public bool IsClaimLinked(string ownerPlayerUid, int localIndex) => FindLinkContaining(ownerPlayerUid, localIndex) != null;
public ClaimLink? FindLinkContaining(string ownerPlayerUid, int localIndex)
{
foreach (ClaimLink link in byGroupId.Values)
foreach (ClaimLinkMember member in link.Members)
if (member.OwnerPlayerUid == ownerPlayerUid && member.LocalClaimIndices.Contains(localIndex))
return link;
return null;
}
public void Save()
{
saveGame.StoreData(SaveKey, new ClaimLinkData { Links = new List<ClaimLink>(byGroupId.Values) });
}
}
+5 -15
View File
@@ -55,7 +55,7 @@ public class ClaimLinkCommandListener : ICommandHookListener
{ {
string word = rawArgs.PeekWord(); string word = rawArgs.PeekWord();
if (topLevel.TryGetValue(word, out SubHandler handler)) if (topLevel.TryGetValue(word, out SubHandler? handler))
{ {
rawArgs.PopWord(); rawArgs.PopWord();
handler(args.Caller, rawArgs); handler(args.Caller, rawArgs);
@@ -77,7 +77,7 @@ public class ClaimLinkCommandListener : ICommandHookListener
{ {
string word = args.PeekWord(); string word = args.PeekWord();
if (claimSub.TryGetValue(word, out SubHandler handler)) if (claimSub.TryGetValue(word, out SubHandler? handler))
{ {
args.PopWord(); args.PopWord();
handler(caller, args); handler(caller, args);
@@ -93,21 +93,10 @@ public class ClaimLinkCommandListener : ICommandHookListener
if (claimIndex == null || claimIndex < 0 || claimIndex > 999) if (claimIndex == null || claimIndex < 0 || claimIndex > 999)
return; return;
List<LandClaim> claims = ClaimLinkModSystem.LandClaimAPI.All, ownedClaims = new List<LandClaim>(); if (!ClaimLinkModSystem.TryResolveOwnedClaim(caller.Player.PlayerUID, (int)claimIndex, out int globalIndex, out _))
List<int> globalIndex = new List<int>();
for (int i = 0; i < claims.Count; ++i)
{
if (claims[i].OwnedByPlayerUid != caller.Player.PlayerUID)
continue;
globalIndex.Add(i);
ownedClaims.Add(claims[i]);
}
if (claimIndex >= ownedClaims.Count)
return; return;
pendingLoads[caller.Player.PlayerUID] = globalIndex[(int)claimIndex]; pendingLoads[caller.Player.PlayerUID] = globalIndex;
} }
private static void ClaimCancel(Caller caller, CmdArgs args) private static void ClaimCancel(Caller caller, CmdArgs args)
@@ -142,5 +131,6 @@ public class ClaimLinkCommandListener : ICommandHookListener
internal static void OnPlayerDisconnect(IServerPlayer player) internal static void OnPlayerDisconnect(IServerPlayer player)
{ {
pendingLoads.Remove(player.PlayerUID); pendingLoads.Remove(player.PlayerUID);
ClaimLinkChatCommand.RemovePending(player.PlayerUID);
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
"anth64" "anth64"
], ],
"description": "Link claims together with groups.", "description": "Link claims together with groups.",
"version": "0.0.2", "version": "0.0.6",
"dependencies": { "dependencies": {
"game": "1.22.3", "game": "1.22.3",
"commandhook": "2.1.0" "commandhook": "2.1.0"