namespace Aeshnidae.InstancesNoDat; /// /// Enclaves: a set of landblocks that, for one group of players, is always that group's own /// copy - registered at runtime by another mod (Aeshnidae.Towns, 2026-09-29: towns an allegiance /// buys). InstancesNoDat supplies the mechanism; the owning mod decides who, what and where. /// /// Register(name, copy, ownerKey, landblocks): every landblock of the set, for a player whose /// allegiance's monarch is ownerKey, resolves to copy `copy` (Context.CopyFor, after explicit /// claims, before awakened worlds) - however they arrive: a portal, a recall, a login. Anyone /// else arriving the same way gets the real landblock. That is the whole access rule: no portal /// has to be restricted, because no way in leads an outsider into the copy. /// /// The set's outdoor landblocks open as one region (Regions.OpenWorld, membership = the set), so /// an island town's seams join and its edge is the sea. A dungeon landblock in a set is a plain /// copy. Copy numbers are the owner mod's (Towns uses 200000 + its town id) and must not collide /// with worlds (Worlds: CopyBase .. CopyBase + 99999). /// /// An enclave copy is Shared by copy (fellowships welcome; Mod.ScopeOf), kept (Keep - dropped /// items and corpses stay, under the copy number), never scaled, takes no variant, and has no /// housing in it. The arrival line is the owner mod's (Arrival.OnArrival); ours stays quiet. /// /// The registry lives in memory; the owning mod registers its enclaves again when it loads, and /// InstancesNoDat asks the owner nothing - a reload of either mod re-registers from the database. /// public static class Enclaves { public sealed class Enclave { public string Name { get; init; } = ""; public int Copy { get; init; } public uint OwnerKey { get; init; } public HashSet Landblocks { get; init; } = new(); public override string ToString() => $"enclave '{Name}' copy {Copy} (owner 0x{OwnerKey:X8}, {Landblocks.Count} landblocks)"; } private static readonly ConcurrentDictionary _byCopy = new(); /// (owner key, landblock) -> copy: the hot-path lookup. private static readonly ConcurrentDictionary<(uint Owner, ushort Landblock), int> _route = new(); /// Every landblock any enclave covers, for the one-lookup fast path. private static readonly ConcurrentDictionary _covered = new(); public static IReadOnlyCollection All => _byCopy.Values.ToList(); public static bool IsEnclaveCopy(int copy) => copy > 0 && _byCopy.ContainsKey(copy); public static Enclave? Of(int copy) => _byCopy.TryGetValue(copy, out var e) ? e : null; /// /// Registers (or re-registers) an enclave. Called by the owning mod through reflection: /// string, int, uint and a list of ushort cross the mod boundary cleanly. Returns false when /// the copy number is taken by a different enclave or collides with the worlds' range. /// public static bool Register(string name, int copy, uint ownerKey, List landblocks) { if (copy <= 0 || Worlds.IsWorldCopy(copy) || landblocks is null || landblocks.Count == 0) return false; if (_byCopy.TryGetValue(copy, out var existing) && existing.OwnerKey != ownerKey) return false; Unregister(copy); var enclave = new Enclave { Name = name, Copy = copy, OwnerKey = ownerKey, Landblocks = landblocks.ToHashSet() }; _byCopy[copy] = enclave; foreach (var lb in enclave.Landblocks) { _route[(ownerKey, lb)] = copy; _covered[lb] = 0; } Mod.Trace($"registered {enclave}"); return true; } public static bool Unregister(int copy) { if (!_byCopy.TryRemove(copy, out var enclave)) return false; foreach (var lb in enclave.Landblocks) _route.TryRemove((enclave.OwnerKey, lb), out _); _covered.Clear(); foreach (var e in _byCopy.Values) foreach (var lb in e.Landblocks) _covered[lb] = 0; return true; } /// The key a player's enclaves are registered under: their allegiance's monarch (themselves, if they are one). public static uint OwnerKeyOf(Player player) => player.Allegiance?.MonarchId ?? player.MonarchId ?? 0; /// The enclave copy of this landblock for this player, 0 for none. public static int CopyFor(Player player, ushort landblock) { if (_covered.IsEmpty || !_covered.ContainsKey(landblock) || !Mod.Settings.Enabled) return 0; var owner = OwnerKeyOf(player); return owner != 0 && _route.TryGetValue((owner, landblock), out var copy) ? copy : 0; } /// InstanceWorld.GetOrCreate for an outdoor landblock of an enclave: opened as part of the enclave's region. public static bool IsOutdoorOf(int copy, ushort landblock) => _byCopy.TryGetValue(copy, out var e) && e.Landblocks.Contains(landblock) && Worlds.IsOutdoor(landblock); public static Landblock? OpenOutdoor(ushort landblock, int copy) { if (!_byCopy.TryGetValue(copy, out var enclave)) return null; var region = Regions.OpenWorld(enclave.Name, copy, lb => enclave.Landblocks.Contains(lb) && Worlds.IsOutdoor(lb), null, landblock); return region is not null && region.Blocks.TryGetValue(landblock, out var block) ? block : null; } }