Commit graph

20 commits

Author SHA1 Message Date
Kamron Batman
346228fa69
fix(ai): pet order/home refactor — stop & post-combat behavior (#2459)
## Summary

Fixes two related pet-behavior bugs and the underlying design flaw behind both:

1. **Post-combat erratic** — after a pet killed its `all kill` target it milled around erratically at the kill site (or failed to return to the master) until the player issued `all follow`/`all stop`.
2. **`all stop` returns home** — a pet with a non-zero `Home` walked back toward that location on `all stop` (on ML; on non-ML the old `DoOrderStop` was a no-op, so the sighting there came from a residual `Stay`).

### Root cause

`ControlOrder` and the wild-creature `Home` field were overloaded to express several distinct ideas, mutated/read inconsistently across order transitions:

- `Home` doubled as the controlled-pet "stay anchor" (`HandleStayOrder` set `Home = Location`) but nothing cleared it when the pet left the staying state; `HandleStopOrder` was the only handler that never touched it.
- `DoOrderStop` had dropped RunUO's `Home = Location` re-anchor, so on ML it walked to a stale anchor.
- The post-combat fallback was a fragile `_lastPetOrder` hack in `DoOrderNone` that re-anchored a resumed `Stay` at the corpse.
- Controlled idle-wander bypassed the `CheckIdle()` rest gate that every non-controlled creature uses, so idling pets jittered every AI tick.

## Approach

Separate three concepts that were tangled together:

- **`ControlOrder`** — the active order (may be transient: Come/Attack/Drop).
- **Persistent command** (`PersistentOrder` ∈ `{None, Stay, Follow, Guard}`) — the standing directive a pet falls back to when a transient order completes. Runtime-only (not serialized; reset to `None` on load) and **derived from master proximity on login** (near → Follow, far → Stay).
- **Anchor** (`Home`) — a pure function of the persistent command, set only when that command changes (never on transient transitions or fallback-resume), so it can't go stale.

### Behavior

- **Stop** is resolved immediately from what the pet was doing: Attack/Come → resume the persistent command; Follow/Guard → cancel to idle where it stands; Stay → stay put.
- **Stay** holds its post (returns only if displaced, e.g. after a fight) — no shuffle.
- **Idle** (`None`) is a gentle wander routed through `CheckMove/CanMoveNow/CheckIdle`, so idling pets take the same 15–25s rest periods as other creatures, on both ML and non-ML.
- **Post-combat** the pet resumes its persistent command (a staying pet returns to its original post, not the corpse).
- **Release** without a spawner anchors where the pet stands instead of pathing to a stale anchor.

This restores the RunUO-intended behavior (verified against the RunUO reference) while fixing the ModernUO regressions.

## Tests

New `PetOrderTests` (13 deterministic xUnit tests) cover: anchor lifecycle, the full Stop truth table, report 1 (post-combat return to post), report 2 (no stale-anchor walk-home), frozen-Stay/gated-idle wiring, release fix, derive-on-login, and a non-ML spot-check. The subjective wander *feel* is covered by a manual-QA checklist in the implementation plan.

## Notes

- Engine project (`Projects/Server`) untouched; the one `BaseCreature.cs` change is the `ControlOrder` setter passing the previous order to `OnCurrentOrderChanged`.
- `DoOrderCome` keeps auto-converting to `Stay` on arrival, which under the new model cleanly means "come and hold near me."
- Commits in this PR are temporarily **unsigned** (the signing agent's passphrase cache expired mid-session); happy to re-sign / amend on request.
2026-06-07 01:22:43 -07:00
Kamron Batman
cff9fbda29
fix(ai): creatures pathfind around concave obstacles instead of oscillating (#2461)
## Summary

Creatures (pets following, monsters chasing, NPCs approaching) would **oscillate — "pace back and forth really fast"** at concave obstacles (reported at the Britain Inn L-desk: a pet at `(1493,1614,20)` never reaching its master at `(1494,1605,21)`) instead of routing around them.

**Root cause** (the A* pathfinder itself was correct): all goal-seeking funnels through `MoveTo` and `WalkMobileRange` → `MoveTowardsOrAwayFrom`, which step greedily via `DoMove(dir, badStateOk:true)`. `DoMove` returns `true` even when the direct step was blocked and the creature merely **auto-turned and sidestepped** (`MoveResult.SuccessAutoTurn`), and the caller then set `Path = null`, discarding the `PathFollower`. So at a concave obstacle a non-progressing sidestep was mistaken for progress and the creature never committed to a route. (AOS pet-follow runs at `CurrentSpeed = 0.1`, hence the "really fast" shuffle.)

## What changed

- **New centralized `BaseAI.ApproachTarget(target, run, range)` primitive.** A greedy step is committed only when it **fully succeeds (`MoveResult.Success`) and actually gets closer**; otherwise the creature commits to a **persistent `PathFollower`** that routes around the obstacle and is never discarded by a greedy step. The open-terrain fast path (one greedy step, no pathfinding) is preserved. `MoveTo`, `MoveTowardsOrAwayFrom`, and `MoveToWithCollisionAvoidance` all delegate to it — public signatures unchanged, so no AI-class call site changes.
- **Best-distance give-up + idle.** A creature that cannot reach a **stationary** in-range goal stops shuffling and idles after `ApproachGiveUpTicks` (40) ticks without lowering its closest-ever distance; a **moving** goal (active chase) never gives up. It resumes the moment the goal moves.
- **Pathfinder fix (required):** `BitmapAStarAlgorithm.IsBlockedByDynamic` now skips the dynamic mobile-block check **at the goal cell only** (`MoveImpl.Goal`). Previously A* returned `null` whenever the target mobile stood on the goal cell, so creatures could never pathfind *toward* another mobile — only toward empty ground. The follower stops within `range` short of it. Static/item blocking and all non-goal mobile blocking are unchanged.

## Tests

New AI-loop integration tests in `ApproachTargetTests.cs` drive the real `BaseAI` primitives against live Britain Inn map statics: exact-repro pet follow, open-terrain (asserts zero pathfinding), `MoveTo` chase (static + walking-away target), route-around-a-dynamic-wall, and walled-off give-up-and-idle.

- Pathfinding + AI subset: **52/52** pass.
- Full `UOContent.Tests`: **301/301** pass. (Note: the test host lingers on shutdown — a pre-existing infra quirk unrelated to this change; all tests complete and pass.)
- Full solution build: clean (0 warnings / 0 errors).

## Notes

- Branched off `main`; independent of the in-flight step-cache work.
- Out of scope (future work): proactive "SmartAI" look-ahead pathfinding so clever creatures plan a route before walking into the obstacle, rather than reacting after they hit it.

## Test Plan

- [X] In-game: order a pet to `follow`/`come` across the Britain Inn L-desk; confirm it routes around and reaches you instead of pacing.
- [X] Aggro a monster and kite it around a building/treeline; confirm it chases around obstacles.
- [X] Confirm open-terrain following/chasing feels unchanged (no extra latency).
- [X] Confirm a creature with a genuinely unreachable target idles rather than shuffling forever.
2026-06-06 10:20:52 -07:00
Kamron Batman
36e09bae13
fix: Pets stop permanently, simplifies pet movement speed (#2401)
## Summary
- Fixes pets falling behind mounted masters in AOS+ by setting `CurrentSpeed = 0.1` when following master
- Fixes AI timer permanently stopping when `Obey()`/`Think()` returns `false` for transient conditions
- Fixes controlled pets losing AI in inactive sectors (pet follows owner across sector boundary, sector deactivates, AI dies)
- Adds defense-in-depth: AI timer restarts on pet resurrection and order changes

## AI Timer Permanent Stop (Bug Fix)

`AITimer.OnTick()` called `Stop()` when `Obey()` or `Think()` returned `false`. By that point, `ShouldStop()` had already validated the creature is alive, on a valid map, and in an active sector — so any `false` return was a **transient** condition, not terminal. The timer stopped permanently with no mechanism to restart it.

**Scenarios that triggered permanent AI death:**
- Dead bonded pet with attack order (`DoOrderAttack` returned `false` for `IsDeadPet`)
- Failed pet transfer — loyalty refusal, combat, disconnected player, or pending trade (`DoOrderTransfer` returned `false` for 5 different transient conditions)
- Unknown `OrderType` or `ActionType` (defensive defaults)

**Fixes:**
- Removed `Stop()` from the `Obey()`/`Think()` failure path — timer skips the tick and fires again next interval
- Changed `DoOrderAttack()` and all five `DoOrderTransfer()` failure paths to return `true` (correct semantics: these are recoverable states, not "stop AI forever" signals)
- Added `Activate()` call in `ResurrectPet()` — ensures dead bonded pets have AI running after resurrection
- Added `Activate()` call in `OnCurrentOrderChanged()` — self-heals timer if any voice command is issued to a pet with a stopped timer

## Controlled Pet Sector Deactivation (Bug Fix)

`ShouldStop()` stopped the AI timer for **all** `PlayerRangeSensitive` creatures in inactive sectors, including controlled pets. But `Deactivate()` intentionally exempted controlled pets. The exemption was dead code — `ShouldStop()` bypassed it.

This matters when a pet follows its owner across a sector boundary: the owner enters the next sector (active), the pet's old sector deactivates (no more players), and the pet's AI dies. The pet stops following and stands there until the player backtracks far enough to reactivate the sector.

**Fix:** Added `Controlled` check to `ShouldStop()` to match `Deactivate()`. Controlled pets now keep their AI running in inactive sectors. The overhead is negligible — controlled pets are bounded by follower slots.

## Movement Speed Simplification

- Simplifies `AITimer` to use `CurrentSpeed` directly as the tick interval (in seconds), removing the complex multiplier/floor logic in `GetBaseInterval`
- Refactors `DoMoveImpl` speed assignment into explicit if/else for clarity
- AOS+ pets following master use `CurrentSpeed = 0.1` (100ms), matching `RunMountDelay`

## Files Changed
- `AITimer.cs` — removed `Stop()` on Obey/Think failure, added `Controlled` exemption to `ShouldStop()`, simplified interval logic
- `BaseAI.cs` — renamed `_timer` to `AITimer` (public), simplified `Deactivate()`, fixed `ReturnToHome` to use `Activate()`
- `PetOrders.cs` — `DoOrderAttack` and `DoOrderTransfer` return `true` for transient failures
- `PetOrderHandlers.cs` — `OnCurrentOrderChanged()` calls `Activate()` to self-heal stopped timers
- `BaseCreature.cs` — `ResurrectPet()` calls `Activate()`, fixed `GoHome_Callback` PlayerRangeSensitive check
- `AIMovement.cs` — refactored speed assignment, AOS+ follow-master speed fix
2026-04-25 11:10:30 -07:00
Bohica
4698299ea7
fix: Fixes various target/movement bugs in BaseAI (#2379) 2026-03-29 09:39:02 -07:00
Kamron Batman
e1e1a7c640
fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
Kamron Batman
7fe9712593
fix: Fixes NPC acquire on being attacked (#2334) 2026-02-11 00:49:29 -08:00
Kamron Batman
d625ea5ba4
fix: Fixes punching while pacified (#2332) 2026-02-09 14:35:07 -08:00
Bohica
e4178bb495
fix: pets will now fallback to follow/stay/guard after combat (#2325) 2026-02-05 15:38:39 -08:00
Kamron Batman
ee2cd1d18d
feat: Adds new decay system and SkipSerialization (#2311)
## Summary

- Replaces scan-based decay checking during world saves with an event-driven timer wheel scheduler
- Removes virtual property checks from serialization hot path, achieving 6-8x faster world saves

## Changes

### DecayScheduler (new):
- Timer wheel with 12 HashSet buckets (5-min intervals) + PriorityQueue for active processing
- O(1) register/unregister vs O(n) scan of all items
- Auto start/stop when items exist/empty
- Configurable tick interval with jitter to prevent system synchronization

### Item.cs:
- Added ScheduledDecayTime computed property
- Added UpdateDecayRegistration() called from SetLastMoved(), property setters, AddItem()/RemoveItem()
- Hooks in Visible, Movable, Spawner setters and Delete()

### World.cs:
- Removed _decayQueue, EnqueueForDecay(), ProcessDecay()
- ItemPersistence.Serialize() now tight loop without virtual calls

## Performance

| Metric        | Before     | After     | Improvement |
|---------------|------------|-----------|-------------|
| Items (230K)  | 245K ticks | 34K ticks | 8x faster   |
| Mobiles (43K) | 56K ticks  | 6K ticks  | 9x faster   |
| Total         | 302K ticks | 40K ticks | 7.5x faster |

## Configuration

decay.maxItemsPerTick = 250       # Items processed per tick
decay.tickInterval = 256ms        # Base processing interval
decay.bucketInterval = 5min       # Timer wheel bucket size
decay.jitterMaxMs = 25            # ±25ms tick jitter
2026-01-08 11:43:51 -08:00
Kamron Batman
c14f361de1
fix: Moves containers/bods/traps/etc to serialization generator (#2310) 2026-01-07 21:55:55 -08:00
Kamron Batman
3e8d548f38
feat: Add spawn position caching and spiral scan optimization (#2295)
### Summary

Adds spawn position caching and optimization for constrained spawners (e.g., those near houses, water, or blocked terrain).

### Key features:
- Sector-based bitmap cache (32 bytes per 16x16 sector) stores valid spawn positions
- Spiral scan progressively discovers positions from spawner center outward
- Automatic mode detects constrained spawners after 5+ non-transient failures
- Prevents mob spawning inside private houses (allows public AoS buildings)
- Deduplicates sector lookups for multi-bounds spawners (RegionSpawner)
- Cache invalidation on house placement/demolition
- Moves SpawnBounds to Spawner

### New spawner properties:
- SpawnPositionMode: Automatic (default), Enabled, Disabled, Abandoned
- MaxSpawnAttempts: Configurable attempts before optimization engages (default: 5)
2025-12-28 02:40:21 -08:00
Kamron Batman
6d51b33cf8
feat: Add CanSpawnMobile overload with props Z-range support. (#2293)
### Summary

- Adds CanSpawnMobile(x, y, minZ, maxZ, canSwim, cantWalk, out spawnZ) overload for finding spawn surfaces within a Z range
- Adds CanSpawnItem(x, y, minZ, maxZ, out spawnZ) for item spawning with Surface+Impassable support (tables, furniture)
- Uses bitmask optimization inspired by Item.DropToWorld's m_OpenSlots pattern for O(1) surface/blocker checks
- HomeRange spawners now use surface detection to set proper Z bounds

### Key Changes

Map.cs:
- CanSpawnMobile with Z-range finds lowest valid surface for mobiles
- CanSpawnItem with Z-range finds lowest valid surface for items (including tables)
- CanFitItem for point-check item placement on Surface+Impassable tiles
- Bitmask approach eliminates nested loops and stackalloc arrays

Spawners:
- Simplified GetSpawnPosition using new Z-range methods
- HomeRange setter detects surface below spawner for proper Z bounds
- Consistent handling for mobiles and items

### Bug Fixes

- Water tiles (Impassable | Wet) no longer block swimming mobs
- Items can now spawn on tables/furniture (Surface+Impassable)

### Test Plan

- Run dotnet test - 631 tests pass
- Manual testing: multi-story spawning, water mobs, item spawning on tables
- Verify HomeRange spawner movement shifts bounds correctly
2025-12-27 17:01:15 -08:00
Kamron Batman
ebaf104935
chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Quick
0b34cc4417
feat: Changes Spawner HomeRange to SpawnBounds (#2290)
### Summary

This PR transitions the spawner system from a simple radius-based model to a flexible 3D boundary system.

### Core Changes

* **Replaced `HomeRange` with `SpawnBounds`**: Spawners now use a `Rectangle3D` to define spawn areas instead of a circular integer range.
* **Backward Compatibility**:
* The `HomeRange` property remains as a helper that generates square `SpawnBounds` centered on the spawner.
* Included a migration path (v10 to v11) that automatically converts old range data into new bounds during deserialization.


* **Dynamic Bounds Shifting**: If a spawner is moved, its `SpawnBounds` will automatically shift with it, provided the bounds are currently configured as a centered square.
* **New Spawn Logic**: Added `SpawnLocationIsHome` toggle. If enabled, spawned mobiles treat their exact spawn coordinates as their "Home" rather than the spawner's location.

### Implementation Details

* **Interface Updates**: Updated `ISpawner` to include `WalkingRange`, `SpawnBounds`, and `IsInSpawnBounds()`.
* **UI Enhancements**: The Spawner Controller Gump now displays "Custom" for complex bounds and allows copying of the new boundary properties between spawners.
* **Refactored Constructors**: Streamlined `BaseSpawner`, `ProximitySpawner`, and `RegionSpawner` constructors to support the new data types.
2025-12-26 18:07:26 -08:00
Bohica
99d8a3db41
fix: Fixes transfer pet spam (#2291) 2025-12-20 09:52:34 -08:00
Bohica
0d25f83de5
fix: Fixes pet guard order (#2287) 2025-12-06 16:18:26 -08:00
Bohica
6938735113
fix: fixes movement jitter for non-AOS (#2245) 2025-07-26 10:21:36 -07:00
Bohica
f59605190c
fix: fixes mobiles pathing home after combat (#2246) 2025-07-26 10:11:31 -07:00
Kamron Batman
aab731ed96
feat: Overhauls AI (Speech/Movement) (#2232)
### Summary

* Refactors AI so it is easier to read and maintain
* Fixes NPC speed issues
* Fixes pet sector AI issue that was causing stuttering
* Fixes direction snapping for Melee/Mage AI
* Refactors pet orders
* Refactors speech commands
* Removes scale speed by dex for HS+ (it was a stupid feature anyways)
2025-07-18 22:39:57 -07:00
Kamron Batman
f89150735e
fix: Consolidates debug check messages for AI and adds debounce (#2235) 2025-07-18 18:08:03 -07:00