Compare commits

...
4 Commits
5 changed files with 307 additions and 9 deletions
+140
View File
@@ -0,0 +1,140 @@
using System.Collections.Generic;
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;
namespace ClaimLink;
internal static class ClaimLinkBuffer
{
internal static List<Cuboidi> GetBuffer(int groupId)
{
List<Cuboidi> cuboids = new();
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
{
foreach (
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(uid, groupId)
)
{
if (
!ClaimLinkModSystem.TryResolveOwnedClaim(uid, claimIndex, out LandClaim? claim)
|| claim == null
)
continue;
foreach (Cuboidi area in claim.Areas)
cuboids.Add(GrowOne(area));
}
}
return cuboids;
}
internal static bool TryFindOverlappingClaimLink(
List<Cuboidi> areas,
ISet<int>? excludeGroupIds,
out int violatingGroupId
)
{
foreach (int groupId in ClaimLinkModSystem.Registry.All)
{
if (excludeGroupIds != null && excludeGroupIds.Contains(groupId))
continue;
foreach (Cuboidi bufferCuboid in GetBuffer(groupId))
foreach (Cuboidi area in areas)
if (area.Intersects(bufferCuboid))
{
violatingGroupId = groupId;
return true;
}
}
violatingGroupId = -1;
return false;
}
internal static bool TryFindOverlappingClaim(
List<Cuboidi> areas,
string excludeOwnerUid,
ISet<int>? excludeGroupIds,
out LandClaim? violatingClaim
)
{
HashSet<LandClaim> excludedClaims = new();
if (excludeGroupIds != null)
foreach (int groupId in excludeGroupIds)
foreach (string uid in ClaimLinkModSystem.Registry.MemberUidsForGroup(groupId))
foreach (
int claimIndex in ClaimLinkModSystem.Registry.ClaimsForPlayerInGroup(
uid,
groupId
)
)
if (
ClaimLinkModSystem.TryResolveOwnedClaim(
uid,
claimIndex,
out LandClaim? excludedClaim
)
&& excludedClaim != null
)
excludedClaims.Add(excludedClaim);
foreach (LandClaim claim in ClaimLinkModSystem.LandClaimAPI.All)
{
if (claim.OwnedByPlayerUid == excludeOwnerUid)
continue;
if (excludedClaims.Contains(claim))
continue;
foreach (Cuboidi claimArea in claim.Areas)
foreach (Cuboidi area in areas)
if (area.Intersects(claimArea))
{
violatingClaim = claim;
return true;
}
}
violatingClaim = null;
return false;
}
internal static ISet<int> MemberClaimLinkIds(string uid)
{
HashSet<int> groupIds = new();
var memberships = ClaimLinkModSystem.PlayerData.GetPlayerDataByUid(uid)?.PlayerGroupMemberships;
if (memberships == null)
return groupIds;
foreach (int groupId in memberships.Keys)
if (ClaimLinkModSystem.Registry.Exists(groupId))
groupIds.Add(groupId);
return groupIds;
}
internal static List<Cuboidi> GrowAreas(List<Cuboidi> areas)
{
List<Cuboidi> grown = new();
foreach (Cuboidi area in areas)
grown.Add(GrowOne(area));
return grown;
}
private static Cuboidi GrowOne(Cuboidi area)
{
Cuboidi normalized = new Cuboidi(
area.MinX,
area.MinY,
area.MinZ,
area.MaxX,
area.MaxY,
area.MaxZ
);
int bufferSize = ClaimLinkModSystem.Config.BufferSize;
return normalized.GrowBy(bufferSize, bufferSize, bufferSize);
}
}
+48 -8
View File
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Config; using Vintagestory.API.Config;
using Vintagestory.API.MathTools;
using Vintagestory.API.Server; using Vintagestory.API.Server;
namespace ClaimLink; namespace ClaimLink;
@@ -375,17 +376,56 @@ public static class ClaimLinkChatCommand
int groupId = group.Uid; int groupId = group.Uid;
string groupName = group.Name; string groupName = group.Name;
if (!ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out _)) if (
return TextCommandResult.Error("You do not own that claim."); !ClaimLinkModSystem.TryResolveOwnedClaim(playerUid, claimIndex, out LandClaim? claim)
|| claim == null
)
return TextCommandResult.Error($"You don't have a claim at index {claimIndex}.");
if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex)) if (ClaimLinkModSystem.Registry.IsClaimLinked(playerUid, claimIndex))
return TextCommandResult.Error("That claim is already part of a claim link."); return TextCommandResult.Error("That claim is already part of a claim link.");
List<Cuboidi> grownAreas = ClaimLinkBuffer.GrowAreas(claim.Areas);
ISet<int> excludeGroupIds = new HashSet<int> { groupId };
if (
ClaimLinkBuffer.TryFindOverlappingClaimLink(
grownAreas,
excludeGroupIds,
out int violatingGroupId
)
)
{
string violatingName = ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
violatingGroupId,
out PlayerGroup? violatingGroup
)
? violatingGroup!.Name
: "another claim link";
return TextCommandResult.Error(
$"Cannot link that claim, it overlaps the protected buffer of '{violatingName}'."
);
}
if (
ClaimLinkBuffer.TryFindOverlappingClaim(
grownAreas,
playerUid,
excludeGroupIds,
out LandClaim? violatingClaim
)
)
{
return TextCommandResult.Error(
$"Cannot link that claim, its buffer would overlap the claim owned by {violatingClaim!.LastKnownOwnerName}."
);
}
string claimDesc = DescribeClaim(playerUid, claimIndex); string claimDesc = DescribeClaim(playerUid, claimIndex);
return Stage( return Stage(
playerUid, playerUid,
groupId, groupId,
$"{claimDesc} will be linked into '{groupName}'.", $"'{claimDesc}' will be linked into '{groupName}'.",
() => () =>
{ {
bool stillMember = bool stillMember =
@@ -397,9 +437,9 @@ public static class ClaimLinkChatCommand
bool success = ClaimLinkModSystem.Registry.AddEntry(playerUid, groupId, claimIndex); bool success = ClaimLinkModSystem.Registry.AddEntry(playerUid, groupId, claimIndex);
return success return success
? TextCommandResult.Success($"Linked {claimDesc} to '{groupName}'.") ? TextCommandResult.Success($"Linked '{claimDesc}' to '{groupName}'.")
: TextCommandResult.Error( : TextCommandResult.Error(
$"Unable to link {claimIndex} to '{groupName}' it is currently being modified." $"Unable to link claim {claimIndex} to '{groupName}', it is currently being modified."
); );
} }
); );
@@ -422,11 +462,11 @@ public static class ClaimLinkChatCommand
return Stage( return Stage(
playerUid, playerUid,
(int)groupId, (int)groupId,
$"{claimDesc} will be unlinked from '{groupName}'.", $"'{claimDesc}' will be unlinked from '{groupName}'.",
() => () =>
{ {
ClaimLinkModSystem.Registry.RemoveEntry(playerUid, (int)groupId, claimIndex); ClaimLinkModSystem.Registry.RemoveEntry(playerUid, (int)groupId, claimIndex);
return TextCommandResult.Success($"Unlinked {claimDesc} from '{groupName}'."); return TextCommandResult.Success($"Unlinked '{claimDesc}' from '{groupName}'.");
} }
); );
} }
@@ -806,7 +846,7 @@ public static class ClaimLinkChatCommand
ClaimLinkModSystem.Registry.RemoveEntry(targetUid, (int)groupId, claimIndex); ClaimLinkModSystem.Registry.RemoveEntry(targetUid, (int)groupId, claimIndex);
return TextCommandResult.Success( return TextCommandResult.Success(
$"Unlinked {claimDesc} from '{groupName}' (owned by {playerName})." $"Unlinked '{claimDesc}' from '{groupName}' (owned by {playerName})."
); );
} }
+6
View File
@@ -0,0 +1,6 @@
namespace ClaimLink;
internal class ClaimLinkConfig
{
public int BufferSize = 16;
}
+3
View File
@@ -13,6 +13,7 @@ public class ClaimLinkModSystem : ModSystem
internal static IGroupManager Groups = null!; internal static IGroupManager Groups = null!;
internal static IWorldAccessor World = null!; internal static IWorldAccessor World = null!;
internal static IPlayerDataManager PlayerData = null!; internal static IPlayerDataManager PlayerData = null!;
internal static ClaimLinkConfig Config = null!;
internal CommandListener? CmdListener; internal CommandListener? CmdListener;
internal GroupCommandListener? GroupCmdListener; internal GroupCommandListener? GroupCmdListener;
@@ -25,6 +26,8 @@ public class ClaimLinkModSystem : ModSystem
Groups = api.Groups; Groups = api.Groups;
Logger = api.Logger; Logger = api.Logger;
PlayerData = api.PlayerData; PlayerData = api.PlayerData;
Config = api.LoadModConfig<ClaimLinkConfig>("claimlink.json") ?? new ClaimLinkConfig();
api.StoreModConfig(Config, "claimlink.json");
Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame); Registry = new ClaimLinkRegistry(api.WorldManager.SaveGame);
CmdListener = new CommandListener(); CmdListener = new CommandListener();
+110 -1
View File
@@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using CommandHook; using CommandHook;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.API.MathTools;
using Vintagestory.API.Server; using Vintagestory.API.Server;
namespace ClaimLink; namespace ClaimLink;
@@ -14,6 +16,8 @@ public class CommandListener : ICommandHookListener
public CommandRegistration Registration => new(Before, After); 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 =
new Dictionary<string, List<Cuboidi>?>();
private delegate void SubHandler(Caller caller, CmdArgs args); private delegate void SubHandler(Caller caller, CmdArgs args);
@@ -28,6 +32,7 @@ public class CommandListener : ICommandHookListener
private static readonly Dictionary<string, SubHandler> claimSub = new() private static readonly Dictionary<string, SubHandler> claimSub = new()
{ {
["load"] = ClaimLoad, ["load"] = ClaimLoad,
["new"] = ClaimNew,
["save"] = ClaimSave, ["save"] = ClaimSave,
["cancel"] = ClaimCancel, ["cancel"] = ClaimCancel,
}; };
@@ -58,7 +63,89 @@ public class CommandListener : ICommandHookListener
return null; return null;
} }
private void After(TextCommandCallingArgs args, TextCommandResult result) { } private void After(TextCommandCallingArgs args, TextCommandResult result)
{
if (args.Caller.Player == null)
return;
string uid = args.Caller.Player.PlayerUID;
if (!PendingSaveChecks.Remove(uid, out List<Cuboidi>? snapshot))
return;
if (result.Status != EnumCommandStatus.Success)
return;
List<LandClaim> allClaims = ClaimLinkModSystem.LandClaimAPI.All;
if (allClaims.Count == 0)
return;
LandClaim savedClaim = allClaims[allClaims.Count - 1];
if (savedClaim.OwnedByPlayerUid != uid)
return;
int? linkedGroupId = null;
if (ClaimLinkModSystem.TryResolveClaimIndex(uid, savedClaim, out int savedIndex))
linkedGroupId = ClaimLinkModSystem.Registry.FindGroupContaining(uid, savedIndex);
bool isLinked = linkedGroupId != null;
List<Cuboidi> checkAreas = isLinked
? ClaimLinkBuffer.GrowAreas(savedClaim.Areas)
: savedClaim.Areas;
ISet<int> excludeGroupIds = isLinked
? new HashSet<int> { linkedGroupId!.Value }
: ClaimLinkBuffer.MemberClaimLinkIds(uid);
LandClaim? violatingClaim = null;
bool violatesClaimLink = ClaimLinkBuffer.TryFindOverlappingClaimLink(
checkAreas,
excludeGroupIds,
out int violatingGroupId
);
bool violatesClaim =
isLinked
&& !violatesClaimLink
&& ClaimLinkBuffer.TryFindOverlappingClaim(
checkAreas,
uid,
excludeGroupIds,
out violatingClaim
);
if (!violatesClaimLink && !violatesClaim)
return;
ClaimLinkModSystem.LandClaimAPI.Remove(savedClaim);
string reason = violatesClaimLink
? (
ClaimLinkModSystem.Groups.PlayerGroupsById.TryGetValue(
violatingGroupId,
out PlayerGroup? violatingGroup
)
? $"it overlaps the protected buffer of '{violatingGroup!.Name}'"
: "it overlaps the protected buffer of another claim link"
)
: $"its buffer would overlap the claim owned by {violatingClaim!.LastKnownOwnerName}";
if (snapshot != null)
{
savedClaim.Areas.Clear();
savedClaim.Areas.AddRange(snapshot);
ClaimLinkModSystem.LandClaimAPI.Add(savedClaim);
NotifyPlayer(args.Caller.Player, $"Your claim growth was reverted: {reason}.");
}
else
{
NotifyPlayer(args.Caller.Player, $"Your claim was rejected: {reason}.");
}
}
private static void NotifyPlayer(IPlayer player, string message)
{
if (player is IServerPlayer sp)
sp.SendMessage(GlobalConstants.GeneralChatGroup, message, EnumChatType.Notification);
}
private static void Claim(Caller caller, CmdArgs args) private static void Claim(Caller caller, CmdArgs args)
{ {
@@ -79,9 +166,22 @@ public class CommandListener : ICommandHookListener
return; return;
string uid = caller.Player.PlayerUID; string uid = caller.Player.PlayerUID;
PendingSaveChecks[uid] = null;
if (!PendingLoads.TryGetValue(uid, out int pendingIndex)) if (!PendingLoads.TryGetValue(uid, out int pendingIndex))
return; return;
if (
ClaimLinkModSystem.TryResolveOwnedClaim(uid, pendingIndex, out LandClaim? existingClaim)
&& existingClaim != null
)
{
List<Cuboidi> snapshot = new();
foreach (Cuboidi area in existingClaim.Areas)
snapshot.Add(area.Clone());
PendingSaveChecks[uid] = snapshot;
}
ClaimLinkModSystem.Registry.ClaimSaved(uid, pendingIndex); ClaimLinkModSystem.Registry.ClaimSaved(uid, pendingIndex);
PendingLoads.Remove(uid); PendingLoads.Remove(uid);
ClaimLinkChatCommand.PendingActions.Remove(uid); ClaimLinkChatCommand.PendingActions.Remove(uid);
@@ -99,6 +199,14 @@ public class CommandListener : ICommandHookListener
PendingLoads[caller.Player.PlayerUID] = (int)claimIndex; PendingLoads[caller.Player.PlayerUID] = (int)claimIndex;
} }
private static void ClaimNew(Caller caller, CmdArgs args)
{
if (caller.Player == null)
return;
PendingLoads.Remove(caller.Player.PlayerUID);
}
private static void ClaimCancel(Caller caller, CmdArgs args) private static void ClaimCancel(Caller caller, CmdArgs args)
{ {
if (caller.Player == null) if (caller.Player == null)
@@ -161,6 +269,7 @@ public class CommandListener : ICommandHookListener
internal static void OnPlayerDisconnect(IServerPlayer player) internal static void OnPlayerDisconnect(IServerPlayer player)
{ {
PendingLoads.Remove(player.PlayerUID); PendingLoads.Remove(player.PlayerUID);
PendingSaveChecks.Remove(player.PlayerUID);
} }
} }