Compare commits

...
2 Commits
10 changed files with 665 additions and 2 deletions
+17
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using ProtoBuf;
using Vintagestory.API.Common;
namespace ClaimLink;
@@ -11,6 +12,22 @@ public class ClaimLinkSaveData
[ProtoMember(2)]
public List<ClaimLinkEntrySaveData> Entries = new();
[ProtoMember(3)]
public List<ClaimLinkPermissionSaveData> Permissions = new();
}
[ProtoContract]
public class ClaimLinkPermissionSaveData
{
[ProtoMember(1)]
public int GroupId;
[ProtoMember(2)]
public Dictionary<string, EnumBlockAccessFlags> PermittedPlayerUids = new();
[ProtoMember(3)]
public Dictionary<int, EnumBlockAccessFlags> PermittedPlayerGroupIds = new();
}
[ProtoContract]
+6
View File
@@ -34,4 +34,10 @@
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="assets\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+67
View File
@@ -0,0 +1,67 @@
using System.Collections.Generic;
using Vintagestory.API.MathTools;
namespace ClaimLink;
internal static class ClaimLinkBufferIndex
{
private static readonly Dictionary<long, HashSet<int>> groupIdsByRegion = new();
private static readonly Dictionary<int, List<Cuboidi>> buffersByGroupId = new();
internal static void Rebuild()
{
groupIdsByRegion.Clear();
buffersByGroupId.Clear();
int regionSize = ClaimLinkModSystem.WorldManager.RegionSize;
if (regionSize <= 0)
return;
foreach (int groupId in ClaimLinkModSystem.Registry.All)
{
List<Cuboidi> buffers = ClaimLinkBuffer.GetBuffer(groupId);
buffersByGroupId[groupId] = buffers;
HashSet<long> regionKeys = new();
foreach (Cuboidi buffer in buffers)
{
int minRegionX = buffer.MinX / regionSize;
int maxRegionX = buffer.MaxX / regionSize;
int minRegionZ = buffer.MinZ / regionSize;
int maxRegionZ = buffer.MaxZ / regionSize;
for (int rx = minRegionX; rx <= maxRegionX; rx++)
for (int rz = minRegionZ; rz <= maxRegionZ; rz++)
regionKeys.Add(ClaimLinkModSystem.WorldManager.MapRegionIndex2D(rx, rz));
}
foreach (long regionKey in regionKeys)
{
if (!groupIdsByRegion.TryGetValue(regionKey, out HashSet<int>? groupIds))
groupIdsByRegion[regionKey] = groupIds = new();
groupIds.Add(groupId);
}
}
}
internal static IEnumerable<int> CandidateGroupIds(BlockPos pos)
{
int regionSize = ClaimLinkModSystem.WorldManager.RegionSize;
if (regionSize <= 0)
return System.Array.Empty<int>();
long regionKey = ClaimLinkModSystem.WorldManager.MapRegionIndex2D(
pos.X / regionSize,
pos.Z / regionSize
);
return groupIdsByRegion.TryGetValue(regionKey, out HashSet<int>? groupIds)
? groupIds
: System.Array.Empty<int>();
}
internal static List<Cuboidi> CachedBuffer(int groupId) =>
buffersByGroupId.TryGetValue(groupId, out List<Cuboidi>? buffers)
? buffers
: new List<Cuboidi>();
}
+81
View File
@@ -0,0 +1,81 @@
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;
using Vintagestory.API.Server;
namespace ClaimLink;
internal static class ClaimLinkBufferProtection
{
internal static void Register(ICoreServerAPI api)
{
api.Event.CanPlaceOrBreakBlock += OnCanPlaceOrBreakBlock;
api.Event.CanUseBlock += OnCanUseBlock;
}
private static bool OnCanUseBlock(IServerPlayer byPlayer, BlockSelection blockSel) =>
IsAllowed(byPlayer, blockSel.Position, EnumBlockAccessFlags.Use);
private static bool OnCanPlaceOrBreakBlock(
IServerPlayer byPlayer,
BlockSelection blockSel,
out string claimant
)
{
bool allowed = IsAllowed(byPlayer, blockSel.Position, EnumBlockAccessFlags.BuildOrBreak);
claimant = allowed ? null! : "custommessage-claimlink-buffer-zone";
return allowed;
}
private static bool IsAllowed(
IServerPlayer byPlayer,
BlockPos pos,
EnumBlockAccessFlags accessFlag
)
{
bool allowed = true;
foreach (int groupId in ClaimLinkBufferIndex.CandidateGroupIds(pos))
{
if (IsMember(byPlayer, groupId))
continue;
foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId))
if (
buffer.Contains(pos)
&& !ClaimLinkModSystem.Registry.HasPermission(groupId, byPlayer, accessFlag)
)
allowed = false;
}
return allowed || HasClaimAccess(byPlayer, pos, accessFlag);
}
private static bool IsMember(IServerPlayer player, int groupId)
{
foreach (PlayerGroupMembership membership in player.Groups)
if (membership.GroupUid == groupId)
return true;
return false;
}
private static bool HasClaimAccess(
IServerPlayer player,
BlockPos pos,
EnumBlockAccessFlags accessFlag
)
{
LandClaim[]? claims = ClaimLinkModSystem.LandClaimAPI.Get(pos);
if (claims == null)
return false;
foreach (LandClaim claim in claims)
{
if (claim.TestPlayerAccess(player, accessFlag) != EnumPlayerAccessResult.Denied)
return true;
if (accessFlag == EnumBlockAccessFlags.Use && claim.AllowUseEveryone)
return true;
}
return false;
}
}
+348
View File
@@ -113,6 +113,44 @@ public static class ClaimLinkChatCommand
true,
Kick
),
new(
new[] { "allow" },
"Grant a player access inside the claim link's buffer zone.",
new ICommandArgumentParser[]
{
p.Word("playername"),
p.WordRange("access", "use", "build", "all"),
p.OptionalWord("groupname"),
},
true,
Allow
),
new(
new[] { "allowgroup" },
"Grant a player group access inside the claim link's buffer zone.",
new ICommandArgumentParser[]
{
p.Word("targetgroup"),
p.WordRange("access", "use", "build", "all"),
p.OptionalWord("groupname"),
},
true,
AllowGroup
),
new(
new[] { "deny" },
"Revoke a player's buffer-zone access.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
true,
Deny
),
new(
new[] { "denygroup" },
"Revoke a player group's buffer-zone access.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("targetgroup") },
true,
DenyGroup
),
new(
new[] { "delete" },
"Delete a claim link.",
@@ -184,6 +222,44 @@ public static class ClaimLinkChatCommand
false,
AdminTransferOwnership
),
new(
new[] { "allow" },
"Grant a player access inside any claim link's buffer zone.",
new ICommandArgumentParser[]
{
p.Word("playername"),
p.WordRange("access", "use", "build", "all"),
p.OptionalWord("groupname"),
},
false,
AdminAllow
),
new(
new[] { "allowgroup" },
"Grant a player group access inside any claim link's buffer zone.",
new ICommandArgumentParser[]
{
p.Word("targetgroup"),
p.WordRange("access", "use", "build", "all"),
p.OptionalWord("groupname"),
},
false,
AdminAllowGroup
),
new(
new[] { "deny" },
"Revoke a player's buffer-zone access to any claim link.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("playername") },
false,
AdminDeny
),
new(
new[] { "denygroup" },
"Revoke a player group's buffer-zone access to any claim link.",
new ICommandArgumentParser[] { p.Word("groupname"), p.OptionalWord("targetgroup") },
false,
AdminDenyGroup
),
};
foreach (var spec in adminCommands)
@@ -532,6 +608,180 @@ public static class ClaimLinkChatCommand
);
}
private static EnumBlockAccessFlags ParseAccess(string word) =>
word switch
{
"use" => EnumBlockAccessFlags.Use,
"build" => EnumBlockAccessFlags.BuildOrBreak,
_ => EnumBlockAccessFlags.Use | EnumBlockAccessFlags.BuildOrBreak,
};
private static TextCommandResult ExecuteAllow(
PlayerGroup group,
string targetName,
string accessWord
)
{
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(targetName)
?.PlayerUID;
if (targetUid == null)
return TextCommandResult.Error($"No such player '{targetName}'.");
ClaimLinkModSystem.Registry.GrantPlayer(group.Uid, targetUid, ParseAccess(accessWord));
return TextCommandResult.Success(
$"Granted {targetName} {accessWord} access to '{group.Name}''s buffer."
);
}
private static TextCommandResult ExecuteDeny(PlayerGroup group, string targetName)
{
string? targetUid = ClaimLinkModSystem
.PlayerData.GetPlayerDataByLastKnownName(targetName)
?.PlayerUID;
if (targetUid == null)
return TextCommandResult.Error($"No such player '{targetName}'.");
return ClaimLinkModSystem.Registry.RevokePlayer(group.Uid, targetUid)
? TextCommandResult.Success($"Revoked {targetName}'s buffer access to '{group.Name}'.")
: TextCommandResult.Error($"{targetName} has no buffer access granted in '{group.Name}'.");
}
private static TextCommandResult ExecuteAllowGroup(
PlayerGroup group,
string targetGroupName,
string accessWord
)
{
TextCommandResult? err = TryResolveGroup(targetGroupName, out PlayerGroup targetGroup);
if (err != null)
return err;
ClaimLinkModSystem.Registry.GrantGroup(group.Uid, targetGroup.Uid, ParseAccess(accessWord));
return TextCommandResult.Success(
$"Granted group '{targetGroup.Name}' {accessWord} access to '{group.Name}''s buffer."
);
}
private static TextCommandResult ExecuteDenyGroup(PlayerGroup group, string targetGroupName)
{
TextCommandResult? err = TryResolveGroup(targetGroupName, out PlayerGroup targetGroup);
if (err != null)
return err;
return ClaimLinkModSystem.Registry.RevokeGroup(group.Uid, targetGroup.Uid)
? TextCommandResult.Success(
$"Revoked group '{targetGroup.Name}''s buffer access to '{group.Name}'."
)
: TextCommandResult.Error(
$"Group '{targetGroup.Name}' has no buffer access granted in '{group.Name}'."
);
}
private static TextCommandResult? ResolveClaimLinkFromWordPair(
TextCommandCallingArgs args,
out PlayerGroup group,
out string targetName
)
{
string word1 = (string)args[0];
string? word2 = args.Parsers[1].IsMissing ? null : (string)args[1];
if (word2 != null)
{
TextCommandResult? groupErr = TryResolveGroup(word1, out group);
targetName = word2;
return groupErr;
}
targetName = word1;
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;
}
public static TextCommandResult Allow(TextCommandCallingArgs args)
{
string targetName = (string)args[0];
string accessWord = (string)args[1];
TextCommandResult? err = TryResolveGroupArg(args, 2, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
err = RequireOpOrOwner(args.Caller.Player, group);
if (err != null)
return err;
return ExecuteAllow(group, targetName, accessWord);
}
public static TextCommandResult AllowGroup(TextCommandCallingArgs args)
{
string targetGroupName = (string)args[0];
string accessWord = (string)args[1];
TextCommandResult? err = TryResolveGroupArg(args, 2, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
err = RequireOpOrOwner(args.Caller.Player, group);
if (err != null)
return err;
return ExecuteAllowGroup(group, targetGroupName, accessWord);
}
public static TextCommandResult Deny(TextCommandCallingArgs args)
{
TextCommandResult? err = ResolveClaimLinkFromWordPair(args, out PlayerGroup group, out string targetName);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
err = RequireOpOrOwner(args.Caller.Player, group);
if (err != null)
return err;
return ExecuteDeny(group, targetName);
}
public static TextCommandResult DenyGroup(TextCommandCallingArgs args)
{
TextCommandResult? err = ResolveClaimLinkFromWordPair(
args,
out PlayerGroup group,
out string targetGroupName
);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
err = RequireOpOrOwner(args.Caller.Player, group);
if (err != null)
return err;
return ExecuteDenyGroup(group, targetGroupName);
}
public static TextCommandResult Delete(TextCommandCallingArgs args)
{
string playerUid = args.Caller.Player.PlayerUID;
@@ -803,9 +1053,45 @@ public static class ClaimLinkChatCommand
sb.AppendLine($" {name}: [{string.Join(", ", claims)}]");
}
if (!showDetails)
return sb.ToString();
var permittedPlayers = ClaimLinkModSystem.Registry.PermittedPlayersForGroup(groupId);
var permittedGroups = ClaimLinkModSystem.Registry.PermittedGroupsForGroup(groupId);
if (permittedPlayers.Count == 0 && permittedGroups.Count == 0)
return sb.ToString();
sb.AppendLine("Buffer access granted:");
foreach (var (uid, flags) in permittedPlayers)
{
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
sb.AppendLine($" {name}: {DescribeAccess(flags)}");
}
foreach (var (targetGroupId, flags) in permittedGroups)
{
string groupLabel = ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
targetGroupId,
out PlayerGroup? targetGroup
)
? targetGroup!.Name
: $"group {targetGroupId}";
sb.AppendLine($" {groupLabel} (group): {DescribeAccess(flags)}");
}
return sb.ToString();
}
private static string DescribeAccess(EnumBlockAccessFlags flags)
{
bool use = (flags & EnumBlockAccessFlags.Use) != 0;
bool buildOrBreak = (flags & EnumBlockAccessFlags.BuildOrBreak) != 0;
if (use && buildOrBreak)
return "all";
if (buildOrBreak)
return "build";
return "use";
}
public static TextCommandResult AdminDelete(TextCommandCallingArgs args)
{
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
@@ -850,6 +1136,68 @@ public static class ClaimLinkChatCommand
);
}
public static TextCommandResult AdminAllow(TextCommandCallingArgs args)
{
string targetName = (string)args[0];
string accessWord = (string)args[1];
TextCommandResult? err = TryResolveGroupArg(args, 2, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
return ExecuteAllow(group, targetName, accessWord);
}
public static TextCommandResult AdminAllowGroup(TextCommandCallingArgs args)
{
string targetGroupName = (string)args[0];
string accessWord = (string)args[1];
TextCommandResult? err = TryResolveGroupArg(args, 2, out PlayerGroup group);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
return ExecuteAllowGroup(group, targetGroupName, accessWord);
}
public static TextCommandResult AdminDeny(TextCommandCallingArgs args)
{
TextCommandResult? err = ResolveClaimLinkFromWordPair(args, out PlayerGroup group, out string targetName);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
return ExecuteDeny(group, targetName);
}
public static TextCommandResult AdminDenyGroup(TextCommandCallingArgs args)
{
TextCommandResult? err = ResolveClaimLinkFromWordPair(
args,
out PlayerGroup group,
out string targetGroupName
);
if (err != null)
return err;
err = TryResolveClaimLink(group);
if (err != null)
return err;
return ExecuteDenyGroup(group, targetGroupName);
}
public static TextCommandResult AdminKick(TextCommandCallingArgs args)
{
string word1 = (string)args[0];
+5
View File
@@ -12,6 +12,7 @@ public class ClaimLinkModSystem : ModSystem
internal static ClaimLinkRegistry Registry = null!;
internal static IGroupManager Groups = null!;
internal static IWorldAccessor World = null!;
internal static IWorldManagerAPI WorldManager = null!;
internal static IPlayerDataManager PlayerData = null!;
internal static ClaimLinkConfig Config = null!;
internal CommandListener? CmdListener;
@@ -23,12 +24,15 @@ public class ClaimLinkModSystem : ModSystem
{
LandClaimAPI = api.World.Claims;
World = api.World;
WorldManager = api.WorldManager;
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);
ClaimLinkBufferIndex.Rebuild();
api.Event.SaveGameLoaded += ClaimLinkBufferIndex.Rebuild;
CmdListener = new CommandListener();
CommandHookModSystem.Register(CmdListener);
@@ -36,6 +40,7 @@ public class ClaimLinkModSystem : ModSystem
CommandHookModSystem.Register(GroupCmdListener);
ClaimLinkChatCommand.Register(api);
ClaimLinkVisualizerNetwork.Start(api);
ClaimLinkBufferProtection.Register(api);
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect;
+131 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
namespace ClaimLink;
@@ -7,10 +8,17 @@ public class ClaimLinkRegistry
{
private const string SaveKey = "claimlink:claimlinkdata";
private static readonly Dictionary<string, EnumBlockAccessFlags> EmptyPlayerFlags = new();
private static readonly Dictionary<int, EnumBlockAccessFlags> EmptyGroupFlags = new();
private readonly ISaveGame saveGame;
private readonly List<int> claimLinks = new();
private readonly Dictionary<string, Dictionary<int, List<int>>> entries = new();
private readonly Dictionary<int, Dictionary<string, EnumBlockAccessFlags>> permittedPlayerUids =
new();
private readonly Dictionary<int, Dictionary<int, EnumBlockAccessFlags>> permittedPlayerGroupIds =
new();
public ClaimLinkRegistry(ISaveGame saveGame)
{
@@ -29,6 +37,14 @@ public class ClaimLinkRegistry
entries[entry.OwnerPlayerUid][entry.GroupId] = new(entry.ClaimIndicies);
}
foreach (var permission in data.Permissions)
{
if (permission.PermittedPlayerUids.Count > 0)
permittedPlayerUids[permission.GroupId] = new(permission.PermittedPlayerUids);
if (permission.PermittedPlayerGroupIds.Count > 0)
permittedPlayerGroupIds[permission.GroupId] = new(permission.PermittedPlayerGroupIds);
}
}
public IReadOnlyList<int> All => claimLinks;
@@ -46,10 +62,96 @@ public class ClaimLinkRegistry
claimLinks.Remove(groupId);
foreach (var groups in entries.Values)
groups.Remove(groupId);
permittedPlayerUids.Remove(groupId);
permittedPlayerGroupIds.Remove(groupId);
Prune();
Save();
}
public void GrantPlayer(int groupId, string uid, EnumBlockAccessFlags flags)
{
if (!permittedPlayerUids.TryGetValue(groupId, out var uids))
permittedPlayerUids[groupId] = uids = new();
uids[uid] = flags;
Save();
}
public bool RevokePlayer(int groupId, string uid)
{
bool removed = permittedPlayerUids.TryGetValue(groupId, out var uids) && uids.Remove(uid);
if (!removed)
return false;
if (uids!.Count == 0)
permittedPlayerUids.Remove(groupId);
Save();
return true;
}
public void GrantGroup(int groupId, int targetGroupId, EnumBlockAccessFlags flags)
{
if (!permittedPlayerGroupIds.TryGetValue(groupId, out var groups))
permittedPlayerGroupIds[groupId] = groups = new();
groups[targetGroupId] = flags;
Save();
}
public bool RevokeGroup(int groupId, int targetGroupId)
{
bool removed =
permittedPlayerGroupIds.TryGetValue(groupId, out var groups)
&& groups.Remove(targetGroupId);
if (!removed)
return false;
if (groups!.Count == 0)
permittedPlayerGroupIds.Remove(groupId);
Save();
return true;
}
public bool HasPermission(int groupId, IPlayer player, EnumBlockAccessFlags accessFlag)
{
if (
permittedPlayerUids.TryGetValue(groupId, out var uids)
&& uids.TryGetValue(player.PlayerUID, out EnumBlockAccessFlags flags)
&& (flags & accessFlag) != 0
)
return true;
if (permittedPlayerGroupIds.TryGetValue(groupId, out var groups))
foreach (PlayerGroupMembership membership in player.Groups)
if (groups.TryGetValue(membership.GroupUid, out flags) && (flags & accessFlag) != 0)
return true;
return false;
}
public IReadOnlyDictionary<string, EnumBlockAccessFlags> PermittedPlayersForGroup(int groupId) =>
permittedPlayerUids.TryGetValue(groupId, out var uids) ? uids : EmptyPlayerFlags;
public IReadOnlyDictionary<int, EnumBlockAccessFlags> PermittedGroupsForGroup(int groupId) =>
permittedPlayerGroupIds.TryGetValue(groupId, out var groups) ? groups : EmptyGroupFlags;
public void RemoveGroupFromAllPermissions(int targetGroupId)
{
bool changed = false;
foreach (var groups in permittedPlayerGroupIds.Values)
if (groups.Remove(targetGroupId))
changed = true;
if (!changed)
return;
List<int>? emptyGroupIds = null;
foreach (var (groupId, groups) in permittedPlayerGroupIds)
if (groups.Count == 0)
(emptyGroupIds ??= new()).Add(groupId);
if (emptyGroupIds != null)
foreach (int groupId in emptyGroupIds)
permittedPlayerGroupIds.Remove(groupId);
Save();
}
public bool IsClaimLinked(string uid, int claimIndex) =>
FindGroupContaining(uid, claimIndex) != null;
@@ -193,9 +295,37 @@ public class ClaimLinkRegistry
}
);
var permissionGroupIds = new HashSet<int>();
foreach (int groupId in permittedPlayerUids.Keys)
permissionGroupIds.Add(groupId);
foreach (int groupId in permittedPlayerGroupIds.Keys)
permissionGroupIds.Add(groupId);
var permissionList = new List<ClaimLinkPermissionSaveData>();
foreach (int groupId in permissionGroupIds)
{
permittedPlayerUids.TryGetValue(groupId, out var uids);
permittedPlayerGroupIds.TryGetValue(groupId, out var groups);
permissionList.Add(
new ClaimLinkPermissionSaveData
{
GroupId = groupId,
PermittedPlayerUids = uids != null ? new(uids) : new(),
PermittedPlayerGroupIds = groups != null ? new(groups) : new(),
}
);
}
saveGame.StoreData(
SaveKey,
new ClaimLinkSaveData { Links = claimLinks, Entries = entryList }
new ClaimLinkSaveData
{
Links = claimLinks,
Entries = entryList,
Permissions = permissionList,
}
);
ClaimLinkBufferIndex.Rebuild();
}
}
+5
View File
@@ -113,7 +113,10 @@ public class CommandListener : ICommandHookListener
);
if (!violatesClaimLink && !violatesClaim)
{
ClaimLinkBufferIndex.Rebuild();
return;
}
ClaimLinkModSystem.LandClaimAPI.Remove(savedClaim);
@@ -387,6 +390,8 @@ public class GroupCommandListener : ICommandHookListener
if (ClaimLinkModSystem.Registry.Exists(disbandedGroupId))
ClaimLinkModSystem.Registry.Remove(disbandedGroupId);
ClaimLinkModSystem.Registry.RemoveGroupFromAllPermissions(disbandedGroupId);
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"custommessage-claimlink-buffer-zone": "This area is a claim link buffer zone.",
"ingameerror-noprivilege-buildbreak-claimlink-buffer-zone": "This area is a claim link buffer zone."
}
+1 -1
View File
@@ -8,7 +8,7 @@
"anth64"
],
"description": "Link claims together with groups.",
"version": "0.3.0",
"version": "0.5.0",
"dependencies": {
"game": "1.22.5",
"commandhook": "2.1.0"