Commit graph

13 commits

Author SHA1 Message Date
Kamron Batman
4fe5dd0eec
fix(spawners): lock column in the spawner gump, read-only locked entries, aligned totals (#2635)
## Summary

Follow-up to #2621, which added a per-entry `Disabled` flag to the spawner gump as a checkbox. The checkbox was placed between the Expand and Delete buttons at x=22, overlapping Expand (5–35), and Delete moved to 46, overlapping the creature text box at 71.

- **Lock column on the far left.** Each entry row gets a toggle at x 5–25: padlock `0x82C` when the entry is disabled (press to unlock), green orb `0x2C88/0x2C89` when enabled (press to lock). The client's gump art has only closed padlocks (`0x82C`, `0x0020`), so the orb marks the unlocked state; XmlSpawner uses the same padlock paired with a blue gem.
- **Expand and Delete are adjacent again** (28 / 61, the original 33px pitch), and everything to the right shifts 23px: creature/#/Max/Prb boxes, headers, the totals row, page arrows, Save/Cancel. The gump is now 369 wide (was 346). Params/Props boxes widen to match.
- **Locked entries are read-only.** Name, max, probability, and Params/Props (when expanded) render as `AddLabelCropped` on a grey tile (`0x23F4`) instead of `AddTextEntry`. There is no read-only text entry in the UO gump protocol, so omitting the entry is the only way to make the field truly non-editable. `CreateArray` already `continue`s on a null type entry, so Save leaves locked entries untouched.
- **Totals row aligned under its columns.** Spawned under `#`, a new total max under `Max`, total weight under `Prb`. Max and weight exclude locked entries, matching `BaseSpawner`, which skips `Disabled` entries when rolling.

## Test plan

- [x] `[props` a spawner with several entries: lock column is on the left, Expand/Delete sit side by side, no overlaps.
- [x] Lock an entry: padlock shows, fields go grey and cannot be typed into; unlock: orb shows, fields are editable again.
- [x] Edit an unlocked entry, lock another, Save: unlocked edits persist, locked entry keeps its values.
- [x] Expand a locked entry: Params/Props are grey and read-only.
- [x] Totals sit under `#` / `Max` / `Prb`; locking an entry drops it from the max and weight totals.
2026-09-11 20:30:02 -07:00
Kamron Batman
a52ce6ef70
refactor(spawners): subclass-owned entries, lifecycle hooks, per-entry Disabled flag (#2621)
## Summary

Moves spawner entry storage out of the abstract `BaseSpawner` into the concrete owner, so a spawner subclass can store its own entry type while every stock code path keeps working. Motivation: an out-of-tree spawner (ModernSpawner) needs `ModernSpawnerEntry : SpawnerEntry` with extra fields; today `BaseSpawner` owns `List<SpawnerEntry>` and a family of non-virtual members, and the serialization generator constructs list elements from the declared element type, so storage has to live in the class that declares the concrete list.

### What changed

- **`BaseSpawner` v13** no longer owns `_entries`. It reads entries through an abstract view and mutates them through an owner contract (`BaseSpawner.Entries.cs`):
  `Entries` (`IReadOnlyList<SpawnerEntry>`, `[IgnoreDupe]`), `EntrySpan` (`ReadOnlySpan<SpawnerEntry>` for hot loops), `CreateEntry`, `AddEntryCore`, `RemoveEntryCore`, `ClearEntriesCore`, `AdoptEntries`, `CloneEntry`, `TransferSpawned`, plus public `RemoveAllEntries()`, `CopyEntriesTo(target)` and protected `RebuildSpawned()`. Every loop inside `BaseSpawner` is an indexed `for` over `EntrySpan`.
- **`Spawner` v2** owns `[SerializedIgnoreDupe] List<SpawnerEntry> _entryList` (generated `EntryList`, protected). `ProximitySpawner`/`RegionSpawner` inherit it unchanged. The `Spawned` rebuild and timer re-arm moved from the base `[AfterDeserialization]` (which runs before derived fields are read) into `Spawner`'s.
- **Save migration**: `MigrateFrom(V12Content)` (and the v10/v11/legacy readers) hand the old list to the owner via `AdoptEntries`; `Spawner.MigrateFrom(V1Content)` restores its own fields and leaves the adopted list alone. Three v12/v1/v0 save blobs captured before the change are committed as fixtures and loaded by tests.
- **Lifecycle hooks** (`BaseSpawner.Hooks.cs`, all no-op by default): `OnStarted`, `OnStopped`, `OnBeforeSpawn(entry)` veto, `OnConfigureSpawned(entry, spawned)` before positioning, entry-aware `GetSpawnPosition(entry, spawned, map)`, `OnSpawned(entry, spawned)`, `OnSpawnedDeath(entry, spawned, killer)`. `BaseCreature.OnDeath` calls `NotifySpawnedDeath` before base death (which deletes the mobile and unlinks the spawner). `Start()` and the `NextSpawn` setter share one start core so `OnStarted` fires on both.
- **`SpawnerEntry` v2**: per-entry `Disabled` (XmlSpawner's entry "lock"), stored inverted so the common case writes nothing in binary or JSON; skipped by weighted selection, live spawns untouched; toggle button per row in `SpawnerGump`. `SetParent` is public and `Parent` is protected so an out-of-tree entry subclass can adopt and dirty-track.
- **DTO**: `SpawnerDto` loses `Entries`; each concrete record declares its own `entries` at the same JSON order, so `Distribution/Data/Spawns/**` is byte-identical. Import adopts the deserialized entry objects instead of recreating them through `AddEntry`, which is what preserves subtype fields (and `disabled`).

### Breaking changes and behaviour changes

- **API:** `BaseSpawner.Entries` is `IReadOnlyList<SpawnerEntry>` instead of `List<SpawnerEntry>`. The generated `AddToEntries`/`RemoveFromEntries`/`InsertIntoEntries`/`RemoveFromEntriesAt`/`ClearEntries` helpers on `BaseSpawner` are gone; use `AddEntry`/`RemoveEntry`/`RemoveAllEntries`/`CopyEntriesTo`. `RemoveAllEntries()` deletes the entries' live spawns as well as the entries (the old generated `ClearEntries()` only cleared the list), which is why it has a new name rather than the old one.
- `SpawnerControllerGump` "copy entries" now goes through `CopyEntriesTo`, which deletes the target's live spawns (previously it cleared the list and left the spawns orphaned) and is a no-op when source == target (previously that wiped the source).
- `RemoveEntry` with an entry the spawner does not own is now a no-op (previously it deleted that entry's spawns).
- `Respawn()` honours `Disabled` because it calls `Spawn()`; `Spawn(int index)`, `RemoveSpawn`, and `RemoveSpawns` ignore it.
- Copying entries between spawners no longer forces a 1-second first spawn; the target re-arms on its normal delay.
- Subclasses that own a different entry list than `Spawner`'s must call `RebuildSpawned()` from their own `[AfterDeserialization]` (`Spawner`'s call runs before their list is read) and, when converting adopted entries into their own type, carry live spawns across with `TransferSpawned`. The in-repo test subclass demonstrates both.

### Performance

Manual harness (`Benchmark_SpawnPath_Manual`, skipped by default): 100k calls, entries all full so `Spawn()` does selection only.

| Path | Before (4bad0cc9e) | After |
|---|---|---|
| `Spawn()` 1 entry | 48.7 ns | 53.2 ns (within run-to-run noise) |
| `Spawn()` 10 entries | 243.7 ns | 153.1 ns |
| `Spawn()` 50 entries | 1077.6 ns | 645.1 ns |
| `Remove()` 10 entries | 139.6 ns | 83.6 ns |

Hooks are no-ops for stock spawners; `OnMovement` is untouched.

### Tests

- `SpawnerEntryOwnershipTests` (add/remove/clear, start after stop, dupe, copy, self-copy, foreign-entry removal, Disabled binary/JSON)
- `SpawnerHookTests` (hook order for mobiles and items, veto, death notification, `NextSpawn` start, a subclass with its own `List<TestEntry>` round-tripping and duping)
- `SpawnerSaveMigrationTests` (v12 fixtures through `Spawner`, `ProximitySpawner`, `RegionSpawner`; new-format byte-identical round trip with a live spawn reference)
- `SpawnerDtoEntryTests` (compact JSON position of `entries`, `disabled` only when set, import adopts the deserialized objects)
- Existing DTO/JSON/spawn-data tests unchanged. UOContent.Tests 801 passed, Server.Tests 869 passed. Migration schemas regenerated (`BaseSpawner.v13`, `Spawner.v2`, `SpawnerEntry.v2`).

Coverage caveats, stated plainly: v10, v11 and the pre-codegen legacy reader could not be captured as fixtures by the current code; each changed only `_entries = …` → `AdoptEntries(…)` into the same sink the v12 fixtures exercise, and is covered by review. The captured v12 fixtures carry no live spawns, so re-linking live `ISpawnable` references is proven by the new-format round trip, not by a legacy blob. The `SpawnerGump` toggle layout could not be checked in a client; the delete button moved from x=38 to x=46 to make room.
2026-09-11 17:41:08 -07: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
Kamron Batman
7bd5a853a3
feat: Adds size/style support to gump builders, optimizes EscapeHtml (#2257)
> [!IMPORTANT]
> **Developer Note**
> THIS IS A BREAKING CHANGE TO THE NEW API GUMP.
> Please give us feedback in [discord ](https://muo.gg/discord) if you have issues, need help, or have ideas for a better API change!

### Summary

* Adds support for size/style to dynamic/static builder.
* Drastically simplifies the dynamic/static builder api for AddHtml.
* Cleans up some legacy gump files.
2025-11-23 11:35:31 -08:00
Kamron Batman
26dfde19ee
fix: Consolidates Color/Center html (#1762)
### Summary
- Fixes bad color in virtual check gump
- Consolidates the Color/Center html strings for all gumps
2024-05-07 23:56:32 -07:00
Kamron Batman
cfe9e04c78
fix: Optimizes gump relay info (Prep for Static Gumps) (#1698) 2024-03-07 20:54:53 -08:00
Fabrizio
537526a328
fix: Update LabelTo, SendMessage, etc to Interpolated Strings (#1283) 2022-11-28 19:58:12 -08:00
Quick
6601101e2e
fix: Updates spawner gump visuals! Thanks Quick! (#1102)
This update changes the default RunUO SpawnerGump's overall look and feel and expands slightly some functionality. Inspired by the XmlSpawner gump.

![image](https://user-images.githubusercontent.com/577652/176790254-cc1fb6bb-b281-49c9-a7cd-ec5376a69921.png)

![image](https://user-images.githubusercontent.com/577652/176790147-408ef682-9670-4753-872c-db843be1ab05.png)

1. The name of the spawner is displayed
2. This displays the total current spawn with the current allowed max spawn
3. Cleans up the display of the current count and the max count
4. The ability to turn on/off the spawner
5. Several changes to buttons 
   - Simple Save and Cancel instead of Okay, Cancel and Apply.
   - Props button to display spawner properties
   - Goto button to teleport to the spawner
   - Reset button that will clear all the spawned mobs and turn off the spawner
   - Repositioned the buttons to make a little more sense
6. The sum of all the max spawn values per entry, from all pages
2022-06-30 20:48:54 -07:00
Kamron Batman
e869c105d0
fix(core): Fixes type caching & cleanup (#422)
- [X] Fixes some issues with the type caching
- [X] Changes type check default to ignore case
- [X] Splits out type caching of insensitive and sensitive lists. This means less stuff to iterate through when checking a type against the string.
- [X] Adds an ArrayEnumerator, mostly for reference purposes (copy/paste as needed)
2021-01-20 00:10:33 -08:00
Kamron Batman
8149620b0c
Fixes brace style (#248) 2020-09-13 21:49:46 -07:00
Kamron Batman
ad3775c4d7
Formats UO Content (#201) 2020-08-27 18:30:38 -07:00
Kamron Batman
390f30e706
Convert Regions/Spawns to JSON (#138) 2020-05-26 00:34:29 -07:00
Renamed from Projects/UOContent/Engines/Spawner/SpawnerGump.cs (Browse further)