ModernUO/dev-docs
Kamron Batman 309fcfeb27
feat(skills): SkillEvents.SkillUsed for cross-assembly subscribers; InternalsVisibleTo ModernSpawner.Tests (#2636)
## Summary

Two small additive changes that an external content assembly (ModernSpawner) needs, as separable commits.

**1. `SkillEvents.SkillUsed`** (`Projects/UOContent/Skills/SkillEvents.cs`, namespace `Server.Misc`): a plain C# event `Action<Mobile, Skill, bool success>` raised once per skill attempt from each of the four `Mobile_SkillCheck*` handlers, with the handler's own result. Attempts the handler resolves without a roll (too difficult, no challenge) raise too, so a grandmaster's trivial success and a guaranteed combat roll are observable. Not raised when the mobile lacks the skill. Each handler keeps its logic in a private core method and raises on the way out, so there is exactly one raise per attempt and `CheckSkill` itself is unchanged.

- **Why a plain event and not a `[GeneratedEvent]`:** generated events are compile-time static dispatch inside the UOContent compilation, so a subscriber in another assembly cannot use `[OnEvent]`. Shape follows `HelpEvents`.
- **Why "used", not "gained":** this is the XmlSpawner skill-trigger semantic (it wrapped the same four handlers and passed their result as `success`; its grammar was `Skill[+/-]` for success-only or failure-only). Gains are already observable through the existing skill-change notification on `Mobile`.
- **Cost:** one delegate null-check per attempt when nothing is subscribed; no boxing, no closure, no allocation. The handlers sit on the combat swing path.
- **Exception contract:** subscriber exceptions propagate, matching `EventSink`/`HelpEvents`; no try/catch by design.

**2. `InternalsVisibleTo("ModernSpawner.Tests")`** on `Server.csproj`, beside the existing `Server.Tests`/`UOContent.Tests` entries, so an external test host can seed `Core._now` the way the engine's own test initializers do. Separable; a public test seam on `Core` would serve the same need without naming a downstream assembly.

## Open question

The payload is the `Skill` object plus a positional `bool`. A `readonly struct` args type passed `in` would leave room to add `chance` or the target later without breaking subscribers. Happy to change before merge.

## Test plan

- [x] `UOContent.Tests`: 4 tests — a rolled attempt raises once with the returned outcome; each short-circuit path (no challenge, too difficult, on both the direct and value-window handlers) raises with the handler's result; a direct `CheckSkill` call does not raise; no subscriber does not throw. Full suite green.
- [x] `Server` and `UOContent` build clean with `TreatWarningsAsErrors`.
- [ ] CI
2026-09-11 22:58:44 -07:00
..
claude-skills fix: pets freeze, forget their orders, obey the wrong players, and fight when told not to (#2614) 2026-09-10 19:28:13 -07:00
runuo-migration-docs perf: keep damage entries in an inline intrusive list (#2605) 2026-09-01 23:25:14 -07:00
code-standards.md feat: Add zero-alloc interpolation handler to ValueStringBuilder, replace all StringBuilder usage (#2387) 2026-03-22 14:23:44 -07:00
commands-targeting.md fix: TextDefinition was uneditable in the props gump, and where parsed constants its own way (#2624) 2026-09-10 19:36:30 -07:00
configuration.md fix: Fixes Argon2 verify correctness and the password upgrade lockout (#2562) 2026-08-08 00:24:59 -07:00
content-patterns.md fix: pets freeze, forget their orders, obey the wrong players, and fight when told not to (#2614) 2026-09-10 19:28:13 -07:00
debugging-event-loop.md fix: stop the idle-sleep backoff tripping on healthy hosts (#2572) 2026-08-13 19:58:03 -07:00
era-expansion.md chore: Adds AI instructions and SKILLs for ModernUO codebase (#2347) 2026-03-01 11:42:19 -08:00
event-scheduler.md chore: Adds AI instructions and SKILLs for ModernUO codebase (#2347) 2026-03-01 11:42:19 -08:00
events.md feat(skills): SkillEvents.SkillUsed for cross-assembly subscribers; InternalsVisibleTo ModernSpawner.Tests (#2636) 2026-09-11 22:58:44 -07:00
generic-commands.md fix: TextDefinition was uneditable in the props gump, and where parsed constants its own way (#2624) 2026-09-10 19:36:30 -07:00
gump-system.md docs(messages): document interpolation anti-patterns and :L format spec (#2441) 2026-05-03 18:23:50 -07:00
ip-bans-and-allowlists.md feat: make the blocklist and manual allowlist opt-in; cut the ban subsystem's on-loop cost (#2577) 2026-08-13 23:22:35 -07:00
networking-packets.md feat: make the blocklist and manual allowlist opt-in; cut the ban subsystem's on-loop cost (#2577) 2026-08-13 23:22:35 -07:00
pathfinding.md fix(pathfinding): stop opening every .swb twice at boot (#2548) 2026-07-25 13:07:20 -07:00
platform-prerequisites.md fix: Require only runtime packages on Linux, and check ICU and tzdata the way the runtime does (#2561) 2026-08-07 15:03:08 -07:00
property-lists.md fix(opl): refuse property list invalidation raised from inside GetProperties (#2555) 2026-07-28 21:29:03 -07:00
regions.md chore: Adds AI instructions and SKILLs for ModernUO codebase (#2347) 2026-03-01 11:42:19 -08:00
serialization.md docs: update serialization docs and skills for generator v4 (#2588) 2026-08-22 18:29:27 -07:00
server-lifecycle.md refactor(server): unify first-boot prompts into the ConfigurePrompts phase (#2477) 2026-06-08 05:18:46 -07:00
server-requirements.md fix: stop the idle-sleep backoff tripping on healthy hosts (#2572) 2026-08-13 19:58:03 -07:00
string-handling.md docs(messages): document interpolation anti-patterns and :L format spec (#2441) 2026-05-03 18:23:50 -07:00
t2a-crafting.md feat: Pre-Publish 14 Crafting (supersedes #2181, #2381) (#2476) 2026-06-07 20:27:22 -07:00
threading-model.md perf(login): run password hashing on a parked worker thread (#2566) 2026-08-09 00:13:34 -07:00
tick-counts.md perf: Sleep the event loop when idle. Fixes networking micro-stalls. Adds event loop instrumentation. (#2559) 2026-08-09 13:24:59 -07:00
timers.md docs: update serialization docs and skills for generator v4 (#2588) 2026-08-22 18:29:27 -07:00