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)
This commit is contained in:
Sergi Rosell 2026-10-05 03:17:05 +02:00 • committed by GitHub
parent ab89cd80f5
commit b7d93f5126
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 20 deletions

View file

@ -12,22 +12,23 @@ namespace Server.Engines.Harvest
private static readonly MutateEntry[] _mutateTable =
{
new(80.0, 80.0, 4080.0, true, typeof(SpecialFishingNet)),
new(80.0, 80.0, 4080.0, true, typeof(BigFish)),
new(90.0, 80.0, 4080.0, true, typeof(TreasureMap)),
new(100.0, 80.0, 4080.0, true, typeof(MessageInABottle)),
new(80.0, 80.0, 4080.0, true, Expansion.UOR, typeof(SpecialFishingNet)),
new(80.0, 80.0, 4080.0, true, Expansion.UOR, typeof(BigFish)),
new(90.0, 80.0, 4080.0, true, Expansion.T2A, typeof(TreasureMap)),
new(100.0, 80.0, 4080.0, true, Expansion.T2A, typeof(MessageInABottle)),
new(
0.0,
125.0,
-2375.0,
false,
Expansion.T2A,
typeof(PrizedFish),
typeof(WondrousFish),
typeof(TrulyRareFish),
typeof(PeculiarFish)
),
new(0.0, 105.0, -420.0, false, typeof(Boots), typeof(Shoes), typeof(Sandals), typeof(ThighBoots)),
new(0.0, 200.0, -200.0, false, new Type[] { null })
new(0.0, 105.0, -420.0, false, Expansion.T2A, typeof(Boots), typeof(Shoes), typeof(Sandals), typeof(ThighBoots)),
new(0.0, 200.0, -200.0, false, Expansion.None, new Type[] { null })
};
private static readonly int[] _waterLandTiles =
@ -173,6 +174,11 @@ namespace Server.Engines.Harvest
{
var entry = _mutateTable[i];
if (Core.Expansion < entry.m_Expansion)
{
continue;
}
if (!deepWater && entry.m_DeepWater)
{
continue;
@ -200,7 +206,7 @@ namespace Server.Engines.Harvest
{
foreach (var sos in pack.FindItemsByType<SOS>())
{
if ((from.Map == Map.Felucca || from.Map == Map.Trammel) && from.InRange(sos.TargetLocation, 60))
if (from.Map == sos.TargetMap && from.InRange(sos.TargetLocation, 60))
{
return true;
}
@ -240,7 +246,7 @@ namespace Server.Engines.Harvest
// We don't have to queue since we are returning on the first SOS.
foreach (var sos in pack.FindItemsByType<SOS>())
{
if ((from.Map == Map.Felucca || from.Map == Map.Trammel) && from.InRange(sos.TargetLocation, 60))
if (from.Map == sos.TargetMap && from.InRange(sos.TargetLocation, 60))
{
Item preLoot = null;
@ -296,14 +302,17 @@ namespace Server.Engines.Harvest
LockableContainer chest = Utility.RandomBool() ? new MetalGoldenChest() : new WoodenChest();
if (sos.IsAncient)
if (sos.IsAncient && Core.SE)
{
chest.Hue = 0x481;
}
TreasureMapChest.Fill(chest, Math.Max(1, Math.Min(4, sos.Level)));
chest.DropItem(sos.IsAncient ? new FabledFishingNet() : new SpecialFishingNet());
if (Core.UOR)
{
chest.DropItem(sos.IsAncient ? new FabledFishingNet() : new SpecialFishingNet());
}
chest.Movable = true;
chest.Locked = false;
@ -531,17 +540,21 @@ namespace Server.Engines.Harvest
private class MutateEntry
{
public readonly bool m_DeepWater;
public readonly Expansion m_Expansion;
public readonly double m_MaxSkill;
public readonly double m_MinSkill;
public readonly double m_ReqSkill;
public readonly Type[] m_Types;
public MutateEntry(double reqSkill, double minSkill, double maxSkill, bool deepWater, params Type[] types)
public MutateEntry(
double reqSkill, double minSkill, double maxSkill, bool deepWater, Expansion expansion, params Type[] types
)
{
m_ReqSkill = reqSkill;
m_MinSkill = minSkill;
m_MaxSkill = maxSkill;
m_DeepWater = deepWater;
m_Expansion = expansion;
m_Types = types;
}
}

View file

@ -30,7 +30,7 @@ public partial class BigFish : Item, ICarvable
{
base.GetProperties(list);
if (Weight >= 20)
if (Core.SE && Weight >= 20)
{
if (_fisher != null)
{

View file

@ -218,7 +218,7 @@ public partial class TrophyAddon : Item, IAddon
{
base.GetProperties(list);
if (_animalWeight >= 20)
if (Core.SE && _animalWeight >= 20)
{
if (!string.IsNullOrWhiteSpace(_hunter))
{
@ -346,7 +346,7 @@ public partial class TrophyDeed : Item
{
base.GetProperties(list);
if (_animalWeight >= 20)
if (Core.SE && _animalWeight >= 20)
{
if (!string.IsNullOrWhiteSpace(_hunter))
{

View file

@ -37,7 +37,7 @@ public partial class MessageInABottle : Item
public static int GetRandomLevel()
{
if (Core.AOS && Utility.Random(25) < 1)
if (Core.AOS && Utility.Random(Core.SE ? 25 : 40) < 1)
{
return 4; // ancient
}

View file

@ -50,7 +50,6 @@ namespace Server.Mobiles
CanSwim = true;
CantWalk = true;
PackItem(new MessageInABottle());
PackItem(new Rope { ItemID = 0x14F8 });
PackItem(new Rope { ItemID = 0x14FA });
}
@ -63,6 +62,13 @@ namespace Server.Mobiles
public override int TreasureMapLevel => 5;
public override bool OnBeforeDeath()
{
PackItem(new MessageInABottle(Map == Map.Felucca ? Map.Felucca : Map.Trammel));
return base.OnBeforeDeath();
}
public static Type[] Artifacts { get; } =
{
// Decorations

View file

@ -46,12 +46,10 @@ namespace Server.Mobiles
rope.ItemID = 0x14F8;
PackItem(rope);
if (Utility.RandomDouble() < .05)
if (Core.UOR)
{
PackItem(new MessageInABottle());
PackItem(new SpecialFishingNet()); // Confirm?
}
PackItem(new SpecialFishingNet()); // Confirm?
}
public override string CorpseName => "a krakens corpse";
@ -59,6 +57,16 @@ namespace Server.Mobiles
public override int TreasureMapLevel => 4;
public override bool OnBeforeDeath()
{
if (Core.T2A && Utility.RandomDouble() < .05)
{
PackItem(new MessageInABottle(Map == Map.Felucca ? Map.Felucca : Map.Trammel));
}
return base.OnBeforeDeath();
}
public override void GenerateLoot()
{
AddLoot(LootPack.Rich);