ModernUO/Projects/UOContent/Engines
Kamron Batman 5ce0f1e92b
fix: Require BOD combine items to be player-crafted (#2573)
## Problem

`SmallBOD.EndCombine` validates an item's **type**, **material** and **exceptional quality**, but never checks that the item was actually crafted by a player. Any item matching the request is accepted, including one bought straight from an NPC vendor.

https://github.com/modernuo/ModernUO/blob/main/Projects/UOContent/Engines/Bulk%20Orders/SmallBOD.cs#L117-L168

Where a vendor stocks a type a BOD can request, a player can fill the deed by buying the items instead of crafting them, and pocket the reward gold for the difference.

Tailoring is the clearest case. `SmallTailorBOD.CreateRandomFor` guarantees `Material = None` and `RequireExceptional = false` below 70.1 skill, so the rolled deed asks for plain cloth items — and tailor vendors stock several of those directly. A qty-20 Bandana BOD can be filled entirely from vendor stock for a small fraction of the reward gold, with no crafting and no material cost.

The same shape applies anywhere else a vendor-sold type overlaps a requestable BOD type; tailoring is simply where the low-skill deed generator and the vendor inventory overlap most.

## Fix

Add a `PlayerConstructed` check alongside the existing material and quality checks.

```csharp
var playerConstructed = armor?.PlayerConstructed ?? clothing?.PlayerConstructed ??
    weapon?.PlayerConstructed ?? false;

if (!playerConstructed)
{
    from.SendLocalizedMessage(1045169); // The item is not in the request.
}
```

This follows the pattern already used in `Engines/Craft/Core/Resmelt.cs` (L98-L100, L155-L160) to distinguish crafted from store-bought items, and reuses the same null-coalescing chain style as the adjacent `GetMaterial(armor?.Resource ?? clothing?.Resource ?? CraftResource.None)` line directly above it.

`PlayerConstructed` is already set in `OnCraft` and serialized on all three bases (`BaseArmor`, `BaseWeapon`, `BaseClothing`), so the flag survives restarts and no serialization change is needed.

## Open question — the message

There is no dedicated cliloc for "this item must be crafted", so I reused **1045169** (*"The item is not in the request."*). It is arguably accurate — a vendor-bought item genuinely is not what the deed asked for — but it is not precise, and a player who does not know the rule will find it confusing.

I would rather flag this than invent a string. If there is a better cliloc, I am happy to switch it.

## Testing

`dotnet build Projects/UOContent/UOContent.csproj` — **0 errors, 0 warnings**.

Not covered: I have not added an automated test, as I could not find existing coverage for `EndCombine` to extend. Happy to add one if you would like it, with a pointer to the preferred pattern.

## Compatibility note

Any *already-existing* vendor-bought item in a player's possession will now be rejected by a BOD. That is the intended behaviour, but it is a visible change for anyone mid-deed. Worth a line in release notes.
2026-08-12 20:12:54 -07:00
..
Advanced Search perf(login): run password hashing on a parked worker thread (#2566) 2026-08-09 00:13:34 -07:00
BuffIcons feat: Standardizes South/East selection gumps (#1839) 2025-02-14 00:15:14 -08:00
Bulk Orders fix: Require BOD combine items to be player-crafted (#2573) 2026-08-12 20:12:54 -07:00
CannedEvil fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
Character Creation fix: Fixes bug with non blessed starter spellbooks (#2368) 2026-03-13 00:20:23 -07:00
Chat fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
ConPVP fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
Craft feat(throwing): core gargoyle Throwing skill (SA) (#2510) 2026-07-02 21:06:11 -07:00
Doom chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Ethics perf(saves): eliminate world-save freeze bottlenecks (~9.5x faster freeze) (#2525) 2026-07-16 22:53:28 -07:00
Events fix: Streamlines event scheduler API. Adds months to weekly recurrence (#2178) 2025-05-07 22:42:44 -07:00
Factions fix(opl): refuse property list invalidation raised from inside GetProperties (#2555) 2026-07-28 21:29:03 -07:00
FeatureFlags fix: Streamlines insurance. Insurance only executes when enabled. (#2550) 2026-07-26 09:47:49 -07:00
Harvest chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Help feat: Add zero-alloc interpolation handler to ValueStringBuilder, replace all StringBuilder usage (#2387) 2026-03-22 14:23:44 -07:00
Insurance fix: Streamlines insurance. Insurance only executes when enabled. (#2550) 2026-07-26 09:47:49 -07:00
Khaldun fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
ML Quests fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
Party fix: Eliminates double lookup with Contains->Remove (#2539) 2026-07-19 09:26:27 -07:00
Pathing perf(login): run password hashing on a parked worker thread (#2566) 2026-08-09 00:13:34 -07:00
Plants feat: Pre-Publish 14 Crafting (supersedes #2181, #2381) (#2476) 2026-06-07 20:27:22 -07:00
Player Murder System fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00
Quests refactor: decompose mobile/corpse hair, delete VirtualHairInfo, fix removal serial (#2462) (#2463) 2026-06-06 14:33:28 -07:00
Spawners refactor(spawners): replace DynamicJson with typed SpawnerDto records (#2505) 2026-06-25 23:24:45 -07:00
Stealables feat(throwing): wire SA loot flavor + Valkyrie's Glaive stealable (#2512) 2026-07-02 22:28:55 -07:00
Treasures of Tokuno fix: Optimizes items to use default weights. (Part 1) (#2240) 2025-07-24 14:44:39 -07:00
UltimaStore fix: Fixes thread guard and cleans up incoming packet reader (#1641) 2023-12-19 17:04:09 -08:00
Veteran Rewards feat: Pre-Publish 14 Crafting (supersedes #2181, #2381) (#2476) 2026-06-07 20:27:22 -07:00
Virtues fix: Removes unnecessary dictionary removal guards (#2565) 2026-08-08 11:50:01 -07:00