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

Registry - Keybinding - Skill - Config modules #48

Open
wants to merge 26 commits into
base: 1.21.1/master
Choose a base branch
from

Conversation

minheragon12345
Copy link
Collaborator

@minheragon12345 minheragon12345 commented Jan 17, 2025

Description

  • Added Registry Module
  • Added Keybinding Module
  • Added Skill Module
  • Added Config Module

List of changes

  • Added Registry Module
  • Added Keybinding Module
  • Added Skill Module
  • Added Config Module

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code Refactoring (non-breaking change which improved the Code Quality)
  • Breaking Code Refactoring (breaking change which improved the Code Quality)
  • Documentation update (JavaDoc or Markdown change)

Summary by CodeRabbit

I'll craft concise release notes focusing on the key additions and improvements across the project.

Release Notes for ManasCore v0.1.0

New Features

  • Added comprehensive skill system with advanced event handling
  • Introduced race management framework with evolution mechanics
  • Implemented attribute customization and enhancement system
  • Created keybinding management utility
  • Developed configuration registry and management tools
  • Added network communication infrastructure

Improvements

  • Enhanced event-driven programming capabilities
  • Implemented cross-platform support for Fabric and NeoForge
  • Added localization support for new systems
  • Improved player interaction mechanics

Technical Enhancements

  • Established modular project structure
  • Implemented access widening configurations
  • Added mixin-based behavior modifications
  • Created utility classes for complex game mechanics

Platforms Supported

  • Minecraft 1.21
  • Fabric Loader
  • NeoForge

@minheragon12345 minheragon12345 self-assigned this Jan 17, 2025
Copy link

coderabbitai bot commented Jan 17, 2025

Walkthrough

The pull request introduces a comprehensive set of changes across multiple modules of the ManasMods project, focusing on expanding the core functionality of the modding framework. The changes primarily involve adding new modules and enhancing existing systems for configuration, skills, races, attributes, keybindings, and testing. Key additions include robust event systems, network communication mechanisms, and platform-specific implementations for Fabric and NeoForge environments.

Changes

File/Module Change Summary
config-* New configuration module with registry, access wideners, and mixin configurations
skill-* Comprehensive skill system with event handling, network communication, and skill management APIs
race-* Race management system with evolution, attribute modifiers, and network integration
attribute-* Enhanced attribute system with critical hit mechanics and custom speed modifiers
keybind-* New keybinding management framework with category and action support
testing-* Expanded testing utilities with registry and test implementations for various game mechanics

Sequence Diagram

sequenceDiagram
    participant Client
    participant NetworkManager
    participant Server
    participant RaceStorage
    participant ManasRace

    Client->>NetworkManager: Send Race Evolution Request
    NetworkManager->>Server: Transmit Race Evolution Packet
    Server->>RaceStorage: Retrieve Current Race
    RaceStorage->>ManasRace: Check Evolution Conditions
    ManasRace-->>RaceStorage: Validate Evolution
    RaceStorage->>Server: Apply Race Evolution
    Server->>NetworkManager: Confirm Evolution
    NetworkManager->>Client: Update Race Instance
Loading

This sequence diagram illustrates the race evolution process, demonstrating the interaction between client, network, server, and race management components.

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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

🧹 Nitpick comments (50)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java (1)

22-26: Consider making the skillList parameter immutable.

While records provide immutable fields, the List itself can still be modified. Consider using ImmutableList or List.copyOf() in the constructor to prevent external modifications.

 public record RequestSkillTogglePacket(
-        List<ResourceLocation> skillList
+        List<ResourceLocation> skillList
 ) implements CustomPacketPayload {
+    public RequestSkillTogglePacket {
+        skillList = List.copyOf(skillList); // Make defensive copy
+    }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (1)

22-26: Add Javadoc to document the record fields.

Please add documentation to explain:

  • What heldTick represents (duration of skill hold?)
  • Purpose of keyNumber (keybinding identifier?)
  • Format/requirements for skillList
keybinding-fabric/src/main/resources/fabric.mod.json (1)

1-14: Add homepage URL for better discoverability.

The homepage URL is currently empty. Consider adding a link to your GitHub repository or documentation page to help users find more information about the mod.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/AbstractSkillRegister.java (4)

15-20: Consider eager initialization of the skills register.

The current implementation initializes skills as null and performs lazy initialization in the skill() method. This pattern could lead to potential race conditions in concurrent scenarios and makes the code harder to reason about.

Consider initializing the register in the constructor:

-    protected DeferredRegister<ManasSkill> skills = null;
+    protected final DeferredRegister<ManasSkill> skills;
     public AbstractSkillRegister(String modId) {
         super(modId);
+        this.skills = DeferredRegister.create(modId, SkillAPI.getSkillRegistryKey());
     }

21-24: Add method documentation.

The init method lacks documentation explaining its purpose, contract with the parent class, and the significance of the beforeRegistration parameter.

Add Javadoc explaining:

  • Method's purpose
  • When it's called
  • What beforeRegistration does
  • Order of operations (super.init followed by skills registration)
+    /**
+     * Initializes the skill register by executing any pre-registration tasks and registering skills.
+     * @param beforeRegistration Runnable to execute before registration occurs
+     */
     public void init(final Runnable beforeRegistration) {

34-51: Document the SkillBuilder class and its methods.

The inner class and its methods lack documentation explaining their purpose and usage.

Add comprehensive documentation:

+    /**
+     * Builder class for creating and configuring skill registrations.
+     * @param <R> The type of the parent register
+     * @param <T> The type of skill being built
+     */
     public static class SkillBuilder<R extends AbstractSkillRegister<R>, T extends ManasSkill> extends ContentBuilder<T, R> {
         protected final Supplier<T> skillFactory;

+        /**
+         * Creates a new SkillBuilder instance.
+         * @param register The parent register
+         * @param name The unique identifier for the skill
+         * @param skillFactory Factory for creating skill instances
+         */
         private SkillBuilder(R register, String name, Supplier<T> skillFactory) {

+        /**
+         * Finalizes the skill registration and returns a registry supplier.
+         * @return A RegistrySupplier for the registered skill
+         */
         @Override
         public RegistrySupplier<T> end() {

+        /**
+         * Finalizes the skill registration and returns a holder.
+         * @return A Holder for the registered skill
+         */
         @Override
         public Holder<T> endAsHolder() {

1-52: Well-designed skill registration system.

The implementation provides a robust foundation for skill registration in a modding context. The use of the builder pattern, generic types, and separation of concerns demonstrates good architectural decisions. The class effectively leverages the Architectury API for cross-platform compatibility.

Consider these architectural improvements:

  1. Add an interface defining the contract for skill registration
  2. Consider using a factory interface for skill creation to allow for different implementation strategies
  3. Add events or hooks for registration lifecycle (pre/post registration)
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java (1)

11-16: Consider adding error handling and logging.

While the implementation follows Fabric's initialization pattern correctly, consider these improvements for better robustness:

  1. Add try-catch block to handle potential initialization errors
  2. Add logging to indicate successful initialization
  3. Consider adding initialization state verification

Example implementation:

 public class ManasCoreSkillFabric implements ModInitializer {
+    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger("manascore-skill");
     @Override
     public void onInitialize() {
-        ManasCoreSkill.init();
+        try {
+            LOGGER.info("Initializing ManasCoreSkill module...");
+            ManasCoreSkill.init();
+            LOGGER.info("ManasCoreSkill module initialized successfully");
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCoreSkill module", e);
+            throw e; // Rethrow to ensure Fabric is aware of initialization failure
+        }
     }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (1)

50-54: Clarify variable usage of keyNumber and mode for better readability.

In the code, keyNumber is assigned to mode, and both variables are used subsequently. This might cause confusion since they appear to represent the same concept.

Consider renaming keyNumber to mode if they are intended to be the same, or differentiate them clearly if they represent different concepts. This will improve code readability and maintainability.

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java (1)

23-23: Specify access modifier for the method

The method onHit currently has package-private visibility. Consider specifying an access modifier (e.g., private or public) to clarify its intended visibility and adhere to best practices.

Apply this diff to specify the access modifier:

-    ProjectileDeflection onHit(FishingHook instance, HitResult result, Operation<ProjectileDeflection> original) {
+    private ProjectileDeflection onHit(FishingHook instance, HitResult result, Operation<ProjectileDeflection> original) {
skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinPlayer.java (1)

25-25: Simplify casting of 'this' reference

The cast (LivingEntity) (Object) this can be simplified to (LivingEntity) this since Player extends LivingEntity. The extra cast to Object is unnecessary and can be removed for clarity.

Apply this diff to simplify the casting:

-        if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F;
+        if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) this, damageSource, changeable).isFalse()) return 0.0F;
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1)

23-23: Specify access modifier for the method

The method onHit currently has package-private visibility. Consider specifying an access modifier (e.g., private or public) to clarify its intended visibility and adhere to best practices.

Apply this diff to specify the access modifier:

-    ProjectileDeflection onHit(AbstractHurtingProjectile instance, HitResult result, Operation<ProjectileDeflection> original) {
+    private ProjectileDeflection onHit(AbstractHurtingProjectile instance, HitResult result, Operation<ProjectileDeflection> original) {
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (5)

41-43: Static Fields Should Be final if They Are Meant to Be Constants

The key field is static but not marked as final. If key is intended to be a constant, it should be declared as static final.

Apply this diff to make key a constant:

-private static StorageKey<SkillStorage> key = null;
+private static final StorageKey<SkillStorage> key = null;

88-89: Unnecessary Return After Check

The method checkPlayerOnlyEffects returns immediately if passiveSkillActivate is false, but there's an additional return after the condition which is redundant.

Simplify the code by removing the unnecessary return statement.


170-186: Possible Duplicate Skill Registration

In the learnSkill method, if a skill instance already exists, the method logs a debug message and returns false. However, it may be helpful to inform the player that they already have the skill.

Consider sending a message to the player indicating they have already learned the skill.


245-252: Exception Handling Could Be More Specific

Catching a general Exception in a loop could mask other potential issues.

Apply this diff to catch a more specific exception:

 try {
     ManasSkillInstance instance = ManasSkillInstance.fromNBT((CompoundTag) tag);
     this.skillInstances.put(instance.getSkillId(), instance);
-} catch (Exception e) {
+} catch (NBTException e) {
     ManasCoreSkill.LOG.error("Failed to load skill instance from NBT", e);
 }

265-266: Typo in Method Name resetDirty

The method resetDirty might be better named markClean to match common conventions.

Consider renaming the method for clarity.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)

429-450: Remove redundant constructor and accessors in record.

In Java records, the canonical constructor and accessor methods are generated automatically. Explicitly redefining them is unnecessary unless you need custom behavior.

You can simplify the AttributeTemplate record by removing the redundant constructor and accessor methods:

 public static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) {
-    public AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) {
-        this.id = id;
-        this.amount = amount;
-        this.operation = operation;
-    }

     public AttributeModifier create(double i) {
         return new AttributeModifier(this.id, this.amount * i, this.operation);
     }
-
-    public ResourceLocation id() {
-        return this.id;
-    }
-
-    public double amount() {
-        return this.amount;
-    }
-
-    public AttributeModifier.Operation operation() {
-        return this.operation;
-    }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)

508-510: Clarify the method documentation for damage events.

The comment for onBeingDamaged mentions it is executed before onDamageEntity, but in practice, onBeingDamaged is typically called when the entity takes damage, not before it deals damage. Ensure the comments accurately reflect the method's behavior.

Consider updating the comment for clarity:

 /**
- * Gets executed before {@link ManasSkillInstance#onDamageEntity}
+ * Called when the entity is being damaged. This method is executed before damage is applied.
  *
  * @return false will prevent the owner from taking damage.
  */
registry-common/src/main/java/io/github/manasmods/manascore/registry/api/AbstractRegister.java (1)

549-550: Use consistent attribute names in registration.

Constructing the attribute name using string formatting can lead to inconsistencies. Consider using this.id.toString() to ensure the attribute name matches the registry ID.

Apply this diff for consistency:

- String name = String.format("%s.attribute.%s", this.id.getNamespace(), this.id.getPath().replaceAll("/", "."));
+ String name = this.id.toString();
config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasConfig.java (1)

3-7: Add documentation and consider making the path configurable.

The abstract class would benefit from:

  1. JavaDoc explaining its purpose and usage
  2. Making the parent path configurable through constructor/setter
  3. Path validation and normalization

Consider this improvement:

+/**
+ * Base configuration class that provides common functionality for mod configurations.
+ * Extend this class to create specific configuration handlers for different mod components.
+ */
 public abstract class ManasConfig {
+    private final String parentPath;
+
+    protected ManasConfig(String parentPath) {
+        this.parentPath = parentPath != null ? parentPath : "config";
+    }
+
+    protected ManasConfig() {
+        this("config");
+    }
+
     public String getParentPath() {
-        return "config";
+        return parentPath;
     }
 }
registry-common/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistry.java (1)

10-14: Add documentation and error handling.

The registry initialization would benefit from:

  1. Class-level JavaDoc explaining its purpose
  2. Error handling for initialization failures
  3. Logging for debugging purposes

Consider this improvement:

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Core registry initialization for ManasCore.
+ * Handles the initialization of all registry components.
+ */
 public class ManasCoreRegistry {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ManasCoreRegistry.class);
+
     public static void init() {
+        LOGGER.info("Initializing ManasCore Registry");
+        try {
             ManasAttributeRegistry.init();
+            LOGGER.info("ManasCore Registry initialization completed");
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCore Registry", e);
+            throw new RuntimeException("Registry initialization failed", e);
+        }
     }
 }
registry-fabric/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistryFabric.java (1)

10-15: Add documentation and error handling.

The Fabric initializer would benefit from:

  1. Class-level JavaDoc explaining its purpose
  2. Error handling for initialization failures
  3. Logging for debugging purposes

Consider this improvement:

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Fabric-specific initialization for ManasCore Registry.
+ * Implements ModInitializer to hook into Fabric's mod loading lifecycle.
+ */
 public class ManasCoreRegistryFabric implements ModInitializer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ManasCoreRegistryFabric.class);
+
     @Override
     public void onInitialize() {
+        LOGGER.info("Initializing ManasCore Registry for Fabric");
+        try {
             ManasCoreRegistry.init();
+            LOGGER.info("ManasCore Registry initialization completed for Fabric");
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCore Registry for Fabric", e);
+            throw new RuntimeException("Fabric registry initialization failed", e);
+        }
     }
 }
registry-neoforge/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistryNeoForge.java (1)

12-14: Consider adding error handling during initialization.

The constructor calls ManasCoreRegistry.init() directly without any error handling. Consider wrapping the initialization in a try-catch block to gracefully handle potential initialization failures.

 public ManasCoreRegistryNeoForge() {
-    ManasCoreRegistry.init();
+    try {
+        ManasCoreRegistry.init();
+    } catch (Exception e) {
+        // Use a logging framework instead of System.err in production
+        System.err.println("Failed to initialize ManasCoreRegistry: " + e.getMessage());
+        throw new RuntimeException("Failed to initialize mod", e);
+    }
 }
keybinding-fabric/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingFabric.java (1)

10-15: Maintain consistency with NeoForge implementation.

For consistency with the NeoForge implementation:

  1. The class should be marked as final to prevent inheritance
  2. Consider adding error handling during initialization
-public class ManasCoreKeybindingFabric implements ModInitializer {
+public final class ManasCoreKeybindingFabric implements ModInitializer {
     @Override
     public void onInitialize() {
-        ManasCoreKeybinding.init();
+        try {
+            ManasCoreKeybinding.init();
+        } catch (Exception e) {
+            // Use a logging framework instead of System.err in production
+            System.err.println("Failed to initialize ManasCoreKeybinding: " + e.getMessage());
+            throw new RuntimeException("Failed to initialize mod", e);
+        }
     }
 }
keybinding-neoforge/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingNeoForge.java (1)

12-14: Consider adding error handling during initialization.

The constructor calls ManasCoreKeybinding.init() directly without any error handling. Consider wrapping the initialization in a try-catch block to gracefully handle potential initialization failures.

 public ManasCoreKeybindingNeoForge() {
-    ManasCoreKeybinding.init();
+    try {
+        ManasCoreKeybinding.init();
+    } catch (Exception e) {
+        // Use a logging framework instead of System.err in production
+        System.err.println("Failed to initialize ManasCoreKeybinding: " + e.getMessage());
+        throw new RuntimeException("Failed to initialize mod", e);
+    }
 }
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybinding.java (1)

11-17: Strengthen implementation with defensive programming.

Consider the following improvements:

  1. Mark the class as final since it's not designed for inheritance
  2. Add null check for Platform.getEnvironment()
-public class ManasCoreKeybinding {
+public final class ManasCoreKeybinding {
     public static void init() {
-        if (Platform.getEnvironment() == Env.CLIENT) {
+        Env currentEnv = Platform.getEnvironment();
+        if (currentEnv != null && currentEnv == Env.CLIENT) {
             ManasCoreKeybindingClient.init();
         }
     }
 }
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingClient.java (1)

11-15: LGTM! Consider adding class-level documentation.

The initialization logic and event registration are well-implemented. Consider adding class-level documentation to explain the client-side responsibility of this class.

Add JavaDoc to explain the class's role:

/**
 * Client-side initialization handler for the ManasCore keybinding system.
 * Registers necessary client lifecycle events and initializes the KeybindingManager.
 */
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingCategory.java (1)

11-18: Add input validation and improve documentation.

The class looks good but could benefit from some improvements:

  1. Add validation for the name parameter
  2. Enhance documentation for API users
 @RequiredArgsConstructor(staticName = "of")
+/**
+ * Represents a category for grouping related keybindings.
+ * Categories are used to organize keybindings in the game's controls menu.
+ */
 public class KeybindingCategory {
     private final String name;
 
+    /**
+     * Creates a localization key for this category.
+     * @return A string in the format "modid.category.categoryname"
+     */
     public String getCategoryString() {
+        Objects.requireNonNull(name, "Category name cannot be null");
+        if (name.isEmpty()) {
+            throw new IllegalArgumentException("Category name cannot be empty");
+        }
         return String.format("%s.category.%s", ModuleConstants.MOD_ID, this.name);
     }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1)

19-26: Document initialization order and add error handling.

The initialization sequence looks good, but consider:

  1. Documenting the required initialization order
  2. Adding error handling for initialization failures
+    /**
+     * Initializes the skill system components in the following order:
+     * 1. SkillRegistry - Registers all available skills
+     * 2. SkillStorage - Sets up skill data persistence
+     * 3. ManasSkillNetwork - Initializes networking for skill synchronization
+     * 4. Client-side initialization (if applicable)
+     *
+     * @throws IllegalStateException if any component fails to initialize
+     */
     public static void init() {
-        SkillRegistry.init();
-        SkillStorage.init();
-        ManasSkillNetwork.init();
+        try {
+            SkillRegistry.init();
+            SkillStorage.init();
+            ManasSkillNetwork.init();
+        } catch (Exception e) {
+            LOG.error("Failed to initialize skill system", e);
+            throw new IllegalStateException("Skill system initialization failed", e);
+        }
         if (Platform.getEnvironment() == Env.CLIENT) {
-            ManasCoreSkillClient.init();
+            try {
+                ManasCoreSkillClient.init();
+            } catch (Exception e) {
+                LOG.error("Failed to initialize client-side skill system", e);
+                throw new IllegalStateException("Client-side skill system initialization failed", e);
+            }
         }
     }
registry-common/src/main/java/io/github/manasmods/manascore/registry/api/Register.java (1)

21-25: Add modId validation and consider thread safety documentation.

The implementation looks good, but consider:

  1. Adding validation for the modId parameter
  2. Documenting thread safety guarantees
 public class Register extends AbstractRegister<Register> {
+    /**
+     * Creates a new Register instance for the specified mod.
+     * This class is not thread-safe and should only be used from the main thread.
+     *
+     * @param modId The mod identifier, must not be null or empty
+     * @throws IllegalArgumentException if modId is null or empty
+     */
     public Register(String modId) {
+        if (modId == null || modId.trim().isEmpty()) {
+            throw new IllegalArgumentException("modId must not be null or empty");
+        }
         super(modId);
     }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/Changeable.java (1)

11-15: Consider defensive copying for mutable objects

For mutable type parameters T, the current implementation doesn't prevent modifications to the stored objects themselves, which could lead to unexpected behavior.

Consider adding a copy constructor or clone method for mutable objects:

protected Changeable(@Nullable T value) {
    this.original = deepCopy(value);
    this.value = deepCopy(value);
}

@SuppressWarnings("unchecked")
private T deepCopy(@Nullable T value) {
    if (value == null) return null;
    // Implementation depends on type T
    // Example for Cloneable objects:
    if (value instanceof Cloneable) {
        try {
            return (T) value.getClass().getMethod("clone").invoke(value);
        } catch (Exception e) {
            return value;
        }
    }
    return value;
}
registry-common/src/main/java/io/github/manasmods/manascore/registry/api/ManasAttributeRegistry.java (1)

27-30: Consider adding validation for builder supplier

The registerNew method should validate that the builder supplier produces non-null builders.

Consider wrapping the supplier:

     @ExpectPlatform
     public static void registerNew(Supplier<EntityType<? extends LivingEntity>> type, Supplier<AttributeSupplier.Builder> builder) {
-        throw new AssertionError();
+        validateParameters(type, builder);
+        Supplier<AttributeSupplier.Builder> validatedBuilder = () -> {
+            AttributeSupplier.Builder result = builder.get();
+            if (result == null) {
+                throw new IllegalStateException("Builder supplier returned null");
+            }
+            return result;
+        };
+        throw new AssertionError("Platform implementation not found for registerNew()");
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java (1)

14-21: Consider implementing a packet registry pattern.

Instead of directly registering packets in the init method, consider implementing a packet registry pattern for better modularity and testability. This would allow easier addition of new packet types and better unit testing capabilities.

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (1)

27-37: Simplify boolean chain and use atomic operations.

The current implementation has a complex chain of boolean returns and non-atomic duration increment.

     public boolean tick(Skills storage, LivingEntity entity) {
-        if (!entity.isAlive()) return false;
-        Optional<ManasSkillInstance> optional = storage.getSkill(skill);
-        if (optional.isEmpty()) return false;
-
-        ManasSkillInstance instance = optional.get();
-        if (reachedMaxDuration(instance, entity)) return false;
-
-        if (!instance.canInteractSkill(entity)) return false;
-        return instance.onHeld(entity, this.duration++, mode);
+        return entity.isAlive() 
+            && storage.getSkill(skill)
+                .filter(instance -> !reachedMaxDuration(instance, entity))
+                .filter(instance -> instance.canInteractSkill(entity))
+                .map(instance -> instance.onHeld(entity, duration.getAndIncrement(), mode))
+                .orElse(false);
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java (1)

8-11: Standardize null-safety annotations.

The interface mixes Lombok's @nonnull with JetBrains' @NotNull annotations. Stick to one annotation library for consistency.

 import io.github.manasmods.manascore.skill.impl.SkillStorage;
-import lombok.NonNull;
 import net.minecraft.resources.ResourceLocation;
 import org.jetbrains.annotations.NotNull;
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java (2)

27-31: Consider optimizing the skill iteration process.

The current implementation creates a new ArrayList and iterates through all learned skills on every scroll event, which could be inefficient for players with many skills.

Consider:

  1. Pre-filtering skills that respond to scroll events
  2. Using a more efficient data structure for frequently accessed skills
-            List<ResourceLocation> packetSkills = new ArrayList<>();
-            for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
+            List<ResourceLocation> packetSkills = SkillAPI.getSkillsFrom(player).getLearnedSkills().stream()
+                .filter(skill -> SkillEvents.SKILL_SCROLL.invoker().scroll(skill, player, amountY).isTrue())
+                .map(ManasSkillInstance::getSkillId)
+                .toList();
-                if (SkillEvents.SKILL_SCROLL.invoker().scroll(skillInstance, player, amountY).isFalse()) continue;
-                packetSkills.add(skillInstance.getSkillId());
-            }

33-36: Consider rate limiting scroll events.

There's no rate limiting on scroll events, which could lead to packet spam if the user scrolls rapidly.

Consider implementing a cooldown mechanism or rate limiting for scroll events to prevent network congestion.

skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/EntityEvents.java (2)

20-26: Add documentation for event fields.

The event fields lack documentation explaining their purpose, timing, and usage.

Consider adding JavaDoc for each event field:

+    /**
+     * Fired before a living entity's tick processing.
+     * Use this event to modify entity state before the tick occurs.
+     */
     public static Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
+
+    /**
+     * Fired after a living entity's tick processing.
+     * Use this event to perform actions after the tick is complete.
+     */
     public static Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();
     // Add similar documentation for other events

59-64: Enhance ProjectileHitResult documentation.

The enum comments could be more descriptive to better explain the behavior of each result type.

Consider improving the documentation:

     public enum ProjectileHitResult {
-        DEFAULT, // Hit, damage + possibly continue
-        HIT, // Hit + damage
-        HIT_NO_DAMAGE, // Hit
-        PASS // Pass through
+        /**
+         * Default behavior: Projectile hits, applies damage, and may continue based on implementation
+         */
+        DEFAULT,
+        /**
+         * Projectile hits and applies damage, then stops
+         */
+        HIT,
+        /**
+         * Projectile hits but applies no damage, then stops
+         */
+        HIT_NO_DAMAGE,
+        /**
+         * Projectile passes through without interaction
+         */
+        PASS
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)

52-56: Consider consolidating packet sending methods.

The three packet sending methods have similar structure and environment checks.

Consider creating a private helper method to reduce code duplication:

+    private static void sendPacketIfClient(Runnable packetSender) {
+        if (Platform.getEnvironment() == Env.CLIENT) {
+            packetSender.run();
+        }
+    }
+
     public static void skillActivationPacket(int keyNumber) {
-        if (Platform.getEnvironment() == Env.CLIENT) {
-            InternalSkillPacketActions.sendSkillActivationPacket(keyNumber);
-        }
+        sendPacketIfClient(() -> InternalSkillPacketActions.sendSkillActivationPacket(keyNumber));
     }

Also applies to: 64-68, 76-80

config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)

48-54: Consider performance optimizations for config retrieval.

The current implementation could be improved by:

  1. Using a more efficient lookup mechanism (e.g., Map) instead of streaming
  2. Adding caching for frequently accessed configs
+private static final Map<String, ManasConfig> configCache = new ConcurrentHashMap<>();

 public static <T extends ManasConfig> T getConfig(Class<T> clazz) {
+    String key = clazz.getSimpleName().toLowerCase();
+    return (T) configCache.computeIfAbsent(key, k ->
         loadedConfigs.stream()
                 .filter(config -> config.getClass().getSimpleName().equalsIgnoreCase(k))
                 .findFirst()
-                .orElse(null));
+                .orElse(null));
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1)

16-80: Well-designed event system with comprehensive coverage!

The event system is well-structured with clear separation of concerns. Consider adding:

  1. Documentation for the mode parameter in SkillUpdateCooldownEvent
  2. Documentation for the return values of EventResult in each event
 @FunctionalInterface
 interface SkillUpdateCooldownEvent {
+    /**
+     * Updates the cooldown for a skill
+     * @param skillInstance The skill instance being updated
+     * @param owner The entity that owns the skill
+     * @param currentCooldown The current cooldown value
+     * @param mode The mode of cooldown update:
+     *            0 = Reset
+     *            1 = Increment
+     *            2 = Decrement
+     * @return EventResult indicating if the cooldown update should proceed
+     */
     EventResult cooldown(ManasSkillInstance skillInstance, LivingEntity owner, int currentCooldown, int mode);
 }
registry-neoforge/src/main/java/io/github/manasmods/manascore/registry/api/neoforge/ManasAttributeRegistryImpl.java (1)

44-69: Consider adding safeguards for event handling.

Potential improvements:

  1. Add validation for builder results
  2. Consider synchronization for registry clearing
  3. Add logging for attribute registration failures
 static void registerAttributes(final EntityAttributeModificationEvent e) {
+    if (e == null) {
+        ManasCoreConfig.LOG.error("EntityAttributeModificationEvent is null");
+        return;
+    }
     Multimap<EntityType<? extends LivingEntity>, Consumer<AttributeSupplier.Builder>> keyResolvedMap = ArrayListMultimap.create();
     REGISTRY.forEach((key, value) -> keyResolvedMap.put(key.get(), value));
     e.getTypes().forEach(type -> {
         AttributeSupplier.Builder builder = new AttributeSupplier.Builder();
         if (keyResolvedMap.containsKey(type)) keyResolvedMap.get(type).forEach(consumer -> consumer.accept(builder));
         GLOBAL_REGISTRY.forEach(consumer -> consumer.accept(builder));
-        builder.build().instances.forEach((attribute, attributeInstance) -> e.add(type, attribute, attributeInstance.getBaseValue()));
+        try {
+            AttributeSupplier supplier = builder.build();
+            if (supplier != null && supplier.instances != null) {
+                supplier.instances.forEach((attribute, attributeInstance) -> 
+                    e.add(type, attribute, attributeInstance.getBaseValue()));
+            }
+        } catch (Exception ex) {
+            ManasCoreConfig.LOG.error("Failed to build attributes for type: " + type, ex);
+        }
     });
     
     synchronized (REGISTRY) {
         REGISTRY.clear();
     }
     synchronized (GLOBAL_REGISTRY) {
         GLOBAL_REGISTRY.clear();
     }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java (3)

31-44: Extract magic numbers and add input validation.

Consider these improvements:

  1. Add validation for keyNumber
  2. Extract packet-related constants
  3. Add debug logging for skill activation attempts
+    private static final int MIN_KEY_NUMBER = 0;
+    private static final int MAX_KEY_NUMBER = 9;  // Adjust based on your requirements
+
     public static void sendSkillActivationPacket(int keyNumber) {
+        if (keyNumber < MIN_KEY_NUMBER || keyNumber > MAX_KEY_NUMBER) {
+            ManasCoreConfig.LOG.warn("Invalid key number: " + keyNumber);
+            return;
+        }
         var minecraft = Minecraft.getInstance();
         Player player = minecraft.player;
         if (player == null) return;
         List<ResourceLocation> packetSkills = new ArrayList<>();
 
         for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
+            ManasCoreConfig.LOG.debug("Attempting to activate skill: " + skillInstance.getSkillId());
             if (SkillEvents.ACTIVATE_SKILL.invoker().activateSkill(skillInstance, player, keyNumber).isFalse()) continue;
             packetSkills.add(skillInstance.getSkillId());
         }

50-63: Add validation for held ticks parameter.

Similar improvements as activation packet, plus validation for held ticks:

+    private static final int MIN_HELD_TICKS = 0;
+
     public static void sendSkillReleasePacket(int keyNumber, int heldTicks) {
+        if (keyNumber < MIN_KEY_NUMBER || keyNumber > MAX_KEY_NUMBER) {
+            ManasCoreConfig.LOG.warn("Invalid key number: " + keyNumber);
+            return;
+        }
+        if (heldTicks < MIN_HELD_TICKS) {
+            ManasCoreConfig.LOG.warn("Invalid held ticks: " + heldTicks);
+            return;
+        }
         var minecraft = Minecraft.getInstance();
         Player player = minecraft.player;
         if (player == null) return;

69-83: Add documentation for toggle conditions.

The toggle packet handling looks good but would benefit from better documentation:

+    /**
+     * Sends a packet to toggle skills that meet the following conditions:
+     * 1. Player must be present on the client
+     * 2. Skill must be learned by the player
+     * 3. Skill must be toggleable (canBeToggled returns true)
+     * 4. Toggle event must not be cancelled
+     */
     public static void sendSkillTogglePacket() {
         var minecraft = Minecraft.getInstance();
         Player player = minecraft.player;
         if (player == null) return;
         List<ResourceLocation> packetSkills = new ArrayList<>();
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/ManasKeybinding.java (1)

31-81: Consolidate constructor overloads using builder pattern

The class has 6 constructors with varying parameters, which could be simplified using a builder pattern for better maintainability and readability.

Consider implementing a builder:

public static class Builder {
    private String langKey;
    private InputConstants.Key defaultKey = InputConstants.UNKNOWN;
    private KeybindingCategory category;
    private KeyBindingAction action;
    private KeyBindingRelease release;
    
    public Builder(String langKey, KeybindingCategory category, KeyBindingAction action) {
        this.langKey = langKey;
        this.category = category;
        this.action = action;
    }
    
    public Builder defaultKey(int key) {
        this.defaultKey = InputConstants.Type.KEYSYM.getOrCreate(key);
        return this;
    }
    
    public Builder release(KeyBindingRelease release) {
        this.release = release;
        return this;
    }
    
    public ManasKeybinding build() {
        return new ManasKeybinding(langKey, defaultKey, category, action, release);
    }
}
config-fabric/src/main/resources/fabric.mod.json (1)

10-12: Add homepage URL

The homepage field is empty. Consider adding the GitHub repository URL or documentation site.

build.gradle (1)

Line range hint 1-85: Consider architectural improvements for better modularity.

The build script handles multiple concerns (compilation, publishing, signing, etc.). Consider splitting it into separate Gradle files for better maintainability:

  • buildSrc/ directory for custom plugins and tasks
  • Separate files for publishing, signing, and platform-specific configurations

Example structure:

buildSrc/
  └── src/main/groovy/
      ├── manas.java-conventions.gradle
      ├── manas.publishing-conventions.gradle
      └── manas.platform-conventions.gradle
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between aa6e6b9 and 608a737.

⛔ Files ignored due to path filters (22)
  • command-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • command-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • config-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • config-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • config-neoforge/src/main/resources/icon.png is excluded by !**/*.png
  • inventory-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • inventory-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • keybinding-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • keybinding-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • keybinding-neoforge/src/main/resources/icon.png is excluded by !**/*.png
  • network-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • network-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • registry-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • registry-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • registry-neoforge/src/main/resources/icon.png is excluded by !**/*.png
  • skill-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • skill-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • skill-neoforge/src/main/resources/icon.png is excluded by !**/*.png
  • storage-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • storage-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • testing-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • testing-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
📒 Files selected for processing (82)
  • build.gradle (1 hunks)
  • config-common/build.gradle (1 hunks)
  • config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1 hunks)
  • config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java (1 hunks)
  • config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasConfig.java (1 hunks)
  • config-common/src/main/resources/architectury.common.json (1 hunks)
  • config-common/src/main/resources/manascore_config.accesswidener (1 hunks)
  • config-common/src/main/resources/manascore_config.mixins.json (1 hunks)
  • config-fabric/src/main/java/io/github/manasmods/manascore/config/fabric/ManasCoreConfigFabric.java (1 hunks)
  • config-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • config-neoforge/gradle.properties (1 hunks)
  • config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java (1 hunks)
  • config-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java (1 hunks)
  • keybinding-common/build.gradle (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybinding.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingClient.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingCategory.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingManager.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/ManasKeybinding.java (1 hunks)
  • keybinding-common/src/main/resources/architectury.common.json (1 hunks)
  • keybinding-common/src/main/resources/manascore_keybinding.accesswidener (1 hunks)
  • keybinding-common/src/main/resources/manascore_keybinding.mixins.json (1 hunks)
  • keybinding-fabric/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingFabric.java (1 hunks)
  • keybinding-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • keybinding-fabric/src/main/resources/manascore_keybinding-fabric.mixins.json (1 hunks)
  • keybinding-neoforge/build.gradle (1 hunks)
  • keybinding-neoforge/gradle.properties (1 hunks)
  • keybinding-neoforge/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingNeoForge.java (1 hunks)
  • keybinding-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • registry-common/build.gradle (1 hunks)
  • registry-common/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistry.java (1 hunks)
  • registry-common/src/main/java/io/github/manasmods/manascore/registry/api/AbstractRegister.java (1 hunks)
  • registry-common/src/main/java/io/github/manasmods/manascore/registry/api/ManasAttributeRegistry.java (1 hunks)
  • registry-common/src/main/java/io/github/manasmods/manascore/registry/api/Register.java (1 hunks)
  • registry-common/src/main/resources/architectury.common.json (1 hunks)
  • registry-common/src/main/resources/manascore_registry.accesswidener (1 hunks)
  • registry-common/src/main/resources/manascore_registry.mixins.json (1 hunks)
  • registry-fabric/build.gradle (1 hunks)
  • registry-fabric/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistryFabric.java (1 hunks)
  • registry-fabric/src/main/java/io/github/manasmods/manascore/registry/api/fabric/ManasAttributeRegistryImpl.java (1 hunks)
  • registry-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • registry-fabric/src/main/resources/manascore_registry-fabric.mixins.json (1 hunks)
  • registry-neoforge/build.gradle (1 hunks)
  • registry-neoforge/gradle.properties (1 hunks)
  • registry-neoforge/src/main/java/io/github/manasmods/manascore/registry/ManasCoreRegistryNeoForge.java (1 hunks)
  • registry-neoforge/src/main/java/io/github/manasmods/manascore/registry/api/neoforge/ManasAttributeRegistryImpl.java (1 hunks)
  • registry-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • settings.gradle (1 hunks)
  • skill-common/build.gradle (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/AbstractSkillRegister.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinPlayer.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/Changeable.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/EntityEvents.java (1 hunks)
  • skill-common/src/main/resources/architectury.common.json (1 hunks)
  • skill-common/src/main/resources/manascore_skill.accesswidener (1 hunks)
  • skill-common/src/main/resources/manascore_skill.mixins.json (1 hunks)
  • skill-fabric/build.gradle (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java (1 hunks)
⛔ Files not processed due to max files limit (24)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java
  • skill-fabric/src/main/resources/fabric.mod.json
  • skill-fabric/src/main/resources/manascore_skill-fabric.mixins.json
  • skill-neoforge/build.gradle
  • skill-neoforge/gradle.properties
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/ManasCoreSkillNeoForge.java
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java
  • skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml
  • testing-common/build.gradle
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/TestConfig.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/ConfigModuleTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRegister.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java
  • testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java
✅ Files skipped from review due to trivial changes (23)
  • config-common/src/main/resources/manascore_config.accesswidener
  • skill-common/src/main/resources/manascore_skill.accesswidener
  • registry-fabric/build.gradle
  • keybinding-common/build.gradle
  • keybinding-common/src/main/resources/manascore_keybinding.accesswidener
  • keybinding-neoforge/gradle.properties
  • inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java
  • config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java
  • skill-common/src/main/resources/architectury.common.json
  • keybinding-common/src/main/resources/architectury.common.json
  • config-neoforge/gradle.properties
  • config-common/src/main/resources/architectury.common.json
  • config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java
  • keybinding-common/src/main/resources/manascore_keybinding.mixins.json
  • registry-common/src/main/resources/architectury.common.json
  • config-common/src/main/resources/manascore_config.mixins.json
  • registry-neoforge/gradle.properties
  • registry-common/src/main/resources/manascore_registry.mixins.json
  • registry-fabric/src/main/resources/manascore_registry-fabric.mixins.json
  • keybinding-fabric/src/main/resources/manascore_keybinding-fabric.mixins.json
  • config-fabric/src/main/java/io/github/manasmods/manascore/config/fabric/ManasCoreConfigFabric.java
  • registry-common/build.gradle
  • skill-common/src/main/resources/manascore_skill.mixins.json
🔇 Additional comments (42)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java (2)

1-21: LGTM! Well-structured package and imports.

The package structure clearly indicates client-to-server communication, and the imports are properly organized with cross-platform networking support via Architectury API.


60-62: LGTM! Clean type method implementation.

The method is properly annotated and returns the correct type.

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (4)

1-21: LGTM! Well-organized package structure and imports.

The package name and imports are well-organized, following Java conventions. The c2s package name clearly indicates this is a client-to-server packet.


27-28: LGTM! Proper packet type and codec implementation.

The packet type and codec are correctly implemented following Minecraft's networking patterns.


30-38: LGTM! Consistent encoding and decoding implementation.

The constructor and encode method maintain perfect symmetry in their read/write operations, ensuring reliable packet transmission.


49-51: LGTM! Correct type() method implementation.

The type() method correctly implements the CustomPacketPayload interface requirement.

keybinding-fabric/src/main/resources/fabric.mod.json (2)

22-25: Verify the existence of mixin configuration files.

Please ensure both mixin configuration files exist and are properly configured:

  • ${mod_id}.mixins.json
  • ${mod_id}-fabric.mixins.json
✅ Verification successful

✓ Mixin configuration files verified

Both mixin configuration files exist in the repository:

  • manascore_keybinding.mixins.json in keybinding-common
  • manascore_keybinding-fabric.mixins.json in keybinding-fabric
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for mixin configuration files
fd -e json -x grep -l "\"package\": \".*mixin.*\"" {}

Length of output: 817


15-21: Verify if client-side initialization is required.

The client entrypoints array is empty, but since this is a keybinding module, it might need client-side initialization. Please verify if any client-specific setup is required.

✅ Verification successful

Client entrypoint is not required for this implementation

The keybinding system uses Architectury's client event system (ClientTickEvent) in the common module, which handles the client-side initialization internally. The empty client entrypoints array is correct for this implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the keybinding implementation has client-specific code
ast-grep --pattern 'class $_ implements ClientModInitializer {
  $$$
}'

# Check for client-only annotations or methods
rg -l "(@Environment|@OnlyIn).*\.CLIENT" 

Length of output: 209


Script:

#!/bin/bash
# Check the keybinding implementation class
ast-grep --pattern 'class ManasCoreKeybindingFabric {
  $$$
}'

# Look for keybinding-related code
rg -l "KeyBinding|registerKeyBinding|onKeyPress" 

# Check if there are any client-specific method calls or registrations
rg "MinecraftClient|ClientTickEvent" -A 3

Length of output: 1445

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/AbstractSkillRegister.java (1)

1-14: LGTM! Well-structured package and imports.

The file has proper copyright notice, well-organized imports, and follows good package naming conventions.

keybinding-neoforge/src/main/resources/META-INF/neoforge.mods.toml (4)

14-14: Verify the existence of the icon file.

Ensure that the referenced icon.png file exists in the correct resources directory.

✅ Verification successful

Icon file verification successful

The referenced icon.png file exists at the correct location in the resources directory.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if icon.png exists in the resources directory
fd -g 'icon.png' keybinding-neoforge/src/main/resources/

Length of output: 104


1-2: Consider constraining the loader version range.

The version range [2,) is very broad and might include incompatible future versions. Consider setting an upper bound to avoid potential compatibility issues with future NeoForge versions.


19-19: Consider constraining the NeoForge version range.

Similar to the loader version, the NeoForge version range [21,) is very broad. Consider setting an upper bound to avoid potential compatibility issues.


30-35: Verify Architectury dependency ordering.

The Architectury dependency is set to load AFTER while others are NONE. Verify this ordering is consistent with other modules and necessary for proper functionality.

✅ Verification successful

Architectury dependency ordering is correct and consistent

The "AFTER" ordering for Architectury is consistently implemented across all neoforge modules and follows a logical pattern where core dependencies use "NONE" while framework dependencies use "AFTER". This ensures proper initialization order.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Architectury dependency ordering in other modules
rg -g '*.toml' 'modId.*=.*"architectury".*\n.*type.*=.*"required".*\n.*ordering.*=.*"AFTER"' .

Length of output: 288


Script:

#!/bin/bash
# Search for Architectury dependencies with multiline mode
echo "=== Searching for all Architectury dependencies ==="
rg -U --multiline -g '*.toml' '(?s)\[\[dependencies\.[^\]]+\]\].*?modId.*=.*"architectury".*?side.*=.*"BOTH"' .

echo -e "\n=== Searching specifically for different ordering values ==="
rg -U --multiline -g '*.toml' 'modId.*=.*"architectury".*ordering.*=.*"[^"]+"' .

Length of output: 18408

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java (3)

1-5: LGTM! License header is properly formatted.

The copyright notice and GPL-3.0 license declaration are correctly specified.


6-10: LGTM! Package and imports are well-organized.

The package structure follows Java conventions, and imports are minimal and necessary.


13-15: Verify integration with other modules.

The PR introduces multiple modules (Registry, Keybinding, Config), but we only see the Skill module initialization here. Ensure that:

  1. The initialization order is correct if there are dependencies between modules
  2. All modules are properly initialized in the Fabric environment

Let's verify the module integration:

✅ Verification successful

Module initialization is correctly implemented.

The current implementation follows best practices:

  • Each module handles its own initialization and dependencies
  • Client/Server components are properly separated
  • No cross-module initialization dependencies require specific ordering
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other module initializers
echo "Searching for other module initializers..."
rg -l "implements ModInitializer" --type java

# Check for potential module dependencies
echo "Checking for module dependencies..."
rg -l "ManasCoreRegistry|ManasCoreKeybinding|ManasCoreConfig" --type java

Length of output: 2293


Script:

#!/bin/bash
# Check initialization code in ModInitializer implementations
echo "Checking ModInitializer implementations..."
for file in $(rg -l "implements ModInitializer" --type java); do
  echo "=== $file ==="
  rg -A 5 "onInitialize\(\)" "$file"
done

# Look for cross-module dependencies in initialization
echo -e "\nChecking for cross-module dependencies..."
ast-grep --pattern 'class $_ implements ModInitializer {
  $$$
  public void onInitialize() {
    $$$
  }
  $$$
}'

Length of output: 5090


Script:

#!/bin/bash
# Check the content of init() methods in common modules
echo "Checking init() methods in common modules..."
for module in "Registry" "Keybinding" "Skill" "Storage" "Network" "Inventory" "Command"; do
  echo "=== Manascore${module} init() ==="
  rg -A 10 "class ManasCore${module}" --type java
  rg -A 10 "init\(\)" "*common*/src/main/java/*/${module,,}*/ManasCore${module}.java" || true
done

Length of output: 25552

registry-neoforge/src/main/resources/META-INF/neoforge.mods.toml (3)

7-14: Verify resource files and template variables.

  1. Ensure that icon.png exists in the resources directory.
  2. Verify that ${mod_id}, ${version}, and ${mod_name} variables are properly defined.

Run this script to verify resources and variables:

✅ Verification successful

Resource files and template variables verified successfully

The icon.png file exists in the correct location, and the template variables follow the standard mod development pattern used consistently across the project.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for icon.png in resources directories
echo "Checking for icon.png..."
fd -g 'icon.png' 'registry-neoforge/src'

# Check for template variable definitions
echo "Checking for template variable definitions..."
rg -l '\$\{mod_(id|name)\}|\$\{version\}' | while read -r file; do
    echo "Found variable references in: $file"
    rg 'mod_(id|name)|version.*=' "$file"
done

Length of output: 9106


3-4: Add issue tracker URL and verify license variable.

  1. The empty issue tracker URL might make it difficult for users to report issues.
  2. Ensure that the ${license} variable is properly defined in your build configuration.

Run this script to verify the license variable resolution:


16-35: Verify dependency version constraints.

The dependency configuration looks good with appropriate ordering for architectury. Please verify:

  1. The NeoForge version constraint "[21,)" is appropriate for your mod's features
  2. The template variables ${minecraft_version} and ${architectury_version} are properly defined

Run this script to verify version variables:

✅ Verification successful

Version constraints and variables are properly configured

The dependency configuration is correct:

  • Template variables are properly defined in gradle.properties
  • NeoForge version constraint "[21,)" is appropriate for the used version 21.1.28
  • Minecraft and Architectury version ranges are correctly templated
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for version variable definitions
echo "Checking for version variable definitions..."
rg -l '\$\{(minecraft|architectury)_version\}' | while read -r file; do
    echo "Found version references in: $file"
    rg '(minecraft|architectury).*version.*=' "$file"
done

Length of output: 5526


Script:

#!/bin/bash
# Check build configuration files for version definitions
echo "Checking build configuration files..."

# First, find all gradle and properties files
echo "=== Gradle and Properties files ==="
fd -e gradle -e properties

# Then search for version definitions
echo -e "\n=== Version definitions ==="
rg -i '(minecraft|architectury).*version\s*=' --type properties --type groovy

Length of output: 1538


Script:

#!/bin/bash
echo "=== Checking root gradle.properties ==="
cat gradle.properties

echo -e "\n=== Checking module gradle.properties ==="
for f in */gradle.properties; do
    if [ -f "$f" ]; then
        echo "=== $f ==="
        cat "$f"
    fi
done

Length of output: 3152

skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinPlayer.java (1)

29-32: Ensure inject and modify variable mixins do not conflict

Both modifyDamage (lines 23-27) and cancelActuallyHurt (lines 29-32) are targeting the actuallyHurt method at the same injection point. Confirm that these mixins do not interfere with each other and execute in the intended order.

Consider reviewing the mixin priority or adjusting the injection points to prevent potential conflicts.

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java (1)

29-31: Appropriate use of @Unique and @Nullable annotations

The declaration of onHitEventResult with @Unique and @Nullable is correct. Using @Unique ensures that the mixin introduces this field without causing conflicts, and @Nullable accurately represents that the field can be null.

skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java (1)

51-54: Validate method parameters in cancelActuallyHurt

In the cancelActuallyHurt method, ensure that the parameters DamageSource damageSource and float damageAmount match those of the actuallyHurt method in LivingEntity. If they do not match, the injection may fail.

Run the following script to verify the method signature:

✅ Verification successful

Parameters in cancelActuallyHurt are correctly defined

The parameter types in the Mixin injection match the target method signature as evidenced by the fully qualified method reference in the @Inject annotation. The code would fail to compile if there was a mismatch.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the parameters of the `actuallyHurt` method in LivingEntity.

# Search for the method signature in the codebase.
rg -A 3 'void actuallyHurt\(DamageSource damageSrc, float damageAmount\)' src/

Length of output: 125


Script:

#!/bin/bash
# First, find all Java files recursively
fd -e java . | xargs rg -l "actuallyHurt.*DamageSource.*float" 

# Then use ast-grep to find the exact method signature
ast-grep --pattern 'void actuallyHurt(DamageSource $_, float $_) {
  $$$
}'

Length of output: 142

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java (1)

81-85: Possible Unintended Behavior in onImpulseSet Method

The onImpulseSet method sets this.onHitEventResult to null regardless of the condition. If this.onHitEventResult is not null, and original.call(instance, value) is not called, this might skip necessary logic.

Please verify if the intended behavior is to always reset onHitEventResult to null. If not, adjust the condition accordingly.

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)

42-52: ⚠️ Potential issue

Null Check Missing for Optional in LIVING_CHANGE_TARGET Event

In the LIVING_CHANGE_TARGET event handler, the code checks if (!changeableTarget.isPresent()) return EventResult.pass();, but later uses changeableTarget.get() without verifying if it's present, which could lead to a NoSuchElementException.

Apply this diff to safely handle the optional:

 if (!changeableTarget.isPresent()) return EventResult.pass();

-LivingEntity owner = changeableTarget.get();
+LivingEntity owner = changeableTarget.orElse(null);
+if (owner == null) return EventResult.pass();

 for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(owner).getLearnedSkills()) {

Likely invalid or redundant comment.


63-72: ⚠️ Potential issue

Incorrect Casting in SKILL_DAMAGE_PRE_CALCULATION Event

The code casts source.getEntity() to LivingEntity without checking if source.getEntity() is indeed an instance of LivingEntity, which could lead to a ClassCastException.

Apply this diff to ensure safe casting:

-if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();

+Entity sourceEntity = source.getEntity();
+if (!(sourceEntity instanceof LivingEntity owner)) return EventResult.pass();

Likely invalid or redundant comment.

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

52-58: Potential Issue with Damage Calculation Event Registration

The events SKILL_DAMAGE_PRE_CALCULATION, SKILL_DAMAGE_CALCULATION, and SKILL_DAMAGE_POST_CALCULATION are invoked sequentially even if one returns interruptFalse(). This might not be the intended behavior.

Please verify if the intention is to stop the chain of event invocations when one of them returns interruptFalse(). If so, consider restructuring the logic to respect the interruption.

registry-common/src/main/resources/manascore_registry.accesswidener (1)

1-4: Document the purpose of widening access to these fields.

While the access widener syntax is correct, please add comments explaining:

  1. Why these fields need to be exposed
  2. Potential security implications
  3. How these fields will be used by the mod

Please ensure these fields are still present in the target Minecraft version:

✅ Verification successful

Access widening is justified by implementation requirements

The widened fields are essential for the mod's core functionality:

  • AttributeSupplier.instances is used to manage attribute system integration across platforms
  • MobEffect.particleFactory enables custom particle effects for mod-specific status effects
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of fields in decompiled Minecraft code
# Note: This assumes the presence of a decompiled Minecraft codebase

# Search for AttributeSupplier.instances
rg -l "class AttributeSupplier" | xargs rg "Map.*instances"

# Search for MobEffect.particleFactory
rg -l "class MobEffect" | xargs rg "Function.*particleFactory"

Length of output: 126


Script:

#!/bin/bash
# Check for mod configuration files and documentation
echo "=== Searching for mod configuration files ==="
fd -e toml -e json "^(fabric|forge|mod|gradle)\." --exec head -n 20 {}

echo -e "\n=== Searching for mapping references ==="
rg -l "net\.minecraft\.(world|entity)" --type yaml --type properties

echo -e "\n=== Checking for documentation ==="
fd "README" --exec cat {}

Length of output: 4560


Script:

#!/bin/bash
# Search for usage of the widened fields
echo "=== Searching for AttributeSupplier.instances usage ==="
rg -l "AttributeSupplier" --type java
rg "instances" --type java -C 3

echo -e "\n=== Searching for MobEffect.particleFactory usage ==="
rg -l "MobEffect" --type java
rg "particleFactory" --type java -C 3

echo -e "\n=== Looking for related registry code ==="
fd . -e java --full-path ".*registry.*" --exec head -n 50 {}

Length of output: 9043

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java (1)

15-20: Consider adding synchronization for packet handling.

The network packet handlers should be thread-safe as they might be called concurrently from multiple client connections.

Run this script to check if packet handlers implement thread safety:

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java (1)

50-50: Consider thread safety in forEachSkill implementation.

The forEachSkill method could lead to deadlocks or inconsistent state if the consumer modifies the skill storage.

Consider providing a read-only view of skills during iteration or documenting thread-safety requirements.

config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)

16-22: Well-structured configuration setup!

Good choice of thread-safe collections and proper Gson configuration for JSON handling.

registry-neoforge/src/main/java/io/github/manasmods/manascore/registry/api/neoforge/ManasAttributeRegistryImpl.java (1)

26-42: Excellent implementation of attribute registry!

Good practices observed:

  • Thread-safe collections
  • Lazy evaluation with suppliers
  • Clear separation of registry types
config-common/build.gradle (1)

1-3: LGTM!

The Loom configuration for access widener path follows standard Minecraft modding practices.

registry-neoforge/build.gradle (1)

11-11: Verify mod_id project property is defined

The project.mod_id is used to construct the access widener filename. Ensure this property is defined in the project's gradle.properties or build script.

✅ Verification successful

Property mod_id is properly defined in build configuration

The mod_id property is dynamically constructed in the root build.gradle for all subprojects using the pattern ${rootProject.archives_name}_$module_name, where module_name is derived from the project path. This ensures unique and consistent mod IDs across all modules.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if mod_id is defined in gradle.properties
if ! grep -q "mod_id=" gradle.properties; then
    echo "Warning: mod_id property not found in gradle.properties"
fi

Length of output: 156


Script:

#!/bin/bash
# Check build.gradle and settings.gradle for mod_id definition
echo "=== Checking build.gradle ==="
rg "mod_id" registry-neoforge/build.gradle -A 2 -B 2

echo -e "\n=== Checking settings.gradle ==="
if [ -f "settings.gradle" ]; then
    rg "mod_id" settings.gradle
fi

echo -e "\n=== Checking for other gradle files ==="
fd "build.gradle" --type f

Length of output: 1220


Script:

#!/bin/bash
echo "=== Checking root build.gradle for project configuration ==="
rg "subprojects|allprojects|mod_id" build.gradle -A 3 -B 3

echo -e "\n=== Looking for gradle.properties files ==="
fd "gradle.properties" --type f

echo -e "\n=== Checking root project files ==="
ls -la *.gradle *.properties 2>/dev/null

Length of output: 2479

keybinding-neoforge/build.gradle (1)

11-11: Verify mod_id project property is defined

Same concern as in registry-neoforge/build.gradle regarding the mod_id property definition.

skill-fabric/build.gradle (1)

10-13: Verify the scope of dependencies

The dependencies on network-common and storage-common look appropriate for a skill system. However, consider if these dependencies should be marked as api instead of implementation if they expose public APIs that skill-fabric consumers might need to access directly.

✅ Verification successful

Dependencies can remain as 'implementation'

The skill-fabric module doesn't expose any network or storage APIs through its public interface. It only provides Fabric-specific integration through mixins and delegates core functionality to the main skill module. The current dependency scope is appropriate.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if any public APIs from network-common or storage-common are exposed through skill-fabric's public interface
rg -t java "import.*(?:network|storage).*" ./skill-fabric/src/main/java/

Length of output: 74


Script:

#!/bin/bash
# Check if the directory exists and list Java files
echo "=== Checking directory structure ==="
fd -e java . skill-fabric/src/

echo -e "\n=== Checking all imports to understand package structure ==="
fd -e java . skill-fabric/src/ -x rg -l "^import"

echo -e "\n=== List all package declarations to understand structure ==="
fd -e java . skill-fabric/src/ -x rg "^package"

Length of output: 3319


Script:

#!/bin/bash
# Check content of main class and mixins for any network/storage related code
echo "=== Checking ManasCoreSkillFabric.java ==="
cat skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java

echo -e "\n=== Checking all imports in all files ==="
fd -e java . skill-fabric/src/ -x cat {} | rg "^import"

Length of output: 6803

skill-common/build.gradle (1)

1-3: Verify the access widener file exists

The Loom configuration references an access widener file. Let's ensure it exists and contains the necessary wideners.

✅ Verification successful

Access widener file exists and is properly configured

The file exists with the correct format header (accessWidener v2 named). While currently empty, this is valid as wideners can be added as needed during development.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the access widener file exists and examine its contents
if [ -f "skill-common/src/main/resources/manascore_skill.accesswidener" ]; then
    echo "Access widener file exists"
    cat "skill-common/src/main/resources/manascore_skill.accesswidener"
else
    echo "Access widener file is missing"
fi

Length of output: 228

settings.gradle (1)

Line range hint 17-21: LGTM! Module structure follows the established pattern

The new modules (registry, keybinding, skill, config) are properly integrated into the existing module structure, maintaining consistency with the common/fabric/neoforge pattern.

✅ Verification successful

✓ Module structure is complete and properly implemented

All modules (registry, keybinding, skill, config) are properly structured with:

  • Common variants containing core APIs
  • Platform-specific implementations for both Fabric and NeoForge
  • Consistent naming patterns and package structure
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify that all module directories exist
for module in registry keybinding skill config; do
    for variant in common fabric neoforge; do
        if [ ! -d "$module-$variant" ]; then
            echo "Missing directory: $module-$variant"
        fi
    done
done

Length of output: 1070


Script:

#!/bin/bash
# Check for source files in module directories
for module in registry keybinding skill config; do
    for variant in common fabric neoforge; do
        echo "=== Checking $module-$variant ==="
        fd -e java -e kt . "$module-$variant" || true
    done
done

Length of output: 7338

config-fabric/src/main/resources/fabric.mod.json (1)

28-28: Consider the Java version requirement

Requiring Java 21 (released in September 2023) might be too restrictive for many users. Consider if Java 17 (the current LTS version) would be sufficient for your needs.

registry-fabric/src/main/resources/fabric.mod.json (1)

28-29: ** Consider the Java version requirement**

Same concern as in config-fabric: requiring Java 21 might be too restrictive. Consider if Java 17 would be sufficient.

config-neoforge/src/main/resources/META-INF/neoforge.mods.toml (2)

16-35: Verify dependency version constraints.

The dependency version ranges use open-ended constraints ([21,), [${minecraft_version},), [${architectury_version},)). This could potentially lead to compatibility issues with future versions.

Consider adding upper bounds to version ranges for better version control:

-    versionRange = "[21,)"
+    versionRange = "[21,22)"
-    versionRange = "[${minecraft_version},)"
+    versionRange = "[${minecraft_version},${minecraft_version_next})"
-    versionRange = "[${architectury_version},)"
+    versionRange = "[${architectury_version},${architectury_version_next})"

37-38: Verify mixin configuration file existence.

Ensure that the referenced mixin configuration file exists and is properly configured.

✅ Verification successful

Mixin configuration verified successfully

The referenced mixin configuration file exists at config-common/src/main/resources/manascore_config.mixins.json and is properly configured with all required fields.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the mixin configuration file exists
fd -e json -x grep -l "\"package\": \".*${mod_id}.*\"" {}

Length of output: 812


Script:

#!/bin/bash
# Get the mod_id from neoforge.mods.toml
echo "=== Mod ID from neoforge.mods.toml ==="
cat config-neoforge/src/main/resources/META-INF/neoforge.mods.toml | grep -A 1 "^modId"

echo -e "\n=== Looking for mixin files in neoforge paths ==="
fd -e json . config-neoforge/

Length of output: 366


Script:

#!/bin/bash
# Look for gradle files defining mod_id
echo "=== Searching for mod_id in gradle files ==="
rg "mod_id" -A 2 --glob "*.gradle*"

echo -e "\n=== Looking for mixin configuration templates ==="
fd ".*mixins?.json.*" config-neoforge/

Length of output: 1772


Script:

#!/bin/bash
echo "=== Searching for archives_name in gradle files ==="
rg "archives_name" --glob "*.gradle*" -A 2

echo -e "\n=== Looking for config module mixin files ==="
fd "manascore.*config.*mixins?.json"

Length of output: 664


Script:

#!/bin/bash
echo "=== Checking mixin configuration content ==="
cat config-common/src/main/resources/manascore_config.mixins.json

Length of output: 390

build.gradle (1)

84-85: Review mixin configuration changes.

Two significant changes to the mixin configuration:

  1. Setting useLegacyMixinAp = false indicates a switch to the modern mixin annotation processor
  2. Commenting out defaultRefmapName might affect mixin reference mapping

Let's verify the impact of these changes:

✅ Verification successful

Mixin configuration changes are safe to proceed

The changes are well-aligned with modern Minecraft mod development practices:

  • Setting useLegacyMixinAp = false is the recommended approach for new projects
  • Removing the custom refmap name is safe as the modern mixin annotation processor handles refmap generation automatically for each module
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for mixin configurations across the project
fd -e json -x grep -l "\"refmap\": " {}

# Check for any custom refmap references in code
rg -l "refmap.*json"

Length of output: 77


Script:

#!/bin/bash
# Check for mixin usage in the codebase
rg "@Mixin" -A 2

# Look for mixin configuration classes
ast-grep --pattern 'class $_ implements IMixinConfigPlugin'

Length of output: 10593

Comment on lines +28 to +34
public RequestSkillTogglePacket(FriendlyByteBuf buf) {
this(buf.readList(FriendlyByteBuf::readResourceLocation));
}

public void encode(FriendlyByteBuf buf) {
buf.writeCollection(this.skillList, FriendlyByteBuf::writeResourceLocation);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add size validation for network security.

To prevent potential DoS attacks, consider adding a maximum size limit for the skill list when reading from the network buffer.

     public RequestSkillTogglePacket(FriendlyByteBuf buf) {
-        this(buf.readList(FriendlyByteBuf::readResourceLocation));
+        this(validateList(buf.readList(FriendlyByteBuf::readResourceLocation)));
     }
+    
+    private static List<ResourceLocation> validateList(List<ResourceLocation> list) {
+        if (list.size() > 100) { // Adjust limit as needed
+            throw new IllegalArgumentException("Skill list exceeds maximum size");
+        }
+        return list;
+    }
📝 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 RequestSkillTogglePacket(FriendlyByteBuf buf) {
this(buf.readList(FriendlyByteBuf::readResourceLocation));
}
public void encode(FriendlyByteBuf buf) {
buf.writeCollection(this.skillList, FriendlyByteBuf::writeResourceLocation);
}
public RequestSkillTogglePacket(FriendlyByteBuf buf) {
this(validateList(buf.readList(FriendlyByteBuf::readResourceLocation)));
}
private static List<ResourceLocation> validateList(List<ResourceLocation> list) {
if (list.size() > 100) { // Adjust limit as needed
throw new IllegalArgumentException("Skill list exceeds maximum size");
}
return list;
}
public void encode(FriendlyByteBuf buf) {
buf.writeCollection(this.skillList, FriendlyByteBuf::writeResourceLocation);
}

Comment on lines +36 to +58
public void handle(NetworkManager.PacketContext context) {
if (context.getEnvironment() != Env.SERVER) return;
context.queue(() -> {
Player player = context.getPlayer();
if(player == null) return;
Skills storage = SkillAPI.getSkillsFrom(player);
for (ResourceLocation id : this.skillList) {
storage.getSkill(id).ifPresent(skill -> {
if(!skill.canInteractSkill(player)) return;

if(skill.isToggled()) {
skill.setToggled(false);
skill.onToggleOff(player);
} else {
skill.setToggled(true);
skill.onToggleOn(player);
}

storage.markDirty();
});
}
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and client feedback.

The current implementation has several areas for improvement:

  1. Add error logging for invalid skills
  2. Consider synchronization for concurrent modifications
  3. Provide feedback to the client about success/failure
     public void handle(NetworkManager.PacketContext context) {
         if (context.getEnvironment() != Env.SERVER) return;
         context.queue(() -> {
             Player player = context.getPlayer();
             if(player == null) return;
             Skills storage = SkillAPI.getSkillsFrom(player);
+            boolean hasChanges = false;
             for (ResourceLocation id : this.skillList) {
-                storage.getSkill(id).ifPresent(skill -> {
+                try {
+                    storage.getSkill(id).ifPresentOrElse(skill -> {
                     if(!skill.canInteractSkill(player)) return;
 
                     if(skill.isToggled()) {
                         skill.setToggled(false);
                         skill.onToggleOff(player);
                     } else {
                         skill.setToggled(true);
                         skill.onToggleOn(player);
                     }
-
-                    storage.markDirty();
-                });
+                    hasChanges = true;
+                }, () -> LOGGER.warn("Player {} attempted to toggle invalid skill: {}", player.getName().getString(), id));
+                } catch (Exception e) {
+                    LOGGER.error("Error toggling skill {} for player {}", id, player.getName().getString(), e);
+                }
             }
+            if (hasChanges) {
+                storage.markDirty();
+                // TODO: Send confirmation packet to client
+            }
         });
     }

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

Comment on lines +40 to +47
public void handle(NetworkManager.PacketContext context) {
if (context.getEnvironment() != Env.SERVER) return;
context.queue(() -> {
Player player = context.getPlayer();
if (player == null) return;
StorageManager.getStorage(player, SkillStorage.getKey()).handleSkillRelease(skillList, heldTick, keyNumber, keyNumber);
});
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Confirmed: handleSkillRelease requires distinct mode parameter

The method signature in SkillStorage.java confirms this is a bug:

public void handleSkillRelease(List<ResourceLocation> skillList, int heldTick, int keyNumber, int mode)

The current implementation incorrectly passes keyNumber twice instead of providing a distinct mode parameter.

🔗 Analysis chain

Fix duplicate keyNumber parameter in handleSkillRelease call.

The handleSkillRelease method is called with keyNumber passed twice as the last two parameters. This appears to be a bug.

Verify the correct parameters and update the call:

-StorageManager.getStorage(player, SkillStorage.getKey()).handleSkillRelease(skillList, heldTick, keyNumber, keyNumber);
+StorageManager.getStorage(player, SkillStorage.getKey()).handleSkillRelease(skillList, heldTick, keyNumber);

Let's verify the method signature in SkillStorage:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for handleSkillRelease method definition
ast-grep --pattern 'handleSkillRelease($_) {
  $$$
}'

Length of output: 55


Script:

#!/bin/bash
# Search for handleSkillRelease method using ripgrep
rg "handleSkillRelease" -A 2

Length of output: 992

Comment on lines 26 to 32
"depends": {
"fabricloader": ">=${fabric_loader_version}",
"minecraft": "~${minecraft_version}",
"java": ">=21",
"architectury": ">=${architectury_version}",
"fabric-api": "*"
},
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Consider lowering Java version requirement to 17

The codebase only uses the record feature which was stabilized in Java 16. Java 17 (current LTS) would be sufficient and provide better mod adoption while maintaining all functionality.

Note: The fabric-api versioning is properly managed through the build system, so using "*" in the mod.json is acceptable.

🔗 Analysis chain

Review dependency constraints.

Two potential concerns in the dependencies:

  1. Java 21 requirement is quite recent and might limit mod adoption. Consider if Java 17 (the current LTS) would be sufficient.
  2. Using "*" for fabric-api version could lead to compatibility issues. Consider specifying a minimum version instead.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if any code actually requires Java 21 features
rg "sealed|permits|record|switch.*case.*when" 

# Find fabric-api version references in other files
rg "fabric-api.*version" -g "*.{gradle,properties}"

Length of output: 1735

Comment on lines 26 to 32
/**
* Creates a new {@link SkillBuilder} for the given name.
*/
public <T extends ManasSkill> SkillBuilder<R, T> skill(final String name, final Supplier<T> skillFactory) {
if (this.skills == null) this.skills = DeferredRegister.create(this.modId, SkillAPI.getSkillRegistryKey());
return new SkillBuilder<>(self(), name, skillFactory);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance input validation and documentation.

The method lacks parameter validation and comprehensive documentation.

Add parameter validation and enhance documentation:

     /**
      * Creates a new {@link SkillBuilder} for the given name.
+     * @param name The unique identifier for the skill. Must not be null or empty.
+     * @param skillFactory A supplier that creates new instances of the skill.
+     * @return A new SkillBuilder instance for configuring the skill.
+     * @throws IllegalArgumentException if name is null or empty
      */
     public <T extends ManasSkill> SkillBuilder<R, T> skill(final String name, final Supplier<T> skillFactory) {
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("Skill name cannot be null or empty");
+        }
+        if (skillFactory == null) {
+            throw new IllegalArgumentException("Skill factory cannot be null");
+        }
-        if (this.skills == null) this.skills = DeferredRegister.create(this.modId, SkillAPI.getSkillRegistryKey());
         return new SkillBuilder<>(self(), name, skillFactory);
     }
📝 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
/**
* Creates a new {@link SkillBuilder} for the given name.
*/
public <T extends ManasSkill> SkillBuilder<R, T> skill(final String name, final Supplier<T> skillFactory) {
if (this.skills == null) this.skills = DeferredRegister.create(this.modId, SkillAPI.getSkillRegistryKey());
return new SkillBuilder<>(self(), name, skillFactory);
}
/**
* Creates a new {@link SkillBuilder} for the given name.
* @param name The unique identifier for the skill. Must not be null or empty.
* @param skillFactory A supplier that creates new instances of the skill.
* @return A new SkillBuilder instance for configuring the skill.
* @throws IllegalArgumentException if name is null or empty
*/
public <T extends ManasSkill> SkillBuilder<R, T> skill(final String name, final Supplier<T> skillFactory) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Skill name cannot be null or empty");
}
if (skillFactory == null) {
throw new IllegalArgumentException("Skill factory cannot be null");
}
return new SkillBuilder<>(self(), name, skillFactory);
}

Comment on lines +56 to +74
public static void createConfigs() {
registeredConfigs.forEach(config -> {
String content = gson.toJson(config);
Path startPath = Paths.get(config.getParentPath() + "/" + config.getClass().getSimpleName() + ".json");
try {
if (!Files.exists(startPath)) {
Files.createDirectories(startPath.getParent());
Files.write(startPath, content.getBytes());
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " created.");
} else {
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " already exists.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error creating config: " + config.getClass().getSimpleName());
e.printStackTrace();
}
});
loadConfigs();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply consistent improvements to config creation.

The same improvements suggested for config loading should be applied here:

  1. Path sanitization
  2. Structured error logging
  3. Parent path validation
 try {
+    String parentPath = config.getParentPath();
+    if (parentPath == null || parentPath.isEmpty()) {
+        ManasCoreConfig.LOG.error("Invalid parent path for config: " + config.getClass().getSimpleName());
+        return;
+    }
+    Path startPath = Paths.get(parentPath).normalize()
+        .resolve(config.getClass().getSimpleName() + ".json");
     if (!Files.exists(startPath)) {
         Files.createDirectories(startPath.getParent());
         Files.write(startPath, content.getBytes());
         ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " created.");
     } else {
         ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " already exists.");
     }
 } catch (IOException e) {
-    ManasCoreConfig.LOG.error("Error creating config: " + config.getClass().getSimpleName());
-    e.printStackTrace();
+    ManasCoreConfig.LOG.error("Error creating config: " + config.getClass().getSimpleName() 
+        + " - " + e.getMessage());
 }
📝 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 void createConfigs() {
registeredConfigs.forEach(config -> {
String content = gson.toJson(config);
Path startPath = Paths.get(config.getParentPath() + "/" + config.getClass().getSimpleName() + ".json");
try {
if (!Files.exists(startPath)) {
Files.createDirectories(startPath.getParent());
Files.write(startPath, content.getBytes());
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " created.");
} else {
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " already exists.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error creating config: " + config.getClass().getSimpleName());
e.printStackTrace();
}
});
loadConfigs();
}
public static void createConfigs() {
registeredConfigs.forEach(config -> {
String content = gson.toJson(config);
try {
String parentPath = config.getParentPath();
if (parentPath == null || parentPath.isEmpty()) {
ManasCoreConfig.LOG.error("Invalid parent path for config: " + config.getClass().getSimpleName());
return;
}
Path startPath = Paths.get(parentPath).normalize()
.resolve(config.getClass().getSimpleName() + ".json");
if (!Files.exists(startPath)) {
Files.createDirectories(startPath.getParent());
Files.write(startPath, content.getBytes());
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " created.");
} else {
ManasCoreConfig.LOG.info("Config: " + config.getClass().getSimpleName() + " already exists.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error creating config: " + config.getClass().getSimpleName()
+ " - " + e.getMessage());
}
});
loadConfigs();
}

Comment on lines +29 to +46
public static void loadConfigs() {
registeredConfigs.forEach(config -> {
Path startPath = Paths.get(config.getParentPath() + "/" + config.getClass().getSimpleName().toLowerCase() + ".json");
try {
if (Files.exists(startPath)) {
String string = Files.readString(startPath);
ManasConfig loadedConfig = gson.fromJson(string, config.getClass());
loadedConfigs.add(loadedConfig);
ManasCoreConfig.LOG.info( config.getClass().getSimpleName() + " loaded.");
} else {
ManasCoreConfig.LOG.info( config.getClass().getSimpleName() + " not found.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error loading config: " + config.getClass().getSimpleName());
e.printStackTrace();
}
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and path construction.

Consider these improvements:

  1. Replace e.printStackTrace() with structured logging
  2. Sanitize file paths to prevent directory traversal
  3. Add validation for config.getParentPath()
 try {
+    String parentPath = config.getParentPath();
+    if (parentPath == null || parentPath.isEmpty()) {
+        ManasCoreConfig.LOG.error("Invalid parent path for config: " + config.getClass().getSimpleName());
+        return;
+    }
+    Path startPath = Paths.get(parentPath).normalize()
+        .resolve(config.getClass().getSimpleName().toLowerCase() + ".json");
     if (Files.exists(startPath)) {
         String string = Files.readString(startPath);
         ManasConfig loadedConfig = gson.fromJson(string, config.getClass());
         loadedConfigs.add(loadedConfig);
         ManasCoreConfig.LOG.info(config.getClass().getSimpleName() + " loaded.");
     } else {
         ManasCoreConfig.LOG.info(config.getClass().getSimpleName() + " not found.");
     }
 } catch (IOException e) {
-    ManasCoreConfig.LOG.error("Error loading config: " + config.getClass().getSimpleName());
-    e.printStackTrace();
+    ManasCoreConfig.LOG.error("Error loading config: " + config.getClass().getSimpleName() 
+        + " - " + e.getMessage());
 }
📝 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 void loadConfigs() {
registeredConfigs.forEach(config -> {
Path startPath = Paths.get(config.getParentPath() + "/" + config.getClass().getSimpleName().toLowerCase() + ".json");
try {
if (Files.exists(startPath)) {
String string = Files.readString(startPath);
ManasConfig loadedConfig = gson.fromJson(string, config.getClass());
loadedConfigs.add(loadedConfig);
ManasCoreConfig.LOG.info( config.getClass().getSimpleName() + " loaded.");
} else {
ManasCoreConfig.LOG.info( config.getClass().getSimpleName() + " not found.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error loading config: " + config.getClass().getSimpleName());
e.printStackTrace();
}
});
}
public static void loadConfigs() {
registeredConfigs.forEach(config -> {
try {
String parentPath = config.getParentPath();
if (parentPath == null || parentPath.isEmpty()) {
ManasCoreConfig.LOG.error("Invalid parent path for config: " + config.getClass().getSimpleName());
return;
}
Path startPath = Paths.get(parentPath).normalize()
.resolve(config.getClass().getSimpleName().toLowerCase() + ".json");
if (Files.exists(startPath)) {
String string = Files.readString(startPath);
ManasConfig loadedConfig = gson.fromJson(string, config.getClass());
loadedConfigs.add(loadedConfig);
ManasCoreConfig.LOG.info(config.getClass().getSimpleName() + " loaded.");
} else {
ManasCoreConfig.LOG.info(config.getClass().getSimpleName() + " not found.");
}
} catch (IOException e) {
ManasCoreConfig.LOG.error("Error loading config: " + config.getClass().getSimpleName()
+ " - " + e.getMessage());
}
});
}

import java.util.HashMap;

public class ManasKeybinding extends KeyMapping {
private static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider thread safety for shared state

The static PRESSED_KEYBINDINGS HashMap is accessed from multiple points but lacks synchronization. In a game environment with multiple input events, this could lead to race conditions.

Consider using a thread-safe collection:

-    private static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
+    private static final Map<ManasKeybinding, Long> PRESSED_KEYBINDINGS = Collections.synchronizedMap(new HashMap<>());
📝 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 static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
private static final Map<ManasKeybinding, Long> PRESSED_KEYBINDINGS = Collections.synchronizedMap(new HashMap<>());

Comment on lines 22 to 25
"mixins": [
"${mod_id}.mixins.json",
"${mod_id}-fabric.mixins.json"
],
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Remove redundant empty mixin configuration

The mod currently includes two identical empty mixin configurations targeting the same package. Since no mixins are defined in either file, having both configurations is unnecessary. Consider removing ${mod_id}-fabric.mixins.json and keeping only the common mixins file for now. The platform-specific mixin file can be added back when needed.

🔗 Analysis chain

Verify the necessity of multiple mixin configurations

The mod includes two mixin configurations. Verify that both are necessary and contain different mixins:

  • ${mod_id}.mixins.json (common mixins)
  • ${mod_id}-fabric.mixins.json (fabric-specific mixins)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the contents of both mixin files
echo "Common mixins:"
find registry-common -name "*.mixins.json" -exec cat {} \;
echo "Fabric mixins:"
find registry-fabric -name "*-fabric.mixins.json" -exec cat {} \;

Length of output: 666

@@ -0,0 +1,38 @@
modLoader = "javafml"
loaderVersion = "[2,)"
issueTrackerURL = ""
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 issue tracker URL for better user support

Set issueTrackerURL = "https://github.com/ManasMods/ManasCore/issues" since the repository has GitHub Issues enabled.

🔗 Analysis chain

Add issue tracker URL for better user support.

The empty issue tracker URL should be populated to help users report bugs and request features.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if issue tracker URL is defined in other platform configurations
rg -l 'issueTrackerURL.*=.*"[^"]'

Length of output: 35


Script:

#!/bin/bash
# Find all mod configuration files
fd -e toml -x cat {} \;

# Get repository information
gh repo view --json url,hasIssuesEnabled

Length of output: 7602

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

♻️ Duplicate comments (2)
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1)

27-27: 🛠️ Refactor suggestion

Use '==' operator for enum comparison instead of 'Objects.equals'

When comparing enum constants in Java, it's recommended to use the == operator rather than Objects.equals, as enums are singletons and == provides a more efficient and type-safe comparison.

-        if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get();
+        if (resultChangeable.get() != EntityEvents.ProjectileHitResult.DEFAULT) return deflectionChangeable.get();
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/ManasKeybinding.java (1)

16-16: ⚠️ Potential issue

Consider thread safety for shared state

The static PRESSED_KEYBINDINGS HashMap lacks synchronization, which could lead to race conditions in a game environment with multiple input events.

🧹 Nitpick comments (11)
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java (2)

1-4: Update the copyright year.

The copyright notice shows the year 2025, which is in the future. Consider updating it to the current year or the year when the code was written.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

22-30: Add method documentation and improve null safety.

The method implementation is solid, but could benefit from these improvements:

  1. Add Javadoc explaining the purpose and behavior of the method
  2. Make the null check more explicit

Consider applying these changes:

+    /**
+     * Wraps the hit detection of LlamaSpit to allow modification through events.
+     * 
+     * @param instance The LlamaSpit entity instance
+     * @param result The original hit result
+     * @param original The original operation to be wrapped
+     * @return The resulting ProjectileDeflection
+     */
     @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/LlamaSpit;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;"))
     ProjectileDeflection onHit(LlamaSpit instance, HitResult result, Operation<ProjectileDeflection> original) {
         Changeable<EntityEvents.ProjectileHitResult> resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT);
         Changeable<ProjectileDeflection> deflectionChangeable = Changeable.of(ProjectileDeflection.NONE);
         EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable);
-        if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get();
+        // If the event modified the result, return the modified deflection
+        if (resultChangeable.get() != null && resultChangeable.get() != EntityEvents.ProjectileHitResult.DEFAULT) {
+            return deflectionChangeable.get();
+        }
         original.call(instance, result);
         return deflectionChangeable.get();
     }
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (2)

2-2: Update the copyright year

The copyright year is set to 2025, but it should be the current year or the year when the code was written.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

22-23: Add method documentation

Add Javadoc to explain the purpose of this method, its parameters, and return value. This will help other developers understand the mixin's functionality.

+    /**
+     * Wraps the hitTargetOrDeflectSelf method to allow modification of projectile hit behavior.
+     * @param instance The projectile instance
+     * @param result The hit result
+     * @param original The original operation
+     * @return The projectile deflection result
+     */
     @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/AbstractHurtingProjectile;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;"))
keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingCategory.java (1)

11-13: Add parameter validation and documentation.

The name parameter lacks validation and documentation. Consider:

  1. Adding parameter validation to prevent null/empty values
  2. Adding Javadoc to document the expected format/constraints
 @RequiredArgsConstructor(staticName = "of")
 public class KeybindingCategory {
+    /**
+     * @param name The category name (non-null, non-empty)
+     * @throws IllegalArgumentException if name is null or empty
+     */
     private final String name;
+
+    private static String validateName(String name) {
+        if (name == null || name.trim().isEmpty()) {
+            throw new IllegalArgumentException("Category name cannot be null or empty");
+        }
+        return name;
+    }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)

301-305: Correct Javadoc syntax for @param tag

The Javadoc comment incorrectly uses {@param heldTicks}. The correct syntax for documenting parameters is @param parameterName description.

Apply this diff to fix the Javadoc:

-     * Called when the {@link LivingEntity} owning this Skill releases the skill activation button after {@param heldTicks}.
+     * Called when the {@link LivingEntity} owning this Skill releases the skill activation button after a certain number of ticks.
+     *
+     * @param heldTicks The number of ticks the skill activation button was held.
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java (2)

31-83: Reduce code duplication in packet action methods.

The packet action methods share similar patterns. Consider extracting common logic into a helper method:

+    private static <T> void processSkillPacket(int keyNumber, 
+            Function<ManasSkillInstance, Boolean> skillFilter,
+            BiFunction<Integer, List<ResourceLocation>, T> packetCreator) {
+        var minecraft = Minecraft.getInstance();
+        Player player = minecraft.player;
+        if (player == null) return;
+        List<ResourceLocation> packetSkills = new ArrayList<>();
+
+        for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
+            if (skillFilter.apply(skillInstance)) {
+                packetSkills.add(skillInstance.getSkillId());
+            }
+        }
+
+        if (!packetSkills.isEmpty()) {
+            NetworkManager.sendToServer(packetCreator.apply(keyNumber, packetSkills));
+        }
+    }

This would simplify the implementation of each packet action method.


34-34: Add logging for failed packet operations.

Consider adding debug logging when packet operations fail (e.g., null player) to aid in troubleshooting.

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;

 public class InternalSkillPacketActions {
+    private static final Logger LOGGER = LoggerFactory.getLogger(InternalSkillPacketActions.class);

     public static void sendSkillActivationPacket(int keyNumber) {
         var minecraft = Minecraft.getInstance();
         Player player = minecraft.player;
-        if (player == null) return;
+        if (player == null) {
+            LOGGER.debug("Cannot send skill activation packet: player is null");
+            return;
+        }

Also applies to: 53-53, 72-72

skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/EntityEvents.java (2)

20-27: Enhance event documentation.

Consider adding more detailed documentation for each event, including:

  • When the event is fired
  • Expected usage scenarios
  • Example implementations
+    /**
+     * Fired before a living entity's tick processing.
+     * Use this event to modify entity behavior at the start of each tick.
+     */
     public static Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
+
+    /**
+     * Fired after a living entity's tick processing.
+     * Use this event to perform post-tick modifications or calculations.
+     */
     public static Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();

59-64: Improve ProjectileHitResult enum documentation.

The enum states would benefit from more detailed documentation explaining their use cases and implications.

     public enum ProjectileHitResult {
-        DEFAULT, // Hit, damage + possibly continue
-        HIT, // Hit + damage
-        HIT_NO_DAMAGE, // Hit
-        PASS // Pass through
+        /**
+         * Default behavior: Hit the target, apply damage, and possibly continue trajectory
+         */
+        DEFAULT,
+        /**
+         * Hit the target and apply damage, then stop
+         */
+        HIT,
+        /**
+         * Hit the target without applying damage
+         */
+        HIT_NO_DAMAGE,
+        /**
+         * Pass through the target without interaction
+         */
+        PASS
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1)

32-119: Enhance error handling in event handlers.

Consider adding try-catch blocks around event handler logic to prevent exceptions from propagating to the event system.

     public static void init() {
         EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> {
+            try {
                 for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
                     if (!instance.canInteractSkill(entity)) continue;
                     if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
                 }
                 return EventResult.pass();
+            } catch (Exception e) {
+                LOGGER.error("Error processing LIVING_EFFECT_ADDED event", e);
+                return EventResult.pass();
+            }
         });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 608a737 and c96dcb2.

📒 Files selected for processing (82)
  • command-common/build.gradle (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Bool.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DoubleNumber.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Enum.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/FloatNumber.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/IntNumber.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Literal.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/LongNumber.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Sender.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Text.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Uuid.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java (1 hunks)
  • command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java (1 hunks)
  • command-fabric/build.gradle (1 hunks)
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java (1 hunks)
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java (1 hunks)
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java (1 hunks)
  • command-neoforge/build.gradle (1 hunks)
  • command-neoforge/gradle.properties (1 hunks)
  • command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java (1 hunks)
  • command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java (1 hunks)
  • config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java (1 hunks)
  • inventory-common/build.gradle (1 hunks)
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java (1 hunks)
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java (1 hunks)
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java (1 hunks)
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java (1 hunks)
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java (1 hunks)
  • inventory-fabric/build.gradle (1 hunks)
  • inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java (1 hunks)
  • inventory-neoforge/build.gradle (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybinding.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingClient.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingCategory.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingManager.java (1 hunks)
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/ManasKeybinding.java (1 hunks)
  • keybinding-fabric/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingFabric.java (1 hunks)
  • keybinding-neoforge/build.gradle (1 hunks)
  • keybinding-neoforge/gradle.properties (1 hunks)
  • keybinding-neoforge/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingNeoForge.java (1 hunks)
  • network-neoforge/build.gradle (1 hunks)
  • settings.gradle (2 hunks)
  • skill-common/build.gradle (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinPlayer.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/Changeable.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/EntityEvents.java (1 hunks)
  • skill-fabric/build.gradle (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1 hunks)
⛔ Files not processed due to max files limit (27)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java
  • skill-neoforge/build.gradle
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/ManasCoreSkillNeoForge.java
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java
  • storage-common/build.gradle
  • storage-common/src/main/java/io/github/manasmods/manascore/storage/ManasCoreStorage.java
  • storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java
  • storage-common/src/main/java/io/github/manasmods/manascore/storage/mixin/MixinServerLevel.java
  • storage-fabric/build.gradle
  • storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/ManasCoreStorageFabric.java
  • storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/mixin/MixinPlayerChunkSender.java
  • storage-neoforge/src/main/java/io/github/manasmods/manascore/storage/neoforge/ManasCoreStorageNeoForge.java
  • testing-common/build.gradle
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/InventoryTabsTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/StorageModuleTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java
  • testing-fabric/build.gradle
  • testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java
  • testing-neoforge/build.gradle
  • testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java
✅ Files skipped from review due to trivial changes (38)
  • command-neoforge/build.gradle
  • command-fabric/build.gradle
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Literal.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Bool.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Enum.java
  • command-neoforge/gradle.properties
  • inventory-neoforge/build.gradle
  • command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Uuid.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/LongNumber.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java
  • command-common/build.gradle
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java
  • network-neoforge/build.gradle
  • inventory-fabric/build.gradle
  • command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java
  • command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Text.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java
  • command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/FloatNumber.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/IntNumber.java
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DoubleNumber.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java
  • inventory-common/build.gradle
  • inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java
  • command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Sender.java
🚧 Files skipped from review as they are similar to previous changes (33)
  • keybinding-neoforge/gradle.properties
  • inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java
  • skill-common/build.gradle
  • settings.gradle
  • keybinding-fabric/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingFabric.java
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingClient.java
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybinding.java
  • skill-fabric/build.gradle
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java
  • keybinding-neoforge/build.gradle
  • keybinding-neoforge/src/main/java/io/github/manasmods/manascore/keybinding/ManasCoreKeybindingNeoForge.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/utils/Changeable.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java
  • keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingManager.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java
  • config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinPlayer.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java
🔇 Additional comments (12)
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java (2)

20-21: Well-structured Mixin class!

The class follows Mixin best practices with proper annotation and abstract declaration.


22-30: Verify consistent implementation across projectile mixins.

Since this Mixin modifies core projectile behavior, we should ensure consistency with other projectile-related mixins.

Let's check for similar patterns in other projectile mixins:

✅ Verification successful

Implementation verified as consistent with projectile mixin patterns

The implementation follows the standard pattern used across other projectile mixins in the codebase, particularly matching MixinThrowableProjectile and MixinShulkerBullet exactly. MixinAbstractArrow has justified variations due to arrow-specific mechanics but maintains the same core event-driven approach.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other projectile-related mixins and their event usage
echo "Checking for other projectile mixins..."
rg -l "ProjectileDeflection" --type java

echo "Checking for PROJECTILE_HIT event usage..."
rg -l "PROJECTILE_HIT" --type java

echo "Checking for similar @WrapOperation patterns..."
ast-grep --pattern 'class $_ {
  @WrapOperation(method = "tick")
  $_(ProjectileDeflection $_) {
    $$$
  }
}'

Length of output: 2654


Script:

#!/bin/bash
# Check implementation patterns in other projectile mixins
echo "Checking MixinThrowableProjectile implementation..."
rg -A 10 "@WrapOperation.*tick" skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java

echo "Checking MixinAbstractArrow implementation..."
rg -A 10 "@WrapOperation.*tick" skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java

echo "Checking MixinShulkerBullet implementation..."
rg -A 10 "@WrapOperation.*tick" skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java

Length of output: 4257

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1)

27-29: Simplify return value handling

The current implementation calls the original operation but ignores its result, always returning the deflection from the changeable. Consider whether the original operation's result should be used when no custom deflection is set.

-        if (resultChangeable.get() != EntityEvents.ProjectileHitResult.DEFAULT) return deflectionChangeable.get();
-        original.call(instance, result);
-        return deflectionChangeable.get();
+        ProjectileDeflection originalDeflection = original.call(instance, result);
+        return deflectionChangeable.get() != ProjectileDeflection.NONE ? deflectionChangeable.get() : originalDeflection;

Let's verify if there are any other mixins or event handlers that might rely on the current behavior:

keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/KeybindingCategory.java (1)

15-17: Add null check for ModuleConstants.MOD_ID.

The getCategoryString() method assumes ModuleConstants.MOD_ID is non-null. Consider adding a null check.

keybinding-common/src/main/java/io/github/manasmods/manascore/keybinding/api/ManasKeybinding.java (1)

15-127: Add missing @OverRide annotations

The class extends KeyMapping but lacks @Override annotations on inherited methods.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)

87-89: Use correct constructor for ResourceLocation

The method ResourceLocation.fromNamespaceAndPath may not exist. Instead, you should use the constructor new ResourceLocation(namespace, path) to create a ResourceLocation instance.

Apply this diff to fix the issue:

- return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath());
+ return new ResourceLocation(id.getNamespace(), "icons/skills/" + id.getPath());
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1)

20-25: Ensure correct use of @ModifyVariable and local variables

The use of @ModifyVariable with @Local(argsOnly = true) assumes that damageSource is a local variable within the target method at the specified injection point. Verify that damageSource is indeed available as a local variable at ordinal 6 in the actuallyHurt method of Player class. If not, this mixin may fail at runtime.

Please confirm the availability of damageSource at the injection point. You can use the following script to decompile the target method and inspect the local variables:

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java (1)

19-25: Verify parameters in @WrapOperation method

In the mixin method onSetTarget, ensure that the parameters match the expected signature when using @WrapOperation. The Operation parameter should be last, and @WrapOperation methods may require additional annotations or adjustments depending on the Mixin version.

Please verify that the method signature correctly matches the expectations of the Mixin framework. Refer to the Mixin documentation for @WrapOperation to ensure compatibility.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (2)

19-22: LGTM! Well-structured utility class.

The class follows the utility class pattern with a private constructor to prevent instantiation.


42-44: Add runtime null validation.

While the method uses @nonnull annotation, it lacks runtime validation for the entity parameter.

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)

25-30: Correct Generic Type Parameters in Registrar Builder.

The generic type parameter specification in the Registrar builder needs correction.


110-118: Potential Null Pointer Exception in PROJECTILE_HIT Event.

The event handler needs null checking for the entity from hitResult.

Comment on lines 98 to 111
this.action = () -> {
if (!PRESSED_KEYBINDINGS.containsKey(this)) {
PRESSED_KEYBINDINGS.put(this, System.currentTimeMillis());
action.onPress();
}
};
this.release = () -> {
if (PRESSED_KEYBINDINGS.containsKey(this)) {
long start = PRESSED_KEYBINDINGS.remove(this);
long end = System.currentTimeMillis();
release.onRelease(end - start);
}
};
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add duration bounds checking and overflow protection

The key press duration calculation has two issues:

  1. No maximum duration limit could lead to resource waste
  2. Potential integer overflow in duration calculation
             this.action = () -> {
                 if (!PRESSED_KEYBINDINGS.containsKey(this)) {
                     PRESSED_KEYBINDINGS.put(this, System.currentTimeMillis());
                     action.onPress();
                 }
             };
             this.release = () -> {
                 if (PRESSED_KEYBINDINGS.containsKey(this)) {
                     long start = PRESSED_KEYBINDINGS.remove(this);
                     long end = System.currentTimeMillis();
-                    release.onRelease(end - start);
+                    long duration = Math.max(0, Math.min(end - start, Long.MAX_VALUE));
+                    // Limit maximum duration to prevent resource waste
+                    if (duration > TimeUnit.MINUTES.toMillis(5)) {
+                        LOGGER.warn("Key {} was pressed for too long: {} ms", this, duration);
+                        return;
+                    }
+                    release.onRelease(duration);
                 }
             };

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

}

public AttributeModifier create(double i) {
return new AttributeModifier(this.id, this.amount * i, this.operation);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect use of AttributeModifier constructor and identifiers

The AttributeModifier constructor expects a UUID and a String as parameters, but a ResourceLocation is being used instead. Additionally, methods like removeModifier(UUID id) require a UUID, but a ResourceLocation is provided. This will cause runtime errors as the types do not match.

Apply this diff to fix the issue:

In AttributeTemplate, modify the id to be a UUID and include a name:

- public static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) {
+ public static record AttributeTemplate(UUID id, String name, double amount, AttributeModifier.Operation operation) {

Update the constructor accordingly:

- public AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) {
+ public AttributeTemplate(UUID id, String name, double amount, AttributeModifier.Operation operation) {

Adjust the create method to use the correct parameters:

- return new AttributeModifier(this.id, this.amount * i, this.operation);
+ return new AttributeModifier(this.id, this.name, this.amount * i, this.operation);

Update the methods where AttributeTemplate is used:

In addHeldAttributeModifier:

- public void addHeldAttributeModifier(Holder<Attribute> holder, ResourceLocation resourceLocation, double amount, AttributeModifier.Operation operation) {
+ public void addHeldAttributeModifier(Holder<Attribute> holder, UUID id, String name, double amount, AttributeModifier.Operation operation) {

In addHeldAttributeModifiers and removeAttributeModifiers, ensure that the correct UUID is used for adding and removing modifiers:

- attributeInstance.removeModifier(entry.getValue().id());
+ attributeInstance.removeModifier(entry.getValue().id());

Also applies to: 230-231, 244-244

Comment on lines +101 to +107
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManasSkill skill = (ManasSkill) o;
return Objects.equals(getRegistryName(), skill.getRegistryName());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Override hashCode() when equals() is overridden

Since equals() is overridden, it's important to also override hashCode() to maintain the general contract between equals() and hashCode(). This ensures consistent behavior when instances are used in hash-based collections.

Consider adding the following hashCode() method:

+    @Override
+    public int hashCode() {
+        return Objects.hash(getRegistryName());
+    }
📝 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
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManasSkill skill = (ManasSkill) o;
return Objects.equals(getRegistryName(), skill.getRegistryName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManasSkill skill = (ManasSkill) o;
return Objects.equals(getRegistryName(), skill.getRegistryName());
}
@Override
public int hashCode() {
return Objects.hash(getRegistryName());
}

Comment on lines +52 to +68
public static void skillActivationPacket(int keyNumber) {
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillActivationPacket(keyNumber);
}
}

/**
* Send {@link InternalSkillPacketActions#sendSkillReleasePacket} with a DistExecutor on client side.
* Used when player release a skill activation key bind.
*
* @see InternalSkillPacketActions#sendSkillReleasePacket
*/
public static void skillReleasePacket(int keyNumber, int heldTicks) {
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillReleasePacket(keyNumber, heldTicks);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for packet methods.

The methods skillActivationPacket and skillReleasePacket should validate their input parameters:

  • keyNumber should be non-negative
  • heldTicks should be non-negative
 public static void skillActivationPacket(int keyNumber) {
+    if (keyNumber < 0) {
+        throw new IllegalArgumentException("keyNumber must be non-negative");
+    }
     if (Platform.getEnvironment() == Env.CLIENT) {
         InternalSkillPacketActions.sendSkillActivationPacket(keyNumber);
     }
 }

 public static void skillReleasePacket(int keyNumber, int heldTicks) {
+    if (keyNumber < 0 || heldTicks < 0) {
+        throw new IllegalArgumentException("keyNumber and heldTicks must be non-negative");
+    }
     if (Platform.getEnvironment() == Env.CLIENT) {
         InternalSkillPacketActions.sendSkillReleasePacket(keyNumber, heldTicks);
     }
 }
📝 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 void skillActivationPacket(int keyNumber) {
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillActivationPacket(keyNumber);
}
}
/**
* Send {@link InternalSkillPacketActions#sendSkillReleasePacket} with a DistExecutor on client side.
* Used when player release a skill activation key bind.
*
* @see InternalSkillPacketActions#sendSkillReleasePacket
*/
public static void skillReleasePacket(int keyNumber, int heldTicks) {
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillReleasePacket(keyNumber, heldTicks);
}
}
public static void skillActivationPacket(int keyNumber) {
if (keyNumber < 0) {
throw new IllegalArgumentException("keyNumber must be non-negative");
}
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillActivationPacket(keyNumber);
}
}
/**
* Send {@link InternalSkillPacketActions#sendSkillReleasePacket} with a DistExecutor on client side.
* Used when player release a skill activation key bind.
*
* @see InternalSkillPacketActions#sendSkillReleasePacket
*/
public static void skillReleasePacket(int keyNumber, int heldTicks) {
if (keyNumber < 0 || heldTicks < 0) {
throw new IllegalArgumentException("keyNumber and heldTicks must be non-negative");
}
if (Platform.getEnvironment() == Env.CLIENT) {
InternalSkillPacketActions.sendSkillReleasePacket(keyNumber, heldTicks);
}
}

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

🧹 Nitpick comments (18)
skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java (3)

1-4: Update the copyright year to 2024.

The copyright notice shows 2025, which is incorrect as it's currently 2024.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

36-44: Add validation for negative damage values.

The modified damage value should be validated to ensure it's not negative.

         if (EntityEvents.LIVING_DAMAGE.invoker().damage(e.getEntity(), e.getSource(), changeableDamage).isFalse()) {
             e.setNewDamage(0);
         } else {
-            e.setNewDamage(changeableDamage.get());
+            float newDamage = changeableDamage.get();
+            e.setNewDamage(Math.max(0, newDamage));
         }

20-21: Add class-level documentation.

The class would benefit from documentation explaining its role in the skill system and how it bridges NeoForge events with the ManasCore skill system.

 @EventBusSubscriber
+/**
+ * Bridges NeoForge events with ManasCore's skill system.
+ * This class listens to NeoForge events and translates them into skill system events,
+ * allowing skills to modify entity targeting, damage, and projectile behavior.
+ */
 public class NeoForgeCommonEventInvoker {
skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1)

3-3: Add issue tracker URL.

An empty issue tracker URL makes it difficult for users to report issues. Consider adding the GitHub repository's issue tracker URL.

-issueTrackerURL = ""
+issueTrackerURL = "https://github.com/ManasMods/ManasCore/issues"
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (2)

40-44: Consider adding @OverRide annotations to overridden methods

Adding @Override annotations to methods that override superclass methods enhances code readability and ensures that any discrepancies in method signatures are caught at compile time.


126-138: Review damage handling for balance considerations

The method onDamageEntity modifies damage amounts significantly, such as instant-killing Creepers and increasing damage to Players tenfold. While acceptable for testing, consider the impact on game balance if this code is used beyond testing scenarios.

keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingCategory.java (2)

11-14: Add input validation and documentation for the name parameter.

Consider adding:

  1. Input validation to ensure the name parameter is not null or empty
  2. JavaDoc explaining the expected format and constraints for the name parameter
 @RequiredArgsConstructor(staticName = "of")
 public class KeybindingCategory {
+    /**
+     * @param name The category name. Must not be null or empty.
+     *             Should only contain lowercase letters, numbers, and underscores.
+     */
     private final String name;
+
+    public static KeybindingCategory of(String name) {
+        if (name == null || name.trim().isEmpty()) {
+            throw new IllegalArgumentException("Category name must not be null or empty");
+        }
+        return new KeybindingCategory(name.trim());
+    }

15-17: Consider externalizing the category string format.

The hardcoded format string could be problematic for internationalization. Consider making it configurable.

+    private static final String CATEGORY_FORMAT = "%s.category.%s";
+
     public String getCategoryString() {
-        return String.format("%s.category.%s", ModuleConstants.MOD_ID, this.name);
+        return String.format(CATEGORY_FORMAT, ModuleConstants.MOD_ID, this.name);
     }
keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/ManasKeybinding.java (2)

98-103: Add cleanup mechanism for stale entries.

The current implementation might lead to memory leaks if a key release event is missed. Consider adding a cleanup mechanism or timeout for stale entries in the PRESSED_KEYBINDINGS map.

     this.action = () -> {
         if (!PRESSED_KEYBINDINGS.containsKey(this)) {
+            // Clean up stale entries older than 5 minutes
+            long currentTime = System.currentTimeMillis();
+            PRESSED_KEYBINDINGS.entrySet().removeIf(entry -> 
+                (currentTime - entry.getValue()) > 300000);
             PRESSED_KEYBINDINGS.put(this, System.currentTimeMillis());
             action.onPress();
         }
     };

107-108: Consider using System.nanoTime() for better precision.

For accurate duration measurements, System.nanoTime() provides better precision than currentTimeMillis(). This is especially important for gaming input handling.

-                    long start = PRESSED_KEYBINDINGS.remove(this);
-                    long end = System.currentTimeMillis();
+                    long start = PRESSED_KEYBINDINGS.remove(this);
+                    long end = System.nanoTime();
+                    long durationNanos = end - start;
+                    long durationMillis = durationNanos / 1_000_000;
keybind-fabric/src/main/resources/fabric.mod.json (2)

10-12: Consider adding homepage URL.

The homepage field is currently empty. Consider adding the GitHub repository URL for better discoverability.

   "contact": {
-    "homepage": ""
+    "homepage": "https://github.com/ManasMods/ManasCore"
   },

28-28: Consider supporting older Java versions.

Java 21 is a very recent version. Consider supporting Java 17 (LTS) to increase adoption, as many users might not have upgraded yet.

keybind-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1)

3-3: Add issue tracker URL.

The issue tracker URL is empty. Consider adding the GitHub issues URL for better user support.

-issueTrackerURL = ""
+issueTrackerURL = "https://github.com/ManasMods/ManasCore/issues"
build.gradle (1)

270-278: Consider using a more robust approach for modifying configuration files.

The current string-based modification of configuration files could be fragile. Consider:

  1. Using a proper TOML parser for neoforge.mods.toml
  2. Adding error handling for malformed files
  3. Validating the modified content before writing

Example approach using a TOML parser:

def toml = new TomlParser().parse(file('META-INF/neoforge.mods.toml'))
if (project.name.startsWith("skill")) {
    toml.accessWidener = "manascore_skill-neoforge.accesswidener"
}
file.text = toml.write()
keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java (2)

1-4: Update the copyright year to the current year.

The copyright year is set to 2025, which is in the future. Consider updating it to 2024 or using a range (e.g., 2024-2025) if you want to future-proof it.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

12-17: Enhance robustness and documentation.

Consider the following improvements:

  1. Add JavaDoc to explain the class purpose and why inheritance is prevented
  2. Add error handling around the init() call
  3. Add logging to indicate successful initialization

Here's a suggested implementation:

+/**
+ * NeoForge entry point for the ManasCore Keybinding module.
+ * This class is marked final as it serves as a singleton mod instance.
+ */
 @Mod(ModuleConstants.MOD_ID)
 public final class ManasCoreKeybindNeoForge {
+    private static final Logger LOGGER = LogUtils.getLogger();
+
     public ManasCoreKeybindNeoForge() {
+        try {
             ManasCoreKeybind.init();
+            LOGGER.info("ManasCore Keybinding module initialized successfully");
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCore Keybinding module", e);
+            throw e;
+        }
     }
 }
skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java (1)

28-32: Extract common damage handling logic

This code duplicates the logic from MixinLivingEntity. Consider extracting the common damage handling logic into a shared utility class.

Create a new utility class:

public class DamageHandlingUtils {
    public static boolean handleDamage(LivingEntity entity, DamageSource source, 
            float amount, CallbackInfo ci, Stack<DamageContainer> containers) {
        Changeable<Float> changeable = Changeable.of(amount);
        if (EntityEvents.LIVING_HURT.invoker().hurt(entity, source, changeable).isFalse()) {
            ci.cancel();
            return false;
        }
        Float newDamage = changeable.get();
        if (newDamage != null && !containers.isEmpty()) {
            containers.peek().setNewDamage(newDamage);
        }
        return true;
    }
}

Then use it in both mixins:

 void onHurt(DamageSource source, float amount, CallbackInfo ci) {
-    Changeable<Float> changeable = Changeable.of(amount);
-    if (EntityEvents.LIVING_HURT.invoker().hurt((Player) (Object) this, source, changeable).isFalse()) ci.cancel();
-    else damageContainers.peek().setNewDamage(changeable.get());
+    DamageHandlingUtils.handleDamage((Player)(Object)this, source, amount, ci, damageContainers);
 }
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1)

20-40: Consider a unified cross-platform damage handling approach

The current implementation duplicates damage handling logic across four different mixin classes (NeoForge and Fabric for both Player and LivingEntity).

Consider these improvements:

  1. Create a common damage handling interface:
public interface DamageHandler {
    float modifyDamage(float amount, DamageSource source, LivingEntity entity);
    boolean shouldCancelDamage(float amount, DamageSource source, LivingEntity entity);
    float modifyTotalDamage(float amount, DamageSource source, LivingEntity entity);
}
  1. Implement platform-specific handlers:
public class FabricDamageHandler implements DamageHandler {
    // Fabric-specific implementation
}

public class NeoForgeDamageHandler implements DamageHandler {
    // NeoForge-specific implementation
}
  1. Use a factory pattern to get the appropriate handler:
public class DamageHandlerFactory {
    public static DamageHandler getHandler() {
        // Return platform-specific handler
    }
}

This approach would:

  • Reduce code duplication
  • Make platform differences explicit
  • Simplify maintenance
  • Make testing easier
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c96dcb2 and 75ebe0c.

⛔ Files ignored due to path filters (3)
  • keybind-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • keybind-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • keybind-neoforge/src/main/resources/icon.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • build.gradle (1 hunks)
  • keybind-common/build.gradle (1 hunks)
  • keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybind.java (1 hunks)
  • keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybindClient.java (1 hunks)
  • keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingCategory.java (1 hunks)
  • keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingManager.java (1 hunks)
  • keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/ManasKeybinding.java (1 hunks)
  • keybind-common/src/main/resources/architectury.common.json (1 hunks)
  • keybind-common/src/main/resources/manascore_keybind.accesswidener (1 hunks)
  • keybind-common/src/main/resources/manascore_keybind.mixins.json (1 hunks)
  • keybind-fabric/build.gradle (1 hunks)
  • keybind-fabric/src/main/java/io/github/manasmods/manascore/keybind/fabric/ManasCoreKeybindFabric.java (1 hunks)
  • keybind-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • keybind-neoforge/build.gradle (1 hunks)
  • keybind-neoforge/gradle.properties (1 hunks)
  • keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java (1 hunks)
  • keybind-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • settings.gradle (2 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java (1 hunks)
  • skill-common/src/main/resources/manascore_skill.accesswidener (1 hunks)
  • skill-common/src/main/resources/manascore_skill.mixins.json (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java (1 hunks)
  • skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • skill-neoforge/src/main/resources/manascore_skill-neoforge.mixins.json (1 hunks)
  • testing-common/build.gradle (2 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (3 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • keybind-neoforge/gradle.properties
  • keybind-common/src/main/resources/architectury.common.json
  • keybind-common/src/main/resources/manascore_keybind.accesswidener
  • keybind-fabric/build.gradle
  • keybind-common/build.gradle
  • keybind-common/src/main/resources/manascore_keybind.mixins.json
  • skill-neoforge/src/main/resources/manascore_skill-neoforge.mixins.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • skill-common/src/main/resources/manascore_skill.mixins.json
  • skill-common/src/main/resources/manascore_skill.accesswidener
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java
  • settings.gradle
🔇 Additional comments (19)
skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java (1)

20-24: Well-structured event invoker setup!

Good practices observed:

  • Appropriate use of @EventBusSubscriber annotation
  • Private constructor prevents instantiation, which is correct for a utility class
skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml (4)

14-14: Verify logo file existence.

Ensure that icon.png exists in the correct resources directory.

✅ Verification successful

Logo file verification successful

The referenced icon.png exists at the correct location in the resources directory.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the icon file exists in the resources directory
fd -t f "icon.png" skill-neoforge/src/main/resources/

Length of output: 96


19-19: Consider constraining NeoForge version range.

Similar to the loader version, the NeoForge version range [21,) is very broad. Consider setting an upper bound to ensure compatibility.


1-2: Consider constraining the loader version range.

The version range [2,) is very broad and might include incompatible future versions. Consider setting an upper bound to ensure compatibility.

✅ Verification successful

Consider using a more specific loader version range

The current range [2,) is too broad. Based on the NeoForge version patterns, consider using [2,3) to ensure compatibility with the current major version while preventing potential issues with future major versions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the latest NeoForge versions to determine an appropriate upper bound
curl -s https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml | grep -oP '<version>\K[^<]+'

Length of output: 10141


37-42: Verify circular dependency.

The manascore_network dependency might create a circular dependency since it's using the same version as this module. Please verify the dependency structure.

✅ Verification successful

No circular dependency detected

The dependency structure appears to be hierarchical, with manascore_network serving as a core module that other feature modules depend on. Using the same version across modules is a common practice in monorepo setups where components are released together. No reverse dependencies were found that would create a circular relationship.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential circular dependencies in other mod configuration files
fd -t f "mods.toml" | xargs rg "modId.*=.*manascore_skill"

Length of output: 58


Script:

#!/bin/bash
# Find the mod_id value and dependencies in both directions
echo "=== Searching for mod_id definition ==="
fd -t f "mods.toml" | xargs rg "mod_id.*=|modId.*=.*skill"

echo -e "\n=== Searching for manascore dependencies ==="
fd -t f "mods.toml" | xargs rg "modId.*=.*manascore"

echo -e "\n=== Searching for version definition ==="
fd -t f "gradle.properties" | xargs cat

Length of output: 1568

testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (3)

43-43: Verify the usage of ResourceLocation.withDefaultNamespace

In the constructor, you use ResourceLocation.withDefaultNamespace("skill.speed"). Please verify that this method correctly creates a ResourceLocation with the intended namespace. If not, consider using new ResourceLocation("skill.speed") for clarity.


118-124: Ensure damage cancellation works as expected

In the onBeingDamaged method, returning false when the source is cactus indicates that the damage should be canceled. Confirm that this behavior appropriately prevents damage from cacti in the game logic.


150-157: Check healing logic in damage taken event

In the onTakenDamage method, when the owner is sneaking, they heal the amount of damage taken. Ensure this logic aligns with intended gameplay mechanics and doesn't introduce unintended invincibility.

keybind-fabric/src/main/java/io/github/manasmods/manascore/keybind/fabric/ManasCoreKeybindFabric.java (1)

10-14: Initialization code is correctly implemented

The ManasCoreKeybindFabric class correctly implements the ModInitializer interface and calls ManasCoreKeybind.init() in onInitialize().

keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybind.java (1)

11-16: Proper client-side initialization check

The init() method appropriately checks the environment and initializes client-specific keybinds when running on the client side.

keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybindClient.java (1)

11-15: Clean implementation of client-side initialization!

The class follows single responsibility principle and correctly uses Architectury's event system for client-side initialization.

keybind-neoforge/build.gradle (1)

6-12: LGTM! Build configuration is properly set up.

The access widener path is correctly configured and the jar remapping is properly set up.

testing-common/build.gradle (1)

11-14: LGTM! Module additions align with PR objectives.

The new modules :skill, :keybind, and :config have been properly added to the includedProjects list, maintaining consistency with the existing structure.

keybind-fabric/src/main/resources/fabric.mod.json (1)

16-21: Verify if client entrypoints are needed for keybinding functionality.

The keybinding module typically requires client-side initialization, but the client entrypoints array is empty.

keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java (2)

6-10: LGTM!

The package structure follows Java conventions, and imports are minimal and necessary.


14-16: Verify the initialization sequence.

Based on the AI summary, ManasCoreKeybind.init() should only execute client-side code when in the client environment. Let's verify this implementation:

✅ Verification successful

Initialization sequence is properly implemented

The code correctly ensures client-side initialization by checking the environment before executing client-specific code. This follows the established pattern used throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the initialization sequence and environment handling

# Check ManasCoreKeybind.init() implementation
ast-grep --pattern 'class ManasCoreKeybind {
  $$$
  static void init() {
    $$$
  }
  $$$
}'

# Check for proper environment checks
rg -A 5 'Env\.CLIENT' 

Length of output: 5669

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java (2)

33-38: Add null check and document the ordinal value

The previous review comment about adding null checks and documenting the ordinal value is still valid.


21-26: ⚠️ Potential issue

Add null check for damage modification

Similar to the NeoForge implementation, add null safety checks.

Apply these changes:

 float modifyDamage(float amount, @Local(argsOnly = true) DamageSource damageSource) {
     Changeable<Float> changeable = Changeable.of(amount);
-    if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F;
-    return changeable.get();
+    if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, damageSource, changeable).isFalse()) {
+        return 0.0F;
+    }
+    Float newDamage = changeable.get();
+    return newDamage != null ? newDamage : amount;
 }

Likely invalid or redundant comment.

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1)

1-40: Enhance test coverage for damage handling

The damage handling modifications are critical to gameplay, but only basic tests are present in test_coderabbit_num.py.

Consider adding these test scenarios:

  1. Null damage values
  2. Empty damage containers
  3. Event cancellation
  4. Platform-specific behavior differences

Run this script to analyze current test coverage:

Comment on lines +25 to +34
@SubscribeEvent
static void onLivingChangeTarget(final LivingChangeTargetEvent e) {
if (!e.getTargetType().equals(LivingChangeTargetEvent.LivingTargetType.MOB_TARGET)) return;
Changeable<LivingEntity> changeableTarget = Changeable.of(e.getNewAboutToBeSetTarget());
if (EntityEvents.LIVING_CHANGE_TARGET.invoker().changeTarget(e.getEntity(), changeableTarget).isFalse()) {
e.setCanceled(true);
} else {
e.setNewAboutToBeSetTarget(changeableTarget.get());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null check for target entity.

The getNewAboutToBeSetTarget() could return null, which might cause NPE when creating the Changeable instance.

-        Changeable<LivingEntity> changeableTarget = Changeable.of(e.getNewAboutToBeSetTarget());
+        LivingEntity newTarget = e.getNewAboutToBeSetTarget();
+        Changeable<LivingEntity> changeableTarget = Changeable.of(newTarget);
+        if (newTarget == null) return;

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

Comment on lines +46 to +52
@SubscribeEvent
static void onProjectileHit(final ProjectileImpactEvent e) {
Changeable<EntityEvents.ProjectileHitResult> result = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT);
Changeable<ProjectileDeflection> deflection = Changeable.of(ProjectileDeflection.NONE);
EntityEvents.PROJECTILE_HIT.invoker().hit(e.getRayTraceResult(), e.getProjectile(), deflection, result);
if (!Objects.equals(result.get(), EntityEvents.ProjectileHitResult.DEFAULT)) e.setCanceled(true);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address unused deflection parameter and add documentation.

The deflection parameter is created but its value is never used in the logic. Also, the method would benefit from documentation explaining the cancellation behavior.

+    /**
+     * Handles projectile impact events.
+     * The event is canceled if the hit result is different from the default,
+     * allowing skills to modify projectile behavior.
+     *
+     * @param e The projectile impact event
+     */
     @SubscribeEvent
     static void onProjectileHit(final ProjectileImpactEvent e) {
         Changeable<EntityEvents.ProjectileHitResult> result = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT);
         Changeable<ProjectileDeflection> deflection = Changeable.of(ProjectileDeflection.NONE);
         EntityEvents.PROJECTILE_HIT.invoker().hit(e.getRayTraceResult(), e.getProjectile(), deflection, result);
-        if (!Objects.equals(result.get(), EntityEvents.ProjectileHitResult.DEFAULT)) e.setCanceled(true);
+        if (!Objects.equals(result.get(), EntityEvents.ProjectileHitResult.DEFAULT)) {
+            e.setCanceled(true);
+            // Apply deflection if modified
+            if (!Objects.equals(deflection.get(), ProjectileDeflection.NONE)) {
+                e.getProjectile().setDeltaMovement(e.getProjectile().getDeltaMovement().multiply(deflection.get().getMultiplier()));
+            }
+        }
     }

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

Comment on lines +44 to +48
[[mixins]]
config = "${mod_id}.mixins.json"

[[mixins]]
config = "manascore_skill-neoforge.mixins.json"
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Remove redundant mixin configuration

The second mixin configuration manascore_skill-neoforge.mixins.json is redundant and inconsistent with the project's pattern. All other NeoForge modules use only the ${mod_id}.mixins.json configuration. Remove the second entry and ensure all mixins are consolidated into the primary configuration file.

  • Remove the second [[mixins]] block referencing manascore_skill-neoforge.mixins.json
  • Move any unique mixins from manascore_skill-neoforge.mixins.json to the primary configuration file
🔗 Analysis chain

Consider consolidating mixin configurations.

Having two separate mixin configurations is unusual. Consider consolidating them into a single configuration file. Also, verify that both mixin JSON files exist.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if both mixin configuration files exist
echo "Checking for mixin configuration files..."
fd -t f "mixins.json" skill-neoforge/src/main/resources/

# Check the content of mixin configurations to see if they can be consolidated
echo "Checking mixin configuration contents..."
fd -t f "mixins.json" skill-neoforge/src/main/resources/ -x cat

Length of output: 650


Script:

#!/bin/bash
# Search for any .mixins.json files and gradle/properties files
echo "Searching for mixin configurations and mod properties..."
fd -t f "\.mixins\.json$|gradle\.properties$" 

# Search for mixin-related references in the codebase
echo "Searching for mixin references in code..."
rg -g "!*.mixins.json" "mixins?.json" 

Length of output: 2519

import java.util.ArrayList;

public class KeybindingManager {
private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider thread-safe collection for keybindings.

Using a non-synchronized ArrayList could lead to concurrent modification issues. Consider using a thread-safe collection.

-    private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
+    private static final List<ManasKeybinding> keybindings = 
+        Collections.synchronizedList(new ArrayList<>());
📝 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 static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
private static final List<ManasKeybinding> keybindings =
Collections.synchronizedList(new ArrayList<>());

Comment on lines +13 to +15
public class KeybindingManager {
private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make the utility class final and add a private constructor.

Utility classes should be final and have a private constructor to prevent instantiation.

-public class KeybindingManager {
+public final class KeybindingManager {
     private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
+
+    private KeybindingManager() {
+        throw new UnsupportedOperationException("Utility class");
+    }
📝 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 class KeybindingManager {
private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
public final class KeybindingManager {
private static final ArrayList<ManasKeybinding> keybindings = new ArrayList<>();
private KeybindingManager() {
throw new UnsupportedOperationException("Utility class");
}

Comment on lines +30 to +38
ClientTickEvent.CLIENT_POST.register(instance -> {
for (final ManasKeybinding keybinding : keybindings) {
if (keybinding.isDown()) {
keybinding.getAction().onPress();
} else if (keybinding.getRelease() != null) {
keybinding.getRelease().run();
}
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for action execution.

The event handler should catch and handle exceptions to prevent crashes from failed keybinding actions.

     ClientTickEvent.CLIENT_POST.register(instance -> {
-        for (final ManasKeybinding keybinding : keybindings) {
-            if (keybinding.isDown()) {
-                keybinding.getAction().onPress();
-            } else if (keybinding.getRelease() != null) {
-                keybinding.getRelease().run();
+        synchronized (keybindings) {
+            for (final ManasKeybinding keybinding : keybindings) {
+                try {
+                    if (keybinding.isDown()) {
+                        keybinding.getAction().onPress();
+                    } else if (keybinding.getRelease() != null) {
+                        keybinding.getRelease().run();
+                    }
+                } catch (Exception e) {
+                    LOGGER.error("Failed to execute keybinding action", e);
+                }
             }
         }
     });

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

import java.util.HashMap;

public class ManasKeybinding extends KeyMapping {
private static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using ConcurrentHashMap for thread safety.

The static PRESSED_KEYBINDINGS HashMap is accessed from key press/release events which might occur on different threads. Using ConcurrentHashMap would prevent potential race conditions.

-    private static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
+    private static final ConcurrentHashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new ConcurrentHashMap<>();
📝 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 static final HashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new HashMap<>();
private static final ConcurrentHashMap<ManasKeybinding, Long> PRESSED_KEYBINDINGS = new ConcurrentHashMap<>();

Comment on lines 17 to 30
KeybindingManager.register(
new ManasKeybinding("manascore.keybinding.test",
category, () -> ManasCoreTesting.LOG.info("Pressing"),
duration -> ManasCoreTesting.LOG.info("Released in {} Seconds", duration / 1000.0)
),
new ManasKeybinding("manascore.keybinding.test_press", category, () -> ManasCoreTesting.LOG.info("Pressed")),
new ManasKeybinding("manascore.keybinding.skill", category,
() -> SkillAPI.skillActivationPacket(0),
duration -> SkillAPI.skillReleasePacket(0, (int) (duration / 50))),
new ManasKeybinding("manascore.keybinding.skill_2", category,
() -> SkillAPI.skillActivationPacket(1),
duration -> SkillAPI.skillReleasePacket(1, (int) (duration / 50))),
new ManasKeybinding("manascore.keybinding.skill_toggle", category, SkillAPI::skillTogglePacket)
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for skill activation/release packets.

The test keybindings directly call SkillAPI methods without any error handling. Consider wrapping these calls in try-catch blocks to handle potential network or API failures gracefully.

     KeybindingManager.register(
         new ManasKeybinding("manascore.keybinding.skill", category,
-                () -> SkillAPI.skillActivationPacket(0),
-                duration -> SkillAPI.skillReleasePacket(0, (int) (duration / 50))),
+                () -> {
+                    try {
+                        SkillAPI.skillActivationPacket(0);
+                    } catch (Exception e) {
+                        ManasCoreTesting.LOG.error("Failed to send skill activation packet", e);
+                    }
+                },
+                duration -> {
+                    try {
+                        SkillAPI.skillReleasePacket(0, (int) (duration / 50));
+                    } catch (Exception e) {
+                        ManasCoreTesting.LOG.error("Failed to send skill release packet", e);
+                    }
+                }),
📝 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
KeybindingManager.register(
new ManasKeybinding("manascore.keybinding.test",
category, () -> ManasCoreTesting.LOG.info("Pressing"),
duration -> ManasCoreTesting.LOG.info("Released in {} Seconds", duration / 1000.0)
),
new ManasKeybinding("manascore.keybinding.test_press", category, () -> ManasCoreTesting.LOG.info("Pressed")),
new ManasKeybinding("manascore.keybinding.skill", category,
() -> SkillAPI.skillActivationPacket(0),
duration -> SkillAPI.skillReleasePacket(0, (int) (duration / 50))),
new ManasKeybinding("manascore.keybinding.skill_2", category,
() -> SkillAPI.skillActivationPacket(1),
duration -> SkillAPI.skillReleasePacket(1, (int) (duration / 50))),
new ManasKeybinding("manascore.keybinding.skill_toggle", category, SkillAPI::skillTogglePacket)
);
KeybindingManager.register(
new ManasKeybinding("manascore.keybinding.test",
category, () -> ManasCoreTesting.LOG.info("Pressing"),
duration -> ManasCoreTesting.LOG.info("Released in {} Seconds", duration / 1000.0)
),
new ManasKeybinding("manascore.keybinding.test_press", category, () -> ManasCoreTesting.LOG.info("Pressed")),
new ManasKeybinding("manascore.keybinding.skill", category,
() -> {
try {
SkillAPI.skillActivationPacket(0);
} catch (Exception e) {
ManasCoreTesting.LOG.error("Failed to send skill activation packet", e);
}
},
duration -> {
try {
SkillAPI.skillReleasePacket(0, (int) (duration / 50));
} catch (Exception e) {
ManasCoreTesting.LOG.error("Failed to send skill release packet", e);
}
}),
new ManasKeybinding("manascore.keybinding.skill_2", category,
() -> SkillAPI.skillActivationPacket(1),
duration -> SkillAPI.skillReleasePacket(1, (int) (duration / 50))),
new ManasKeybinding("manascore.keybinding.skill_toggle", category, SkillAPI::skillTogglePacket)
);

Comment on lines +55 to +62
if (sender.getPlayer() != null) {
Entity entity = ((ServerLevel) sender.getPlayer().level()).getEntity(uuid);
if (entity instanceof LivingEntity living) {
Skills storage = SkillAPI.getSkillsFrom(living);
if (storage.learnSkill(RegistryTest.TEST_SKILL.get()))
ManasCoreTesting.LOG.info("Added Tested Skill to Entity!");
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and user feedback in skill learning.

The skill learning logic lacks proper error handling and user feedback. Consider adding appropriate error messages and success/failure notifications to the command sender.

     if (sender.getPlayer() != null) {
         Entity entity = ((ServerLevel) sender.getPlayer().level()).getEntity(uuid);
         if (entity instanceof LivingEntity living) {
             Skills storage = SkillAPI.getSkillsFrom(living);
-            if (storage.learnSkill(RegistryTest.TEST_SKILL.get()))
+            try {
+                if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
                     ManasCoreTesting.LOG.info("Added Tested Skill to Entity!");
+                    sender.sendSuccess(() -> Component.literal("Successfully added skill to entity"), true);
+                } else {
+                    sender.sendFailure(Component.literal("Entity already has this skill"));
+                }
+            } catch (Exception e) {
+                ManasCoreTesting.LOG.error("Failed to add skill to entity", e);
+                sender.sendFailure(Component.literal("Failed to add skill to entity: " + e.getMessage()));
+            }
         }
     }
📝 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
if (sender.getPlayer() != null) {
Entity entity = ((ServerLevel) sender.getPlayer().level()).getEntity(uuid);
if (entity instanceof LivingEntity living) {
Skills storage = SkillAPI.getSkillsFrom(living);
if (storage.learnSkill(RegistryTest.TEST_SKILL.get()))
ManasCoreTesting.LOG.info("Added Tested Skill to Entity!");
}
}
if (sender.getPlayer() != null) {
Entity entity = ((ServerLevel) sender.getPlayer().level()).getEntity(uuid);
if (entity instanceof LivingEntity living) {
Skills storage = SkillAPI.getSkillsFrom(living);
try {
if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
ManasCoreTesting.LOG.info("Added Tested Skill to Entity!");
sender.sendSuccess(() -> Component.literal("Successfully added skill to entity"), true);
} else {
sender.sendFailure(Component.literal("Entity already has this skill"));
}
} catch (Exception e) {
ManasCoreTesting.LOG.error("Failed to add skill to entity", e);
sender.sendFailure(Component.literal("Failed to add skill to entity: " + e.getMessage()));
}
}
}

Comment on lines +26 to +30
void onHurt(DamageSource source, float amount, CallbackInfo ci) {
Changeable<Float> changeable = Changeable.of(amount);
if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) ci.cancel();
else damageContainers.peek().setNewDamage(changeable.get());
}
Copy link

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 and empty stack validation

The code has potential null pointer risks:

  1. Missing null check for changeable.get()
  2. No validation for empty damageContainers stack

Apply these changes:

 void onHurt(DamageSource source, float amount, CallbackInfo ci) {
     Changeable<Float> changeable = Changeable.of(amount);
-    if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) ci.cancel();
-    else damageContainers.peek().setNewDamage(changeable.get());
+    if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) {
+        ci.cancel();
+        return;
+    }
+    Float newDamage = changeable.get();
+    if (newDamage != null && !damageContainers.isEmpty()) {
+        damageContainers.peek().setNewDamage(newDamage);
+    }
 }
📝 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
void onHurt(DamageSource source, float amount, CallbackInfo ci) {
Changeable<Float> changeable = Changeable.of(amount);
if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) ci.cancel();
else damageContainers.peek().setNewDamage(changeable.get());
}
void onHurt(DamageSource source, float amount, CallbackInfo ci) {
Changeable<Float> changeable = Changeable.of(amount);
if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) {
ci.cancel();
return;
}
Float newDamage = changeable.get();
if (newDamage != null && !damageContainers.isEmpty()) {
damageContainers.peek().setNewDamage(newDamage);
}
}

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

🧹 Nitpick comments (21)
attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java (1)

24-26: Injection point at HEAD can cause conflicts with other mixins.
Injecting at the head of hurt is fine but can conflict if other mixins also inject at the head for the same method in a different mod. Consider using a more precise injection point (e.g., BEFORE a particular line or method call) if you foresee potential mod conflicts.

attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java (2)

26-37: Consider extracting repeated critical-hit logic into a shared helper method.
The logic for determining multipliers, chance, and triggering critical events appears similar to the code in lines 42-55. Extracting this logic to a single private or utility method would reduce duplication and improve maintainability.

Example refactor sketch:

+private float applyCritLogic(LivingEntity attacker, LivingEntity target, float baseDamage) {
+    Changeable<Float> multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER));
+    Changeable<Double> chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
+    if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(attacker, target, 1, multiplier, chance).isFalse()) {
+        return baseDamage;
+    }
+    if (target.getRandom().nextFloat() > chance.get()) {
+        return baseDamage;
+    }
+    ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, attacker);
+    return baseDamage * multiplier.get();
+}

42-55: Clarify method name or add documentation for damage modifications.
The method signature getCritChanceDamage also adjusts the final damage to incorporate enchantment or events. Consider documenting the exact logic and side effects, or rename to reflect that it may perform advanced critical-hit checks that include enchantment.

attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java (4)

1-4: Update the copyright year.

The copyright year is set to 2025, which is in the future. Consider updating it to the current year (2024).

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

25-27: Improve entityTypes initialization.

Consider the following improvements:

  1. Add @SuppressWarnings("unchecked") to acknowledge the unchecked cast
  2. Make the field final for immutability
  3. Combine filter and map operations for better readability
+    @SuppressWarnings("unchecked")
-    private static final List<EntityType<? extends LivingEntity>> entityTypes = BuiltInRegistries.ENTITY_TYPE.stream()
-            .filter(DefaultAttributes::hasSupplier).map(entityType -> (EntityType<? extends LivingEntity>) entityType)
-            .collect(Collectors.toList());
+    private static final List<EntityType<? extends LivingEntity>> ENTITY_TYPES = BuiltInRegistries.ENTITY_TYPE.stream()
+            .filter(entityType -> DefaultAttributes.hasSupplier(entityType))
+            .map(entityType -> (EntityType<? extends LivingEntity>) entityType)
+            .collect(Collectors.toUnmodifiableList());

29-35: Enhance registration methods with validation and documentation.

The registration methods could benefit from:

  1. Null parameter validation
  2. Duplicate entry prevention
  3. JavaDoc documentation
+    /**
+     * Registers an attribute to be applied to player entities.
+     * @param holder The attribute holder to register
+     * @throws NullPointerException if holder is null
+     */
     public static void registerToPlayers(Holder<Attribute> holder) {
+        Objects.requireNonNull(holder, "Attribute holder cannot be null");
+        if (!PLAYER_REGISTRY.contains(holder)) {
             PLAYER_REGISTRY.add(holder);
+        }
     }

+    /**
+     * Registers an attribute to be applied to all living entities.
+     * @param holder The attribute holder to register
+     * @throws NullPointerException if holder is null
+     */
     public static void registerToGeneric(Holder<Attribute> holder) {
+        Objects.requireNonNull(holder, "Attribute holder cannot be null");
+        if (!GENERIC_REGISTRY.contains(holder)) {
             GENERIC_REGISTRY.add(holder);
+        }
     }

22-61: Consider architectural improvements for robustness.

The current implementation could benefit from the following architectural improvements:

  1. Add logging for debugging and monitoring
  2. Implement registration verification
  3. Add error handling for registration failures
  4. Consider adding a registration status query mechanism

Would you like me to provide a detailed implementation for these improvements?

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java (1)

3-7: Add class-level documentation.

The initialization logic is clean and follows good separation of concerns. Consider adding Javadoc to describe the class's purpose and initialization sequence.

Add class documentation:

+/**
+ * Common entry point for ManasCore attribute system initialization.
+ * This class delegates the initialization to platform-specific implementations
+ * through the ManasCoreAttributeRegister.
+ */
 public class ManasCoreAttribute {
attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeNeoForge.java (1)

7-12: Add error handling around initialization.

While the implementation follows NeoForge conventions, consider adding error handling around the initialization call to gracefully handle potential failures.

 @Mod(ModuleConstants.MOD_ID)
 public final class ManasCoreAttributeNeoForge {
     public ManasCoreAttributeNeoForge() {
+        try {
             ManasCoreAttribute.init();
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCore attributes", e);
+            throw new RuntimeException("Failed to initialize ManasCore attributes", e);
+        }
     }
+    
+    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(ManasCoreAttributeNeoForge.class);
 }
attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeFabric.java (2)

1-4: Fix copyright year.

The copyright year is set to 2025, which appears to be incorrect as it's in the future.

 /*
- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
  * GNU General Public License 3
  */

11-16: Improve consistency with NeoForge implementation.

Consider the following improvements for consistency:

  1. Mark class as final (like NeoForge implementation)
  2. Add error handling around initialization
  3. Add logging support
-public class ManasCoreAttributeFabric implements ModInitializer {
+public final class ManasCoreAttributeFabric implements ModInitializer {
+    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(ManasCoreAttributeFabric.class);
+
     @Override
     public void onInitialize() {
+        try {
             ManasCoreAttribute.init();
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ManasCore attributes", e);
+            throw new RuntimeException("Failed to initialize ManasCore attributes", e);
+        }
     }
 }
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttributeRegister.java (1)

12-27: Add class-level documentation to explain the platform-specific implementation pattern.

The class serves as a platform-agnostic registration point for attributes, but this isn't immediately clear to other developers. Consider adding class-level JavaDoc to explain:

  • The purpose of the register
  • How platform-specific implementations should be provided
  • Usage examples for each method
+/**
+ * Platform-agnostic attribute registration handler.
+ * Platform-specific implementations must be provided for each method.
+ * <p>
+ * Usage:
+ * - Use {@link #registerToPlayers} to register attributes that apply to players
+ * - Use {@link #registerToGeneric} to register attributes that apply to any entity
+ * - Call {@link #init} during mod initialization to set up the attribute system
+ */
 public class ManasCoreAttributeRegister {
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/Changeable.java (2)

11-48: Enhance thread safety and null handling.

The current implementation has several areas for improvement:

  1. Consider using volatile or AtomicReference instead of method-level synchronization
  2. Add immutable access to the original value
  3. Strengthen null handling with Optional
+/**
+ * Thread-safe wrapper for mutable values that tracks changes from an original value.
+ * @param <T> The type of the wrapped value
+ */
 public class Changeable<T> {
     @Nullable
     private final T original;
-    private T value;
+    private volatile T value;
 
     protected Changeable(@Nullable T value) {
         this.original = value;
         this.value = value;
     }
 
     public static <T> Changeable<T> of(@Nullable T value) {
         return new Changeable<>(value);
     }
 
-    @Synchronized
     @Nullable
     public T get() {
         return value;
     }
 
-    @Synchronized
     public void set(@Nullable T value) {
         this.value = value;
     }
 
+    /**
+     * @return The original value this instance was created with
+     */
+    @Nullable
+    public T getOriginal() {
+        return original;
+    }
+
+    /**
+     * @return Optional wrapper of the current value
+     */
+    public Optional<T> getOptional() {
+        return Optional.ofNullable(value);
+    }
 
     public boolean isPresent() {
         return value != null;
     }
 
     public boolean isEmpty() {
         return value == null;
     }
 
     public boolean hasChanged() {
-        if (original == null) return value != null;
-        return !original.equals(value);
+        return !Objects.equals(original, value);
     }
 }

25-34: Consider adding atomic operations for thread safety.

The current synchronized methods could be replaced with atomic operations for better performance in concurrent scenarios.

-    @Synchronized
-    @Nullable
-    public T get() {
-        return value;
-    }
-
-    @Synchronized
-    public void set(@Nullable T value) {
-        this.value = value;
-    }
+    private final AtomicReference<T> valueRef;
+
+    protected Changeable(@Nullable T value) {
+        this.original = value;
+        this.valueRef = new AtomicReference<>(value);
+    }
+
+    @Nullable
+    public T get() {
+        return valueRef.get();
+    }
+
+    public void set(@Nullable T value) {
+        valueRef.set(value);
+    }
+
+    public boolean compareAndSet(@Nullable T expect, @Nullable T update) {
+        return valueRef.compareAndSet(expect, update);
+    }
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (2)

27-29: Consider adding validation for critical attack chance.

While the range is set correctly (0-100), consider adding runtime validation in case the attribute is modified during gameplay to ensure it stays within bounds.

 public static final Holder<Attribute> CRITICAL_ATTACK_CHANCE = registerGenericAttribute(ModuleConstants.MOD_ID,
         "critical_attack_chance", "manascore.attribute.critical_attack_chance",
-        0, 0, 100, true, Attribute.Sentiment.POSITIVE);
+        0, 0, 100, true, Attribute.Sentiment.POSITIVE) {
+    @Override
+    public double sanitizeValue(double value) {
+        return Mth.clamp(value, 0.0D, 100.0D);
+    }
+};

31-37: Consider consolidating duplicate registration methods.

The registerPlayerAttribute and registerGenericAttribute methods are nearly identical except for the final registration call. Consider refactoring to reduce code duplication.

+private static Holder<Attribute> registerAttribute(String modID, String id, String name, double amount,
+                                                double min, double max, boolean syncable, 
+                                                Attribute.Sentiment sentiment,
+                                                Consumer<Holder<Attribute>> registrationHandler) {
+    Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE,
+            getResourceKey(modID, id),
+            new RangedAttribute(name, amount, min, max)
+                .setSyncable(syncable)
+                .setSentiment(sentiment));
+    registrationHandler.accept(attribute);
+    return attribute;
+}

 public static Holder<Attribute> registerPlayerAttribute(String modID, String id, String name, double amount,
                                                     double min, double max, boolean syncable, 
                                                     Attribute.Sentiment sentiment) {
-    Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE,
-            getResourceKey(modID, id),
-            new RangedAttribute(name, amount, min, max)
-                .setSyncable(syncable)
-                .setSentiment(sentiment));
-    ManasCoreAttributeRegister.registerToPlayers(attribute);
-    return attribute;
+    return registerAttribute(modID, id, name, amount, min, max, syncable, sentiment,
+            ManasCoreAttributeRegister::registerToPlayers);
 }

 public static Holder<Attribute> registerGenericAttribute(String modID, String id, String name, double amount,
                                                      double min, double max, boolean syncable, 
                                                      Attribute.Sentiment sentiment) {
-    Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE,
-            getResourceKey(modID, id),
-            new RangedAttribute(name, amount, min, max)
-                .setSyncable(syncable)
-                .setSentiment(sentiment));
-    ManasCoreAttributeRegister.registerToGeneric(attribute);
-    return attribute;
+    return registerAttribute(modID, id, name, amount, min, max, syncable, sentiment,
+            ManasCoreAttributeRegister::registerToGeneric);
 }

Also applies to: 39-45

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java (1)

52-60: Consider caching trigonometric calculations.

The getLookTowardVec method performs multiple trigonometric calculations that could be optimized by caching intermediate results.

 public static Vec3 getLookTowardVec(Player player, double distance) {
     float f = player.getXRot();
     float g = player.getYRot();
-    float h = Mth.cos(-g * 0.017453292F - 3.1415927F);
-    float i = Mth.sin(-g * 0.017453292F - 3.1415927F);
-    float j = -Mth.cos(-f * 0.017453292F);
-    float k = Mth.sin(-f * 0.017453292F);
+    float yawRad = -g * 0.017453292F - 3.1415927F;
+    float pitchRad = -f * 0.017453292F;
+    float h = Mth.cos(yawRad);
+    float i = Mth.sin(yawRad);
+    float j = -Mth.cos(pitchRad);
+    float k = Mth.sin(pitchRad);
     return new Vec3(i * j * distance, k * distance, h * j * distance);
 }
attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (1)

25-38: Consider extracting critical hit logic to a separate method.

The applyEntityCrit method contains complex critical hit logic that could be extracted for better maintainability and reusability.

+private static boolean shouldApplyCriticalHit(LivingEntity attacker, LivingEntity target,
+                                             Changeable<Float> multiplier,
+                                             Changeable<Double> chance) {
+    if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker()
+            .applyCrit(attacker, target, 1, multiplier, chance)
+            .isFalse()) {
+        return false;
+    }
+    return target.getRandom().nextFloat() <= chance.get();
+}

 @SubscribeEvent(priority = EventPriority.HIGH)
 public static void applyEntityCrit(final LivingIncomingDamageEvent e) {
     if (!(e.getSource().getDirectEntity() instanceof LivingEntity attacker)) return;
     if (attacker instanceof Player) return;
     LivingEntity target = e.getEntity();

     Changeable<Float> multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER));
     Changeable<Double> chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
-    if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(attacker, target, 1, multiplier, chance).isFalse()) return;
-
-    if (target.getRandom().nextFloat() > chance.get()) return;
+    if (!shouldApplyCriticalHit(attacker, target, multiplier, chance)) return;

     ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, attacker);
     e.setAmount(e.getAmount() * multiplier.get());
 }
attribute-fabric/src/main/resources/fabric.mod.json (1)

11-12: Add homepage URL for better discoverability.

The homepage field is empty. Consider adding a URL to the project's GitHub repository or documentation.

testing-common/src/main/resources/assets/manascore/lang/en_us.json (1)

8-11: Consider more descriptive potion names.

The potion names are generic ("Test Potion", etc.). Consider using more descriptive names that reflect their effects.

-  "item.minecraft.lingering_potion.effect.lmao_potion": "Test Lingering Potion",
-  "item.minecraft.splash_potion.effect.lmao_potion": "Test Splash Potion",
-  "item.minecraft.potion.effect.lmao_potion": "Test Potion",
-  "item.minecraft.tipped_arrow.effect.lmao_potion": "Test Tipped Arrow",
+  "item.minecraft.lingering_potion.effect.lmao_potion": "Test Explosive Lingering Potion",
+  "item.minecraft.splash_potion.effect.lmao_potion": "Test Explosive Splash Potion",
+  "item.minecraft.potion.effect.lmao_potion": "Test Explosive Potion",
+  "item.minecraft.tipped_arrow.effect.lmao_potion": "Test Explosive Arrow",
attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1)

16-35: Consider using consistent version constraint format.

The version constraints use different formats:

  • NeoForge: Hardcoded "[21,)"
  • Minecraft: "[${minecraft_version},)"
  • Architectury: "[${architectury_version},)"

Consider using placeholders for NeoForge version as well to maintain consistency and ease version updates.

-    versionRange = "[21,)"
+    versionRange = "[${neoforge_version},)"
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 75ebe0c and db27667.

⛔ Files ignored due to path filters (3)
  • attribute-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • attribute-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • attribute-neoforge/src/main/resources/icon.png is excluded by !**/*.png
📒 Files selected for processing (30)
  • attribute-common/build.gradle (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttributeRegister.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/Changeable.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (1 hunks)
  • attribute-common/src/main/resources/architectury.common.json (1 hunks)
  • attribute-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
  • attribute-common/src/main/resources/manascore_attribute.accesswidener (1 hunks)
  • attribute-common/src/main/resources/manascore_attribute.mixins.json (1 hunks)
  • attribute-fabric/build.gradle (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeFabric.java (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java (1 hunks)
  • attribute-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • attribute-fabric/src/main/resources/manascore_attribute-fabric.mixins.json (1 hunks)
  • attribute-neoforge/build.gradle (1 hunks)
  • attribute-neoforge/gradle.properties (1 hunks)
  • attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeNeoForge.java (1 hunks)
  • attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeRegisterImpl.java (1 hunks)
  • attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (1 hunks)
  • attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1 hunks)
  • settings.gradle (2 hunks)
  • testing-common/build.gradle (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java (1 hunks)
  • testing-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • attribute-neoforge/gradle.properties
  • attribute-common/build.gradle
  • attribute-fabric/build.gradle
  • attribute-common/src/main/resources/assets/manascore/lang/en_us.json
  • attribute-fabric/src/main/resources/manascore_attribute-fabric.mixins.json
  • attribute-common/src/main/resources/architectury.common.json
  • attribute-common/src/main/resources/manascore_attribute.mixins.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • settings.gradle
  • testing-common/build.gradle
  • config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java
🔇 Additional comments (18)
attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java (3)

1-4: License header looks appropriate.
No issues found with the licensing information.


8-9: Confirm mixin-extras version compatibility.
Make sure your project's build.gradle or equivalent includes the correct version of Mixin Extras, as this library is required for @Local injection. Otherwise, this code may fail at runtime.


27-38: Enhance coverage of non-player LivingEntity attacks.

  1. The code currently excludes players (Player instance check), which seems intentional. However, note that indirect player attacks (e.g., arrows) are ignored. If you want to handle those, consider an explicit check for projectiles.
  2. Ensure attribute values are valid (non-null), thus preventing edge cases where the attributes might not be present.
  3. Consider using the attacker’s random for the critical chance rather than the target’s random, to more consistently reflect the attacker’s capabilities.
attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java (3)

1-4: Add or confirm inclusion of complete license texts.
While the header references the GNU General Public License v3, ensure that the project fully includes and distributes the relevant GPL license texts in accordance with compliance guidelines.


25-25: Verify local injection for newAmount.
Injecting at HEAD and referencing a local float might fail if the expected local variable doesn't exist or is re-ordered in the bytecode. Confirm that the method's local variable structure matches your injection site by verifying any relevant local variable mapping or specifying the correct ordinal if needed.


57-60: Handle unexpected or absent attribute values.
When overriding the vanilla 1.5F critical multiplier, ensure that any missing or zero attribute value is handled gracefully to avoid inadvertently causing zero or negative multipliers. A sensible default or a safety clamp might be beneficial.

attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java (1)

37-60: 🛠️ Refactor suggestion

Improve initialization robustness and documentation.

Several improvements could enhance the initialization logic:

  1. The null check suggests potential nulls in ENTITY_TYPES, which should be prevented during list creation
  2. Registry clearing could cause issues if init() is called multiple times
  3. Comments could be more descriptive

Consider this refactored implementation:

     public static void init() {
+        // Prevent multiple initialization
+        if (!GENERIC_REGISTRY.isEmpty() || !PLAYER_REGISTRY.isEmpty()) {
+            LOGGER.warn("Attribute registration has already been initialized");
+            return;
+        }
+
         LifecycleEvent.SETUP.register(() -> {
-            entityTypes.forEach(entityType -> {
-                if (entityType == null) return;
+            ENTITY_TYPES.forEach(entityType -> {
                 AttributeSupplier.Builder builder = new AttributeSupplier.Builder();
-                // Apply existing attributes
+                // Copy vanilla attributes to preserve default behavior
                 if (DefaultAttributes.hasSupplier(entityType)) {
                     DefaultAttributes.getSupplier(entityType).instances.forEach((attribute, attributeInstance) -> {
                         builder.add(attribute, attributeInstance.getBaseValue());
                     });
                 }

+                // Apply player-specific attributes if this is a player entity
                 if (entityType.equals(EntityType.PLAYER)) PLAYER_REGISTRY.forEach(builder::add);
-                // Apply global custom attributes
+                // Apply global attributes to all entity types
                 GENERIC_REGISTRY.forEach(builder::add);
-                // Register the attributes
+                // Register the combined attribute set with Fabric
                 FabricDefaultAttributeRegistry.register(entityType, builder);
             });

-            // Clear the registry
+            // Clear registries to prevent duplicate registrations and free memory
             PLAYER_REGISTRY.clear();
             GENERIC_REGISTRY.clear();
         });
     }

Let's verify that init() is only called once:

attribute-common/src/main/resources/manascore_attribute.accesswidener (1)

1-2: LGTM! Verify access widener configuration.

The access widener configuration is correctly structured and follows Minecraft modding best practices. The field access modification is appropriate for attribute registration purposes.

Run this script to verify the access widener configuration is properly recognized:

✅ Verification successful

Access widener configuration verified and correctly integrated

The access widener is properly configured in the build system and follows Fabric modding best practices. The configuration is clean, minimal, and correctly referenced in the build files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify access widener configuration is properly referenced in build files

# Check if access widener path is configured in build.gradle files
rg -l "accessWidenerPath.*manascore_attribute.accesswidener" --type gradle

Length of output: 106


Script:

#!/bin/bash
# Check the actual access widener configuration in build.gradle
cat attribute-common/build.gradle

Length of output: 202

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (1)

23-25: Review the default value and maximum limit for critical damage multiplier.

The critical damage multiplier has a maximum value of 1024x which seems excessive and could lead to balance issues. Consider reducing this to a more reasonable value (e.g., 5x or 10x) to prevent potential exploits.

attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeRegisterImpl.java (1)

19-20: LGTM! Thread-safe implementation using CopyOnWriteArrayList.

Good choice using CopyOnWriteArrayList for thread-safe concurrent modifications during registration.

testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (3)

193-195: LGTM: Block entity creation is properly implemented.

The block entity creation is correctly implemented using the registered block entity type.


161-176: Consider adding safety checks for explosion radius.

The explosion radius (g) is set to a random value between 10.0F and 12.0F, which is quite large and could cause significant damage. Consider adding safety checks or configuration options.


62-67: Review attribute modifier values for balance.

The attribute modifiers for critical damage and chance seem extremely high:

  • Critical damage multiplier: 10x (1000% increase)
  • Critical attack chance: 50% (50% increase)

These values might be too powerful for gameplay balance, even in a testing environment.

attribute-neoforge/build.gradle (1)

6-8: LGTM: Access widener configuration is correct.

The access widener path is properly inherited from the common module.

attribute-fabric/src/main/resources/fabric.mod.json (1)

27-31: Verify Java version compatibility.

The mod requires Java 21, which is a very recent version. This might limit compatibility with some mod loaders and other mods.

attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml (3)

1-4: Add issue tracker URL and verify license placeholder.

  1. The empty issue tracker URL should be populated to help users report issues.
  2. Ensure that the ${license} placeholder is correctly replaced during the build process.

Run this script to verify the license placeholder replacement:

✅ Verification successful

Add issue tracker URL - license placeholder is correctly configured

The ${license} placeholder is properly configured in build.gradle and will be replaced with "GPLv3" during the build process. However, the issue tracker URL is still empty and should be set to "https://github.com/ManasMods/ManasCore" to match the project's repository.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify license placeholder replacement in build process

# Check build configuration files for license variable definition
echo "Checking for license variable in build files..."
fd -e gradle -e properties | xargs grep -l "license"

# Check if there are any remaining unresolved placeholders in built artifacts
echo "Checking for unresolved placeholders in built artifacts..."
fd -e toml | xargs grep -l "\${license}"

Length of output: 916


Script:

#!/bin/bash
# Check build.gradle for license configuration
echo "=== build.gradle license configuration ==="
rg "license" build.gradle -A 3 -B 3

# Check if this is a common pattern in other similar projects
echo -e "\n=== Checking root project structure ==="
ls -la

Length of output: 3582


37-38: Verify mixin configuration file existence.

Ensure that the mixin configuration file exists and is properly named according to the mod ID.

Run this script to verify the mixin configuration:

✅ Verification successful

Mixin configuration is correctly set up

The mixin configuration references manascore_attribute.mixins.json which exists in the common module at attribute-common/src/main/resources/manascore_attribute.mixins.json. This is a valid setup as NeoForge can use the mixin configuration from the common module.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify mixin configuration file

# First, find the mod_id value from build files
echo "Checking for mod_id in build files..."
fd -e gradle -e properties | xargs grep "mod_id"

# Then, check for the corresponding mixins.json file
echo "Checking for mixins.json file..."
fd "mixins.json" -t f

Length of output: 1864


Script:

#!/bin/bash
# Get archives_name from gradle.properties and construct expected mod_id
echo "Getting archives_name from gradle.properties..."
grep "archives_name" gradle.properties

# Check for any mixin config file specifically for attribute-neoforge
echo -e "\nChecking for attribute-neoforge specific mixin configs..."
fd . attribute-neoforge/src/main/resources -t f

Length of output: 461


6-14: Verify placeholders and logo file existence.

  1. Ensure that all placeholders (${mod_id}, ${version}, ${mod_name}) are properly configured in the build system.
  2. Verify that icon.png exists in the correct resources directory.

Run this script to verify the configuration:

✅ Verification successful

✓ Placeholders and icon file configuration verified

All placeholders are properly configured in the build system and the icon file exists at the correct location. The build process will correctly populate:

  • ${version} from version 4.0.0.0
  • ${mod_id} as "manascore_attribute"
  • ${mod_name} as "ManasCore - Attribute"
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify mod metadata configuration

# Check build configuration files for variable definitions
echo "Checking for mod variables in build files..."
fd -e gradle -e properties | xargs grep -l "mod_id\|version\|mod_name"

# Verify icon.png existence
echo "Checking for icon.png..."
fd "icon.png" -t f

Length of output: 1465


Script:

#!/bin/bash
# Check variable definitions in main configuration files
echo "=== Checking gradle.properties ==="
cat gradle.properties

echo -e "\n=== Checking root build.gradle ==="
cat build.gradle

echo -e "\n=== Checking attribute-neoforge/build.gradle ==="
cat attribute-neoforge/build.gradle

Length of output: 11202

Comment on lines +14 to +21
public interface AttributeEvents {
Event<CriticalAttackChanceEvent> CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult();

@FunctionalInterface
interface CriticalAttackChanceEvent {
EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add comprehensive documentation for the event interface and its parameters.

The event interface handles critical attack mechanics but lacks documentation explaining the purpose and parameter usage. Consider:

  1. Adding interface-level documentation
  2. Adding parameter validation for null checks
  3. Documenting the expected ranges for numerical parameters
+/**
+ * Events related to attribute-based mechanics.
+ */
 public interface AttributeEvents {
     Event<CriticalAttackChanceEvent> CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult();
 
     @FunctionalInterface
+    /**
+     * Event fired when calculating critical attack chance.
+     * @param attacker The entity performing the attack
+     * @param target The entity being attacked
+     * @param originalMultiplier The base damage multiplier [0.0, inf)
+     * @param multiplier Mutable damage multiplier
+     * @param chance Mutable critical hit chance [0.0, 1.0]
+     * @return EventResult indicating if the critical hit should be applied
+     * @throws NullPointerException if attacker, multiplier or chance is null
+     */
     interface CriticalAttackChanceEvent {
-        EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance);
+        default EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance) {
+            Objects.requireNonNull(attacker, "Attacker cannot be null");
+            Objects.requireNonNull(multiplier, "Multiplier cannot be null");
+            Objects.requireNonNull(chance, "Chance cannot be null");
+            if (originalMultiplier < 0) {
+                throw new IllegalArgumentException("Original multiplier must be non-negative");
+            }
+            return onCriticalAttack(attacker, target, originalMultiplier, multiplier, chance);
+        }
+        
+        EventResult onCriticalAttack(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance);
     }
📝 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 interface AttributeEvents {
Event<CriticalAttackChanceEvent> CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult();
@FunctionalInterface
interface CriticalAttackChanceEvent {
EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance);
}
}
/**
* Events related to attribute-based mechanics.
*/
public interface AttributeEvents {
Event<CriticalAttackChanceEvent> CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult();
@FunctionalInterface
/**
* Event fired when calculating critical attack chance.
* @param attacker The entity performing the attack
* @param target The entity being attacked
* @param originalMultiplier The base damage multiplier [0.0, inf)
* @param multiplier Mutable damage multiplier
* @param chance Mutable critical hit chance [0.0, 1.0]
* @return EventResult indicating if the critical hit should be applied
*/
interface CriticalAttackChanceEvent {
EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable<Float> multiplier, Changeable<Double> chance);
}
}

Comment on lines +14 to +18
public static final Holder<Attribute> TEST_ATTRIBUTE_PLAYER = ManasCoreAttributes.registerPlayerAttribute(ModuleConstants.MOD_ID,
"test_attribute_player", "manascore.attribute.test_attribute_player",69, 0, 420, true, Attribute.Sentiment.NEUTRAL);
public static final Holder<Attribute> TEST_ATTRIBUTE_ALL = ManasCoreAttributes.registerGenericAttribute(ModuleConstants.MOD_ID,
"test_attribute_all", "manascore.attribute.test_attribute_all", 420, 69, 4200, true, Attribute.Sentiment.NEGATIVE);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test attribute configuration maintainability.

The current implementation has several maintainability issues:

  1. Magic numbers for attribute ranges
  2. Hardcoded string identifiers
  3. No documentation explaining the test cases
+/**
+ * Registry for test attributes used in integration tests.
+ */
 public class TestAttributeRegistry {
+    private static final String TEST_ATTRIBUTE_PLAYER_ID = "test_attribute_player";
+    private static final String TEST_ATTRIBUTE_ALL_ID = "test_attribute_all";
+    
+    // Test ranges for player attributes
+    private static final float PLAYER_MIN = 0;
+    private static final float PLAYER_DEFAULT = 69;
+    private static final float PLAYER_MAX = 420;
+    
+    // Test ranges for generic attributes
+    private static final float GENERIC_MIN = 69;
+    private static final float GENERIC_DEFAULT = 420;
+    private static final float GENERIC_MAX = 4200;
+
     public static final Holder<Attribute> TEST_ATTRIBUTE_PLAYER = ManasCoreAttributes.registerPlayerAttribute(ModuleConstants.MOD_ID,
-            "test_attribute_player", "manascore.attribute.test_attribute_player",69, 0, 420, true, Attribute.Sentiment.NEUTRAL);
+            TEST_ATTRIBUTE_PLAYER_ID, 
+            "manascore.attribute." + TEST_ATTRIBUTE_PLAYER_ID,
+            PLAYER_DEFAULT, PLAYER_MIN, PLAYER_MAX, 
+            true, Attribute.Sentiment.NEUTRAL);
+
     public static final Holder<Attribute> TEST_ATTRIBUTE_ALL = ManasCoreAttributes.registerGenericAttribute(ModuleConstants.MOD_ID,
-            "test_attribute_all", "manascore.attribute.test_attribute_all", 420, 69, 4200, true, Attribute.Sentiment.NEGATIVE);
+            TEST_ATTRIBUTE_ALL_ID,
+            "manascore.attribute." + TEST_ATTRIBUTE_ALL_ID,
+            GENERIC_DEFAULT, GENERIC_MIN, GENERIC_MAX,
+            true, Attribute.Sentiment.NEGATIVE);
📝 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 Holder<Attribute> TEST_ATTRIBUTE_PLAYER = ManasCoreAttributes.registerPlayerAttribute(ModuleConstants.MOD_ID,
"test_attribute_player", "manascore.attribute.test_attribute_player",69, 0, 420, true, Attribute.Sentiment.NEUTRAL);
public static final Holder<Attribute> TEST_ATTRIBUTE_ALL = ManasCoreAttributes.registerGenericAttribute(ModuleConstants.MOD_ID,
"test_attribute_all", "manascore.attribute.test_attribute_all", 420, 69, 4200, true, Attribute.Sentiment.NEGATIVE);
}
/**
* Registry for test attributes used in integration tests.
*/
public class TestAttributeRegistry {
private static final String TEST_ATTRIBUTE_PLAYER_ID = "test_attribute_player";
private static final String TEST_ATTRIBUTE_ALL_ID = "test_attribute_all";
// Test ranges for player attributes
private static final float PLAYER_MIN = 0;
private static final float PLAYER_DEFAULT = 69;
private static final float PLAYER_MAX = 420;
// Test ranges for generic attributes
private static final float GENERIC_MIN = 69;
private static final float GENERIC_DEFAULT = 420;
private static final float GENERIC_MAX = 4200;
public static final Holder<Attribute> TEST_ATTRIBUTE_PLAYER = ManasCoreAttributes.registerPlayerAttribute(ModuleConstants.MOD_ID,
TEST_ATTRIBUTE_PLAYER_ID,
"manascore.attribute." + TEST_ATTRIBUTE_PLAYER_ID,
PLAYER_DEFAULT, PLAYER_MIN, PLAYER_MAX,
true, Attribute.Sentiment.NEUTRAL);
public static final Holder<Attribute> TEST_ATTRIBUTE_ALL = ManasCoreAttributes.registerGenericAttribute(ModuleConstants.MOD_ID,
TEST_ATTRIBUTE_ALL_ID,
"manascore.attribute." + TEST_ATTRIBUTE_ALL_ID,
GENERIC_DEFAULT, GENERIC_MIN, GENERIC_MAX,
true, Attribute.Sentiment.NEGATIVE);
}

Comment on lines +32 to +43
public static float getWeaponDamage(LivingEntity attacker, @Nullable Entity target, @Nullable DamageSource source) {
AttributeInstance attack = attacker.getAttribute(Attributes.ATTACK_DAMAGE);
if (attack == null) return 0;

float damage = 1F;
AttributeModifier modifier = attack.getModifier(Item.BASE_ATTACK_DAMAGE_ID);
if (modifier != null) damage += (float) modifier.amount();

if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel)
damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage);
return damage;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checks for weapon item and improve error handling.

The getWeaponDamage method could benefit from additional null checks and better error handling:

  1. Check if attacker.getWeaponItem() is null before using it
  2. Consider logging when attack attribute is null
 public static float getWeaponDamage(LivingEntity attacker, @Nullable Entity target, @Nullable DamageSource source) {
     AttributeInstance attack = attacker.getAttribute(Attributes.ATTACK_DAMAGE);
-    if (attack == null) return 0;
+    if (attack == null) {
+        LOGGER.warn("Attack attribute is null for entity: {}", attacker);
+        return 0;
+    }

     float damage = 1F;
     AttributeModifier modifier = attack.getModifier(Item.BASE_ATTACK_DAMAGE_ID);
     if (modifier != null) damage += (float) modifier.amount();

-    if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel)
-        damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage);
+    if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel) {
+        var weaponItem = attacker.getWeaponItem();
+        if (!weaponItem.isEmpty()) {
+            damage = EnchantmentHelper.modifyDamage(serverLevel, weaponItem, target, source, damage);
+        }
+    }
     return damage;
 }

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

Comment on lines +30 to +39
static void registerAttributes(final EntityAttributeModificationEvent e) {
e.getTypes().forEach(type -> {
if (type.equals(EntityType.PLAYER)) PLAYER_REGISTRY.forEach(holder -> e.add(type, holder));
GENERIC_REGISTRY.forEach(holder -> e.add(type, holder));
});

// Clear the registry
PLAYER_REGISTRY.clear();
GENERIC_REGISTRY.clear();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling for attribute registration.

The registerAttributes method should handle potential exceptions during attribute registration and provide appropriate logging.

 static void registerAttributes(final EntityAttributeModificationEvent e) {
+    try {
         e.getTypes().forEach(type -> {
             if (type.equals(EntityType.PLAYER)) PLAYER_REGISTRY.forEach(holder -> e.add(type, holder));
             GENERIC_REGISTRY.forEach(holder -> e.add(type, holder));
         });

         // Clear the registry
         PLAYER_REGISTRY.clear();
         GENERIC_REGISTRY.clear();
+    } catch (Exception ex) {
+        LOGGER.error("Failed to register attributes", ex);
+        throw ex; // Re-throw to ensure the error is propagated
+    }
 }

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

Comment on lines +40 to +57
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onCriticalHit(final CriticalHitEvent e) {
if (e.isVanillaCritical()) {
float multiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
e.setDamageMultiplier(e.getDamageMultiplier() / e.getVanillaMultiplier() * multiplier);
return;
}

float critMultiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
Changeable<Float> multiplier = Changeable.of(e.getDamageMultiplier() * critMultiplier);
Changeable<Double> chance = Changeable.of(e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(e.getEntity(), e.getTarget(),
e.getDamageMultiplier(), multiplier, chance).isFalse()) return;

if (e.getEntity().getRandom().nextFloat() > chance.get()) return;
e.setDamageMultiplier(multiplier.get());
e.setCriticalHit(true);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for damage multiplier calculations.

The onCriticalHit method should validate the damage multiplier to prevent potential exploits or overflow.

 @SubscribeEvent(priority = EventPriority.HIGH)
 public static void onCriticalHit(final CriticalHitEvent e) {
     if (e.isVanillaCritical()) {
         float multiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
+        // Validate multiplier to prevent exploits
+        multiplier = Math.max(0.0f, Math.min(multiplier, MAX_SAFE_MULTIPLIER));
         e.setDamageMultiplier(e.getDamageMultiplier() / e.getVanillaMultiplier() * multiplier);
         return;
     }

     float critMultiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
+    // Validate critMultiplier to prevent exploits
+    critMultiplier = Math.max(0.0f, Math.min(critMultiplier, MAX_SAFE_MULTIPLIER));
     Changeable<Float> multiplier = Changeable.of(e.getDamageMultiplier() * critMultiplier);
     Changeable<Double> chance = Changeable.of(e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
     if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(e.getEntity(), e.getTarget(),
             e.getDamageMultiplier(), multiplier, chance).isFalse()) return;

     if (e.getEntity().getRandom().nextFloat() > chance.get()) return;
+    // Validate final multiplier to prevent overflow
+    float finalMultiplier = Math.max(0.0f, Math.min(multiplier.get(), MAX_SAFE_MULTIPLIER));
-    e.setDamageMultiplier(multiplier.get());
+    e.setDamageMultiplier(finalMultiplier);
     e.setCriticalHit(true);
 }

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

Comment on lines 120 to 136
PlayerEvent.DROP_ITEM.register((player, entity) -> {
//Test giving Skills
if (entity.getItem().is(Items.DIAMOND)) {
Skills storage = SkillAPI.getSkillsFrom(player);
Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
ManasCoreTesting.LOG.info("Added Tested Skill!");
}
} else if (entity.getItem().is(Items.EMERALD)) {
Skills storage = SkillAPI.getSkillsFrom(player);
storage.forgetSkill(RegistryTest.TEST_SKILL.get());
ManasCoreTesting.LOG.info("Forgot Tested Skill!");
}

return EventResult.pass();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in skill management.

The skill learning/forgetting logic lacks error handling:

  1. No null checks for storage or testSkill
  2. No feedback when skill learning fails
  3. Logging only happens on success
 PlayerEvent.DROP_ITEM.register((player, entity) -> {
     if (entity.getItem().is(Items.DIAMOND)) {
         Skills storage = SkillAPI.getSkillsFrom(player);
+        if (storage == null) {
+            ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+            return EventResult.pass();
+        }
         Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
         RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
+        if (testSkill == null) {
+            ManasCoreTesting.LOG.error("Test skill is not registered");
+            return EventResult.pass();
+        }
         if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
             ManasCoreTesting.LOG.info("Added Tested Skill!");
+        } else {
+            ManasCoreTesting.LOG.warn("Failed to learn test skill");
+        }
     } else if (entity.getItem().is(Items.EMERALD)) {
         Skills storage = SkillAPI.getSkillsFrom(player);
+        if (storage == null) {
+            ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+            return EventResult.pass();
+        }
         storage.forgetSkill(RegistryTest.TEST_SKILL.get());
         ManasCoreTesting.LOG.info("Forgot Tested Skill!");
     }
     return EventResult.pass();
 });
📝 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
PlayerEvent.DROP_ITEM.register((player, entity) -> {
//Test giving Skills
if (entity.getItem().is(Items.DIAMOND)) {
Skills storage = SkillAPI.getSkillsFrom(player);
Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
ManasCoreTesting.LOG.info("Added Tested Skill!");
}
} else if (entity.getItem().is(Items.EMERALD)) {
Skills storage = SkillAPI.getSkillsFrom(player);
storage.forgetSkill(RegistryTest.TEST_SKILL.get());
ManasCoreTesting.LOG.info("Forgot Tested Skill!");
}
return EventResult.pass();
});
PlayerEvent.DROP_ITEM.register((player, entity) -> {
if (entity.getItem().is(Items.DIAMOND)) {
Skills storage = SkillAPI.getSkillsFrom(player);
if (storage == null) {
ManasCoreTesting.LOG.error("Failed to get skills storage for player");
return EventResult.pass();
}
Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
if (testSkill == null) {
ManasCoreTesting.LOG.error("Test skill is not registered");
return EventResult.pass();
}
if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
ManasCoreTesting.LOG.info("Added Tested Skill!");
} else {
ManasCoreTesting.LOG.warn("Failed to learn test skill");
}
} else if (entity.getItem().is(Items.EMERALD)) {
Skills storage = SkillAPI.getSkillsFrom(player);
if (storage == null) {
ManasCoreTesting.LOG.error("Failed to get skills storage for player");
return EventResult.pass();
}
storage.forgetSkill(RegistryTest.TEST_SKILL.get());
ManasCoreTesting.LOG.info("Forgot Tested Skill!");
}
return EventResult.pass();
});

"item.manascore_testing.test_item": "Test Item",
"item.manascore_testing.test_block_item": "Test Block Item",
"block.manascore_testing.test_block": "Test Block",
"entity.manascore_testing.test_block": "Test Entity",
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 incorrect translation key for test entity.

The translation key for the test entity uses test_block instead of test_entity, which doesn't match the entity ID registered in RegistryTest.java.

-  "entity.manascore_testing.test_block": "Test Entity",
+  "entity.manascore_testing.test_entity": "Test Entity",
📝 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
"entity.manascore_testing.test_block": "Test Entity",
"entity.manascore_testing.test_entity": "Test Entity",

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

♻️ Duplicate comments (2)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (1)

106-111: ⚠️ Potential issue

Improve error handling in skill management.

The skill learning logic lacks proper error handling and null checks.

     public void learnIntrinsicSkills(ManasRaceInstance instance, LivingEntity entity) {
         Skills storage = SkillAPI.getSkillsFrom(entity);
+        if (storage == null) {
+            ManasCoreTesting.LOG.error("Failed to get skills storage for entity");
+            return;
+        }
         for (ManasSkill skill : instance.getIntrinsicSkills(entity)) {
+            if (skill == null) continue;
             if (storage.learnSkill(skill)) ManasCoreTesting.LOG.info("LEARNT SKILL FROM RACE?!?!");
+            else ManasCoreTesting.LOG.warn("Failed to learn skill: {}", skill.getName());
         }
     }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1)

129-142: ⚠️ Potential issue

Add error handling for skill management operations.

The skill learning/forgetting logic lacks proper error handling and null checks.

         PlayerEvent.DROP_ITEM.register((player, entity) -> {
             if (entity.getItem().is(Items.DIAMOND)) {
                 Skills storage = SkillAPI.getSkillsFrom(player);
+                if (storage == null) {
+                    ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+                    return EventResult.pass();
+                }
                 Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
                 RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
+                if (testSkill == null) {
+                    ManasCoreTesting.LOG.error("Test skill is not registered");
+                    return EventResult.pass();
+                }
                 if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
                     ManasCoreTesting.LOG.info("Added Tested Skill!");
+                } else {
+                    ManasCoreTesting.LOG.warn("Failed to learn test skill");
+                }
             } else if (entity.getItem().is(Items.EMERALD)) {
                 Skills storage = SkillAPI.getSkillsFrom(player);
+                if (storage == null) {
+                    ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+                    return EventResult.pass();
+                }
                 storage.forgetSkill(RegistryTest.TEST_SKILL.get());
                 ManasCoreTesting.LOG.info("Forgot Tested Skill!");
             }
🧹 Nitpick comments (28)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java (2)

1-4: Update copyright year to reflect current year.

The copyright notice shows 2025, which extends into the future. Consider updating it to 2024 or using a range (e.g., 2024-2025) if the project started in 2024.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

17-23: Enhance error handling and code organization.

Consider these improvements:

  1. Add a descriptive error message to orElseThrow() to aid debugging
  2. Extract the key lookup lambda to a named method for better readability
+    private static ResourceKey<ManasRace> getManasRaceKey(ManasRace manasSkill) {
+        return RaceRegistry.RACES.getKey(manasSkill)
+            .orElseThrow(() -> new IllegalStateException(
+                "Failed to find registry key for ManasRace: " + manasSkill));
+    }
+
     public RaceTagProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
-        super(output, RaceRegistry.KEY, lookupProvider, manasSkill -> RaceRegistry.RACES.getKey(manasSkill).orElseThrow());
+        super(output, RaceRegistry.KEY, lookupProvider, RaceTagProvider::getManasRaceKey);
     }

     public RaceTagProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, CompletableFuture<TagLookup<ManasRace>> parentProvider) {
-        super(output, RaceRegistry.KEY, lookupProvider, parentProvider, manasSkill -> RaceRegistry.RACES.getKey(manasSkill).orElseThrow());
+        super(output, RaceRegistry.KEY, lookupProvider, parentProvider, RaceTagProvider::getManasRaceKey);
     }
race-neoforge/src/main/resources/META-INF/neoforge.mods.toml (2)

3-3: Add issue tracker URL.

An empty issue tracker URL makes it difficult for users to report issues. Consider adding the GitHub repository's issue tracker URL.

-issueTrackerURL = ""
+issueTrackerURL = "https://github.com/ManasMods/ManasCore/issues"

11-13: Enhance the mod description.

The current description is quite brief. Consider adding more details about:

  • The specific utilities and core features provided
  • Key functionalities (Registry, Keybinding, Skill, Config modules)
  • Target audience (mod developers, players)
race-neoforge/build.gradle (1)

1-5: Update copyright year to current year.

The copyright notice shows 2025, but it's currently January 2025. It's standard practice to use the current year or a year range (e.g., 2024-2025) for copyright notices.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024-2025. ManasMods
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (4)

1-4: Update the copyright year.

The copyright year is set to 2025, which is in the future. Consider updating it to the current year.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

43-46: Optimize target handling logic.

The current implementation could be more efficient by:

  1. Early return if the target is null
  2. Using a more descriptive variable name than 'mob'
-public boolean onBeingTargeted(ManasRaceInstance instance, Changeable<LivingEntity> target, LivingEntity mob) {
-    if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) target.set(null);
+public boolean onBeingTargeted(ManasRaceInstance instance, Changeable<LivingEntity> target, LivingEntity attacker) {
+    if (target.get() == null) return true;
+    if (attacker.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
+        target.set(null);
+    }
     return true;
 }

48-55: Enhance logging messages for better debugging.

The current log messages are not very descriptive. Consider adding more context such as the entity name, position, or cause of death.

 public boolean onDeath(ManasRaceInstance instance, LivingEntity owner, DamageSource source) {
-    ManasCoreTesting.LOG.info("AWWWWW MANNNNN x2");
+    ManasCoreTesting.LOG.info("TestRaceEvolved: Entity {} died from {}", owner.getName().getString(), source.getMsgId());
     return true;
 }

 public void onRespawn(ManasRaceInstance instance, ServerPlayer owner, boolean conqueredEnd) {
-    ManasCoreTesting.LOG.info("CREEPER x2");
+    ManasCoreTesting.LOG.info("TestRaceEvolved: Player {} respawned. Conquered end: {}", owner.getName().getString(), conqueredEnd);
 }

57-61: Optimize evolution list creation.

Consider using a constant list to avoid creating a new ArrayList on each method call.

+    private static final List<ManasRace> PREVIOUS_EVOLUTIONS = List.of(RegistryTest.TEST_RACE.get());
+
     public List<ManasRace> getPreviousEvolutions(ManasRaceInstance instance, LivingEntity entity) {
-        List<ManasRace> list = new ArrayList<>();
-        list.add(RegistryTest.TEST_RACE.get());
-        return list;
+        return PREVIOUS_EVOLUTIONS;
     }
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java (1)

45-46: Add logging for unregistered evolutions.

Currently, if race == null, the code simply returns. Consider logging a warning to help with troubleshooting or debugging missing evolutions.

 if (race == null) {
+    LOGGER.warn("Received evolution request, but no corresponding race was found for: {}", evolution);
     return;
 }
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (3)

43-49: Reduce frequent calls to markDirty().

markDirty() is called on every server-side entity tick. This can be expensive if many entities exist. Consider marking the storage as dirty only when actual changes occur (e.g., a race updates progress), rather than once per tick.


91-110: Handle the case of setting the same race.

When setting a new race, the code unconditionally removes the old race's attributes and adds the new race's attributes, even if it's the same race instance. This could lead to redundant event triggers or potential double-application of skills if overlooked. Consider short-circuiting when the old and new races are identical.

 public boolean setRace(@NonNull ManasRaceInstance newRace, boolean evolution) {
     ManasRaceInstance oldRace = this.raceInstance;
+    if (oldRace != null && oldRace.equals(newRace)) {
+        // If it's the same race, skip to avoid redundant steps.
+        return false;
+    }

     // ...existing code...
 }

112-122: Consider storing additional state in save/load methods.

If the raceInstance changes frequently (e.g., partial evolution progress or dirty flags), these might need to be persisted. If you eventually need partial or incremental synchronization, storing more fields here could simplify debugging.

race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (2)

104-109: Handle missing or invalid races gracefully.

Objects.requireNonNull(...) throws a NullPointerException if the race is missing in the registry. Consider providing a more descriptive error or a fallback to avoid hard crashes.

- ManasRaceInstance instance = Objects.requireNonNull(RaceAPI.getRaceRegistry().get(skillLocation))
-         .createDefaultInstance();
+ ManasRace race = RaceAPI.getRaceRegistry().get(skillLocation);
+ if (race == null) {
+     throw new IllegalArgumentException("No registered race found for " + skillLocation);
+ }
+ ManasRaceInstance instance = race.createDefaultInstance();

377-380: Skip race evolution if the instance is already the desired evolution.

If a player tries to evolve into the same race, consider short-circuiting. Re-evolving into the same race might trigger unintended re-initialization events or ability resets.

 public void onRaceEvolution(LivingEntity entity, ManasRaceInstance evolution) {
+    if (this.equals(evolution)) {
+        return;
+    }
     this.getRace().onRaceEvolution(this, entity, evolution);
 }
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (3)

33-34: Avoid using AWT Color on dedicated servers.

Relying on java.awt.Color may cause unexpected behavior in server environments. Recommend using Minecraft’s ChatFormatting or built-in text styling to ensure compatibility across different platforms.


55-60: Consider referencing the difficulty in your logic.

The constructor sets the difficulty, but it isn’t used elsewhere in this class. Incorporate logic that uses the difficulty level or remove it if it’s not needed.


341-363: Use stable or unique identifiers for AttributeModifiers if needed.

The record AttributeTemplate uses ResourceLocation as the ID for attribute modifiers. Ensure these IDs don’t conflict across the codebase. Otherwise, a stable UUID or unique ID approach might be more robust.

race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java (1)

15-15: Consider using a more specific logger name.

While the current logger name is acceptable, consider making it more specific to better identify the source of log messages in a larger codebase.

-    public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Race");
+    public static final Logger LOG = LoggerFactory.getLogger(ManasCoreRace.class);
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java (1)

12-19: Consider making the class final and adding documentation.

Since this class only contains static initialization logic, consider:

  1. Making it final to prevent inheritance
  2. Adding documentation to describe the purpose of each registered packet type
+/**
+ * Network registration handler for race-related packets.
+ * Registers the following packets:
+ * - RequestRaceAbilityActivationPacket: Handles race ability activation requests
+ * - RequestRaceEvolutionPacket: Handles race evolution requests
+ */
-public class ManasRaceNetwork {
+public final class ManasRaceNetwork {
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1)

13-38: Add comprehensive documentation for events.

Consider adding Javadoc to document:

  • The purpose and timing of each event
  • The expected usage of EventResult values
  • The threading context of these events
+/**
+ * Defines the core events for the race system.
+ * These events allow for modifying and monitoring race-related actions.
+ */
 public interface RaceEvents {
+    /**
+     * Fired when attempting to set or change a race.
+     * Return PASS to allow the change, FAIL to prevent it.
+     */
     Event<SetRaceEvent> SET_RACE = EventFactory.createEventResult();
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (2)

32-95: Refactor event handlers to reduce code duplication.

The event handlers follow a similar pattern with repeated code blocks. Consider extracting common logic into helper methods.

Here's a suggested refactor:

+private static EventResult handleRaceEvent(LivingEntity entity, 
+    BiFunction<ManasRaceInstance, LivingEntity, Boolean> action) {
+    Optional<ManasRaceInstance> optional = RaceAPI.getRaceFrom(entity).getRace();
+    if (optional.isEmpty()) return EventResult.pass();
+
+    ManasRaceInstance instance = optional.get();
+    if (!instance.canActivateAbility(entity)) return EventResult.pass();
+    return action.apply(instance, entity) ? EventResult.pass() : EventResult.interruptFalse();
+}

 public static void init() {
     EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> {
-        Optional<ManasRaceInstance> optional = RaceAPI.getRaceFrom(entity).getRace();
-        if (optional.isEmpty()) return EventResult.pass();
-
-        ManasRaceInstance instance = optional.get();
-        if (!instance.canActivateAbility(entity)) return EventResult.pass();
-        if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
-        return EventResult.pass();
+        return handleRaceEvent(entity, 
+            (instance, e) -> instance.onEffectAdded(e, source, changeableTarget));
     });
     // Apply similar pattern to other event handlers
 }

26-30: Add documentation for registry initialization.

The registry initialization lacks documentation explaining the purpose of syncToClients and the significance of the registry key.

Add JavaDoc to explain the registry setup:

+/**
+ * Registry for ManasRace instances.
+ * The registry is synchronized to clients to ensure consistent race data across the network.
+ * The registry key is used for creating deferred registers and accessing the registry.
+ */
 public class RaceRegistry {
     private static final ResourceLocation registryId = ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "races");
     public static final Registrar<ManasRace> RACES = RegistrarManager.get(ModuleConstants.MOD_ID).<ManasRace>builder(registryId)
             .syncToClients().build();
     public static final ResourceKey<Registry<ManasRace>> KEY = (ResourceKey<Registry<ManasRace>>) RACES.key();
race-fabric/src/main/resources/fabric.mod.json (4)

11-11: Homepage URL should be provided.

The homepage URL is currently empty. Consider adding the GitHub repository URL or documentation site to help users find more information about the mod.

-    "homepage": ""
+    "homepage": "https://github.com/ManasMods/ManasCore"

20-20: Empty client entrypoint array.

The client entrypoint array is empty. If this mod has no client-side initialization needs, consider removing the empty "client" entry entirely.

-    "client": []

31-31: Specify Fabric API version requirement.

The Fabric API dependency is set to "*" which accepts any version. This could lead to compatibility issues if breaking changes are introduced in future versions.

-    "fabric-api": "*",
+    "fabric-api": ">=${fabric_api_version}",

34-35: Consider adding suggested mods.

The "suggests" section is empty. Consider adding complementary mods that work well with this one to help users discover related content.

   "suggests": {
+    "manascore_skills": "*",
+    "manascore_attributes": "*"
   }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1)

115-150: Consider using a configuration file for test values.

Currently, many test values (attribute modifiers, explosion radii, etc.) are hardcoded. Consider moving these to a configuration file to make testing more flexible and maintainable.

This would allow:

  1. Easy adjustment of test values without code changes
  2. Different test configurations for different scenarios
  3. Better separation of test configuration from implementation
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between db27667 and d910e45.

⛔ Files ignored due to path filters (3)
  • race-fabric/src/main/resources/assets/manascore/icon.png is excluded by !**/*.png
  • race-fabric/src/main/resources/assets/manascore/logo.png is excluded by !**/*.png
  • race-neoforge/src/main/resources/icon.png is excluded by !**/*.png
📒 Files selected for processing (37)
  • race-common/build.gradle (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/InternalRacePacketActions.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java (1 hunks)
  • race-common/src/main/resources/architectury.common.json (1 hunks)
  • race-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
  • race-common/src/main/resources/manascore_race.accesswidener (1 hunks)
  • race-common/src/main/resources/manascore_race.mixins.json (1 hunks)
  • race-fabric/build.gradle (1 hunks)
  • race-fabric/src/main/java/io/github/manasmods/manascore/race/fabric/ManasCoreRaceFabric.java (1 hunks)
  • race-fabric/src/main/resources/fabric.mod.json (1 hunks)
  • race-fabric/src/main/resources/manascore_race-fabric.mixins.json (1 hunks)
  • race-neoforge/build.gradle (1 hunks)
  • race-neoforge/gradle.properties (1 hunks)
  • race-neoforge/src/main/java/io/github/manasmods/manascore/race/neoforge/ManasCoreRaceNeoForge.java (1 hunks)
  • race-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
  • race-neoforge/src/main/resources/manascore_race-neoforge.mixins.json (1 hunks)
  • settings.gradle (2 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1 hunks)
  • skill-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
  • testing-common/build.gradle (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1 hunks)
  • testing-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • race-neoforge/gradle.properties
  • race-common/src/main/resources/architectury.common.json
  • race-common/src/main/resources/manascore_race.accesswidener
  • skill-common/src/main/resources/assets/manascore/lang/en_us.json
  • race-common/src/main/resources/assets/manascore/lang/en_us.json
  • race-common/src/main/resources/manascore_race.mixins.json
  • race-neoforge/src/main/resources/manascore_race-neoforge.mixins.json
  • race-fabric/src/main/resources/manascore_race-fabric.mixins.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • testing-common/build.gradle
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java
  • testing-common/src/main/resources/assets/manascore/lang/en_us.json
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java
🔇 Additional comments (31)
settings.gradle (1)

17-17: Verify the missing Registry module.

The PR objectives mention adding a Registry module, but it's not included in the module list. Please clarify if this was intentional or if the Registry functionality is implemented within another module.

The multi-platform module structure (common, fabric, neoforge) follows best practices for cross-platform Minecraft modding.

race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java (1)

8-16: LGTM! Well-structured class definition.

The class is properly designed as an abstract tag provider, following Minecraft's data generation patterns. The imports are clean and the inheritance hierarchy is appropriate.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (4)

87-89: Use the correct constructor for ResourceLocation
This code calls ResourceLocation.fromNamespaceAndPath, which may not exist or be supported. Instead, construct a ResourceLocation using its standard (namespace, path) constructor.

- return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath());
+ return new ResourceLocation(id.getNamespace(), "icons/skills/" + id.getPath());

101-107: Missing hashCode() override when equals() is overridden
Since equals() is overridden, you should also override hashCode() to maintain the contract between these two methods, especially for use in hash-based data structures.

+ @Override
+ public int hashCode() {
+     ResourceLocation registryName = getRegistryName();
+     return registryName != null ? registryName.hashCode() : 0;
+ }

229-231: Invalid argument type for removing attribute modifiers
The removeModifier method expects a UUID or a valid name, but entry.getValue().id() returns a ResourceLocation. This mismatch will lead to runtime issues.

Also applies to: 244-244


436-436: Incorrect AttributeModifier constructor argument types
Using a ResourceLocation as the first parameter is invalid, since the AttributeModifier constructor generally expects a UUID and a String name, not a ResourceLocation. This type mismatch will cause runtime errors.

- return new AttributeModifier(this.id, this.amount * i, this.operation);
+ // Example fix: add a UUID field and a string name field to AttributeTemplate
+ return new AttributeModifier(UUID.fromString("00000000-0000-0000-0000-000000000000"),
+                              this.id.toString(),
+                              this.amount * i,
+                              this.operation);
race-fabric/build.gradle (2)

6-8: LGTM! Access widener configuration looks good.

The Loom configuration correctly references the access widener path from the race-common project, maintaining consistency across subprojects.


10-14: Verify the dependency configurations and their impact.

The dependencies are correctly configured to use 'transformProductionFabric', but let's verify:

  1. That all required dependencies are included
  2. That these dependencies don't introduce circular dependencies
  3. That the transformation configuration is consistently used across other Fabric modules
✅ Verification successful

Dependencies are correctly configured and consistent

The dependency configuration review shows:

  • All Fabric modules consistently use 'transformProductionFabric'
  • No circular dependencies detected
  • Dependencies follow a logical hierarchy and are appropriate for each module's functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify dependency configurations across Fabric modules

# Check for other Fabric modules using similar configurations
echo "Checking for other Fabric module configurations..."
fd -e gradle --exec grep -l "transformProductionFabric" {}

# Check for potential circular dependencies
echo "Checking for potential circular dependencies..."
fd -e gradle --exec grep -l "race-fabric" {}

Length of output: 423


Script:

#!/bin/bash
# Examine the content of Gradle files using transformProductionFabric
echo "=== Root build.gradle ==="
cat build.gradle

echo -e "\n=== storage-fabric/build.gradle ==="
cat storage-fabric/build.gradle

echo -e "\n=== skill-fabric/build.gradle ==="
cat skill-fabric/build.gradle

# We already know race-fabric/build.gradle content from the review

Length of output: 10972

race-common/build.gradle (2)

1-3: Verify the access widener file existence and configuration.

The access widener configuration looks correct, following the project's convention. However, we should verify that the referenced file exists and is properly configured.

✅ Verification successful

Access widener configuration is valid and follows project conventions

The access widener file exists and contains valid configuration, consistent with other modules in the project.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the access widener file exists and examine its content
if [ -f "race-common/src/main/resources/manascore_race.accesswidener" ]; then
    echo "Access widener file exists"
    cat "race-common/src/main/resources/manascore_race.accesswidener"
else
    echo "Warning: Access widener file not found"
fi

# Look for other access widener files in the project for consistency check
echo -e "\nChecking other access widener files for consistency:"
fd -e accesswidener

Length of output: 956


5-9: Review the implications of disabled transitive dependencies.

While disabling transitive dependencies can help avoid dependency conflicts and reduce build size, it may also exclude critical dependencies. Please verify that:

  1. All necessary dependencies are explicitly declared
  2. No critical transitive dependencies are being excluded
✅ Verification successful

Verified: Disabled transitive dependencies are appropriate

The dependency structure is well-designed with network-common having no dependencies to propagate, and all modules explicitly declaring their required dependencies. The consistent use of transitive false helps maintain a clean and predictable dependency graph.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for dependency declarations in the referenced modules
echo "Checking dependencies in referenced modules:"
for module in "skill-common" "network-common" "storage-common"; do
    echo -e "\nChecking $module/build.gradle:"
    if [ -f "$module/build.gradle" ]; then
        # Extract dependencies block
        awk '/dependencies {/,/}/' "$module/build.gradle"
    else
        echo "Warning: $module/build.gradle not found"
    fi
done

# Look for any shared dependencies across modules
echo -e "\nChecking for potential shared dependencies across all build.gradle files:"
fd build.gradle | xargs grep -l "implementation"

Length of output: 1665


Script:

#!/bin/bash
# Check network-common's dependencies and potential shared dependencies
echo "Checking network-common's dependencies:"
cat network-common/build.gradle

echo -e "\nChecking for common implementation dependencies across modules:"
for f in $(fd build.gradle); do
    echo -e "\n=== $f ==="
    awk '/dependencies {/,/}/' "$f" | grep "implementation"
done

Length of output: 8300

race-neoforge/src/main/resources/META-INF/neoforge.mods.toml (5)

1-2: Consider tightening the loader version constraint.

The current constraint "[2,)" is very broad and might lead to compatibility issues with future versions. Consider setting an upper bound to ensure compatibility.


19-19: Consider constraining the NeoForge version range.

The current constraint "[21,)" is very broad. Consider setting an upper bound to ensure compatibility with tested versions.


14-14: Verify the icon file existence.

Ensure that "icon.png" exists in the correct resources directory.

✅ Verification successful

Icon file verification successful

The icon.png file exists at the correct location in race-neoforge/src/main/resources/icon.png.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the icon file exists in the resources directory
fd -t f "icon.png" race-neoforge/src/main/resources/

Length of output: 94


44-48: Verify mixin configurations and maintain consistent naming.

  1. The mixin configurations use different naming patterns:

    • ${mod_id}.mixins.json
    • manascore_race-neoforge.mixins.json
  2. Verify that both mixin configuration files exist.

✅ Verification successful

Mixin configurations verified and naming pattern is consistent

Both mixin configurations exist and follow the project's established naming pattern:

  • Common module: ${mod_id}.mixins.json (resolves to manascore_race.mixins.json)
  • NeoForge module: manascore_race-neoforge.mixins.json
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for the existence of mixin configuration files
fd -t f "mixins.json" .

# Look for references to these mixin configurations
rg "mixins.json" -t json

Length of output: 1965


37-42: Verify manascore_network dependency.

Ensure that the manascore_network module exists and is properly referenced.

✅ Verification successful

The manascore_network dependency is correctly configured

The module exists in the network-common directory and is properly referenced as a dependency across multiple other modules in the project, including storage-neoforge, skill-neoforge, and network-neoforge.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for the existence of manascore_network module
fd -t f "manascore_network" .

# Check for references to manascore_network in build files
rg "manascore_network" -t gradle

Length of output: 307


Script:

#!/bin/bash
# Check mod configuration files for module ID declarations
fd -e toml -e json "mods" .

# Search for manascore_network in all files to find module ID declarations
rg -g '!{*.md,*.txt}' -i "modid.*manascore_network|manascore_network.*modid"

Length of output: 1143

race-neoforge/build.gradle (3)

6-8: LGTM! Good practice sharing access widener configuration.

Sharing the access widener path with the common module ensures consistency across the project.


16-18: Verify mod_id property and access widener file existence.

Please ensure that:

  1. The mod_id property is properly defined in the project
  2. The referenced access widener file exists
✅ Verification successful

Access widener configuration is correct

The setup is properly configured:

  • mod_id property is defined in the root build.gradle
  • Access widener file exists at race-common/src/main/resources/manascore_race.accesswidener
  • race-neoforge correctly inherits the access widener path from race-common
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify mod_id property and access widener file

# Check if mod_id is defined in gradle.properties or settings
echo "Checking for mod_id property:"
for file in "gradle.properties" "settings.gradle"; do
  if [ -f "$file" ] && grep -q "mod_id" "$file"; then
    echo "✓ mod_id found in $file"
    grep "mod_id" "$file"
  fi
done

# Check for access widener file
echo -e "\nChecking for access widener file:"
find "race-neoforge/src" -name "*.accesswidener"

Length of output: 432


Script:

#!/bin/bash
# Expanded search for mod_id and access widener files

echo "Checking for mod_id in all Gradle files:"
fd -e gradle -e properties -x grep -l "mod_id" {} \;
fd -e gradle -e properties -x grep "mod_id" {} \;

echo -e "\nChecking for any .accesswidener files in the repository:"
fd ".accesswidener$"

echo -e "\nChecking build.gradle content in race-neoforge:"
cat race-neoforge/build.gradle

Length of output: 2586


10-14: Verify dependency requirements and availability.

Please ensure that:

  1. These dependencies are actually required by the race-neoforge module
  2. All referenced projects exist and are properly configured
✅ Verification successful

Dependencies are correctly configured and follow project architecture

The dependencies are valid and properly configured:

  • The project follows a modular architecture where platform-specific implementations (neoforge) depend on their common counterparts
  • The transformProductionNeoForge configuration is automatically set up by the build system for all NeoForge modules
  • The dependency structure matches the project's established pattern for cross-platform module organization
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of dependent projects and their configurations

# Check if the dependent projects exist
for project in "skill-common" "network-common" "storage-common"; do
  if [ -d "$project" ]; then
    echo "✓ Project $project exists"
    
    # Check if the project has the necessary Gradle configuration
    if grep -q "transformProductionNeoForge" "$project/build.gradle"; then
      echo "✓ Project $project has transformProductionNeoForge configuration"
    else
      echo "✗ Project $project missing transformProductionNeoForge configuration"
    fi
  else
    echo "✗ Project $project not found"
  fi
done

# Check for actual usage of these dependencies in the race-neoforge module
echo -e "\nChecking dependency usage:"
rg -l "import.*skill.*common|import.*network.*common|import.*storage.*common" "race-neoforge/src/"

Length of output: 1331


Script:

#!/bin/bash
# List all source files and check for any usage of the common modules

echo "=== Source files in race-neoforge ==="
fd -e java -e kt -e groovy . race-neoforge/src/

echo -e "\n=== Searching for any references to common modules ==="
rg -l "skill|network|storage" race-neoforge/src/

echo -e "\n=== Checking for Gradle configuration files ==="
fd build.gradle
fd settings.gradle

echo -e "\n=== Checking content of root build.gradle ==="
cat build.gradle

Length of output: 11652

testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1)

26-32: Review attribute modifier values for game balance.

The current attribute modifications seem extremely high:

  • Jump strength is doubled (2x multiplier)
  • Health is increased by 100 points

These values might significantly impact game balance. Consider reducing them to more reasonable values unless these extreme values are intentionally set for testing purposes.

✅ Verification successful

High attribute values are intentional for testing purposes

The class is located in the testing package and is specifically designed for testing race evolution mechanics. Both TestRace and TestRaceEvolved use intentionally extreme values to verify attribute modification system:

  • TestRace (parent): +50 attack damage, +10 armor
  • TestRaceEvolved (evolution): 2x jump strength, +100 health
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other race implementations to compare attribute modifier values
ast-grep --pattern 'class $_ extends ManasRace {
  $$$
  addAttributeModifier($_, $_, $_, $_)
  $$$
}'

Length of output: 104


Script:

#!/bin/bash
# First, find all relevant race files
fd -e java "Race" 

# Then examine their content for attribute modifiers
echo "=== File contents ==="
fd -e java "Race" -x cat {}

Length of output: 61345

race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java (1)

35-36: Server-side validation looks good.

It's a good practice to ensure we're only processing this packet on the server side. This helps avoid unintended changes caused by client-side code.

race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)

128-135: equals() ignores tag data.

Currently, two instances with the same race but different internal tags are considered equal. This may lead to logical inconsistencies if tag-based data is critical to identifying a unique race instance. Evaluate whether the tag should factor into the equality check.

race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (2)

1-4: License header looks good.

The inclusion of the GPLv3 license header aligns with open source requirements.


134-135: Question the default return value in canTick().

canTick() returns false by default, disabling race-specific ticking unless explicitly overridden. Confirm if this is intentional, as it may confuse future contributors who expect a race to tick by default.

race-fabric/src/main/java/io/github/manasmods/manascore/race/fabric/ManasCoreRaceFabric.java (2)

1-4: Confirmed license header.

GPL v3 license is properly declared.


13-15: Initialization looks correct.

onInitialize() simply delegates to ManasCoreRace.init(), which is concise and appropriate for Fabric mods. Confirm that no additional client-specific registrations are needed.

race-neoforge/src/main/java/io/github/manasmods/manascore/race/neoforge/ManasCoreRaceNeoForge.java (2)

1-4: License header is acceptable.

GPL v3 license is consistently applied here.


13-17: Validate event bus usage.

Your constructor calls ManasCoreRace.init() directly. Consider whether this method should register events using modEventBus to ensure NeoForge event hooks are fully integrated.

race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java (1)

14-22: LGTM! Well-structured initialization sequence.

The class follows good practices with:

  • Final class declaration preventing unwanted inheritance
  • Clear initialization sequence for race-related components
  • Proper separation of concerns across different modules
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/InternalRacePacketActions.java (2)

35-40: Add environment check and error handling for network operations.

Similar to the previous method, this one needs environment checks. Additionally, consider adding error handling for network operations.

Add environment check and error handling:

 public static void sendRaceEvolutionPacket(ResourceLocation evolution) {
+    if (Platform.getEnvironment() != Env.CLIENT) {
+        return;
+    }
     var minecraft = Minecraft.getInstance();
     Player player = minecraft.player;
     if (player == null) return;
-    NetworkManager.sendToServer(new RequestRaceEvolutionPacket(evolution));
+    try {
+        NetworkManager.sendToServer(new RequestRaceEvolutionPacket(evolution));
+    } catch (Exception e) {
+        LOGGER.error("Failed to send race evolution packet", e);
+    }
 }

24-29: Add environment check before accessing Minecraft instance.

The method directly calls Minecraft.getInstance() which should only be called from the client thread. While the JavaDoc mentions client-side execution, adding an explicit environment check would prevent potential crashes.

Add this check at the start of the method:

 public static void sendRaceAbilityActivationPacket() {
+    if (Platform.getEnvironment() != Env.CLIENT) {
+        return;
+    }
     var minecraft = Minecraft.getInstance();
     Player player = minecraft.player;
     if (player == null) return;
     NetworkManager.sendToServer(new RequestRaceAbilityActivationPacket());
 }
race-fabric/src/main/resources/fabric.mod.json (1)

22-24: Verify mixin configurations.

Ensure that both mixin configuration files exist and are properly configured:

  • ${mod_id}.mixins.json
  • ${mod_id}-fabric.mixins.json
✅ Verification successful

✓ Mixin configurations verified successfully

Both required mixin configuration files exist and are properly configured with all necessary fields:

  • manascore_race.mixins.json in race-common
  • manascore_race-fabric.mixins.json in race-fabric
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if mixin configuration files exist
echo "Checking for mixin configuration files..."
fd --type f --extension json . | grep -E "mixins\.json$"

# Validate mixin configuration format
echo "Validating mixin configurations..."
for f in $(fd --type f --extension json . | grep -E "mixins\.json$"); do
  echo "Checking $f..."
  jq -e '.package and .mixins and .client' "$f" || echo "Invalid mixin configuration in $f"
done

Length of output: 6123

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024. ManasMods
* Copyright (c) 2025. ManasMods
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update copyright year to current year.

The copyright year is set to 2025, but it's currently January 2025. Copyright notices typically use the current year or the year of creation, not future dates.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
📝 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
* Copyright (c) 2025. ManasMods
* Copyright (c) 2024. ManasMods

Comment on lines +1 to +4
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update copyright year to current year (2024).

The copyright year is set to 2025, which is in the future. Please update it to the current year.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

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

Comment on lines +34 to +36
public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) {
entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 10F, Level.ExplosionInteraction.BLOCK);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add safety checks for explosion ability.

The explosion ability lacks safety checks that could prevent griefing or unintended damage. Consider adding:

  • Cooldown period
  • Permission checks
  • Safe zone restrictions
  • Configurable explosion power
 public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) {
+    if (entity.level().isClientSide()) return;
+    if (instance.isOnCooldown()) return;
+    // Configure explosion power through race attributes or config
+    float explosionPower = 10F;
     entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 
-        10F, Level.ExplosionInteraction.BLOCK);
+        explosionPower, Level.ExplosionInteraction.NONE);
+    instance.setCooldown(20 * 30); // 30 seconds cooldown
 }

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

Comment on lines 17 to 19
default boolean setRace(@NotNull ResourceLocation raceId) {
return setRace(RaceAPI.getRaceRegistry().get(raceId).createDefaultInstance(), false);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix parameter naming and standardize null annotations.

  1. The parameter name skill in setRace and evolveRace methods should be race to match the context.
  2. Standardize the use of null annotations by using either JetBrains' @NotNull or Lombok's @nonnull consistently.
-    default boolean setRace(@NotNull ResourceLocation raceId) {
+    default boolean setRace(@NonNull ResourceLocation raceId) {

-    default boolean setRace(@NonNull ManasRace skill) {
+    default boolean setRace(@NonNull ManasRace race) {
-        return setRace(skill.createDefaultInstance(), false);
+        return setRace(race.createDefaultInstance(), false);

-    default boolean evolveRace(@NonNull ManasRace skill) {
+    default boolean evolveRace(@NonNull ManasRace race) {
-        return setRace(skill.createDefaultInstance(), true);
+        return setRace(race.createDefaultInstance(), true);

Also applies to: 21-23, 31-33

Comment on lines +15 to +17
Event<RaceTickEvent> SKILL_PRE_TICK = EventFactory.createEventResult();
Event<RacePostTickEvent> SKILL_POST_TICK = EventFactory.createLoop();
Event<RaceAbilityActivationEvent> ACTIVATE_ABILITY = EventFactory.createEventResult();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix inconsistent event naming.

The event names SKILL_PRE_TICK and SKILL_POST_TICK use "SKILL" prefix while being in a race-related class. Consider renaming for consistency:

-    Event<RaceTickEvent> SKILL_PRE_TICK = EventFactory.createEventResult();
-    Event<RacePostTickEvent> SKILL_POST_TICK = EventFactory.createLoop();
+    Event<RaceTickEvent> RACE_PRE_TICK = EventFactory.createEventResult();
+    Event<RacePostTickEvent> RACE_POST_TICK = EventFactory.createLoop();
📝 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
Event<RaceTickEvent> SKILL_PRE_TICK = EventFactory.createEventResult();
Event<RacePostTickEvent> SKILL_POST_TICK = EventFactory.createLoop();
Event<RaceAbilityActivationEvent> ACTIVATE_ABILITY = EventFactory.createEventResult();
Event<RaceTickEvent> RACE_PRE_TICK = EventFactory.createEventResult();
Event<RacePostTickEvent> RACE_POST_TICK = EventFactory.createLoop();
Event<RaceAbilityActivationEvent> ACTIVATE_ABILITY = EventFactory.createEventResult();

Comment on lines +48 to +50
public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) {
entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 4F, Level.ExplosionInteraction.BLOCK);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add safety constraints to explosion ability.

The current implementation allows unlimited explosions without cooldown or resource cost, which could be exploited.

+    private long lastExplosionTime = 0;
+    private static final long EXPLOSION_COOLDOWN = 10000; // 10 seconds in milliseconds
+
     public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) {
+        long currentTime = System.currentTimeMillis();
+        if (currentTime - lastExplosionTime < EXPLOSION_COOLDOWN) {
+            return;
+        }
         entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 4F, Level.ExplosionInteraction.BLOCK);
+        lastExplosionTime = currentTime;
     }

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

Comment on lines 79 to 85
public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
if (owner.isShiftKeyDown() && target instanceof Pillager) {
amount.set(amount.get() * 100F);
ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get());
}
return true;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential overflow from excessive damage multiplier.

The 100x damage multiplier could cause integer overflow with high base damage values. Consider using a more reasonable multiplier and adding an upper limit.

     public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
         if (owner.isShiftKeyDown() && target instanceof Pillager) {
-            amount.set(amount.get() * 100F);
+            float newAmount = Math.min(amount.get() * 2.5F, 50F); // 2.5x damage with 50 damage cap
+            amount.set(newAmount);
             ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get());
         }
         return true;
     }
📝 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 boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
if (owner.isShiftKeyDown() && target instanceof Pillager) {
amount.set(amount.get() * 100F);
ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get());
}
return true;
}
public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
if (owner.isShiftKeyDown() && target instanceof Pillager) {
float newAmount = Math.min(amount.get() * 2.5F, 50F); // 2.5x damage with 50 damage cap
amount.set(newAmount);
ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get());
}
return true;
}

Comment on lines +66 to +71
() -> new Item(new Item.Properties().arch$tab(TESTING_TAB).stacksTo(69)
.attributes(ItemAttributeModifiers.builder()
.add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 10,
AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND)
.add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 50,
AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND).build())));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Adjust attribute modifier values for testing.

The current critical hit modifiers are extremely high:

  • 10x critical damage multiplier
  • 50% critical chance
    Consider using more reasonable values to ensure proper testing of edge cases.
                             .attributes(ItemAttributeModifiers.builder()
-                                    .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 10,
-                                            AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND)
-                                    .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 50,
-                                            AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND).build())));
+                                    .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 1.5,
+                                            AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND)
+                                    .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 0.15,
+                                            AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND).build())));
📝 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
() -> new Item(new Item.Properties().arch$tab(TESTING_TAB).stacksTo(69)
.attributes(ItemAttributeModifiers.builder()
.add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 10,
AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND)
.add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 50,
AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND).build())));
() -> new Item(new Item.Properties().arch$tab(TESTING_TAB).stacksTo(69)
.attributes(ItemAttributeModifiers.builder()
.add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 1.5,
AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND)
.add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 0.15,
AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND).build())));

Comment on lines 142 to 146
} else if (entity.getItem().is(Items.GOLD_INGOT)) {
Races storage = RaceAPI.getRaceFrom(player);
storage.setRace(RegistryTest.TEST_RACE.getId());
ManasCoreTesting.LOG.info("Set to Test Race!");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for race management.

The race setting logic lacks error handling and race ID validation.

             else if (entity.getItem().is(Items.GOLD_INGOT)) {
                 Races storage = RaceAPI.getRaceFrom(player);
+                if (storage == null) {
+                    ManasCoreTesting.LOG.error("Failed to get race storage for player");
+                    return EventResult.pass();
+                }
+                if (!RaceAPI.getRaceRegistry().containsKey(RegistryTest.TEST_RACE.getId())) {
+                    ManasCoreTesting.LOG.error("Test race is not registered");
+                    return EventResult.pass();
+                }
                 storage.setRace(RegistryTest.TEST_RACE.getId());
                 ManasCoreTesting.LOG.info("Set to Test Race!");
+                player.displayClientMessage(Component.literal("Race set to Test Race"), true);
             }
📝 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
} else if (entity.getItem().is(Items.GOLD_INGOT)) {
Races storage = RaceAPI.getRaceFrom(player);
storage.setRace(RegistryTest.TEST_RACE.getId());
ManasCoreTesting.LOG.info("Set to Test Race!");
}
} else if (entity.getItem().is(Items.GOLD_INGOT)) {
Races storage = RaceAPI.getRaceFrom(player);
if (storage == null) {
ManasCoreTesting.LOG.error("Failed to get race storage for player");
return EventResult.pass();
}
if (!RaceAPI.getRaceRegistry().containsKey(RegistryTest.TEST_RACE.getId())) {
ManasCoreTesting.LOG.error("Test race is not registered");
return EventResult.pass();
}
storage.setRace(RegistryTest.TEST_RACE.getId());
ManasCoreTesting.LOG.info("Set to Test Race!");
player.displayClientMessage(Component.literal("Race set to Test Race"), true);
}

Comment on lines +174 to +189
public void onMobRemoved(LivingEntity entity, int i, Entity.RemovalReason removalReason) {
if (removalReason == Entity.RemovalReason.KILLED) {
if (entity.level() instanceof ServerLevel level) {
double d = entity.getX();
double e = entity.getY() + (double)(entity.getBbHeight() / 2.0F);
double f = entity.getZ();
float g = 10.0F + entity.getRandom().nextFloat() * 2.0F;
if (this.equals(RegistryTest.TEST_MOB_EFFECT_PARTICLE.get()))
level.explode(entity, null, AbstractWindCharge.EXPLOSION_DAMAGE_CALCULATOR, d, e, f, g,
false, Level.ExplosionInteraction.TRIGGER, ParticleTypes.GUST_EMITTER_SMALL,
ParticleTypes.GUST_EMITTER_LARGE, SoundEvents.BREEZE_WIND_CHARGE_BURST);
else level.explode(entity, Explosion.getDefaultDamageSource(level, entity), null, d, e, f, g,
false, Level.ExplosionInteraction.MOB, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add safety limits to death explosion mechanics.

The current implementation uses an uncapped random explosion radius and could create unexpectedly large explosions.

-                    float g = 10.0F + entity.getRandom().nextFloat() * 2.0F;
+                    float baseRadius = 3.0F;
+                    float randomAddition = entity.getRandom().nextFloat() * 1.0F;
+                    float g = Math.min(baseRadius + randomAddition, 4.0F);
📝 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 void onMobRemoved(LivingEntity entity, int i, Entity.RemovalReason removalReason) {
if (removalReason == Entity.RemovalReason.KILLED) {
if (entity.level() instanceof ServerLevel level) {
double d = entity.getX();
double e = entity.getY() + (double)(entity.getBbHeight() / 2.0F);
double f = entity.getZ();
float g = 10.0F + entity.getRandom().nextFloat() * 2.0F;
if (this.equals(RegistryTest.TEST_MOB_EFFECT_PARTICLE.get()))
level.explode(entity, null, AbstractWindCharge.EXPLOSION_DAMAGE_CALCULATOR, d, e, f, g,
false, Level.ExplosionInteraction.TRIGGER, ParticleTypes.GUST_EMITTER_SMALL,
ParticleTypes.GUST_EMITTER_LARGE, SoundEvents.BREEZE_WIND_CHARGE_BURST);
else level.explode(entity, Explosion.getDefaultDamageSource(level, entity), null, d, e, f, g,
false, Level.ExplosionInteraction.MOB, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
}
}
}
public void onMobRemoved(LivingEntity entity, int i, Entity.RemovalReason removalReason) {
if (removalReason == Entity.RemovalReason.KILLED) {
if (entity.level() instanceof ServerLevel level) {
double d = entity.getX();
double e = entity.getY() + (double)(entity.getBbHeight() / 2.0F);
double f = entity.getZ();
float baseRadius = 3.0F;
float randomAddition = entity.getRandom().nextFloat() * 1.0F;
float g = Math.min(baseRadius + randomAddition, 4.0F);
if (this.equals(RegistryTest.TEST_MOB_EFFECT_PARTICLE.get()))
level.explode(entity, null, AbstractWindCharge.EXPLOSION_DAMAGE_CALCULATOR, d, e, f, g,
false, Level.ExplosionInteraction.TRIGGER, ParticleTypes.GUST_EMITTER_SMALL,
ParticleTypes.GUST_EMITTER_LARGE, SoundEvents.BREEZE_WIND_CHARGE_BURST);
else level.explode(entity, Explosion.getDefaultDamageSource(level, entity), null, d, e, f, g,
false, Level.ExplosionInteraction.MOB, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
}
}
}

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

♻️ Duplicate comments (4)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (4)

88-90: ⚠️ Potential issue

Use correct constructor for ResourceLocation.
Using ResourceLocation.fromNamespaceAndPath(...) may cause compilation or runtime issues if that method doesn’t exist in the codebase. Instead, use the constructor that takes (namespace, path) as arguments.

Apply this diff to fix the issue:

- return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath());
+ return new ResourceLocation(id.getNamespace(), "icons/skills/" + id.getPath());

102-108: 🛠️ Refactor suggestion

Override hashCode() when equals() is overridden.
To maintain the contract between equals() and hashCode(), ensure that both are overridden together. Otherwise, objects of this class can behave unpredictably in hashed collections.

Consider adding:

+    @Override
+    public int hashCode() {
+        return Objects.hash(getRegistryName());
+    }

442-442: ⚠️ Potential issue

Use correct constructor for ResourceLocation.
Similar to the icon path creation, using ResourceLocation.fromNamespaceAndPath(...) may lead to issues if that method does not exist in the environment. Switch to a constructor-based approach.

Suggested fix:

- this(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, id), amount, operation);
+ this(new ResourceLocation(ModuleConstants.MOD_ID, id), amount, operation);

445-447: ⚠️ Potential issue

Incorrect use of AttributeModifier constructor and identifiers.
Minecraft’s AttributeModifier typically expects a UUID and a string for the name, or certain other supported signatures. Passing a ResourceLocation as the first argument will cause a type mismatch at runtime.

Apply this diff to resolve the issue by switching to a proper UUID and name:

- public static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) {
+ public static record AttributeTemplate(UUID id, String name, double amount, AttributeModifier.Operation operation) {

  public AttributeTemplate(UUID id, String name, double amount, AttributeModifier.Operation operation) {
      this.id = id;
      this.name = name;
      this.amount = amount;
      this.operation = operation;
  }

  public AttributeModifier create(double i) {
-     return new AttributeModifier(this.id, this.amount * i, this.operation);
+     return new AttributeModifier(this.id, this.name, this.amount * i, this.operation);
  }
}
🧹 Nitpick comments (13)
attribute-common/build.gradle (1)

1-4: Update copyright year to reflect actual creation date.

The copyright year is set to 2025, which is the current year. Consider using the year when the code was first created, or use a range (e.g., "2023-2025") if this is a modification of existing code.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2023-2025. ManasMods
attribute-neoforge/build.gradle (1)

1-5: Update the copyright year to 2024.

The copyright year is set to 2025, which extends beyond the current date. Consider updating it to 2024 or using a range (e.g., 2023-2024) if applicable.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
attribute-common/src/main/resources/assets/manascore/lang/en_us.json (1)

1-7: LGTM! Consider adding unit clarification for multipliers.

The translations are clear and follow a consistent naming pattern. However, consider adding unit clarification for multiplier attributes (e.g., "Critical Damage Multiplier (x)" or "Swim Speed Multiplier (%)") to improve user understanding.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (3)

23-25: Consider reducing the maximum range for critical damage multiplier.

A maximum of 1024x critical damage seems excessive and could lead to balance issues. Consider reducing it to a more reasonable value (e.g., 5.0 or 10.0) to prevent potential exploits or balance issues.


34-41: Clarify elytra interaction in documentation.

The documentation needs more clarity about:

  1. How this attribute interacts with vanilla elytra mechanics
  2. Whether the multiplier stacks with elytra gliding
  3. What value enables gliding without elytra (is it any value > 0?)

58-72: Reduce code duplication in registration methods.

The registerPlayerAttribute and registerGenericAttribute methods share almost identical code. Consider refactoring to reduce duplication:

+ private static Holder<Attribute> registerAttribute(String modID, String id, String name, double amount,
+                                                  double min, double max, boolean syncable, 
+                                                  Attribute.Sentiment sentiment, 
+                                                  Consumer<Holder<Attribute>> registrationCallback) {
+     Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, 
+             getResourceKey(modID, id),
+             new RangedAttribute(name, amount, min, max)
+                 .setSyncable(syncable)
+                 .setSentiment(sentiment));
+     registrationCallback.accept(attribute);
+     return attribute;
+ }
+
- public static Holder<Attribute> registerPlayerAttribute(String modID, String id, String name, double amount,
-                                                         double min, double max, boolean syncable, 
-                                                         Attribute.Sentiment sentiment) {
-     Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, 
-             getResourceKey(modID, id),
-             new RangedAttribute(name, amount, min, max)
-                 .setSyncable(syncable)
-                 .setSentiment(sentiment));
-     ManasCoreAttributeRegister.registerToPlayers(attribute);
-     return attribute;
- }
+ public static Holder<Attribute> registerPlayerAttribute(String modID, String id, String name, 
+         double amount, double min, double max, boolean syncable, Attribute.Sentiment sentiment) {
+     return registerAttribute(modID, id, name, amount, min, max, syncable, sentiment, 
+             ManasCoreAttributeRegister::registerToPlayers);
+ }

- public static Holder<Attribute> registerGenericAttribute(String modID, String id, String name, double amount,
-                                                         double min, double max, boolean syncable, 
-                                                         Attribute.Sentiment sentiment) {
-     Holder<Attribute> attribute = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, 
-             getResourceKey(modID, id),
-             new RangedAttribute(name, amount, min, max)
-                 .setSyncable(syncable)
-                 .setSentiment(sentiment));
-     ManasCoreAttributeRegister.registerToGeneric(attribute);
-     return attribute;
- }
+ public static Holder<Attribute> registerGenericAttribute(String modID, String id, String name,
+         double amount, double min, double max, boolean syncable, Attribute.Sentiment sentiment) {
+     return registerAttribute(modID, id, name, amount, min, max, syncable, sentiment,
+             ManasCoreAttributeRegister::registerToGeneric);
+ }
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java (1)

32-39: Refactor Repetitive Attribute Checking

Here, you retrieve the attribute instance and check its nullity and value to modify glide speed. This same pattern (lines 41-48 and 50-57) is repeated for lava speed and swim speed. Consider refactoring into a common utility method to avoid code duplication:

- if (instance == null || instance.getValue() <= 0) return vec3;
- return vec3.multiply(instance.getValue(), 1, instance.getValue());
+ return multiplyIfValid(instance, vec3);

...

private Vec3 multiplyIfValid(AttributeInstance instance, Vec3 original) {
    if (instance == null || instance.getValue() <= 0) {
        return original;
    }
    return original.multiply(instance.getValue(), 1, instance.getValue());
}
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java (1)

6-15: Single Responsibility & Error Handling

The init() method cleanly registers the C2S payload. However, consider handling scenarios in which the registration might fail or if the packet type is already registered. Logging or a fallback mechanism could provide more robust error handling.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java (2)

2-2: Update copyright year.

The copyright year is set to 2025, which is incorrect as it's currently 2024.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

24-31: Add validation for network packet sending.

While the null check is good, consider adding validation before sending the network packet to ensure the player is in a valid state for gliding.

 public void canStartGliding(CallbackInfo cb) {
     LocalPlayer player = Minecraft.getInstance().player;
     if (player == null) return;
     if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) {
+        if (!player.isSpectator() && !player.isCreative()) {
+            // Check if player has equipped elytra before starting glide
+            if (!player.getItemBySlot(EquipmentSlot.CHEST).isEmpty()) {
                 player.startFallFlying();
                 NetworkManager.sendToServer(new RequestGlideStartPacket());
+            }
+        }
     }
 }
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java (3)

2-2: Update copyright year.

The copyright year is set to 2025, which is incorrect as it's currently 2024.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

24-29: Document the attack damage calculation and extract magic numbers.

The damage calculation formula uses magic numbers without explanation. Consider adding documentation and extracting these values as named constants.

+    /** Base multiplier for minimum attack damage */
+    private static final float BASE_DAMAGE_MULTIPLIER = 0.2F;
+    /** Scaling multiplier for attack strength */
+    private static final float STRENGTH_SCALING_MULTIPLIER = 0.8F;
+
+    /**
+     * Calculates the player's attack damage based on their base attribute and attack strength.
+     * The final damage is scaled using the formula: damage = damage * (BASE + strength² * SCALING)
+     *
+     * @param player The player entity
+     * @return The calculated attack damage
+     */
     public static float getAttackDamage(Player player) {
         float f = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE);
         float h = player.getAttackStrengthScale(0.5F);
-        f *= 0.2F + h * h * 0.8F;
+        f *= BASE_DAMAGE_MULTIPLIER + h * h * STRENGTH_SCALING_MULTIPLIER;
         return f;
     }

44-49: Add error handling for sound and animation effects.

Consider adding error handling to gracefully handle cases where sound or animation effects fail.

     public static void triggerCriticalAttackEffect(Entity target, Entity attacker) {
+        if (target == null || attacker == null) return;
+        
+        try {
             target.level().playSound(null, target.getX(), target.getY(), target.getZ(),
                     SoundEvents.PLAYER_ATTACK_CRIT, attacker.getSoundSource(), 1.0F, 1.0F);
             if (target.level() instanceof ServerLevel level)
                 level.getChunkSource().broadcastAndSend(target, new ClientboundAnimatePacket(target, 4));
+        } catch (Exception e) {
+            LOGGER.error("Failed to trigger critical attack effect", e);
+        }
     }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d910e45 and aa7649f.

📒 Files selected for processing (14)
  • attribute-common/build.gradle (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/c2s/RequestGlideStartPacket.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java (1 hunks)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java (1 hunks)
  • attribute-common/src/main/resources/assets/manascore/lang/en_us.json (1 hunks)
  • attribute-common/src/main/resources/manascore_attribute.mixins.json (1 hunks)
  • attribute-fabric/build.gradle (1 hunks)
  • attribute-neoforge/build.gradle (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java
🔇 Additional comments (22)
attribute-fabric/build.gradle (2)

6-8: LGTM! Well-structured Loom configuration.

The Loom configuration correctly shares the access widener path from the common module, which is a good practice for maintaining consistency across subprojects.


10-12: LGTM! Proper dependency configuration.

The dependency configuration correctly references the network-common module using the transformProductionFabric configuration, which is appropriate for cross-platform mod development.

attribute-common/build.gradle (2)

6-8: Verify the existence of the access widener file.

The Loom configuration references an access widener file. Let's ensure this file exists and contains the necessary configurations.

✅ Verification successful

Access widener file exists and is properly configured

The file contains valid access widener configuration to expose the instances field from Minecraft's AttributeSupplier class.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the access widener file exists
if [ -f "attribute-common/src/main/resources/manascore_attribute.accesswidener" ]; then
    echo "Access widener file exists"
    cat "attribute-common/src/main/resources/manascore_attribute.accesswidener"
else
    echo "Access widener file is missing"
fi

Length of output: 346


10-12: Verify the network-common project dependency.

The dependency configuration looks good. The use of namedElements configuration and disabled transitive dependencies is a good practice for maintaining clean dependency management.

✅ Verification successful

Project dependency verified successfully

The network-common project exists and is properly configured as a Gradle project. The dependency declaration is valid and follows best practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the network-common project exists
if [ -d "network-common" ]; then
    echo "network-common project exists"
    if [ -f "network-common/build.gradle" ]; then
        echo "build.gradle found in network-common"
    else
        echo "WARNING: build.gradle missing in network-common"
    fi
else
    echo "WARNING: network-common project directory not found"
fi

Length of output: 220

attribute-neoforge/build.gradle (3)

10-12: Verify the network-common project configuration.

The dependency references a custom configuration 'transformProductionNeoForge'. Let's verify this setup.

✅ Verification successful

Dependency configuration is correct and follows project standards

The transformProductionNeoForge configuration is a standard part of the build setup for NeoForge modules in this Architectury-based project, and the dependency declaration in attribute-neoforge's build.gradle follows the established pattern.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if network-common project exists and defines the custom configuration
fd -t f "build.gradle" -x grep -l "transformProductionNeoForge"

Length of output: 233


Script:

#!/bin/bash
# Check if network-common project exists
fd -t d "network-common$"

# Look for network-common build file
fd -t f "build.gradle" -p "network-common"

# Check root build.gradle for configuration definition
cat build.gradle

Length of output: 10154


6-8: Verify the attribute-common project configuration.

The loom configuration references the accessWidenerPath from the attribute-common project. Let's verify this dependency.

✅ Verification successful

Access widener configuration is correct

The configuration correctly references the access widener path from attribute-common, which points to an existing file at src/main/resources/manascore_attribute.accesswidener.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if attribute-common project exists and has the access widener configuration
fd -t f "build.gradle" -x grep -l "accessWidenerPath"

Length of output: 751


Script:

#!/bin/bash
# Check the loom configuration in attribute-common/build.gradle
cat attribute-common/build.gradle

Length of output: 326


Script:

#!/bin/bash
# Verify the access widener file exists
ls -l attribute-common/src/main/resources/manascore_attribute.accesswidener

Length of output: 195


14-16: Verify the mod_id property and access widener file.

The remapJar configuration uses project.mod_id to reference the access widener file. Let's verify these components.

✅ Verification successful

✓ Configuration verified: mod_id and accesswidener are properly set up

The mod_id property is correctly defined in the root build.gradle and the referenced accesswidener file exists at attribute-common/src/main/resources/manascore_attribute.accesswidener. The configuration is valid and follows the project's conventions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if mod_id is defined in gradle.properties or settings
fd -t f "gradle.properties" -x grep -l "mod_id"

# Check if the access widener file exists
fd -e accesswidener

Length of output: 657


Script:

#!/bin/bash
# Check build.gradle files for mod_id
fd build.gradle -x grep -l "mod_id"

# Check content of attribute-neoforge/build.gradle
cat attribute-neoforge/build.gradle

Length of output: 659


Script:

#!/bin/bash
# Check content of root build.gradle
cat build.gradle

Length of output: 10041

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (2)

1-4: Verify the copyright year.

The copyright year is set to 2025. Please verify if this is intentional or if it should be set to the project's inception year or a year range (e.g., "2023-2025").


74-76: LGTM!

The getResourceKey method is well-implemented and follows Minecraft conventions.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java (5)

1-5: Header Check – License and Copyright

Everything appears in order with the license header. No issues detected here.


20-24: Mixin Class Definition – Clarity and Consistency

Declaring this class as abstract is consistent with the standard Mixin pattern. No concerns regarding class-level design.


26-30: Conditional Logic in updateFallFlying

The method overrides the boolean flag controlling Elytra flight with a custom attribute check from ManasCoreAttributeUtils. The approach is clear and appropriate.


41-48: Potential Negative Value Edge Case

While you check for <= 0 to handle invalid or zero multipliers, you might also want to log or handle the case if the attribute is unexpectedly negative, as it could indicate a data error or malicious manipulation.


50-57: Maintain Consistent Behavior Across Movement Methods

The approach for modifying swim speed is consistent with lava speed logic. Ensure that any future attribute-based movement modifications follow the same pattern to maintain predictability.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java (1)

1-5: License Declaration

The license header is present and consistent.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/c2s/RequestGlideStartPacket.java (3)

1-5: License Header

Correct license header noted.


19-22: Packet Type Definition

Defining both TYPE and STREAM_CODEC in a straightforward manner is good practice for maintainability.


41-43: Method Return Annotations

Using @NotNull for method return is helpful in clarifying the contract for consumers of the type() method.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java (1)

33-37: LGTM! Clean implementation of elytra activation control.

The method effectively prevents default elytra activation when custom glide conditions are met, falling back to original behavior otherwise.

attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java (2)

31-42: Add null checks for weapon item and improve error handling.

The same issues identified in the past review are still present in this implementation.


51-54: LGTM! Comprehensive glide condition checking.

The method effectively combines all necessary conditions for elytra gliding, including ground state, passenger state, levitation effect, and the custom glide speed multiplier attribute.

attribute-common/src/main/resources/manascore_attribute.mixins.json (1)

1-15: Verify Java 21 compatibility with target platforms.

The mixin configuration specifies Java 21 compatibility. Please ensure this aligns with the minimum Java version requirements of all target platforms and dependencies.

✅ Verification successful

Java 21 compatibility is correctly configured

The Java 21 requirement in the mixin configuration aligns perfectly with:

  • Minecraft 1.21.1 and NeoForge 21.1.28 platform requirements
  • Project's build configuration
  • All mod metadata specifications
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check build configuration and dependency requirements
echo "Checking build configuration..."
fd -e gradle -e properties | xargs cat | grep -i "java\|jdk\|sourceCompatibility"

echo "Checking dependency requirements..."
if [ -f "gradle/libs.versions.toml" ]; then
    cat gradle/libs.versions.toml
fi

Length of output: 679


Script:

#!/bin/bash
# Check Minecraft and Forge versions
echo "Checking Minecraft and Forge configurations..."
rg -i "minecraft|forge" build.gradle
rg -i "minecraft|forge" gradle.properties

echo "Checking mod metadata..."
fd -e toml -e json | grep -i "mods\|fabric\|forge" | xargs cat 2>/dev/null | grep -i "minecraft\|java\|forge"

Length of output: 3702

Comment on lines +1 to +5
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update the copyright year to the current year.

The copyright notice shows 2025, which is a future date. Please update it to the current year (2024).

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
📝 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
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/
/*
* Copyright (c) 2024. ManasMods
* GNU General Public License 3
*/

Comment on lines +46 to +56
public static final Holder<Attribute> LAVA_SPEED_MULTIPLIER = registerGenericAttribute(ModuleConstants.MOD_ID,
"lava_speed_multiplier", "manascore.attribute.lava_speed_multiplier",
1, 0, 1024, true, Attribute.Sentiment.POSITIVE);

/**
* Determine how fast the player can swim in Water.
* Similar to NeoForge/Forge's Swim speed instead of Vanilla's Water Movement Efficiency.
*/
public static final Holder<Attribute> SWIM_SPEED_MULTIPLIER = registerGenericAttribute(ModuleConstants.MOD_ID,
"swim_speed_multiplier", "manascore.attribute.swim_speed_multiplier",
1, 0, 1024, true, Attribute.Sentiment.POSITIVE);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Review speed multiplier ranges and performance impact.

Both LAVA_SPEED_MULTIPLIER and SWIM_SPEED_MULTIPLIER:

  1. Have an extremely wide range (0-1024x)
  2. Could potentially cause performance issues at high values due to increased chunk loading
  3. Might need server-side validation to prevent exploitation

Consider:

  • Reducing the maximum range to a reasonable value (e.g., 5.0)
  • Adding server-side speed caps for performance

Comment on lines +30 to +39
public void handle(NetworkManager.PacketContext context) {
if (context.getEnvironment() != Env.SERVER) return;
context.queue(() -> {
Player player = context.getPlayer();
if (player == null) return;
player.stopFallFlying();
if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid()))
player.startFallFlying();
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Server-Side Execution and Edge Cases

While the code properly checks for server environment and a non-null player, consider:

  • Logging attempts to initiate glide for debugging.
  • Checking additional states (e.g., ensuring the player is not in creative mode, specifying conditions on dimension).

Adding such validations can prevent unexpected or exploitable behaviors.

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

♻️ Duplicate comments (11)
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1)

27-27: 🛠️ Refactor suggestion

Use '==' operator for enum comparison instead of 'Objects.equals'

When comparing enum constants in Java, it's recommended to use the == operator rather than Objects.equals, as enums are singletons and == provides a more efficient and type-safe comparison.

Apply this diff to correct the comparison:

-        if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get();
+        if (resultChangeable.get() != EntityEvents.ProjectileHitResult.DEFAULT) return deflectionChangeable.get();
attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (1)

40-57: 🛠️ Refactor suggestion

Add validation for damage multiplier calculations.

The validation concerns from the previous review still apply. Additionally:

  1. Add documentation explaining the different critical hit scenarios
  2. Consider simplifying the branching logic
+    /**
+     * Handles critical hits for players, with special handling for vanilla critical hits.
+     * @param e The critical hit event
+     */
     @SubscribeEvent(priority = EventPriority.HIGH)
     public static void onCriticalHit(final CriticalHitEvent e) {
+        // Get and validate the critical damage multiplier
+        float critMultiplier = (float) Math.max(1.0, Math.min(
+            e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER),
+            10.0
+        ));
+
         if (e.isVanillaCritical()) {
-            float multiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
-            e.setDamageMultiplier(e.getDamageMultiplier() / e.getVanillaMultiplier() * multiplier);
+            e.setDamageMultiplier(e.getDamageMultiplier() / e.getVanillaMultiplier() * critMultiplier);
             return;
         }
 
-        float critMultiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER);
         Changeable<Float> multiplier = Changeable.of(e.getDamageMultiplier() * critMultiplier);
         Changeable<Double> chance = Changeable.of(e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1)

41-43: ⚠️ Potential issue

Add safety checks for explosion ability.

The explosion ability lacks critical safety measures that could prevent griefing or unintended damage.

The previous review's concerns about adding safety checks remain valid:

  • Add client-side check
  • Implement cooldown mechanism
  • Add safe zone restrictions
  • Make explosion power configurable
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1)

15-18: 🛠️ Refactor suggestion

Fix inconsistent event naming.

The event names SKILL_PRE_TICK and SKILL_POST_TICK use "SKILL" prefix while being in a race-related class.

-    Event<RaceTickEvent> SKILL_PRE_TICK = EventFactory.createEventResult();
-    Event<RacePostTickEvent> SKILL_POST_TICK = EventFactory.createLoop();
+    Event<RaceTickEvent> RACE_PRE_TICK = EventFactory.createEventResult();
+    Event<RacePostTickEvent> RACE_POST_TICK = EventFactory.createLoop();
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (2)

43-49: 🛠️ Refactor suggestion

Consider reducing the armor attribute modifier value.

The armor modifier value of 10 is quite high for testing purposes and could mask potential issues during testing.

Apply this diff to use a more reasonable value:

         this.addAttributeModifier(Attributes.STEP_HEIGHT, ResourceLocation.withDefaultNamespace("race.step"),
                 1, AttributeModifier.Operation.ADD_VALUE);
         this.addAttributeModifier(Attributes.ARMOR, ResourceLocation.withDefaultNamespace("race.armor"),
-                10, AttributeModifier.Operation.ADD_VALUE);
+                2, AttributeModifier.Operation.ADD_VALUE);

69-79: ⚠️ Potential issue

Fix effect handling inconsistencies.

The log message mentions "Poison" but the code handles confusion effect.

Apply this diff to fix the inconsistency:

     public boolean onEffectAdded(ManasRaceInstance instance, LivingEntity entity, @Nullable Entity source, Changeable<MobEffectInstance> effect) {
         MobEffectInstance effectInstance = effect.get();
         if (effectInstance == null) return false;
-        if (effectInstance.getEffect().equals(MobEffects.WEAKNESS)) return false;
+        MobEffect mobEffect = effectInstance.getEffect();
+        if (mobEffect == null || mobEffect.equals(MobEffects.WEAKNESS)) return false;
 
-        if (effectInstance.getEffect().equals(MobEffects.CONFUSION)) {
-            ManasCoreTesting.LOG.info("Poison is bad!");
+        if (mobEffect.equals(MobEffects.CONFUSION)) {
+            ManasCoreTesting.LOG.info("Confusion converted to damage boost!");
             effect.set(new MobEffectInstance(MobEffects.DAMAGE_BOOST, effectInstance.getDuration(), effectInstance.getAmplifier()));
         }
         return true;
     }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1)

142-150: ⚠️ Potential issue

Prevent potential overflow from excessive damage multiplier.

The 100x damage multiplier could cause integer overflow with high base damage values.

Apply this diff to use a more reasonable multiplier:

     public boolean onTouchEntity(ManasSkillInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
         if (owner.isShiftKeyDown() && target instanceof Villager) {
             instance.setMastery(instance.getMastery() + 1);
             ManasCoreTesting.LOG.info("My mastery is {}", instance.getMastery());
-            amount.set(amount.get() * 100F);
+            float newAmount = Math.min(amount.get() * 2.5F, 50F); // 2.5x damage with 50 damage cap
+            amount.set(newAmount);
         } else if (target instanceof Pig) amount.set(0F);
         ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get());
         return true;
     }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (4)

67-71: 🛠️ Refactor suggestion

Adjust attribute modifier values for testing.

The current critical hit modifiers are extremely high:

  • 10x critical damage multiplier
  • 50% critical chance

Apply this diff to use more reasonable values:

                             .attributes(ItemAttributeModifiers.builder()
-                                    .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 10,
-                                            AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND)
-                                    .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 50,
-                                            AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND).build())));
+                                    .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 1.5,
+                                            AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND)
+                                    .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 0.15,
+                                            AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND).build())));

131-137: ⚠️ Potential issue

Improve error handling in skill management.

The skill learning logic lacks error handling:

  1. No null checks for storage or testSkill
  2. No feedback when skill learning fails

Apply this diff to add proper error handling:

             if (entity.getItem().is(Items.DIAMOND)) {
                 Skills storage = SkillAPI.getSkillsFrom(player);
+                if (storage == null) {
+                    ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+                    return EventResult.pass();
+                }
                 Registrar<ManasSkill> skills = SkillAPI.getSkillRegistry();
                 RegistrySupplier<TestSkill> testSkill = TEST_SKILL;
+                if (testSkill == null) {
+                    ManasCoreTesting.LOG.error("Test skill is not registered");
+                    return EventResult.pass();
+                }
                 if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
                     ManasCoreTesting.LOG.info("Added Tested Skill!");
+                } else {
+                    ManasCoreTesting.LOG.warn("Failed to learn test skill");
                 }

142-146: ⚠️ Potential issue

Add error handling for race management.

The race setting logic lacks error handling and race ID validation.

Apply this diff to add proper error handling:

             else if (entity.getItem().is(Items.GOLD_INGOT)) {
                 Races storage = RaceAPI.getRaceFrom(player);
+                if (storage == null) {
+                    ManasCoreTesting.LOG.error("Failed to get race storage for player");
+                    return EventResult.pass();
+                }
+                if (!RaceAPI.getRaceRegistry().containsKey(RegistryTest.TEST_RACE.getId())) {
+                    ManasCoreTesting.LOG.error("Test race is not registered");
+                    return EventResult.pass();
+                }
                 storage.setRace(RegistryTest.TEST_RACE.getId(), true);
                 ManasCoreTesting.LOG.info("Set to Test Race!");
+                player.displayClientMessage(Component.literal("Race set to Test Race"), true);
             }

174-189: ⚠️ Potential issue

Add safety limits to death explosion mechanics.

The current implementation uses an uncapped random explosion radius and could create unexpectedly large explosions.

Apply this diff to add safety limits:

-                    float g = 10.0F + entity.getRandom().nextFloat() * 2.0F;
+                    float baseRadius = 3.0F;
+                    float randomAddition = entity.getRandom().nextFloat() * 1.0F;
+                    float g = Math.min(baseRadius + randomAddition, 4.0F);
🧹 Nitpick comments (39)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (5)

44-52: Ensure safe fallback for unexpected dimension inputs.
While this method correctly distinguishes between ServerPlayer and other entities, consider adding logs in case the dimension is not valid or available, to help with debugging.


54-60: Optimize repeated server-level retrievals.
This overload correctly fetches the dimension and delegates, but you might explore caching or centralizing dimension lookups if you're performing them frequently in other areas of the codebase.


65-70: Add fallback behavior or logging on null spawn.
When spawn == null, the method silently returns. Consider adding a debug log or user-friendly message so admins can diagnose teleport issues.


81-111: Consider a custom spawn for non-Overworld & non-End scenarios.
Currently, if the dimension is neither End nor Overworld, you default to the Overworld’s shared spawn. Depending on your design, you may want to define an explicit fallback or unique spawn logic for other dimensions.


114-179: Remove duplicated comment lines and clarify spiral search logic.
Lines 119 and 120 repeat the same explanation. Additionally, consider adding more context about how and why the spiral approach is used, to improve readability and maintenance.

-        // (di, dj) is a vector - direction in which we move right now
-        // (di, dj) is a vector - direction in which we move right now
+        // (dx, dz) represent the direction in which we move currently
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (9)

60-65: Leverage composition or interfaces for advanced race features.
The current design leads you to extend ManasRace for new races. If advanced race logic expands, consider using an interface-based approach or composition to avoid a large inheritance chain.


72-74: Use a specialized factory for instance creation.
If you expect more specialized ManasRaceInstance classes, consider introducing a factory or builder pattern for clarity and extensibility.


139-141: Allow optional ticking for advanced race logic.
Currently returning false might skip race logic entirely. Consider making it configurable or dynamic so un-ticked races can still run partial logic if needed.


148-150: Centralize attribute modifiers for maintainability.
These methods effectively add/remove attribute modifiers. If you foresee more attribute logic or conditions, consider grouping them in a dedicated “AttributeManager” to keep ManasRace uncluttered.

Also applies to: 158-167, 174-180


218-221: Consider structured event-based design.
The override methods (onEffectAdded, onBeingTargeted, onAttackEntity, onHurt, onDeath, onRespawn) could be handled with a more unified or event-driven approach, especially if your codebase has a consistent event bus or observer pattern.

Also applies to: 228-231, 238-241, 248-251, 258-261, 268-270


278-280: Parameterize the default dimension and platform logic.
getRespawnDimension always returns Overworld and air blocks. You might enable race-specific or config-driven dimension overrides that are more specialized.


300-305: Log skill acquisitions for better debugging.
When the entity learns intrinsic skills, consider adding logs or debug statements to help modpack developers track skill assignments.


356-378: Use dynamic IDs for attribute templates.
Relying on a static ResourceLocation for each logic might become cumbersome if your mod or others add new attributes. Loading the ID from config or generating it at runtime could be beneficial.


381-392: Use built-in color constants instead of AWT.
You rely on Color.ORANGE.getRGB() for the INTERMEDIATE difficulty. Minecraft’s ChatFormatting or built-in color codes might offer more consistent cross-platform compatibility.

-INTERMEDIATE(Component.translatable("manascore.race.difficulty.intermediate").withStyle(style -> style.withColor(Color.ORANGE.getRGB()))),
+INTERMEDIATE(Component.translatable("manascore.race.difficulty.intermediate").withStyle(ChatFormatting.GOLD)),
attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (1)

1-4: Update the copyright year.

The copyright year is set to 2025, but it should reflect when the code was actually written (2024 or earlier) since we're only in January 2025.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (3)

1-4: Update the copyright year.

The copyright year is set to 2025, which is the current year. Consider using a range (e.g., "2024-2025") to indicate when the file was first created.

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024-2025. ManasMods

35-40: Consider using more Lombok annotations to reduce boilerplate code.

The class could benefit from additional Lombok annotations to reduce boilerplate code and improve maintainability:

  • @ToString for better debugging
  • @EqualsAndHashCode(of = "raceRegistryObject") to replace manual implementations

331-333: Consider caching intrinsic skills for performance.

The getIntrinsicSkills and learnIntrinsicSkills methods might be called frequently. Consider caching the results to improve performance, especially if the race's intrinsic skills don't change during runtime.

+    private Map<LivingEntity, List<ManasSkill>> cachedIntrinsicSkills = new WeakHashMap<>();
+
     public List<ManasSkill> getIntrinsicSkills(LivingEntity entity) {
-        return this.getRace().getIntrinsicSkills(this, entity);
+        return cachedIntrinsicSkills.computeIfAbsent(entity,
+            e -> Collections.unmodifiableList(this.getRace().getIntrinsicSkills(this, e)));
     }

Also applies to: 344-346

testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (4)

45-48: Improve sound configuration and feedback.

The sound effect parameters should be configurable, and the message should be localized.

 public void onRaceSet(ManasRaceInstance instance, LivingEntity living) {
-    ManasCoreTesting.LOG.info("You are a Test Race Evolved!");
-    living.playSound(SoundEvents.ENCHANTMENT_TABLE_USE, 1, 1);
+    ManasCoreTesting.LOG.info(Component.translatable("race.evolved.set").getString());
+    living.playSound(SoundEvents.ENCHANTMENT_TABLE_USE, 
+        instance.getSoundVolume(), 
+        instance.getSoundPitch());
 }

50-53: Improve target nullification feedback.

Silently nullifying the target might confuse players. Consider providing feedback or particle effects.

 public boolean onBeingTargeted(ManasRaceInstance instance, Changeable<LivingEntity> target, LivingEntity mob) {
-    if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) target.set(null);
+    if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
+        target.set(null);
+        // Add visual feedback
+        mob.level().addParticle(ParticleTypes.SMOKE, 
+            mob.getX(), mob.getY() + 0.5, mob.getZ(),
+            0.0D, 0.0D, 0.0D);
+    }
     return true;
 }

64-71: Improve death and respawn feedback.

The hardcoded messages should be localized and more informative.

 public boolean onDeath(ManasRaceInstance instance, LivingEntity owner, DamageSource source) {
-    ManasCoreTesting.LOG.info("AWWWWW MANNNNN x2");
+    ManasCoreTesting.LOG.info(Component.translatable("race.evolved.death", 
+        owner.getName(), source.getMsgId()).getString());
     return true;
 }

 public void onRespawn(ManasRaceInstance instance, ServerPlayer owner, boolean conqueredEnd) {
-    ManasCoreTesting.LOG.info("CREEPER x2");
+    ManasCoreTesting.LOG.info(Component.translatable("race.evolved.respawn",
+        owner.getName()).getString());
 }

79-81: Make respawn dimension configurable.

The respawn dimension and block state should be configurable rather than hardcoded.

 public Pair<ResourceKey<Level>, BlockState> getRespawnDimension(ManasRaceInstance instance, LivingEntity owner) {
-    return Pair.of(Level.END, Blocks.END_STONE.defaultBlockState());
+    return instance.getConfiguredRespawnPoint().orElse(
+        Pair.of(Level.END, Blocks.END_STONE.defaultBlockState()));
 }
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1)

1-4: Update copyright year to current year.

The copyright year is set to 2025, which is in the future. Please update it to the current year (2024).

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1)

38-42: Use namespaced identifier for attribute modifier.

The attribute modifier's identifier "test_speed" should be properly namespaced to avoid potential conflicts.

Apply this diff to add proper namespacing:

         super();
         ManasCoreTesting.LOG.info("Created skill!");
-        this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, "test_speed", 1, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL);
+        this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, ResourceLocation.withDefaultNamespace("test_speed"), 1, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL);
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (3)

33-33: Consider making this interval configurable instead of hardcoding 20.
Using a hardcoded tick interval of 20 might not be suitable for all situations, especially if the user wants to adjust balance in different modpacks or servers.

- public static final int INSTANCE_UPDATE = 20;
+ public static int INSTANCE_UPDATE = 20; // or make it configurable via a config file

42-46: Avoid unnecessary storage flagging to improve performance.
storage.markDirty() is called unconditionally after handleSkillTick() execution. Consider only marking it dirty when a change actually occurs, to reduce overhead in large-scale servers.

- storage.markDirty();
+ if (someDataChanged) {
+     storage.markDirty();
+ }

71-77: Review partial checks for ability activation and ticking.
Currently, the code stops further logic if canActivateAbility or canTick is false. If you intend partial logic or fallback for either condition, consider splitting the checks or documenting why the entire logic is skipped.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)

203-209: Clarify usage of ResourceLocation vs. String for attribute modifiers.

You have two overloads for addHeldAttributeModifier(Holder<Attribute>..., ResourceLocation/String, ...). Consider unifying them or clarifying their intended usage to avoid confusion when developers add new modifiers.

network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)

8-9: Consider potential overhead of @synchronized.

@Synchronized locks on the instance, which may be unnecessary if this class is only used on the main server/client thread. Consider removing or replacing with narrower synchronization if concurrency is truly required.

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java (1)

22-30: Improve readability of the onHit method.

The method logic is correct, but the code structure could be more readable.

Consider this refactoring for better readability:

     @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ThrowableProjectile;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;"))
     ProjectileDeflection onHit(ThrowableProjectile instance, HitResult result, Operation<ProjectileDeflection> original) {
         Changeable<EntityEvents.ProjectileHitResult> resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT);
         Changeable<ProjectileDeflection> deflectionChangeable = Changeable.of(ProjectileDeflection.NONE);
         EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable);
-        if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get();
-        original.call(instance, result);
-        return deflectionChangeable.get();
+        if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) {
+            return deflectionChangeable.get();
+        }
+        original.call(instance, result);
+        return deflectionChangeable.get();
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (2)

20-28: Consider documenting the event creation pattern.

The events are created using different patterns (createLoop() vs createEventResult()). This choice affects how the events behave and should be documented.

Add class-level documentation explaining:

  • When to use createLoop() vs createEventResult()
  • The implications of each event type
  • The execution order of event handlers

31-58: Add Javadoc to public API methods.

The functional interfaces define the public API and would benefit from proper documentation explaining:

  • Method parameters
  • Expected behavior
  • Return value implications
  • Usage examples

Example for LivingTickEvent:

    /**
     * Event fired when a living entity ticks.
     */
    @FunctionalInterface
    public interface LivingTickEvent {
        /**
         * Called when a living entity ticks.
         *
         * @param entity The entity that is ticking
         */
        void tick(LivingEntity entity);
    }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (2)

26-28: Document the damage calculation event chain.

The presence of pre/post/main calculation events suggests a specific execution order that should be documented.

Add documentation explaining:

  • The execution order of damage calculation events
  • The purpose of each phase
  • How modifications in each phase affect the final damage

32-79: Standardize interface visibility and document parameters.

The functional interfaces have inconsistent visibility modifiers and lack parameter documentation.

  1. Add public modifier to all interfaces
  2. Document complex parameters, especially:
    • mode in SkillUpdateCooldownEvent
    • keyNumber in SkillActivationEvent
    • delta in SkillScrollEvent

Example:

    /**
     * Event fired when updating skill cooldown.
     */
    @FunctionalInterface
    public interface SkillUpdateCooldownEvent {
        /**
         * Called when a skill's cooldown is being updated.
         *
         * @param skillInstance The skill instance being updated
         * @param owner The entity that owns the skill
         * @param currentCooldown The current cooldown value
         * @param mode The update mode (0: set, 1: add, 2: subtract)
         * @return EventResult indicating if the cooldown update should proceed
         */
        EventResult cooldown(ManasSkillInstance skillInstance, LivingEntity owner, int currentCooldown, int mode);
    }
skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java (1)

20-25: Improve readability and add null check.

The target setting logic could be more readable and should handle null targets.

Consider this refactoring:

     @WrapOperation(method = "setTarget", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Mob;target:Lnet/minecraft/world/entity/LivingEntity;"))
     private void onSetTarget(Mob instance, LivingEntity value, Operation<LivingEntity> original) {
+        // Allow clearing target
+        if (value == null) {
+            original.call(instance, null);
+            return;
+        }
         Changeable<LivingEntity> target = Changeable.of(value);
-        if (EntityEvents.LIVING_CHANGE_TARGET.invoker().changeTarget(instance, target).isFalse()) {
-            original.call(instance, value);
-        } else original.call(instance, target.get());
+        boolean shouldChangeTarget = !EntityEvents.LIVING_CHANGE_TARGET.invoker()
+            .changeTarget(instance, target)
+            .isFalse();
+        original.call(instance, shouldChangeTarget ? target.get() : value);
     }
race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (2)

2-2: Update copyright year to current year.

The copyright year is set to 2025, which is in the future. Update it to the current year (2024).

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

39-39: Add documentation for markDirty method.

Consider adding Javadoc to explain when this method should be called and its implications for the race system.

+ /**
+  * Marks the race state as dirty, indicating that changes have occurred.
+  * This method should be called when the race instance is modified and needs to be synchronized.
+  */
 void markDirty();
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)

33-118: Refactor repeated event handling pattern.

The event handling code follows a similar pattern across all events. Consider extracting the common pattern into a reusable helper method.

+ private static EventResult handleSkillEvent(LivingEntity entity, SkillEventHandler handler) {
+     for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
+         if (!instance.canInteractSkill(entity)) continue;
+         if (!handler.handle(instance)) return EventResult.interruptFalse();
+     }
+     return EventResult.pass();
+ }
+
+ @FunctionalInterface
+ private interface SkillEventHandler {
+     boolean handle(ManasSkillInstance instance);
+ }

This would simplify the event registrations, for example:

- EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> {
-     for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
-         if (!instance.canInteractSkill(entity)) continue;
-         if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
-     }
-     return EventResult.pass();
- });
+ EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) ->
+     handleSkillEvent(entity, instance -> instance.onEffectAdded(entity, source, changeableTarget))
+ );

121-122: Add class documentation.

Add Javadoc to explain that this is a utility class and why instantiation is prevented.

+ /**
+  * Utility class for managing skill registrations and event handlers.
+  * This class should not be instantiated as it only contains static members.
+  */
 public class SkillRegistry {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between aa7649f and 63ea442.

📒 Files selected for processing (36)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java (1 hunks)
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java (1 hunks)
  • attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (1 hunks)
  • network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java (1 hunks)
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java (1 hunks)
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java
  • skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java
  • attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java
  • skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java
🔇 Additional comments (23)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (2)

72-79: Confirm race dimension logic for re-teleportation.
This logic prevents re-teleporting if the player is already in the correct dimension. Ensure that the dimension check aligns with race evolution or dimension overrides for special scenarios.


181-196: Evaluate block replacement side effects.
Dropping blocks (line 191) when replacing them might lead to item exploitation if triggered repeatedly. If this is intentional, consider clarifying this behavior in the documentation. Otherwise, remove or revise to avoid duping.

race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (1)

114-120: Prevent collisions between race IDs.
Ensuring unique race registry names is crucial. If collisions happen, equals/hashCode might produce unexpected issues. Verify that your race registry enforces uniqueness.

attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java (2)

20-24: LGTM! Well-structured utility class.

The class follows best practices with a private constructor and proper event bus subscription.


25-38: 🛠️ Refactor suggestion

Add safety checks and documentation for critical hit mechanics.

Several improvements needed for robustness:

  1. Add validation for damage multiplier values
  2. Add null checks for attacker attributes
  3. Add documentation explaining the critical hit mechanics
+    /**
+     * Applies critical hit mechanics for non-player entities.
+     * @param e The incoming damage event
+     */
     @SubscribeEvent(priority = EventPriority.HIGH)
     public static void applyEntityCrit(final LivingIncomingDamageEvent e) {
         if (!(e.getSource().getDirectEntity() instanceof LivingEntity attacker)) return; // Direct attack
         if (attacker instanceof Player) return; // Players have their own Critical Event
         LivingEntity target = e.getEntity();
 
+        // Safely get attributes with fallback values
+        double critDamageMultiplier = attacker.getAttributes().hasAttribute(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)
+            ? attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)
+            : 1.0;
+        double critChance = attacker.getAttributes().hasAttribute(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE)
+            ? attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE)
+            : 0.0;
 
-        Changeable<Float> multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER));
-        Changeable<Double> chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100);
+        // Validate and cap multiplier to prevent excessive damage
+        float safeMultiplier = (float) Math.max(1.0, Math.min(critDamageMultiplier, 10.0));
+        Changeable<Float> multiplier = Changeable.of(safeMultiplier);
+        Changeable<Double> chance = Changeable.of(critChance / 100);

Likely invalid or redundant comment.

race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)

35-395: Overall implementation looks solid!

The ManasRaceInstance class provides a robust and well-documented implementation for managing race instances. The delegation pattern to ManasRace is well-implemented, and the event system is comprehensive. While there are some suggested improvements, the core functionality is sound.

testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1)

73-77: Add null check for race evolution.

The TEST_RACE.get() call could return null if the registry is not properly initialized.

 public List<ManasRace> getPreviousEvolutions(ManasRaceInstance instance, LivingEntity entity) {
     List<ManasRace> list = new ArrayList<>();
-    list.add(RegistryTest.TEST_RACE.get());
+    ManasRace testRace = RegistryTest.TEST_RACE.get();
+    if (testRace != null) {
+        list.add(testRace);
+    }
     return list;
 }

Let's verify the registry initialization:

race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (3)

8-12: LGTM!

The imports are well-organized and all are being used within the interface.


20-38: LGTM! Well-structured event interfaces.

The event interfaces are well-designed with:

  • Proper @FunctionalInterface annotations
  • Clear method signatures
  • Logical parameter naming
  • Appropriate use of EventResult for cancellable events

15-18: Verify event usage across the codebase.

Let's ensure these events are being used consistently across the codebase.

✅ Verification successful

Events are properly used across the codebase

All events are being used consistently with proper result checking for result events and loop handling for post-tick events. The implementation follows a clear pattern that's also reflected in the skill system.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for event usage
echo "Searching for event usage..."
rg -A 2 "SKILL_PRE_TICK|SKILL_POST_TICK|SET_RACE|ACTIVATE_ABILITY" --type java

Length of output: 3980

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (2)

1-4: Check license usage and clarity.
The license notice seems fine at first glance. However, ensure that distributing this file under the GNU GPLv3 is compatible with any third-party libraries or frameworks you rely on.

Please validate that no conflicting licenses are in use by examining dependencies and other modules:

✅ Verification successful

License usage is consistent and appropriate
The codebase consistently uses GNU GPLv3 throughout all source files, with proper headers and complete license text. The only exception is the standard Gradle wrapper scripts which use Apache 2.0, which is compatible with GPLv3.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check dependencies' licenses for potential conflicts with GNU GPLv3.
rg -A 2 "License" .

Length of output: 60832


31-32: Confirm static storage-key assignment safety.
Because init() assigns key at runtime, ensure that init() is only called once. Multiple calls could overwrite the static key and cause undefined behavior if multiple assignments conflict.

To confirm usage, you can search for all calls to RaceStorage.init():

✅ Verification successful

Static storage-key assignment is safe
The implementation is secure as RaceStorage.init() is called only once during mod initialization in ManasCoreRace.java, and the key assignment happens through the event registration system which guarantees single execution.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify only a single call to RaceStorage.init().

rg -A 2 "RaceStorage.init("

Length of output: 114


Script:

#!/bin/bash
# Search for RaceStorage.init calls and implementation
rg "RaceStorage\.init" -A 3
echo "---"
# Find the init method implementation
rg "void init\(" race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java -A 5

Length of output: 945

skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java (2)

22-23: Ensure correctness of the Mixin target.

Please verify that your @WrapOperation target (hitTargetOrDeflectSelf) matches the method signature exactly in the mapped environment. Any mismatch can cause crashes or unexpected behavior if the method signature in the target Minecraft version changes.


24-30: Validate event parameter usage and fallback logic.

The logic in this method defers to original.call(instance, result) if the event’s result remains DEFAULT. This order looks correct, but make sure that event subscribers correctly set both resultChangeable and deflectionChangeable so that skipping the fallback is an intentional override rather than an accidental effect.

skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (3)

87-89: Use the standard ResourceLocation constructor.

ResourceLocation.fromNamespaceAndPath(...) may not exist in all mod loaders or may differ in behavior. Consider using the standard constructor for broader compatibility:

-return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath());
+return new ResourceLocation(id.getNamespace(), "icons/skills/" + id.getPath());

101-107: Also override hashCode() to match the equals() contract.

When you override equals(), overriding hashCode() is important for maintaining consistent behavior in hashed data structures.


434-459: AttributeModifier may require a UUID and a descriptive name rather than a ResourceLocation.

The AttributeModifier constructor typically takes a UUID and a string as the name. Using a ResourceLocation might cause runtime issues on certain platforms.

network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)

44-47: Validate equality checks for null-safety.

Your hasChanged() method handles null by checking original == null. This is correct for most use cases, but ensure any downstream code expecting deeper comparisons is aware that null comparisons skip object fields inside T.

race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (3)

14-15: LGTM! Good use of Optional for null safety.

The getRace method correctly uses Optional to handle the potential absence of a race instance.


21-23: Fix parameter naming and standardize null annotations.

The parameter name skill in setRace and evolveRace methods should be race to match the context.

Also applies to: 31-33


17-37: Verify teleport functionality implementation.

The methods include a teleportToSpawn parameter, but its implementation and testing are not visible in the current context.

Run the following script to check for teleport-related tests and implementation:

✅ Verification successful

Teleport functionality is properly implemented

The teleport functionality is implemented through:

  • SpawnPointHelper.teleportToNewSpawn handles the actual teleportation with cross-dimensional support
  • RaceStorage integrates with the event system allowing customization of teleport behavior
  • Proper null safety and error handling is in place
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for teleport implementation and tests
echo "Searching for teleport implementation..."
rg -l "teleportToSpawn" --type java

echo "\nSearching for related tests..."
ast-grep --pattern 'class $_ extends $_Test {
  $$$
  test$_teleport$_($$$) {
    $$$
  }
  $$$
}'

Length of output: 542


Script:

#!/bin/bash
# Check implementation details
echo "=== RaceStorage.java implementation ==="
rg -A 10 "setRace.*teleportToSpawn" race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java

echo -e "\n=== RaceEvents.java handling ==="
rg -A 10 "teleportToSpawn" race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java

echo -e "\n=== Looking for test files ==="
fd "Test.*\.java" race-common/src/test

Length of output: 1652


Script:

#!/bin/bash
# Search for teleport implementation and event listeners
echo "=== Searching for teleport implementation ==="
rg -A 5 "teleport.*spawn|spawn.*teleport" --type java

echo -e "\n=== Searching for SET_RACE event listeners ==="
rg -A 10 "SET_RACE.*Event" --type java

echo -e "\n=== Checking for spawn-related code ==="
rg -l "getRespawnPosition|getSpawnPoint" --type java

Length of output: 3637

skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)

25-30: Correct Generic Type Parameters in Registrar Builder.

The generic type parameter specification needs correction in the Registrar builder.


110-118: Potential Null Pointer Exception in PROJECTILE_HIT Event.

The event handler needs null checking for entity access.

Comment on lines +28 to +29
original.call(instance, result);
return deflectionChangeable.get();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Store and use the return value from original.call

The return value from original.call is being ignored, which could lead to unexpected behavior. The original method's result should be considered when determining the final deflection.

Apply this diff to handle the return value:

-        original.call(instance, result);
-        return deflectionChangeable.get();
+        ProjectileDeflection originalDeflection = original.call(instance, result);
+        return deflectionChangeable.get() != ProjectileDeflection.NONE ? deflectionChangeable.get() : originalDeflection;
📝 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
original.call(instance, result);
return deflectionChangeable.get();
ProjectileDeflection originalDeflection = original.call(instance, result);
return deflectionChangeable.get() != ProjectileDeflection.NONE ? deflectionChangeable.get() : originalDeflection;

Comment on lines +1 to +4
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update the copyright year to the current year.

The copyright notice shows the year 2025, which is in the future. This should be updated to the current year (2024) to maintain legal accuracy.

Apply this diff to correct the year:

- * Copyright (c) 2025. ManasMods
+ * Copyright (c) 2024. ManasMods

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

Comment on lines +265 to +267
public boolean onEffectAdded(LivingEntity entity, @Nullable Entity source, Changeable<MobEffectInstance> instance) {
return this.getRace().onEffectAdded(this, entity, source, instance);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add parameter validation in critical event handlers.

Critical event handlers like onEffectAdded, onAttackEntity, and onHurt should validate their parameters to prevent null pointer exceptions and ensure data integrity.

     public boolean onEffectAdded(LivingEntity entity, @Nullable Entity source, Changeable<MobEffectInstance> instance) {
+        Objects.requireNonNull(entity, "entity cannot be null");
+        Objects.requireNonNull(instance, "effect instance cannot be null");
         return this.getRace().onEffectAdded(this, entity, source, instance);
     }

     public boolean onAttackEntity(LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
+        Objects.requireNonNull(owner, "owner cannot be null");
+        Objects.requireNonNull(target, "target cannot be null");
+        Objects.requireNonNull(source, "damage source cannot be null");
+        Objects.requireNonNull(amount, "amount cannot be null");
         return this.getRace().onAttackEntity(this, owner, target, source, amount);
     }

     public boolean onHurt(LivingEntity owner, DamageSource source, Changeable<Float> amount) {
+        Objects.requireNonNull(owner, "owner cannot be null");
+        Objects.requireNonNull(source, "damage source cannot be null");
+        Objects.requireNonNull(amount, "amount cannot be null");
         return this.getRace().onHurt(this, owner, source, amount);
     }

Also applies to: 285-287, 296-298

Comment on lines +382 to +384
public float getEvolutionProgress(LivingEntity entity, ManasRace evolution) {
return this.getRace().getEvolutionProgress(this, entity, evolution);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for evolution progress value.

The getEvolutionProgress method documentation states that acceptable values are between 0 and 1.0, but there's no validation to ensure this constraint.

     public float getEvolutionProgress(LivingEntity entity, ManasRace evolution) {
-        return this.getRace().getEvolutionProgress(this, entity, evolution);
+        float progress = this.getRace().getEvolutionProgress(this, entity, evolution);
+        if (progress < 0.0f || progress > 1.0f) {
+            throw new IllegalStateException("Evolution progress must be between 0 and 1.0, got: " + progress);
+        }
+        return progress;
     }
📝 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 float getEvolutionProgress(LivingEntity entity, ManasRace evolution) {
return this.getRace().getEvolutionProgress(this, entity, evolution);
}
public float getEvolutionProgress(LivingEntity entity, ManasRace evolution) {
float progress = this.getRace().getEvolutionProgress(this, entity, evolution);
if (progress < 0.0f || progress > 1.0f) {
throw new IllegalStateException("Evolution progress must be between 0 and 1.0, got: " + progress);
}
return progress;
}

Comment on lines +133 to +144
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManasRaceInstance instance = (ManasRaceInstance) o;
return this.getRaceId().equals(instance.getRaceId()) &&
raceRegistryObject.getRegistryKey().equals(instance.raceRegistryObject.getRegistryKey());
}

@Override
public int hashCode() {
return Objects.hash(raceRegistryObject);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix potential issues in equals and hashCode implementation.

The current implementation:

  1. Might break symmetry with subclasses
  2. Doesn't use all fields that define object identity
  3. Inconsistent use of fields between equals and hashCode
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (this == o) return true;
+        if (!(o instanceof ManasRaceInstance)) return false;
         ManasRaceInstance instance = (ManasRaceInstance) o;
         return this.getRaceId().equals(instance.getRaceId()) &&
                 raceRegistryObject.getRegistryKey().equals(instance.raceRegistryObject.getRegistryKey());
     }

     @Override
     public int hashCode() {
-        return Objects.hash(raceRegistryObject);
+        return Objects.hash(getRaceId(), raceRegistryObject.getRegistryKey());
     }
📝 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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManasRaceInstance instance = (ManasRaceInstance) o;
return this.getRaceId().equals(instance.getRaceId()) &&
raceRegistryObject.getRegistryKey().equals(instance.raceRegistryObject.getRegistryKey());
}
@Override
public int hashCode() {
return Objects.hash(raceRegistryObject);
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ManasRaceInstance)) return false;
ManasRaceInstance instance = (ManasRaceInstance) o;
return this.getRaceId().equals(instance.getRaceId()) &&
raceRegistryObject.getRegistryKey().equals(instance.raceRegistryObject.getRegistryKey());
}
@Override
public int hashCode() {
return Objects.hash(getRaceId(), raceRegistryObject.getRegistryKey());
}

Comment on lines +86 to +93
public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
if (owner.isShiftKeyDown() && target instanceof Pillager) {
BlockPos pos = ServerLevel.END_SPAWN_POINT;
SpawnPointHelper.teleportToAcrossDimensions(target,
this.getRespawnDimension(instance, owner).getFirst(), pos.getX(), pos.getY(), pos.getZ(), 0, 0);
}
return true;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add safety checks for teleportation.

The function uses a hardcoded spawn point without validating if it's safe or accessible. This could lead to entities being teleported into unsafe locations.

Apply this diff to add safety checks:

     public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable<Float> amount) {
         if (owner.isShiftKeyDown() && target instanceof Pillager) {
-            BlockPos pos = ServerLevel.END_SPAWN_POINT;
+            BlockPos pos = findSafeSpawnPoint(target, this.getRespawnDimension(instance, owner).getFirst());
+            if (pos == null) {
+                ManasCoreTesting.LOG.warn("Could not find safe spawn point for teleportation");
+                return true;
+            }
             SpawnPointHelper.teleportToAcrossDimensions(target,
                     this.getRespawnDimension(instance, owner).getFirst(), pos.getX(), pos.getY(), pos.getZ(), 0, 0);
         }
         return true;
     }
+
+    private @Nullable BlockPos findSafeSpawnPoint(LivingEntity entity, ResourceKey<Level> dimension) {
+        if (!(entity.level() instanceof ServerLevel serverLevel)) return null;
+        BlockPos pos = ServerLevel.END_SPAWN_POINT;
+        return SpawnPlacements.getPlacementType(entity.getType()).canSpawnAt(serverLevel, pos, entity.getType()) ? pos : null;
+    }

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

Comment on lines +59 to +66
private static void handleSkillTick(LivingEntity entity, Level level, Races storage) {
MinecraftServer server = level.getServer();
if (server == null) return;
boolean shouldTickRace = server.getTickCount() % INSTANCE_UPDATE == 0;
if (!shouldTickRace) return;
tickRace(entity, storage);
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wrap race ticking in error handling or logging.
If raceInstance.onTick() throws an exception, the server might crash. Consider adding try/catch or logging to avoid halting the entire server loop.

- tickRace(entity, storage);
+ try {
+     tickRace(entity, storage);
+ } catch (Exception e) {
+     log.error("Error ticking race for entity {}: {}", entity, e.getMessage(), e);
+ }
📝 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 static void handleSkillTick(LivingEntity entity, Level level, Races storage) {
MinecraftServer server = level.getServer();
if (server == null) return;
boolean shouldTickRace = server.getTickCount() % INSTANCE_UPDATE == 0;
if (!shouldTickRace) return;
tickRace(entity, storage);
}
private static void handleSkillTick(LivingEntity entity, Level level, Races storage) {
MinecraftServer server = level.getServer();
if (server == null) return;
boolean shouldTickRace = server.getTickCount() % INSTANCE_UPDATE == 0;
if (!shouldTickRace) return;
try {
tickRace(entity, storage);
} catch (Exception e) {
log.error("Error ticking race for entity {}: {}", entity, e.getMessage(), e);
}
}

Comment on lines +89 to +99
public boolean setRace(@NonNull ManasRaceInstance race, boolean evolution, boolean teleportToSpawn) {
ManasRaceInstance instance = this.raceInstance;
Changeable<Boolean> teleport = Changeable.of(teleportToSpawn);
EventResult result = RaceEvents.SET_RACE.invoker().set(instance, getOwner(), race, evolution, teleport);
if (result.isFalse()) return false;

LivingEntity owner = this.getOwner();
if (instance != null) {
instance.removeAttributeModifiers(owner);
if (evolution) instance.onRaceEvolution(owner, race);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Skip re-application of race attributes if the old and new race are the same instance.
Re-removing and re-adding the same race’s attributes can cause flickers or repeated triggers. Add a check to see if race == instance early.

- if (instance != null) {
+ if (instance != null && instance != race) {
     instance.removeAttributeModifiers(owner);
     if (evolution) instance.onRaceEvolution(owner, race);
 }
📝 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 boolean setRace(@NonNull ManasRaceInstance race, boolean evolution, boolean teleportToSpawn) {
ManasRaceInstance instance = this.raceInstance;
Changeable<Boolean> teleport = Changeable.of(teleportToSpawn);
EventResult result = RaceEvents.SET_RACE.invoker().set(instance, getOwner(), race, evolution, teleport);
if (result.isFalse()) return false;
LivingEntity owner = this.getOwner();
if (instance != null) {
instance.removeAttributeModifiers(owner);
if (evolution) instance.onRaceEvolution(owner, race);
}
public boolean setRace(@NonNull ManasRaceInstance race, boolean evolution, boolean teleportToSpawn) {
ManasRaceInstance instance = this.raceInstance;
Changeable<Boolean> teleport = Changeable.of(teleportToSpawn);
EventResult result = RaceEvents.SET_RACE.invoker().set(instance, getOwner(), race, evolution, teleport);
if (result.isFalse()) return false;
LivingEntity owner = this.getOwner();
if (instance != null && instance != race) {
instance.removeAttributeModifiers(owner);
if (evolution) instance.onRaceEvolution(owner, race);
}

Comment on lines +113 to +123
@Override
public void save(CompoundTag data) {
if (this.raceInstance == null) return;
data.put(RACE_KEY, this.raceInstance.toNBT());
}

@Override
public void load(CompoundTag data) {
if (!data.contains(RACE_KEY)) return;
this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

The review comment is correct - attributes need to be reapplied after loading

The code shows that while setRace() properly handles attribute reapplication, the load() method only deserializes and sets the race instance without reapplying attributes. This could lead to stale attributes when loading entities from NBT data.

  • Consider calling this.raceInstance.addAttributeModifiers(entity) after setting the instance in the load() method to ensure attributes are properly reapplied.
🔗 Analysis chain

Reapply race to entity after loading to prevent stale attributes.
When load() sets this.raceInstance, you might need to add back attribute modifiers if the entity is live at that time. Otherwise, on chunk reload or dimension change, the entity could lose race buffs.

Consider verifying whether corresponding events or mechanism reapply the race attributes after load(). If not, reapply them manually here or in an appropriate event callback.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for attribute reapplication logic after load.
rg -A 5 "load("

Length of output: 78


Script:

#!/bin/bash
# Search for attribute-related methods and their usage
ast-grep --pattern 'getAttribute($$$)' 
ast-grep --pattern 'addAttribute($$$)'
ast-grep --pattern 'removeAttribute($$$)'

# Search for event handlers related to entity loading/joining
rg -l "EntityJoin|EntityLoad|PlayerLoad|PlayerJoin"

# Search for race instance attribute application
rg -l "raceInstance.*attribute"

Length of output: 213


Script:

#!/bin/bash
# Search for attribute-related code with broader patterns
rg -i "attribute" -A 5
rg -i "modif.*attribute" -A 5

# Find race instance implementation and usage
fd -e java -e kt -x cat {} | rg -A 10 "class.*Race(Instance|Storage|Handler)"

# Search for common Minecraft mod lifecycle events
rg -i "(onentity|onevent|setup|init|register).*event" -A 5

Length of output: 71844

Comment on lines +33 to +118
EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> {
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
if (!instance.canInteractSkill(entity)) continue;
if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

EntityEvents.LIVING_CHANGE_TARGET.register((entity, changeableTarget) -> {
if (!changeableTarget.isPresent()) return EventResult.pass();

LivingEntity owner = changeableTarget.get();
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(owner).getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onBeingTargeted(changeableTarget, entity)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

EntityEvent.LIVING_HURT.register((entity, source, amount) -> {
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
if (!instance.canInteractSkill(entity)) continue;
if (!instance.onBeingDamaged(entity, source, amount)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();

for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(owner).getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onDamageEntity(owner, target, source, amount)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();

for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(owner).getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onTouchEntity(owner, target, source, amount)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

EntityEvents.LIVING_DAMAGE.register((entity, source, amount) -> {
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
if (!instance.canInteractSkill(entity)) continue;
if (!instance.onTakenDamage(entity, source, amount)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

EntityEvent.LIVING_DEATH.register((entity, source) -> {
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
if (!instance.canInteractSkill(entity)) continue;
if (!instance.onDeath(entity, source)) return EventResult.interruptFalse();
}

return EventResult.pass();
});

PlayerEvent.PLAYER_RESPAWN.register((newPlayer, conqueredEnd, removalReason) -> {
for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(newPlayer).getLearnedSkills()) {
if (!instance.canInteractSkill(newPlayer)) continue;
instance.onRespawn(newPlayer, conqueredEnd);
}
});

EntityEvents.PROJECTILE_HIT.register((result, projectile, deflectionChangeable, hitResultChangeable) -> {
if (!(result instanceof EntityHitResult hitResult)) return;
if (!(hitResult.getEntity() instanceof LivingEntity hitEntity)) return;

for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(hitEntity).getLearnedSkills()) {
if (!instance.canInteractSkill(hitEntity)) continue;
instance.onProjectileHit(hitEntity, hitResult, projectile, deflectionChangeable, hitResultChangeable);
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for skill interactions.

The event handlers should catch and handle potential exceptions from skill interactions to prevent event processing from being interrupted unexpectedly.

 EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> {
     for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(entity).getLearnedSkills()) {
         if (!instance.canInteractSkill(entity)) continue;
-        if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
+        try {
+            if (!instance.onEffectAdded(entity, source, changeableTarget)) return EventResult.interruptFalse();
+        } catch (Exception e) {
+            LOGGER.error("Error processing effect added event for skill: " + instance.getSkill().getId(), e);
+        }
     }
     return EventResult.pass();
 });

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

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.

3 participants