Skip to content

Extract the experience of the player and make it extensible - #882

Merged
sven-n merged 2 commits into
masterfrom
claude/player-class-refactor-plan-aha5v2
Aug 16, 2026
Merged

Extract the experience of the player and make it extensible#882
sven-n merged 2 commits into
masterfrom
claude/player-class-refactor-plan-aha5v2

Conversation

@sven-n

@sven-n sven-n commented Aug 15, 2026

Copy link
Copy Markdown
Member

Phase 3 of the Player refactoring (docs/PlayerRefactoringPlan.md). The experience and leveling move into a PlayerExperience component, and the parts of it which are game rules rather than mechanism become plugin points.

New plugin points

Point Purpose
IExperienceCalculationPlugIn modify the amount of experience gained for a kill — events, party or VIP bonuses, seasonal rates
IPlayerGainedExperiencePlugIn react to a player gaining experience; this is where the pet experience now lives
ICharacterMasterLevelUpPlugIn the counterpart of the existing ICharacterLevelUpPlugIn for the master level, which had no plugin point at all

Since plugin points can't return values, IExperienceCalculationPlugIn uses a mutable ExperienceCalculationArgs, following the SpeedHackCheckEventArgs precedent.

Pet experience is no longer hardcoded in Player. The 20 % share, the split between riding and attacking pet, and the pet level-ups are now PetExperiencePlugIn on IPlayerGainedExperiencePlugIn. It is active by default (no PlugInConfiguration entry 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 CalculateExpAfterKill into individual plugins (map multiplier, bonus rate, random multipliers). I did not do that: plugins are unordered, and the random multiplier truncates to int, 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

CalculateExpAfterKill becomes CalculateExpAfterKillAsync, because the calculation calls a plugin point now. It had no callers outside of Player.

Player.cs: 2,269 → 2,058 lines (3,098 before this refactoring started).

Tests

New ExperiencePlugInTests with 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 PlayerTestHelper also 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.

claude added 2 commits August 15, 2026 21:03
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
@sven-n
sven-n merged commit 7f88995 into master Aug 16, 2026
2 of 3 checks passed
@sven-n
sven-n deleted the claude/player-class-refactor-plan-aha5v2 branch August 16, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants