Compare commits
7
Commits
8cedbc841b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f30fc626c | ||
|
|
05ff998287 | ||
|
|
cac8e590d3 | ||
|
|
62c94bc775 | ||
|
|
b14eb5c538 | ||
|
|
8f0e4bd687 | ||
|
|
725f450da7 |
@@ -1,25 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Vintagestory.API.MathTools;
|
||||
|
||||
namespace ClaimLink;
|
||||
|
||||
internal static class ClaimLinkBufferIndex
|
||||
internal class ClaimLinkBufferIndex
|
||||
{
|
||||
private static readonly Dictionary<long, HashSet<int>> groupIdsByRegion = new();
|
||||
private static readonly Dictionary<int, List<Cuboidi>> buffersByGroupId = new();
|
||||
private readonly Func<int, int, long> regionIndex;
|
||||
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();
|
||||
buffersByGroupId.Clear();
|
||||
|
||||
int regionSize = ClaimLinkModSystem.WorldManager.RegionSize;
|
||||
if (regionSize <= 0)
|
||||
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;
|
||||
|
||||
HashSet<long> regionKeys = new();
|
||||
@@ -32,7 +39,7 @@ internal static class ClaimLinkBufferIndex
|
||||
|
||||
for (int rx = minRegionX; rx <= maxRegionX; rx++)
|
||||
for (int rz = minRegionZ; rz <= maxRegionZ; rz++)
|
||||
regionKeys.Add(ClaimLinkModSystem.WorldManager.MapRegionIndex2D(rx, rz));
|
||||
regionKeys.Add(regionIndex(rx, rz));
|
||||
}
|
||||
|
||||
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)
|
||||
return System.Array.Empty<int>();
|
||||
return Array.Empty<int>();
|
||||
|
||||
long regionKey = ClaimLinkModSystem.WorldManager.MapRegionIndex2D(
|
||||
pos.X / regionSize,
|
||||
pos.Z / regionSize
|
||||
);
|
||||
long regionKey = regionIndex(pos.X / regionSize, pos.Z / regionSize);
|
||||
|
||||
return groupIdsByRegion.TryGetValue(regionKey, out HashSet<int>? 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)
|
||||
? buffers
|
||||
: new List<Cuboidi>();
|
||||
|
||||
@@ -34,12 +34,12 @@ internal static class ClaimLinkBufferProtection
|
||||
{
|
||||
bool allowed = true;
|
||||
|
||||
foreach (int groupId in ClaimLinkBufferIndex.CandidateGroupIds(pos))
|
||||
foreach (int groupId in ClaimLinkModSystem.BufferIndex.CandidateGroupIds(pos))
|
||||
{
|
||||
if (IsMember(byPlayer, groupId))
|
||||
continue;
|
||||
|
||||
foreach (Cuboidi buffer in ClaimLinkBufferIndex.CachedBuffer(groupId))
|
||||
foreach (Cuboidi buffer in ClaimLinkModSystem.BufferIndex.CachedBuffer(groupId))
|
||||
if (
|
||||
buffer.Contains(pos)
|
||||
&& !ClaimLinkModSystem.Registry.HasPermission(groupId, byPlayer, accessFlag)
|
||||
|
||||
@@ -1034,6 +1034,9 @@ public static class ClaimLinkChatCommand
|
||||
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)
|
||||
{
|
||||
int memberCount = GroupMemberCount(groupId);
|
||||
@@ -1045,7 +1048,7 @@ public static class ClaimLinkChatCommand
|
||||
|
||||
foreach (string uid in GroupMemberUids(groupId))
|
||||
{
|
||||
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
|
||||
string name = PlayerNameForUid(uid);
|
||||
|
||||
if (!showDetails)
|
||||
{
|
||||
@@ -1070,7 +1073,7 @@ public static class ClaimLinkChatCommand
|
||||
sb.AppendLine("Buffer access granted:");
|
||||
foreach (var (uid, flags) in permittedPlayers)
|
||||
{
|
||||
string name = ClaimLinkModSystem.World.PlayerByUid(uid)?.PlayerName ?? uid;
|
||||
string name = PlayerNameForUid(uid);
|
||||
sb.AppendLine($" {name}: {DescribeAccess(flags)}");
|
||||
}
|
||||
foreach (var (targetGroupId, flags) in permittedGroups)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using CommandHook;
|
||||
using Vintagestory.API.Client;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.Server;
|
||||
|
||||
@@ -15,10 +15,9 @@ public class ClaimLinkModSystem : ModSystem
|
||||
internal static IWorldManagerAPI WorldManager = null!;
|
||||
internal static IPlayerDataManager PlayerData = null!;
|
||||
internal static ClaimLinkConfig Config = null!;
|
||||
internal CommandListener? CmdListener;
|
||||
internal GroupCommandListener? GroupCmdListener;
|
||||
|
||||
public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server;
|
||||
internal static ClaimLinkBufferIndex BufferIndex = null!;
|
||||
private object? cmdListenerProxy;
|
||||
private object? groupCmdListenerProxy;
|
||||
|
||||
public override void StartServerSide(ICoreServerAPI api)
|
||||
{
|
||||
@@ -31,28 +30,79 @@ public class ClaimLinkModSystem : ModSystem
|
||||
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;
|
||||
BufferIndex = new ClaimLinkBufferIndex(WorldManager.MapRegionIndex2D);
|
||||
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);
|
||||
ClaimLinkVisualizerNetwork.Start(api);
|
||||
ClaimLinkBufferProtection.Register(api);
|
||||
|
||||
api.Event.PlayerDisconnect += CommandListener.OnPlayerDisconnect;
|
||||
api.Event.PlayerDisconnect += GroupCommandListener.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()
|
||||
{
|
||||
if (CmdListener != null)
|
||||
CommandHookModSystem.Unregister(CmdListener);
|
||||
if (GroupCmdListener != null)
|
||||
CommandHookModSystem.Unregister(GroupCmdListener);
|
||||
if (cmdListenerProxy != null || groupCmdListenerProxy != null)
|
||||
UnregisterCommandHooks();
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ClaimLinkRegistry
|
||||
public void Add(int groupId)
|
||||
{
|
||||
claimLinks.Add(groupId);
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
public void Remove(int groupId)
|
||||
@@ -69,7 +69,7 @@ public class ClaimLinkRegistry
|
||||
permittedPlayerUids.Remove(groupId);
|
||||
permittedPlayerGroupIds.Remove(groupId);
|
||||
Prune();
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
public void GrantPlayer(int groupId, string uid, EnumBlockAccessFlags flags)
|
||||
@@ -77,7 +77,7 @@ public class ClaimLinkRegistry
|
||||
if (!permittedPlayerUids.TryGetValue(groupId, out var uids))
|
||||
permittedPlayerUids[groupId] = uids = new();
|
||||
uids[uid] = flags;
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
public bool RevokePlayer(int groupId, string uid)
|
||||
@@ -87,7 +87,7 @@ public class ClaimLinkRegistry
|
||||
return false;
|
||||
if (uids!.Count == 0)
|
||||
permittedPlayerUids.Remove(groupId);
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class ClaimLinkRegistry
|
||||
if (!permittedPlayerGroupIds.TryGetValue(groupId, out var groups))
|
||||
permittedPlayerGroupIds[groupId] = groups = new();
|
||||
groups[targetGroupId] = flags;
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
public bool RevokeGroup(int groupId, int targetGroupId)
|
||||
@@ -108,7 +108,7 @@ public class ClaimLinkRegistry
|
||||
return false;
|
||||
if (groups!.Count == 0)
|
||||
permittedPlayerGroupIds.Remove(groupId);
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -138,12 +138,12 @@ public class ClaimLinkRegistry
|
||||
|
||||
public void RemoveGroupFromAllPermissions(int targetGroupId)
|
||||
{
|
||||
bool changed = false;
|
||||
foreach (var groups in permittedPlayerGroupIds.Values)
|
||||
List<int>? changedGroupIds = null;
|
||||
foreach (var (groupId, groups) in permittedPlayerGroupIds)
|
||||
if (groups.Remove(targetGroupId))
|
||||
changed = true;
|
||||
(changedGroupIds ??= new()).Add(groupId);
|
||||
|
||||
if (!changed)
|
||||
if (changedGroupIds == null)
|
||||
return;
|
||||
|
||||
List<int>? emptyGroupIds = null;
|
||||
@@ -154,7 +154,7 @@ public class ClaimLinkRegistry
|
||||
foreach (int groupId in emptyGroupIds)
|
||||
permittedPlayerGroupIds.Remove(groupId);
|
||||
|
||||
Save();
|
||||
Save(changedGroupIds);
|
||||
}
|
||||
|
||||
public bool IsClaimLinked(string uid, int claimIndex) =>
|
||||
@@ -181,7 +181,7 @@ public class ClaimLinkRegistry
|
||||
if (!groups.TryGetValue(groupId, out var claims))
|
||||
groups[groupId] = claims = new();
|
||||
claims.Add(claimIndex);
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ public class ClaimLinkRegistry
|
||||
if (entries.TryGetValue(uid, out var groups) && groups.TryGetValue(groupId, out var claims))
|
||||
claims.Remove(claimIndex);
|
||||
Prune();
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
public void ClaimRemoved(string uid, int removedIndex)
|
||||
@@ -198,6 +198,8 @@ public class ClaimLinkRegistry
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
return;
|
||||
|
||||
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
||||
|
||||
foreach (var claims in groups.Values)
|
||||
{
|
||||
claims.Remove(removedIndex);
|
||||
@@ -207,7 +209,7 @@ public class ClaimLinkRegistry
|
||||
}
|
||||
|
||||
Prune();
|
||||
Save();
|
||||
Save(affectedGroupIds);
|
||||
}
|
||||
|
||||
public void ClaimSaved(string uid, int claimIndex)
|
||||
@@ -215,6 +217,8 @@ public class ClaimLinkRegistry
|
||||
if (!entries.TryGetValue(uid, out var groups))
|
||||
return;
|
||||
|
||||
int[] affectedGroupIds = new List<int>(groups.Keys).ToArray();
|
||||
|
||||
int count = 0;
|
||||
foreach (Vintagestory.API.Common.LandClaim c in ClaimLinkModSystem.LandClaimAPI.All)
|
||||
if (c.OwnedByPlayerUid == uid)
|
||||
@@ -233,7 +237,7 @@ public class ClaimLinkRegistry
|
||||
claims[i] = newIndex;
|
||||
}
|
||||
|
||||
Save();
|
||||
Save(affectedGroupIds);
|
||||
}
|
||||
|
||||
public bool HasAnyEntry(string uid, int groupId) =>
|
||||
@@ -246,13 +250,17 @@ public class ClaimLinkRegistry
|
||||
if (entries.TryGetValue(uid, out var groups))
|
||||
groups.Remove(groupId);
|
||||
Prune();
|
||||
Save();
|
||||
Save(new[] { groupId });
|
||||
}
|
||||
|
||||
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);
|
||||
Save();
|
||||
Save(affectedGroupIds);
|
||||
}
|
||||
|
||||
public IEnumerable<string> MemberUidsForGroup(int groupId)
|
||||
@@ -286,7 +294,7 @@ public class ClaimLinkRegistry
|
||||
entries.Remove(uid);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
private void Save(IReadOnlyCollection<int> affectedGroupIds)
|
||||
{
|
||||
var entryList = new List<ClaimLinkEntrySaveData>();
|
||||
foreach (var (uid, groups) in entries)
|
||||
@@ -331,6 +339,7 @@ public class ClaimLinkRegistry
|
||||
}
|
||||
);
|
||||
|
||||
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 ProtoBuf;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.MathTools;
|
||||
using Vintagestory.API.Server;
|
||||
|
||||
namespace ClaimLink;
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferRequest { }
|
||||
|
||||
[ProtoContract]
|
||||
public class ClaimLinkBufferCuboidDto
|
||||
{
|
||||
@@ -38,6 +36,9 @@ public class ClaimLinkBufferEntryDto
|
||||
|
||||
[ProtoMember(2)]
|
||||
public List<ClaimLinkBufferCuboidDto> Cuboids = new();
|
||||
|
||||
[ProtoMember(3)]
|
||||
public int GrantedAccess;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -45,37 +46,67 @@ public class ClaimLinkBufferResponse
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public List<ClaimLinkBufferEntryDto> Entries = new();
|
||||
|
||||
[ProtoMember(2)]
|
||||
public bool IsFullSync;
|
||||
}
|
||||
|
||||
internal static class ClaimLinkVisualizerNetwork
|
||||
{
|
||||
internal const string ChannelName = "claimlinkvisualizer";
|
||||
private static IServerNetworkChannel channel = null!;
|
||||
|
||||
internal static void Start(ICoreServerAPI api)
|
||||
{
|
||||
api.Network.RegisterChannel(ChannelName)
|
||||
.RegisterMessageType<ClaimLinkBufferRequest>()
|
||||
.RegisterMessageType<ClaimLinkBufferResponse>()
|
||||
.SetMessageHandler<ClaimLinkBufferRequest>(
|
||||
(fromPlayer, _) => SendBuffers(api, fromPlayer)
|
||||
);
|
||||
channel = api.Network.RegisterChannel(ChannelName).RegisterMessageType<ClaimLinkBufferResponse>();
|
||||
|
||||
api.Event.PlayerNowPlaying += SendFullSync;
|
||||
}
|
||||
|
||||
private static void SendBuffers(ICoreServerAPI api, IServerPlayer toPlayer)
|
||||
private static void SendFullSync(IServerPlayer toPlayer)
|
||||
{
|
||||
ClaimLinkBufferResponse response = new();
|
||||
|
||||
BlockPos playerPos = toPlayer.Entity.Pos.AsBlockPos;
|
||||
int radius = toPlayer.WorldData.LastApprovedViewDistance;
|
||||
ClaimLinkBufferResponse response = new() { IsFullSync = true };
|
||||
|
||||
foreach (int groupId in ClaimLinkModSystem.Registry.All)
|
||||
{
|
||||
ClaimLinkBufferEntryDto entry = new() { GroupId = groupId };
|
||||
foreach (Cuboidi cuboid in ClaimLinkBuffer.GetBuffer(groupId))
|
||||
ClaimLinkBufferEntryDto entry = BuildEntry(groupId, toPlayer);
|
||||
if (entry.Cuboids.Count > 0)
|
||||
response.Entries.Add(entry);
|
||||
}
|
||||
|
||||
channel.SendPacket(response, toPlayer);
|
||||
}
|
||||
|
||||
internal static void BroadcastDelta(IReadOnlyCollection<int> affectedGroupIds)
|
||||
{
|
||||
if (!IsNearby(cuboid, playerPos, radius))
|
||||
if (affectedGroupIds.Count == 0 || channel == null)
|
||||
return;
|
||||
|
||||
foreach (IPlayer onlinePlayer in ClaimLinkModSystem.World.AllOnlinePlayers)
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -89,18 +120,6 @@ internal static class ClaimLinkVisualizerNetwork
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.Cuboids.Count > 0)
|
||||
response.Entries.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
api.Network.GetChannel(ChannelName).SendPacket(response, toPlayer);
|
||||
}
|
||||
|
||||
private static bool IsNearby(Cuboidi cuboid, BlockPos playerPos, int radius) =>
|
||||
cuboid.MinX <= playerPos.X + radius
|
||||
&& cuboid.MaxX >= playerPos.X - radius
|
||||
&& cuboid.MinY <= playerPos.Y + radius
|
||||
&& cuboid.MaxY >= playerPos.Y - radius
|
||||
&& cuboid.MinZ <= playerPos.Z + radius
|
||||
&& cuboid.MaxZ >= playerPos.Z - radius;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,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 CommandHook;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.Config;
|
||||
using Vintagestory.API.MathTools;
|
||||
@@ -7,14 +6,12 @@ using Vintagestory.API.Server;
|
||||
|
||||
namespace ClaimLink;
|
||||
|
||||
public class CommandListener : ICommandHookListener
|
||||
public class CommandListener
|
||||
{
|
||||
public string ModId => "claimlink";
|
||||
|
||||
public IReadOnlyList<string> Commands => new[] { "land" };
|
||||
|
||||
public CommandRegistration Registration => new(Before, After);
|
||||
|
||||
internal static Dictionary<string, int> PendingLoads = new Dictionary<string, int>();
|
||||
internal static Dictionary<string, List<Cuboidi>?> PendingSaveChecks =
|
||||
new Dictionary<string, List<Cuboidi>?>();
|
||||
@@ -57,13 +54,13 @@ public class CommandListener : ICommandHookListener
|
||||
}
|
||||
}
|
||||
|
||||
private TextCommandResult? Before(TextCommandCallingArgs args)
|
||||
internal TextCommandResult? Before(TextCommandCallingArgs args)
|
||||
{
|
||||
Dispatch(args);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||
internal void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||
{
|
||||
if (args.Caller.Player == null)
|
||||
return;
|
||||
@@ -114,7 +111,7 @@ public class CommandListener : ICommandHookListener
|
||||
|
||||
if (!violatesClaimLink && !violatesClaim)
|
||||
{
|
||||
ClaimLinkBufferIndex.Rebuild();
|
||||
ClaimLinkModSystem.RebuildBufferIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,19 +273,17 @@ public class CommandListener : ICommandHookListener
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupCommandListener : ICommandHookListener
|
||||
public class GroupCommandListener
|
||||
{
|
||||
public string ModId => "claimlink.group";
|
||||
|
||||
public IReadOnlyList<string> Commands => new[] { "group" };
|
||||
|
||||
public CommandRegistration Registration => new(Before, After);
|
||||
|
||||
internal static Dictionary<string, int> PendingDisbands = new Dictionary<string, int>();
|
||||
internal static Dictionary<string, (string AffectedUid, int GroupId)> PendingMembershipChecks =
|
||||
new Dictionary<string, (string, int)>();
|
||||
|
||||
private TextCommandResult? Before(TextCommandCallingArgs args)
|
||||
internal TextCommandResult? Before(TextCommandCallingArgs args)
|
||||
{
|
||||
if (args.Caller.Player == null)
|
||||
return null;
|
||||
@@ -370,7 +365,7 @@ public class GroupCommandListener : ICommandHookListener
|
||||
PendingMembershipChecks[callerUid] = (targetUid, (int)groupId);
|
||||
}
|
||||
|
||||
private void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||
internal void After(TextCommandCallingArgs args, TextCommandResult result)
|
||||
{
|
||||
if (args.Caller.Player == null)
|
||||
return;
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
"type": "Code",
|
||||
"modid": "claimlink",
|
||||
"name": "ClaimLink",
|
||||
"side": "Server",
|
||||
"side": "Universal",
|
||||
"authors": [
|
||||
"anth64"
|
||||
],
|
||||
"description": "Link claims together with groups.",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"game": "1.22.5",
|
||||
"commandhook": "2.1.0"
|
||||
"game": "1.22.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,3 @@ A vanilla claim can be made of multiple cuboid areas (grown one face-adjacent cu
|
||||
|
||||
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.
|
||||
|
||||
See the companion mod [ClaimLinkVisualizer](https://git.forlornoutpost.ca/anth64/ClaimLinkVisualizer) to actually see buffer zones rendered in-world.
|
||||
|
||||
Reference in New Issue
Block a user