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"));
+ }
}
}