ModernUO/Projects/UOContent
Kamron Batman d857f03b68
fix: bound and purge NPC vendor buyback stock (#2677)
## Summary

Stacked on #2676. Review that first; this PR's diff is against it.

Items players sell to NPC vendors go into the vendor's buyback (`ShopBuy`) pack. That pack was purged only when someone opened the buy menu, so a vendor that players sold to but never bought from grew without limit.
- A server operator reported 250k+ items piling up from players crafting and selling on a macro.
- Every one of those items was saved.
- Purging or deleting that vendor then sent one remove packet per item to every nearby client and disconnected them.

The behavior is inherited from RunUO.

- **`VendorBuybackPack`** replaces the plain backpack on `Layer.ShopBuy`.
  - It opts in to `SkipsChildSerialization`, so buyback items are never saved.
  - Its contents are private like any container's, so removing them reaches only the vendor (no client) and openers. Purges and deletes send nothing to bystanders.
- **Capacity.** The buyback holds at most `250 − buy-list entries` items, which is what the buy gump can show. The oldest items are evicted first. At capacity 0 the sold item is consumed and the seller is still paid.
- **Purge on the restock grid.**
  - `Restock()` empties the buyback.
  - A purge-only timer, armed on the first sale, covers vendors nobody buys from. It is due at the next restock boundary after now, so a vendor whose restock hasn't run for hours doesn't purge an item the moment it's sold.
  - The timer never restocks, so stock doubling and halving is unchanged.
  - The old per-item one-hour `LastMoved` check is gone.
- **Existing saves.** A legacy `ShopBuy` pack is replaced on the first tick after load. Thanks to #2676, deleting a pack of 250k+ items takes well under a second.

**Behavior changes for shard operators:**
- A buyback item lasts until the next restock boundary (at most `RestockDelay`), not one hour after its sale.
- A vendor that changes maps drops its buyback.
- A buy window that is open during a purge can show a row whose item is gone. Buying that row does nothing, and reopening shows the current list.

## Test plan

- [x] `VendorBuybackPackTests`: evicts oldest first; capacity 0 consumes the item; purge; a stale front entry can't spin eviction
- [x] `VendorBuybackTests`:
  - purge due time stays on the restock grid, including a stale restock and a restock time in the future;
  - partial stack sale: the split goes to buyback and the seller keeps the remainder;
  - zero capacity consumes the item and still pays;
  - restock purges and clears the timer, and the next sale re-arms it;
  - deleting the vendor cancels the timer;
  - a legacy 20k-item pack is replaced.
- [x] UOContent.Tests and Server.Tests green; schema generator clean
- [x] Manual in-game pass:
  - sell and buy back, including a partial stack;
  - over-capacity sale;
  - purge with no buyer;
  - `[restock`;
  - vendor facet change;
  - restart;
  - legacy save with a large `ShopBuy` pack;
  - mass selling with bystanders present;
  - deleting a vendor with a full buyback.
2026-10-09 22:56:39 -07:00
..
Accounting perf(login): run password hashing on a parked worker thread (#2566) 2026-08-09 00:13:34 -07:00
Assistants chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Commands refactor: master views over BaseCreature's one master reference (#2674) 2026-09-24 22:45:24 -07:00
Compression feat: Implements new robust/pluggable backup/archive system. (#2388) 2026-03-22 19:51:20 -07:00
Configuration fix: Streamlines insurance. Insurance only executes when enabled. (#2550) 2026-07-26 09:47:49 -07:00
Console feat: Adds console commands (#1714) 2024-03-30 09:40:43 -07:00
Context Menus fix: Moves ContextMenu out of core, streamlines code, fixes bugs (#1873) 2024-07-20 21:33:23 -07:00
Engines fix: T2A tailoring make-last no longer reuses a stale cloth hue for leather (#2694) 2026-10-06 21:42:23 -07:00
Gumps fix: release a connection's gumps on disconnect (#2689) 2026-10-04 17:53:41 -07:00
Holiday Stuff perf: Migrate Veteran Reward gumps to DynamicGump/StaticGump (#2415) 2026-04-25 20:09:27 -07:00
Items feat: scope container child removal, skip-serialize and linearly delete container children (#2676) 2026-10-09 22:21:50 -07:00
Migrations fix: bound and purge NPC vendor buyback stock (#2677) 2026-10-09 22:56:39 -07:00
Misc fix: send crash reports and bound email retries (#2680) 2026-10-04 18:41:27 -07:00
Mobiles fix: bound and purge NPC vendor buyback stock (#2677) 2026-10-09 22:56:39 -07:00
Multis fix: enumerators that end early, repeat entries, or never end (#2685) 2026-09-30 21:24:44 -07:00
Network fix: Fixes tick count wrap-around in movement throttle, and eliminates more allocations in NetState (#2603) 2026-09-01 20:25:20 -07:00
Regions fix(regions): correct end Z coordinate assignment in InitRectangles (#2597) 2026-08-27 06:51:42 -07:00
Skills fix: stealing from a container on the ground follows its lift rules (#2697) 2026-10-09 00:21:43 -07:00
Special Systems feat: Add zero-alloc interpolation handler to ValueStringBuilder, replace all StringBuilder usage (#2387) 2026-03-22 14:23:44 -07:00
Spells fix: MeteorSwarm resist halves that target's damage, not the shared pool (#2691) 2026-10-06 16:30:05 -07:00
Systems/JailSystem fix: players jailed across a restart can be jailed again after release (#2690) 2026-10-04 18:21:50 -07:00
Targets chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Text fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
Utilities fix(commands): parse and edit IPoint2D/IPoint3D properties (#2646) 2026-09-14 21:20:22 -07:00
World Saves perf(messages): mechanical interpolation cleanups (#2436) 2026-05-03 18:26:49 -07:00
Module.cs fix: Fixes sending packets and sidesteps a major issue with stackalloc and PGO in .NET 8 (#1607) 2023-11-21 12:18:20 -08:00
UOContent.csproj fix: Adds nullability support and fixes crash bugs in serialization generator (#2671) 2026-09-24 01:01:40 -07:00