From c0fa6badb421be12c0b8093a30584f49b0d9ddde Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 10:13:35 +0000 Subject: [PATCH 1/2] feat: add CellonService The options each cellon level can roll, with their value ranges, and the gold each level costs to apply. The prices are the ones the client shows in its add-option window. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014gSmkUxD9KGmbgzcx1sCB2 --- ...tationTest.CellonDocumentation.approved.md | 70 +++++++++++++++++++ .../CellonService/CellonOption.cs | 18 +++++ .../CellonService/CellonService.cs | 56 +++++++++++++++ .../CellonService/ICellonService.cs | 30 ++++++++ src/NosCore.Algorithm/Constants.cs | 1 + .../NosCore.Algorithm.csproj | 4 +- .../DocumentationTest.cs | 19 +++++ 7 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 documentation/DocumentationTest.CellonDocumentation.approved.md create mode 100644 src/NosCore.Algorithm/CellonService/CellonOption.cs create mode 100644 src/NosCore.Algorithm/CellonService/CellonService.cs create mode 100644 src/NosCore.Algorithm/CellonService/ICellonService.cs diff --git a/documentation/DocumentationTest.CellonDocumentation.approved.md b/documentation/DocumentationTest.CellonDocumentation.approved.md new file mode 100644 index 0000000..1c3e9e4 --- /dev/null +++ b/documentation/DocumentationTest.CellonDocumentation.approved.md @@ -0,0 +1,70 @@ +# Cellon Table +## Cellon 1 - Cost: 700 +- Hp: 30 - 100 +- Mp: 50 - 120 +- HpRecovery: 5 - 10 +- MpRecovery: 8 - 15 + +## Cellon 2 - Cost: 1400 +- Hp: 120 - 200 +- Mp: 150 - 250 +- HpRecovery: 14 - 20 +- MpRecovery: 16 - 25 + +## Cellon 3 - Cost: 3000 +- Hp: 220 - 300 +- Mp: 280 - 330 +- HpRecovery: 22 - 28 +- MpRecovery: 28 - 35 + +## Cellon 4 - Cost: 5000 +- Hp: 330 - 400 +- Mp: 350 - 420 +- HpRecovery: 30 - 38 +- MpRecovery: 38 - 45 + +## Cellon 5 - Cost: 10000 +- Hp: 430 - 550 +- Mp: 450 - 550 +- HpRecovery: 40 - 50 +- MpRecovery: 50 - 60 + +## Cellon 6 - Cost: 20000 +- Hp: 600 - 750 +- Mp: 600 - 750 +- HpRecovery: 55 - 70 +- MpRecovery: 65 - 80 +- MpConsumption: 1 - 7 +- CriticalDamageDecrease: 1 - 10 + +## Cellon 7 - Cost: 32000 +- Hp: 800 - 1000 +- Mp: 800 - 1000 +- HpRecovery: 75 - 90 +- MpRecovery: 75 - 90 +- MpConsumption: 8 - 12 +- CriticalDamageDecrease: 11 - 20 + +## Cellon 8 - Cost: 58000 +- Hp: 1000 - 1300 +- Mp: 1000 - 1300 +- HpRecovery: 100 - 120 +- MpRecovery: 100 - 120 +- MpConsumption: 13 - 17 +- CriticalDamageDecrease: 21 - 35 + +## Cellon 9 - Cost: 95000 +- Hp: 1100 - 1500 +- Mp: 1100 - 1500 +- HpRecovery: 110 - 135 +- MpRecovery: 110 - 135 +- MpConsumption: 14 - 21 +- CriticalDamageDecrease: 22 - 45 + +## Cellon 10 - Cost: 134900 +- Hp: 1200 - 1700 +- Mp: 1200 - 1700 +- HpRecovery: 120 - 150 +- MpRecovery: 120 - 150 +- MpConsumption: 15 - 25 +- CriticalDamageDecrease: 23 - 55 diff --git a/src/NosCore.Algorithm/CellonService/CellonOption.cs b/src/NosCore.Algorithm/CellonService/CellonOption.cs new file mode 100644 index 0000000..e001a52 --- /dev/null +++ b/src/NosCore.Algorithm/CellonService/CellonOption.cs @@ -0,0 +1,18 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using NosCore.Shared.Enumerations; + +namespace NosCore.Algorithm.CellonService +{ + /// + /// An option a cellon can roll, with the inclusive range its value is drawn from + /// + /// The option type + /// The lowest value the option can roll + /// The highest value the option can roll + public sealed record CellonOption(CellonType Type, int Minimum, int Maximum); +} diff --git a/src/NosCore.Algorithm/CellonService/CellonService.cs b/src/NosCore.Algorithm/CellonService/CellonService.cs new file mode 100644 index 0000000..ee794b5 --- /dev/null +++ b/src/NosCore.Algorithm/CellonService/CellonService.cs @@ -0,0 +1,56 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; +using System.Collections.Generic; +using NosCore.Shared.Enumerations; + +namespace NosCore.Algorithm.CellonService +{ + /// + /// Provides the options a cellon can add to an accessory and what applying it costs + /// + public class CellonService : ICellonService + { + internal static readonly long[] Price = + { 0, 700, 1400, 3000, 5000, 10000, 20000, 32000, 58000, 95000, 134900 }; + + internal static readonly CellonOption[][] Options = + { + Array.Empty(), + new CellonOption[] { new(CellonType.Hp, 30, 100), new(CellonType.Mp, 50, 120), new(CellonType.HpRecovery, 5, 10), new(CellonType.MpRecovery, 8, 15) }, + new CellonOption[] { new(CellonType.Hp, 120, 200), new(CellonType.Mp, 150, 250), new(CellonType.HpRecovery, 14, 20), new(CellonType.MpRecovery, 16, 25) }, + new CellonOption[] { new(CellonType.Hp, 220, 300), new(CellonType.Mp, 280, 330), new(CellonType.HpRecovery, 22, 28), new(CellonType.MpRecovery, 28, 35) }, + new CellonOption[] { new(CellonType.Hp, 330, 400), new(CellonType.Mp, 350, 420), new(CellonType.HpRecovery, 30, 38), new(CellonType.MpRecovery, 38, 45) }, + new CellonOption[] { new(CellonType.Hp, 430, 550), new(CellonType.Mp, 450, 550), new(CellonType.HpRecovery, 40, 50), new(CellonType.MpRecovery, 50, 60) }, + new CellonOption[] { new(CellonType.Hp, 600, 750), new(CellonType.Mp, 600, 750), new(CellonType.HpRecovery, 55, 70), new(CellonType.MpRecovery, 65, 80), new(CellonType.MpConsumption, 1, 7), new(CellonType.CriticalDamageDecrease, 1, 10) }, + new CellonOption[] { new(CellonType.Hp, 800, 1000), new(CellonType.Mp, 800, 1000), new(CellonType.HpRecovery, 75, 90), new(CellonType.MpRecovery, 75, 90), new(CellonType.MpConsumption, 8, 12), new(CellonType.CriticalDamageDecrease, 11, 20) }, + new CellonOption[] { new(CellonType.Hp, 1000, 1300), new(CellonType.Mp, 1000, 1300), new(CellonType.HpRecovery, 100, 120), new(CellonType.MpRecovery, 100, 120), new(CellonType.MpConsumption, 13, 17), new(CellonType.CriticalDamageDecrease, 21, 35) }, + new CellonOption[] { new(CellonType.Hp, 1100, 1500), new(CellonType.Mp, 1100, 1500), new(CellonType.HpRecovery, 110, 135), new(CellonType.MpRecovery, 110, 135), new(CellonType.MpConsumption, 14, 21), new(CellonType.CriticalDamageDecrease, 22, 45) }, + new CellonOption[] { new(CellonType.Hp, 1200, 1700), new(CellonType.Mp, 1200, 1700), new(CellonType.HpRecovery, 120, 150), new(CellonType.MpRecovery, 120, 150), new(CellonType.MpConsumption, 15, 25), new(CellonType.CriticalDamageDecrease, 23, 55) }, + }; + + /// + /// Gets the options a cellon of a specific level can roll + /// + /// The cellon level + /// The options, or an empty list for a level no cellon has + public IReadOnlyList GetOptions(byte level) + { + return level <= Constants.MaxCellonLevel ? Options[level] : Array.Empty(); + } + + /// + /// Gets the gold cost of applying a cellon of a specific level + /// + /// The cellon level + /// The gold cost + public long GetPrice(byte level) + { + return Price[level]; + } + } +} diff --git a/src/NosCore.Algorithm/CellonService/ICellonService.cs b/src/NosCore.Algorithm/CellonService/ICellonService.cs new file mode 100644 index 0000000..5a21fb4 --- /dev/null +++ b/src/NosCore.Algorithm/CellonService/ICellonService.cs @@ -0,0 +1,30 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System.Collections.Generic; + +namespace NosCore.Algorithm.CellonService +{ + /// + /// Service for retrieving the options a cellon can add to an accessory and what applying it costs + /// + public interface ICellonService + { + /// + /// Gets the options a cellon of a specific level can roll + /// + /// The cellon level + /// The options, or an empty list for a level no cellon has + IReadOnlyList GetOptions(byte level); + + /// + /// Gets the gold cost of applying a cellon of a specific level + /// + /// The cellon level + /// The gold cost + long GetPrice(byte level); + } +} diff --git a/src/NosCore.Algorithm/Constants.cs b/src/NosCore.Algorithm/Constants.cs index 1d90315..96566f9 100644 --- a/src/NosCore.Algorithm/Constants.cs +++ b/src/NosCore.Algorithm/Constants.cs @@ -12,6 +12,7 @@ internal class Constants internal const byte MaxMateLevel = 99; internal const byte MaxFamilyLevel = 30; internal const byte MaxGroupSize = 3; + internal const byte MaxCellonLevel = 10; internal static readonly int ClassCount = Enum.GetNames(typeof(CharacterClassType)).Length; } } diff --git a/src/NosCore.Algorithm/NosCore.Algorithm.csproj b/src/NosCore.Algorithm/NosCore.Algorithm.csproj index a9280f6..34b8530 100644 --- a/src/NosCore.Algorithm/NosCore.Algorithm.csproj +++ b/src/NosCore.Algorithm/NosCore.Algorithm.csproj @@ -13,7 +13,7 @@ https://github.com/NosCoreIO/NosCore.Algorithm.git nostale, noscore, nostale private server source, nostale emulator - 2.1.0 + 2.3.0 false NosCore's Algorithm GPL-3.0-only @@ -29,7 +29,7 @@ - + diff --git a/test/NosCore.Algorithm.Tests/DocumentationTest.cs b/test/NosCore.Algorithm.Tests/DocumentationTest.cs index c32bc15..e49fb2c 100644 --- a/test/NosCore.Algorithm.Tests/DocumentationTest.cs +++ b/test/NosCore.Algorithm.Tests/DocumentationTest.cs @@ -406,6 +406,25 @@ public void SumDocumentation() Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); } + [TestMethod] + public void CellonDocumentation() + { + var cellonService = new CellonService.CellonService(); + + var resultBuilder = new StringBuilder("# Cellon Table"); + for (byte level = 1; level <= 10; level++) + { + resultBuilder.AppendLine(); + resultBuilder.AppendLine($"## Cellon {level} - Cost: {cellonService.GetPrice(level)}"); + foreach (var option in cellonService.GetOptions(level)) + { + resultBuilder.AppendLine($"- {option.Type}: {option.Minimum} - {option.Maximum}"); + } + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + [TestMethod] public void MateExperienceDocumentation() { From bedd45efe80699272a3f4aa46b570a8381b9e557 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 10:18:51 +0000 Subject: [PATCH 2/2] fix(cellon): hand out a read-only view of the option table Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014gSmkUxD9KGmbgzcx1sCB2 --- src/NosCore.Algorithm/CellonService/CellonService.cs | 2 +- test/NosCore.Algorithm.Tests/DocumentationTest.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/NosCore.Algorithm/CellonService/CellonService.cs b/src/NosCore.Algorithm/CellonService/CellonService.cs index ee794b5..83f956f 100644 --- a/src/NosCore.Algorithm/CellonService/CellonService.cs +++ b/src/NosCore.Algorithm/CellonService/CellonService.cs @@ -40,7 +40,7 @@ public class CellonService : ICellonService /// The options, or an empty list for a level no cellon has public IReadOnlyList GetOptions(byte level) { - return level <= Constants.MaxCellonLevel ? Options[level] : Array.Empty(); + return level <= Constants.MaxCellonLevel ? Array.AsReadOnly(Options[level]) : Array.Empty(); } /// diff --git a/test/NosCore.Algorithm.Tests/DocumentationTest.cs b/test/NosCore.Algorithm.Tests/DocumentationTest.cs index e49fb2c..33a367c 100644 --- a/test/NosCore.Algorithm.Tests/DocumentationTest.cs +++ b/test/NosCore.Algorithm.Tests/DocumentationTest.cs @@ -425,6 +425,15 @@ public void CellonDocumentation() Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); } + [TestMethod] + public void NoCellonLevelHasNoOptions() + { + var cellonService = new CellonService.CellonService(); + + Assert.AreEqual(0, cellonService.GetOptions(0).Count); + Assert.AreEqual(0, cellonService.GetOptions(11).Count); + } + [TestMethod] public void MateExperienceDocumentation() {