fix: fishing catch chances by era and the leviathan's treasure map (#2709)
Part of #2687. Magic fish and footwear roll the same in every era: magic fish get rarer with skill (5% at 0 skill, 1% at GM) and footwear has a 20% chance at 0 skill. The leviathan rolls the generic 1% for its level 5 map. A fishing table row can now end at an expansion, so each era gets its own magic fish and footwear rows. The chances at GM come from the Fishing Council of Britannia's cast counts before and after Publish 16, which came out in parts from April to December 2002, during LBR [O14]. The leviathan gets its 25% through a `BaseCreature.TreasureMapChance` override; leviathans only come from the fabled net, which already needs AOS. | Change | Era | Source | |---|---|---| | Footwear: 75% at 0 skill, 2.5% at GM | T2A to UOTD | "The beginning fisherman will find this item on his hook most of the times" [S1–S7]; 2.5% of catches [F1] | | Footwear: 75% at 0 skill, 10% at GM | LBR on | The same quote; 10.5% of catches [F2] | | Magic fish: 80 skill, 2.5% at GM | T2A to UOTD | 80 from July 1999 to October 2002 [S1–S6, S9]; 2.4% of catches [F1] | | Magic fish: no minimum, 5% at GM | LBR to SA | 0 from December 2002 [S7], so the minimum went in late 2002, during LBR; 5.6% of catches [F2] | | Magic fish: 80 skill again | HS on | Still 0 in June 2010, during SA [S13]; 80 today [O9]. HS is the first expansion after that | | Leviathan's map: 25% | AOS | Publish 27: "It also has a 25% chance to carry a level 5 treasure map." [O5] | - These are chances per roll. The FCB's counts leave out empty casts, so they line up once the "nothing" row #2687 lists is gone (a follow-up PR removes it); while it stays, footwear is 3.2% of a GM's catches before LBR and 12% from LBR. - No source counts footwear below GM, so 75% at 0 skill only follows Stratics' "most of the times". The numbers in my comment were placeholders. - 80 again from HS is the one change beyond my comment, which proposed no minimum from LBR on. The keys are #2687's, plus: - [O14] uo.com, the 2002 publishes: Publish 16 in parts on April 23, July 23, September 20 and December 2, 2002. https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2002-2/ - [F1] The Fishin' Statistician, 11,101 casts from August 2001 to April 2002, before Publish 16; skill not stated, most likely GM. https://web.archive.org/web/20020609120859/http://www.thefcb.com:80/novellettes/stats.html - [F2] The same page, 7,371 casts from July 2002 to February 2003, after Publish 16; skill not stated either. https://web.archive.org/web/20030216074120/http://www.thefcb.com:80/novellettes/stats.html - [S13] Stratics' fishing guide, captured June 5, 2010. https://web.archive.org/web/20100605065528/http://uo.stratics.com:80/content/skills/fishing.shtml
This commit is contained in:
parent
24ebce6f84
commit
f4ba6036fe
4 changed files with 140 additions and 14 deletions
108
Projects/UOContent.Tests/Tests/Engines/Harvest/FishingTests.cs
Normal file
108
Projects/UOContent.Tests/Tests/Engines/Harvest/FishingTests.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Engines.Harvest;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Random;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class FishingTests
|
||||
{
|
||||
private const int Casts = 100_000;
|
||||
|
||||
// On land in Britain, so a cast there never counts as deep water.
|
||||
private static readonly Point3D _shallow = new(1434, 1699, 0);
|
||||
|
||||
private static readonly Type[] _magicFish =
|
||||
[typeof(PrizedFish), typeof(WondrousFish), typeof(TrulyRareFish), typeof(PeculiarFish)];
|
||||
|
||||
private static readonly Type[] _footwear = [typeof(Boots), typeof(Shoes), typeof(Sandals), typeof(ThighBoots)];
|
||||
|
||||
[Theory]
|
||||
[InlineData(Expansion.None, 50.0, 0.0, 0.0)]
|
||||
[InlineData(Expansion.T2A, 0.0, 0.0, 0.75)]
|
||||
[InlineData(Expansion.T2A, 50.0, 0.0, 0.3877)]
|
||||
[InlineData(Expansion.T2A, 100.0, 0.025, 0.0247)]
|
||||
[InlineData(Expansion.UOTD, 80.0, 0.02, 0.1669)]
|
||||
[InlineData(Expansion.LBR, 0.0, 0.0, 0.75)]
|
||||
[InlineData(Expansion.LBR, 50.0, 0.025, 0.4519)]
|
||||
[InlineData(Expansion.AOS, 100.0, 0.05, 0.095)]
|
||||
[InlineData(Expansion.SA, 50.0, 0.025, 0.4519)]
|
||||
[InlineData(Expansion.HS, 50.0, 0.0, 0.4635)]
|
||||
[InlineData(Expansion.HS, 120.0, 0.06, 0.047)]
|
||||
[InlineData(Expansion.EJ, 120.0, 0.06, 0.047)]
|
||||
public void MutateType_MagicFishAndFootwear_FollowTheEra(
|
||||
Expansion expansion, double skill, double magicFishRate, double footwearRate
|
||||
)
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var previousGenerator = BuiltInRng.Generator;
|
||||
var fisher = new PlayerMobile(World.NewMobile);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = expansion;
|
||||
BuiltInRng.Generator = new System.Random(1);
|
||||
fisher.DefaultMobileInit();
|
||||
fisher.Skills.Fishing.Base = skill;
|
||||
|
||||
Assert.Equal(skill, fisher.Skills.Fishing.Value);
|
||||
Assert.False(SpecialFishingNet.FullValidation(Map.Felucca, _shallow.X, _shallow.Y));
|
||||
|
||||
var definition = Fishing.System.Definitions[0];
|
||||
var magicFish = 0;
|
||||
var footwear = 0;
|
||||
|
||||
for (var i = 0; i < Casts; i++)
|
||||
{
|
||||
var type = Fishing.System.MutateType(typeof(Fish), fisher, null, definition, Map.Felucca, _shallow, null);
|
||||
|
||||
if (Array.IndexOf(_magicFish, type) >= 0)
|
||||
{
|
||||
magicFish++;
|
||||
}
|
||||
else if (Array.IndexOf(_footwear, type) >= 0)
|
||||
{
|
||||
footwear++;
|
||||
}
|
||||
}
|
||||
|
||||
AssertRate(magicFishRate, magicFish);
|
||||
AssertRate(footwearRate, footwear);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fisher.Delete();
|
||||
BuiltInRng.Generator = previousGenerator;
|
||||
Core.Expansion = previousExpansion;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Leviathan_CarriesItsTreasureMapAQuarterOfTheTime()
|
||||
{
|
||||
var leviathan = new Leviathan();
|
||||
var serpent = new SeaSerpent();
|
||||
|
||||
try
|
||||
{
|
||||
Assert.Equal(0.25, leviathan.TreasureMapChance);
|
||||
Assert.Equal(TreasureMap.LootChance, serpent.TreasureMapChance);
|
||||
}
|
||||
finally
|
||||
{
|
||||
leviathan.Delete();
|
||||
serpent.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Within five standard deviations of the expected rate; a rate of zero must never happen.
|
||||
private static void AssertRate(double expected, int count)
|
||||
{
|
||||
var tolerance = 5 * Math.Sqrt(expected * (1 - expected) / Casts);
|
||||
Assert.InRange((double)count / Casts, expected - tolerance, expected + tolerance);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue