using System; using System.Collections.Generic; using System.Linq; using Server.Multis; namespace Server.Custom.Bridge { /// /// The housing registry (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md §11 #9). BridgeSweeps already emits house.decay /// *transitions*; this is the complementary *board*: one row per house with owner, location, /// region, co-owners, value, and current decay level, so the website can render an owner→houses /// map. Like the other Part B boards it is a diff sweep over BaseHouse.AllHouses — emit /// house.update only when a house's signature changes, and house.remove when a house is gone. /// /// Note: stock ServUO has no "for sale" flag on a house (houses are traded, not listed), so the /// registry is owner→houses; `price` is the house's placement value, not a sale listing. /// public static class BridgeHousing { private static Timer _timer; // house serial -> last-emitted signature. private static readonly Dictionary _last = new Dictionary(); private static long _sweeps, _emitted, _removed; public static void Initialize() { if (!BridgeConfig.Enabled) return; EventSink.ServerStarted += OnServerStarted; } private static void OnServerStarted() { BridgeLink.Connected_Core += OnConnected; Rearm(); } private static void OnConnected() { _last.Clear(); } /// Stops and recreates the timer from current config. Called by `[bridge reload`. public static void Rearm() { Stop(); _timer = Timer.DelayCall( TimeSpan.FromSeconds(BridgeConfig.HousingSweepSeconds), TimeSpan.FromSeconds(BridgeConfig.HousingSweepSeconds), HouseSweep); } public static void Stop() { if (_timer != null) { _timer.Stop(); _timer = null; } } public static string Status() { return String.Format("housing(sweeps={0} emitted={1} removed={2} tracked={3})", _sweeps, _emitted, _removed, _last.Count); } /// Runs one sweep now. Wired into `[bridge sweepnow`. public static void SweepOnce() { HouseSweep(); } private static void HouseSweep() { try { _sweeps++; if (!BridgeLink.Connected) return; // nothing is listening; do not fill the queue with perishable snapshots var seen = new HashSet(); foreach (var house in BaseHouse.AllHouses) { if (house == null || house.Deleted) continue; seen.Add(house.Serial); var level = house.DecayLevel; // computed getter — read once var sig = Signature(house, level); string prior; if (_last.TryGetValue(house.Serial, out prior) && prior == sig) continue; // unchanged since last emit _last[house.Serial] = sig; BridgeLink.Emit(WriteHouse(house, level)); _emitted++; } var gone = _last.Keys.Where(k => !seen.Contains(k)).ToList(); foreach (var serial in gone) { _last.Remove(serial); BridgeLink.Emit(BridgeJson.Begin("house.remove").Ser("serial", serial).End()); _removed++; } } catch (Exception ex) { Console.WriteLine("[Bridge] housing sweep threw: {0}", ex.Message); } } private static string Signature(BaseHouse house, DecayLevel level) { var ownerSerial = house.Owner == null ? 0 : house.Owner.Serial.Value; var region = house.Region; var regionName = region == null ? "" : (region.Name ?? ""); var sign = house.Sign; var name = sign == null ? "" : (sign.GetName() ?? ""); var coOwners = house.CoOwners == null ? 0 : house.CoOwners.Count; return String.Concat( ownerSerial.ToString(), "|", level.ToString(), "|", regionName, "|", name, "|", coOwners.ToString(), "|", house.Price.ToString()); } private static string WriteHouse(BaseHouse house, DecayLevel level) { var sb = BridgeJson.Begin("house.update") .Ser("serial", house.Serial) .Str("decay", level.ToString()) .Num("price", house.Price) .Str("map", house.Map == null ? null : house.Map.Name) .Num("x", house.X).Num("y", house.Y).Num("z", house.Z); var sign = house.Sign; if (sign != null) sb.Str("name", sign.GetName()); var region = house.Region; if (region != null) sb.Str("region", region.Name); sb.Actor("owner", house.Owner); sb.Num("coOwners", house.CoOwners == null ? 0 : house.CoOwners.Count); sb.Num("friends", house.Friends == null ? 0 : house.Friends.Count); sb.Str("builtOn", house.BuiltOn.ToUniversalTime().ToString("o")); sb.Str("lastRefreshed", house.LastRefreshed.ToUniversalTime().ToString("o")); return sb.End(); } } }