Files
installer/tests/fixtures/playervendor-sale-eventsink.patch
wtclaude 52d330167b
Some checks failed
PR Checks / rust-gates (pull_request) Failing after 46s
feat(installer): implement Phase 3 — the patch tier
Two features need edits to stock ServUO sources, because the events they
depend on do not exist. This adds the rung ladder of PLAN.md §2.2.1, the
unsupported-version path of §2.2.2, and the record and cache Phase 4 will read.

Three decisions were not settled by the plan:

* The engine is fully native, with no `git`. §2.2.1 wrote rung 1 as "apply
  verbatim with git apply", but §1 chose the release tarball specifically so
  there would be no git on the shard host, and rung 2 needs a native applier
  regardless. Rung 1 keeps its distinct, stronger verdict — the whole file
  reproduced the diff's `index` pre-image, computed as a git blob SHA1 in
  process — while the write goes through the same code path as rung 2. On the
  real trees here that is not academic: the shipped .patch files are CRLF in a
  Windows checkout and two of their three targets are LF, so `git apply`
  refuses patches this applies correctly.

* Per-patch metadata is declared by the release, with a built-in fallback.
  Which patches form one all-or-nothing unit, which companion .cs follows
  which, whether a CORE rebuild is needed and what declining costs are not
  derivable from a diff. servuo-plugins now declares them; overlay v0.1.1 is in
  the current bundle and declares nothing, so a built-in copy stands in for it.
  A checked-in fixture of the release workflow's own jq output asserts the two
  descriptions are identical, so the repos cannot drift quietly.

* Pre-images are cached in the state directory. The tier edits files the
  operator owns, and `/etc/runicgateway/patches/originals/` is what turns "here
  are the hunks we added" into a revert anyone can verify — kept out of the
  ServUO tree, which uninstall has promised never to clean up.

Everything else follows §2.2.1: exact matching with only line-ending and
trailing-whitespace normalization, exactly one occurrence or it fails,
all-or-nothing per patch file and again per feature, and a byte-preserving
splice so nothing outside a hunk can be reformatted.

Verified against the ServUO 57.4 tree on this machine across four scratch
roots: a hand-patched tree (rung 0), a reverse-applied stock one (rung 1 on the
real EventSink.cs, its blob matching the patch's declared pre-image), a
mixed-rung feature, a tree with edits inside two patched regions (rung 3 —
nothing written, nothing held back applied, no companions copied), and a
non-57.4 tree both with and without the extra consent flag. Three consecutive
runs left install.json byte-identical and the cached pre-image still pre-patch.

Three reporting defects the live runs caught are fixed with tests: a dry run
and a held-back patch both claimed to be "applied", the core-rebuild warning
fired when nothing had been written and named a Scripts file as core, and a
declined tier announced the loss of features install.json showed as applied.
Refused patches are now cached too, since the refusal message names that path.

Refs: docs/installer/PLAN.md §2.2, §5 Phase 3

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 19:54:36 -05:00

67 lines
2.4 KiB
Diff

diff --git a/Server/EventSink.cs b/Server/EventSink.cs
index d30788f..1da2667 100644
--- a/Server/EventSink.cs
+++ b/Server/EventSink.cs
@@ -171,6 +171,8 @@ namespace Server
public delegate void ValidVendorSellEventHandler(ValidVendorSellEventArgs e);
+ public delegate void PlayerVendorSaleEventHandler(PlayerVendorSaleEventArgs e);
+
public delegate void CorpseLootEventHandler(CorpseLootEventArgs e);
public delegate void RepairItemEventHandler(RepairItemEventArgs e);
@@ -1521,6 +1523,29 @@ namespace Server
}
}
+ // Player-vendor purchases raise no other EventSink. This fires at the committed sale in
+ // PlayerVendorBuyGump.OnResponse, where buyer, vendor owner, item, price, and commission
+ // are all in scope -- the data the bridge's cheat-detection feed needs.
+ public class PlayerVendorSaleEventArgs : EventArgs
+ {
+ public Mobile Buyer { get; set; }
+ public Mobile Vendor { get; set; }
+ public Mobile Owner { get; set; }
+ public Item Item { get; set; }
+ public int Price { get; set; }
+ public int Commission { get; set; }
+
+ public PlayerVendorSaleEventArgs(Mobile buyer, Mobile vendor, Mobile owner, Item item, int price, int commission)
+ {
+ Buyer = buyer;
+ Vendor = vendor;
+ Owner = owner;
+ Item = item;
+ Price = price;
+ Commission = commission;
+ }
+ }
+
public class CorpseLootEventArgs : EventArgs
{
public Mobile Mobile { get; set; }
@@ -1771,6 +1796,7 @@ namespace Server
public static event TameCreatureEventHandler TameCreature;
public static event ValidVendorPurchaseEventHandler ValidVendorPurchase;
public static event ValidVendorSellEventHandler ValidVendorSell;
+ public static event PlayerVendorSaleEventHandler PlayerVendorSale;
public static event CorpseLootEventHandler CorpseLoot;
public static event RepairItemEventHandler RepairItem;
public static event AlterItemEventHandler AlterItem;
@@ -2416,6 +2442,14 @@ namespace Server
}
}
+ public static void InvokePlayerVendorSale(PlayerVendorSaleEventArgs e)
+ {
+ if (PlayerVendorSale != null)
+ {
+ PlayerVendorSale(e);
+ }
+ }
+
public static void InvokeCorpseLoot(CorpseLootEventArgs e)
{
if (CorpseLoot != null)