diff --git a/.gitignore b/.gitignore index cbd642a..ab56ae2 100644 --- a/.gitignore +++ b/.gitignore @@ -501,4 +501,7 @@ fabric.properties /documentation/*.received.md /documentation/*.received.txt /documentation/*.approved.txt -build_output.txt \ No newline at end of file +build_output.txt +# Vim swap files +*.swp +*.swo diff --git a/documentation/DocumentationTest.FamilyExperienceDocumentation.approved.md b/documentation/DocumentationTest.FamilyExperienceDocumentation.approved.md new file mode 100644 index 0000000..148e21f --- /dev/null +++ b/documentation/DocumentationTest.FamilyExperienceDocumentation.approved.md @@ -0,0 +1,20 @@ +# Family Experience Table +- Level 1 - XP: 100000 +- Level 2 - XP: 250000 +- Level 3 - XP: 370000 +- Level 4 - XP: 560000 +- Level 5 - XP: 840000 +- Level 6 - XP: 1260000 +- Level 7 - XP: 1900000 +- Level 8 - XP: 2850000 +- Level 9 - XP: 3570000 +- Level 10 - XP: 3830000 +- Level 11 - XP: 4150000 +- Level 12 - XP: 4750000 +- Level 13 - XP: 5500000 +- Level 14 - XP: 6500000 +- Level 15 - XP: 7000000 +- Level 16 - XP: 8500000 +- Level 17 - XP: 9500000 +- Level 18 - XP: 10000000 +- Level 19 - XP: 17000000 diff --git a/documentation/DocumentationTest.MateExperienceDocumentation.approved.md b/documentation/DocumentationTest.MateExperienceDocumentation.approved.md new file mode 100644 index 0000000..d17b051 --- /dev/null +++ b/documentation/DocumentationTest.MateExperienceDocumentation.approved.md @@ -0,0 +1,100 @@ +# Mate Experience Table +- Level 1 - Pet: 15 - Partner: 60 +- Level 2 - Pet: 42 - Partner: 168 +- Level 3 - Pet: 90 - Partner: 360 +- Level 4 - Pet: 165 - Partner: 660 +- Level 5 - Pet: 273 - Partner: 1092 +- Level 6 - Pet: 420 - Partner: 1680 +- Level 7 - Pet: 612 - Partner: 2448 +- Level 8 - Pet: 855 - Partner: 3420 +- Level 9 - Pet: 1155 - Partner: 4620 +- Level 10 - Pet: 1518 - Partner: 6072 +- Level 11 - Pet: 1950 - Partner: 7800 +- Level 12 - Pet: 2457 - Partner: 9828 +- Level 13 - Pet: 3045 - Partner: 12180 +- Level 14 - Pet: 3720 - Partner: 14880 +- Level 15 - Pet: 5256 - Partner: 21024 +- Level 16 - Pet: 6990 - Partner: 27960 +- Level 17 - Pet: 8934 - Partner: 35736 +- Level 18 - Pet: 11100 - Partner: 44400 +- Level 19 - Pet: 13500 - Partner: 54000 +- Level 20 - Pet: 16146 - Partner: 64584 +- Level 21 - Pet: 19050 - Partner: 76200 +- Level 22 - Pet: 22224 - Partner: 88896 +- Level 23 - Pet: 25680 - Partner: 102720 +- Level 24 - Pet: 29430 - Partner: 117720 +- Level 25 - Pet: 33486 - Partner: 133944 +- Level 26 - Pet: 37860 - Partner: 151440 +- Level 27 - Pet: 42564 - Partner: 170256 +- Level 28 - Pet: 47610 - Partner: 190440 +- Level 29 - Pet: 53010 - Partner: 212040 +- Level 30 - Pet: 58776 - Partner: 235104 +- Level 31 - Pet: 64920 - Partner: 259680 +- Level 32 - Pet: 71454 - Partner: 285816 +- Level 33 - Pet: 78390 - Partner: 313560 +- Level 34 - Pet: 85740 - Partner: 342960 +- Level 35 - Pet: 93516 - Partner: 374064 +- Level 36 - Pet: 101730 - Partner: 406920 +- Level 37 - Pet: 110394 - Partner: 441576 +- Level 38 - Pet: 119520 - Partner: 478080 +- Level 39 - Pet: 129120 - Partner: 516480 +- Level 40 - Pet: 161059 - Partner: 644236 +- Level 41 - Pet: 194575 - Partner: 778300 +- Level 42 - Pet: 229706 - Partner: 918824 +- Level 43 - Pet: 266490 - Partner: 1065960 +- Level 44 - Pet: 304965 - Partner: 1219860 +- Level 45 - Pet: 345169 - Partner: 1380676 +- Level 46 - Pet: 387140 - Partner: 1548560 +- Level 47 - Pet: 430916 - Partner: 1723664 +- Level 48 - Pet: 476535 - Partner: 1906140 +- Level 49 - Pet: 524035 - Partner: 2096140 +- Level 50 - Pet: 573454 - Partner: 2293816 +- Level 51 - Pet: 624830 - Partner: 2499320 +- Level 52 - Pet: 678201 - Partner: 2712804 +- Level 53 - Pet: 733605 - Partner: 2934420 +- Level 54 - Pet: 791080 - Partner: 3164320 +- Level 55 - Pet: 850664 - Partner: 3402656 +- Level 56 - Pet: 912395 - Partner: 3649580 +- Level 57 - Pet: 976311 - Partner: 3905244 +- Level 58 - Pet: 1042450 - Partner: 4169800 +- Level 59 - Pet: 1110850 - Partner: 4443400 +- Level 60 - Pet: 1371320 - Partner: 5485280 +- Level 61 - Pet: 1640400 - Partner: 6561600 +- Level 62 - Pet: 1918230 - Partner: 7672920 +- Level 63 - Pet: 2204950 - Partner: 8819800 +- Level 64 - Pet: 2500700 - Partner: 10002800 +- Level 65 - Pet: 2805620 - Partner: 11222480 +- Level 66 - Pet: 3119850 - Partner: 12479400 +- Level 67 - Pet: 3443530 - Partner: 13774120 +- Level 68 - Pet: 3776800 - Partner: 15107200 +- Level 69 - Pet: 4119800 - Partner: 16479200 +- Level 70 - Pet: 4472670 - Partner: 17890680 +- Level 71 - Pet: 4835550 - Partner: 19342200 +- Level 72 - Pet: 5208580 - Partner: 20834320 +- Level 73 - Pet: 5591900 - Partner: 22367600 +- Level 74 - Pet: 5985650 - Partner: 23942600 +- Level 75 - Pet: 6389970 - Partner: 25559880 +- Level 76 - Pet: 6805000 - Partner: 27220000 +- Level 77 - Pet: 7230880 - Partner: 28923520 +- Level 78 - Pet: 7667750 - Partner: 30671000 +- Level 79 - Pet: 8115750 - Partner: 32463000 +- Level 80 - Pet: 9756000 - Partner: 39024000 +- Level 81 - Pet: 11437000 - Partner: 45748000 +- Level 82 - Pet: 13159250 - Partner: 52637000 +- Level 83 - Pet: 16334450 - Partner: 65337800 +- Level 84 - Pet: 19585700 - Partner: 78342800 +- Level 85 - Pet: 24393100 - Partner: 97572400 +- Level 86 - Pet: 29312950 - Partner: 117251800 +- Level 87 - Pet: 34346550 - Partner: 137386200 +- Level 88 - Pet: 39495200 - Partner: 157980800 +- Level 89 - Pet: 44760200 - Partner: 179040800 +- Level 90 - Pet: 50142850 - Partner: 200571400 +- Level 91 - Pet: 55644450 - Partner: 222577800 +- Level 92 - Pet: 61266300 - Partner: 245065200 +- Level 93 - Pet: 67009700 - Partner: 268038800 +- Level 94 - Pet: 72875950 - Partner: 291503800 +- Level 95 - Pet: 78866350 - Partner: 315465400 +- Level 96 - Pet: 84982200 - Partner: 339928800 +- Level 97 - Pet: 91224800 - Partner: 364899200 +- Level 98 - Pet: 97595450 - Partner: 390381800 +- Level 99 - Pet: 104095450 - Partner: 416381800 diff --git a/src/NosCore.Algorithm/Constants.cs b/src/NosCore.Algorithm/Constants.cs index c5a60ed..10bec9c 100644 --- a/src/NosCore.Algorithm/Constants.cs +++ b/src/NosCore.Algorithm/Constants.cs @@ -9,6 +9,7 @@ internal class Constants internal const byte MaxFairyLevel = 80; internal const byte MaxJobLevel = 80; internal const byte MaxHeroLevel = 60; + internal const byte MaxMateLevel = 99; internal static readonly int ClassCount = Enum.GetNames(typeof(CharacterClassType)).Length; } } diff --git a/src/NosCore.Algorithm/FamilyExperienceService/FamilyExperienceService.cs b/src/NosCore.Algorithm/FamilyExperienceService/FamilyExperienceService.cs new file mode 100644 index 0000000..88c1f60 --- /dev/null +++ b/src/NosCore.Algorithm/FamilyExperienceService/FamilyExperienceService.cs @@ -0,0 +1,37 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.FamilyExperienceService +{ + /// + /// Provides family experience requirement calculations for different levels + /// + /// + /// A ginfo line puts a level 7 family's bar at 640 000 against the 1 900 000 here, so at + /// least that row is suspect. One observation is not enough to rebuild the curve, so the + /// table stands until more levels can be checked. + /// + public class FamilyExperienceService : IFamilyExperienceService + { + private static readonly uint[] FamilyXpData = + [ + 100_000, 250_000, 370_000, 560_000, 840_000, + 1_260_000, 1_900_000, 2_850_000, 3_570_000, 3_830_000, + 4_150_000, 4_750_000, 5_500_000, 6_500_000, 7_000_000, + 8_500_000, 9_500_000, 10_000_000, 17_000_000 + ]; + + /// + /// Gets the experience a family needs to leave a specific level + /// + /// The family level + /// The experience required, or uint.MaxValue past the last known level + public uint GetFamilyExperience(byte level) + { + return level >= 1 && level <= FamilyXpData.Length ? FamilyXpData[level - 1] : uint.MaxValue; + } + } +} diff --git a/src/NosCore.Algorithm/FamilyExperienceService/IFamilyExperienceService.cs b/src/NosCore.Algorithm/FamilyExperienceService/IFamilyExperienceService.cs new file mode 100644 index 0000000..a6eafbf --- /dev/null +++ b/src/NosCore.Algorithm/FamilyExperienceService/IFamilyExperienceService.cs @@ -0,0 +1,21 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.FamilyExperienceService +{ + /// + /// Service for calculating family experience requirements + /// + public interface IFamilyExperienceService + { + /// + /// Gets the experience a family needs to leave a specific level + /// + /// The family level + /// The experience required + uint GetFamilyExperience(byte level); + } +} diff --git a/src/NosCore.Algorithm/MateExperienceService/IMateExperienceService.cs b/src/NosCore.Algorithm/MateExperienceService/IMateExperienceService.cs new file mode 100644 index 0000000..fc51680 --- /dev/null +++ b/src/NosCore.Algorithm/MateExperienceService/IMateExperienceService.cs @@ -0,0 +1,28 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.MateExperienceService +{ + /// + /// Service for calculating pet and partner experience requirements + /// + public interface IMateExperienceService + { + /// + /// Gets the experience a pet needs to leave a specific level + /// + /// The pet level + /// The experience required + long GetPetExperience(byte level); + + /// + /// Gets the experience a partner needs to leave a specific level + /// + /// The partner level + /// The experience required + long GetPartnerExperience(byte level); + } +} diff --git a/src/NosCore.Algorithm/MateExperienceService/MateExperienceService.cs b/src/NosCore.Algorithm/MateExperienceService/MateExperienceService.cs new file mode 100644 index 0000000..b7f9fb4 --- /dev/null +++ b/src/NosCore.Algorithm/MateExperienceService/MateExperienceService.cs @@ -0,0 +1,94 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.MateExperienceService +{ + /// + /// Provides pet and partner experience requirement calculations for different levels + /// + /// + /// A pet needs a twentieth of the curve and a partner a fifth. Both divisors come from the + /// XpLoad field of sc_p and sc_n, which match to the unit across eleven levels between 1 + /// and 88. + /// + public class MateExperienceService : IMateExperienceService + { + private const int PetDivisor = 20; + private const int PartnerDivisor = 5; + + private readonly long[] _mateXpData = new long[Constants.MaxMateLevel + 1]; + + /// + /// Initializes a new instance of the MateExperienceService and pre-calculates experience + /// requirements for all mate levels + /// + public MateExperienceService() + { + var step = new double[_mateXpData.Length]; + step[0] = 540; + step[1] = 960; + for (var i = 2; i < step.Length; i++) + { + step[i] = step[i - 1] + 420 + 120 * (i - 1); + } + + var factor = 1d; + _mateXpData[0] = 300; + for (var i = 1; i < _mateXpData.Length; i++) + { + if (i < 79) + { + factor = i switch + { + 14 => 6 / 3d, + 39 => 19 / 3d, + 59 => 70 / 3d, + _ => factor + }; + + _mateXpData[i] = (long)(_mateXpData[i - 1] + factor * step[i - 1]); + continue; + } + + factor = i switch + { + 79 => 5000, + 82 => 9000, + 84 => 13000, + _ => factor + }; + + _mateXpData[i] = (long)(_mateXpData[i - 1] + factor * (i + 2) * (i + 2)); + } + } + + /// + /// Gets the experience a pet needs to leave a specific level + /// + /// The pet level + /// The experience required + public long GetPetExperience(byte level) + { + return At(level) / PetDivisor; + } + + /// + /// Gets the experience a partner needs to leave a specific level + /// + /// The partner level + /// The experience required + public long GetPartnerExperience(byte level) + { + return At(level) / PartnerDivisor; + } + + private long At(byte level) + { + var index = level < 1 ? 0 : level - 1; + return _mateXpData[index >= _mateXpData.Length ? _mateXpData.Length - 1 : index]; + } + } +} diff --git a/test/NosCore.Algorithm.Tests/DocumentationTest.cs b/test/NosCore.Algorithm.Tests/DocumentationTest.cs index 78049ce..625f2d6 100644 --- a/test/NosCore.Algorithm.Tests/DocumentationTest.cs +++ b/test/NosCore.Algorithm.Tests/DocumentationTest.cs @@ -405,5 +405,39 @@ public void SumDocumentation() Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); } + + [TestMethod] + public void MateExperienceDocumentation() + { + var mateExperienceService = new MateExperienceService.MateExperienceService(); + + var resultBuilder = new StringBuilder("# Mate Experience Table"); + resultBuilder.AppendLine(); + + for (byte level = 1; level < 100; level++) + { + resultBuilder.AppendLine( + $"- Level {level,2} - Pet: {mateExperienceService.GetPetExperience(level)} - Partner: {mateExperienceService.GetPartnerExperience(level)}"); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + + [TestMethod] + public void FamilyExperienceDocumentation() + { + var familyExperienceService = new FamilyExperienceService.FamilyExperienceService(); + + var resultBuilder = new StringBuilder("# Family Experience Table"); + resultBuilder.AppendLine(); + + for (byte level = 1; level < 20; level++) + { + resultBuilder.AppendLine( + $"- Level {level,2} - XP: {familyExperienceService.GetFamilyExperience(level)}"); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } } }