From 4474ad3ddb42571bf16354c3c2dfb2f4713e1914 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 10:52:55 +0000 Subject: [PATCH] Add group experience share, level penalty, rarity point and specialist point services - ExperienceShareService: each member's share of a group kill. - LevelPenaltyService: experience and gold multipliers for monsters below the player's level. - RarityPointService: stat points an equipment's rarity adds. - SpecialistPointService: a card's skill points and the stat level they buy. - DignityService.GetShopPriceMultiplier: NPC shop prices by dignity. Each has an approval table under documentation/. Closes #18 Closes #23 Closes #24 Closes #27 Closes #43 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014gSmkW4H42dDTduM5qrKt2 --- ....DignityShopPriceDocumentation.approved.md | 7 + ...t.ExperienceShareDocumentation.approved.md | 4 + ...Test.LevelPenaltyDocumentation.approved.md | 42 +++ ...nTest.RarityPointDocumentation.approved.md | 242 ++++++++++++++++++ ...t.SpecialistPointDocumentation.approved.md | 204 +++++++++++++++ src/NosCore.Algorithm/Constants.cs | 1 + .../DignityService/DignityService.cs | 16 ++ .../DignityService/IDignityService.cs | 7 + .../ExperienceShareService.cs | 39 +++ .../IExperienceShareService.cs | 28 ++ .../ILevelPenaltyService.cs | 30 +++ .../LevelPenaltyService.cs | 45 ++++ .../RarityPointService/IRarityPointService.cs | 22 ++ .../RarityPointService/RarityPointService.cs | 31 +++ .../ISpecialistPointService.cs | 50 ++++ .../SpecialistPointService.cs | 74 ++++++ .../DocumentationTest.cs | 107 ++++++++ 17 files changed, 949 insertions(+) create mode 100644 documentation/DocumentationTest.DignityShopPriceDocumentation.approved.md create mode 100644 documentation/DocumentationTest.ExperienceShareDocumentation.approved.md create mode 100644 documentation/DocumentationTest.LevelPenaltyDocumentation.approved.md create mode 100644 documentation/DocumentationTest.RarityPointDocumentation.approved.md create mode 100644 documentation/DocumentationTest.SpecialistPointDocumentation.approved.md create mode 100644 src/NosCore.Algorithm/ExperienceShareService/ExperienceShareService.cs create mode 100644 src/NosCore.Algorithm/ExperienceShareService/IExperienceShareService.cs create mode 100644 src/NosCore.Algorithm/LevelPenaltyService/ILevelPenaltyService.cs create mode 100644 src/NosCore.Algorithm/LevelPenaltyService/LevelPenaltyService.cs create mode 100644 src/NosCore.Algorithm/RarityPointService/IRarityPointService.cs create mode 100644 src/NosCore.Algorithm/RarityPointService/RarityPointService.cs create mode 100644 src/NosCore.Algorithm/SpecialistPointService/ISpecialistPointService.cs create mode 100644 src/NosCore.Algorithm/SpecialistPointService/SpecialistPointService.cs diff --git a/documentation/DocumentationTest.DignityShopPriceDocumentation.approved.md b/documentation/DocumentationTest.DignityShopPriceDocumentation.approved.md new file mode 100644 index 0000000..8f576d6 --- /dev/null +++ b/documentation/DocumentationTest.DignityShopPriceDocumentation.approved.md @@ -0,0 +1,7 @@ +# Dignity Shop Price Table +- Default - Price: x1 +- Dubious - Price: x1 +- Dreadful - Price: x1.1 +- Unqualified - Price: x1.2 +- Useless - Price: x1.5 +- Failed - Price: x1.5 diff --git a/documentation/DocumentationTest.ExperienceShareDocumentation.approved.md b/documentation/DocumentationTest.ExperienceShareDocumentation.approved.md new file mode 100644 index 0000000..d1084ab --- /dev/null +++ b/documentation/DocumentationTest.ExperienceShareDocumentation.approved.md @@ -0,0 +1,4 @@ +# Group Experience Share Table +- 1 member(s) - XP: 100.00 % Job XP: 100.00 % +- 2 member(s) - XP: 57.50 % Job XP: 62.50 % +- 3 member(s) - XP: 43.33 % Job XP: 50.00 % diff --git a/documentation/DocumentationTest.LevelPenaltyDocumentation.approved.md b/documentation/DocumentationTest.LevelPenaltyDocumentation.approved.md new file mode 100644 index 0000000..4711e00 --- /dev/null +++ b/documentation/DocumentationTest.LevelPenaltyDocumentation.approved.md @@ -0,0 +1,42 @@ +# Level Penalty Table +- Player -5 level(s) above the monster - XP: x1 Gold: x1 +- Player -4 level(s) above the monster - XP: x1 Gold: x1 +- Player -3 level(s) above the monster - XP: x1 Gold: x1 +- Player -2 level(s) above the monster - XP: x1 Gold: x1 +- Player -1 level(s) above the monster - XP: x1 Gold: x1 +- Player 0 level(s) above the monster - XP: x1 Gold: x1 +- Player 1 level(s) above the monster - XP: x1 Gold: x1 +- Player 2 level(s) above the monster - XP: x1 Gold: x1 +- Player 3 level(s) above the monster - XP: x1 Gold: x1 +- Player 4 level(s) above the monster - XP: x1 Gold: x1 +- Player 5 level(s) above the monster - XP: x1 Gold: x0.9 +- Player 6 level(s) above the monster - XP: x0.9 Gold: x0.7 +- Player 7 level(s) above the monster - XP: x0.7 Gold: x0.5 +- Player 8 level(s) above the monster - XP: x0.5 Gold: x0.3 +- Player 9 level(s) above the monster - XP: x0.3 Gold: x0.2 +- Player 10 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 11 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 12 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 13 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 14 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 15 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 16 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 17 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 18 level(s) above the monster - XP: x0.1 Gold: x0.1 +- Player 19 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 20 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 21 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 22 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 23 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 24 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 25 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 26 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 27 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 28 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 29 level(s) above the monster - XP: x0.1 Gold: x0.05 +- Player 30 level(s) above the monster - XP: x0.1 Gold: x0 +- Player 31 level(s) above the monster - XP: x0.1 Gold: x0 +- Player 32 level(s) above the monster - XP: x0.1 Gold: x0 +- Player 33 level(s) above the monster - XP: x0.1 Gold: x0 +- Player 34 level(s) above the monster - XP: x0.1 Gold: x0 +- Player 35 level(s) above the monster - XP: x0.1 Gold: x0 diff --git a/documentation/DocumentationTest.RarityPointDocumentation.approved.md b/documentation/DocumentationTest.RarityPointDocumentation.approved.md new file mode 100644 index 0000000..9a174b7 --- /dev/null +++ b/documentation/DocumentationTest.RarityPointDocumentation.approved.md @@ -0,0 +1,242 @@ +# Rarity Point Table +## Rarity -2 +- Item level 0- 4 - Points: -4 +- Item level 5- 9 - Points: -8 +- Item level 10-14 - Points: -12 +- Item level 15-19 - Points: -16 +- Item level 20-24 - Points: -20 +- Item level 25-29 - Points: -24 +- Item level 30-34 - Points: -28 +- Item level 35-39 - Points: -32 +- Item level 40-44 - Points: -36 +- Item level 45-49 - Points: -40 +- Item level 50-54 - Points: -44 +- Item level 55-59 - Points: -48 +- Item level 60-64 - Points: -52 +- Item level 65-69 - Points: -56 +- Item level 70-74 - Points: -60 +- Item level 75-79 - Points: -64 +- Item level 80-84 - Points: -68 +- Item level 85-89 - Points: -72 +- Item level 90-94 - Points: -76 +- Item level 95-99 - Points: -80 + +## Rarity -1 +- Item level 0- 4 - Points: -2 +- Item level 5- 9 - Points: -4 +- Item level 10-14 - Points: -6 +- Item level 15-19 - Points: -8 +- Item level 20-24 - Points: -10 +- Item level 25-29 - Points: -12 +- Item level 30-34 - Points: -14 +- Item level 35-39 - Points: -16 +- Item level 40-44 - Points: -18 +- Item level 45-49 - Points: -20 +- Item level 50-54 - Points: -22 +- Item level 55-59 - Points: -24 +- Item level 60-64 - Points: -26 +- Item level 65-69 - Points: -28 +- Item level 70-74 - Points: -30 +- Item level 75-79 - Points: -32 +- Item level 80-84 - Points: -34 +- Item level 85-89 - Points: -36 +- Item level 90-94 - Points: -38 +- Item level 95-99 - Points: -40 + +## Rarity 0 +- Item level 0- 4 - Points: 0 +- Item level 5- 9 - Points: 0 +- Item level 10-14 - Points: 0 +- Item level 15-19 - Points: 0 +- Item level 20-24 - Points: 0 +- Item level 25-29 - Points: 0 +- Item level 30-34 - Points: 0 +- Item level 35-39 - Points: 0 +- Item level 40-44 - Points: 0 +- Item level 45-49 - Points: 0 +- Item level 50-54 - Points: 0 +- Item level 55-59 - Points: 0 +- Item level 60-64 - Points: 0 +- Item level 65-69 - Points: 0 +- Item level 70-74 - Points: 0 +- Item level 75-79 - Points: 0 +- Item level 80-84 - Points: 0 +- Item level 85-89 - Points: 0 +- Item level 90-94 - Points: 0 +- Item level 95-99 - Points: 0 + +## Rarity 1 +- Item level 0- 4 - Points: 1 +- Item level 5- 9 - Points: 2 +- Item level 10-14 - Points: 3 +- Item level 15-19 - Points: 4 +- Item level 20-24 - Points: 5 +- Item level 25-29 - Points: 6 +- Item level 30-34 - Points: 7 +- Item level 35-39 - Points: 8 +- Item level 40-44 - Points: 9 +- Item level 45-49 - Points: 10 +- Item level 50-54 - Points: 11 +- Item level 55-59 - Points: 12 +- Item level 60-64 - Points: 13 +- Item level 65-69 - Points: 14 +- Item level 70-74 - Points: 15 +- Item level 75-79 - Points: 16 +- Item level 80-84 - Points: 17 +- Item level 85-89 - Points: 18 +- Item level 90-94 - Points: 19 +- Item level 95-99 - Points: 20 + +## Rarity 2 +- Item level 0- 4 - Points: 2 +- Item level 5- 9 - Points: 4 +- Item level 10-14 - Points: 6 +- Item level 15-19 - Points: 8 +- Item level 20-24 - Points: 10 +- Item level 25-29 - Points: 12 +- Item level 30-34 - Points: 14 +- Item level 35-39 - Points: 16 +- Item level 40-44 - Points: 18 +- Item level 45-49 - Points: 20 +- Item level 50-54 - Points: 22 +- Item level 55-59 - Points: 24 +- Item level 60-64 - Points: 26 +- Item level 65-69 - Points: 28 +- Item level 70-74 - Points: 30 +- Item level 75-79 - Points: 32 +- Item level 80-84 - Points: 34 +- Item level 85-89 - Points: 36 +- Item level 90-94 - Points: 38 +- Item level 95-99 - Points: 40 + +## Rarity 3 +- Item level 0- 4 - Points: 3 +- Item level 5- 9 - Points: 6 +- Item level 10-14 - Points: 9 +- Item level 15-19 - Points: 12 +- Item level 20-24 - Points: 15 +- Item level 25-29 - Points: 18 +- Item level 30-34 - Points: 21 +- Item level 35-39 - Points: 24 +- Item level 40-44 - Points: 27 +- Item level 45-49 - Points: 30 +- Item level 50-54 - Points: 33 +- Item level 55-59 - Points: 36 +- Item level 60-64 - Points: 39 +- Item level 65-69 - Points: 42 +- Item level 70-74 - Points: 45 +- Item level 75-79 - Points: 48 +- Item level 80-84 - Points: 51 +- Item level 85-89 - Points: 54 +- Item level 90-94 - Points: 57 +- Item level 95-99 - Points: 60 + +## Rarity 4 +- Item level 0- 4 - Points: 4 +- Item level 5- 9 - Points: 8 +- Item level 10-14 - Points: 12 +- Item level 15-19 - Points: 16 +- Item level 20-24 - Points: 20 +- Item level 25-29 - Points: 24 +- Item level 30-34 - Points: 28 +- Item level 35-39 - Points: 32 +- Item level 40-44 - Points: 36 +- Item level 45-49 - Points: 40 +- Item level 50-54 - Points: 44 +- Item level 55-59 - Points: 48 +- Item level 60-64 - Points: 52 +- Item level 65-69 - Points: 56 +- Item level 70-74 - Points: 60 +- Item level 75-79 - Points: 64 +- Item level 80-84 - Points: 68 +- Item level 85-89 - Points: 72 +- Item level 90-94 - Points: 76 +- Item level 95-99 - Points: 80 + +## Rarity 5 +- Item level 0- 4 - Points: 5 +- Item level 5- 9 - Points: 10 +- Item level 10-14 - Points: 15 +- Item level 15-19 - Points: 20 +- Item level 20-24 - Points: 25 +- Item level 25-29 - Points: 30 +- Item level 30-34 - Points: 35 +- Item level 35-39 - Points: 40 +- Item level 40-44 - Points: 45 +- Item level 45-49 - Points: 50 +- Item level 50-54 - Points: 55 +- Item level 55-59 - Points: 60 +- Item level 60-64 - Points: 65 +- Item level 65-69 - Points: 70 +- Item level 70-74 - Points: 75 +- Item level 75-79 - Points: 80 +- Item level 80-84 - Points: 85 +- Item level 85-89 - Points: 90 +- Item level 90-94 - Points: 95 +- Item level 95-99 - Points: 100 + +## Rarity 6 +- Item level 0- 4 - Points: 7 +- Item level 5- 9 - Points: 14 +- Item level 10-14 - Points: 21 +- Item level 15-19 - Points: 28 +- Item level 20-24 - Points: 35 +- Item level 25-29 - Points: 42 +- Item level 30-34 - Points: 49 +- Item level 35-39 - Points: 56 +- Item level 40-44 - Points: 63 +- Item level 45-49 - Points: 70 +- Item level 50-54 - Points: 77 +- Item level 55-59 - Points: 84 +- Item level 60-64 - Points: 91 +- Item level 65-69 - Points: 98 +- Item level 70-74 - Points: 105 +- Item level 75-79 - Points: 112 +- Item level 80-84 - Points: 119 +- Item level 85-89 - Points: 126 +- Item level 90-94 - Points: 133 +- Item level 95-99 - Points: 140 + +## Rarity 7 +- Item level 0- 4 - Points: 10 +- Item level 5- 9 - Points: 20 +- Item level 10-14 - Points: 30 +- Item level 15-19 - Points: 40 +- Item level 20-24 - Points: 50 +- Item level 25-29 - Points: 60 +- Item level 30-34 - Points: 70 +- Item level 35-39 - Points: 80 +- Item level 40-44 - Points: 90 +- Item level 45-49 - Points: 100 +- Item level 50-54 - Points: 110 +- Item level 55-59 - Points: 120 +- Item level 60-64 - Points: 130 +- Item level 65-69 - Points: 140 +- Item level 70-74 - Points: 150 +- Item level 75-79 - Points: 160 +- Item level 80-84 - Points: 170 +- Item level 85-89 - Points: 180 +- Item level 90-94 - Points: 190 +- Item level 95-99 - Points: 200 + +## Rarity 8 +- Item level 0- 4 - Points: 15 +- Item level 5- 9 - Points: 30 +- Item level 10-14 - Points: 45 +- Item level 15-19 - Points: 60 +- Item level 20-24 - Points: 75 +- Item level 25-29 - Points: 90 +- Item level 30-34 - Points: 105 +- Item level 35-39 - Points: 120 +- Item level 40-44 - Points: 135 +- Item level 45-49 - Points: 150 +- Item level 50-54 - Points: 165 +- Item level 55-59 - Points: 180 +- Item level 60-64 - Points: 195 +- Item level 65-69 - Points: 210 +- Item level 70-74 - Points: 225 +- Item level 75-79 - Points: 240 +- Item level 80-84 - Points: 255 +- Item level 85-89 - Points: 270 +- Item level 90-94 - Points: 285 +- Item level 95-99 - Points: 300 diff --git a/documentation/DocumentationTest.SpecialistPointDocumentation.approved.md b/documentation/DocumentationTest.SpecialistPointDocumentation.approved.md new file mode 100644 index 0000000..585894e --- /dev/null +++ b/documentation/DocumentationTest.SpecialistPointDocumentation.approved.md @@ -0,0 +1,204 @@ +# Specialist Point Table +## Points by level (+0) +- Level 1 - Points: 0 +- Level 2 - Points: 0 +- Level 3 - Points: 0 +- Level 4 - Points: 0 +- Level 5 - Points: 0 +- Level 6 - Points: 0 +- Level 7 - Points: 0 +- Level 8 - Points: 0 +- Level 9 - Points: 0 +- Level 10 - Points: 0 +- Level 11 - Points: 0 +- Level 12 - Points: 0 +- Level 13 - Points: 0 +- Level 14 - Points: 0 +- Level 15 - Points: 0 +- Level 16 - Points: 0 +- Level 17 - Points: 0 +- Level 18 - Points: 0 +- Level 19 - Points: 0 +- Level 20 - Points: 0 +- Level 21 - Points: 3 +- Level 22 - Points: 6 +- Level 23 - Points: 9 +- Level 24 - Points: 12 +- Level 25 - Points: 15 +- Level 26 - Points: 18 +- Level 27 - Points: 21 +- Level 28 - Points: 24 +- Level 29 - Points: 27 +- Level 30 - Points: 30 +- Level 31 - Points: 33 +- Level 32 - Points: 36 +- Level 33 - Points: 39 +- Level 34 - Points: 42 +- Level 35 - Points: 45 +- Level 36 - Points: 48 +- Level 37 - Points: 51 +- Level 38 - Points: 54 +- Level 39 - Points: 57 +- Level 40 - Points: 60 +- Level 41 - Points: 63 +- Level 42 - Points: 66 +- Level 43 - Points: 69 +- Level 44 - Points: 72 +- Level 45 - Points: 75 +- Level 46 - Points: 78 +- Level 47 - Points: 81 +- Level 48 - Points: 84 +- Level 49 - Points: 87 +- Level 50 - Points: 90 +- Level 51 - Points: 93 +- Level 52 - Points: 96 +- Level 53 - Points: 99 +- Level 54 - Points: 102 +- Level 55 - Points: 105 +- Level 56 - Points: 108 +- Level 57 - Points: 111 +- Level 58 - Points: 114 +- Level 59 - Points: 117 +- Level 60 - Points: 120 +- Level 61 - Points: 123 +- Level 62 - Points: 126 +- Level 63 - Points: 129 +- Level 64 - Points: 132 +- Level 65 - Points: 135 +- Level 66 - Points: 138 +- Level 67 - Points: 141 +- Level 68 - Points: 144 +- Level 69 - Points: 147 +- Level 70 - Points: 150 +- Level 71 - Points: 153 +- Level 72 - Points: 156 +- Level 73 - Points: 159 +- Level 74 - Points: 162 +- Level 75 - Points: 165 +- Level 76 - Points: 168 +- Level 77 - Points: 171 +- Level 78 - Points: 174 +- Level 79 - Points: 177 +- Level 80 - Points: 180 +- Level 81 - Points: 183 +- Level 82 - Points: 186 +- Level 83 - Points: 189 +- Level 84 - Points: 192 +- Level 85 - Points: 195 +- Level 86 - Points: 198 +- Level 87 - Points: 201 +- Level 88 - Points: 204 +- Level 89 - Points: 207 +- Level 90 - Points: 210 +- Level 91 - Points: 213 +- Level 92 - Points: 216 +- Level 93 - Points: 219 +- Level 94 - Points: 222 +- Level 95 - Points: 225 +- Level 96 - Points: 228 +- Level 97 - Points: 231 +- Level 98 - Points: 234 +- Level 99 - Points: 237 + +## Points at level 99 by upgrade +- +0 - Points: 237 +- +1 - Points: 242 +- +2 - Points: 247 +- +3 - Points: 252 +- +4 - Points: 257 +- +5 - Points: 265 +- +6 - Points: 273 +- +7 - Points: 283 +- +8 - Points: 293 +- +9 - Points: 305 +- +10 - Points: 317 +- +11 - Points: 332 +- +12 - Points: 347 +- +13 - Points: 365 +- +14 - Points: 385 +- +15 - Points: 410 + +## Stat level by points spent +- 0 points - Attack: 0 Defence: 0 Element: 0 HP/MP: 0 +- 5 points - Attack: 5 Defence: 5 Element: 5 HP/MP: 5 +- 10 points - Attack: 10 Defence: 10 Element: 10 HP/MP: 10 +- 15 points - Attack: 12 Defence: 12 Element: 15 HP/MP: 12 +- 20 points - Attack: 15 Defence: 15 Element: 20 HP/MP: 15 +- 25 points - Attack: 17 Defence: 17 Element: 22 HP/MP: 17 +- 30 points - Attack: 19 Defence: 20 Element: 25 HP/MP: 20 +- 35 points - Attack: 21 Defence: 22 Element: 27 HP/MP: 22 +- 40 points - Attack: 23 Defence: 25 Element: 30 HP/MP: 25 +- 45 points - Attack: 24 Defence: 27 Element: 31 HP/MP: 27 +- 50 points - Attack: 26 Defence: 29 Element: 33 HP/MP: 30 +- 55 points - Attack: 28 Defence: 31 Element: 35 HP/MP: 31 +- 60 points - Attack: 29 Defence: 33 Element: 36 HP/MP: 33 +- 65 points - Attack: 31 Defence: 34 Element: 38 HP/MP: 35 +- 70 points - Attack: 33 Defence: 36 Element: 40 HP/MP: 36 +- 75 points - Attack: 34 Defence: 38 Element: 41 HP/MP: 38 +- 80 points - Attack: 36 Defence: 39 Element: 42 HP/MP: 40 +- 85 points - Attack: 38 Defence: 41 Element: 43 HP/MP: 41 +- 90 points - Attack: 39 Defence: 42 Element: 45 HP/MP: 43 +- 95 points - Attack: 40 Defence: 43 Element: 46 HP/MP: 45 +- 100 points - Attack: 42 Defence: 44 Element: 47 HP/MP: 46 +- 105 points - Attack: 43 Defence: 46 Element: 48 HP/MP: 48 +- 110 points - Attack: 44 Defence: 47 Element: 50 HP/MP: 50 +- 115 points - Attack: 45 Defence: 48 Element: 51 HP/MP: 51 +- 120 points - Attack: 47 Defence: 49 Element: 52 HP/MP: 52 +- 125 points - Attack: 48 Defence: 51 Element: 53 HP/MP: 53 +- 130 points - Attack: 49 Defence: 52 Element: 54 HP/MP: 55 +- 135 points - Attack: 50 Defence: 53 Element: 55 HP/MP: 56 +- 140 points - Attack: 52 Defence: 54 Element: 56 HP/MP: 57 +- 145 points - Attack: 53 Defence: 56 Element: 57 HP/MP: 58 +- 150 points - Attack: 54 Defence: 57 Element: 58 HP/MP: 60 +- 155 points - Attack: 55 Defence: 58 Element: 59 HP/MP: 61 +- 160 points - Attack: 57 Defence: 59 Element: 60 HP/MP: 62 +- 165 points - Attack: 58 Defence: 60 Element: 61 HP/MP: 63 +- 170 points - Attack: 59 Defence: 61 Element: 62 HP/MP: 64 +- 175 points - Attack: 60 Defence: 62 Element: 63 HP/MP: 65 +- 180 points - Attack: 61 Defence: 63 Element: 64 HP/MP: 66 +- 185 points - Attack: 62 Defence: 64 Element: 65 HP/MP: 67 +- 190 points - Attack: 63 Defence: 65 Element: 66 HP/MP: 68 +- 195 points - Attack: 64 Defence: 66 Element: 67 HP/MP: 69 +- 200 points - Attack: 65 Defence: 67 Element: 68 HP/MP: 70 +- 205 points - Attack: 66 Defence: 68 Element: 69 HP/MP: 70 +- 210 points - Attack: 67 Defence: 69 Element: 70 HP/MP: 71 +- 215 points - Attack: 68 Defence: 70 Element: 70 HP/MP: 72 +- 220 points - Attack: 69 Defence: 71 Element: 71 HP/MP: 73 +- 225 points - Attack: 70 Defence: 72 Element: 72 HP/MP: 74 +- 230 points - Attack: 71 Defence: 73 Element: 73 HP/MP: 75 +- 235 points - Attack: 72 Defence: 74 Element: 74 HP/MP: 75 +- 240 points - Attack: 73 Defence: 75 Element: 75 HP/MP: 76 +- 245 points - Attack: 74 Defence: 76 Element: 75 HP/MP: 77 +- 250 points - Attack: 75 Defence: 77 Element: 76 HP/MP: 78 +- 255 points - Attack: 76 Defence: 78 Element: 77 HP/MP: 79 +- 260 points - Attack: 77 Defence: 79 Element: 78 HP/MP: 80 +- 265 points - Attack: 78 Defence: 79 Element: 79 HP/MP: 80 +- 270 points - Attack: 79 Defence: 80 Element: 80 HP/MP: 81 +- 275 points - Attack: 80 Defence: 81 Element: 80 HP/MP: 82 +- 280 points - Attack: 81 Defence: 82 Element: 81 HP/MP: 82 +- 285 points - Attack: 81 Defence: 83 Element: 82 HP/MP: 83 +- 290 points - Attack: 82 Defence: 84 Element: 82 HP/MP: 84 +- 295 points - Attack: 83 Defence: 84 Element: 83 HP/MP: 85 +- 300 points - Attack: 84 Defence: 85 Element: 84 HP/MP: 85 +- 305 points - Attack: 85 Defence: 86 Element: 85 HP/MP: 86 +- 310 points - Attack: 86 Defence: 86 Element: 85 HP/MP: 87 +- 315 points - Attack: 86 Defence: 87 Element: 86 HP/MP: 87 +- 320 points - Attack: 87 Defence: 88 Element: 87 HP/MP: 88 +- 325 points - Attack: 88 Defence: 89 Element: 87 HP/MP: 89 +- 330 points - Attack: 89 Defence: 89 Element: 88 HP/MP: 90 +- 335 points - Attack: 90 Defence: 90 Element: 89 HP/MP: 90 +- 340 points - Attack: 90 Defence: 91 Element: 90 HP/MP: 91 +- 345 points - Attack: 91 Defence: 91 Element: 90 HP/MP: 91 +- 350 points - Attack: 92 Defence: 92 Element: 91 HP/MP: 92 +- 355 points - Attack: 93 Defence: 93 Element: 92 HP/MP: 93 +- 360 points - Attack: 93 Defence: 94 Element: 92 HP/MP: 93 +- 365 points - Attack: 94 Defence: 94 Element: 93 HP/MP: 94 +- 370 points - Attack: 95 Defence: 95 Element: 94 HP/MP: 95 +- 375 points - Attack: 95 Defence: 95 Element: 95 HP/MP: 95 +- 380 points - Attack: 96 Defence: 96 Element: 95 HP/MP: 96 +- 385 points - Attack: 97 Defence: 97 Element: 96 HP/MP: 96 +- 390 points - Attack: 97 Defence: 97 Element: 97 HP/MP: 97 +- 395 points - Attack: 98 Defence: 98 Element: 97 HP/MP: 98 +- 400 points - Attack: 99 Defence: 99 Element: 98 HP/MP: 98 +- 405 points - Attack: 99 Defence: 99 Element: 99 HP/MP: 99 +- 410 points - Attack: 100 Defence: 100 Element: 100 HP/MP: 100 diff --git a/src/NosCore.Algorithm/Constants.cs b/src/NosCore.Algorithm/Constants.cs index a1abcd4..1d90315 100644 --- a/src/NosCore.Algorithm/Constants.cs +++ b/src/NosCore.Algorithm/Constants.cs @@ -11,6 +11,7 @@ internal class Constants internal const byte MaxHeroLevel = 60; internal const byte MaxMateLevel = 99; internal const byte MaxFamilyLevel = 30; + internal const byte MaxGroupSize = 3; internal static readonly int ClassCount = Enum.GetNames(typeof(CharacterClassType)).Length; } } diff --git a/src/NosCore.Algorithm/DignityService/DignityService.cs b/src/NosCore.Algorithm/DignityService/DignityService.cs index a3cf03f..f3e02b5 100644 --- a/src/NosCore.Algorithm/DignityService/DignityService.cs +++ b/src/NosCore.Algorithm/DignityService/DignityService.cs @@ -58,5 +58,21 @@ public DignityType GetLevelFromDignity(short dignity) { return (level == DignityType.Default ? (short)200 : (short)(_dignityData[level - 1] - 1), _dignityData[level]); } + + /// + /// Gets the multiplier applied to NPC shop prices for a dignity level + /// + /// The buyer's dignity level type + /// The price multiplier + public double GetShopPriceMultiplier(DignityType level) + { + return level switch + { + DignityType.Dreadful => 1.1, + DignityType.Unqualified => 1.2, + DignityType.Useless or DignityType.Failed => 1.5, + _ => 1 + }; + } } } diff --git a/src/NosCore.Algorithm/DignityService/IDignityService.cs b/src/NosCore.Algorithm/DignityService/IDignityService.cs index 47c36d3..8d89494 100644 --- a/src/NosCore.Algorithm/DignityService/IDignityService.cs +++ b/src/NosCore.Algorithm/DignityService/IDignityService.cs @@ -26,5 +26,12 @@ public interface IDignityService /// The dignity level type /// A tuple containing the maximum and minimum dignity values for the level (short, short) GetDignity(DignityType level); + + /// + /// Gets the multiplier applied to NPC shop prices for a dignity level + /// + /// The buyer's dignity level type + /// The price multiplier + double GetShopPriceMultiplier(DignityType level); } } \ No newline at end of file diff --git a/src/NosCore.Algorithm/ExperienceShareService/ExperienceShareService.cs b/src/NosCore.Algorithm/ExperienceShareService/ExperienceShareService.cs new file mode 100644 index 0000000..b0bda96 --- /dev/null +++ b/src/NosCore.Algorithm/ExperienceShareService/ExperienceShareService.cs @@ -0,0 +1,39 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.ExperienceShareService +{ + /// + /// Provides the experience split of a group + /// + /// + /// Each extra member adds 15% experience and 25% job experience to the kill, and the total + /// is divided equally: 100%, 57.5% and 43.33% each for experience, 100%, 62.5% and 50% each + /// for job experience. + /// + public class ExperienceShareService : IExperienceShareService + { + private const double ExperienceBonusPerMember = 0.15; + private const double JobExperienceBonusPerMember = 0.25; + + /// + public double GetExperienceShare(byte memberCount) => Share(memberCount, ExperienceBonusPerMember); + + /// + public double GetJobExperienceShare(byte memberCount) => Share(memberCount, JobExperienceBonusPerMember); + + private static double Share(byte memberCount, double bonusPerMember) + { + var members = memberCount switch + { + 0 => 1, + > Constants.MaxGroupSize => Constants.MaxGroupSize, + _ => memberCount + }; + return (1 + bonusPerMember * (members - 1)) / members; + } + } +} diff --git a/src/NosCore.Algorithm/ExperienceShareService/IExperienceShareService.cs b/src/NosCore.Algorithm/ExperienceShareService/IExperienceShareService.cs new file mode 100644 index 0000000..7860c09 --- /dev/null +++ b/src/NosCore.Algorithm/ExperienceShareService/IExperienceShareService.cs @@ -0,0 +1,28 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.ExperienceShareService +{ + /// + /// Service for splitting monster experience between the members of a group + /// + public interface IExperienceShareService + { + /// + /// Gets the share of a monster's experience each member receives + /// + /// The number of group members sharing the kill + /// The fraction of the monster's experience given to each member + double GetExperienceShare(byte memberCount); + + /// + /// Gets the share of a monster's job experience each member receives + /// + /// The number of group members sharing the kill + /// The fraction of the monster's job experience given to each member + double GetJobExperienceShare(byte memberCount); + } +} diff --git a/src/NosCore.Algorithm/LevelPenaltyService/ILevelPenaltyService.cs b/src/NosCore.Algorithm/LevelPenaltyService/ILevelPenaltyService.cs new file mode 100644 index 0000000..8700306 --- /dev/null +++ b/src/NosCore.Algorithm/LevelPenaltyService/ILevelPenaltyService.cs @@ -0,0 +1,30 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.LevelPenaltyService +{ + /// + /// Service for reducing the rewards of monsters far below the player's level + /// + public interface ILevelPenaltyService + { + /// + /// Gets the multiplier applied to the experience of a monster + /// + /// The level the experience is for (level, job level or hero level) + /// The monster's level + /// A multiplier between 0 and 1 + double GetExperiencePenalty(byte playerLevel, byte monsterLevel); + + /// + /// Gets the multiplier applied to the chance of a monster dropping gold + /// + /// The player's level + /// The monster's level + /// A multiplier between 0 and 1 + double GetGoldPenalty(byte playerLevel, byte monsterLevel); + } +} diff --git a/src/NosCore.Algorithm/LevelPenaltyService/LevelPenaltyService.cs b/src/NosCore.Algorithm/LevelPenaltyService/LevelPenaltyService.cs new file mode 100644 index 0000000..08f9b00 --- /dev/null +++ b/src/NosCore.Algorithm/LevelPenaltyService/LevelPenaltyService.cs @@ -0,0 +1,45 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.LevelPenaltyService +{ + /// + /// Provides the reward multipliers for killing a monster below the player's level + /// + public class LevelPenaltyService : ILevelPenaltyService + { + /// + public double GetExperiencePenalty(byte playerLevel, byte monsterLevel) + { + return (playerLevel - monsterLevel) switch + { + <= 5 => 1, + 6 => 0.9, + 7 => 0.7, + 8 => 0.5, + 9 => 0.3, + _ => 0.1 + }; + } + + /// + public double GetGoldPenalty(byte playerLevel, byte monsterLevel) + { + return (playerLevel - monsterLevel) switch + { + <= 4 => 1, + 5 => 0.9, + 6 => 0.7, + 7 => 0.5, + 8 => 0.3, + 9 => 0.2, + <= 18 => 0.1, + <= 29 => 0.05, + _ => 0 + }; + } + } +} diff --git a/src/NosCore.Algorithm/RarityPointService/IRarityPointService.cs b/src/NosCore.Algorithm/RarityPointService/IRarityPointService.cs new file mode 100644 index 0000000..49d72a2 --- /dev/null +++ b/src/NosCore.Algorithm/RarityPointService/IRarityPointService.cs @@ -0,0 +1,22 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.RarityPointService +{ + /// + /// Service for the stat points an equipment's rarity adds + /// + public interface IRarityPointService + { + /// + /// Gets the points an item of a given rarity and level adds to its stats + /// + /// The item's rarity + /// The item's level + /// The rarity points + int GetRarityPoint(sbyte rarity, byte itemLevel); + } +} diff --git a/src/NosCore.Algorithm/RarityPointService/RarityPointService.cs b/src/NosCore.Algorithm/RarityPointService/RarityPointService.cs new file mode 100644 index 0000000..813bac0 --- /dev/null +++ b/src/NosCore.Algorithm/RarityPointService/RarityPointService.cs @@ -0,0 +1,31 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.RarityPointService +{ + /// + /// Provides the rarity points of equipment + /// + /// + /// Each rarity has a base value that is multiplied by one plus a fifth of the item level. + /// + public class RarityPointService : IRarityPointService + { + /// + public int GetRarityPoint(sbyte rarity, byte itemLevel) + { + var points = rarity switch + { + >= 0 and <= 5 => rarity, + 6 => 7, + 7 => 10, + 8 => 15, + _ => rarity * 2 + }; + return points * (itemLevel / 5 + 1); + } + } +} diff --git a/src/NosCore.Algorithm/SpecialistPointService/ISpecialistPointService.cs b/src/NosCore.Algorithm/SpecialistPointService/ISpecialistPointService.cs new file mode 100644 index 0000000..bd2d245 --- /dev/null +++ b/src/NosCore.Algorithm/SpecialistPointService/ISpecialistPointService.cs @@ -0,0 +1,50 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Algorithm.SpecialistPointService +{ + /// + /// Service for the skill points of a specialist card and the stats they buy + /// + public interface ISpecialistPointService + { + /// + /// Gets the skill points a specialist card has to spend + /// + /// The card's level + /// The card's upgrade (+0 to +15) + /// The points available + int GetSpecialistPoints(byte specialistLevel, byte upgrade); + + /// + /// Gets the attack level bought with the points spent on attack + /// + /// The points spent + /// The attack level + int GetAttackLevel(short points); + + /// + /// Gets the defence level bought with the points spent on defence + /// + /// The points spent + /// The defence level + int GetDefenceLevel(short points); + + /// + /// Gets the element level bought with the points spent on element + /// + /// The points spent + /// The element level + int GetElementLevel(short points); + + /// + /// Gets the HP/MP level bought with the points spent on HP/MP + /// + /// The points spent + /// The HP/MP level + int GetHpMpLevel(short points); + } +} diff --git a/src/NosCore.Algorithm/SpecialistPointService/SpecialistPointService.cs b/src/NosCore.Algorithm/SpecialistPointService/SpecialistPointService.cs new file mode 100644 index 0000000..c8d841c --- /dev/null +++ b/src/NosCore.Algorithm/SpecialistPointService/SpecialistPointService.cs @@ -0,0 +1,74 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; + +namespace NosCore.Algorithm.SpecialistPointService +{ + /// + /// Provides specialist card skill points and the stat levels they buy + /// + /// + /// A card earns 3 points per level above 20, plus a bonus for its upgrade. Each stat is + /// bought in bands: the first points buy one level each, later bands cost 2, 3, ... points + /// per level, and the stat stops at the level reached with all the points a card can earn. + /// + public class SpecialistPointService : ISpecialistPointService + { + private static readonly int[] UpgradeBonus = [0, 5, 10, 15, 20, 28, 36, 46, 56, 68, 80, 95, 110, 128, 148, 173]; + + private static readonly (short UpTo, int Cost)[] AttackBands = + [(10, 1), (28, 2), (88, 3), (168, 4), (268, 5), (334, 6), (383, 7), (391, 8), (400, 9), (410, 10)]; + + // A 360-400 band that starts at level 97 instead of 94 is sometimes seen; it would make + // 400 points worth more than 410, so the band is taken to continue from 94 like the others. + private static readonly (short UpTo, int Cost)[] DefenceBands = + [(10, 1), (48, 2), (81, 3), (161, 4), (236, 5), (290, 6), (360, 7), (400, 8), (410, 10)]; + + private static readonly (short UpTo, int Cost)[] ElementBands = + [(20, 1), (40, 2), (70, 3), (110, 4), (210, 5), (270, 6), (410, 7)]; + + private static readonly (short UpTo, int Cost)[] HpMpBands = + [(10, 1), (50, 2), (110, 3), (150, 4), (200, 5), (260, 6), (330, 7), (410, 8)]; + + /// + public int GetSpecialistPoints(byte specialistLevel, byte upgrade) + { + var points = specialistLevel > 20 ? (specialistLevel - 20) * 3 : 0; + return points + (upgrade < UpgradeBonus.Length ? UpgradeBonus[upgrade] : UpgradeBonus[^1]); + } + + /// + public int GetAttackLevel(short points) => Level(AttackBands, points); + + /// + public int GetDefenceLevel(short points) => Level(DefenceBands, points); + + /// + public int GetElementLevel(short points) => Level(ElementBands, points); + + /// + public int GetHpMpLevel(short points) => Level(HpMpBands, points); + + private static int Level((short UpTo, int Cost)[] bands, short points) + { + var level = 0; + short from = 0; + foreach (var (upTo, cost) in bands) + { + if (points <= upTo) + { + return level + Math.Max(0, points - from) / cost; + } + + level += (upTo - from) / cost; + from = upTo; + } + + return level; + } + } +} diff --git a/test/NosCore.Algorithm.Tests/DocumentationTest.cs b/test/NosCore.Algorithm.Tests/DocumentationTest.cs index 33431ed..c32bc15 100644 --- a/test/NosCore.Algorithm.Tests/DocumentationTest.cs +++ b/test/NosCore.Algorithm.Tests/DocumentationTest.cs @@ -439,5 +439,112 @@ public void FamilyExperienceDocumentation() Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); } + + [TestMethod] + public void DignityShopPriceDocumentation() + { + var dignityService = new DignityService.DignityService(); + + var resultBuilder = new StringBuilder("# Dignity Shop Price Table"); + resultBuilder.AppendLine(); + foreach (var dignity in Enum.GetValues(typeof(DignityType)).Cast()) + { + resultBuilder.AppendLine($"- {dignity,-11} - Price: x{dignityService.GetShopPriceMultiplier(dignity)}"); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + + [TestMethod] + public void ExperienceShareDocumentation() + { + var experienceShareService = new ExperienceShareService.ExperienceShareService(); + + var resultBuilder = new StringBuilder("# Group Experience Share Table"); + resultBuilder.AppendLine(); + for (byte members = 1; members <= 3; members++) + { + resultBuilder.AppendLine( + $"- {members} member(s) - XP: {experienceShareService.GetExperienceShare(members):P2} Job XP: {experienceShareService.GetJobExperienceShare(members):P2}"); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + + [TestMethod] + public void LevelPenaltyDocumentation() + { + var levelPenaltyService = new LevelPenaltyService.LevelPenaltyService(); + + var resultBuilder = new StringBuilder("# Level Penalty Table"); + resultBuilder.AppendLine(); + for (var difference = -5; difference <= 35; difference++) + { + const byte playerLevel = 50; + var monsterLevel = (byte)(playerLevel - difference); + resultBuilder.AppendLine( + $"- Player {difference,3} level(s) above the monster - XP: x{levelPenaltyService.GetExperiencePenalty(playerLevel, monsterLevel)} Gold: x{levelPenaltyService.GetGoldPenalty(playerLevel, monsterLevel)}"); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + + [TestMethod] + public void RarityPointDocumentation() + { + var rarityPointService = new RarityPointService.RarityPointService(); + + var resultBuilder = new StringBuilder("# Rarity Point Table"); + for (sbyte rarity = -2; rarity <= 8; rarity++) + { + resultBuilder.AppendLine(); + resultBuilder.AppendLine($"## Rarity {rarity}"); + for (byte level = 0; level < 100; level += 5) + { + resultBuilder.AppendLine($"- Item level {level,2}-{level + 4,2} - Points: {rarityPointService.GetRarityPoint(rarity, level)}"); + } + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } + + [TestMethod] + public void SpecialistPointDocumentation() + { + var specialistPointService = new SpecialistPointService.SpecialistPointService(); + + var resultBuilder = new StringBuilder("# Specialist Point Table"); + resultBuilder.AppendLine(); + resultBuilder.AppendLine("## Points by level (+0)"); + for (byte level = 1; level < 100; level++) + { + resultBuilder.AppendLine($"- Level {level,2} - Points: {specialistPointService.GetSpecialistPoints(level, 0)}"); + } + + resultBuilder.AppendLine(); + resultBuilder.AppendLine("## Points at level 99 by upgrade"); + for (byte upgrade = 0; upgrade <= 15; upgrade++) + { + resultBuilder.AppendLine($"- +{upgrade,-2} - Points: {specialistPointService.GetSpecialistPoints(99, upgrade)}"); + } + + resultBuilder.AppendLine(); + resultBuilder.AppendLine("## Stat level by points spent"); + for (short points = 0; points <= 410; points += 5) + { + resultBuilder.AppendLine( + $"- {points,3} points - Attack: {specialistPointService.GetAttackLevel(points)} Defence: {specialistPointService.GetDefenceLevel(points)} Element: {specialistPointService.GetElementLevel(points)} HP/MP: {specialistPointService.GetHpMpLevel(points)}"); + } + + for (short points = 1; points <= 420; points++) + { + Assert.IsTrue(specialistPointService.GetAttackLevel(points) >= specialistPointService.GetAttackLevel((short)(points - 1))); + Assert.IsTrue(specialistPointService.GetDefenceLevel(points) >= specialistPointService.GetDefenceLevel((short)(points - 1))); + Assert.IsTrue(specialistPointService.GetElementLevel(points) >= specialistPointService.GetElementLevel((short)(points - 1))); + Assert.IsTrue(specialistPointService.GetHpMpLevel(points) >= specialistPointService.GetHpMpLevel((short)(points - 1))); + } + + Approvals.Verify(WriterFactory.CreateTextWriter(resultBuilder.ToString(), "md")); + } } }