Commit graph

1969 commits

Author SHA1 Message Date
292cd625c5 #W# Added: Added an Overworld dungeon region. Tweaked overworld animal spawn. 2026-10-01 20:37:16 -04:00
6213604bb1 #W# Skill: The heal skill is now activatable. It heal poison/bleed 100% has a 15 second timer. 2026-10-01 18:53:52 -04:00
10d1368fb3 #W# Work: Working on innate abilities. 2026-10-01 17:20:22 -04:00
a628d30ed6 #W# Work: Working on innate abilities. 2026-10-01 13:45:10 -04:00
c22afc8ad6 #W# Tweak: Tweaked FarmSpawner times. 2026-10-01 11:36:01 -04:00
eb35515ad8 #W# Update: Updated BuildWorld gump. 2026-10-01 11:33:26 -04:00
029b4c2bc3 #W# Gumps: Tweaked the admin gump to use the new buildworld gump. Also added [buildworld command. 2026-10-01 10:26:01 -04:00
Kamron Batman
d4531cd94b
fix: enumerators that end early, repeat entries, or never end (#2685)
Fixes #2682. Thanks @DatMoshu for the detailed report and repro.

While fixing it I audited the other map, item and multi enumerators for the same class of defect. None repeats #2682 itself, but four others turned up. All are fixed here.

## Fixes

- **`StaticTileEnumerator` (#2682)**: `SetMulti` returned `SetTile()` for the first multi whose bounds contained the point. If that multi had no tile at the cell, the enumeration ended and every later multi in the sector was skipped. Movement and line of sight lost a floor that stood inside another multi's bounding box. It now moves on to the next multi.
- **`MultiBoundsEnumerator`**: deduplication used one static `HashSet` that every new query cleared. A query run inside another made the outer query skip multis or return them twice. Each multi is now yielded only from the sector that holds its `Location`; the bounds filter guarantees that sector is visited. A per-enumerator set covers a multi that isn't registered in that sector (components changed after placement). Normal queries allocate nothing.
- **`Item`/`Mobile`/`ClientDistanceEnumerator`**: ring sectors were bounded only by `sx >= _sectorStartX`. Ring cells past the map edge resolved to the shared invalid sector, once per cell. Entities at off-map coordinates were returned more than once, and sectors outside the query were created lazily. The check now covers all four sides. Results match the bounds enumerators, which never returned off-map entities.
- **`BaseBoat.MovingEntitiesEnumerator`**: `GetMovingEntities` handles a null map, but the enumerator constructor still called `boat.Map.GetMobilesInBounds` and threw. A boat with no map now gets empty enumerators.
- **`BaseDoor.ChainEnumerator`**: the walk stopped only on returning to the start door. Links are set in pairs, so `[ChainLink A B C` followed by `[Link B C` leaves A→B→C→B, and enumerating that chain never ended. The walk now stops on any door it has already yielded. Only reachable when `UseChainedFunctionality` is true, which is off in base and in all shipped content.

## Testing

- A regression test for each fix except the boat null guard. Each failed before its fix and passes after.
- Server.Tests 910/910. UOContent.Tests 1185 passed, 2 skipped.
- Test note: the `MultiComponentList(List<MultiTileEntry>)` constructor drops entries after the first whose `Flags` are 0. The new test multis use non-zero flags.
2026-09-30 21:24:44 -07:00
72ce92cf34 #W# Added: Added a new [bandself command. 2026-09-30 08:10:01 -04:00
Reetus
edd7553c6f
fix: a juka mage buff no longer permanently raises a juka lord (#2684)
## Summary

Fixes Juka buff so that restored values are correct.
2026-09-29 08:32:44 -07:00
Sergi Rosell
e440c6fafa
fix: stealing no longer takes items out of a locked container (#2681)
`TryStealItem` doesn't check for a locked container, so an item that can still be targeted inside one (the gump stays open when it gets locked, or the client targets by serial) is stolen without picking the lock. 

It now refuses with 501747 ("It appears to be locked."); GMs are exempt, and stealing the locked container itself is unchanged. It checks the lock directly rather than calling `CheckLift`, which would also refuse items in deco containers.
2026-09-28 10:32:01 -07:00
813ba0cd20 #W# Added: Added more farm plots to the farm spawner system. 2026-09-27 20:09:29 -04:00
0d3b637a45 #W# Added: Farm spawner system. 2026-09-27 18:38:26 -04:00
b94e0d634d #W# Change: Change Veridia's map rule set. 2026-09-27 12:56:07 -04:00
accb38d7d2 #W# Spawn: Added low tier mobs to the dynamic spawner. 2026-09-26 20:15:54 -04:00
895e18149a #W# Spawn: Overworld animal spawn is done. 2026-09-26 18:06:20 -04:00
Kamron Batman
225c634bf7
test: stop the 1 ms promotion window leaking out of StepCacheLifecycleTests (#2679)
## Summary

`Find_TwoPathfindsOverlappingChunks_PromoteToBuildOnSecondFind` fails intermittently in CI with `second Find through overlapping chunks must promote (got 0 builds)`.

**Cause:** `SecondTouchAfterWindow_RestartsTheCount` and `TwoFindsAcrossTheWindow_DoNotPromote` set `StepCache.Instance.MissPromotionWindowMs = 1` and never restore it, and `Clear()` does not reset it. Both classes share the `Sequential Pathfinding Tests` collection, so when a lifecycle test runs first, the promotion test's two Finds have to land within 1 ms of each other. On Linux, `Environment.TickCount` moves in coarse steps of several ms, so even a sub-millisecond test can see the window lapse. The second Find then restarts the miss count instead of promoting. On Windows the ~15.6 ms tick nearly always reads the same value for both Finds, which is why it didn't reproduce locally.

**Fix (tests only):**
- `StepCacheLifecycleTests` implements `IDisposable` and restores the window after each test.
- The promotion test sets the window it depends on (30 s) instead of inheriting it.

## Test plan

- [x] Reproduced deterministically: with a temporary 20 ms sleep between the two Finds, the test fails whenever the lifecycle tests run first and passes when run alone
- [x] With the sleep still in, the test passes with the Dispose restore alone and with both fixes
- [x] All pathfinding tests pass (123/123) with the sleep removed
2026-09-25 00:55:07 -07:00
Sergi Rosell
37a80e3447
fix: Begging's failed-roll refusal (500404) goes to the beggar, not the target (#2678)
### Summary

Fixes an issue where beggars that fail their skill check don't get the error message saying the NPC is unwilling to give them any money.
2026-09-25 00:50:10 -07:00
Kamron Batman
95177fb647
fix: a deleted character's followers, and familiars that never unregistered (#2675)
## Summary

**Followers of a deleted character.** `PlayerMobile.OnDelete` deleted stabled pets and left every active follower pointing at the deleted mobile. Pets stayed controlled by nobody, and after a restart they loaded as owned pets with no owner. Now, when a character is deleted:
- bonded or dead pets and summons (controlled or not) are deleted;
- unbonded pets are released: they go wild and despawn on the abandoned-pet timer;
- escorts and hirelings are left alone, because they already notice a deleted master and walk off on their own.

**Familiars and animated dead that never unregistered.** `BaseCreature` unregistered them in `OnAfterDelete`, after `OnDelete` had already cleared the master. Both calls did nothing, so `SummonFamiliarSpell.Table` and the animated-dead registry kept deleted creatures until the caster's next cast. The unregisters now run in `OnDelete`, before the master is cleared.

## Test plan

- [x] `DeletedMasterTests` (6 cases, all failing on main): unbonded pet released; bonded and bonded-dead pets deleted; controlled and uncontrolled summons deleted; familiar unregistered on delete
- [x] UOContent.Tests (1184 passed, 2 skipped) and Server.Tests (907) green
2026-09-24 23:01:40 -07:00
Kamron Batman
843a29e7dc
refactor: master views over BaseCreature's one master reference (#2674)
## Summary

After #2670, `BaseCreature` stores one master. Callers still reached it through three differently gated names plus hand-rolled combinations of the `Controlled`/`Summoned` flags. Each gate is now one named view:

| Member | Definition | Meaning |
|---|---|---|
| `Master` | the reference; its setter does the follower bookkeeping; serialized as before | whoever the creature answers to |
| `ControlMaster` | `Controlled ? Master : null` | the owner |
| `SummonMaster` | `Summoned ? Master : null` | the summoner |
| `GetMaster()` | `ControlMaster ?? SummonMaster` | who answers for the creature |

`ControlMaster` and `SummonMaster` stay settable so custom code keeps compiling: both setters assign `Master`. There is no guard, so a write through either one can read back as null until the matching flag is set.

An enraged creature is the one shape with a master and neither flag. It keeps its meer through `Master`, while `GetMaster()` stays null, so the meer never answers for it (notoriety, kill credit).

Readers move onto the view they mean:
- "Owner or summoner" pairs and `ControlMaster ?? SummonMaster` collapse into `GetMaster()`: pack instinct, familiars, golem, house access, mounts, pack horse, Solen friendship, spell target and guild checks, and the `[` pet command.
- Checks such as `Controlled && ControlMaster == from` drop the flag the view already applies. Checks that don't reduce keep it: `Controlled && ControlMaster != from`, a possibly-null `from`, and `MageAI.CanDispel`'s `Summoned &&`.

**No behavior change, save format unchanged** (the schema generator produces no diff). Edge cases for custom code and shard operators:
- `SummonMaster = x` on a plain pet now replaces its owner (#2670 made that a no-op), and `ControlMaster = null` on an uncontrolled summon now clears its caster (it did nothing before).
- `SpellHelper.GetGuildFor` no longer falls back to an enraged creature's meer. Meer mages have no guild.

## Docs

- `dev-docs/content-patterns.md` § Masters: which view answers which question, how to set a master, and which flag checks are redundant.
- `dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md` § Masters: RunUO's two-field reads mapped onto the views, plus the gotcha for creatures that set `SummonMaster` without `Summoned` (the write compiles, the read now returns null; use `Master`).
- The `modernuo-content-patterns` and `migrate-items-mobiles` skills point at both.

## Test plan

- [x] `MasterViewsTests`: all four views across wild, pet, controlled summon, uncontrolled summon and enraged shapes; reassigning `Master` moves follower slots; `SetControlMaster(null)` keeps an uncontrolled summon's caster; deleting a summon returns the caster's slots
- [x] Serialization tests updated to the new views (round-trip, legacy v22, v23 migration)
- [x] UOContent.Tests (1178 passed, 2 skipped) and Server.Tests (907) green
2026-09-24 22:45:24 -07:00
Kamron Batman
37970a4811
fix(ai): meer mages and their enraged creatures no longer fight each other (#2673)
## Summary

A meer mage fights `FightMode.Evil` and its enraged creatures carry -1000 karma, so `BaseCreature.IsEnemy` made each an enemy of the other:
- The meer acquired its own enraged creatures as ordinary targets.
- An enraged creature fell back to attacking its meer when nothing else was in range. It isn't `Summoned`, so it reached the meer through `AcquireNewFocusMob`'s summon-master fallback.

`BaseEnraged` and `MeerMage` now override `IsEnemy` to exclude each other. Another meer's creatures stay enemies.

`BaseEnraged` also pulled its meer into fights against any creature with a `SummonMaster`, which included another meer's enraged creatures. It now checks `GetMaster()`, matching the "controlled/summoned" OSI note in that method.

## Test plan

- [x] `MeerAndItsEnragedCreature_AreNotEnemies`: fails before the fix
- [x] `AnotherMeersEnragedCreature_StaysAnEnemy`
- [x] `MeerAndItsEnragedCreature_NeverAcquireEachOther`: fails before the fix (the meer acquired its own creature)
- [x] UOContent.Tests (1171 passed, 2 skipped) and Server.Tests (907) green
2026-09-24 21:42:40 -07:00
Kamron Batman
05b6ca9d5d
refactor: one master reference behind ControlMaster and SummonMaster (#2670)
## Summary

`BaseCreature` stored the pet owner (`ControlMaster`) and the summoner (`SummonMaster`) in two fields. Every flow that manages pets kept them in step by hand: summon, transfer, stable, claim, ball of summoning and GM obey. They now share one `_master` field, and the two properties read it through the Controlled and Summoned flags:

```csharp
ControlMaster => Controlled ? master : null
SummonMaster  => Controlled && !Summoned ? null : master
```

Every existing reader keeps its meaning:
- `ControlMaster` is still "the owner".
- `SummonMaster` is still "the summoner, controlled or not".

Blade spirits, energy vortexes, clones, undead minions and enraged creatures (a summon master without `Summoned`) never become visible through `ControlMaster`, so the ungated readers (ML notoriety, aggressor entries and similar) behave as before.

- Setting `SummonMaster` on a plain pet does nothing, instead of quietly replacing the owner.
- `SetControlMaster` now keeps both masters in step, so the hand-written `SummonMaster` copies are removed from ball of summoning, GM obey, pet transfer, auto-stable and claim, the animal trainer's stable and claim, and jail stabling.

## Serialization

BaseCreature moves to v24 with one serialized master (slot 15); the old `SummonMaster` slot is dropped.
- `MigrateFrom(V23Content)` resolves absent fields through the same default-value methods the live reader uses.
- A save that carried both masters (a summon traded before ownership followed transfers) resolves to the owner. Legacy pre-codegen streams follow the same rule.

Requires `ModernUO.Serialization.Generator` 4.2.0 (#2671). `UOContent.Tests` was still on 4.1.0 and is bumped to match.

## Test plan

- [x] `MasterShapes_RoundTrip`: controlled pet, controlled summon, energy vortex, enraged creature
- [x] `SummonMaster_OnPlainPet_KeepsTheOwner`: owner and follower counts unchanged
- [x] `LegacyV22Stream_DifferingMasters_OwnerWins`
- [x] `V23Stream_MigratesToOneMaster_AndDefaultsAbsentFields`: both master slots -> owner; flags past bit 32; absent fields take class defaults
- [x] UOContent.Tests (1168 passed, 2 skipped) and Server.Tests (907) green
2026-09-24 21:37:28 -07:00
Kamron Batman
77bd0d9a64
fix: Adds nullability support and fixes crash bugs in serialization generator (#2671)
### Summary

Bumps serialization generator to add nullability support and to fix various bugs.
2026-09-24 01:01:40 -07:00
Kamron Batman
92ed9c5456
fix: player corpse notoriety ignores the owner's body at death (#2669)
CorpseNotoriety returned Murderer for a player corpse whenever the corpse body was a monster
body. RunUO's check was `body.IsMonster && IsSummoned(owner)`, which is never true for a player;
the second half was lost when the branch was simplified (#134). Players who died while
polymorphed or in a necromancer form left a corpse with the wrong notoriety.

Player corpses also check murderer and criminal status before guild standing again, matching
RunUO and MobileNotoriety.

Tests: UOContent.Tests 1162 passed (4 new CorpseNotorietyTests).
2026-09-23 19:43:37 -07:00
01c0676c7a #W# Save: Just a quick save. 2026-09-23 17:20:43 -04:00
Kamron Batman
08dab47413
fix: check Controlled before reading ControlMaster (#2668)
Readers of `ControlMaster` that act on "this creature is a player's pet" now also require
`Controlled`. Nothing sets a master on an uncontrolled creature today, so behavior does not
change, except that the ML notoriety branch was the one read with nothing in front of it.
The guard keeps these readers correct if a creature ever keeps a master without being
controlled (a suspended-control state such as a charm or turn-pet ability, or folding
SummonMaster into one master field).

- Notoriety (ML): a creature only takes its master's notoriety while controlled
- BaseCreature.AggressiveAction: the aggressor link to the master only applies to controlled pets
- PlayerMobile.AutoStablePets: uncontrolled summons (blade spirits, energy vortexes) are skipped
- CrystalCaveBarrier: only a controlled pet passes on its owner's quest progress
- Discordance: the own-pet exception only applies to a controlled pet
- Dismount (ML): the remount block only goes to the owner of a controlled pet

Also removes the "Summons from monsters can attack players" branch in Mobile_AllowHarmful.
It has been unreachable since #2000: a creature with no player master returns from the
NPC check above it.

Tests: UOContent.Tests 1158 passed.
2026-09-23 11:45:48 -07:00
428f7c4c1d #W# Spawns: Still tweaking the Dynamic Spawners. 2026-09-23 14:27:13 -04:00
Kamron Batman
00688a04cf
fix(ai): guard zero-int detection cooldown and duplicate pet friends (#2667)
Supersedes #2658 (rebased onto main; edits by maintainers were not enabled on the original branch). Credit to @MithrilHammer for both fixes and the regression tests; their three commits are kept as authored.

Addresses two items from #2627:

- The detect-hidden cooldown clamps its divisor to at least one. A creature whose effective intelligence reaches zero now uses the existing capped cooldown instead of throwing.
- Pet friends are unique. `AddPetFriend` ignores an existing friend, so one removal revokes the membership.

On top of that, `AddPetFriend` and `RemovePetFriend` now go through the generated `AddToFriends` / `RemoveFromFriends`, which mark the creature dirty. `Friends` goes back to null when the last friend is removed, and removing a non-friend no longer touches the list.

Duplicates already stored in saves are not migrated.

## Tests

`DetectHiddenTimerTests` (7 cases) drives the real AI timer and checks cooldown bounds and retry timing. `PetFriendMembershipTests` (3 cases) covers duplicate adds, removal, and re-adding without disturbing other friends. `UOContent.Tests`: 1158 passed, 2 skipped, 0 failed on Windows.
2026-09-23 10:42:38 -07:00
Kamron Batman
3eb3f0fb5c
fix(ai): restore pre-AOS berserk energy vortexes and blade spirits (#2666)
#2132 let a player's energy vortex or blade spirits in pre-AOS Felucca target their own caster, gated by `FollowsAcquireRules`. #2232's AI overhaul moved the summon checks into `IsInvalidSummonTarget`, which excluded the caster in every era, and dropped the gate. Since then `FollowsAcquireRules` has had no readers and summons have never turned on their caster.

## Changes

- `IsInvalidSummonTarget` reads `FollowsAcquireRules` again. A summon that follows the acquire rules spares its caster and anyone the caster's harmful spells would spare. The vortex and blade spirits don't follow them in pre-AOS Felucca when a player summoned them.
- The animated-dead restrictions (no players, no other animated dead, no pets) apply to every animated summon regardless of that setting.
- `AcquireNewFocusMob` sets a berserk summon's caster aside and targets them only when nobody else is available. #2132 meant to prefer others, but its check only worked when the caster was scanned before the other candidates, and the vortex's Int-plus-Magery ranking usually puts a nearby caster first.
- The AOS summon-target check no longer dereferences a missing `SummonMaster`. A summon whose caster was deleted before a reload comes back without one.

Controlled summons are unaffected: since #2665 they target through `HandleControlled` and never reach this scan.

## Tests

`SummonAcquireRulesTests` has 6 cases, skipped without client map data. Pre-AOS Felucca: a vortex and blade spirits each target their caster when the caster is alone, and a vortex targets a bystander even when the caster ranks higher. Pre-AOS Trammel and AOS Felucca: the caster is still spared. An AOS scan next to a summon with no master doesn't throw. `UOContent.Tests`: 1148 passed, 2 skipped, 0 failed on Windows.
2026-09-23 10:29:20 -07:00
Kamron Batman
6b910ff089
fix(ai): guarding pets never target their owner or the owner's pets (#2665)
Supersedes #2657 (rebased onto #2662, which the original branch conflicted with). Credit to @MithrilHammer for the diagnosis, the fix and the regression tests; their three commits are kept as authored.

Fixes #2656.

## Root cause

When a guarding pet's combatant hides or becomes invalid, its AI calls `AcquireFocusMob` to find a new target. A controlled creature with no `ControlTarget` (Guard normally has none) fell out of `HandleControlled` and into the wild-creature scan, where `IsEnemy` treats any player as an enemy. With `FightMode.Closest`, the owner won. The fall-through dates to #2232. RunUO never let a controlled creature reach the wild scan.

## Changes

- `AcquireFocusMob`: a controlled creature returns whatever `HandleControlled` decides. Guard uses `FindGuardTarget`, Attack uses its `ControlTarget`, and every other order acquires nothing.
- `FindGuardTarget` skips guard allies: the master and creatures the master controls, including controlled summons. Uncontrolled summons (energy vortex, blade spirits) attack their caster, so a guard still defends against them, as in RunUO.
- Friendly fire: `BaseAI.OnAggressiveAction` stands a guard down when an ally is the aggressor. `BaseCreature.Combatant` refuses an ally while guarding, because `Mobile.AggressiveAction` assigns the first aggressor as `Combatant` before any retaliation policy runs. The veto sits on `Combatant` rather than `CanBeHarmful`, so what a pet can harm doesn't depend on its current order. An explicit Attack order and bard provocation can still target an ally.

## Tests

16 regression cases in `GuardFriendlyFireTests` (skipped without client map data), including a real `WhiteWyrm` reproduction. `UOContent.Tests`: 1142 passed, 2 skipped, 0 failed on Windows.
2026-09-23 10:13:34 -07:00
484fbb6512 #W# Spawns: Working on a Biome spawn system. 2026-09-23 13:02:12 -04:00
Kamron Batman
28c38168bc
test: fix order-dependent enumerator test failures; bump MailKit and Test.Sdk (#2664)
## Summary
- `AnchoredItemSerializationTests.LastMovedAndDecayReset_RoundTripExactly` never deleted its deserialized copy. `Item.Deserialize` enters the item into its map's sectors, so the copy stayed live on Felucca at (121,100). `ItemEnumerator_FiltersByBoundsAndOrder`, `ItemByDistanceEnumerator_Bounds_FindsItemsInBounds` and `ItemByDistanceEnumerator_Bounds_MakeBoundsInclusive` scan that area and found an extra item whenever the serialization test ran before them, so they failed depending on test order. The copy is now deleted in a `finally`.
- Bump MailKit 4.17.0 → 4.18.0 and Microsoft.NET.Test.Sdk 18.10.0 → 18.10.1.

## Test plan
- [x] Probe test: a deserialized copy shows up in `GetItemsInBounds` (reproduced), and after the fix the area is empty
- [x] Server.Tests: 907/907 pass
- [x] UOContent.Tests: 1126 pass, 2 skipped
- [x] Solution builds with 0 errors
2026-09-22 18:53:06 -07:00
Kamron Batman
502c3f6aa4
fix(ai): BaseAI.OnAggressiveAction owns the retaliation policy; familiars refuse cleanly (#2662)
> [!IMPORTANT]
> **Breaking change to API to fix NPC AI issues**

## Symptom

`FamiliarAITests.HiddenCaster_FamiliarRefusesRetaliation` fails on main at `Assert.False(wolf.Warmode)`: the familiar's `Combatant` stays null as intended, but it is left in war stance. In-game the familiar of a hidden caster flips into warmode for a tick when struck, until `FamiliarAI.StandDown()` clears it on the next think.

CI never saw it because the familiar tests are `SkippableFact` (they need client tiledata).

## Root cause

#2644 and #2645 landed a day apart and are individually correct but conflict at one seam.

Since #2645, a familiar (`Commandable == false`) is no longer "told to stand down", so `BaseCreature.AggressiveAction` continues past `AIObject.OnAggressiveAction(aggressor)` to its generic fallback:

```cs
else if (Combatant == null && !BardPacified)
{
    Warmode = true;
    Combatant = aggressor;
}
```

`FamiliarAI.OnAggressiveAction` had already declined (caster hidden), but the hook was `void`, so the creature could not tell. The fallback raised `Warmode` before `BaseFamiliar.Combatant` vetoed the target.

## Fix

This is the follow-up agreed in #2645: the retaliation policy moves into a virtual `BaseAI.OnAggressiveAction(aggressor) -> bool`.

- **`BaseAI.OnAggressiveAction`** now holds the stand-down verdict, the closer-aggressor swap (`PreferCloserAggressor`), and the `IssueOrder(Attack)` vs `Combatant =` decision. Returns `false` when the creature stands down.
- **`BaseCreature.AggressiveAction`** keeps only bookkeeping — `ControlMaster.Aggressors` entry, `StopFlee`, `ForceReacquire`, ethics `FinishShield` — keyed off the return, so an AI that declines is never second-guessed.
- **`FamiliarAI`** overrides the whole policy: no orders to stand down on, never fights for a hidden caster or a hidden aggressor.

Behaviour for every other AI is unchanged. Moving the fallback ahead of `StopFlee`/`ForceReacquire` is order-safe: both are pure field resets, `IssueAttack` does not `Think()`, and `OnCombatantChange` only derives `Warmode`.

Two commits: the first is the minimal `bool` hook that alone fixes the test, the second the policy move.

## Not changed (noted for later)

- `Warmode = true; Combatant = x;` (also in `FamiliarAI.Act`, `IssueAttack`) is redundant when the setter accepts — `BaseCreature.OnCombatantChange` derives `Warmode` — and is the dangling-warmode hazard whenever a `Combatant` setter vetoes.
- Since #2614 a stood-down pet skips the ethics `FinishShield`; RunUO ran it unconditionally. Preserved as-is.

## Tests

`HiddenCaster_FamiliarRefusesRetaliation` fails on main and passes here. Full `UOContent.Tests`: 1126 passed, 0 failed, 2 skipped (spawner fixture capture + benchmark).
2026-09-22 18:48:13 -07:00
Kamron Batman
29a3ab1bd4
feat(admin): replace dead ThreadPool counters with a network readout (#2663)
## Summary

The admin gump's **Performance** page still rendered `ThreadPool` worker / IOCP quotas — a RunUO leftover from `Socket.BeginReceive` days. Both figures are dead: IORingGroup drives RIO / io_uring / epoll / kqueue synchronously on the game loop, so the IOCP figure reads 0% forever, and the real off-loop work (serialization workers, password checks, advanced search, console input) runs on dedicated threads the worker figure never sees.

This replaces the block with a read-only network readout.

## Engine

- `NetState.GetNetworkStats()` — new public accessor returning a `NetworkStats` snapshot (ModernUO-owned record types in `Network/NetworkStats.cs`, so UOContent takes no dependency on transport types):
  - live: connected / max sockets, authenticated count, per-tier growth usage, the three loop queues (throttled, flush pending, pending disconnects)
  - configured: base / pre-auth / max buffer sizes, growth budget, memory ceiling, last-read available memory
  - `LastSweep`: everything the once-a-minute maintenance sweep saw, including the refusal counters it resets
- The sweep already kept four of its results (`_lastTier*`, `_lastBaseCapacityBytes`) for change detection; those fold into one `SendBufferSweep` snapshot. Sweep logic and logging are unchanged.
- The accessor never calls `Maintain()`, so opening the page cannot rotate the retention window.
- Tick age is compared by subtraction; "has a sweep run" is a `bool`, not a zero-tick sentinel.

## Gump

```
Event Loop:    Healthy
Connections:   123 / 4096 (117 authenticated)
Loop Queues:   throttled 0, flush 3, closing 0
Send Buffers:  [scrollable]
  Base: 64.0 KB recv, 256.0 KB send; pre-auth 4.0 KB / 4.0 KB
  Base pools: 10.2 MB
  Growth tiers (live):
    512.0 KB: 3 / 32 in use, floor 5
    1.0 MB: 0 / 0 in use, floor 0
    2.0 MB: 0 / 0 in use, floor 0
  Budget: 16.0 MB of 256.0 MB, max 2.0 MB per socket
  Ceiling: 80% of 7.8 GB available
  Last sweep 42s ago: released 0 tier, 0 base
  Refused: budget 0, at max 0, ceiling 0
```
2026-09-21 15:57:23 -07:00
5b6463a4a9 #W# Added: Full Spellboooks are now spawnable. 2026-09-21 14:45:13 -04:00
625b9eee1b #W# Added: Expa's Exile Hunter Bestiary system 2026-09-21 10:16:46 -04:00
86d28f32a2 #W# Added: Innate weapon abilities. 2026-09-21 10:15:27 -04:00
58ebc86b6f #W# Fix: Boat wrapping is working. 2026-09-21 10:04:48 -04:00
e2cee3ce9f #W# Added: Autostable. Script to Umounts/Remounts in certain regions. 2026-09-21 10:02:09 -04:00
0a4d972af6 #W# Added: Added some custom regions. 2026-09-21 10:01:24 -04:00
40dd003fa8 #W# Change: Change new character start locations. 2026-09-21 09:59:08 -04:00
c8a376c104 #W# Tweak: Necro books are now be equiped. 2026-09-21 09:51:37 -04:00
2cfa8c8a9c #W# Change: Mobs are hidden until player has line of site. 2026-09-21 09:48:08 -04:00
128a40e2c0 #W# Change: Spinning wheels and looms process the entire stack. 2026-09-21 09:38:11 -04:00
373369c28e #W# Change: Walking over farmable crops auto harvests them and puts it in your backpack. 2026-09-21 09:33:39 -04:00
16bd0c8269 #W# Change: Mining now only produces the large graphic. 2026-09-21 09:30:20 -04:00
dcfaced31e #W# Change: Harvesting (mining, lumberjacking, fishing,) continue until complete. 2026-09-21 09:19:24 -04:00
4170f92bdc #W# Added: [ESA command exports all spawners on the map. [IS </path/*.json> imports spawners from specified file. 2026-09-21 09:08:13 -04:00
ec93eeb4fb #W# Change: [decorate now looks in Overword/Underworld Folders. 2026-09-21 09:03:07 -04:00
6388a96fa9 #W# Change: [Go menu now uses 1 file Data/go.json. It also excepts a 'map' destination. 2026-09-21 09:00:23 -04:00