Compare commits
11
Commits
c1158d50c3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f30fc626c | ||
|
|
05ff998287 | ||
|
|
cac8e590d3 | ||
|
|
62c94bc775 | ||
|
|
b14eb5c538 | ||
|
|
8f0e4bd687 | ||
|
|
725f450da7 | ||
|
|
8cedbc841b | ||
|
|
b93abfe899 | ||
|
|
a03219c114 | ||
|
|
095b826178 |
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ProtoBuf;
|
using ProtoBuf;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
namespace ClaimLink;
|
namespace ClaimLink;
|
||||||
|
|
||||||
@@ -11,6 +12,22 @@ public class ClaimLinkSaveData
|
|||||||
|
|
||||||
[ProtoMember(2)]
|
[ProtoMember(2)]
|
||||||
public List<ClaimLinkEntrySaveData> Entries = new();
|
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]
|
[ProtoContract]
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ internal static class ClaimLinkBuffer
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (Cuboidi bufferCuboid in GetBuffer(groupId))
|
foreach (Cuboidi bufferCuboid in GetBuffer(groupId))
|
||||||
foreach (Cuboidi area in areas)
|
foreach (Cuboidi area in areas)
|
||||||
if (area.Intersects(bufferCuboid))
|
if (area.Intersects(bufferCuboid))
|
||||||
{
|
{
|
||||||
violatingGroupId = groupId;
|
violatingGroupId = groupId;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
violatingGroupId = -1;
|
violatingGroupId = -1;
|
||||||
@@ -64,22 +64,19 @@ internal static class ClaimLinkBuffer
|
|||||||
HashSet<LandClaim> excludedClaims = new();
|
HashSet<LandClaim> excludedClaims = new();
|
||||||
if (excludeGroupIds != null)
|
if (excludeGroupIds != null)
|
||||||
foreach (int groupId in excludeGroupIds)
|
foreach (int groupId in excludeGroupIds)
|
||||||
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
|
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
|
||||||
foreach (
|
foreach (
|
||||||
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(
|
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(uid, groupId)
|
||||||
uid,
|
)
|
||||||
groupId
|
if (
|
||||||
)
|
ClaimLinkModSystem.TryResolveOwnedClaim(
|
||||||
|
uid,
|
||||||
|
claimIndex,
|
||||||
|
out LandClaim? excludedClaim
|
||||||
)
|
)
|
||||||
if (
|
&& excludedClaim != null
|
||||||
ClaimLinkModSystem.TryResolveOwnedClaim(
|
)
|
||||||
uid,
|
excludedClaims.Add(excludedClaim);
|
||||||
claimIndex,
|
|
||||||
out LandClaim? excludedClaim
|
|
||||||
)
|
|
||||||
&& excludedClaim != null
|
|
||||||
)
|
|
||||||
excludedClaims.Add(excludedClaim);
|
|
||||||
|
|
||||||
foreach (LandClaim claim in ClaimLinkModSystem.LandClaimAPI.All)
|
foreach (LandClaim claim in ClaimLinkModSystem.LandClaimAPI.All)
|
||||||
{
|
{
|
||||||
@@ -90,12 +87,12 @@ internal static class ClaimLinkBuffer
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (Cuboidi claimArea in claim.Areas)
|
foreach (Cuboidi claimArea in claim.Areas)
|
||||||
foreach (Cuboidi area in areas)
|
foreach (Cuboidi area in areas)
|
||||||
if (area.Intersects(claimArea))
|
if (area.Intersects(claimArea))
|
||||||
{
|
{
|
||||||
violatingClaim = claim;
|
violatingClaim = claim;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
violatingClaim = null;
|
violatingClaim = null;
|
||||||
@@ -105,7 +102,9 @@ internal static class ClaimLinkBuffer
|
|||||||
internal static ISet<int> MemberClaimLinkIds(string uid)
|
internal static ISet<int> MemberClaimLinkIds(string uid)
|
||||||
{
|
{
|
||||||
HashSet<int> groupIds = new();
|
HashSet<int> groupIds = new();
|
||||||
var memberships = ClaimLinkModSystem.PlayerData.GetPlayerDataByUid(uid)?.PlayerGroupMemberships;
|
var memberships = ClaimLinkModSystem
|
||||||
|
.PlayerData.GetPlayerDataByUid(uid)
|
||||||
|
?.PlayerGroupMemberships;
|
||||||
if (memberships == null)
|
if (memberships == null)
|
||||||
return groupIds;
|
return groupIds;
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Vintagestory.API.MathTools;
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
namespace ClaimLink;
|
namespace ClaimLink;
|
||||||
|
|
||||||
internal static class ClaimLinkBufferIndex
|
internal class ClaimLinkBufferIndex
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<long, HashSet<int>> groupIdsByRegion = new();
|
private readonly Func<int, int, long> regionIndex;
|
||||||
private static readonly Dictionary<int, List<Cuboidi>> buffersByGroupId = new();
|
private readonly Dictionary<long, HashSet<int>> groupIdsByRegion = new();
|
||||||
|
private readonly Dictionary<int, List<Cuboidi>> buffersByGroupId = new();
|
||||||
|
private int regionSize;
|
||||||
|
|
||||||
internal static void Rebuild()
|
internal ClaimLinkBufferIndex(Func<int, int, long> regionIndex)
|
||||||
{
|
{
|
||||||
|
this.regionIndex = regionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Rebuild(IEnumerable<(int groupId, List<Cuboidi> buffers)> source, int regionSize)
|
||||||
|
{
|
||||||
|
this.regionSize = regionSize;
|
||||||
groupIdsByRegion.Clear();
|
groupIdsByRegion.Clear();
|
||||||
buffersByGroupId.Clear();
|
buffersByGroupId.Clear();
|
||||||
|
|
||||||
int regionSize = ClaimLinkModSystem.WorldManager.RegionSize;
|
|
||||||
if (regionSize <= 0)
|
if (regionSize <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
foreach ((int groupId, List<Cuboidi> buffers) in source)
|
||||||
{
|
{
|
||||||
List<Cuboidi> buffers = ClaimLinkBuffer.GetBuffer(groupId);
|
|
||||||
buffersByGroupId[groupId] = buffers;
|
buffersByGroupId[groupId] = buffers;
|
||||||
|
|
||||||
HashSet<long> regionKeys = new();
|
HashSet<long> regionKeys = new();
|
||||||
@@ -32,7 +39,7 @@ internal static class ClaimLinkBufferIndex
|
|||||||
|
|
||||||
for (int rx = minRegionX; rx <= maxRegionX; rx++)
|
for (int rx = minRegionX; rx <= maxRegionX; rx++)
|
||||||
for (int rz = minRegionZ; rz <= maxRegionZ; rz++)
|
for (int rz = minRegionZ; rz <= maxRegionZ; rz++)
|
||||||
regionKeys.Add(ClaimLinkModSystem.WorldManager.MapRegionIndex2D(rx, rz));
|
regionKeys.Add(regionIndex(rx, rz));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (long regionKey in regionKeys)
|
foreach (long regionKey in regionKeys)
|
||||||
@@ -44,23 +51,19 @@ internal static class ClaimLinkBufferIndex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IEnumerable<int> CandidateGroupIds(BlockPos pos)
|
internal IEnumerable<int> CandidateGroupIds(BlockPos pos)
|
||||||
{
|
{
|
||||||
int regionSize = ClaimLinkModSystem.WorldManager.RegionSize;
|
|
||||||
if (regionSize <= 0)
|
if (regionSize <= 0)
|
||||||
return System.Array.Empty<int>();
|
return Array.Empty<int>();
|
||||||
|
|
||||||
long regionKey = ClaimLinkModSystem.WorldManager.MapRegionIndex2D(
|
long regionKey = regionIndex(pos.X / regionSize, pos.Z / regionSize);
|
||||||
pos.X / regionSize,
|
|
||||||
pos.Z / regionSize
|
|
||||||
);
|
|
||||||
|
|
||||||
return groupIdsByRegion.TryGetValue(regionKey, out HashSet<int>? groupIds)
|
return groupIdsByRegion.TryGetValue(regionKey, out HashSet<int>? groupIds)
|
||||||
? groupIds
|
? groupIds
|
||||||
: System.Array.Empty<int>();
|
: Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static List<Cuboidi> CachedBuffer(int groupId) =>
|
internal List<Cuboidi> CachedBuffer(int groupId) =>
|
||||||
buffersByGroupId.TryGetValue(groupId, out List<Cuboidi>? buffers)
|
buffersByGroupId.TryGetValue(groupId, out List<Cuboidi>? buffers)
|
||||||
? buffers
|
? buffers
|
||||||
: new List<Cuboidi>();
|
: new List<Cuboidi>();
|
||||||
|
|||||||
@@ -34,13 +34,16 @@ internal static class ClaimLinkBufferProtection
|
|||||||
{
|
{
|
||||||
bool allowed = true;
|
bool allowed = true;
|
||||||
|
|
||||||
foreach (int groupId in ClaimLinkBufferIndex.CandidateGroupIds(pos))
|
foreach (int groupId in ClaimLinkModSystem.BufferIndex.CandidateGroupIds(pos))
|
||||||
{
|
{
|
||||||
if (IsMember(byPlayer, groupId))
|
if (IsMember(byPlayer, groupId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId))
|
foreach (Cuboidi buffer in ClaimLinkModSystem.BufferIndex.CachedBuffer(groupId))
|
||||||
if (buffer.Contains(pos))
|
if (
|
||||||
|
buffer.Contains(pos)
|
||||||
|
&& !ClaimLinkModSystem.Registry.HasPermission(groupId, byPlayer, accessFlag)
|
||||||
|
)
|
||||||
allowed = false;
|
allowed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,44 @@ public static class ClaimLinkChatCommand
|
|||||||
true,
|
true,
|
||||||
Kick
|
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(
|
||||||
new[] { "delete" },
|
new[] { "delete" },
|
||||||
"Delete a claim link.",
|
"Delete a claim link.",
|
||||||
@@ -184,6 +222,44 @@ public static class ClaimLinkChatCommand
|
|||||||
false,
|
false,
|
||||||
AdminTransferOwnership
|
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)
|
foreach (var spec in adminCommands)
|
||||||
@@ -532,6 +608,186 @@ 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)
|
public static TextCommandResult Delete(TextCommandCallingArgs args)
|
||||||
{
|
{
|
||||||
string playerUid = args.Caller.Player.PlayerUID;
|
string playerUid = args.Caller.Player.PlayerUID;
|
||||||
@@ -778,6 +1034,9 @@ public static class ClaimLinkChatCommand
|
|||||||
return string.IsNullOrEmpty(claim.Description) ? $"claim {claimIndex}" : claim.Description;
|
return string.IsNullOrEmpty(claim.Description) ? $"claim {claimIndex}" : claim.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string PlayerNameForUid(string uid) =>
|
||||||
|
ClaimLinkModSystem.PlayerData.GetPlayerDataByUid(uid)?.LastKnownPlayername ?? uid;
|
||||||
|
|
||||||
private static string FormatInfo(string groupName, int groupId, bool showDetails)
|
private static string FormatInfo(string groupName, int groupId, bool showDetails)
|
||||||
{
|
{
|
||||||
int memberCount = GroupMemberCount(groupId);
|
int memberCount = GroupMemberCount(groupId);
|
||||||
@@ -789,7 +1048,7 @@ public static class ClaimLinkChatCommand
|
|||||||
|
|
||||||
foreach (string uid in GroupMemberUids(groupId))
|
foreach (string uid in GroupMemberUids(groupId))
|
||||||
{
|
{
|
||||||
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
|
string name = PlayerNameForUid(uid);
|
||||||
|
|
||||||
if (!showDetails)
|
if (!showDetails)
|
||||||
{
|
{
|
||||||
@@ -803,9 +1062,45 @@ public static class ClaimLinkChatCommand
|
|||||||
sb.AppendLine($" {name}: [{string.Join(", ", claims)}]");
|
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 = PlayerNameForUid(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();
|
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)
|
public static TextCommandResult AdminDelete(TextCommandCallingArgs args)
|
||||||
{
|
{
|
||||||
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
|
TextCommandResult? err = TryResolveGroupArg(args, 0, out PlayerGroup group);
|
||||||
@@ -850,6 +1145,72 @@ 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)
|
public static TextCommandResult AdminKick(TextCommandCallingArgs args)
|
||||||
{
|
{
|
||||||
string word1 = (string)args[0];
|
string word1 = (string)args[0];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CommandHook;
|
using Vintagestory.API.Client;
|
||||||
using Vintagestory.API.Common;
|
using Vintagestory.API.Common;
|
||||||
using Vintagestory.API.Server;
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
@@ -15,10 +15,9 @@ public class ClaimLinkModSystem : ModSystem
|
|||||||
internal static IWorldManagerAPI WorldManager = null!;
|
internal static IWorldManagerAPI WorldManager = null!;
|
||||||
internal static IPlayerDataManager PlayerData = null!;
|
internal static IPlayerDataManager PlayerData = null!;
|
||||||
internal static ClaimLinkConfig Config = null!;
|
internal static ClaimLinkConfig Config = null!;
|
||||||
internal CommandListener? CmdListener;
|
internal static ClaimLinkBufferIndex BufferIndex = null!;
|
||||||
internal GroupCommandListener? GroupCmdListener;
|
private object? cmdListenerProxy;
|
||||||
|
private object? groupCmdListenerProxy;
|
||||||
public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server;
|
|
||||||
|
|
||||||
public override void StartServerSide(ICoreServerAPI api)
|
public override void StartServerSide(ICoreServerAPI api)
|
||||||
{
|
{
|
||||||
@@ -31,28 +30,79 @@ public class ClaimLinkModSystem : ModSystem
|
|||||||
Config = api.LoadModConfig<ClaimLinkConfig>("claimlink.json") ?? new ClaimLinkConfig();
|
Config = api.LoadModConfig<ClaimLinkConfig>("claimlink.json") ?? new ClaimLinkConfig();
|
||||||
api.StoreModConfig(Config, "claimlink.json");
|
api.StoreModConfig(Config, "claimlink.json");
|
||||||
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
|
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
|
||||||
ClaimLinkBufferIndex.Rebuild();
|
BufferIndex = new ClaimLinkBufferIndex(WorldManager.MapRegionIndex2D);
|
||||||
api.Event.SaveGameLoaded += ClaimLinkBufferIndex.Rebuild;
|
RebuildBufferIndex();
|
||||||
|
api.Event.SaveGameLoaded += RebuildBufferIndex;
|
||||||
|
|
||||||
|
if (api.ModLoader.IsModEnabled("commandhook"))
|
||||||
|
{
|
||||||
|
api.Event.SaveGameLoaded += () => RegisterCommandHooks(api);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
api.Logger.Fatal(
|
||||||
|
"ClaimLink requires the CommandHook mod on the server for /land and /group integration. That integration is disabled; /claimlink commands and buffer protection still work."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
CmdListener = new CommandListener();
|
|
||||||
CommandHookModSystem.Register(CmdListener);
|
|
||||||
GroupCmdListener = new GroupCommandListener();
|
|
||||||
CommandHookModSystem.Register(GroupCmdListener);
|
|
||||||
ClaimLinkChatCommand.Register(api);
|
ClaimLinkChatCommand.Register(api);
|
||||||
ClaimLinkVisualizerNetwork.Start(api);
|
ClaimLinkVisualizerNetwork.Start(api);
|
||||||
ClaimLinkBufferProtection.Register(api);
|
ClaimLinkBufferProtection.Register(api);
|
||||||
|
|
||||||
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
|
|
||||||
api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect;
|
|
||||||
api.Event.PlayerDisconnect += ClaimLinkChatCommand.OnPlayerDisconnect;
|
api.Event.PlayerDisconnect += ClaimLinkChatCommand.OnPlayerDisconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RegisterCommandHooks(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
CommandListener cmdListener = new();
|
||||||
|
CommandHook.ICommandHookListener cmdProxy = CommandHookListenerProxy.Create(
|
||||||
|
cmdListener.ModId,
|
||||||
|
cmdListener.Commands,
|
||||||
|
cmdListener.Before,
|
||||||
|
cmdListener.After
|
||||||
|
);
|
||||||
|
CommandHook.CommandHookModSystem.Register(cmdProxy);
|
||||||
|
cmdListenerProxy = cmdProxy;
|
||||||
|
|
||||||
|
GroupCommandListener groupListener = new();
|
||||||
|
CommandHook.ICommandHookListener groupProxy = CommandHookListenerProxy.Create(
|
||||||
|
groupListener.ModId,
|
||||||
|
groupListener.Commands,
|
||||||
|
groupListener.Before,
|
||||||
|
groupListener.After
|
||||||
|
);
|
||||||
|
CommandHook.CommandHookModSystem.Register(groupProxy);
|
||||||
|
groupCmdListenerProxy = groupProxy;
|
||||||
|
|
||||||
|
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
|
||||||
|
api.Event.PlayerDisconnect += GroupCommandListener.OnPlayerDisconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void RebuildBufferIndex()
|
||||||
|
{
|
||||||
|
List<(int groupId, List<Vintagestory.API.MathTools.Cuboidi> buffers)> source = new();
|
||||||
|
foreach (int groupId in Registry.All)
|
||||||
|
source.Add((groupId, ClaimLinkBuffer.GetBuffer(groupId)));
|
||||||
|
BufferIndex.Rebuild(source, WorldManager.RegionSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StartClientSide(ICoreClientAPI api) => ClaimLinkVisualizerClient.Start(api);
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
if (CmdListener != null)
|
if (cmdListenerProxy != null || groupCmdListenerProxy != null)
|
||||||
CommandHookModSystem.Unregister(CmdListener);
|
UnregisterCommandHooks();
|
||||||
if (GroupCmdListener != null)
|
}
|
||||||
CommandHookModSystem.Unregister(GroupCmdListener);
|
|
||||||
|
private void UnregisterCommandHooks()
|
||||||
|
{
|
||||||
|
if (cmdListenerProxy != null)
|
||||||
|
CommandHook.CommandHookModSystem.Unregister(
|
||||||
|
(CommandHook.ICommandHookListener)cmdListenerProxy
|
||||||
|
);
|
||||||
|
if (groupCmdListenerProxy != null)
|
||||||
|
CommandHook.CommandHookModSystem.Unregister(
|
||||||
|
(CommandHook.ICommandHookListener)groupCmdListenerProxy
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<(int claimIndex, LandClaim claim)> EnumerateOwnedClaims(
|
private static IEnumerable<(int claimIndex, LandClaim claim)> EnumerateOwnedClaims(
|
||||||
|
|||||||
+153
-11
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
using Vintagestory.API.Server;
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
namespace ClaimLink;
|
namespace ClaimLink;
|
||||||
@@ -7,10 +8,19 @@ public class ClaimLinkRegistry
|
|||||||
{
|
{
|
||||||
private const string SaveKey = "claimlink:claimlinkdata";
|
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 ISaveGame saveGame;
|
||||||
|
|
||||||
private readonly List<int> claimLinks = new();
|
private readonly List<int> claimLinks = new();
|
||||||
private readonly Dictionary<string, Dictionary<int, List<int>>> entries = 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)
|
public ClaimLinkRegistry(ISaveGame saveGame)
|
||||||
{
|
{
|
||||||
@@ -29,6 +39,16 @@ public class ClaimLinkRegistry
|
|||||||
|
|
||||||
entries[entry.OwnerPlayerUid][entry.GroupId] = new(entry.ClaimIndicies);
|
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;
|
public IReadOnlyList<int> All => claimLinks;
|
||||||
@@ -38,7 +58,7 @@ public class ClaimLinkRegistry
|
|||||||
public void Add(int groupId)
|
public void Add(int groupId)
|
||||||
{
|
{
|
||||||
claimLinks.Add(groupId);
|
claimLinks.Add(groupId);
|
||||||
Save();
|
Save(new[] { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(int groupId)
|
public void Remove(int groupId)
|
||||||
@@ -46,8 +66,95 @@ public class ClaimLinkRegistry
|
|||||||
claimLinks.Remove(groupId);
|
claimLinks.Remove(groupId);
|
||||||
foreach (var groups in entries.Values)
|
foreach (var groups in entries.Values)
|
||||||
groups.Remove(groupId);
|
groups.Remove(groupId);
|
||||||
|
permittedPlayerUids.Remove(groupId);
|
||||||
|
permittedPlayerGroupIds.Remove(groupId);
|
||||||
Prune();
|
Prune();
|
||||||
Save();
|
Save(new[] { groupId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GrantPlayer(int groupId, string uid, EnumBlockAccessFlags flags)
|
||||||
|
{
|
||||||
|
if (!permittedPlayerUids.TryGetValue(groupId, out var uids))
|
||||||
|
permittedPlayerUids[groupId] = uids = new();
|
||||||
|
uids[uid] = flags;
|
||||||
|
Save(new[] { groupId });
|
||||||
|
}
|
||||||
|
|
||||||
|
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(new[] { groupId });
|
||||||
|
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(new[] { groupId });
|
||||||
|
}
|
||||||
|
|
||||||
|
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(new[] { groupId });
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
List<int>? changedGroupIds = null;
|
||||||
|
foreach (var (groupId, groups) in permittedPlayerGroupIds)
|
||||||
|
if (groups.Remove(targetGroupId))
|
||||||
|
(changedGroupIds ??= new()).Add(groupId);
|
||||||
|
|
||||||
|
if (changedGroupIds == null)
|
||||||
|
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(changedGroupIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsClaimLinked(string uid, int claimIndex) =>
|
public bool IsClaimLinked(string uid, int claimIndex) =>
|
||||||
@@ -74,7 +181,7 @@ public class ClaimLinkRegistry
|
|||||||
if (!groups.TryGetValue(groupId, out var claims))
|
if (!groups.TryGetValue(groupId, out var claims))
|
||||||
groups[groupId] = claims = new();
|
groups[groupId] = claims = new();
|
||||||
claims.Add(claimIndex);
|
claims.Add(claimIndex);
|
||||||
Save();
|
Save(new[] { groupId });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +190,7 @@ public class ClaimLinkRegistry
|
|||||||
if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims))
|
if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims))
|
||||||
claims.Remove(claimIndex);
|
claims.Remove(claimIndex);
|
||||||
Prune();
|
Prune();
|
||||||
Save();
|
Save(new[] { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClaimRemoved(string uid, int removedIndex)
|
public void ClaimRemoved(string uid, int removedIndex)
|
||||||
@@ -91,6 +198,8 @@ public class ClaimLinkRegistry
|
|||||||
if (!entries.TryGetValue(uid, out var groups))
|
if (!entries.TryGetValue(uid, out var groups))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
||||||
|
|
||||||
foreach (var claims in groups.Values)
|
foreach (var claims in groups.Values)
|
||||||
{
|
{
|
||||||
claims.Remove(removedIndex);
|
claims.Remove(removedIndex);
|
||||||
@@ -100,7 +209,7 @@ public class ClaimLinkRegistry
|
|||||||
}
|
}
|
||||||
|
|
||||||
Prune();
|
Prune();
|
||||||
Save();
|
Save(affectedGroupIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClaimSaved(string uid, int claimIndex)
|
public void ClaimSaved(string uid, int claimIndex)
|
||||||
@@ -108,6 +217,8 @@ public class ClaimLinkRegistry
|
|||||||
if (!entries.TryGetValue(uid, out var groups))
|
if (!entries.TryGetValue(uid, out var groups))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All)
|
foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All)
|
||||||
if (c.OwnedByPlayerUid == uid)
|
if (c.OwnedByPlayerUid == uid)
|
||||||
@@ -126,7 +237,7 @@ public class ClaimLinkRegistry
|
|||||||
claims[i] = newIndex;
|
claims[i] = newIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Save();
|
Save(affectedGroupIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasAnyEntry(string uid, int groupId) =>
|
public bool HasAnyEntry(string uid, int groupId) =>
|
||||||
@@ -139,13 +250,17 @@ public class ClaimLinkRegistry
|
|||||||
if (entries.TryGetValue(uid, out var groups))
|
if (entries.TryGetValue(uid, out var groups))
|
||||||
groups.Remove(groupId);
|
groups.Remove(groupId);
|
||||||
Prune();
|
Prune();
|
||||||
Save();
|
Save(new[] { groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearAllForPlayer(string uid)
|
public void ClearAllForPlayer(string uid)
|
||||||
{
|
{
|
||||||
|
int[] affectedGroupIds = entries.TryGetValue(uid, out var groups)
|
||||||
|
? new List<int>(groups.Keys).ToArray()
|
||||||
|
: System.Array.Empty<int>();
|
||||||
|
|
||||||
entries.Remove(uid);
|
entries.Remove(uid);
|
||||||
Save();
|
Save(affectedGroupIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> MemberUidsForGroup(int groupId)
|
public IEnumerable<string> MemberUidsForGroup(int groupId)
|
||||||
@@ -179,7 +294,7 @@ public class ClaimLinkRegistry
|
|||||||
entries.Remove(uid);
|
entries.Remove(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
private void Save(IReadOnlyCollection<int> affectedGroupIds)
|
||||||
{
|
{
|
||||||
var entryList = new List<ClaimLinkEntrySaveData>();
|
var entryList = new List<ClaimLinkEntrySaveData>();
|
||||||
foreach (var (uid, groups) in entries)
|
foreach (var (uid, groups) in entries)
|
||||||
@@ -193,11 +308,38 @@ 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(
|
saveGame.StoreData(
|
||||||
SaveKey,
|
SaveKey,
|
||||||
new ClaimLinkSaveData { Links = claimLinks, Entries = entryList }
|
new ClaimLinkSaveData
|
||||||
|
{
|
||||||
|
Links = claimLinks,
|
||||||
|
Entries = entryList,
|
||||||
|
Permissions = permissionList,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ClaimLinkBufferIndex.Rebuild();
|
ClaimLinkModSystem.RebuildBufferIndex();
|
||||||
|
ClaimLinkVisualizerNetwork.BroadcastDelta(affectedGroupIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace ClaimLink;
|
||||||
|
|
||||||
|
internal static class ClaimLinkVisualizerClient
|
||||||
|
{
|
||||||
|
private const int HighlightSlotId = 8341;
|
||||||
|
private const int ClientRegionSize = 512;
|
||||||
|
|
||||||
|
private static ICoreClientAPI capi = null!;
|
||||||
|
private static bool visible;
|
||||||
|
private static ClaimLinkVisualizerConfig config = null!;
|
||||||
|
private static IClientNetworkChannel channel = null!;
|
||||||
|
private static readonly Dictionary<int, ClaimLinkBufferEntryDto> knownEntries = new();
|
||||||
|
private static readonly ClaimLinkBufferIndex bufferIndex = new(ClientRegionKey);
|
||||||
|
|
||||||
|
internal static void Start(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
capi = api;
|
||||||
|
config = api.LoadModConfig<ClaimLinkVisualizerConfig>("claimlinkvisualizer.json") ?? new ClaimLinkVisualizerConfig();
|
||||||
|
api.StoreModConfig(config, "claimlinkvisualizer.json");
|
||||||
|
|
||||||
|
channel = api.Network.RegisterChannel(ClaimLinkVisualizerNetwork.ChannelName)
|
||||||
|
.RegisterMessageType<ClaimLinkBufferResponse>()
|
||||||
|
.SetMessageHandler<ClaimLinkBufferResponse>(OnBuffersPushed);
|
||||||
|
|
||||||
|
api.Input.RegisterHotKey(
|
||||||
|
"claimlinkvisualizertoggle",
|
||||||
|
"Toggle ClaimLink buffer visualization",
|
||||||
|
GlKeys.R,
|
||||||
|
HotkeyType.CharacterControls,
|
||||||
|
ctrlPressed: true
|
||||||
|
);
|
||||||
|
api.Input.SetHotKeyHandler("claimlinkvisualizertoggle", OnToggle);
|
||||||
|
|
||||||
|
api.Event.TestBlockAccess += OnTestBlockAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long ClientRegionKey(int regionX, int regionZ) => ((long)regionX << 32) | (uint)regionZ;
|
||||||
|
|
||||||
|
private static bool OnToggle(KeyCombination comb)
|
||||||
|
{
|
||||||
|
visible = !visible;
|
||||||
|
if (visible)
|
||||||
|
RenderHighlights();
|
||||||
|
else
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnBuffersPushed(ClaimLinkBufferResponse response)
|
||||||
|
{
|
||||||
|
if (response.IsFullSync)
|
||||||
|
knownEntries.Clear();
|
||||||
|
|
||||||
|
foreach (ClaimLinkBufferEntryDto entry in response.Entries)
|
||||||
|
{
|
||||||
|
if (entry.Cuboids.Count == 0)
|
||||||
|
knownEntries.Remove(entry.GroupId);
|
||||||
|
else
|
||||||
|
knownEntries[entry.GroupId] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
RebuildIndex();
|
||||||
|
|
||||||
|
if (visible)
|
||||||
|
RenderHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RebuildIndex()
|
||||||
|
{
|
||||||
|
List<(int groupId, List<Cuboidi> buffers)> source = new();
|
||||||
|
|
||||||
|
foreach ((int groupId, ClaimLinkBufferEntryDto entry) in knownEntries)
|
||||||
|
{
|
||||||
|
List<Cuboidi> cuboids = new();
|
||||||
|
foreach (ClaimLinkBufferCuboidDto c in entry.Cuboids)
|
||||||
|
cuboids.Add(new Cuboidi(c.MinX, c.MinY, c.MinZ, c.MaxX, c.MaxY, c.MaxZ));
|
||||||
|
source.Add((groupId, cuboids));
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferIndex.Rebuild(source, ClientRegionSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EnumWorldAccessResponse OnTestBlockAccess(
|
||||||
|
IPlayer player,
|
||||||
|
BlockSelection blockSel,
|
||||||
|
EnumBlockAccessFlags accessType,
|
||||||
|
ref string claimant,
|
||||||
|
EnumWorldAccessResponse response
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (response != EnumWorldAccessResponse.Granted)
|
||||||
|
return response;
|
||||||
|
|
||||||
|
BlockPos pos = blockSel.Position;
|
||||||
|
|
||||||
|
foreach (int groupId in bufferIndex.CandidateGroupIds(pos))
|
||||||
|
{
|
||||||
|
if (IsMember(groupId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool inBuffer = false;
|
||||||
|
foreach (Cuboidi buffer in bufferIndex.CachedBuffer(groupId))
|
||||||
|
if (buffer.Contains(pos))
|
||||||
|
{
|
||||||
|
inBuffer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!inBuffer)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
EnumBlockAccessFlags granted = knownEntries.TryGetValue(groupId, out var entry)
|
||||||
|
? (EnumBlockAccessFlags)entry.GrantedAccess
|
||||||
|
: EnumBlockAccessFlags.None;
|
||||||
|
if ((granted & accessType) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (HasClaimAccess(pos, accessType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
claimant = "custommessage-claimlink-buffer-zone";
|
||||||
|
return EnumWorldAccessResponse.DeniedByMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMember(int groupId)
|
||||||
|
{
|
||||||
|
foreach (PlayerGroupMembership membership in capi.World.Player.Groups)
|
||||||
|
if (membership.GroupUid == groupId)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasClaimAccess(BlockPos pos, EnumBlockAccessFlags accessFlag)
|
||||||
|
{
|
||||||
|
LandClaim[]? claims = capi.World.Claims.Get(pos);
|
||||||
|
if (claims == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (LandClaim claim in claims)
|
||||||
|
{
|
||||||
|
if (claim.TestPlayerAccess(capi.World.Player, accessFlag) != EnumPlayerAccessResult.Denied)
|
||||||
|
return true;
|
||||||
|
if (accessFlag == EnumBlockAccessFlags.Use && claim.AllowUseEveryone)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderHighlights()
|
||||||
|
{
|
||||||
|
List<BlockPos> positions = new();
|
||||||
|
List<int> colors = new();
|
||||||
|
|
||||||
|
foreach ((int groupId, ClaimLinkBufferEntryDto entry) in knownEntries)
|
||||||
|
{
|
||||||
|
int color = HashToColor(groupId);
|
||||||
|
foreach (ClaimLinkBufferCuboidDto cuboid in entry.Cuboids)
|
||||||
|
{
|
||||||
|
positions.Add(new BlockPos(cuboid.MinX, cuboid.MinY, cuboid.MinZ));
|
||||||
|
positions.Add(new BlockPos(cuboid.MaxX, cuboid.MaxY, cuboid.MaxZ));
|
||||||
|
colors.Add(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
capi.World.HighlightBlocks(
|
||||||
|
capi.World.Player,
|
||||||
|
HighlightSlotId,
|
||||||
|
positions,
|
||||||
|
colors,
|
||||||
|
EnumHighlightBlocksMode.Absolute,
|
||||||
|
EnumHighlightShape.Cubes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Clear() =>
|
||||||
|
capi.World.HighlightBlocks(
|
||||||
|
capi.World.Player,
|
||||||
|
HighlightSlotId,
|
||||||
|
new List<BlockPos>(),
|
||||||
|
new List<int>()
|
||||||
|
);
|
||||||
|
|
||||||
|
private static int HashToColor(int groupId)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
uint h = (uint)groupId * config.ColorHashSeed;
|
||||||
|
int r = (byte)(h >> 16);
|
||||||
|
int g = (byte)(h >> 8);
|
||||||
|
int b = (byte)h;
|
||||||
|
return ColorUtil.ColorFromRgba(r, g, b, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ClaimLink;
|
||||||
|
|
||||||
|
internal class ClaimLinkVisualizerConfig
|
||||||
|
{
|
||||||
|
public uint ColorHashSeed = 2654435761u;
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ProtoBuf;
|
using ProtoBuf;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
using Vintagestory.API.MathTools;
|
using Vintagestory.API.MathTools;
|
||||||
using Vintagestory.API.Server;
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
namespace ClaimLink;
|
namespace ClaimLink;
|
||||||
|
|
||||||
[ProtoContract]
|
|
||||||
public class ClaimLinkBufferRequest { }
|
|
||||||
|
|
||||||
[ProtoContract]
|
[ProtoContract]
|
||||||
public class ClaimLinkBufferCuboidDto
|
public class ClaimLinkBufferCuboidDto
|
||||||
{
|
{
|
||||||
@@ -38,6 +36,9 @@ public class ClaimLinkBufferEntryDto
|
|||||||
|
|
||||||
[ProtoMember(2)]
|
[ProtoMember(2)]
|
||||||
public List<ClaimLinkBufferCuboidDto> Cuboids = new();
|
public List<ClaimLinkBufferCuboidDto> Cuboids = new();
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public int GrantedAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtoContract]
|
[ProtoContract]
|
||||||
@@ -45,63 +46,80 @@ public class ClaimLinkBufferResponse
|
|||||||
{
|
{
|
||||||
[ProtoMember(1)]
|
[ProtoMember(1)]
|
||||||
public List<ClaimLinkBufferEntryDto> Entries = new();
|
public List<ClaimLinkBufferEntryDto> Entries = new();
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public bool IsFullSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class ClaimLinkVisualizerNetwork
|
internal static class ClaimLinkVisualizerNetwork
|
||||||
{
|
{
|
||||||
internal const string ChannelName = "claimlinkvisualizer";
|
internal const string ChannelName = "claimlinkvisualizer";
|
||||||
|
private static IServerNetworkChannel channel = null!;
|
||||||
|
|
||||||
internal static void Start(ICoreServerAPI api)
|
internal static void Start(ICoreServerAPI api)
|
||||||
{
|
{
|
||||||
api.Network
|
channel = api.Network.RegisterChannel(ChannelName).RegisterMessageType<ClaimLinkBufferResponse>();
|
||||||
.RegisterChannel(ChannelName)
|
|
||||||
.RegisterMessageType<ClaimLinkBufferRequest>()
|
api.Event.PlayerNowPlaying += SendFullSync;
|
||||||
.RegisterMessageType<ClaimLinkBufferResponse>()
|
|
||||||
.SetMessageHandler<ClaimLinkBufferRequest>((fromPlayer, _) =>
|
|
||||||
SendBuffers(api, fromPlayer)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SendBuffers(ICoreServerAPI api, IServerPlayer toPlayer)
|
private static void SendFullSync(IServerPlayer toPlayer)
|
||||||
{
|
{
|
||||||
ClaimLinkBufferResponse response = new();
|
ClaimLinkBufferResponse response = new() { IsFullSync = true };
|
||||||
|
|
||||||
BlockPos playerPos = toPlayer.Entity.Pos.AsBlockPos;
|
|
||||||
int radius = toPlayer.WorldData.LastApprovedViewDistance;
|
|
||||||
|
|
||||||
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
||||||
{
|
{
|
||||||
ClaimLinkBufferEntryDto entry = new() { GroupId = groupId };
|
ClaimLinkBufferEntryDto entry = BuildEntry(groupId, toPlayer);
|
||||||
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)
|
if (entry.Cuboids.Count > 0)
|
||||||
response.Entries.Add(entry);
|
response.Entries.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
api.Network.GetChannel(ChannelName).SendPacket(response, toPlayer);
|
channel.SendPacket(response, toPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsNearby(Cuboidi cuboid, BlockPos playerPos, int radius) =>
|
internal static void BroadcastDelta(IReadOnlyCollection<int> affectedGroupIds)
|
||||||
cuboid.MinX <= playerPos.X + radius
|
{
|
||||||
&& cuboid.MaxX >= playerPos.X - radius
|
if (affectedGroupIds.Count == 0 || channel == null)
|
||||||
&& cuboid.MinY <= playerPos.Y + radius
|
return;
|
||||||
&& cuboid.MaxY >= playerPos.Y - radius
|
|
||||||
&& cuboid.MinZ <= playerPos.Z + radius
|
foreach (IPlayer onlinePlayer in ClaimLinkModSystem.World.AllOnlinePlayers)
|
||||||
&& cuboid.MaxZ >= playerPos.Z - radius;
|
{
|
||||||
|
if (onlinePlayer is not IServerPlayer serverPlayer)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ClaimLinkBufferResponse response = new() { IsFullSync = false };
|
||||||
|
foreach (int groupId in affectedGroupIds)
|
||||||
|
response.Entries.Add(BuildEntry(groupId, serverPlayer));
|
||||||
|
|
||||||
|
channel.SendPacket(response, serverPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClaimLinkBufferEntryDto BuildEntry(int groupId, IServerPlayer forPlayer)
|
||||||
|
{
|
||||||
|
EnumBlockAccessFlags granted = EnumBlockAccessFlags.None;
|
||||||
|
if (ClaimLinkModSystem.Registry.HasPermission(groupId, forPlayer, EnumBlockAccessFlags.BuildOrBreak))
|
||||||
|
granted |= EnumBlockAccessFlags.BuildOrBreak;
|
||||||
|
if (ClaimLinkModSystem.Registry.HasPermission(groupId, forPlayer, EnumBlockAccessFlags.Use))
|
||||||
|
granted |= EnumBlockAccessFlags.Use;
|
||||||
|
|
||||||
|
ClaimLinkBufferEntryDto entry = new() { GroupId = groupId, GrantedAccess = (int)granted };
|
||||||
|
|
||||||
|
foreach (Cuboidi cuboid in ClaimLinkBuffer.GetBuffer(groupId))
|
||||||
|
{
|
||||||
|
entry.Cuboids.Add(
|
||||||
|
new ClaimLinkBufferCuboidDto
|
||||||
|
{
|
||||||
|
MinX = cuboid.MinX,
|
||||||
|
MinY = cuboid.MinY,
|
||||||
|
MinZ = cuboid.MinZ,
|
||||||
|
MaxX = cuboid.MaxX,
|
||||||
|
MaxY = cuboid.MaxY,
|
||||||
|
MaxZ = cuboid.MaxZ,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
|
namespace ClaimLink;
|
||||||
|
|
||||||
|
internal delegate TextCommandResult? HookBeforeDelegate(TextCommandCallingArgs args);
|
||||||
|
|
||||||
|
internal delegate void HookAfterDelegate(TextCommandCallingArgs args, TextCommandResult result);
|
||||||
|
|
||||||
|
internal class CommandHookListenerProxy : DispatchProxy
|
||||||
|
{
|
||||||
|
private string modId = null!;
|
||||||
|
private IReadOnlyList<string> commands = null!;
|
||||||
|
private HookBeforeDelegate? before;
|
||||||
|
private HookAfterDelegate? after;
|
||||||
|
|
||||||
|
internal static CommandHook.ICommandHookListener Create(
|
||||||
|
string modId,
|
||||||
|
IReadOnlyList<string> commands,
|
||||||
|
HookBeforeDelegate? before,
|
||||||
|
HookAfterDelegate? after
|
||||||
|
)
|
||||||
|
{
|
||||||
|
object proxy = Create<CommandHook.ICommandHookListener, CommandHookListenerProxy>()!;
|
||||||
|
CommandHookListenerProxy p = (CommandHookListenerProxy)proxy;
|
||||||
|
p.modId = modId;
|
||||||
|
p.commands = commands;
|
||||||
|
p.before = before;
|
||||||
|
p.after = after;
|
||||||
|
return (CommandHook.ICommandHookListener)proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) =>
|
||||||
|
targetMethod!.Name switch
|
||||||
|
{
|
||||||
|
"get_ModId" => modId,
|
||||||
|
"get_Commands" => commands,
|
||||||
|
"get_Registration" => new CommandHook.CommandRegistration(
|
||||||
|
before == null ? null : new CommandHook.CommandBeforeDelegate(before),
|
||||||
|
after == null ? null : new CommandHook.CommandAfterDelegate(after)
|
||||||
|
),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CommandHook;
|
|
||||||
using Vintagestory.API.Common;
|
using Vintagestory.API.Common;
|
||||||
using Vintagestory.API.Config;
|
using Vintagestory.API.Config;
|
||||||
using Vintagestory.API.MathTools;
|
using Vintagestory.API.MathTools;
|
||||||
@@ -7,14 +6,12 @@ using Vintagestory.API.Server;
|
|||||||
|
|
||||||
namespace ClaimLink;
|
namespace ClaimLink;
|
||||||
|
|
||||||
public class CommandListener : ICommandHookListener
|
public class CommandListener
|
||||||
{
|
{
|
||||||
public string ModId => "claimlink";
|
public string ModId => "claimlink";
|
||||||
|
|
||||||
public IReadOnlyList<string> Commands => new[] { "land" };
|
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, int> PendingLoads = new Dictionary<string, int>();
|
||||||
internal static Dictionary<string, List<Cuboidi>?> PendingSaveChecks =
|
internal static Dictionary<string, List<Cuboidi>?> PendingSaveChecks =
|
||||||
new Dictionary<string, List<Cuboidi>?>();
|
new Dictionary<string, List<Cuboidi>?>();
|
||||||
@@ -57,13 +54,13 @@ public class CommandListener : ICommandHookListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextCommandResult? Before(TextCommandCallingArgs args)
|
internal TextCommandResult? Before(TextCommandCallingArgs args)
|
||||||
{
|
{
|
||||||
Dispatch(args);
|
Dispatch(args);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void After(TextCommandCallingArgs args, TextCommandResult result)
|
internal void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||||
{
|
{
|
||||||
if (args.Caller.Player == null)
|
if (args.Caller.Player == null)
|
||||||
return;
|
return;
|
||||||
@@ -114,7 +111,7 @@ public class CommandListener : ICommandHookListener
|
|||||||
|
|
||||||
if (!violatesClaimLink && !violatesClaim)
|
if (!violatesClaimLink && !violatesClaim)
|
||||||
{
|
{
|
||||||
ClaimLinkBufferIndex.Rebuild();
|
ClaimLinkModSystem.RebuildBufferIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,19 +273,17 @@ public class CommandListener : ICommandHookListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GroupCommandListener : ICommandHookListener
|
public class GroupCommandListener
|
||||||
{
|
{
|
||||||
public string ModId => "claimlink.group";
|
public string ModId => "claimlink.group";
|
||||||
|
|
||||||
public IReadOnlyList<string> Commands => new[] { "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, int> PendingDisbands = new Dictionary<string, int>();
|
||||||
internal static Dictionary<string, (string AffectedUid, int GroupId)> PendingMembershipChecks =
|
internal static Dictionary<string, (string AffectedUid, int GroupId)> PendingMembershipChecks =
|
||||||
new Dictionary<string, (string, int)>();
|
new Dictionary<string, (string, int)>();
|
||||||
|
|
||||||
private TextCommandResult? Before(TextCommandCallingArgs args)
|
internal TextCommandResult? Before(TextCommandCallingArgs args)
|
||||||
{
|
{
|
||||||
if (args.Caller.Player == null)
|
if (args.Caller.Player == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -370,7 +365,7 @@ public class GroupCommandListener : ICommandHookListener
|
|||||||
PendingMembershipChecks[callerUid] = (targetUid, (int)groupId);
|
PendingMembershipChecks[callerUid] = (targetUid, (int)groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void After(TextCommandCallingArgs args, TextCommandResult result)
|
internal void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||||
{
|
{
|
||||||
if (args.Caller.Player == null)
|
if (args.Caller.Player == null)
|
||||||
return;
|
return;
|
||||||
@@ -390,6 +385,8 @@ public class GroupCommandListener : ICommandHookListener
|
|||||||
|
|
||||||
if (ClaimLinkModSystem.Registry.Exists(disbandedGroupId))
|
if (ClaimLinkModSystem.Registry.Exists(disbandedGroupId))
|
||||||
ClaimLinkModSystem.Registry.Remove(disbandedGroupId);
|
ClaimLinkModSystem.Registry.Remove(disbandedGroupId);
|
||||||
|
|
||||||
|
ClaimLinkModSystem.Registry.RemoveGroupFromAllPermissions(disbandedGroupId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
"type": "Code",
|
"type": "Code",
|
||||||
"modid": "claimlink",
|
"modid": "claimlink",
|
||||||
"name": "ClaimLink",
|
"name": "ClaimLink",
|
||||||
"side": "Server",
|
"side": "Universal",
|
||||||
"authors": [
|
"authors": [
|
||||||
"anth64"
|
"anth64"
|
||||||
],
|
],
|
||||||
"description": "Link claims together with groups.",
|
"description": "Link claims together with groups.",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"game": "1.22.5",
|
"game": "1.22.5"
|
||||||
"commandhook": "2.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,54 @@
|
|||||||
# ClaimLink
|
# ClaimLink
|
||||||
|
|
||||||
A modification for claims in Vintage Story
|
Links vanilla Vintage Story land claims to an existing player group and adds a protected buffer zone around the linked area. Requires [CommandHook](https://mods.vintagestory.at/show/mod/56633) 2.1.0+.
|
||||||
|
|
||||||
|
Root command: `/claimlink` (aliases: `/clink`, `/claiml`, `/cl`). Requires the `chat` privilege.
|
||||||
|
|
||||||
|
Most actions that change state (`new`, `link`, `unlink`, `kick`, `delete`, `transferownership`) stage the change and ask you to confirm it with `/claimlink confirm` or back out with `/claimlink cancel`. Buffer permission commands (`allow`, `allowgroup`, `deny`, `denygroup`) and read-only commands (`info`, `list`) apply immediately.
|
||||||
|
|
||||||
|
A `groupname` argument can usually be left off if you're sending the command from that group's chat channel, it'll infer the group from context, same as vanilla's own `/group` commands.
|
||||||
|
|
||||||
|
## Player commands
|
||||||
|
|
||||||
|
| Command | Args | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `new` (`n`) | `[groupname]` | Promote a group you own into a claim link. Owner only. |
|
||||||
|
| `link` (`l`) | `<claim> [groupname]` | Link a claim you own into a claim link. Must be a member. Rejected if the claim's buffer would overlap another claim link's buffer or another player's raw claim. |
|
||||||
|
| `unlink` (`ul`) | `<claim>` | Remove one of your claims from its claim link. |
|
||||||
|
| `confirm` (`c`) | | Confirm your pending staged action. |
|
||||||
|
| `cancel` | | Cancel your pending staged action. |
|
||||||
|
| `kick` | `<groupname> [playername]` | Force-unlink all of a player's claims from the claim link. Owner or op only. |
|
||||||
|
| `allow` | `<playername> <use\|build\|all> [groupname]` | Grant a player buffer access without adding them to the group. Owner or op only. |
|
||||||
|
| `allowgroup` | `<targetgroup> <use\|build\|all> [groupname]` | Grant a whole other player group buffer access. Owner or op only. |
|
||||||
|
| `deny` | `<groupname> [playername]` | Revoke a player's buffer access. Owner or op only. |
|
||||||
|
| `denygroup` | `<groupname> [targetgroup]` | Revoke a group's buffer access. Owner or op only. |
|
||||||
|
| `delete` | `[groupname]` | Delete a claim link (the underlying vanilla group is untouched, it just stops being a claim link). Owner only. |
|
||||||
|
| `transferownership` (`transfer`, `to`) | `<groupname> [playername]` | Transfer ownership of the claim link to another member. Owner only. |
|
||||||
|
| `info` (`i`) | `[groupname]` | Show a claim link's members and linked claims. Full detail (claims, granted access) only shown to members or ops with `controlserver`; others just see the member list. |
|
||||||
|
| `list` (`ls`) | | List every claim link on the server and its member count. |
|
||||||
|
|
||||||
|
`claim` arguments are the claim's index in `/land claim list` (your own claim numbering, same as vanilla).
|
||||||
|
|
||||||
|
`allow`/`allowgroup`/`deny`/`denygroup` access levels: `use` (interact), `build` (place/break), `all` (both).
|
||||||
|
|
||||||
|
## Admin commands
|
||||||
|
|
||||||
|
`/claimlink admin` (alias `/claimlink a`), requires `controlserver`. Same shape as the player commands but works on any claim link regardless of ownership or membership, and skips the confirm/cancel staging (instant).
|
||||||
|
|
||||||
|
| Command | Args | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `admin delete` (`del`) | `[groupname]` | Delete any claim link. |
|
||||||
|
| `admin unlink` | `<playername> <claim>` | Unlink a specific claim from any claim link. |
|
||||||
|
| `admin kick` | `<groupname> [playername]` | Force-unlink all of a player's claims from any claim link. |
|
||||||
|
| `admin transferownership` (`transfer`, `to`) | `<playername> [groupname]` | Force-transfer ownership of any claim link. |
|
||||||
|
| `admin allow` | `<playername> <use\|build\|all> [groupname]` | Grant buffer access on any claim link. |
|
||||||
|
| `admin allowgroup` | `<targetgroup> <use\|build\|all> [groupname]` | Grant a group buffer access on any claim link. |
|
||||||
|
| `admin deny` | `<groupname> [playername]` | Revoke a player's buffer access on any claim link. |
|
||||||
|
| `admin denygroup` | `<groupname> [targetgroup]` | Revoke a group's buffer access on any claim link. |
|
||||||
|
|
||||||
|
## How the buffer works
|
||||||
|
|
||||||
|
A vanilla claim can be made of multiple cuboid areas (grown one face-adjacent cuboid at a time), and the buffer follows the same shape: each area gets its own grown box, outward by a configurable margin (`BufferSize`, default 16 blocks, in `ModConfig/claimlink.json`) on every axis. Areas close enough for their grown boxes to touch or overlap, whether from the same claim or different claims linked to the same claim link, read as one continuous protected area. Linking a claim, or growing an existing linked claim via `/land claim save`, checks the resulting buffer against every other claim link's buffer and every other player's raw claim, and rejects the change if it would overlap either. Your own claim link is exempt from that check, so its own areas are free to overlap or touch each other, which is exactly how they end up reading as one continuous protected area. Only overlap with a different claim link, or with someone else's claim, is blocked.
|
||||||
|
|
||||||
|
Buffer permissions only cover the empty space around the linked claims, not the claims themselves. In that space, only members of the claim link's group, or anyone explicitly granted access via `allow`/`allowgroup`, can place, break, or use blocks; grant level (`use`/`build`/`all`) controls which. On the ground of a claim that's actually part of the claim link, normal vanilla permissions (owner, claim's group, explicit permission, `AllowUseEveryone`) decide access instead, same as any other claim.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user