Skip to content

Conversation

@kirushkinx
Copy link
Contributor

@kirushkinx kirushkinx commented Aug 24, 2025

Usage example:

interface:
  glass-material: PURPLE_STAINED_GLASS_PANE
  main-color: <dark_purple> # Menu title color (MiniMessage format: <blue>, <red>, <green>, etc.)
2025-08-25_01 15 02

Summary by CodeRabbit

  • New Features
    • Added runtime theme support: configurable main UI color and filler pane material via config; menus and titles now use the configured color.
  • Refactor
    • Consolidated filler-pane creation into a single helper for consistent visuals.
    • Styling initialized earlier at startup so theme applies across commands, menus, and listeners.

@coderabbitai
Copy link

coderabbitai bot commented Aug 24, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a StyleManager singleton and config keys, initializes it during plugin startup, and refactors GUI, commands, and listeners to use StyleManager for title colors and glass-pane material; adds ItemUtil.createGlassPane(). No public API signatures changed; control flow is largely unchanged.

Changes

Cohort / File(s) Summary
Styling system introduction
src/main/java/dev/noah/perplayerkit/util/StyleManager.java, src/main/resources/config.yml
Adds StyleManager singleton that loads interface.glass-material and interface.main-color from config (MiniMessage → legacy color conversion) and exposes getGlassMaterial() and getPrimaryColor(). Adds config defaults.
Plugin boot wiring
src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
Instantiates StyleManager in onEnable() after config load and before other managers, changing startup ordering to initialize style state early.
UI theming refactor (menus, commands, listeners)
src/main/java/dev/noah/perplayerkit/gui/GUI.java, src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java, src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java, src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java, src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java
Replaces hard-coded ChatColor.BLUE titles/strings with StyleManager.get().getPrimaryColor() and updates title-matching logic in listeners. Adjusts imports; control flow and behavior otherwise unchanged.
Utility enhancement
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java
Adds public static ItemStack createGlassPane() that returns a pane using StyleManager.get().getGlassMaterial().
Pane usage consolidation
src/main/java/dev/noah/perplayerkit/gui/GUI.java, src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java
Replaces explicit BLUE_STAINED_GLASS_PANE filler items with ItemUtil.createGlassPane() calls across menus.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Srv as Server
  participant P as PerPlayerKit
  participant Cfg as ConfigManager
  participant SM as StyleManager
  participant UI as GUI/Commands/Listeners

  Srv->>P: onEnable()
  P->>Cfg: loadConfig()
  P->>SM: new StyleManager(plugin)
  activate SM
  SM->>SM: loadConfig() (read interface.glass-material, interface.main-color)
  deactivate SM
  P->>UI: register/init remaining components
  note right of UI: Components now call StyleManager.get() for colors/materials
Loading
sequenceDiagram
  autonumber
  participant Player
  participant Cmd as Command Executor
  participant SM as StyleManager
  participant UI as GUI
  participant It as ItemUtil

  Player->>Cmd: execute command (e.g., /ec, /rg)
  Cmd->>SM: getPrimaryColor()
  Cmd->>UI: open inventory(title with primary color)
  UI->>It: createGlassPane()
  It->>SM: getGlassMaterial()
  It-->>UI: ItemStack pane
  UI-->>Player: show inventory with styled title and panes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I hopped through menus, coat of blue,
I found a config, two tokens true.
A pane, a title, colors sing—
My tiny paws give style a ring.
Thump! — the UI gleams anew. 🐇✨

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.


📜 Recent 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 82cb9a4 and 075bc91.

📒 Files selected for processing (6)
  • src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java (3 hunks)
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java (14 hunks)
  • src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java (6 hunks)
  • src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1 hunks)
✨ 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/dev/noah/perplayerkit/PerPlayerKit.java (1)

21-45: Static StyleManager.get() Calls Occurring at Class Initialization Must Be Deferred

The only early calls to StyleManager.get() happen in static field initializers in RegearCommand.java, which will run as soon as the class is loaded—potentially before your plugin’s onEnable() guard is in place. All other StyleManager.get() invocations occur inside methods or event handlers (i.e., after onEnable()).

• src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java
– Lines 29–30:
java public static final ItemStack REGEAR_SHULKER_ITEM = ItemUtil.createItem( Material.WHITE_SHULKER_BOX, 1, StyleManager.get().getMainColor() + "Regear Shulker", …); public static final ItemStack REGEAR_SHELL_ITEM = ItemUtil.createItem( Material.SHULKER_SHELL, 1, StyleManager.get().getMainColor() + "Regear Shell", …);
These run at class‐load time, before onEnable().

Recommendation: Move these static ItemStack initializations into a setup method—e.g., inside your plugin’s onEnable() or lazily in the command constructor—so that StyleManager.get() is only invoked after the guard is active.

🧹 Nitpick comments (10)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (3)

33-38: Harden material parsing (null-safety, case-insensitive, and log fallback).

Material.valueOf(...) throws on null or invalid input; only IllegalArgumentException is caught. Make parsing null-safe, accept lowercase config, and log the fallback so admins know why defaults were used.

@@
-    public void loadConfig() {
-        try {
-            this.glassMaterial = Material.valueOf(plugin.getConfig().getString("interface.glass-material", "BLUE_STAINED_GLASS_PANE"));
-        } catch (IllegalArgumentException e) {
-            this.glassMaterial = Material.BLUE_STAINED_GLASS_PANE;
-        }
+    public void loadConfig() {
+        String raw = plugin.getConfig().getString("interface.glass-material", "BLUE_STAINED_GLASS_PANE");
+        try {
+            this.glassMaterial = Material.valueOf(raw.toUpperCase(java.util.Locale.ROOT));
+        } catch (RuntimeException e) {
+            plugin.getLogger().warning("[StyleManager] Invalid interface.glass-material: '" + raw
+                    + "'. Falling back to BLUE_STAINED_GLASS_PANE");
+            this.glassMaterial = Material.BLUE_STAINED_GLASS_PANE;
+        }

29-31: Guard the singleton accessor to fail fast on misuse.

If any code calls StyleManager.get() before onEnable() initializes it, you'll hit a null dereference elsewhere. A defensive guard improves diagnostics.

-    public static StyleManager get() {
-        return instance;
-    }
+    public static StyleManager get() {
+        if (instance == null) {
+            throw new IllegalStateException("StyleManager not initialized yet. Initialize it in onEnable().");
+        }
+        return instance;
+    }

16-26: Minor: consider preventing re-initialization.

Reassigning instance on each constructor call can mask accidental multiple instantiations. Optionally reject subsequent constructions or provide a static init(Plugin) to enforce one-time init.

src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)

58-60: Graceful fallback if StyleManager is unavailable.

If createGlassPane() is called too early or StyleManager initialization fails, this will NPE. Use a safe fallback to a default material.

-    public static ItemStack createGlassPane() {
-        return createItem(StyleManager.get().getGlassMaterial(), 1, " ");
-    }
+    public static ItemStack createGlassPane() {
+        Material mat;
+        try {
+            mat = StyleManager.get().getGlassMaterial();
+        } catch (IllegalStateException ex) {
+            mat = Material.BLUE_STAINED_GLASS_PANE; // safe default
+        }
+        return createItem(mat, 1, " ");
+    }
src/main/java/dev/noah/perplayerkit/PerPlayerKit.java (1)

55-67: Wire style reload into your existing reload flow.

If you support config reloads, also call StyleManager.get().loadConfig() so style changes apply without a full restart.

Would you like me to add a reload hook in your reload command to call StyleManager.get().loadConfig()?

src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java (1)

44-44: Make title matching color-agnostic to support any configured color.

Relying on the exact legacy color prefix is brittle (and breaks if color parsing fails or is changed). Strip colors before the contains check.

-                    if (view.getTitle().contains(StyleManager.get().getMainColor() + p.getName() + "'s Kits")) {
+                    if (net.md_5.bungee.api.ChatColor.stripColor(view.getTitle()).contains(p.getName() + "'s Kits")) {
src/main/resources/config.yml (1)

56-58: Config keys look good; add a brief hint for valid values.

A short note can reduce misconfigurations (e.g., lowercase materials, hex colors). Optional doc improvement.

 interface:
-  glass-material: BLUE_STAINED_GLASS_PANE
-  main-color: "<blue>" # Plugin main color (MiniMessage format: <blue>, <red>, <green>, etc.)
+  glass-material: BLUE_STAINED_GLASS_PANE   # Bukkit Material enum (e.g., BLUE_STAINED_GLASS_PANE). Case-insensitive.
+  main-color: "<blue>"                      # MiniMessage color (e.g., <blue>, <red>, <green>, hex: <#AA66FF>)
src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java (1)

55-55: Title now honors theme color; consider resilience to config reloads.

Using StyleManager for the title is correct. Note that if interface.main-color is reloaded while a menu is open, listeners that parse titles should not depend on the live color value to match (see my comment on KitMenuCloseListener for a robust approach using ChatColor.stripColor).

Would you like me to wire a lightweight “/ppk style reload” that safely refreshes open menus or at least warns players?

src/main/java/dev/noah/perplayerkit/gui/GUI.java (2)

87-88: Micro-optimization: reuse a single filler ItemStack per menu render.

Repeatedly calling createGlassPane() in tight loops allocates many identical ItemStacks. Cache one per method and reuse in the loop; it’s cheaper and reduces allocations.

Example pattern you can apply to these loops:

-        for (int i = 41; i < 54; i++) {
-            menu.getSlot(i).setItem(ItemUtil.createGlassPane());
-        }
+        final ItemStack fill = ItemUtil.createGlassPane();
+        for (int i = 41; i < 54; i++) {
+            menu.getSlot(i).setItem(fill);
+        }

Repeat similarly in the other loops in this file.

Also applies to: 120-121, 144-145, 148-149, 183-184, 216-217, 220-221, 254-255, 279-280, 308-309, 355-356, 382-383


25-26: Consistency nit: mix of static import vs. qualified call for createGlassPane.

You use both createGlassPane() (static import) and ItemUtil.createGlassPane(). Prefer one style across the file for consistency; static import reduces verbosity.

Also applies to: 46-47

📜 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 820c6a3 and 82cb9a4.

📒 Files selected for processing (9)
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java (3 hunks)
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java (14 hunks)
  • src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java (6 hunks)
  • src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java (2 hunks)
  • src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1 hunks)
  • src/main/resources/config.yml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java (1)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java (1)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/PerPlayerKit.java (1)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java (2)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)
  • ItemUtil (33-93)
src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java (2)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)
  • ItemUtil (33-93)
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
src/main/java/dev/noah/perplayerkit/gui/GUI.java (2)
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)
  • ItemUtil (33-93)
src/main/java/dev/noah/perplayerkit/util/StyleManager.java (1)
  • StyleManager (16-60)
🔇 Additional comments (6)
src/main/java/dev/noah/perplayerkit/gui/ItemUtil.java (1)

21-21: Nice addition; reduces duplication and centralizes theming.

The new helper cleanly routes filler panes through config. LGTM.

Also applies to: 58-60

src/main/java/dev/noah/perplayerkit/PerPlayerKit.java (1)

66-67: Good placement of StyleManager initialization.

Initialized after config load and before GUI/commands use it. This ordering is correct.

src/main/java/dev/noah/perplayerkit/listeners/KitRoomSaveListener.java (1)

44-44: Dependent on StyleManager’s legacy output.

If you keep the current approach, it depends on StyleManager.get().getMainColor() returning a legacy (§) code. The fix in StyleManager ensures this.

After updating StyleManager, please verify this flow by opening the Kit Room and confirming saves trigger as expected with a non-default color (e.g., "<dark_purple>").

src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java (1)

53-53: Good move: centralize filler panes via ItemUtil.createGlassPane().

This keeps visuals consistent with the configured glass material and avoids hard-coding.

src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java (1)

196-196: LGTM: inventory title now reflects the configured main color.

This keeps the regear UI aligned with the rest of the themed menus.

src/main/java/dev/noah/perplayerkit/gui/GUI.java (1)

66-68: Titles now consistently use StyleManager — good alignment with the theming system.

  • Public kit room, per-kit, EC, inspect, main, and kit-room titles all honor the configured color.
  • This pairs well with the listener changes once title parsing is made color-agnostic (see my comment on KitMenuCloseListener).

No functional issues spotted here.

After you update the listeners to strip colors, please sanity-check that opening/closing each menu works after a color change without restarting the server.

Also applies to: 652-678

Comment on lines 29 to 31
public static final ItemStack REGEAR_SHULKER_ITEM = ItemUtil.createItem(Material.WHITE_SHULKER_BOX, 1, StyleManager.get().getMainColor() + "Regear Shulker", "&7● Restocks Your Kit", "&7● Use " + StyleManager.get().getMainColor() + "/rg &7to get another regear shulker");
public static final ItemStack REGEAR_SHELL_ITEM = ItemUtil.createItem(Material.SHULKER_SHELL, 1, StyleManager.get().getMainColor() + "Regear Shell", "&7● Restocks Your Kit", "&7● Click to use!");

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid static, config-dependent ItemStack constants; tag items with PDC and build on demand.

Problems:

  • Class-load order risk: StyleManager.get() may be null when these static fields initialize.
  • Stale config: after changing interface.main-color, equals() comparisons against REGEAR_SHULKER_ITEM/REGEAR_SHELL_ITEM will fail for old/new items.
  • equals(ItemStack) is brittle (meta or minor changes break equality).

Refactor to:

  • Build the items on demand using the current StyleManager color, and
  • Identify them via a PersistentDataContainer tag instead of equals().

Apply this local change (replacing the static fields with helpers):

-    public static final ItemStack REGEAR_SHULKER_ITEM = ItemUtil.createItem(Material.WHITE_SHULKER_BOX, 1, StyleManager.get().getMainColor() + "Regear Shulker", "&7● Restocks Your Kit", "&7● Use " + StyleManager.get().getMainColor() + "/rg &7to get another regear shulker");
-    public static final ItemStack REGEAR_SHELL_ITEM = ItemUtil.createItem(Material.SHULKER_SHELL, 1, StyleManager.get().getMainColor() + "Regear Shell", "&7● Restocks Your Kit", "&7● Click to use!");
+    // Build items using current theme; identify via PDC
+    private ItemStack buildRegearShulkerItem() {
+        ItemStack item = ItemUtil.createItem(
+                Material.WHITE_SHULKER_BOX,
+                1,
+                StyleManager.get().getMainColor() + "Regear Shulker",
+                "&7● Restocks Your Kit",
+                "&7● Use " + StyleManager.get().getMainColor() + "/rg &7to get another regear shulker");
+        org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
+        if (meta != null) {
+            meta.getPersistentDataContainer().set(new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
+                    org.bukkit.persistence.PersistentDataType.STRING, "shulker");
+            item.setItemMeta(meta);
+        }
+        return item;
+    }
+
+    private ItemStack buildRegearShellItem() {
+        ItemStack item = ItemUtil.createItem(
+                Material.SHULKER_SHELL,
+                1,
+                StyleManager.get().getMainColor() + "Regear Shell",
+                "&7● Restocks Your Kit",
+                "&7● Click to use!");
+        org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
+        if (meta != null) {
+            meta.getPersistentDataContainer().set(new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
+                    org.bukkit.persistence.PersistentDataType.STRING, "shell");
+            item.setItemMeta(meta);
+        }
+        return item;
+    }
+
+    private boolean isRegearItemOfType(ItemStack item, String type) {
+        if (item == null) return false;
+        org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
+        if (meta == null) return false;
+        String val = meta.getPersistentDataContainer().get(new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
+                org.bukkit.persistence.PersistentDataType.STRING);
+        return type.equals(val);
+    }

And update usages elsewhere in this class (outside this hunk):

  • Give item: replace player.getInventory().setItem(slot, REGEAR_SHULKER_ITEM) with buildRegearShulkerItem().
  • Open shulker GUI: inventory.setItem(13, buildRegearShellItem()).
  • Placement guard: replace if (!event.getItemInHand().equals(REGEAR_SHULKER_ITEM)) with if (!isRegearItemOfType(event.getItemInHand(), "shulker")) return;.
  • Click guard: replace if (!currentItem.equals(REGEAR_SHELL_ITEM)) with if (!isRegearItemOfType(currentItem, "shell")) { ... }.

Result: no NPE risk at class load; items survive theme changes; identification is reliable.

I can push a follow-up commit with these changes if you’d like.

📝 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
public static final ItemStack REGEAR_SHULKER_ITEM = ItemUtil.createItem(Material.WHITE_SHULKER_BOX, 1, StyleManager.get().getMainColor() + "Regear Shulker", "&7● Restocks Your Kit", "&7● Use " + StyleManager.get().getMainColor() + "/rg &7to get another regear shulker");
public static final ItemStack REGEAR_SHELL_ITEM = ItemUtil.createItem(Material.SHULKER_SHELL, 1, StyleManager.get().getMainColor() + "Regear Shell", "&7● Restocks Your Kit", "&7● Click to use!");
// Build items using current theme; identify via PDC
private ItemStack buildRegearShulkerItem() {
ItemStack item = ItemUtil.createItem(
Material.WHITE_SHULKER_BOX,
1,
StyleManager.get().getMainColor() + "Regear Shulker",
"&7● Restocks Your Kit",
"&7● Use " + StyleManager.get().getMainColor() + "/rg &7to get another regear shulker");
org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.getPersistentDataContainer().set(
new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
org.bukkit.persistence.PersistentDataType.STRING,
"shulker"
);
item.setItemMeta(meta);
}
return item;
}
private ItemStack buildRegearShellItem() {
ItemStack item = ItemUtil.createItem(
Material.SHULKER_SHELL,
1,
StyleManager.get().getMainColor() + "Regear Shell",
"&7● Restocks Your Kit",
"&7● Click to use!");
org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.getPersistentDataContainer().set(
new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
org.bukkit.persistence.PersistentDataType.STRING,
"shell"
);
item.setItemMeta(meta);
}
return item;
}
private boolean isRegearItemOfType(ItemStack item, String type) {
if (item == null) return false;
org.bukkit.inventory.meta.ItemMeta meta = item.getItemMeta();
if (meta == null) return false;
String val = meta.getPersistentDataContainer().get(
new org.bukkit.NamespacedKey(plugin, "ppk-regear-type"),
org.bukkit.persistence.PersistentDataType.STRING
);
return type.equals(val);
}
🤖 Prompt for AI Agents
In src/main/java/dev/noah/perplayerkit/commands/RegearCommand.java around lines
29-31, remove the static config-dependent ItemStack constants and replace them
with on-demand builder methods that construct the regear shulker and shell using
StyleManager.get() at call time; tag the built items with a
PersistentDataContainer key/value (e.g., type="shulker" or "shell") for reliable
identity instead of equals(), then update usages in this class: replace
inventory setItem calls with buildRegearShulkerItem()/buildRegearShellItem(),
replace equality checks like event.getItemInHand().equals(REGEAR_SHULKER_ITEM)
with a helper isRegearItemOfType(item,"shulker"), and replace
currentItem.equals(REGEAR_SHELL_ITEM) with
isRegearItemOfType(currentItem,"shell"); ensure null checks for ItemStack and
ItemMeta before reading PDC.

Comment on lines 45 to 49
if (view.getTitle().contains(StyleManager.get().getMainColor() + "Kit: ")) {
Player p = (Player) e.getPlayer();
UUID uuid = p.getUniqueId();
int slot = Integer.parseInt(view.getTitle().replace(ChatColor.BLUE + "Kit: ", ""));
int slot = Integer.parseInt(view.getTitle().replace(StyleManager.get().getMainColor() + "Kit: ", ""));
ItemStack[] kit = new ItemStack[41];
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Make title parsing color-agnostic to survive theme changes and formatting variance.

Right now the code matches and extracts data using StyleManager.get().getMainColor() + "...". If interface.main-color is changed (or MiniMessage returns multi-code sequences), open menus will close with titles that no longer match the new prefix, and saves will silently skip. Strip colors before matching and parse based on plain-text prefixes.

Apply these diffs to the affected blocks:

@@
-                if (view.getTitle().contains(StyleManager.get().getMainColor() + "Kit: ")) {
+                String stripped = ChatColor.stripColor(view.getTitle());
+                if (stripped.startsWith("Kit: ")) {
                     Player p = (Player) e.getPlayer();
                     UUID uuid = p.getUniqueId();
-                    int slot = Integer.parseInt(view.getTitle().replace(StyleManager.get().getMainColor() + "Kit: ", ""));
+                    int slot = Integer.parseInt(stripped.substring("Kit: ".length()));
@@
-                if (view.getTitle().contains(StyleManager.get().getMainColor() + "Public Kit: ")) {
+                String stripped = ChatColor.stripColor(view.getTitle());
+                if (stripped.startsWith("Public Kit: ")) {
                     Player player = (Player) e.getPlayer();
-                    String publickit = view.getTitle().replace(StyleManager.get().getMainColor() + "Public Kit: ", "");
+                    String publickit = stripped.substring("Public Kit: ".length());
@@
-                if (view.getTitle().contains(StyleManager.get().getMainColor() + "Enderchest: ")) {
+                String stripped = ChatColor.stripColor(view.getTitle());
+                if (stripped.startsWith("Enderchest: ")) {
                     Player p = (Player) e.getPlayer();
                     UUID uuid = p.getUniqueId();
-                    int slot = Integer.parseInt(view.getTitle().replace(StyleManager.get().getMainColor() + "Enderchest: ", ""));
+                    int slot = Integer.parseInt(stripped.substring("Enderchest: ".length()));
@@
-                if (view.getTitle().contains(StyleManager.get().getMainColor() + "Inspecting ") && view.getTitle().contains("'s kit ")) {
+                String stripped = ChatColor.stripColor(view.getTitle());
+                if (stripped.startsWith("Inspecting ") && stripped.contains("'s kit ")) {
                     Player p = (Player) e.getPlayer();
@@
-                    String title = view.getTitle();
-                    String[] parts = title.replace(StyleManager.get().getMainColor() + "Inspecting ", "").split("'s kit ");
+                    String[] parts = stripped.substring("Inspecting ".length()).split("'s kit ");
@@
-                if (view.getTitle().contains(StyleManager.get().getMainColor() + "Inspecting ") && view.getTitle().contains("'s enderchest ")) {
+                String stripped = ChatColor.stripColor(view.getTitle());
+                if (stripped.startsWith("Inspecting ") && stripped.contains("'s enderchest ")) {
                     Player p = (Player) e.getPlayer();
@@
-                    String title = view.getTitle();
-                    String[] parts = title.replace(StyleManager.get().getMainColor() + "Inspecting ", "").split("'s enderchest ");
+                    String[] parts = stripped.substring("Inspecting ".length()).split("'s enderchest ");

This makes the logic robust to any color code (legacy, hex, gradient) and to runtime theme reloads.

If you prefer not to parse titles at all, I can convert these menus to use a custom InventoryHolder per menu carrying the slot/id, which is the most reliable approach.

Also applies to: 71-74, 96-100, 122-129, 189-196

Comment on lines +40 to +42
String colorTag = plugin.getConfig().getString("interface.main-color", "<blue>");
this.titleColor = miniMessageToLegacy(colorTag);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix legacy color fallback; current "" fallback breaks string comparisons.

miniMessageToLegacy returns legacy §-codes, but on error it returns "" (MiniMessage tag), which will not match callers that expect legacy (e.g., Inventory title contains checks). Return a legacy code and validate conversion.

@@
-        String colorTag = plugin.getConfig().getString("interface.main-color", "<blue>");
-        this.titleColor = miniMessageToLegacy(colorTag);
+        String colorTag = plugin.getConfig().getString("interface.main-color", "<blue>");
+        this.titleColor = miniMessageToLegacy(colorTag);
@@
-    private String miniMessageToLegacy(String miniMessage) {
-        try {
-            Component component = MiniMessage.miniMessage().deserialize(miniMessage + "test");
-            return LegacyComponentSerializer.legacySection().serialize(component).replace("test", "");
-        } catch (Exception e) {
-            return "<blue>";
-        }
-    }
+    private String miniMessageToLegacy(String miniMessage) {
+        final String defaultLegacyBlue = "§9";
+        try {
+            Component component = MiniMessage.miniMessage().deserialize(miniMessage + "test");
+            String out = LegacyComponentSerializer.legacySection().serialize(component).replace("test", "");
+            // If conversion produced no legacy codes (misconfigured value), use default.
+            if (!out.contains("§")) {
+                plugin.getLogger().warning("[StyleManager] Invalid interface.main-color: '" + miniMessage
+                        + "'. Falling back to blue.");
+                return defaultLegacyBlue;
+            }
+            return out;
+        } catch (Exception e) {
+            plugin.getLogger().warning("[StyleManager] Failed to parse interface.main-color: '" + miniMessage
+                    + "'. Falling back to blue. Cause: " + e.getClass().getSimpleName());
+            return defaultLegacyBlue;
+        }
+    }

Also applies to: 44-51

🤖 Prompt for AI Agents
In src/main/java/dev/noah/perplayerkit/util/StyleManager.java around lines 40-42
and also 44-51, the fallback string "<blue>" is a MiniMessage tag while
miniMessageToLegacy() returns legacy §-codes — causing mismatches in callers
that expect legacy codes; change the fallback to the legacy blue code (e.g.,
"§9"), call miniMessageToLegacy(plugin.getConfig().getString(..., "<blue>")) and
then validate the result (check for null/empty or that it does not start with
'<') and if validation fails use the legacy fallback "§9" (and optionally log a
warning) so titleColor and other fields always contain a legacy color code.

@rossnoah rossnoah merged commit 21fabb8 into rossnoah:main Aug 27, 2025
1 of 2 checks passed
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