Compare commits

..
2 Commits
6 changed files with 501 additions and 3 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]
+4 -1
View File
@@ -40,7 +40,10 @@ internal static class ClaimLinkBufferProtection
continue;
foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId))
if (buffer.Contains(pos))
if (
buffer.Contains(pos)
&& !ClaimLinkModSystem.Registry.HasPermission(groupId, byPlayer, accessFlag)
)
allowed = 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,7 +1053,43 @@ 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)
@@ -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];
+129 -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,35 @@ 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();
+2
View File
@@ -390,6 +390,8 @@ public class GroupCommandListener : ICommandHookListener
if (ClaimLinkModSystem.Registry.Exists(disbandedGroupId))
ClaimLinkModSystem.Registry.Remove(disbandedGroupId);
ClaimLinkModSystem.Registry.RemoveGroupFromAllPermissions(disbandedGroupId);
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
"anth64"
],
"description": "Link claims together with groups.",
"version": "0.4.0",
"version": "0.5.0",
"dependencies": {
"game": "1.22.5",
"commandhook": "2.1.0"