Compare commits
31
Commits
32dbd4ad91
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
636a7dee98 | ||
|
|
1e4400555c | ||
|
|
0dfcc5396a | ||
|
|
c110ee2604 | ||
|
|
baf3ccbd93 | ||
|
|
3cf6db1d2a | ||
|
|
12219e6ef5 | ||
|
|
0e0e4e0f74 | ||
|
|
154e7b7848 | ||
|
|
9e37ec11cf | ||
|
|
4d98a115ed | ||
|
|
99238c4744 | ||
|
|
1c079b7e44 | ||
|
|
b7ca9fa53b | ||
|
|
c55787ce40 | ||
|
|
b10d3a0f55 | ||
|
|
b5ceba442a | ||
|
|
d248b772dc | ||
|
|
c8fc86069b | ||
|
|
2e4cc25b20 | ||
|
|
fc7e601f5b | ||
|
|
a2e4a68f9e | ||
|
|
741d283f38 | ||
|
|
9fcf6f1da5 | ||
|
|
f699cd6d54 | ||
|
|
94b6723456 | ||
|
|
de2026d239 | ||
|
|
fffc046126 | ||
|
|
eaa79f8f52 | ||
|
|
1c0a5f88a1 | ||
|
|
d4ef3eba06 |
+13
-10
@@ -4,21 +4,24 @@ using ProtoBuf;
|
||||
namespace ClaimLink;
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkMember
|
||||
public class ClaimLinkSaveData
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public List<int> Links = new();
|
||||
|
||||
[ProtoMember(2)]
|
||||
public List<ClaimLinkEntrySaveData> Entries = new();
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkEntrySaveData
|
||||
{
|
||||
[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();
|
||||
[ProtoMember(3)]
|
||||
public List<int> ClaimIndicies = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.MathTools;
|
||||
|
||||
namespace ClaimLink;
|
||||
|
||||
internal static class ClaimLinkBuffer
|
||||
{
|
||||
internal static List<Cuboidi> GetBuffer(int groupId)
|
||||
{
|
||||
List<Cuboidi> cuboids = new();
|
||||
|
||||
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
|
||||
{
|
||||
foreach (
|
||||
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(uid, groupId)
|
||||
)
|
||||
{
|
||||
if (
|
||||
!ClaimLinkModSystem.TryResolveOwnedClaim(uid, claimIndex, out LandClaim? claim)
|
||||
|| claim == null
|
||||
)
|
||||
continue;
|
||||
|
||||
foreach (Cuboidi area in claim.Areas)
|
||||
cuboids.Add(GrowOne(area));
|
||||
}
|
||||
}
|
||||
|
||||
return cuboids;
|
||||
}
|
||||
|
||||
internal static bool TryFindOverlappingClaimLink(
|
||||
List<Cuboidi> areas,
|
||||
ISet<int>? excludeGroupIds,
|
||||
out int violatingGroupId
|
||||
)
|
||||
{
|
||||
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
||||
{
|
||||
if (excludeGroupIds != null && excludeGroupIds.Contains(groupId))
|
||||
continue;
|
||||
|
||||
foreach (Cuboidi bufferCuboid in GetBuffer(groupId))
|
||||
foreach (Cuboidi area in areas)
|
||||
if (area.Intersects(bufferCuboid))
|
||||
{
|
||||
violatingGroupId = groupId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
violatingGroupId = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryFindOverlappingClaim(
|
||||
List<Cuboidi> areas,
|
||||
string excludeOwnerUid,
|
||||
ISet<int>? excludeGroupIds,
|
||||
out LandClaim? violatingClaim
|
||||
)
|
||||
{
|
||||
HashSet<LandClaim> excludedClaims = new();
|
||||
if (excludeGroupIds != null)
|
||||
foreach (int groupId in excludeGroupIds)
|
||||
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
|
||||
foreach (
|
||||
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(
|
||||
uid,
|
||||
groupId
|
||||
)
|
||||
)
|
||||
if (
|
||||
ClaimLinkModSystem.TryResolveOwnedClaim(
|
||||
uid,
|
||||
claimIndex,
|
||||
out LandClaim? excludedClaim
|
||||
)
|
||||
&& excludedClaim != null
|
||||
)
|
||||
excludedClaims.Add(excludedClaim);
|
||||
|
||||
foreach (LandClaim claim in ClaimLinkModSystem.LandClaimAPI.All)
|
||||
{
|
||||
if (claim.OwnedByPlayerUid == excludeOwnerUid)
|
||||
continue;
|
||||
|
||||
if (excludedClaims.Contains(claim))
|
||||
continue;
|
||||
|
||||
foreach (Cuboidi claimArea in claim.Areas)
|
||||
foreach (Cuboidi area in areas)
|
||||
if (area.Intersects(claimArea))
|
||||
{
|
||||
violatingClaim = claim;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
violatingClaim = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static ISet<int> MemberClaimLinkIds(string uid)
|
||||
{
|
||||
HashSet<int> groupIds = new();
|
||||
var memberships = ClaimLinkModSystem.PlayerData.GetPlayerDataByUid(uid)?.PlayerGroupMemberships;
|
||||
if (memberships == null)
|
||||
return groupIds;
|
||||
|
||||
foreach (int groupId in memberships.Keys)
|
||||
if (ClaimLinkModSystem.Registry.Exists(groupId))
|
||||
groupIds.Add(groupId);
|
||||
|
||||
return groupIds;
|
||||
}
|
||||
|
||||
internal static List<Cuboidi> GrowAreas(List<Cuboidi> areas)
|
||||
{
|
||||
List<Cuboidi> grown = new();
|
||||
foreach (Cuboidi area in areas)
|
||||
grown.Add(GrowOne(area));
|
||||
return grown;
|
||||
}
|
||||
|
||||
private static Cuboidi GrowOne(Cuboidi area)
|
||||
{
|
||||
Cuboidi normalized = new Cuboidi(
|
||||
area.MinX,
|
||||
area.MinY,
|
||||
area.MinZ,
|
||||
area.MaxX,
|
||||
area.MaxY,
|
||||
area.MaxZ
|
||||
);
|
||||
int bufferSize = ClaimLinkModSystem.Config.BufferSize;
|
||||
return normalized.GrowBy(bufferSize, bufferSize, bufferSize);
|
||||
}
|
||||
}
|
||||
+670
-198
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
namespace ClaimLink;
|
||||
|
||||
internal class ClaimLinkConfig
|
||||
{
|
||||
public int BufferSize = 16;
|
||||
}
|
||||
@@ -12,60 +12,94 @@ public class ClaimLinkModSystem : ModSystem
|
||||
internal static ClaimLinkRegistry Registry = null!;
|
||||
internal static IGroupManager Groups = null!;
|
||||
internal static IWorldAccessor World = null!;
|
||||
internal ClaimLinkCommandListener? cmdListener;
|
||||
internal static IPlayerDataManager PlayerData = null!;
|
||||
internal static ClaimLinkConfig Config = null!;
|
||||
internal CommandListener? CmdListener;
|
||||
internal GroupCommandListener? GroupCmdListener;
|
||||
|
||||
public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server;
|
||||
|
||||
public override void StartServerSide(ICoreServerAPI api)
|
||||
{
|
||||
LandClaimAPI = api.World.Claims;
|
||||
Logger = api.Logger;
|
||||
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
|
||||
Groups = api.Groups;
|
||||
World = api.World;
|
||||
Groups = api.Groups;
|
||||
Logger = api.Logger;
|
||||
PlayerData = api.PlayerData;
|
||||
Config = api.LoadModConfig<ClaimLinkConfig>("claimlink.json") ?? new ClaimLinkConfig();
|
||||
api.StoreModConfig(Config, "claimlink.json");
|
||||
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
|
||||
|
||||
cmdListener = new ClaimLinkCommandListener();
|
||||
CommandHookModSystem.Register(cmdListener);
|
||||
CmdListener = new CommandListener();
|
||||
CommandHookModSystem.Register(CmdListener);
|
||||
GroupCmdListener = new GroupCommandListener();
|
||||
CommandHookModSystem.Register(GroupCmdListener);
|
||||
ClaimLinkChatCommand.Register(api);
|
||||
ClaimLinkVisualizerNetwork.Start(api);
|
||||
|
||||
api.Event.PlayerDisconnect += ClaimLinkCommandListener.OnPlayerDisconnect;
|
||||
|
||||
// TODO remove debug logging once transfer ownership persistence is confirmed tested
|
||||
foreach (ClaimLink link in Registry.All)
|
||||
{
|
||||
PlayerGroup group = Groups.PlayerGroupsById[link.GroupId];
|
||||
Logger.Notification($"claimlink: startup state '{group.Name}' (uid {group.Uid}) OwnerUID={group.OwnerUID}");
|
||||
}
|
||||
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
|
||||
api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect;
|
||||
api.Event.PlayerDisconnect += ClaimLinkChatCommand.OnPlayerDisconnect;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (cmdListener != null)
|
||||
CommandHookModSystem.Unregister(cmdListener);
|
||||
if (CmdListener != null)
|
||||
CommandHookModSystem.Unregister(CmdListener);
|
||||
if (GroupCmdListener != null)
|
||||
CommandHookModSystem.Unregister(GroupCmdListener);
|
||||
}
|
||||
|
||||
internal static bool TryResolveOwnedClaim(string ownerPlayerUid, int localIndex, out int globalIndex, out LandClaim? claim)
|
||||
private static IEnumerable<(int claimIndex, LandClaim claim)> EnumerateOwnedClaims(
|
||||
string ownerPlayerUid
|
||||
)
|
||||
{
|
||||
globalIndex = -1;
|
||||
claim = null;
|
||||
|
||||
List<LandClaim> claims = LandClaimAPI.All;
|
||||
int count = 0;
|
||||
for (int i = 0; i < claims.Count; ++i)
|
||||
int index = 0;
|
||||
foreach (LandClaim c in LandClaimAPI.All)
|
||||
{
|
||||
if (claims[i].OwnedByPlayerUid != ownerPlayerUid)
|
||||
if (c.OwnedByPlayerUid != ownerPlayerUid)
|
||||
continue;
|
||||
|
||||
if (count == localIndex)
|
||||
yield return (index, c);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryResolveOwnedClaim(
|
||||
string ownerPlayerUid,
|
||||
int claimIndex,
|
||||
out LandClaim? claim
|
||||
)
|
||||
{
|
||||
globalIndex = i;
|
||||
claim = claims[i];
|
||||
foreach ((int index, LandClaim c) in EnumerateOwnedClaims(ownerPlayerUid))
|
||||
{
|
||||
if (index == claimIndex)
|
||||
{
|
||||
claim = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
claim = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryResolveClaimIndex(
|
||||
string ownerPlayerUid,
|
||||
LandClaim target,
|
||||
out int claimIndex
|
||||
)
|
||||
{
|
||||
foreach ((int index, LandClaim c) in EnumerateOwnedClaims(ownerPlayerUid))
|
||||
{
|
||||
if (ReferenceEquals(c, target))
|
||||
{
|
||||
claimIndex = index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
claimIndex = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+162
-26
@@ -1,65 +1,201 @@
|
||||
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 const string SaveKey = "claimlink:claimlinkdata";
|
||||
|
||||
private readonly ISaveGame saveGame;
|
||||
private readonly Dictionary<int, ClaimLink> byGroupId = new();
|
||||
|
||||
private readonly List<int> claimLinks = new();
|
||||
private readonly Dictionary<string, Dictionary<int, List<int>>> entries = new();
|
||||
|
||||
public ClaimLinkRegistry(ISaveGame saveGame)
|
||||
{
|
||||
this.saveGame = saveGame;
|
||||
|
||||
ClaimLinkData? data = saveGame.GetData<ClaimLinkData?>(SaveKey, null);
|
||||
ClaimLinkSaveData? data = saveGame.GetData<ClaimLinkSaveData?>(SaveKey, null);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
foreach (ClaimLink link in data.Links)
|
||||
byGroupId[link.GroupId] = link;
|
||||
claimLinks.AddRange(data.Links);
|
||||
|
||||
foreach (var entry in data.Entries)
|
||||
{
|
||||
if (!entries.ContainsKey(entry.OwnerPlayerUid))
|
||||
entries[entry.OwnerPlayerUid] = new Dictionary<int, List<int>>();
|
||||
|
||||
entries[entry.OwnerPlayerUid][entry.GroupId] = new(entry.ClaimIndicies);
|
||||
}
|
||||
}
|
||||
|
||||
public ClaimLink? Get(int groupId) => byGroupId.TryGetValue(groupId, out ClaimLink? link) ? link : null;
|
||||
public IReadOnlyList<int> All => claimLinks;
|
||||
|
||||
public IReadOnlyCollection<ClaimLink> All => byGroupId.Values;
|
||||
public bool Exists(int groupId) => claimLinks.Contains(groupId);
|
||||
|
||||
public void Add(ClaimLink link)
|
||||
public void Add(int groupId)
|
||||
{
|
||||
byGroupId[link.GroupId] = link;
|
||||
claimLinks.Add(groupId);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Remove(int groupId)
|
||||
{
|
||||
byGroupId.Remove(groupId);
|
||||
claimLinks.Remove(groupId);
|
||||
foreach (var groups in entries.Values)
|
||||
groups.Remove(groupId);
|
||||
Prune();
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool IsClaimLinked(string ownerPlayerUid, int localIndex) => FindLinkContaining(ownerPlayerUid, localIndex) != null;
|
||||
public bool IsClaimLinked(string uid, int claimIndex) =>
|
||||
FindGroupContaining(uid, claimIndex) != null;
|
||||
|
||||
public ClaimLink? FindLinkContaining(string ownerPlayerUid, int localIndex)
|
||||
public int? FindGroupContaining(string uid, int claimIndex)
|
||||
{
|
||||
foreach (ClaimLink link in byGroupId.Values)
|
||||
foreach (ClaimLinkMember member in link.Members)
|
||||
if (member.OwnerPlayerUid == ownerPlayerUid && member.LocalClaimIndices.Contains(localIndex))
|
||||
return link;
|
||||
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
return null;
|
||||
foreach (var (groupId, claims) in groups)
|
||||
if (claims.Contains(claimIndex))
|
||||
return groupId;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool AddEntry(string uid, int groupId, int claimIndex)
|
||||
{
|
||||
if (CommandListener.PendingLoads.ContainsKey(uid))
|
||||
return false;
|
||||
if (!Exists(groupId))
|
||||
return false;
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
entries[uid] = groups = new();
|
||||
if (!groups.TryGetValue(groupId, out var claims))
|
||||
groups[groupId] = claims = new();
|
||||
claims.Add(claimIndex);
|
||||
Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveEntry(string uid, int groupId, int claimIndex)
|
||||
{
|
||||
if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims))
|
||||
claims.Remove(claimIndex);
|
||||
Prune();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void ClaimRemoved(string uid, int removedIndex)
|
||||
{
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
return;
|
||||
|
||||
foreach (var claims in groups.Values)
|
||||
{
|
||||
claims.Remove(removedIndex);
|
||||
for (int i = 0; i < claims.Count; i++)
|
||||
if (claims[i] > removedIndex)
|
||||
claims[i]--;
|
||||
}
|
||||
|
||||
Prune();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void ClaimSaved(string uid, int claimIndex)
|
||||
{
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
return;
|
||||
|
||||
int count = 0;
|
||||
foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All)
|
||||
if (c.OwnedByPlayerUid == uid)
|
||||
count++;
|
||||
int newIndex = count - 1;
|
||||
|
||||
foreach (var claims in groups.Values)
|
||||
{
|
||||
int i = claims.IndexOf(claimIndex);
|
||||
|
||||
for (int j = 0; j < claims.Count; j++)
|
||||
if (j != i && claims[j] > claimIndex)
|
||||
claims[j]--;
|
||||
|
||||
if (i >= 0)
|
||||
claims[i] = newIndex;
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool HasAnyEntry(string uid, int groupId) =>
|
||||
entries.TryGetValue(uid, out var groups)
|
||||
&& groups.TryGetValue(groupId, out var claims)
|
||||
&& claims.Count > 0;
|
||||
|
||||
public void RemoveAllForPlayerInGroup(string uid, int groupId)
|
||||
{
|
||||
if (entries.TryGetValue(uid, out var groups))
|
||||
groups.Remove(groupId);
|
||||
Prune();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void ClearAllForPlayer(string uid)
|
||||
{
|
||||
entries.Remove(uid);
|
||||
Save();
|
||||
}
|
||||
|
||||
public IEnumerable<string> MemberUidsForGroup(int groupId)
|
||||
{
|
||||
foreach (var (uid, groups) in entries)
|
||||
if (groups.ContainsKey(groupId))
|
||||
yield return uid;
|
||||
}
|
||||
|
||||
public int MemberCountForGroup(int groupId)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var uid in MemberUidsForGroup(groupId))
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
public IReadOnlyList<int> ClaimsForPlayerInGroup(string uid, int groupId) =>
|
||||
entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims)
|
||||
? claims
|
||||
: System.Array.Empty<int>();
|
||||
|
||||
private void Prune()
|
||||
{
|
||||
List<string>? empty = null;
|
||||
foreach (var (uid, groups) in entries)
|
||||
if (groups.Count == 0)
|
||||
(empty ??= new()).Add(uid);
|
||||
if (empty != null)
|
||||
foreach (var uid in empty)
|
||||
entries.Remove(uid);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
saveGame.StoreData(SaveKey, new ClaimLinkData { Links = new List<ClaimLink>(byGroupId.Values) });
|
||||
var entryList = new List<ClaimLinkEntrySaveData>();
|
||||
foreach (var (uid, groups) in entries)
|
||||
foreach (var (groupId, claims) in groups)
|
||||
entryList.Add(
|
||||
new ClaimLinkEntrySaveData
|
||||
{
|
||||
OwnerPlayerUid = uid,
|
||||
GroupId = groupId,
|
||||
ClaimIndicies = new(claims),
|
||||
}
|
||||
);
|
||||
|
||||
saveGame.StoreData(
|
||||
SaveKey,
|
||||
new ClaimLinkSaveData { Links = claimLinks, Entries = entryList }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Generic;
|
||||
using ProtoBuf;
|
||||
using Vintagestory.API.MathTools;
|
||||
using Vintagestory.API.Server;
|
||||
|
||||
namespace ClaimLink;
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferRequest { }
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferCuboidDto
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int MinX;
|
||||
|
||||
[ProtoMember(2)]
|
||||
public int MinY;
|
||||
|
||||
[ProtoMember(3)]
|
||||
public int MinZ;
|
||||
|
||||
[ProtoMember(4)]
|
||||
public int MaxX;
|
||||
|
||||
[ProtoMember(5)]
|
||||
public int MaxY;
|
||||
|
||||
[ProtoMember(6)]
|
||||
public int MaxZ;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferEntryDto
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int GroupId;
|
||||
|
||||
[ProtoMember(2)]
|
||||
public List<ClaimLinkBufferCuboidDto> Cuboids = new();
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferResponse
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public List<ClaimLinkBufferEntryDto> Entries = new();
|
||||
}
|
||||
|
||||
internal static class ClaimLinkVisualizerNetwork
|
||||
{
|
||||
internal const string ChannelName = "claimlinkvisualizer";
|
||||
|
||||
internal static void Start(ICoreServerAPI api)
|
||||
{
|
||||
api.Network
|
||||
.RegisterChannel(ChannelName)
|
||||
.RegisterMessageType<ClaimLinkBufferRequest>()
|
||||
.RegisterMessageType<ClaimLinkBufferResponse>()
|
||||
.SetMessageHandler<ClaimLinkBufferRequest>((fromPlayer, _) =>
|
||||
SendBuffers(api, fromPlayer)
|
||||
);
|
||||
}
|
||||
|
||||
private static void SendBuffers(ICoreServerAPI api, IServerPlayer toPlayer)
|
||||
{
|
||||
ClaimLinkBufferResponse response = new();
|
||||
|
||||
BlockPos playerPos = toPlayer.Entity.Pos.AsBlockPos;
|
||||
int radius = toPlayer.WorldData.LastApprovedViewDistance;
|
||||
|
||||
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
||||
{
|
||||
ClaimLinkBufferEntryDto entry = new() { GroupId = groupId };
|
||||
foreach (Cuboidi cuboid in ClaimLinkBuffer.GetBuffer(groupId))
|
||||
{
|
||||
if (!IsNearby(cuboid, playerPos, radius))
|
||||
continue;
|
||||
|
||||
entry.Cuboids.Add(
|
||||
new ClaimLinkBufferCuboidDto
|
||||
{
|
||||
MinX = cuboid.MinX,
|
||||
MinY = cuboid.MinY,
|
||||
MinZ = cuboid.MinZ,
|
||||
MaxX = cuboid.MaxX,
|
||||
MaxY = cuboid.MaxY,
|
||||
MaxZ = cuboid.MaxZ,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.Cuboids.Count > 0)
|
||||
response.Entries.Add(entry);
|
||||
}
|
||||
|
||||
api.Network.GetChannel(ChannelName).SendPacket(response, toPlayer);
|
||||
}
|
||||
|
||||
private static bool IsNearby(Cuboidi cuboid, BlockPos playerPos, int radius) =>
|
||||
cuboid.MinX <= playerPos.X + radius
|
||||
&& cuboid.MaxX >= playerPos.X - radius
|
||||
&& cuboid.MinY <= playerPos.Y + radius
|
||||
&& cuboid.MaxY >= playerPos.Y - radius
|
||||
&& cuboid.MinZ <= playerPos.Z + radius
|
||||
&& cuboid.MaxZ >= playerPos.Z - radius;
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
using System.Collections.Generic;
|
||||
using CommandHook;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.Config;
|
||||
using Vintagestory.API.MathTools;
|
||||
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>();
|
||||
internal static Dictionary<string, List<Cuboidi>?> PendingSaveChecks =
|
||||
new Dictionary<string, List<Cuboidi>?>();
|
||||
|
||||
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,
|
||||
["new"] = ClaimNew,
|
||||
["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)
|
||||
{
|
||||
if (args.Caller.Player == null)
|
||||
return;
|
||||
|
||||
string uid = args.Caller.Player.PlayerUID;
|
||||
if (!PendingSaveChecks.Remove(uid, out List<Cuboidi>? snapshot))
|
||||
return;
|
||||
|
||||
if (result.Status != EnumCommandStatus.Success)
|
||||
return;
|
||||
|
||||
List<LandClaim> allClaims = ClaimLinkModSystem.LandClaimAPI.All;
|
||||
if (allClaims.Count == 0)
|
||||
return;
|
||||
|
||||
LandClaim savedClaim = allClaims[allClaims.Count - 1];
|
||||
if (savedClaim.OwnedByPlayerUid != uid)
|
||||
return;
|
||||
|
||||
int? linkedGroupId = null;
|
||||
if (ClaimLinkModSystem.TryResolveClaimIndex(uid, savedClaim, out int savedIndex))
|
||||
linkedGroupId = ClaimLinkModSystem.Registry.FindGroupContaining(uid, savedIndex);
|
||||
|
||||
bool isLinked = linkedGroupId != null;
|
||||
List<Cuboidi> checkAreas = isLinked
|
||||
? ClaimLinkBuffer.GrowAreas(savedClaim.Areas)
|
||||
: savedClaim.Areas;
|
||||
|
||||
ISet<int> excludeGroupIds = isLinked
|
||||
? new HashSet<int> { linkedGroupId!.Value }
|
||||
: ClaimLinkBuffer.MemberClaimLinkIds(uid);
|
||||
|
||||
LandClaim? violatingClaim = null;
|
||||
bool violatesClaimLink = ClaimLinkBuffer.TryFindOverlappingClaimLink(
|
||||
checkAreas,
|
||||
excludeGroupIds,
|
||||
out int violatingGroupId
|
||||
);
|
||||
bool violatesClaim =
|
||||
isLinked
|
||||
&& !violatesClaimLink
|
||||
&& ClaimLinkBuffer.TryFindOverlappingClaim(
|
||||
checkAreas,
|
||||
uid,
|
||||
excludeGroupIds,
|
||||
out violatingClaim
|
||||
);
|
||||
|
||||
if (!violatesClaimLink && !violatesClaim)
|
||||
return;
|
||||
|
||||
ClaimLinkModSystem.LandClaimAPI.Remove(savedClaim);
|
||||
|
||||
string reason = violatesClaimLink
|
||||
? (
|
||||
ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
|
||||
violatingGroupId,
|
||||
out PlayerGroup? violatingGroup
|
||||
)
|
||||
? $"it overlaps the protected buffer of '{violatingGroup!.Name}'"
|
||||
: "it overlaps the protected buffer of another claim link"
|
||||
)
|
||||
: $"its buffer would overlap the claim owned by {violatingClaim!.LastKnownOwnerName}";
|
||||
|
||||
if (snapshot != null)
|
||||
{
|
||||
savedClaim.Areas.Clear();
|
||||
savedClaim.Areas.AddRange(snapshot);
|
||||
ClaimLinkModSystem.LandClaimAPI.Add(savedClaim);
|
||||
NotifyPlayer(args.Caller.Player, $"Your claim growth was reverted: {reason}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyPlayer(args.Caller.Player, $"Your claim was rejected: {reason}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void NotifyPlayer(IPlayer player, string message)
|
||||
{
|
||||
if (player is IServerPlayer sp)
|
||||
sp.SendMessage(GlobalConstants.GeneralChatGroup, message, EnumChatType.Notification);
|
||||
}
|
||||
|
||||
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;
|
||||
PendingSaveChecks[uid] = null;
|
||||
|
||||
if (!PendingLoads.TryGetValue(uid, out int pendingIndex))
|
||||
return;
|
||||
|
||||
if (
|
||||
ClaimLinkModSystem.TryResolveOwnedClaim(uid, pendingIndex, out LandClaim? existingClaim)
|
||||
&& existingClaim != null
|
||||
)
|
||||
{
|
||||
List<Cuboidi> snapshot = new();
|
||||
foreach (Cuboidi area in existingClaim.Areas)
|
||||
snapshot.Add(area.Clone());
|
||||
PendingSaveChecks[uid] = snapshot;
|
||||
}
|
||||
|
||||
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 ClaimNew(Caller caller, CmdArgs args)
|
||||
{
|
||||
if (caller.Player == null)
|
||||
return;
|
||||
|
||||
PendingLoads.Remove(caller.Player.PlayerUID);
|
||||
}
|
||||
|
||||
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);
|
||||
PendingSaveChecks.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);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using Vintagestory.API.Common;
|
||||
using System.Collections.Generic;
|
||||
using CommandHook;
|
||||
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);
|
||||
|
||||
private 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,
|
||||
["info"] = Info,
|
||||
["list"] = List,
|
||||
["adminfree"] = AdminFree,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, SubHandler> claimSub = new()
|
||||
{
|
||||
["load"] = ClaimLoad,
|
||||
["new"] = ClaimNew,
|
||||
["grant"] = ClaimGrant,
|
||||
["revoke"] = ClaimRevoke,
|
||||
["grantgroup"] = ClaimGrantGroup,
|
||||
["revokegroup"] = ClaimRevokeGroup,
|
||||
["start"] = ClaimStart,
|
||||
["end"] = ClaimEnd,
|
||||
["grow"] = ClaimGrow,
|
||||
["shrink"] = ClaimShrink,
|
||||
["add"] = ClaimAdd,
|
||||
["allowuseeveryone"] = ClaimAllowUseEveryone,
|
||||
["plevel"] = ClaimPLevel,
|
||||
["fullheight"] = ClaimFullHeight,
|
||||
["save"] = ClaimSave,
|
||||
["cancel"] = ClaimCancel,
|
||||
["download"] = ClaimDownload,
|
||||
};
|
||||
|
||||
private TextCommandResult? Before(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();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||
{
|
||||
}
|
||||
|
||||
private static void Claim(Caller caller, CmdArgs args)
|
||||
{
|
||||
string word = args.PeekWord();
|
||||
|
||||
if (claimSub.TryGetValue(word, out SubHandler? handler))
|
||||
{
|
||||
args.PopWord();
|
||||
handler(caller, args);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClaimLoad(Caller caller, CmdArgs args)
|
||||
{
|
||||
if (caller.Player == null)
|
||||
return;
|
||||
|
||||
int? claimIndex = args.PopInt();
|
||||
if (claimIndex == null || claimIndex < 0 || claimIndex > 999)
|
||||
return;
|
||||
|
||||
if (!ClaimLinkModSystem.TryResolveOwnedClaim(caller.Player.PlayerUID, (int)claimIndex, out int globalIndex, out _))
|
||||
return;
|
||||
|
||||
pendingLoads[caller.Player.PlayerUID] = globalIndex;
|
||||
}
|
||||
|
||||
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) { }
|
||||
private static void Info(Caller caller, CmdArgs args) { }
|
||||
private static void List(Caller caller, CmdArgs args) { }
|
||||
private static void AdminFree(Caller caller, CmdArgs args) { }
|
||||
|
||||
private static void ClaimNew(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimGrant(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimRevoke(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimGrantGroup(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimRevokeGroup(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimStart(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimEnd(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimGrow(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimShrink(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimAdd(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimAllowUseEveryone(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimPLevel(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimFullHeight(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimSave(Caller caller, CmdArgs args) { }
|
||||
private static void ClaimDownload(Caller caller, CmdArgs args) { }
|
||||
|
||||
internal static void OnPlayerDisconnect(IServerPlayer player)
|
||||
{
|
||||
pendingLoads.Remove(player.PlayerUID);
|
||||
ClaimLinkChatCommand.RemovePending(player.PlayerUID);
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@
|
||||
"anth64"
|
||||
],
|
||||
"description": "Link claims together with groups.",
|
||||
"version": "0.0.6",
|
||||
"version": "0.3.0",
|
||||
"dependencies": {
|
||||
"game": "1.22.3",
|
||||
"game": "1.22.5",
|
||||
"commandhook": "2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user