Skip to content

Add mastery commands for player management and debugging - #562

Merged
AngeloTadeucci merged 1 commit into
MS2Community:masterfrom
Tandashi:feat/mastery_player_command
Aug 25, 2025
Merged

Add mastery commands for player management and debugging#562
AngeloTadeucci merged 1 commit into
MS2Community:masterfrom
Tandashi:feat/mastery_player_command

Conversation

@Tandashi

@Tandashi Tandashi commented Aug 25, 2025

Copy link
Copy Markdown
Contributor

Overview

To better debug and test different mastery related features a new subcommand has been added to the player commands. This new subcommand allows for changing mastery exp/levels of the current player.

Usage:
/player mastery <"exp"/"level"> <mastery> <exp/level>

Masteries:

  • Fishing
  • Music
  • Mining
  • Gathering
  • Breeding
  • Farming
  • Blacksmithing
  • Engraving
  • Alchemist
  • Cooking
  • PetTaming

Examples:

# Sets the Fishing Mastery to a total of 20 exp
/player mastery exp Fishing 20

# Sets the Fishing Mastery to level 2
/player mastery level Fishing 2

Not sure if this is needed or others find this helpful. If not feel free to close this Pull Request :)

Summary by CodeRabbit

  • New Features
    • Added player mastery commands to adjust character progression.
    • Supports setting mastery experience directly per mastery type.
    • Supports setting mastery level, automatically calculating the appropriate experience.
    • Provides clear feedback on success or errors via command responses.

To better debug and test different mastery related features a new subcommand has been
added to the player commands. This new subcommand allows for changing mastery exp/levels
of the current player.

**Usage:**
`/player mastery <"exp"/"level"> <mastery> <exp/level>`

**Masteries**:
- `Fishing`
- `Music`
- `Mining`
- `Gathering`
- `Breeding`
- `Farming`
- `Blacksmithing`
- `Engraving`
- `Alchemist`
- `Cooking`
- `PetTaming`

**Examples:**
```
# Sets the Fishing Mastery to a total of 20 exp
/player mastery exp Fishing 20

# Sets the Fishing Mastery to level 2
/player mastery level Fishing 2
```
@coderabbitai

coderabbitai Bot commented Aug 25, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new Mastery command group under PlayerCommand with two subcommands to set mastery experience directly or via level-derived experience. Commands update session mastery values; the level variant computes experience from TableMetadata’s MasteryRewardTable thresholds. Errors write exception messages and return non-zero exit codes.

Changes

Cohort / File(s) Summary
Player commands
Maple2.Server.Game/Commands/PlayerCommand.cs
Added private nested MasteryCommand with mastery exp and mastery level subcommands. Wires into PlayerCommand via AddCommand. exp sets session.Mastery[mastery] directly. level looks up threshold in session.TableMetadata.MasteryRewardTable.Entries to derive exp, then sets mastery. Both handle SystemException and set ExitCode.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as Admin/User
  participant PC as PlayerCommand
  participant MC as MasteryCommand
  participant Sess as Session
  participant Meta as TableMetadata

  Admin->>PC: mastery exp <type> <exp>
  PC->>MC: Route to MasteryExpCommand
  MC->>Sess: Set Mastery[<type>] = <exp>
  MC-->>Admin: ExitCode 0 or error (message on console)

  Admin->>PC: mastery level <type> <level>
  PC->>MC: Route to MasteryLevelCommand
  MC->>Meta: Get MasteryRewardTable.Entries
  MC->>MC: Find highest threshold <= level
  MC->>Sess: Set Mastery[<type>] = derivedExp
  MC-->>Admin: ExitCode 0 or error (message on console)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • AngeloTadeucci
  • Zintixx

Poem

I twitch my ears at leveling lore,
Hop-hop! New mastery at the core—
EXP or level, pick your way,
Thresholds guide the bunny’s play.
With tidy commands and exits neat,
I stamp approval with my lucky feet. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
Maple2.Server.Game/Commands/PlayerCommand.cs (2)

37-46: Argument description says “to add” but code sets an absolute value.

The handler assigns the mastery experience directly, not incrementally. Update the help text to prevent confusion.

Apply this diff to clarify the argument description:

-                var exp = new Argument<int>("exp", "Experience points to add.");
+                var exp = new Argument<int>("exp", "Absolute mastery experience (total).");

48-56: Validate non-negative exp before assignment.

Setting negative mastery exp can corrupt state or trigger underflow in downstream logic. Reject negative values early.

Apply this diff inside the handler:

             private void Handle(InvocationContext ctx, MasteryType masteryType, int exp) {
                 try {
+                    if (exp < 0) {
+                        ctx.Console.Error.WriteLine("Exp must be >= 0.");
+                        ctx.ExitCode = 1;
+                        return;
+                    }
                     session.Mastery[masteryType] = exp;
                     ctx.ExitCode = 0;
                 } catch (SystemException ex) {
                     ctx.Console.Error.WriteLine(ex.Message);
                     ctx.ExitCode = 1;
                 }
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between caa3bb3 and 5e17132.

📒 Files selected for processing (1)
  • Maple2.Server.Game/Commands/PlayerCommand.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Maple2.Server.Game/Commands/PlayerCommand.cs (3)
Maple2.Server.Game/Session/GameSession.cs (3)
  • GameSession (36-852)
  • GameSession (114-124)
  • GameSession (742-742)
Maple2.Model/Game/User/Mastery.cs (1)
  • Mastery (7-35)
Maple2.File.Ingest/Mapper/TableMapper.cs (1)
  • MasteryRewardTable (764-778)
🔇 Additional comments (3)
Maple2.Server.Game/Commands/PlayerCommand.cs (3)

25-26: Mastery command registration is well-integrated.

Hooking MasteryCommand into PlayerCommand alongside the existing subcommands is consistent with the file’s structure and permissioning (AdminPermissions.PlayerCommands).


28-33: Good encapsulation via nested command group.

Keeping MasteryExpCommand and MasteryLevelCommand under a dedicated MasteryCommand mirrors the structure of the other player subcommands and keeps the surface area minimal.


48-51: Awaiting details from MasteryManager setter block for full context.

Comment on lines +40 to +46
var masteryCode = new Argument<MasteryType>("mastery", "MasteryType of the player.");
var exp = new Argument<int>("exp", "Experience points to add.");

AddArgument(masteryCode);
AddArgument(exp);
this.SetHandler<InvocationContext, MasteryType, int>(Handle, masteryCode, exp);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify MasteryType names vs PR examples; consider documenting accepted values or mapping synonyms.

Your PR text lists “Music, Gathering, Breeding, Engraving” but Model/Mastery properties use “Instrument, Foraging, Ranching, Handicrafts.” If the enum uses the latter names, System.CommandLine will reject the former. Either:

  • Update PR examples to match the actual enum names; and/or
  • Accept common synonyms by parsing a string argument and mapping to MasteryType.

Run this to confirm the enum members used at runtime:

If synonyms are needed and you want a lightweight change, I can propose a small mapper without touching other callsites. Want me to draft it?

Also applies to: 65-71


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Show the MasteryType enum declaration and members
rg -nPU '(?s)\benum\s+MasteryType\s*{.*?}' -C2 --glob '!**/bin/**' --glob '!**/obj/**' | sed -n '/enum MasteryType/,/}/p'

Length of output: 143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "🔍 Searching for any references to MasteryType in C# files..."
rg -n --glob '*.cs' '\bMasteryType\b' -C2 --glob '!**/bin/**' --glob '!**/obj/**' || true

echo
echo "🔍 Looking for a file named MasteryType.cs..."
fd --type f MasteryType.cs || true

# If it exists at root or in a discovered path, dump its declaration
if [ -f MasteryType.cs ]; then
  echo
  echo "📄 Contents of MasteryType.cs (first 200 lines):"
  sed -n '1,200p' MasteryType.cs
fi

Length of output: 21794


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Dump the contents of the MasteryType enum file for inspection
echo "📄 Showing the MasteryType enum declaration:"
sed -n '1,200p' Maple2.Model/Enum/MasteryType.cs

Length of output: 451


Document or Align MasteryType Inputs

Please ensure that the command-line examples and handlers use the exact enum identifiers defined in Maple2.Model.Enum.MasteryType (Unknown, Fishing, Music, Mining, Gathering, Breeding, Farming, Blacksmithing, Engraving, Alchemist, Cooking, PetTaming). Currently, System.CommandLine will only accept those names (e.g. “Music”) and numeric values, not the game-facing synonyms (“Instrument”, “Foraging”, “Ranching”, “Handicrafts”).

• Update your PR’s documentation/examples in

  • Maple2.Server.Game/Commands/PlayerCommand.cs (constructor overloads at lines 40–46 and 65–71)
    to list the actual enum values, for instance:
- // Example: mastery Music exp 100
+ // Example: mastery Music exp 100

• If you’d like to support synonyms, replace the raw Argument<MasteryType> with Argument<string> (or keep both) and map input strings to the correct enum before handling. For example:

- var masteryCode = new Argument<MasteryType>("mastery", "MasteryType of the player.");
+ var masteryInput = new Argument<string>("mastery", "MasteryType (e.g. Music, Instrument).");
  var exp = new Argument<int>("exp", "Experience points to add.");
   AddArgument(masteryInput);
   AddArgument(exp);
-  this.SetHandler<InvocationContext, MasteryType, int>(Handle, masteryCode, exp);
+  this.SetHandler<InvocationContext, string, int>((ctx, input, e) =>
+  {
+    var mastery = ParseMastery(input);
+    Handle(ctx, mastery, e);
+  }, masteryInput, exp);

+ static MasteryType ParseMastery(string s) =>
+   s.ToLowerInvariant() switch
+   {
+     "music" or "instrument" => MasteryType.Music,
+     "gathering" or "foraging" => MasteryType.Gathering,
+     "breeding" or "ranching" => MasteryType.Breeding,
+     "engraving" or "handicrafts" => MasteryType.Engraving,
+     _ when Enum.TryParse<MasteryType>(s, true, out var m) => m,
+     _ => throw new ArgumentException($"Unknown mastery '{s}'", nameof(s))
+   };

Let me know if you’d like a fuller PR to add this mapper.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Maple2.Server.Game/Commands/PlayerCommand.cs around lines 40–46 (and also
apply to the constructor overloads at lines 65–71), the command currently
declares Argument<MasteryType> which leads to confusion because
System.CommandLine only accepts the enum identifiers (Unknown, Fishing, Music,
Mining, Gathering, Breeding, Farming, Blacksmithing, Engraving, Alchemist,
Cooking, PetTaming) and numeric values; update the argument help text/examples
to list those exact enum names, or change the signature to accept
Argument<string> (or add a second string argument) and perform a mapping from
input synonyms (e.g., "Instrument" -> "Music", "Foraging" -> "Gathering",
"Ranching" -> "Breeding", "Handicrafts" -> "Blacksmithing") to the MasteryType
enum before invoking Handle; finally ensure invalid inputs produce a clear error
and update the constructor documentation/examples accordingly.

Comment on lines +73 to +80
private void Handle(InvocationContext ctx, MasteryType masteryType, int level) {
try {
int exp = 0;
if (session.TableMetadata.MasteryRewardTable.Entries.TryGetValue(masteryType, out IReadOnlyDictionary<int, MasteryRewardTable.Entry>? masteryRewardMetadata)) {
exp = masteryRewardMetadata.OrderByDescending(mastery => mastery.Key).FirstOrDefault(mastery => level >= mastery.Key).Value.Value;
}
session.Mastery[masteryType] = exp;
ctx.ExitCode = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid NullReference when no threshold ≤ level; handle missing metadata and invalid levels.

If there’s no mastery grade ≤ requested level, FirstOrDefault returns a default KVP and .Value.Value will throw. Also, missing mastery metadata currently results in silently setting exp=0. Handle both explicitly and fail fast.

Apply this diff to make the computation robust and user-friendly:

             private void Handle(InvocationContext ctx, MasteryType masteryType, int level) {
                 try {
-                    int exp = 0;
-                    if (session.TableMetadata.MasteryRewardTable.Entries.TryGetValue(masteryType, out IReadOnlyDictionary<int, MasteryRewardTable.Entry>? masteryRewardMetadata)) {
-                        exp = masteryRewardMetadata.OrderByDescending(mastery => mastery.Key).FirstOrDefault(mastery => level >= mastery.Key).Value.Value;
-                    }
-                    session.Mastery[masteryType] = exp;
-                    ctx.ExitCode = 0;
+                    if (!session.TableMetadata.MasteryRewardTable.Entries.TryGetValue(masteryType, out IReadOnlyDictionary<int, MasteryRewardTable.Entry>? masteryRewardMetadata)) {
+                        ctx.Console.Error.WriteLine($"Mastery metadata not found for {masteryType}.");
+                        ctx.ExitCode = 1;
+                        return;
+                    }
+
+                    if (level < 1) {
+                        ctx.Console.Error.WriteLine($"Invalid mastery level: {level}. Must be >= 1.");
+                        ctx.ExitCode = 1;
+                        return;
+                    }
+
+                    // Highest grade threshold <= requested level
+                    int applicableGrade = masteryRewardMetadata.Keys.Where(k => k <= level).DefaultIfEmpty(-1).Max();
+                    if (applicableGrade < 0 || !masteryRewardMetadata.TryGetValue(applicableGrade, out var entry)) {
+                        ctx.Console.Error.WriteLine($"No mastery grade threshold found for level {level} ({masteryType}).");
+                        ctx.ExitCode = 1;
+                        return;
+                    }
+
+                    session.Mastery[masteryType] = entry.Value;
+                    ctx.ExitCode = 0;
                 } catch (SystemException ex) {
                     ctx.Console.Error.WriteLine(ex.Message);
                     ctx.ExitCode = 1;
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void Handle(InvocationContext ctx, MasteryType masteryType, int level) {
try {
int exp = 0;
if (session.TableMetadata.MasteryRewardTable.Entries.TryGetValue(masteryType, out IReadOnlyDictionary<int, MasteryRewardTable.Entry>? masteryRewardMetadata)) {
exp = masteryRewardMetadata.OrderByDescending(mastery => mastery.Key).FirstOrDefault(mastery => level >= mastery.Key).Value.Value;
}
session.Mastery[masteryType] = exp;
ctx.ExitCode = 0;
private void Handle(InvocationContext ctx, MasteryType masteryType, int level) {
try {
if (!session.TableMetadata.MasteryRewardTable.Entries.TryGetValue(masteryType, out IReadOnlyDictionary<int, MasteryRewardTable.Entry>? masteryRewardMetadata)) {
ctx.Console.Error.WriteLine($"Mastery metadata not found for {masteryType}.");
ctx.ExitCode = 1;
return;
}
if (level < 1) {
ctx.Console.Error.WriteLine($"Invalid mastery level: {level}. Must be >= 1.");
ctx.ExitCode = 1;
return;
}
// Highest grade threshold <= requested level
int applicableGrade = masteryRewardMetadata.Keys
.Where(k => k <= level)
.DefaultIfEmpty(-1)
.Max();
if (applicableGrade < 0
|| !masteryRewardMetadata.TryGetValue(applicableGrade, out var entry)) {
ctx.Console.Error.WriteLine($"No mastery grade threshold found for level {level} ({masteryType}).");
ctx.ExitCode = 1;
return;
}
session.Mastery[masteryType] = entry.Value;
ctx.ExitCode = 0;

@AngeloTadeucci
AngeloTadeucci merged commit 6dbc50a into MS2Community:master Aug 25, 2025
4 checks passed
@Tandashi
Tandashi deleted the feat/mastery_player_command branch August 25, 2025 21:25
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