Phase 7: PlayerVendorSale core event + subscriber
The one non-drop-in piece. Player-vendor purchases raise no EventSink, so the sale is invisible to subscription. Two git-format core patches add a PlayerVendorSale event and raise it at the committed sale in PlayerVendorBuyGump.OnResponse (right after HoldGold +=), where buyer, vendor owner, item, price, and commission are all in scope. The subscriber BridgeVendorSale emits vendor.sale. All three are a coupled unit. The subscriber references PlayerVendorSaleEventArgs, which does not exist until the EventSink patch is applied, so it lives in patches/ not overlay/ -- shipping it in overlay would break the build on any unpatched install. patches/README.md documents applying the unit; both patches verified with git apply --check against stock ServUO 57.4. This is the first phase that rebuilds the core (ServUO.exe), not just Scripts.dll. vendor.sale carries buyer and vendor-owner accounts, both present and distinct, which is the pair that flags gold-laundering when they match -- richer than the ownerless NPC ValidVendor* events, and on a committed sale rather than a validation stage. Verified with a probe firing the event on real seeded-vendor data: vendor.sale emitted with buyerAcct=seed_001, ownerAcct=seed_000, Longsword, price 69819. The probe proves the event, args, subscriber, and payload; the literal gump call site firing on a real purchase needs a live buyer with a NetState and is confirmed by an in-game buy. Evidence in docs/PLAN.md §17. This completes every phase on the ServUO side. Phases 0-6 are drop-in (overlay/); 7 is patches/. Cheat signals are folded into existing streams (fastwalk, audit, vendor.sale), not a separate phase. Remaining work is the Rust sidecar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
104
tools/scaffolding/BridgeVendorSaleProbe.cs
Normal file
104
tools/scaffolding/BridgeVendorSaleProbe.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
|
||||
using Server.Accounting;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Custom
|
||||
{
|
||||
/// <summary>
|
||||
/// Fires PlayerVendorSale with real seeded-vendor data to prove the full chain: the
|
||||
/// EventSink event added by the Phase 7 patch, its args, the subscriber, and the vendor.sale
|
||||
/// JSON. It does NOT exercise the gump call site (that needs a live buyer with a NetState) —
|
||||
/// the hook line is placed at the committed-sale point in PlayerVendorBuyGump.OnResponse and
|
||||
/// is verified by a real in-game purchase.
|
||||
///
|
||||
/// Test scaffolding. Never deployed. Read-only (invokes an event; changes no world state).
|
||||
/// </summary>
|
||||
public static class BridgeVendorSaleProbe
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Config.Get("Bridge.VendorSaleProbeOnStart", false))
|
||||
EventSink.ServerStarted += () => Timer.DelayCall(TimeSpan.FromSeconds(4.0), Run);
|
||||
}
|
||||
|
||||
private static void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
var vendors = PlayerVendor.PlayerVendors;
|
||||
|
||||
if (vendors == null || vendors.Count == 0)
|
||||
{
|
||||
Console.WriteLine("[VendorSaleProbe] no player vendors; seed the world first");
|
||||
return;
|
||||
}
|
||||
|
||||
// A real seeded vendor, its owner, and one of its actual priced items.
|
||||
PlayerVendor vendor = null;
|
||||
Item item = null;
|
||||
int price = 0;
|
||||
|
||||
foreach (var v in vendors)
|
||||
{
|
||||
if (v == null || v.Deleted || v.Owner == null || v.Backpack == null)
|
||||
continue;
|
||||
|
||||
foreach (var it in v.Backpack.Items)
|
||||
{
|
||||
var vi = v.GetVendorItem(it);
|
||||
if (vi != null)
|
||||
{
|
||||
vendor = v; item = it; price = vi.Price;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (vendor == null)
|
||||
{
|
||||
Console.WriteLine("[VendorSaleProbe] no vendor with a priced item found");
|
||||
return;
|
||||
}
|
||||
|
||||
// A buyer on a DIFFERENT account than the owner, so buyerAcct != ownerAcct.
|
||||
Mobile buyer = null;
|
||||
foreach (Account a in Accounting.Accounts.GetAccounts())
|
||||
{
|
||||
if (!a.Username.StartsWith("seed_", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var pm = a[0] as PlayerMobile;
|
||||
if (pm != null && pm != vendor.Owner &&
|
||||
!(vendor.Owner != null && vendor.Owner.Account == a))
|
||||
{
|
||||
buyer = pm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int commission = 0;
|
||||
if (vendor.IsCommission)
|
||||
commission = (int)(price * (vendor.CommissionPerc / 100));
|
||||
|
||||
Console.WriteLine("[VendorSaleProbe] firing PlayerVendorSale: buyer={0} owner={1} item={2} price={3} commission={4}",
|
||||
buyer == null ? "?" : buyer.Name,
|
||||
vendor.Owner == null ? "?" : vendor.Owner.Name,
|
||||
item.GetType().Name, price, commission);
|
||||
|
||||
EventSink.InvokePlayerVendorSale(
|
||||
new PlayerVendorSaleEventArgs(buyer, vendor, vendor.Owner, item, price, commission));
|
||||
|
||||
Console.WriteLine("[VendorSaleProbe] done; watch for vendor.sale");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[VendorSaleProbe] FAILED: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ These two scripts produced the measured budget in `docs/PLAN.md` §1. They are k
|
||||
| `BridgeSweepProbe.cs` | `Scripts/Custom/BridgeSweepProbe.cs` | Bumps one seeded house's decay stage after baseline so the decay sweep's transition detection can be observed without waiting a real IDOC stage. Flag: `SweepProbeOnStart`. Pair with short `*SweepSeconds` overrides. |
|
||||
| `BridgeLinkProbe.cs` | `Scripts/Custom/BridgeLinkProbe.cs` | Triggers `[link` for seed_001 without a client, then saves so the `WebsiteUserId` tag reaches `accounts.xml`. Flag: `LinkProbeOnStart`. Pair with a sidecar that reads the code and sends `link.confirm`. |
|
||||
| `BridgeCrierProbe.cs` | `Scripts/Custom/BridgeCrierProbe.cs` | Logs the global town-crier entry list every 3s so `towncrier.add` / `remove` can be seen landing in game state. Flag: `CrierProbeOnStart`. |
|
||||
| `BridgeVendorSaleProbe.cs` | `Scripts/Custom/BridgeVendorSaleProbe.cs` | Fires `PlayerVendorSale` (Phase 7) with real seeded-vendor data so `vendor.sale` can be verified without a live buy. Requires the Phase 7 patches applied. Flag: `VendorSaleProbeOnStart`. |
|
||||
|
||||
## Deploy overwrites Bridge.cfg
|
||||
|
||||
|
||||
Reference in New Issue
Block a user