Commit graph

1482 commits

Author SHA1 Message Date
ad38314ca7 #W# Confict: Fixed BaseCreature git merge conflict. 2026-10-10 23:17:41 -04:00
08fd68b9d0 #W# Factions: Disabled the faction regions. 2026-10-10 23:05:33 -04:00
4160a83eaa #W# Removed: Removed ToT Stuff. 2026-10-10 21:21:57 -04:00
314fbf0003 #W# Bounty: Doing some bounty tier work. 2026-10-10 20:03:24 -04:00
c672cda6dc #W# Tweak: Removed -Karma from some animals. 2026-10-10 19:49:58 -04:00
25d3f980f2 #W# Tweak: Make a few tweaks. Also added Tier list of mobs by fame. 2026-10-10 13:42:41 -04:00
Kamron Batman
d857f03b68
fix: bound and purge NPC vendor buyback stock (#2677)
## Summary

Stacked on #2676. Review that first; this PR's diff is against it.

Items players sell to NPC vendors go into the vendor's buyback (`ShopBuy`) pack. That pack was purged only when someone opened the buy menu, so a vendor that players sold to but never bought from grew without limit.
- A server operator reported 250k+ items piling up from players crafting and selling on a macro.
- Every one of those items was saved.
- Purging or deleting that vendor then sent one remove packet per item to every nearby client and disconnected them.

The behavior is inherited from RunUO.

- **`VendorBuybackPack`** replaces the plain backpack on `Layer.ShopBuy`.
  - It opts in to `SkipsChildSerialization`, so buyback items are never saved.
  - Its contents are private like any container's, so removing them reaches only the vendor (no client) and openers. Purges and deletes send nothing to bystanders.
- **Capacity.** The buyback holds at most `250 − buy-list entries` items, which is what the buy gump can show. The oldest items are evicted first. At capacity 0 the sold item is consumed and the seller is still paid.
- **Purge on the restock grid.**
  - `Restock()` empties the buyback.
  - A purge-only timer, armed on the first sale, covers vendors nobody buys from. It is due at the next restock boundary after now, so a vendor whose restock hasn't run for hours doesn't purge an item the moment it's sold.
  - The timer never restocks, so stock doubling and halving is unchanged.
  - The old per-item one-hour `LastMoved` check is gone.
- **Existing saves.** A legacy `ShopBuy` pack is replaced on the first tick after load. Thanks to #2676, deleting a pack of 250k+ items takes well under a second.

**Behavior changes for shard operators:**
- A buyback item lasts until the next restock boundary (at most `RestockDelay`), not one hour after its sale.
- A vendor that changes maps drops its buyback.
- A buy window that is open during a purge can show a row whose item is gone. Buying that row does nothing, and reopening shows the current list.

## Test plan

- [x] `VendorBuybackPackTests`: evicts oldest first; capacity 0 consumes the item; purge; a stale front entry can't spin eviction
- [x] `VendorBuybackTests`:
  - purge due time stays on the restock grid, including a stale restock and a restock time in the future;
  - partial stack sale: the split goes to buyback and the seller keeps the remainder;
  - zero capacity consumes the item and still pays;
  - restock purges and clears the timer, and the next sale re-arms it;
  - deleting the vendor cancels the timer;
  - a legacy 20k-item pack is replaced.
- [x] UOContent.Tests and Server.Tests green; schema generator clean
- [x] Manual in-game pass:
  - sell and buy back, including a partial stack;
  - over-capacity sale;
  - purge with no buyer;
  - `[restock`;
  - vendor facet change;
  - restart;
  - legacy save with a large `ShopBuy` pack;
  - mass selling with bystanders present;
  - deleting a vendor with a full buyback.
2026-10-09 22:56:39 -07:00
Kamron Batman
89a86d81b4
feat: scope container child removal, skip-serialize and linearly delete container children (#2676)
## Summary

Removing an item from a container now reaches only the clients that were sent the item. This PR also adds container-scoped skip serialization, makes container deletion linear, and closes a lift-rejection resync gap. It is the groundwork for the NPC vendor buyback fix (#2677), and it is safe to merge by itself.

### Container child removal reaches only the clients that were sent the item

Adds and updates for an item inside a private container have always gone only to the root mobile, the secure-trade partner, and the container's `Openers` (`ProcessDelta`, the same as RunUO). Removes, though, were broadcast to every client in range. That too is inherited from RunUO; #326 only converted the packets. Two costs:
- **Bandwidth.** One packet per nearby client for every reagent, bandage or arrow stack that runs out, and every container-to-container move.
- **Disclosure.** It sent the serial and timing of items moved in someone else's pack to every nearby client.

Now:
- **`Item.SendRemovePacket`:** a private container child's remove goes only to the root mobile, both secure-trade parties (found by walking up to the trade container, as `ProcessDelta` does), and the container's `Openers`. Each recipient is deduplicated and must be on the same map and in range.
- **`Container.IsChildPublic(Item child)`** (default `IsPublicContainer`) marks children that were sent to every client in range. Their removal still broadcasts. Overrides can only widen: a public container's children always broadcast.
- **`Corpse`** marks its worn gear public on human corpses. `SendInfoTo` shows that gear to everyone who sees the corpse, so bystanders still see looted gear disappear. Loot stays private, and corpses are not made public.
- **`IsPublicContainer` is unchanged** and remains the escape hatch.
- **Unchanged:** items equipped on mobiles, children of non-container items (spellbooks, bulletin boards), and ground items.
- **`Item.Map` no longer sends removes for contained items.** A contained item only changes map along with its parent or root, and that ancestor's own remove already clears the subtree for everyone who knew it. The owner keeps their nested bags across a facet change, as before, and bystanders no longer get a remove per nested item.

**For forks:** a custom container that sends its contents to clients other than the root, trade parties and openers must override `IsChildPublic` or `IsPublicContainer`. Otherwise those clients keep the item until they reopen the container, it leaves their range, or they log out.

- **`Item.MoveToWorld` no longer sends a second remove for an item that had a parent.** It used to detach through the parent's `RemoveItem` (correctly scoped), then broadcast another remove from the old location. Every lift goes through `Internalize()`, so every lift from a pack or container broadcast the item's serial. Ground items are unchanged.
- **Property-only updates** (`ItemDelta.Properties` without `Update`) of a private child now send the object property list revision only to the same recipients, not every client in range. Gump tooltips that reference such an item without the client holding it no longer get the revision nudge; their cached tooltip refreshes when re-requested.

### Lift rejection resyncs only items the requester was sent

A rejected lift re-sent the item's full data to the requester without checking that their client had ever been sent it. `Item.IsSentTo(Mobile)` now applies the same rule as the adds, including `CanSee`:
- **Private container children:** the root mobile, the trade parties, or the openers.
- **Everything else:** visible and in update range.

`Mobile.Lift` still always sends the lift-reject packet, but resyncs the item only when `IsSentTo` holds.

### Container content packets stay within the protocol limit

`SendContainerContent` stack-allocated `items.Count × 20` bytes, which overflowed the stack for a container with hundreds of thousands of items. The `0x3C` length and count fields are 16-bit anyway. It now caps entries so the packet fits (3448 entries, or 3276 with grid lines), and rents a pooled buffer above a small size. Output is byte-identical for normal containers.

### Container-scoped skip serialization

**`Container.SkipsChildSerialization`** (default `false`). When a container returns true:
- its direct children are left out of world saves (the base `Item.SkipSerialization` checks the parent);
- it writes no child list, so a skipped child's serial can never resolve to an unrelated item at load;
- an item that loads naming such a container as its parent is deleted by the existing missing-parent path.

**Known limitation:** `SkipSerialization` is read while the save files are written, after the world unfreezes. An item that enters an opted-in container in that window can be left out of that save. The next save is correct. Moving the decision into the freeze is a follow-up.

### Linear container deletion

`Item.Delete` removes children back to front, but `RemoveItem` searched from the front with `List.Remove`, so deleting a container of *n* items was O(n²). `RemoveItem` now checks the last entry first.
- 100,000 children took 8.0s before and 162ms after.
- Other removals cost the same as before.

**Save format unchanged for every existing type** (the schema generator produces no diff).

## Test plan

- [x] `ContainerChildRemovalTests`:
  - a private child's removal isn't broadcast;
  - openers, the root owner's client and a trade party (who never opened the pouch) still receive it;
  - public containers and per-child `IsChildPublic` overrides still broadcast;
  - an override can't narrow a public container;
  - no duplicate packets when root and opener overlap;
  - deleting the owner sends no remove per child;
  - a facet change sends no remove for nested children to the owner or bystanders;
  - lifting from a backpack or moving a contained item to the ground sends no remove to bystanders;
  - equipped items and ground items keep their broadcast.
- [x] `PrivateChildPropertiesTests`: a property-only update of a private child reaches the owner and openers, not bystanders; public-container children and ground items still broadcast.
- [x] `CorpseChildPublicTests`: human corpse worn gear is public and loot isn't, bones and non-human bodies aren't; end to end, worn gear leaving the corpse reaches a bystander and deleted loot doesn't.
- [x] `LiftRejectResyncTests`:
  - a bystander and an out-of-range requester get no resync;
  - the owner, an opener and a trade party do;
  - an invisible item isn't resynced to a non-staff owner, but is to staff;
  - equipment on a hidden mobile and an invisible ground item aren't resynced.
- [x] `ContainerPacketTests`: large containers are capped at the protocol limit (with and without grid lines) and the pooled-buffer path is byte-identical.
- [x] `ContainerChildSkipTests`, `ContainerBulkRemovalTests`.
- [x] Server.Tests (944) and UOContent.Tests (1203) green on current `main`; schema generator clean.
- [x] Manual in-game pass with three clients:
  - consumption;
  - snooping;
  - co-opened containers;
  - bank;
  - trade with and without opening the pouch;
  - facet change with nested bags;
  - pack animals;
  - player vendors;
  - human corpse looting by drag and by script;
  - every lift-rejection reason.

  Verified with a ClassicUO build instrumented to count:
  - removes for unknown serials;
  - adds dropped for an unknown parent;
  - items learned inside containers the client never opened;
  - lift resyncs of items the client never had.
2026-10-09 22:21:50 -07:00
Kamron Batman
1992c9d1b5
fix: worn gear lifted off a corpse stays loot when put back (#2701)
A corpse's worn-gear list was fixed at creation, and clients learn worn gear only from the 0x89 packet sent on first sight. A piece lifted and dropped back showed as loose loot to everyone watching, but came back "worn" after a relog. Lifting now takes the piece off the list, so every view agrees it is loot.

Programmatic moves (the duel gather-and-re-equip in `DuelContext`) don't go through `OnItemLifted` and still see the original list.
2026-10-09 17:02:36 -07:00
4a20c34c0d #W# Bounty: The bounty system is working. Its a standalone system not MLQuest. Need to fill it in and add rewards. 2026-10-09 20:00:00 -04:00
b4e25a2713 #W# MLQuest: Bounty system. 2026-10-09 18:16:18 -04:00
a994b61445 #W# Cleanup: Removed default OSI MLQuests. 2026-10-09 18:15:30 -04:00
f333f8da65 #W# MLQuest: Added a bounty quest. Earn tokens to spend at the bounty vendors. 2026-10-09 13:24:17 -04:00
b5307a2db9 #W# Dungeon: Added the Dungeon of Ice. 2026-10-09 10:01:19 -04:00
Sergi Rosell
0dbb0f5a2c
fix: stealing from a container on the ground follows its lift rules (#2697)
`Stealing` moves the item with `AddToBackpack` and never asks the container, so a theft skipped the rules a container sets for a hand lift: a treasure chest's crime, refusal and guardian roll, a boat hold from outside the boat, a house's moving crate, and the Haochi/Emino quest chests.

An item in a container on the ground now goes through its `CheckLift` after the weight check, and a successful theft calls `OnItemLifted`, as a hand lift does. Deco containers are skipped: their `CheckLift` refuses everyone, and stealing is the only way into them.

These are the containers with their own `CheckLift` that a thief can reach:

| Container on the ground | What a hand lift already enforces | A theft until now |
|---|---|---|
| Treasure map chest | crime for a non-owner; refused if temporary, or for a non-owner on Trammel rules; guardian roll | none of it |
| Boat hold | only from aboard, while the boat is stopped | also from outside the boat, or while it moves |
| House moving crate | owner only | anyone |
| Haochi / Emino quest chests | one item, only while on the quest | anyone, any number, and it didn't count for the quest |
| Fish bowl not locked down | the fish stay in | the fish could be stolen |
2026-10-09 00:21:43 -07:00
b22a1495af #W# MLQuest: Working on some new quests. Or trying to. 2026-10-08 15:35:25 -04:00
f961f484a5 #W# Spawn: Dungeon traps and chests are done. Finished up TerathanKeep also. Also added a TaskManager script. 2026-10-08 13:35:34 -04:00
e5029166e2 #W# Spawn: Tweaked RegionSpawner so it detects the region when you spawn it. 2026-10-08 09:07:58 -04:00
800c0227da #W# Weapons: Weapon damage change for durability. 2026-10-07 19:53:37 -04:00
ebae100e6a #W# Change: Potions now stack. 2026-10-07 18:00:27 -04:00
1efa69c9f4 #W# Added: Armor and shields now show ACTUAL AR rating. 2026-10-07 13:52:22 -04:00
e463db2955 #W# Fix: Newbie dungeon does accumulate mkp totalgold. 2026-10-07 12:53:32 -04:00
2a3551511e #W# DOTD: Added more rare rewards to the drop list. Set the default rare drop chance to 2% 2026-10-07 12:20:30 -04:00
4afeb5dd98 #W# DOTD: 50% more gold and 25% more MKPs. 2026-10-07 11:14:36 -04:00
cb5906025a #W# Feature: Added a Dungeon of the Day! [dotd command. Increased gold/skill gain/ and rare drops. 2026-10-07 10:52:08 -04:00
Kamron Batman
15a7ce55ce
fix: T2A tailoring make-last no longer reuses a stale cloth hue for leather (#2694)
## Summary
T2A tailoring Make Last failed after crafting a cloth item and then a leather item. `CraftContext.LastHue` was set only on the hued (cloth) path, so the next Make Last of a leather item took the hue-aware path and failed with "You don't have the resources". The leather path now resets `LastHue` to -1.

## Testing
- `dotnet build`: 0 warnings, 0 errors
- No automated test: driving `TailoringMenu` needs a NetState plus tiledata-backed resources, and CI has no tiledata. Not yet exercised in game.
2026-10-06 21:42:23 -07:00
b9cc7f8d9a #W# Fix: Shields now display str requirements. 2026-10-06 21:16:09 -04:00
34b7945fc7 #W# Change: Changed to UOR expansion. Added Hover text for armor and weapons. 2026-10-06 20:51:27 -04:00
Jack
b98b0e2964
fix: digging up a treasure map chest no longer crashes when guardians spawn (#2692)
## Summary
Digging up a decoded treasure map of level 0, or of level 2 and above, throws inside `DigTimer.OnTick` once the chest surfaces. The exception escapes the timer, so the server goes down:

```
System.NullReferenceException: Object reference not set to an instance of an object.
   at Server.Items.TreasureMap.DigTimer.OnTick() in ...\Items\Maps\TreasureMap.cs:line 809
```

#2568 made `TreasureMapChest._guardians` lazily created: the constructor no longer initialises it, and migration nulls an empty list. #2569 updated the read in `CheckLocked` to match, but the dig timer still adds each spawned guardian directly:

```csharp
if (bc != null)
{
    m_Chest.Guardians.Add(bc); // null on a freshly dug chest
}
```

Level 1 maps survive only because they spawn no guardians. I confirmed the regression by running the dig test below at #2568's parent, where it passes, and at #2568, where it fails.

The dig timer now calls the generated `AddToGuardians`, which creates the list on first use.

## Testing
- `dotnet build`: 0 warnings, 0 errors.
- New `TreasureMapChestGuardianTests`:
  - `Dig_AddsSpawnedGuardiansToTheChest` (levels 0 and 2) drives the real `DigTimer` tick by tick until the chest surfaces. Before the fix both cases fail with the `NullReferenceException` above. It needs client map data, so it calls `TileDataRequirement.SkipIfMissing()` and skips on CI.
  - `AddToGuardians_OnNewChest_KeepsLevelZeroChestShut` needs no client data, so it runs on CI: the list starts null, the first guardian creates it, and a level 0 chest stays shut to players until that guardian is dead.
- Full `UOContent.Tests`: 1196 passed and 2 skipped with client data; 1105 passed and 93 skipped without.
2026-10-06 17:39:04 -07:00
fa33f8466d #W# Fix: Fixed a teleporter and a str typo for CowardlyBrigand 2026-10-06 20:15:03 -04:00
Jack
73a1aaf5e0
fix: MeteorSwarm resist halves that target's damage, not the shared pool (#2691)
## Summary
Pre-AOS, a Meteor Swarm target that resists should take half of its share of the damage. Instead the resist branch halves the **shared** `damage` variable:

```csharp
var toDeal = damage;

if (!Core.AOS && CheckResisted(m))
{
    damage *= 0.5;   // should be toDeal
```

`toDeal` has already been copied, so:
- the target that resisted takes **full** damage, and
- every target processed after it is halved, once per earlier resist, so a group where several resist ends up taking a fraction of the intended damage for reasons unrelated to their own resist.

`ChainLightning` already handles this correctly (`toDeal *= 0.5;`), and this change makes Meteor Swarm match it. The AOS path is unaffected, since resist checks only run pre-AOS.

The bug predates ModernUO (it was carried over from RunUO in the first alpha).

## Testing
- `dotnet build Projects/UOContent/UOContent.csproj`: 0 warnings, 0 errors.
- No unit test: the per-target damage sits inside `Target()`'s queue loop alongside a random resist roll, so testing it would mean refactoring the spell for a one-word fix. Happy to factor out a helper and add one if preferred.
2026-10-06 16:30:05 -07:00
45ac268d7c #W# Dungeon: Added a New Player Dungeon with some weaker mobs for level 1. 2026-10-06 14:59:05 -04:00
11b620b0dc #W# Region: Added a new NewbieDungeonRegion. Accelerated skill gain upto 80.0 2026-10-05 19:45:14 -04:00
e337ca20bc #W# Tweak: New characters start with 20 thirst now. 2026-10-05 10:26:13 -04:00
be23031a32 #W# Added: Added a waterskin. Water only. Dclick will check surrounding area for water to refill or it will drink. 2026-10-05 10:12:55 -04:00
Sergi Rosell
261ea01ab4
fix: send crash reports and bound email retries (#2680)
Crash reports never reached SMTP: `SendCrashEmail` returned immediately when email was enabled and never called the sender. It now respects the enabled setting, sends the report, and waits up to 30 seconds before the crash handler returns and the process can exit.

The sender uses the `Message-ID` generated by `MimeMessage` and the sender address already added by each caller. This removes the duplicate `Message-ID` header and duplicate address within the `From` header. Gmail rejects duplicate `Message-ID` headers: https://support.google.com/a/answer/13567860.

Email retries use a bounded doubling schedule instead of squaring an integer delay, which could overflow and throw through an `async void` method. The sender returns `Task`, uses `ConfigureAwait(false)` throughout, and logs failures. With the default five attempts and three-second initial delay, waits are 3, 6, 12, and 24 seconds. Each wait is capped at one hour, and there is no wait after the last attempt.

Validation:

- Full Release test suite on the branch updated with `main`: 2,103 passed, 2 intentionally skipped.
- Eight email test cases cover disabled-email early returns, retry timing, low attempt counts, and overflow-prone delays.
- SMTP delivery and the crash-time shutdown path are not covered by an integration test.
2026-10-04 18:41:27 -07:00
Sergi Rosell
6102cdabd7
fix: players jailed across a restart can be jailed again after release (#2690)
Loading the jail records added every player still serving a sentence to
CurrentlyBeingJailed, which only guards the jail sequence and is cleared
when it ends. Nothing removed them on release, so JailPlayer silently
returned for them until the next restart.
2026-10-04 18:21:50 -07:00
Sergi Rosell
b7d93f5126
fix: fishing catches by expansion, SOS facets and the big fish mark (#2688)
The fishing mutate table offers every special catch in every era, so a pre-T2A shard fishes up boots, magic fish, treasure maps and bottles (added in the March 1999 fishing update), and a T2A shard gets the special fishing net and big fish, which no T2A publish or guide lists.

Each entry now carries the expansion that added it; the chances are unchanged. The nets in SOS chests and on krakens need UO:R as well, and before SE ancient SOS roll 1 in 40 and their chests aren't hued white, since Publish 28 changed both.

Two fixes from the same notes come with it. An SOS now only works in the facet it was found in, so kraken and leviathan bottles are made at death with the creature's facet. Big fish, their trophy deed and their trophy show the fisherman's mark and weight only from SE.

## Why each change

### The rule

- `Core.T2A`, `Core.UOR`, `Core.SE`… mean "the shard is this expansion **or newer**".
- An expansion includes every publish that came out while it was the newest one. Publish 27 (September 2004) came out before Samurai Empire (November 2004), so it belongs to AOS.
- An item can't drop on a shard older than the expansion that added it.
- When the sources only narrow it down to a window that spans two expansions, the item takes the older one, so no shard loses something it may really have had.

### When each fishing item arrived

| Expansion | Released | What it added to fishing | Sources |
|---|---|---|---|
| T2A | Oct 1, 1998 | Boots, magic fish, treasure maps, bottles (update of March 17, 1999) | [1] |
| UO:R | May 4, 2000 | Special fishing net (first listed in August 2000) | [2], [6], [7] |
| UO:R or Third Dawn | | Big fish (first listed between November 2000 and March 2001) | [3], [8], [9] |
| AOS | Feb 11, 2003 | Ancient SOS at 1 in 40, fabled fishing net (Publish 27, September 14, 2004) | [4] |
| SE | Nov 2, 2004 | Ancient SOS at 1 in 25, white ancient chests, the fisherman's mark on big fish (Publish 28, January 12, 2005) | [5] |

Release dates: [13].

### `Fishing.cs`

**`MutateEntry` gets an `Expansion` field and constructor argument.**
Each row has to know which expansion added it. `MutateEntry` is private to `Fishing`, so no other code is affected.

**`MutateType` skips rows newer than the shard:**

```csharp
if (Core.Expansion < entry.m_Expansion)
{
    continue;
}
```

A skipped row behaves as if it weren't in the table. Every other row rolls exactly as before.

**The rows:**

| Row | Expansion | Why |
|---|---|---|
| Boots, shoes, sandals, thigh boots | T2A | The March 17, 1999 update is the first time fishing gives anything but fish: "you now have a chance of pulling up … One of any of several types of boots". [1] |
| Magic fish (prized, wondrous, truly rare, peculiar) | T2A | Same update: "Magical fish, which can boost stamina, strength, intelligence, or dexterity". [1] |
| Treasure map | T2A | Same update: "Treasure maps". [1] |
| Message in a bottle | T2A | Same update: "Messages in bottles". [1] |
| Special fishing net | UO:R | Missing from the 1999 list [1] and from the March 2000 fishing changes, which only mention treasure maps and SOS bottles [2]. Stratics' fishing guide doesn't have it from July 1999 to July 8, 2000 [6] and has it on August 10, 2000 [7], after UO:R came out. |
| Big fish | UO:R | Stratics' fishing guide doesn't have it from July 1999 [6] to November 7, 2000 [8] and has it on May 24, 2001 [9]. Publish 11 (March 14, 2001) already lets taxidermy kits use it [3]. So it is certainly not T2A. It came at the end of UO:R or with Third Dawn (March 7, 2001); the sources can't tell which, so it takes UO:R, the older one. |
| Nothing (the empty row) | None | Exists in every expansion; unchanged. |

**SOS chest, white hue: `if (sos.IsAncient && Core.SE)`**
Publish 28: "Hued level 4 fishing chests white." [5] It came out after SE, so before SE the ancient chest keeps its normal colour.

**SOS chest, net: `if (Core.UOR) { chest.DropItem(...); }`**
You can fish up an SOS chest from T2A on, because bottles are T2A [1]. The net inside it is UO:R (see the table), so before UO:R the chest comes without a net. The fabled net only goes in ancient chests, and those already need AOS [4].

**SOS: `from.Map == sos.TargetMap`, in `CheckResources` and `Construct`**
Both accepted an SOS whenever the fisher was in Felucca or Trammel, without comparing the SOS's `TargetMap`, so a Trammel SOS paid out in Felucca at the same spot. Publish 5: "SOS bottles and treasure maps will only work in the land they are found in (for instance, a map found in Felucca will only work in Felucca)." [14] Treasure maps already check their facet.

### `MessageInABottle.cs`

**`GetRandomLevel`: `Utility.Random(Core.SE ? 25 : 40) < 1`**
Publish 27 added ancient SOS at "a 1 in 40 chance" [4]. Publish 28 "Changed Ancient SoS spawn chance from 1 in 40 to 1 in 25" [5]. So AOS rolls 1 in 40, SE and newer 1 in 25. The `Core.AOS` check was already there.

### `Kraken.cs`

**Bottle, now made in `OnBeforeDeath`: `if (Core.T2A && Utility.RandomDouble() < .05)`**
Bottles don't exist before T2A [1], so a kraken can't carry one there. The 5% is unchanged; no official source gives a number. The kraken is in Stratics' 1999 creature list [10]; its kraken page says "Loot: Unknown" in January 2000 [11] and "Loot: Messages in a Bottle, Treasure Maps, 300 - 600 gold" in October 2000 [12].

The constructor packed the bottle before the kraken had a map, so it always pointed to Trammel; with the SOS check above, a kraken killed in Felucca would drop an SOS that only works in Trammel. It is now made at death with the kraken's facet, as `BaseCreature.OnBeforeDeath` does for treasure maps; the net already does the same for the kraken it brings up.

**Net: `if (Core.UOR) { PackItem(new SpecialFishingNet()); // Confirm? }`**
Nets are UO:R [6], [7]. The `// Confirm?` stays: in 2000 Stratics says the net was "loot on Sea Serpents" [7], [12], not on krakens. Which creature drops the net is out of scope; this PR only fixes the expansion.

### `Leviathan.cs`

**Bottle, now made in `OnBeforeDeath`**
Same as the kraken: it was packed in the constructor and pointed to Trammel. It is now made at death with the leviathan's facet, and still drops every time.

### `BigFish.cs` and `TaxidermyKit.cs`

**Mark and weight: `if (Core.SE && Weight >= 20)`, and the same in the trophy and its deed**
Publish 28: "Added variable weight and fisherman's mark on big fish, big fish trophy deed, and big fish trophy. Any big fish caught that is below 20 stones will not display weight or fisherman's mark." [5] It came out after SE, so before SE none of the three shows them.

### What this PR doesn't change

No chance, skill threshold or row order changes. The open questions (which creature drops the net, the exact expansion of big fish, its weight before Publish 28, the rates) are in #2687.

## References

1. Origin Update Center, update of March 17, 1999: the fishing overhaul that adds boots, magic fish, treasure maps and bottles. https://web.archive.org/web/19990417083713/http://update.owo.com/latest.html
2. Publish 4, March 8, 2000: "Every time you successfully fish up a treasure map or SOS bottle a sea serpent will surface and attack." https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2000-2/2000-publish-04-8th-march/ (copy from the time: https://web.archive.org/web/20000408162342/http://update.uo.com/design_180.html)
3. Publish 11, March 14, 2001: taxidermy kits accept "Big Fish". https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2001-2/2001-publish-11-14th-march/ (copy from the time: https://web.archive.org/web/20010331072129/http://update.uo.com/design_321.html)
4. Publish 27, September 14, 2004: "There is a 1 in 40 chance that any message in a bottle becomes 'an ancient SOS'", "Added fabled fishing nets and ancient sos treasure". https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2004-2/publish-27/
5. Publish 28, January 12, 2005: "Hued level 4 fishing chests white", "Changed Ancient SoS spawn chance from 1 in 40 to 1 in 25". https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2005-2/publish-28-12th-january/
6. Stratics fishing guide without the net or big fish, the same text from July 24, 1999 to July 8, 2000: last modified July 24, 1999, https://web.archive.org/web/19991009203653/http://uo.stratics.com:80/fishing.htm ; last modified July 8, 2000, https://web.archive.org/web/20000711071050/http://uo.stratics.com:80/fishing.shtml
7. Stratics fishing guide, last modified August 10, 2000: lists the special fishing net, "found as loot on sea serpents". https://web.archive.org/web/20000831173915/http://uo.stratics.com:80/fishing.shtml
8. Stratics fishing guide, last modified November 7, 2000: still no big fish. https://web.archive.org/web/20001209013100/http://uo.stratics.com:80/fishing.shtml
9. Stratics fishing guide, last modified May 24, 2001: lists big fish. https://web.archive.org/web/20010608153326/http://uo.stratics.com:80/content/skills/fishing.shtml
10. Stratics Hunter's Guide menu, last modified July 4, 1999: the kraken is in the creature list. https://web.archive.org/web/19991013090543/http://uo.stratics.com:80/hunters/huntmenu.htm
11. Stratics kraken page, last modified January 1, 2000: "Loot: Unknown". https://web.archive.org/web/20000119061647/http://uo.stratics.com:80/hunters/kraken.html
12. Stratics kraken page, last modified October 15, 2000: "Loot: Messages in a Bottle, Treasure Maps, 300 - 600 gold"; the net "can be found as loot on Sea Serpents". https://web.archive.org/web/20001017190252/http://uo.stratics.com:80/hunters/kraken.shtml
13. Expansion release dates: https://en.wikipedia.org/wiki/Ultima_Online#Expansions_and_follow-up_releases
14. Publish 5, April 27, 2000: "SOS bottles and treasure maps will only work in the land they are found in". https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2000-2/2000-publish-05-27th-april/ (copy from the time: https://web.archive.org/web/20000510032101/http://update.uo.com/design_196.html)
2026-10-04 18:17:05 -07:00
Sergi Rosell
ab89cd80f5
fix: release a connection's gumps on disconnect (#2689)
`GumpSystem` clears a connection's gumps on `EventSink.Disconnected`, but `Mobile.NetState` raises it after the state is already null, so nothing is ever removed: every connection that touched a gump stays in `_gumps` until restart, with any gumps still open when it closed.

It now listens to `BeforeDisconnected`, while `m.NetState` still returns the connection. That event also fires on login, where there is no previous state and the handler does nothing.
2026-10-04 17:53:41 -07:00
Sergi Rosell
d26ae63e94
fix: duel teams start on their own arena points, in formation (#2686)
* fix: duel teams start on their own arena points, in formation

MoveInside clamped the team index with Math.Min instead of Math.Max, so
every team started on the west edge point. The formation rotation also
reused the already-rotated X when computing Y, stacking players on the
north and south edges, and the SW/NE corners swapped axes, which leaves
the corner formation pointing out of the arena.

* refactor: one start point index clamp for GetBaseStartPoint and MoveInside

The clamp was written twice, and only one copy was correct.

* refactor: leave the placement loop as it was

Only the rotation lines in the loop belong to the fix.
2026-10-04 17:48:10 -07:00
1be86c0a40 #W# Added: Hunger and Thirst now matter. Taking damage now gives you exhaustion points that will lower your hunger and thirst faster. 2026-10-04 18:26:52 -04:00
7ca5cd3a4e #W# Added: Added a [mystats command to view the stuff i'm tracking. 2026-10-04 12:35:56 -04:00
d7f998cf6b #W# Added: Healing bufficon. 2026-10-03 20:29:18 -04:00
65d434c44a #W# Added: Now tracking player deaths, mobs kill (need to drop gold) and total gold, doesn't have to be looted. 2026-10-03 20:07:08 -04:00
ce9983b081 #W# Added: I am now tracking monster kills. Points are based on mob fame. Also added a nice [StatusBar command. 2026-10-03 18:32:43 -04:00
c34a5f18a5 #W# Typo: Fixed a Typo. 2026-10-03 12:57:48 -04:00
b787df9164 #W# Bug Fix: Removed Exile Hunter Stuff. Seems buggy. 2026-10-03 12:50:44 -04:00
9503ad1513 #W# Weather: Setup snowy weather for the winter islands. 2026-10-03 10:53:01 -04:00
314a2384d2 #W# Added: Settings.cs has some custimizable settings exposed for easy tweaking. 2026-10-02 13:59:36 -04:00
292cd625c5 #W# Added: Added an Overworld dungeon region. Tweaked overworld animal spawn. 2026-10-01 20:37:16 -04:00