Extract the experience of the player and make it extensible - #882
Merged
Conversation
Moves the experience and leveling into a PlayerExperience component and introduces the plugin points around it: - IExperienceCalculationPlugIn can modify the amount of experience which is gained for a kill. It's called after the configured rates and the map multiplier have been applied and before the random multiplier, where factors compose independently of the plugin order. - IPlayerGainedExperiencePlugIn is called after the experience was added. The pet experience, which was hardcoded in the player, is now the PetExperiencePlugIn on this point. - ICharacterMasterLevelUpPlugIn is the counterpart of the existing ICharacterLevelUpPlugIn for the master level, which had no plugin point at all. CalculateExpAfterKill becomes CalculateExpAfterKillAsync, because the calculation calls a plugin point now. It had no callers outside of the player. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5grV5oveZNhhjK1mazM2B
The mock character class of the PlayerTestHelper didn't define the master level as stat attribute, unlike every real character class, so a master level up didn't stick and couldn't be asserted in tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5grV5oveZNhhjK1mazM2B
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of the
Playerrefactoring (docs/PlayerRefactoringPlan.md). The experience and leveling move into aPlayerExperiencecomponent, and the parts of it which are game rules rather than mechanism become plugin points.New plugin points
IExperienceCalculationPlugInIPlayerGainedExperiencePlugInICharacterMasterLevelUpPlugInICharacterLevelUpPlugInfor the master level, which had no plugin point at allSince plugin points can't return values,
IExperienceCalculationPlugInuses a mutableExperienceCalculationArgs, following theSpeedHackCheckEventArgsprecedent.Pet experience is no longer hardcoded in
Player. The 20 % share, the split between riding and attacking pet, and the pet level-ups are nowPetExperiencePlugInonIPlayerGainedExperiencePlugIn. It is active by default (noPlugInConfigurationentry means active), so existing installations keep the current behavior, and a server that wants a different pet progression can replace it.One deviation from the plan
The plan proposed splitting the whole multiplier chain of
CalculateExpAfterKillinto individual plugins (map multiplier, bonus rate, random multipliers). I did not do that: plugins are unordered, and the random multiplier truncates toint, so it has to run last — as competing plugins their result would depend on the discovery order.Instead the rates and the map multiplier stay in the component, and the plugin point runs after them and before the randomization, where factors compose independently of the order. The plan is updated with this reasoning. If plugin ordering (§3.1a) is implemented later, the chain can still be split.
Breaking change
CalculateExpAfterKillbecomesCalculateExpAfterKillAsync, because the calculation calls a plugin point now. It had no callers outside ofPlayer.Player.cs: 2,269 → 2,058 lines (3,098 before this refactoring started).
Tests
New
ExperiencePlugInTestswith five cases: the calculation plugin changes the calculated and the actually granted experience, the gain is reported with the right amount and kind, nothing is reported when no experience is gained, and a master level up reaches its plugin point.The mock character class of
PlayerTestHelperalso gets the master level as a stat attribute, like every real character class has it. Without it, a master level up didn't stick in tests and couldn't be asserted.Full suite: 716 of 716 pass, solution builds with no errors.