using System; using Server; using Server.Accounting; using Server.Custom.Bridge; namespace Server.Custom.Bridge { /// /// Subscribes to the PlayerVendorSale event added by the Phase 7 core patches. This file is /// part of that coupled unit and is NOT in overlay/, because it references /// PlayerVendorSaleEventArgs, which does not exist until the EventSink patch is applied — /// shipping it in overlay/ would break the build on any install without the patch. /// /// Deploy: apply patches/playervendor-sale-*.patch, then copy this file to /// Scripts/Custom/Bridge/BridgeVendorSale.cs. /// /// The event fires at the committed sale (PlayerVendorBuyGump.OnResponse), on the Core /// thread, with buyer, vendor owner, item, price, and commission all in scope — richer than /// the NPC ValidVendor* events (which lack owner and commission) and, unlike them, on a /// committed sale rather than a validation stage. It is the backbone of the cheat-detection /// feed: same-account buyer≈owner is gold laundering, off-market prices and burst patterns /// are visible to the sidecar. /// public static class BridgeVendorSale { public static void Initialize() { if (!BridgeConfig.Enabled) return; EventSink.PlayerVendorSale += OnPlayerVendorSale; Console.WriteLine("[Bridge] player-vendor sale stream attached"); } private static void OnPlayerVendorSale(PlayerVendorSaleEventArgs e) { try { var sb = BridgeJson.Begin("vendor.sale") .Bool("committed", true); // Buyer if (e.Buyer != null) { sb.Ser("buyerSerial", e.Buyer.Serial); var ba = e.Buyer.Account as Account; if (ba != null) sb.Str("buyerAcct", ba.Username); } // Vendor owner — the player who actually profits. if (e.Owner != null) { sb.Ser("ownerSerial", e.Owner.Serial); var oa = e.Owner.Account as Account; if (oa != null) sb.Str("ownerAcct", oa.Username); } if (e.Vendor != null) sb.Ser("vendorSerial", e.Vendor.Serial); if (e.Item != null) { sb.Ser("itemSerial", e.Item.Serial); sb.Str("itemType", e.Item.GetType().Name); sb.Num("itemId", e.Item.ItemID); sb.Num("amount", e.Item.Amount); } sb.Num("price", e.Price); sb.Num("commission", e.Commission); BridgeLink.Emit(sb.End()); } catch (Exception ex) { Console.WriteLine("[Bridge] vendor.sale handler threw: {0}", ex.Message); } } } }