Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hub: Code quality, enhancments and better maintainability #23

Open
wants to merge 15 commits into
base: staging
Choose a base branch
from

Conversation

palsmo
Copy link
Contributor

@palsmo palsmo commented Jan 1, 2025

I made it so it's easier to tweak certain parameters.
Structured the code for better maintainability (easier to test).
Separated stages for player connection (slight optimization).
Made the firework unique and detonates ~2 blocks above the player when they first connect.
The welcome sound now rely on a collection of appropriate sounds (before it was any sound, which would be creepy cave noises, warden spawn, enderdragon defeat etc.)

Previous logic when it comes to '/fly' and player vanish was essentially untouched.
(* tested /fly)

Summary by CodeRabbit

  • New Features

    • Enhanced player join experience with new welcome messages, fireworks, and sounds.
    • Added permission-based flight control for players.
    • Introduced a new class for managing welcome sounds.
    • New configuration options for join and leave messages.
    • Added support for external dependencies to enhance functionality.
    • New class to handle player quit events with custom messages.
  • Bug Fixes

    • Suppressed default join message.
    • Improved player state initialization on join.
    • Removed unnecessary join and leave message suppression.
  • Chores

    • Minor formatting updates to project configuration files.
    • Updated welcome messages for new players with community guidelines.

@palsmo palsmo requested a review from JaedanC as a code owner January 1, 2025 17:08
Copy link

coderabbitai bot commented Jan 1, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request implements changes to the Zander Hub plugin, enhancing the player join experience and overall configuration. Modifications include the addition of new repositories and dependencies in the pom.xml, extensive refactoring of player join logic in HubPlayerJoin, and the introduction of a new class for managing welcome sounds. Additionally, the plugin.yml and welcome.yml files were updated to reflect new dependencies and revised welcome messages, respectively. A new configuration class for managing join and leave messages was also added, along with a utility class for player visibility checks.

Changes

File Change Summary
zander-hub/pom.xml Added repositories and dependencies for PremiumVanishAPI and ProtocolLib
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java Refactored player join logic, added methods for permissions, welcome messages, fireworks, and sound effects
zander-hub/src/main/resources/plugin.yml Added dependencies for PremiumVanish and ProtocolLib
zander-hub/src/main/resources/welcome.yml Replaced welcome section with newplayerwelcome, updating messages and formatting
zander-hub/src/main/resources/welcome_old.yml Introduced new sections for welcome messages with color formatting
zander-hub/src/main/java/org/modularsoft/zander/hub/ConfigurationManager.java Added message configuration capabilities for join and leave messages
zander-hub/src/main/java/org/modularsoft/zander/hub/configs/MessageConfig.java Created class for managing join and leave messages
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerLeave.java Added class to handle player quit events
zander-hub/src/main/java/org/modularsoft/zander/hub/protection/HubProtection.java Added constructor and removed methods for hiding join/leave messages
zander-hub/src/main/java/org/modularsoft/zander/hub/utils/WelcomeSounds.java Introduced utility class for random welcome sounds
zander-hub/src/main/java/org/modularsoft/zander/hub/utils/Misc.java Added utility class for player visibility checks

Possibly related PRs

Suggested reviewers

  • JaedanC

Poem

🐰 A Rabbit's Welcome Song 🎉
Hop, skip, and join the server's delight,
Fireworks dancing, permissions set right,
No collisions, just pure player glee,
Welcome messages floating free,
Zander Hub sparkles with coding might! 🚀


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 0

🧹 Nitpick comments (5)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (5)

30-34: Consider making constants configurable.
Currently, the constants (TEST_ALWAYS_FIRST_JOIN, NAV_COMPASS_SLOT, etc.) may be better placed in a config file if you anticipate needing to change these values without recompiling.


43-46: Keep pitch or volume dynamic for variation.
If you wish to add a bit of variety, randomizing pitch and volume can produce a more immersive experience. This is just a minor enhancement idea.


47-91: Potential memory optimization.
Storing a large array of sounds is generally fine, but if the list grows more, consider referencing smaller curated subsets or external config. This can help keep the plugin footprint more manageable and flexible.


153-187: Firework logic is well-structured; watch out for performance.
Repeatedly spawning and detonating fireworks is fine, but keep in mind that too many fireworks in short intervals can lag the server. The random color palette is a nice touch.


189-196: Consider placeholders or translations for the welcome message.
For better localization or dynamic placeholders, you might integrate a library (like MiniMessage or a custom translator) so certain parts of the message can be adapted more easily or support multiple languages.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c4f0df and d28121f.

📒 Files selected for processing (3)
  • zander-hub/pom.xml (0 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1 hunks)
  • zander-hub/src/main/resources/plugin.yml (1 hunks)
💤 Files with no reviewable changes (1)
  • zander-hub/pom.xml
✅ Files skipped from review due to trivial changes (1)
  • zander-hub/src/main/resources/plugin.yml
🔇 Additional comments (9)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (9)

35-42: Validate negative or zero bounds for FIREWORK_GROUND_HEIGHT.
If a negative or zero height is unintentionally set, the firework might spawn underground or inside a block, causing weird behavior or immediate detonation. Consider catching invalid values in the configuration.


98-105: Ensure consistent permission checks for flight.
The onPlayerLogin event sets flight for players with zander.hub.fly. Verify that no other constraints or meltdown states are needed (e.g., creative mode checks).


106-122: Suppressing default join message is valid.
Your approach (event.joinMessage(null)) effectively hides the system join notification. Just ensure your custom join messaging logic handles all relevant cases (e.g. vanish states).


124-129: Good approach to separate permission logic.
Centralizing the permission handling in setPermissions() method keeps the code maintainable. No issues found.


131-138: Consider null-checking the hub location.
If the configuration or location is missing or invalid, player.teleport(...) can fail. Add a small safeguard to ensure ConfigurationManager.getHubLocation() returns a valid location.


140-151: Team collision rule is properly set.
Using Team.Option.COLLISION_RULE is the recommended approach. Be sure to handle scoreboard conflicts if multiple plugins share the main scoreboard.


198-206: Broadcaster pattern for join message is clear.
Bukkit.getOnlinePlayers().forEach(...) is straightforward. If performance becomes a concern with large player counts, you might switch to asynchronous broadcasting.


209-213: Randomizing welcome sound is a nice touch.
This ensures that each join feels slightly different. Just watch for potentially loud or disruptive sounds if they are triggered frequently.


215-215: isVanished method logic.
This logic looks correct, as it checks metadata for “vanished.” Ensure the vanish plugin sets the metadata reliably on all use cases.

Also applies to: 217-218

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: 1

🧹 Nitpick comments (2)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (2)

30-34: Consider moving TEST_ALWAYS_FIRST_JOIN to configuration

The TEST_ALWAYS_FIRST_JOIN flag should ideally be moved to the ConfigurationManager for easier testing environment management.

- private static final boolean TEST_ALWAYS_FIRST_JOIN = false; // * default false

Add this to your configuration file instead:

debug:
  always_first_join: false

154-187: Simplify firework generation logic

The firework generation code could be made more concise and maintainable.

 private void spawnWelcomeFirework(Player player) {
     Location spawnLoc = player.getLocation().add(0, FIREWORK_GROUND_HEIGHT, 0);
     Firework firework = player.getWorld().spawn(spawnLoc, Firework.class);
     FireworkMeta meta = firework.getFireworkMeta();
-
-    Random random = new Random();
-    Color[] FCP = FIREWORK_COLOR_PALETTE;
-
-    // random primary-colors
-    List<Color> colors = new ArrayList<>();
-    int numColors = random.nextInt(3) + 2; // 2-4 colors
-    for (int i = 0; i < numColors; i++) {
-        colors.add(FCP[random.nextInt(FCP.length)]);
-    }
-    // random fade-color and firework type
-    Color fadeColor = FCP[random.nextInt(FCP.length)];
-    FireworkEffect.Type[] types = FireworkEffect.Type.values();
-    FireworkEffect.Type type = types[random.nextInt(types.length)];
+    Random random = new Random();
+    List<Color> colors = random.ints(random.nextInt(3) + 2, 0, FIREWORK_COLOR_PALETTE.length)
+            .mapToObj(i -> FIREWORK_COLOR_PALETTE[i])
+            .collect(Collectors.toList());
+    
+    Color fadeColor = FIREWORK_COLOR_PALETTE[random.nextInt(FIREWORK_COLOR_PALETTE.length)];
+    FireworkEffect.Type type = FireworkEffect.Type.values()[random.nextInt(FireworkEffect.Type.values().length)];

     FireworkEffect effect = FireworkEffect.builder()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d28121f and cff0dfd.

📒 Files selected for processing (1)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1 hunks)
🔇 Additional comments (4)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (4)

98-122: Well-structured event handling with clear separation of concerns!

Good practices observed:

  • Login event for permissions and validation
  • Join event for world interactions
  • Delayed execution for welcome actions
  • Clear suppression of default join message

Line range hint 1-220: Overall excellent improvements to code quality and maintainability!

The changes successfully achieve the PR objectives with:

  • Clear separation of concerns in event handling
  • Well-structured constants for easy configuration
  • Improved player experience with better effects
  • Good documentation and method organization

The code is now more maintainable and easier to test.


131-138: Verify teleport success and handle inventory errors

The setInitialState method should verify teleport success and handle potential inventory errors.


140-151: Consider handling team creation failure

The team creation code should handle potential exceptions when creating or modifying the scoreboard team.

 private void setupNoCollision(Player player) {
     Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
-    Team team = scoreboard.getTeam("nocollision");
-    if (team == null) {
-        team = scoreboard.registerNewTeam("nocollision");
-        team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
+    Team team;
+    try {
+        team = scoreboard.getTeam("nocollision");
+        if (team == null) {
+            team = scoreboard.registerNewTeam("nocollision");
+            team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
+        }
+        team.addEntry(player.getName());
+    } catch (IllegalArgumentException e) {
+        plugin.getLogger().warning("Failed to setup no-collision for player: " + player.getName());
     }
-    team.addEntry(player.getName());
 }

Comment on lines 215 to 218
/// Check if the player is currently vanished.
private boolean isVanished(Player player) {
for (MetadataValue meta : player.getMetadata("vanished")) {
if (meta.asBoolean()) return true;
}
return false;
return player.getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);

Copy link

@coderabbitai coderabbitai bot Jan 1, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check in isVanished method

The stream operation on metadata could throw NPE if the "vanished" metadata is not set.

 private boolean isVanished(Player player) {
-    return player.getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
+    return player.hasMetadata("vanished") && 
+           player.getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
 }
📝 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
/// Check if the player is currently vanished.
private boolean isVanished(Player player) {
for (MetadataValue meta : player.getMetadata("vanished")) {
if (meta.asBoolean()) return true;
}
return false;
return player.getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
/// Check if the player is currently vanished.
private boolean isVanished(Player player) {
return player.hasMetadata("vanished") &&
player.getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);

Copy link
Member

Choose a reason for hiding this comment

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

@palsmo you'll need a null check as stated.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@palsmo
Copy link
Contributor Author

palsmo commented Jan 1, 2025

I have replaced the placeholder text in welcome.yml to a fitting message for the CFC server.

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: 1

🧹 Nitpick comments (4)
zander-hub/src/main/java/org/modularsoft/zander/hub/utils/WelcomeSounds.java (1)

6-50: Consider verifying that SOUNDS is non-empty and using a more robust RNG.
While Math.random() is a valid approach, you could utilize ThreadLocalRandom.current().nextInt(SOUNDS.length) for potentially better performance and distribution. Additionally, in an edge case scenario where the SOUNDS array might be empty or manipulated, adding a sanity check could prevent an IndexOutOfBoundsException.

 public static Sound getRandomSound() {
-    return SOUNDS[(int) (Math.random() * SOUNDS.length)];
+    int index = ThreadLocalRandom.current().nextInt(SOUNDS.length);
+    return SOUNDS[index];
 }
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (3)

55-62: Centralizing permission-based setup logic
This event handler is straightforward. You might consider centralizing permission-based logic if there are other permissions or flags to set in the future, improving maintainability.


97-108: Possible collision rule conflicts
While this works for your immediate needs, be aware that if other teams exist, they may override the collision rule or cause confusion. If multiple functionalities require custom collision rules, a more granular approach might be needed.


167-172: Random pitch or config-driven approach
Currently, the pitch is fixed at 1.0. Some servers prefer slight variations or a configurable pitch to provide a more dynamic audio experience.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cff0dfd and 9a23f5b.

📒 Files selected for processing (4)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/utils/WelcomeSounds.java (1 hunks)
  • zander-hub/src/main/resources/welcome.yml (1 hunks)
  • zander-hub/src/main/resources/welcome_old.yml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • zander-hub/src/main/resources/welcome_old.yml
🔇 Additional comments (2)
zander-hub/src/main/resources/welcome.yml (1)

2-11: Looks good!
The new content is clear and aligns with the updated branding/messaging. No issues from a YAML standpoint.

zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1)

173-176: Add null-check to avoid NPE
This is a repeated suggestion from a previous review. Please ensure the "vanished" metadata entry is present before streaming over it to avoid NullPointerExceptions.

Comment on lines 63 to 79
/// Triggers when player's client has joined the world.
/// Best used for initial world interactions (player world state changes etc).
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
event.joinMessage(null); // * suppress default
chatJoinMessage(player);
setInitialState(player);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
// * bukkit uses 'world/playerdata' dir for tracking
if (!player.hasPlayedBefore() || TEST_ALWAYS_FIRST_JOIN) {
chatWelcomeMessage(player);
spawnWelcomeFirework(player);
}
playWelcomeSound(player);
}, ROUTINE_PLAYER_JOINED_DELAY);
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Add player online check before delayed task execution

The verification shows that the delayed task performs several player-dependent operations that could fail if the player disconnects:

  • spawnWelcomeFirework: Spawns entities and modifies world state at player's location
  • chatWelcomeMessage: Sends messages to the player
  • playWelcomeSound: Plays sound at player's location

Add a player.isOnline() check at the start of the delayed task to prevent potential exceptions:

Bukkit.getScheduler().runTaskLater(plugin, () -> {
    if (!player.isOnline()) return;
    
    if (!player.hasPlayedBefore() || TEST_ALWAYS_FIRST_JOIN) {
        chatWelcomeMessage(player);
        spawnWelcomeFirework(player);
    }
    playWelcomeSound(player);
}, ROUTINE_PLAYER_JOINED_DELAY);
🔗 Analysis chain

Potential scheduling check
When scheduling tasks with a delay, consider verifying that the player is still online before running logic that references player state (e.g., for spawnWelcomeFirework). This helps avoid possible exceptions or NPEs if the player quits early.


Let me search for more context about the player-related methods being called to better understand the potential impact.


Let me try a different approach to find these methods using ripgrep instead.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# This script searches for runTaskLater calls to see if there are any checks around player presence afterward.
rg "runTaskLater" -A 5

Length of output: 1445


Script:

#!/bin/bash
# Search for the implementation of methods that interact with the player
ast-grep --pattern 'void chatWelcomeMessage(Player $_) { $$$ }'
ast-grep --pattern 'void spawnWelcomeFirework(Player $_) { $$$ }'
ast-grep --pattern 'void playWelcomeSound(Player $_) { $$$ }'

Length of output: 197


Script:

#!/bin/bash
# Search for the method implementations using ripgrep
rg "chatWelcomeMessage|spawnWelcomeFirework|playWelcomeSound" -A 10

Length of output: 5507

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: 0

🧹 Nitpick comments (8)
zander-hub/pom.xml (1)

50-63: Consider documenting dependency usage.

Since these dependencies are crucial for core functionality (player visibility and effects), consider:

  1. Adding comments in the code where these dependencies are used
  2. Documenting fallback behavior if these plugins are missing
  3. Adding version checks in your plugin's onEnable method
zander-hub/src/main/java/org/modularsoft/zander/hub/utils/Misc.java (1)

12-14: Consider additional player checks for robust vanish detection.
You may want to ensure the player.isOnline() state or handle cases where player may be offline to avoid unexpected behavior in edge cases.

zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerLeave.java (1)

15-17: Plugin reference usage check.
Since this code stores an instance of ZanderHubMain in plugin, confirm how it will be leveraged later. If it's strictly not needed here, consider removing to keep the class cleaner.

zander-hub/src/main/java/org/modularsoft/zander/hub/ConfigurationManager.java (1)

16-16: Static field usage caution.
A static MessageConfig can simplify access but be mindful of potential concurrency issues in environments with multiple threads. As long as the underlying data isn't mutated unsafely, this is acceptable.

zander-hub/src/main/java/org/modularsoft/zander/hub/protection/HubProtection.java (1)

18-21: Consider marking plugin field as private final and validating non-null.

Declaring the field as private final ensures immutability, preventing accidental reassignments and making the code safer. Also, you may want to confirm that plugin is non-null by throwing an IllegalArgumentException if it’s null, to quickly detect invalid states.

- ZanderHubMain plugin;
+ private final ZanderHubMain plugin;

  public HubProtection(ZanderHubMain plugin) {
+     if (plugin == null) {
+         throw new IllegalArgumentException("plugin cannot be null");
+     }
      this.plugin = plugin;
  }
zander-hub/src/main/java/org/modularsoft/zander/hub/configs/MessageConfig.java (2)

15-18: Use private fields to enforce encapsulation.

Marking these text component fields as private helps ensure that they are only accessed through the class's methods, improving encapsulation.

- private TextComponent textCompJoinTemplate;
- private TextComponent textCompLeaveTemplate;
+ private TextComponent textCompJoinTemplate;
+ private TextComponent textCompLeaveTemplate;

83-98: Handle potential concurrency or disk write performance issues.

setPremiumVanish loads and saves messages.yml on each plugin startup or message configuration change, which can be I/O intensive in some environments. Consider lazy-loading or caching if performance becomes an issue.

zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1)

126-159: Validate firework detonation state to avoid errors.

If the firework is destroyed (e.g., chunk unload, plugin-based removal) before the scheduled detonation, calling firework.detonate() might trigger unwanted exceptions or logs. You may wish to check if the entity is still valid to safely handle edge cases.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f387bdd and f19ee38.

📒 Files selected for processing (10)
  • zander-hub/pom.xml (2 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/ConfigurationManager.java (3 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/ZanderHubMain.java (4 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/configs/MessageConfig.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerLeave.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/protection/HubProtection.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/utils/Misc.java (1 hunks)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/utils/WelcomeSounds.java (1 hunks)
  • zander-hub/src/main/resources/plugin.yml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/utils/WelcomeSounds.java
  • zander-hub/src/main/resources/plugin.yml
🔇 Additional comments (11)
zander-hub/pom.xml (2)

30-39: LGTM! Repository configurations are secure and well-structured.

The new repositories for PremiumVanishAPI and ProtocolLib are properly configured using secure HTTPS URLs.


50-63: Verify dependency versions and compatibility.

The dependency configuration looks good with appropriate 'provided' scopes. However, let's verify the versions:

zander-hub/src/main/java/org/modularsoft/zander/hub/utils/Misc.java (1)

8-10: Good job preventing instantiation of the utility class.
Declaring a private constructor in a utility class and throwing an exception is a solid practice to avoid accidental instantiation.

zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerLeave.java (1)

19-26: Confirm the event API method usage for quit messages.
Some server versions use event.setQuitMessage(...) instead of event.quitMessage(...). Verify compatibility and ensure the vanish check is correct if the vanish plugin is absent.

✅ Verification successful

Let me gather more information about the server API version and dependencies.


Let me check the vanish plugin implementation to ensure it's properly handled.


The code is using the correct API and dependencies

The implementation is correct for the following reasons:

  • The project uses Paper API 1.20.2-R0.1-SNAPSHOT which supports the quitMessage(Component) method
  • PremiumVanish is properly declared as a required dependency in both pom.xml and plugin.yml
  • The vanish check is correctly implemented using PremiumVanish's API (VanishAPI.isInvisible())
  • The quit message handling is consistent with Paper API's Component-based message system
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Checking references to PlayerQuitEvent usage across the codebase
# to ensure consistency with the version of the server API.

rg -A 5 $'class PlayerQuitEvent'
rg -A 3 $'quitMessage'

Length of output: 396


Script:

#!/bin/bash
# Check pom.xml for server API version and dependencies
cat **/pom.xml

# Check for any other quit message handling patterns
rg -A 3 "PlayerQuitEvent|setQuitMessage"

Length of output: 8213


Script:

#!/bin/bash
# Check the Misc.isVanish implementation and any vanish-related code
rg -A 5 "isVanish"

# Check for any plugin.yml to see if PremiumVanish is a dependency
fd plugin.yml --exec cat {}

Length of output: 2527

zander-hub/src/main/java/org/modularsoft/zander/hub/ZanderHubMain.java (2)

41-41: Event listener registration looks good.
Registering HubPlayerLeave ensures your custom quit messages and vanish checks execute as intended.


59-59: Configuration setup invoked properly.
Calling ConfigurationManager.setupMessage() during plugin enable is a good approach to ensure message defaults are prepared.

zander-hub/src/main/java/org/modularsoft/zander/hub/ConfigurationManager.java (2)

89-91: Accessor for message config is straightforward.
The getMessage() method neatly exposes the initialized MessageConfig instance. Make sure all calls in the codebase consistently use this accessor to avoid referencing uninitialized objects.


65-87: Setup method ensures default messages exist.
This is a clean approach for initializing configuration messages if absent. However, verify that silently overwriting user changes is desired if they intentionally remove these config keys.

✅ Verification successful

Let me check the configuration handling pattern more thoroughly.


Let me check the default configuration file content.


Default messages are safely added only when missing

Based on the verification:

  1. The messages.join and messages.leave keys are only referenced in ConfigurationManager.java
  2. The default config.yml doesn't contain these message keys
  3. The code only adds default messages when the keys are completely missing (!config.contains())
  4. If a user intentionally removes these keys, they will be restored with defaults on next startup

This is actually a good practice because:

  • It ensures essential configuration always exists for core functionality
  • It doesn't overwrite existing values, only provides defaults when missing
  • The default messages are simple and neutral ("joined"/"left")
  • Changes are saved only when modifications were necessary (hasModifiedConfig)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching for references to the messages.join/messages.leave keys
# to confirm they're used consistently and won't be overwritten unexpectedly.
rg 'messages.join|messages.leave'

Length of output: 951


Script:

#!/bin/bash
# Check for any default config.yml or similar files that might contain default values
fd "config.yml"

# Look for any other configuration-related code that might handle these messages
ast-grep --pattern 'getConfig().set($$$)'

Length of output: 185


Script:

#!/bin/bash
# Check the content of the hub's default config.yml
cat zander-hub/src/main/resources/config.yml

# Look for any other message configuration handling
rg -A 3 "messages" zander-hub/src/main/resources/

Length of output: 190

zander-hub/src/main/java/org/modularsoft/zander/hub/configs/MessageConfig.java (2)

23-35: Validate PremiumVanish plugin presence more comprehensively.

Currently, setPremiumVanish is called only if plugin.getServer().getPluginManager().getPlugin("PremiumVanish") != null. Consider also verifying that the plugin is enabled or that the version meets minimum requirements to avoid potential runtime anomalies.


59-81: Confirm placeholder usage aligns with server messages.

The code enforces exactly one occurrence of %p%. If your server messages require a scenario missing the placeholder (or multiple placeholders), you might need more flexible logic. Otherwise, this is a valid approach to ensure correct player name insertion.

zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1)

71-79: 🛠️ Refactor suggestion

Check if the player remains online before executing delayed tasks.

When the delayed task triggers (1.2s later), the player might have disconnected. Consider verifying player.isOnline() to avoid potential NPE or unexpected states when sending messages or spawning fireworks.

 Bukkit.getScheduler().runTaskLater(plugin, () -> {
+     if (!player.isOnline()) {
+         return;
+     }
      if (!player.hasPlayedBefore() || TEST_ALWAYS_FIRST_JOIN) {
          chatWelcomeMessage(player);
          spawnWelcomeFirework(player);
      }
      playWelcomeSound(player);
 }, ROUTINE_PLAYER_JOINED_DELAY);

Likely invalid or redundant comment.

@palsmo
Copy link
Contributor Author

palsmo commented Jan 2, 2025

The integration with PremiumVanish was essentially proper handling and graphic for join/leave messages between our HubPlayerJoin and the vanish plugin. Messages are configurable in legacy string format in 'config.yml' and will be read via ConfigurationManager.java to do setup of adventure component (for zander-hub) and modification of legacy string in PremiumVanish 'messages.yml'

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: 0

🧹 Nitpick comments (3)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (3)

29-29: Consider moving test flag to configuration

The TEST_ALWAYS_FIRST_JOIN flag should ideally be in a configuration file rather than hardcoded, allowing for easier testing without code changes.


102-108: Consider using per-player scoreboards

Using the main scoreboard for collision teams could conflict with other plugins. Consider creating per-player scoreboards or a dedicated plugin scoreboard.


119-124: Move message formatting to ConfigurationManager

Consider moving the color code translation logic to ConfigurationManager to maintain consistent message formatting across the plugin.

-        List<String> message = ConfigurationManager.getWelcome().getStringList("newplayerwelcome");
-        player.sendMessage("");
-        for (String row : message) {
-            player.sendMessage(ChatColor.translateAlternateColorCodes('&', row));
-        }
-        player.sendMessage("");
+        List<String> message = ConfigurationManager.getWelcome().getFormattedWelcomeMessage();
+        message.forEach(player::sendMessage);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f19ee38 and 0c15e01.

📒 Files selected for processing (1)
  • zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (1 hunks)
🔇 Additional comments (3)
zander-hub/src/main/java/org/modularsoft/zander/hub/events/HubPlayerJoin.java (3)

51-162: Overall implementation looks solid! 👍

The code is well-structured with clear separation of concerns, good documentation, and proper error handling. The refactoring has improved maintainability while adding new features like customized fireworks and welcome sounds.


158-162: 🛠️ Refactor suggestion

Add player connection check for firework detonation

Similar to the join event handler, add a player connection check before detonating the firework to prevent unnecessary world modifications.

         Bukkit.getScheduler().runTaskLater(plugin, () -> {
+            if (!player.isConnected()) return;
             if (firework.isValid()) {
                 firework.detonate();
             }
         }, FIREWORK_DETONATE_DELAY);

Likely invalid or redundant comment.


64-66: Potential race condition with vanish check

The comment "runs before checking vanish" suggests a potential race condition. Consider moving the setInitialState call after the vanish check to ensure consistent behavior.

-        setInitialState(player); // * just be aware, runs before checking vanish
-        if (Misc.isVanish(player))
-            return;
+        if (Misc.isVanish(player))
+            return;
+        setInitialState(player);

@benrobson
Copy link
Member

The integration with PremiumVanish was essentially proper handling and graphic for join/leave messages between our HubPlayerJoin and the vanish plugin. Messages are configurable in legacy string format in 'config.yml' and will be read via ConfigurationManager.java to do setup of adventure component (for zander-hub) and modification of legacy string in PremiumVanish 'messages.yml'

Very cool and additionally personally I would have left the PV messages alone done the vanish check and have a messages config option in the hub rather than depending on 2 configs.

@benrobson benrobson changed the base branch from master to staging January 2, 2025 23:55
@palsmo
Copy link
Contributor Author

palsmo commented Jan 3, 2025

Looked through Codacy and made one change I found appropriate.

palsmo added 2 commits January 3, 2025 15:47
…les, added robustness checks on a few places, updated pom.xml to specify java 21
@palsmo
Copy link
Contributor Author

palsmo commented Jan 5, 2025

Now there is a nice and robust way to have integration with 'config.yml'
I have tested the edge-cases I could think of [in modifying config.yml etc] and fixed a novel bug which I am glad to have hit (furthest corner in my mind).
I noticed that PremiumVanish was one server restart behind using configured join/leave message, which had to do with PV being a dependency ofc will load before zander. Was simply fixed by triggering a reload for PV of its config files.

@palsmo
Copy link
Contributor Author

palsmo commented Jan 5, 2025

Oh should mention that the internal/embedded 'config.yml' is copied over to the external/server 'config.yml' (any missing fields are filled, existing are respected).

palsmo added 2 commits January 5, 2025 02:21
…'always_first_join', reverted to old 'welcome.yml' and refined some existing code.
@benrobson benrobson changed the title Code quality, enhancments and better maintainability - HubPlayerJoin.java Hub: Code quality, enhancments and better maintainability Jan 6, 2025
Copy link
Member

Choose a reason for hiding this comment

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

I make the welcome message and all others generic so that eventually other networks could use this.

Copy link
Member

Choose a reason for hiding this comment

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

Could make a Boolean in the config to use the preset or to use random as I would still like the option to use all

Copy link
Member

Choose a reason for hiding this comment

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

Could make the compass slot and such a configuration option in the config yml instead of setting it static.

setInitialState(player);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
// * bukkit uses 'world/playerdata' dir for tracking
if (!player.hasPlayedBefore() || TEST_ALWAYS_FIRST_JOIN) {
Copy link
Member

Choose a reason for hiding this comment

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

player.hasPlayedBefore should always return a Boolean, not sure why the secondary test is present?

@benrobson
Copy link
Member

Now in testing on the CFC Hub.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants