Skip to content

Registry - Keybinding - Skill - Config modules#48

Open
minheragon12345 wants to merge 59 commits into1.21.1/masterfrom
1.21.1/minh
Open

Registry - Keybinding - Skill - Config modules#48
minheragon12345 wants to merge 59 commits into1.21.1/masterfrom
1.21.1/minh

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

  • New Features

    • Full skill system: learnable skills with toggle/hold/scroll controls, client↔server sync and networked activation/release.
    • Race system: set/evolve races with abilities, intrinsic skills, respawn helpers and networked evolution.
    • Attributes & combat: critical-hit mechanics, glide/lava/swim speed modifiers, richer projectile and damage hooks.
    • Configs & sync: TOML-based configs with load/save and automatic server→client sync on join.
    • Commands & keybindings: expanded argument types and client keybinding manager.
    • Testing content: sample skills, races, items and keybinding tests.
  • Chores

    • Build, packaging, localization and version/property updates.

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

coderabbitai bot commented Jan 17, 2025

Walkthrough

Adds multiple new subsystems (config, skills, races, attributes, keybinds), extended command argument API, platform entrypoints (Fabric/NeoForge), network payloads for C2S/S2C sync, many mixins/resources/access-wideners, testing/demo content, and extensive Gradle/build/publishing wiring changes.

Changes

Cohort / File(s) Summary
Build & repo config
settings.gradle, build.gradle, gradle.properties, **/build.gradle, **/gradle.properties
Expanded module list; updated mod/neoforge versions; publishing repo + credentials changed; added loom.accessWidenerPath/remapJar/loom.platform across modules; conditional bundle handling and many header year updates.
Config subsystem
config-common/src/main/java/..., config-common/src/main/resources/..., config-fabric/..., config-neoforge/...
New config API and runtime: ManasConfig, ManasSubConfig, ConfigRegistry, ManasCoreConfig, Comment, SyncToClient; network sync (ManasConfigNetwork, SyncConfigToClientPayload); architectury/mixins/accesswidener and build deps added.
Skill subsystem
skill-common/src/main/java/..., skill-common/src/main/resources/..., skill-fabric/..., skill-neoforge/...
Comprehensive skill framework added: ManasSkill, ManasSkillInstance, SkillAPI, SkillEvents, Skills, registries/storage (SkillRegistry, SkillStorage, TickingSkill), client handlers, C2S packets and network registration, many mixins/resources and Gradle wiring.
Race subsystem
race-common/..., race-fabric/..., race-neoforge/...
Full race framework: ManasRace, ManasRaceInstance, RaceAPI, RaceEvents, registries/storage (RaceRegistry, RaceStorage, TickingRace), spawn helper, C2S packets, tag provider, mixins/accesswidener/resources, and platform Gradle updates.
Attribute subsystem
attribute-common/..., attribute-fabric/..., attribute-neoforge/...
Attribute API and utils (ManasCoreAttributes, ManasCoreAttributeUtils, AttributeEvents), platform registrars/impls, glide packet/handler, critical-hit & movement mixins, localization strings and build changes.
Keybind subsystem
keybind-common/..., keybind-fabric/..., keybind-neoforge/...
Keybinding API/runtime: ManasKeybinding, KeybindingManager, KeybindingCategory, client init classes, platform entrypoints, mixins, access wideners, resources, and build config.
Command system & args
command-common/..., command-fabric/..., command-neoforge/...
Reworked command argument API: many new/renamed *Arg annotations, updated CommandArgumentRegistrationEvent signature (adds dispatcher & build context), updated registrations and tests.
Network & util
network-common/src/main/java/.../Changeable.java, **/network/**
Added Changeable<T> mutable wrapper; many new packet payloads, stream codecs and network registration helpers used by config/skill/race/attribute subsystems.
Testing / Demo
testing-common/..., testing-fabric/..., testing-neoforge/...
Test/demo content and registries (RegistryTest, TestSkill, TestRace, TestRaceEvolved, KeybindingTest), test configs (TestConfig, SkillConfig), client init changes, localization and test dependencies.
Storage / Inventory / misc
storage-common/..., inventory-*/*, assorted
Minor storage cloning change (CombinedStorage rebuilt on player clone), pervasive header/year updates and small refactors across modules.
Mixins / resources / access wideners
many src/main/resources/*/*.mixins.json, architectury.common.json, *.accesswidener, assets/**/lang/en_us.json
Added numerous mixin configs, architectury descriptors, access widener files, and localization strings to support new features and platform builds.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant Internal as InternalSkillPacketActions
    participant Network as NetworkManager
    participant Server
    participant Storage as SkillStorage

    Client->>Internal: user input (key/scroll)
    Internal->>Network: send RequestSkillActivation/Release/Scroll/Toggle
    Network->>Server: deliver packet
    Server->>Storage: lookup player's Skills
    Storage->>Storage: validate via SkillEvents (permission/mode/cooldown)
    Storage->>Storage: update ManasSkillInstance / add TickingSkill
    Storage->>Server: mark dirty / persist / schedule sync
Loading
sequenceDiagram
    autonumber
    participant Server
    participant Net as ManasConfigNetwork
    participant Payload as SyncConfigToClientPayload
    participant Client
    participant Registry as ConfigRegistry

    Server->>Registry: ConfigRegistry.getConfigSyncData()
    Server->>Net: ManasConfigNetwork.syncToClients()
    Net->>Payload: build payload(map)
    Net->>Client: send S2C SyncConfigToClientPayload
    Client->>Payload: receive & decode
    Payload->>Registry: ConfigRegistry.loadConfigSyncData(map)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • 1.21.1/mixin issues #49 — Overlaps on mixin/accessWidener and Gradle/platform wiring; likely related to build and resource configuration changes.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately and concisely summarizes the primary additions: Registry, Keybinding, Skill, and Config modules.
Description check ✅ Passed Description follows the repository template, lists the added modules, and marks type as a new feature, satisfying required sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1.21.1/minh

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

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.

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:

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

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

♻️ Duplicate comments (8)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (1)

32-40: Clamp delta and validate map entries during decode.

Defensive decoding prevents disconnects/crashes from malicious packets and normalizes data early. This also addresses earlier feedback on delta validation.

 public RequestSkillScrollPacket(FriendlyByteBuf buf) {
-    this(buf.readDouble(), validateList(buf.readMap(FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readInt)));
+    this(
+        validateDelta(buf.readDouble()),
+        validateList(buf.readMap(FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readInt))
+    );
 }
 
 private static Map<ResourceLocation, Integer> validateList(Map<ResourceLocation, Integer> map) {
-    int maxSize = 100;
-    if (map.size() > maxSize) throw new IllegalArgumentException("Skill map exceeds maximum size of " + maxSize);
-    return map;
+    if (map.size() > MAX_SKILLS_PER_PACKET) {
+        throw new IllegalArgumentException("Skill map exceeds maximum size of " + MAX_SKILLS_PER_PACKET);
+    }
+    // Drop negative modes defensively
+    map.entrySet().removeIf(e -> e.getValue() == null || e.getValue() < 0);
+    return map;
 }
+
+private static double validateDelta(double d) {
+    if (Double.isNaN(d) || Double.isInfinite(d)) return 0.0;
+    return Math.max(-MAX_ABS_SCROLL_DELTA, Math.min(MAX_ABS_SCROLL_DELTA, d));
+}
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (2)

304-306: Mark dirty after replacing cooldown list (state change).

 public void setCoolDownList(List<Integer> list) {
-    this.cooldownList = list;
+    this.cooldownList = new ArrayList<>(list);
+    markDirty();
 }

115-122: Validate skillLocation before registry lookup.

ResourceLocation.tryParse can return null; current code could NPE or mislead error messages.

 public static ManasSkillInstance fromNBT(CompoundTag tag) throws NullPointerException {
-    ResourceLocation skillLocation = ResourceLocation.tryParse(tag.getString("skill"));
-    ManasSkill skill = SkillAPI.getSkillRegistry().get(skillLocation);
+    ResourceLocation skillLocation = ResourceLocation.tryParse(tag.getString("skill"));
+    if (skillLocation == null) {
+        throw new IllegalArgumentException("Invalid skill ID in NBT data.");
+    }
+    ManasSkill skill = SkillAPI.getSkillRegistry().get(skillLocation);
     if (skill == null) throw new IllegalArgumentException("Skill not found in registry: " + skillLocation);
     ManasSkillInstance instance = skill.createDefaultInstance();
     instance.deserialize(tag);
     return instance;
 }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (2)

37-43: Use a stable UUID for attribute modifier to prevent duplicates.

 public class TestSkill extends ManasSkill {
+    private static final java.util.UUID SPEED_MODIFIER_UUID =
+            java.util.UUID.fromString("60ab112f-9d82-4f45-9fc9-799d7d2658b8");
     public TestSkill() {
         super();
         ManasCoreTesting.LOG.info("Created skill!");
-        this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, "test_speed", -0.95, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL);
+        this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, SPEED_MODIFIER_UUID, -0.95, AttributeModifier.Operation.MULTIPLY_TOTAL);
     }

141-146: Avoid applying extra damage directly; mutate the event amount instead.

-        if (target instanceof Creeper creeper && skillConfig.instaKillCreeper) {
-            creeper.hurt(owner.level().damageSources().dragonBreath(), 100F);
-            ManasCoreTesting.LOG.info("No creeper");
+        if (target instanceof Creeper && skillConfig != null && skillConfig.instaKillCreeper) {
+            amount.set(100F); // lethal once in the normal pipeline
+            ManasCoreTesting.LOG.info("No creeper");
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (3)

104-109: Use the supported ResourceLocation constructor.

fromNamespaceAndPath may not exist in your mappings; prefer the two-arg constructor.

-        return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath());
+        return new ResourceLocation(id.getNamespace(), "icons/skills/" + id.getPath());

485-515: Refactor AttributeTemplate to UUID + name and remove unsupported ResourceLocation ctor usages.

Aligns with AttributeModifier API; also replaces fromNamespaceAndPath with the standard constructor in convenience paths.

-    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 AttributeTemplate(String id, double amount, AttributeModifier.Operation operation) {
-            this(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, id), amount, operation);
-        }
-
-        public AttributeModifier create(double i) {
-            return new AttributeModifier(this.id, this.amount * i, this.operation);
-        }
-
-        public AttributeModifier create(ResourceLocation location, double i) {
-            return new AttributeModifier(location, this.amount * i, this.operation);
-        }
-
-        public ResourceLocation id() {
-            return this.id;
-        }
-
-        public double amount() {
-            return this.amount;
-        }
-
-        public AttributeModifier.Operation operation() {
-            return this.operation;
-        }
-    }
+    public static record AttributeTemplate(java.util.UUID id, String name, double amount, AttributeModifier.Operation operation) {
+        public AttributeTemplate(java.util.UUID id, String name, double amount, AttributeModifier.Operation operation) {
+            this.id = id;
+            this.name = name;
+            this.amount = amount;
+            this.operation = operation;
+        }
+
+        public AttributeTemplate(ResourceLocation key, String name, double amount, AttributeModifier.Operation operation) {
+            this(java.util.UUID.nameUUIDFromBytes(key.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)), name, amount, operation);
+        }
+
+        public AttributeTemplate(String keyPath, String name, double amount, AttributeModifier.Operation operation) {
+            this(new ResourceLocation(ModuleConstants.MOD_ID, keyPath), name, amount, operation);
+        }
+    }

233-239: AttributeModifier id/name types are incorrect; switch to UUID + name (and adjust API).

Minecraft AttributeModifier expects (UUID id, String name, double amount, Operation). Using ResourceLocation will not compile and breaks removeModifier.

-    public void addHeldAttributeModifier(Holder<Attribute> holder, ResourceLocation resourceLocation, double amount, AttributeModifier.Operation operation) {
-        this.attributeModifiers.put(holder, new AttributeTemplate(resourceLocation, amount, operation));
-    }
-
-    public void addHeldAttributeModifier(Holder<Attribute> holder, String id, double amount, AttributeModifier.Operation operation) {
-        this.attributeModifiers.put(holder, new AttributeTemplate(id, amount, operation));
-    }
+    public void addHeldAttributeModifier(Holder<Attribute> holder, java.util.UUID id, String name, double amount, AttributeModifier.Operation operation) {
+        this.attributeModifiers.put(holder, new AttributeTemplate(id, name, amount, operation));
+    }
+
+    // Convenience: derive a stable UUID from a namespaced key
+    public void addHeldAttributeModifier(Holder<Attribute> holder, ResourceLocation key, String name, double amount, AttributeModifier.Operation operation) {
+        java.util.UUID derived = java.util.UUID.nameUUIDFromBytes(key.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
+        this.attributeModifiers.put(holder, new AttributeTemplate(derived, name, amount, operation));
+    }
🧹 Nitpick comments (13)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (3)

25-31: Promote limits to named constants for reuse (client/server consistency).

Define shared MAX constants here so both decode and client code can enforce identical caps.

 public record RequestSkillScrollPacket(
         double delta,
         Map<ResourceLocation, Integer> skillList
 ) implements CustomPacketPayload {
-    public static final CustomPacketPayload.Type<RequestSkillScrollPacket> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_scroll"));
+    // Caps to protect server from large/abusive inputs
+    public static final int MAX_SKILLS_PER_PACKET = 100;
+    public static final double MAX_ABS_SCROLL_DELTA = 20.0;
+    public static final CustomPacketPayload.Type<RequestSkillScrollPacket> TYPE =
+            new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_scroll"));
     public static final StreamCodec<FriendlyByteBuf, RequestSkillScrollPacket> STREAM_CODEC = CustomPacketPayload.codec(RequestSkillScrollPacket::encode, RequestSkillScrollPacket::new);

53-71: Reduce repeated storage.markDirty() calls.

Mark once after processing all entries for minor perf/IO savings.

-            Skills storage = SkillAPI.getSkillsFrom(player);
-            for (Map.Entry<ResourceLocation, Integer> entry : skillList.entrySet()) {
+            Skills storage = SkillAPI.getSkillsFrom(player);
+            boolean changed = false;
+            for (Map.Entry<ResourceLocation, Integer> entry : skillList.entrySet()) {
                 storage.getSkill(entry.getKey()).ifPresent(skillInstance -> {
@@
-                    skill.onScroll(player, deltaChangeable.get(), modeChangeable.get());
-                    storage.markDirty();
+                    skill.onScroll(player, deltaChangeable.get(), modeVal);
+                    changed = true;
                 });
             }
+            if (changed) storage.markDirty();

47-71: Guard processing block with try/catch to avoid crashing the server thread.

Third-party handlers can throw; isolate per-entry to keep the server stable. Add logging as appropriate for your logging facility.

-            for (Map.Entry<ResourceLocation, Integer> entry : skillList.entrySet()) {
-                storage.getSkill(entry.getKey()).ifPresent(skillInstance -> {
+            for (Map.Entry<ResourceLocation, Integer> entry : skillList.entrySet()) {
+                try {
+                    storage.getSkill(entry.getKey()).ifPresent(skillInstance -> {
                         // ...
-                });
+                    });
+                } catch (Exception e) {
+                    // TODO: replace with mod logger
+                    e.printStackTrace();
+                }
             }
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java (1)

36-41: Clamp delta and align with server constants before sending.

Normalize the scroll delta to match server-side validation.

-            if (!packetSkills.isEmpty()) {
-                NetworkManager.sendToServer(new RequestSkillScrollPacket(amountY, packetSkills));
+            if (!packetSkills.isEmpty()) {
+                double clamped = Math.max(-RequestSkillScrollPacket.MAX_ABS_SCROLL_DELTA,
+                        Math.min(RequestSkillScrollPacket.MAX_ABS_SCROLL_DELTA, amountY));
+                NetworkManager.sendToServer(new RequestSkillScrollPacket(clamped, packetSkills));
                 return EventResult.interruptFalse();
             }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (2)

103-108: Normalize cooldown list size on deserialize.

Keep list length consistent with getModes() to avoid index issues elsewhere.

     this.cooldownList = Arrays.stream(tag.getIntArray("CooldownList")).boxed().collect(Collectors.toList());
+    int expected = this.getModes();
+    if (this.cooldownList.size() != expected) {
+        List<Integer> norm = new ArrayList<>(Math.max(expected, 0));
+        for (int i = 0; i < expected; i++) {
+            norm.add(i < this.cooldownList.size() ? this.cooldownList.get(i) : 0);
+        }
+        this.cooldownList = norm;
+    }

375-381: Avoid double markDirty in getOrCreateTag.

setTag already marks dirty; the extra call is redundant.

 public CompoundTag getOrCreateTag() {
     if (this.tag == null) {
         this.setTag(new CompoundTag());
-        this.markDirty();
     }
     return this.tag;
 }
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (4)

100-103: High-frequency logging in onTick can spam logs.

Consider gating behind debug or sampling.

-    if (living.isShiftKeyDown()) ManasCoreTesting.LOG.info("You're sneaky");
+    if (living.isShiftKeyDown()) {
+        if (living.level().getGameTime() % 40 == 0) ManasCoreTesting.LOG.debug("You're sneaky");
+    }

141-153: Defensive null-check for config.

ConfigRegistry.getConfig can return null in tests; avoid NPE.

-        SkillConfig skillConfig = ConfigRegistry.getConfig(SkillConfig.class);
+        SkillConfig skillConfig = ConfigRegistry.getConfig(SkillConfig.class);
+        if (skillConfig == null) {
+            ManasCoreTesting.LOG.warn("SkillConfig not registered; skipping damage tweaks.");
+            return true;
+        }

165-172: Null-safe heal amount.

Guard against unexpected null in changeable.

-    if (owner.isShiftKeyDown()) {
-        owner.heal(amount.get());
+    if (owner.isShiftKeyDown()) {
+        Float heal = amount.get();
+        if (heal != null) owner.heal(heal);

133-139: Modern DamageTypes API not present

I don’t see any DamageTypes imports or usages in the codebase; you’re on the older damageSources() API. Continue using the existing source.equals(…) checks for now, and when you upgrade to a modern Minecraft version that includes the new DamageTypes registry (e.g. 1.20+), migrate to source.is(DamageTypes.CACTUS) to avoid brittle equality comparisons.

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

78-80: Allow cooldown mutation in the event.

If handlers are expected to modify cooldowns, pass a Changeable instead of a plain int.

-    interface SkillUpdateCooldownEvent {
-        EventResult cooldown(ManasSkillInstance skillInstance, LivingEntity owner, int currentCooldown, int mode);
-    }
+    interface SkillUpdateCooldownEvent {
+        EventResult cooldown(ManasSkillInstance skillInstance, LivingEntity owner, Changeable<Integer> currentCooldown, int mode);
+    }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (2)

81-87: Make getName non-null at call sites or return a safe fallback.

You mark getName() as @nullable but most usages assume non-null. Either document the null case clearly or return a fallback component to reduce null handling overhead in consumers.


295-315: Minor: wording consistency (“owning” vs “owing”).

Javadoc repeatedly says “owing this Skill”; should be “owning this Skill”.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 253ddc6 and 4ab6797.

📒 Files selected for processing (7)
  • gradle.properties (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/SkillEvents.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.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 (1)
  • gradle.properties
🧰 Additional context used
🧬 Code graph analysis (6)
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java (3)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-619)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (2)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (2)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (3)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-619)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (6)
config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)
  • ConfigRegistry (13-100)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (20-66)
testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java (1)
  • ManasCoreTesting (19-33)
testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java (1)
  • SkillConfig (7-20)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java (1)
  • TestTags (14-16)
🔇 Additional comments (4)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)

150-153: hashCode/equals alignment looks good.

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

16-30: Event layout looks solid for scroll and activation flows.

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

277-293: removeAttributeModifiers: use UUID and consider symmetry in client sync.

  • removeModifier must use UUID.
  • Consider sending an attributes update packet when adding as well, for parity.
-            attributeInstance.removeModifier(entry.getValue().id());
+            attributeInstance.removeModifier(entry.getValue().id());

If addHeldAttributeModifiers does not always sync on your target platform/mappings, mirror the packet send there as well.


461-463: EntityEvents is in the same package—no import needed
The enum ProjectileHitResult is nested in EntityEvents within io.github.manasmods.manascore.skill.api, so the reference resolves without additional imports.

Likely an incorrect or invalid review comment.

Comment on lines +88 to +99
public MutableComponent getChatDisplayName(boolean withDescription) {
Style style = Style.EMPTY.withColor(ChatFormatting.GRAY);
if (withDescription) {
MutableComponent hoverMessage = this.getName().append("\n");
hoverMessage.append(this.getSkillDescription().withStyle(ChatFormatting.GRAY));
hoverMessage.append("\n").append(Component.literal(SkillAPI.getSkillRegistry().getId(this).toString()).withStyle(ChatFormatting.DARK_GRAY));
style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMessage));
}

MutableComponent component = Component.literal("[").append(this.getName()).append("]");
return component.withStyle(style);
}
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 potential NPEs in chat display name construction.

getName() and getRegistryName() can be null; current code appends/uses them without checks.

-    public MutableComponent getChatDisplayName(boolean withDescription) {
+    public MutableComponent getChatDisplayName(boolean withDescription) {
         Style style = Style.EMPTY.withColor(ChatFormatting.GRAY);
         if (withDescription) {
-            MutableComponent hoverMessage = this.getName().append("\n");
-            hoverMessage.append(this.getSkillDescription().withStyle(ChatFormatting.GRAY));
-            hoverMessage.append("\n").append(Component.literal(SkillAPI.getSkillRegistry().getId(this).toString()).withStyle(ChatFormatting.DARK_GRAY));
+            final ResourceLocation id = this.getRegistryName();
+            final MutableComponent name = Objects.requireNonNullElseGet(this.getName(),
+                () -> id != null ? Component.literal(id.toString()) : Component.literal("unregistered"));
+            MutableComponent hoverMessage = name.copy().append("\n");
+            hoverMessage.append(this.getSkillDescription().withStyle(ChatFormatting.GRAY));
+            if (id != null) {
+                hoverMessage.append("\n").append(Component.literal(id.toString()).withStyle(ChatFormatting.DARK_GRAY));
+            }
             style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMessage));
         }
 
-        MutableComponent component = Component.literal("[").append(this.getName()).append("]");
+        final MutableComponent baseName = Objects.requireNonNullElseGet(this.getName(),
+            () -> {
+                ResourceLocation id = this.getRegistryName();
+                return id != null ? Component.literal(id.toString()) : Component.literal("unregistered");
+            });
+        MutableComponent component = Component.literal("[").append(baseName).append("]");
         return component.withStyle(style);
     }

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

Comment on lines +120 to +126
@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(this.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

equals() treats two unregistered skills as equal; also add hashCode().

If both registry names are null, Objects.equals(null, null) returns true for distinct instances. Fall back to identity when id is null, and implement hashCode().

     @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(this.getRegistryName(), skill.getRegistryName());
+        final ResourceLocation a = this.getRegistryName();
+        final ResourceLocation b = skill.getRegistryName();
+        if (a == null || b == null) return false;
+        return a.equals(b);
     }
+
+    @Override
+    public int hashCode() {
+        final ResourceLocation id = this.getRegistryName();
+        return id != null ? id.hashCode() : System.identityHashCode(this);
+    }
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java
around lines 120 to 126, equals currently treats two unregistered skills (null
registryName) as equal and there is no hashCode implementation; change equals to
check the registryName values first and if both registryName are non-null
compare them, but if the registryName is null fall back to identity comparison
(return this == o), and add a matching hashCode() that returns
registryName.hashCode() when registryName is non-null and otherwise returns
System.identityHashCode(this).

Comment on lines +259 to +270
public void addHeldAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) {
if (this.attributeModifiers.isEmpty()) return;

AttributeMap attributeMap = entity.getAttributes();
for (Map.Entry<Holder<Attribute>, AttributeTemplate> entry : this.attributeModifiers.entrySet()) {
AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey());

if (attributeInstance == null) continue;
attributeInstance.removeModifier(entry.getValue().id());
attributeInstance.addOrUpdateTransientModifier(entry.getValue().create(instance.getAttributeModifierAmplifier(entity, entry.getKey(), entry.getValue(), mode)));
}
}
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 bad call target and apply/remove modifiers with correct types.

  • Should call this.getAttributeModifierAmplifier(...)
  • removeModifier expects UUID
  • Construct AttributeModifier with UUID + name
-        AttributeMap attributeMap = entity.getAttributes();
+        AttributeMap attributeMap = entity.getAttributes();
         for (Map.Entry<Holder<Attribute>, AttributeTemplate> entry : this.attributeModifiers.entrySet()) {
             AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey());
 
             if (attributeInstance == null) continue;
-            attributeInstance.removeModifier(entry.getValue().id());
-            attributeInstance.addOrUpdateTransientModifier(entry.getValue().create(instance.getAttributeModifierAmplifier(entity, entry.getKey(), entry.getValue(), mode)));
+            AttributeTemplate tpl = entry.getValue();
+            attributeInstance.removeModifier(tpl.id());
+            double amp = this.getAttributeModifierAmplifier(instance, entity, entry.getKey(), tpl, mode);
+            attributeInstance.addOrUpdateTransientModifier(new AttributeModifier(tpl.id(), tpl.name(), tpl.amount() * amp, tpl.operation()));
         }
📝 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 addHeldAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) {
if (this.attributeModifiers.isEmpty()) return;
AttributeMap attributeMap = entity.getAttributes();
for (Map.Entry<Holder<Attribute>, AttributeTemplate> entry : this.attributeModifiers.entrySet()) {
AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey());
if (attributeInstance == null) continue;
attributeInstance.removeModifier(entry.getValue().id());
attributeInstance.addOrUpdateTransientModifier(entry.getValue().create(instance.getAttributeModifierAmplifier(entity, entry.getKey(), entry.getValue(), mode)));
}
}
public void addHeldAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) {
if (this.attributeModifiers.isEmpty()) return;
AttributeMap attributeMap = entity.getAttributes();
for (Map.Entry<Holder<Attribute>, AttributeTemplate> entry : this.attributeModifiers.entrySet()) {
AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey());
if (attributeInstance == null) continue;
AttributeTemplate tpl = entry.getValue();
attributeInstance.removeModifier(tpl.id());
double amp = this.getAttributeModifierAmplifier(instance, entity, entry.getKey(), tpl, mode);
attributeInstance.addOrUpdateTransientModifier(new AttributeModifier(tpl.id(), tpl.name(), tpl.amount() * amp, tpl.operation()));
}
}

Comment on lines +62 to +66
ManasSkillInstance skill = skillChangeable.get();
if (skill == null || deltaChangeable.isEmpty()) return;
if (!skill.canScroll(player, modeChangeable.get())) return;
if (!skill.canInteractSkill(player)) return;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null-safe mode handling to avoid NPE on auto-unboxing.

modeChangeable.get() can be null after event mutation; auto-unboxing to int would NPE.

-                    ManasSkillInstance skill = skillChangeable.get();
-                    if (skill == null || deltaChangeable.isEmpty()) return;
-                    if (!skill.canScroll(player, modeChangeable.get())) return;
+                    ManasSkillInstance skill = skillChangeable.get();
+                    if (skill == null || deltaChangeable.isEmpty() || modeChangeable.isEmpty()) return;
+                    int modeVal = Math.max(0, modeChangeable.get());
+                    if (!skill.canScroll(player, modeVal)) return;
-                    if (!skill.canInteractSkill(player)) return;
+                    if (!skill.canInteractSkill(player)) return;
 
-                    skill.onScroll(player, deltaChangeable.get(), modeChangeable.get());
+                    skill.onScroll(player, deltaChangeable.get(), modeVal);
📝 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
ManasSkillInstance skill = skillChangeable.get();
if (skill == null || deltaChangeable.isEmpty()) return;
if (!skill.canScroll(player, modeChangeable.get())) return;
if (!skill.canInteractSkill(player)) return;
ManasSkillInstance skill = skillChangeable.get();
if (skill == null || deltaChangeable.isEmpty() || modeChangeable.isEmpty()) return;
int modeVal = Math.max(0, modeChangeable.get());
if (!skill.canScroll(player, modeVal)) return;
if (!skill.canInteractSkill(player)) return;
skill.onScroll(player, deltaChangeable.get(), modeVal);
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java
around lines 62 to 66, the code calls modeChangeable.get() and relies on
auto-unboxing which can NPE if the event mutated it to null; update the logic to
read Integer mode = modeChangeable.get() once, check mode for null (and return
or handle a sensible default) before using it in any canScroll call or any
int-context, and replace modeChangeable.get() uses with the null-checked local
variable so no auto-unboxing occurs on a null value.

Comment on lines +28 to +34
Map<ResourceLocation, Integer> packetSkills = new HashMap<>();
for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
Changeable<Integer> mode = Changeable.of(0);
if (SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(skillInstance, player, mode, amountY).isFalse()) continue;
if (!skillInstance.canScroll(player, mode.get())) continue;
packetSkills.put(skillInstance.getSkillId(), mode.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

⚠️ Potential issue

Null-safe mode and early cap during collection.

Avoid NPE from mode.get() and cap the number of skills collected to the server’s limit.

-            Map<ResourceLocation, Integer> packetSkills = new HashMap<>();
-            for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
+            Map<ResourceLocation, Integer> packetSkills = new HashMap<>();
+            int collected = 0;
+            for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
                 Changeable<Integer> mode = Changeable.of(0);
                 if (SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(skillInstance, player, mode, amountY).isFalse()) continue;
-                if (!skillInstance.canScroll(player, mode.get())) continue;
-                packetSkills.put(skillInstance.getSkillId(), mode.get());
+                if (mode.isEmpty()) continue;
+                int m = Math.max(0, mode.get());
+                if (!skillInstance.canScroll(player, m)) continue;
+                packetSkills.put(skillInstance.getSkillId(), m);
+                if (++collected >= RequestSkillScrollPacket.MAX_SKILLS_PER_PACKET) break;
             }
📝 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
Map<ResourceLocation, Integer> packetSkills = new HashMap<>();
for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
Changeable<Integer> mode = Changeable.of(0);
if (SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(skillInstance, player, mode, amountY).isFalse()) continue;
if (!skillInstance.canScroll(player, mode.get())) continue;
packetSkills.put(skillInstance.getSkillId(), mode.get());
}
Map<ResourceLocation, Integer> packetSkills = new HashMap<>();
int collected = 0;
for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) {
Changeable<Integer> mode = Changeable.of(0);
if (SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(skillInstance, player, mode, amountY).isFalse()) continue;
if (mode.isEmpty()) continue;
int m = Math.max(0, mode.get());
if (!skillInstance.canScroll(player, m)) continue;
packetSkills.put(skillInstance.getSkillId(), m);
if (++collected >= RequestSkillScrollPacket.MAX_SKILLS_PER_PACKET) break;
}
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java
around lines 28-34, make the mode access null-safe and stop collecting once the
server cap is reached: replace direct mode.get() calls with mode.orElse(0) (use
the safe default when passing into
SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(...) and
skillInstance.canScroll(...)), and after adding to packetSkills check the server
limit (e.g., if (packetSkills.size() >= SkillAPI.getPacketSkillLimit()) break;)
so you early-exit the loop once the maximum allowed skills for the packet is
reached.

Comment on lines +60 to +66
public boolean canScroll(ManasSkillInstance instance, LivingEntity entity) {
return entity.isShiftKeyDown();
}

public boolean canIgnoreCoolDown(ManasSkillInstance instance, LivingEntity entity, int mode) {
return mode == 1 && entity.isShiftKeyDown();
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm override signature for canScroll.

This overload lacks mode; ensure you also override the 3-arg variant used by ManasSkillInstance to avoid default behavior.


🏁 Script executed:

#!/bin/bash
# Verify ManasSkill.canScroll signatures and TestSkill overrides
rg -n -C2 -g '!**/build/**' -P '\bclass\s+ManasSkill\b|\bcanScroll\s*\('

Length of output: 4429


Override the 3-arg canScroll signature in TestSkill
TestSkill currently defines only a 2-arg canScroll(ManasSkillInstance, LivingEntity), but ManasSkill declares public boolean canScroll(ManasSkillInstance, LivingEntity, int mode) (which defaults to false). Replace or add the 3-arg override in TestSkill.java so the intended scrolling behavior is actually invoked.

🤖 Prompt for AI Agents
In
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java
around lines 60 to 66, the class only defines the 2-arg canScroll method but
ManasSkill declares a 3-arg canScroll(ManasSkillInstance, LivingEntity, int)
which is currently not overridden; add or replace with the 3-arg override so the
intended behavior is used: implement public boolean canScroll(ManasSkillInstance
instance, LivingEntity entity, int mode) and return the correct condition (e.g.,
mode == 1 && entity.isShiftKeyDown()) so scrolling respects the mode parameter.

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 (1)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1)

60-67: Harden ticking with try/catch to avoid server crashes.

If tickRace throws, it can take down the server loop. Wrap the call and log.

-        tickRace(entity, storage);
+        try {
+            tickRace(entity, storage);
+        } catch (Exception e) {
+            log.error("Error ticking race for entity {}: {}", entity, e.getMessage(), e);
+        }
🧹 Nitpick comments (3)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (3)

80-82: Clamp cooldown to zero to avoid negative values.

Prevents underflow into negative cooldowns.

-                instance.setCooldown(instance.getCooldown() - 1);
+                instance.setCooldown(Math.max(0, instance.getCooldown() - 1));

40-41: Tighten visibility of tickingRaces to prevent accidental cross-thread misuse.

Expose helpers instead of a public mutable collection.

-public static final Multimap<UUID, TickingRace> tickingRaces = ArrayListMultimap.create();
+private static final Multimap<UUID, TickingRace> tickingRaces = ArrayListMultimap.create();

If external access is needed, add controlled methods (add/remove/clear) on this class.


120-121: Health reset on race change — confirm intended UX.

Resetting to full health can affect combat balance. If only max-health changes should be applied, consider clamping instead: owner.setHealth(Math.min(owner.getHealth(), owner.getMaxHealth())).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab6797 and 4388476.

📒 Files selected for processing (2)
  • gradle.properties (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • gradle.properties
🧰 Additional context used
🧬 Code graph analysis (1)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (20-66)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)
  • ManasRaceInstance (43-497)
🔇 Additional comments (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (2)

108-116: Verify evolution semantics when reusing the same instance.

When evolution == true and instance == race, the evolution copy (deserialize/serialize) and onRaceEvolution are skipped. If evolutions may reuse an instance, this path won’t run. Confirm this can’t happen or guard for it.

Would you like a targeted patch that handles “same-instance” evolutions explicitly?


48-55: No changes needed: Races declares markDirty() The Races interface at race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java line 69 defines void markDirty(), so the call compiles as written.

Comment on lines 48 to 55
EntityEvents.LIVING_POST_TICK.register(entity -> {
Level level = entity.level();
if (level.isClientSide()) return;
Races storage = RaceAPI.getRaceFrom(entity);
handleRaceTick(entity, level, storage);
if (entity instanceof Player player) handleRaceHeldTick(player, storage);
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

Reduce network churn: don’t mark storage dirty every server tick.

storage.markDirty() is called every tick for every living entity, causing unnecessary sync traffic. Move it into the race-tick path so it runs at most once per second (or only when something actually changes).

Apply:

@@
         EntityEvents.LIVING_POST_TICK.register(entity -> {
             Level level = entity.level();
             if (level.isClientSide()) return;
             Races storage = RaceAPI.getRaceFrom(entity);
             handleRaceTick(entity, level, storage);
             if (entity instanceof Player player) handleRaceHeldTick(player, storage);
-            storage.markDirty();
         });
@@
-    private static void handleRaceTick(LivingEntity entity, Level level, Races storage) {
+    private static void handleRaceTick(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);
+        tickRace(entity, storage);
+        // Only mark dirty on the (throttled) race-tick to cut sync volume by ~20x.
+        storage.markDirty();
     }

Also applies to: 60-67

Comment on lines +117 to +121
if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
race.markDirty();
race.addAttributeModifiers(owner);
owner.setHealth(owner.getMaxHealth());
race.onRaceSet(owner);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Send race messages only to players and avoid double-applying attribute modifiers.

  • sendSystemMessage should target players; non-player entities don’t support it.
  • Re-applying modifiers when instance == race can cause duplicate modifiers.
-        if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
-        race.markDirty();
-        race.addAttributeModifiers(owner);
+        if (raceMessage.isPresent() && owner instanceof Player p) {
+            p.sendSystemMessage(raceMessage.get());
+        }
+        race.markDirty();
+        if (instance != race) {
+            race.addAttributeModifiers(owner);
+        }
📝 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 (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
race.markDirty();
race.addAttributeModifiers(owner);
owner.setHealth(owner.getMaxHealth());
race.onRaceSet(owner);
// only send the race-change message to actual players
if (raceMessage.isPresent() && owner instanceof Player p) {
p.sendSystemMessage(raceMessage.get());
}
race.markDirty();
// avoid re-applying modifiers if this race was already set
if (instance != race) {
race.addAttributeModifiers(owner);
}
owner.setHealth(owner.getMaxHealth());
race.onRaceSet(owner);
🤖 Prompt for AI Agents
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java
around lines 117 to 121: ensure system messages are only sent to player entities
and avoid re-applying attribute modifiers when the race hasn't actually changed;
wrap the sendSystemMessage call in an instanceof check (only call it if owner is
a Player) and guard the race.addAttributeModifiers(owner) call so it only runs
when the stored/previous race instance differs from the new race (i.e., only
apply modifiers on actual race change) or otherwise remove existing modifiers
before re-adding to prevent duplicates.

Comment on lines +126 to +127
if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
markDirty();
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 potential NPE when reading Changeable teleport.

Changeable.get() is nullable. Auto-unboxing in if (teleport.get() && ...) can NPE if a listener sets it to null.

-        if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
+        if (Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player) {
+            SpawnPointHelper.teleportToNewSpawn(player);
+        }
📝 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 (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
markDirty();
if (Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player) {
SpawnPointHelper.teleportToNewSpawn(player);
}
markDirty();
🤖 Prompt for AI Agents
In
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java
around lines 126-127, the expression if (teleport.get() && getOwner() instanceof
ServerPlayer player) can throw an NPE because Changeable<Boolean>.get() may
return null; replace the condition with a null-safe check such as
Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player
so the Boolean is compared safely, keep the
SpawnPointHelper.teleportToNewSpawn(player) call and ensure markDirty() remains
after the conditional.

Comment on lines +137 to +141
@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.

⚠️ Potential issue

NB: Loading NBT can crash on bad data; also reapply attributes after load.

ManasRaceInstance.fromNBT can throw (e.g., unknown race id). Wrap and log; on success, reapply attribute modifiers server-side to prevent stale stats after chunk/relog.

     @Override
     public void load(CompoundTag data) {
         if (!data.contains(RACE_KEY)) return;
-        this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+        try {
+            ManasRaceInstance loaded = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+            this.raceInstance = loaded;
+            LivingEntity owner = this.getOwner();
+            if (!owner.level().isClientSide()) {
+                loaded.addAttributeModifiers(owner);
+            }
+        } catch (Exception e) {
+            log.error("Failed to load race instance for {}. Clearing race. Data: {}", this.getOwner(), data, e);
+            this.raceInstance = null;
+        }
     }
📝 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 void load(CompoundTag data) {
if (!data.contains(RACE_KEY)) return;
this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
}
@Override
public void load(CompoundTag data) {
if (!data.contains(RACE_KEY)) return;
try {
ManasRaceInstance loaded = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
this.raceInstance = loaded;
LivingEntity owner = this.getOwner();
if (!owner.level().isClientSide()) {
loaded.addAttributeModifiers(owner);
}
} catch (Exception e) {
log.error("Failed to load race instance for {}. Clearing race. Data: {}", this.getOwner(), data, e);
this.raceInstance = null;
}
}

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

♻️ Duplicate comments (8)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (4)

54-56: Guard isShiftKeyDown() to Player to avoid crashes on non-player entities

Use a Player check where sneaking is read.

Apply:

-        return entity.isShiftKeyDown();
+        return entity instanceof Player player && player.isShiftKeyDown();
-        return mode == 1 && entity.isShiftKeyDown();
+        return mode == 1 && entity instanceof Player player && player.isShiftKeyDown();
-        if (living.isShiftKeyDown()) ManasCoreTesting.LOG.info("You're sneaky");
+        if (living instanceof Player player && player.isShiftKeyDown()) ManasCoreTesting.LOG.info("You're sneaky");
-            if (living.isShiftKeyDown()) {
+            if (living instanceof Player player && player.isShiftKeyDown()) {
-        if (owner.isShiftKeyDown()) {
+        if (owner instanceof Player player && player.isShiftKeyDown()) {
-        if (owner.isShiftKeyDown() && target instanceof Villager) {
+        if (owner instanceof Player player && player.isShiftKeyDown() && target instanceof Villager) {

Also applies to: 66-68, 102-104, 185-191, 167-172, 158-164


43-44: Use a stable ResourceLocation id for the attribute modifier

A plain string id is brittle; prefer a namespaced id to ensure consistent add/remove.

Apply:

+        // import: net.minecraft.resources.ResourceLocation
+        // import: io.github.manasmods.manascore.skill.ModuleConstants
-        this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, "test_speed", -0.95, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL);
+        this.addHeldAttributeModifier(
+            Attributes.MOVEMENT_SPEED,
+            ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "test_speed"),
+            -0.95,
+            AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL
+        );

62-64: Wrong override signature: canScroll is never called

Base API defines canScroll(ManasSkillInstance, LivingEntity, int). Your 2-arg method won’t be invoked, so scrolling is effectively disabled.

Apply:

-    public boolean canScroll(ManasSkillInstance instance, LivingEntity entity) {
-        return entity.isShiftKeyDown();
-    }
+    public boolean canScroll(ManasSkillInstance instance, LivingEntity entity, int mode) {
+        return entity instanceof Player player && player.isShiftKeyDown();
+    }

145-148: Don’t directly damage creepers inside onDamageEntity; set amount instead

Calling creeper.hurt here double-applies damage and breaks the event contract.

Apply:

-        if (target instanceof Creeper creeper && skillConfig.instaKillCreeper) {
-            creeper.hurt(owner.level().damageSources().dragonBreath(), 100F);
-            ManasCoreTesting.LOG.info("No creeper");
-        } else if (target instanceof IronGolem) {
+        if (target instanceof Creeper && skillConfig.instaKillCreeper) {
+            amount.set(100F); // lethal; processed once by the pipeline
+            ManasCoreTesting.LOG.info("No creeper");
+        } else if (target instanceof IronGolem) {
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

44-44: Synchronize tickingSkills or confine all access to the main thread

ArrayListMultimap is not thread-safe. If any hook runs off-thread, this can race.

Apply:

+import com.google.common.collect.Multimaps;
@@
-    public static final Multimap<UUID, TickingSkill> tickingSkills = ArrayListMultimap.create();
+    public static final Multimap<UUID, TickingSkill> tickingSkills =
+        Multimaps.synchronizedListMultimap(ArrayListMultimap.create());

Optionally make tickingSkills private and expose helper methods to mutate safely.

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

66-74: Guard config access and use safer, saner attribute modifiers.

  • Avoid potential NPE/empty list when reading stacks from config; clamp to [1,64].
  • Critical damage/chance modifiers are extreme and the chance uses a multiplicative op against a 0 base (likely no effect). Use moderate values and correct ops.
-    public static final RegistrySupplier<Item> TEST_ITEM = ITEMS.register("test_item",
-                    () -> new Item(new Item.Properties().arch$tab(TESTING_TAB)
-                            .stacksTo(ConfigRegistry.getConfig(TestConfig.class).random_lists.numberLists.intList.getFirst())
-                            .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())));
+    public static final RegistrySupplier<Item> TEST_ITEM = ITEMS.register("test_item",
+            () -> {
+                TestConfig cfg = ConfigRegistry.getConfig(TestConfig.class);
+                int stacks = 64;
+                if (cfg != null && cfg.random_lists != null && cfg.random_lists.numberLists != null
+                        && cfg.random_lists.numberLists.intList != null && !cfg.random_lists.numberLists.intList.isEmpty()) {
+                    int v = cfg.random_lists.numberLists.intList.get(0);
+                    stacks = Math.max(1, Math.min(64, v));
+                }
+                return new Item(new Item.Properties().arch$tab(TESTING_TAB)
+                        .stacksTo(stacks)
+                        .attributes(ItemAttributeModifiers.builder()
+                                // 1.5x base crit damage
+                                .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER,
+                                        new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"),
+                                                1.5, AttributeModifier.Operation.MULTIPLY_BASE), EquipmentSlotGroup.OFFHAND)
+                                // +15% crit chance (attribute is 0..100); ADD_VALUE avoids multiplying a 0 base.
+                                .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE,
+                                        new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
+                                                15, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.OFFHAND)
+                                .build()));
+            });

134-153: Add null checks, remove unused locals, and surface user feedback in drop event.

Prevents NPEs and gives clear UX when an action succeeds/fails.

-        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(), Component.literal("Forgot Tested Skill!"));
-            }  else if (entity.getItem().is(Items.GOLD_INGOT)) {
-                Races storage = RaceAPI.getRaceFrom(player);
-                storage.setRace(RegistryTest.TEST_RACE.getId(), true, Component.literal("Set to Test Race!"));
-            }
-
-            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.getScoreboardName());
+                    return EventResult.pass();
+                }
+                if (TEST_SKILL == null || TEST_SKILL.get() == null) {
+                    ManasCoreTesting.LOG.error("Test skill is not registered");
+                    return EventResult.pass();
+                }
+                if (storage.learnSkill(TEST_SKILL.get())) {
+                    ManasCoreTesting.LOG.info("Added Tested Skill!");
+                    player.displayClientMessage(Component.literal("Added Tested Skill!"), true);
+                } 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.getScoreboardName());
+                    return EventResult.pass();
+                }
+                storage.forgetSkill(TEST_SKILL.get(), Component.literal("Forgot Tested Skill!"));
+            } 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.getScoreboardName());
+                    return EventResult.pass();
+                }
+                if (!RaceAPI.getRaceRegistry().containsKey(TEST_RACE.getId())) {
+                    ManasCoreTesting.LOG.error("Test race is not registered");
+                    return EventResult.pass();
+                }
+                storage.setRace(TEST_RACE.getId(), true, Component.literal("Set to Test Race!"));
+                player.displayClientMessage(Component.literal("Race set to Test Race"), true);
+            }
+            return EventResult.pass();
+        });

178-191: Cap death-explosion radius; use identity compare for effect type.

Prevents unexpectedly large blasts in test worlds and simplifies the comparison.

-                    float g = 10.0F + entity.getRandom().nextFloat() * 2.0F;
-                    if (this.equals(RegistryTest.TEST_MOB_EFFECT_PARTICLE.get()))
+                    float baseRadius = 3.0F;
+                    float randomAddition = entity.getRandom().nextFloat() * 1.0F;
+                    float g = Math.min(baseRadius + randomAddition, 4.0F);
+                    if (this == 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);
🧹 Nitpick comments (9)
race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java (2)

20-25: Call original once; then conditionally copy state (also clamp health and keep absorption).

Reduces duplication, avoids future drift, and guards against health > max. Copying absorption keeps client/server visuals consistent after End exit.

Apply:

-        if (!endConquered) original.call(instance, oldPlayer, endConquered);
-        else {
-            original.call(instance, oldPlayer, endConquered);
-            instance.getAttributes().assignAllValues(oldPlayer.getAttributes());
-            instance.setHealth(oldPlayer.getHealth());
-        }
+        original.call(instance, oldPlayer, endConquered);
+        if (endConquered) {
+            instance.getAttributes().assignAllValues(oldPlayer.getAttributes());
+            instance.setHealth(Math.min(oldPlayer.getHealth(), instance.getMaxHealth()));
+            instance.setAbsorptionAmount(oldPlayer.getAbsorptionAmount());
+        }

16-16: Rename class for target clarity.

The mixin targets PlayerList but the class is named MixinServerPlayer. Consider MixinPlayerList_Respawn or similar for maintainability.

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

167-172: Healing equals incoming damage may nullify damage or cause odd interactions

onTakenDamage heals for the same amount, effectively canceling damage and possibly interacting poorly with other modifiers. Consider capping or tagging sources.

Would you like a mode/threshold-based example that avoids full negation except for specific sources?

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

190-205: Forgetting a skill should also clean up attribute modifiers/toggle state

Current flow sends messages and removes the instance, but may leave transient modifiers applied.

Apply:

         if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
         instance.onForgetSkill(this.getOwner());
         instance.markDirty();
 
-        this.getLearnedSkills().remove(instance);
+        // Ensure transient state is cleared before removal
+        for (int mode = 0; mode < instance.getModes(); mode++) {
+            instance.removeAttributeModifiers(getOwner(), mode);
+        }
+        instance.setToggled(false);
+        this.getLearnedSkills().remove(instance);
         this.hasRemovedSkills = true;
         markDirty();
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (5)

61-64: Prefer translatable text for the creative tab title.

Use a translation key to avoid hardcoded, non-localized UI strings.

-            CreativeTabRegistry.create(Component.literal("Testing Creative Tab").withStyle(ChatFormatting.RED),
+            CreativeTabRegistry.create(Component.translatable("itemGroup.manascore.testing").withStyle(ChatFormatting.RED),
                     () -> new ItemStack(RegistryTest.TEST_ITEM.get())))

86-89: Tighten generics on block entity type.

Use the concrete type for stronger compile-time checks.

-    public static final RegistrySupplier<BlockEntityType<?>> TEST_BLOCK_ENTITY = BLOCK_ENTITIES.register("test_block_entity",
+    public static final RegistrySupplier<BlockEntityType<TestBlockEntity>> TEST_BLOCK_ENTITY = BLOCK_ENTITIES.register("test_block_entity",
             () -> BlockEntityType.Builder.of(TestBlockEntity::new, RegistryTest.TEST_BLOCK.get()).build(null));

90-94: Align entity type generics and consider category.

  • Use concrete TestEntity generic.
  • Consider MobCategory.CREATURE for a Villager-derived entity unless you intentionally need MONSTER rules.
-    public static final RegistrySupplier<EntityType<? extends Villager>> TEST_ENTITY_TYPE = ENTITY_TYPES.register("test_entity",
-            () -> EntityType.Builder.of(TestEntity::new, MobCategory.MONSTER).fireImmune()
+    public static final RegistrySupplier<EntityType<TestEntity>> TEST_ENTITY_TYPE = ENTITY_TYPES.register("test_entity",
+            () -> EntityType.Builder.of(TestEntity::new, MobCategory.CREATURE).fireImmune()
                     .sized(1F, 1F).clientTrackingRange(4).updateInterval(10).build("test_entity"));

96-104: Attribute bounds sanity check.

Adding +3 WATER_MOVEMENT_EFFICIENCY might exceed expected ranges and produce odd movement. If intentional for tests, ignore; otherwise reduce.


156-160: Use the provided entityType parameter.

Avoid hard-coding the static type; use the constructor parameter for correctness and reuse.

-        public TestEntity(EntityType<TestEntity> entityType, Level level) {
-            super(TEST_ENTITY_TYPE.get(), level);
+        public TestEntity(EntityType<TestEntity> entityType, Level level) {
+            super(entityType, level);
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ae207b and 7754cae.

📒 Files selected for processing (9)
  • gradle.properties (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java (1 hunks)
  • race-common/src/main/resources/manascore_race.mixins.json (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/SkillStorage.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/TestSkill.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • race-common/src/main/resources/manascore_race.mixins.json
  • gradle.properties
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java
🧰 Additional context used
🧬 Code graph analysis (4)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (10)
config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)
  • ConfigRegistry (13-100)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (20-66)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)
  • ManasSkill (56-524)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-623)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java (1)
  • ManasCoreTesting (19-33)
testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java (1)
  • SkillConfig (7-20)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1)
  • RegistryTest (59-214)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java (1)
  • TestTags (14-16)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (10)
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (1)
  • ManasCoreAttributes (16-61)
config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)
  • ConfigRegistry (13-100)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)
  • RaceRegistry (27-106)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java (1)
  • ManasCoreTesting (19-33)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java (1)
  • TestAttributeRegistry (13-21)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1)
  • TestSkill (39-203)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (1)
  • TestRace (42-155)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1)
  • TestRaceEvolved (32-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1)
  • ManasCoreSkill (16-27)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (20-66)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-623)
🔇 Additional comments (5)
race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java (2)

23-24: Verify client sync of attributes/health post-respawn.

Depending on mappings/version, direct AttributeMap mutation may not auto-sync to the player’s own client. Validate that the client immediately shows the correct max health, current health, and modifiers after End respawn. If desync occurs, explicitly broadcast attribute changes or schedule a sync on login tick.


17-19: Stability: ensure the call site is unique across mappings.

If multiple restoreFrom invokes ever appear in PlayerList.respawn, add selector constraints (e.g., ordinal) to avoid wrapping the wrong call. Confirm across Fabric/NeoForge 1.21.1 mappings.

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

50-56: Damage pipeline ordering looks good

Pre/Calc/Post events are chained with early interrupts; this matches expected semantics.

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

16-31: Event surface is coherent and minimal

Good separation: result-based for gating, loop for post-tick. SKILL_MASTERY addition fits well.

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

105-117: LGTM on registries.

Skill and race registries are correctly wired via Registrar keys; suppliers look good.

Comment on lines +81 to +84
.add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
69, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
.add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
420, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND).build())));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate modifier IDs on the same item can collide.

Both modifiers use the same ResourceLocation ID "test_critical_chance". Give each a unique, stable ID.

-                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
+                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_attr_player"),
                                     69, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
-                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
+                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_attr_all"),
                                     420, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND).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
.add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
69, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
.add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
420, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND).build())));
.add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(
ResourceLocation.withDefaultNamespace("test_attr_player"),
69, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.MAINHAND)
.add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(
ResourceLocation.withDefaultNamespace("test_attr_all"),
420, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.MAINHAND)
.build())));
🤖 Prompt for AI Agents
In
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java
around lines 81 to 84, two AttributeModifier entries use the same
ResourceLocation ID "test_critical_chance", which can collide; change each
modifier to use a unique, stable ResourceLocation ID (for example
"test_critical_chance_player" and "test_critical_chance_all" or define
descriptive constants) so each modifier has a distinct identifier and update any
related references accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (1)

43-49: Gate test command registration to dev/debug builds.

ManasCoreTesting.init() is called unconditionally from platform entrypoints and then calls CommandModuleTest.init(), which registers test commands — this exposes test commands in non-dev builds. Guard the registration with a dev-only check or config switch.

Locations to update:

  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java — init() registers TestCommand (lines ~43–49).
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java — line 27: CommandModuleTest.init().
  • testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java — line 14: ManasCoreTesting.init().
  • testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java — line 16: ManasCoreTesting.init().

Change either: (A) add a dev-check in ManasCoreTesting.init() before calling CommandModuleTest.init(), or (B) add a dev-check inside CommandModuleTest.init(). Use the existing Platform.getEnv() or a configurable dev flag.

♻️ Duplicate comments (5)
testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (5)

70-79: Add user feedback and exception handling when learning a skill; fix log name usage.

-            Entity entity = selector.findSingleEntity(sender);
-            if (entity instanceof LivingEntity living) {
-                Skills storage = SkillAPI.getSkillsFrom(living);
-                if (storage.learnSkill(RegistryTest.TEST_SKILL.get()))
-                    ManasCoreTesting.LOG.info("Added Test Skill to " + entity.getName());
-            }
+            Entity entity = selector.findSingleEntity(sender);
+            if (entity instanceof LivingEntity living) {
+                Skills storage = SkillAPI.getSkillsFrom(living);
+                try {
+                    if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) {
+                        ManasCoreTesting.LOG.info("Added Test Skill to {}", entity.getName().getString());
+                        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()));
+                }
+            }

83-89: Teleportation: add try/catch and clear player-only feedback.

-            if (sender.getPlayer() != null)
-                SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), Level.OVERWORLD,
-                        pos.getX(), pos.getY(), pos.getZ(), xRot.getRotation(sender).x, yRot.getRotation(sender).y);
+            if (sender.getPlayer() != null) {
+                try {
+                    SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), Level.OVERWORLD,
+                            pos.getX(), pos.getY(), pos.getZ(), xRot.getRotation(sender).x, yRot.getRotation(sender).y);
+                    sender.sendSuccess(() -> Component.literal("Teleported successfully"), true);
+                } catch (Exception e) {
+                    ManasCoreTesting.LOG.error("Failed to teleport player", e);
+                    sender.sendFailure(Component.literal("Teleportation failed: " + e.getMessage()));
+                }
+            } else {
+                sender.sendFailure(Component.literal("Only players can be teleported"));
+            }

93-99: Teleportation across dimensions: add error handling and user feedback.

-            if (sender.getPlayer() != null)
-                SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), dimension, pos.x(), pos.y(), pos.z(), 0, 0);
-            return true;
+            if (sender.getPlayer() != null) {
+                try {
+                    SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), dimension, pos.x(), pos.y(), pos.z(), 0, 0);
+                    sender.sendSuccess(() -> Component.literal("Teleported successfully to dimension"), true);
+                } catch (Exception e) {
+                    ManasCoreTesting.LOG.error("Failed to teleport player to dimension", e);
+                    sender.sendFailure(Component.literal("Teleportation failed: " + e.getMessage()));
+                }
+            } else {
+                sender.sendFailure(Component.literal("Only players can be teleported"));
+            }
+            return true;

102-111: Skill removal: validate player/skill and add success/failure messaging; prefer parameterized log.

-            ManasSkill skill = SkillAPI.getSkillRegistry().get(location);
-            if (skill != null && sender.getPlayer() != null) {
-                SkillAPI.getSkillsFrom(sender.getPlayer()).forgetSkill(skill);
-                ManasCoreTesting.LOG.info("Removed Test Skill from " + sender.getPlayer().getName());
-            }
+            ManasSkill skill = SkillAPI.getSkillRegistry().get(location);
+            if (sender.getPlayer() == null) {
+                sender.sendFailure(Component.literal("Only players can have skills removed"));
+                return true;
+            }
+            if (skill == null) {
+                sender.sendFailure(Component.literal("Skill not found: " + location));
+                return true;
+            }
+            try {
+                boolean removed = SkillAPI.getSkillsFrom(sender.getPlayer()).forgetSkill(skill);
+                if (removed) {
+                    ManasCoreTesting.LOG.info("Removed Test Skill from {}", sender.getPlayer().getName().getString());
+                    sender.sendSuccess(() -> Component.literal("Successfully removed skill"), true);
+                } else {
+                    sender.sendFailure(Component.literal("Player doesn't have this skill"));
+                }
+            } catch (Exception e) {
+                ManasCoreTesting.LOG.error("Failed to remove skill", e);
+                sender.sendFailure(Component.literal("Failed to remove skill: " + e.getMessage()));
+            }

114-126: Item creation/gifting: handle errors and inventory-full cases; add user feedback.

-            if (sender.getPlayer() != null) {
-                ItemStack stack = itemInput.createItemStack(5, false);
-                stack.enchant(location, 100);
-                sender.getPlayer().addItem(stack);
-                sender.getPlayer().addItem(itemInput2.createItemStack(4, false));
-                sender.getPlayer().addItem(itemInput3.createItemStack(3, false));
-            }
+            if (sender.getPlayer() != null) {
+                try {
+                    ItemStack stack = itemInput.createItemStack(5, false);
+                    stack.enchant(location, 100);
+                    boolean added1 = sender.getPlayer().addItem(stack);
+                    boolean added2 = sender.getPlayer().addItem(itemInput2.createItemStack(4, false));
+                    boolean added3 = sender.getPlayer().addItem(itemInput3.createItemStack(3, false));
+                    if (added1 && added2 && added3) {
+                        sender.sendSuccess(() -> Component.literal("Items added to inventory"), true);
+                    } else {
+                        sender.sendFailure(Component.literal("One or more items could not be added (inventory may be full)"));
+                    }
+                } catch (Exception e) {
+                    ManasCoreTesting.LOG.error("Failed to create or add item", e);
+                    sender.sendFailure(Component.literal("Failed to create or add item: " + e.getMessage()));
+                }
+            } else {
+                sender.sendFailure(Component.literal("Only players can receive items"));
+            }
🧹 Nitpick comments (2)
testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (2)

55-57: Add permission gating to foo root command.

Root withPerms has no @Permission; gate it like the subcommand to prevent accidental exposure.

 @Execute
+@Permission(value = "manascore.command.test", permissionLevel = Permission.PermissionLevel.GAMEMASTER)
 public boolean withPerms(@SenderArg CommandSourceStack sender) {

150-159: Avoid NPE from auto-unboxing Boolean in ternary.

Use null-safe comparison.

-                    .append(bool ? "true" : "false")
+                    .append(Boolean.TRUE.equals(bool) ? "true" : "false")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7754cae and 9cc4696.

📒 Files selected for processing (5)
  • gradle.properties (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.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/impl/SkillRegistry.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java
  • gradle.properties
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java
🧰 Additional context used
🧬 Code graph analysis (1)
testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (5)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)
  • ManasSkill (56-524)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java (1)
  • ManasCoreTesting (19-33)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1)
  • RegistryTest (59-214)
🔇 Additional comments (2)
testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java (2)

62-67: Permission on subcommand looks good.


320-324: Enum extension looks fine.

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

♻️ Duplicate comments (4)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)

338-340: Call markDirty() after replacing the cooldown list.

This method modifies the instance's state but doesn't mark it dirty for synchronization, unlike other state-manipulating methods (e.g., setCoolDown, setMastery, setToggled). This inconsistency could lead to the cooldown list not being synchronized properly.

Apply this diff to add the missing markDirty() call:

 public void setCoolDownList(List<Integer> list) {
     this.cooldownList = list;
+    markDirty();
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1)

104-112: Add null check for hitResult.getEntity() to prevent NPE.

The code assumes that hitResult.getEntity() is not null. If hitResult does not contain an entity, the instanceof check will pass null through to LivingEntity, which would then be cast incorrectly in subsequent code. While the instanceof check itself won't throw an NPE, it's safer to explicitly verify the entity is non-null.

Apply this diff to add an explicit null check:

 EntityEvents.PROJECTILE_HIT.register((result, projectile, deflectionChangeable, hitResultChangeable) -> {
     if (!(result instanceof EntityHitResult hitResult)) return;
-    if (!(hitResult.getEntity() instanceof LivingEntity hitEntity)) return;
+    Entity entity = hitResult.getEntity();
+    if (entity == null || !(entity instanceof LivingEntity hitEntity)) return;

     for (ManasSkillInstance instance : SkillAPI.getSkillsFrom(hitEntity).getLearnedSkills()) {
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (2)

45-45: Replace ArrayListMultimap with a thread-safe alternative for tickingSkills.

The tickingSkills multimap is accessed and modified from multiple event hooks (e.g., PLAYER_QUIT at line 84, handleSkillHeldTick at line 158, and handleSkillRelease at line 246). If any code path runs off the main thread or during concurrent event processing, data races may occur. ArrayListMultimap is not thread-safe.

Consider using Multimaps.synchronizedMultimap to wrap the multimap, or switch to a ConcurrentHashMap<UUID, Collection<TickingSkill>> with thread-safe collections:

-public static final Multimap<UUID, TickingSkill> tickingSkills = ArrayListMultimap.create();
+public static final Multimap<UUID, TickingSkill> tickingSkills = 
+    Multimaps.synchronizedMultimap(ArrayListMultimap.create());

Or:

+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
-public static final Multimap<UUID, TickingSkill> tickingSkills = ArrayListMultimap.create();
+private static final Map<UUID, Collection<TickingSkill>> tickingSkills = new ConcurrentHashMap<>();

Don't forget to add the import:

import com.google.common.collect.Multimaps;

246-248: Fix type mismatch in removeIf predicate—released skills are not removed.

At line 247, the code compares tickingSkill.getSkill() with skill.getSkill() using ==. Based on the context and the TickingSkill class (which likely stores a ResourceLocation), tickingSkill.getSkill() returns a ResourceLocation, while skill.getSkill() returns a ManasSkill object. This type mismatch means the equality check will always be false, causing released skills to leak in tickingSkills.

Apply this diff to compare by skill ID:

             UUID ownerID = getOwner().getUUID();
             if (tickingSkills.containsKey(ownerID))
-                tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.getSkill() == skill.getSkill());
+                tickingSkills.get(ownerID).removeIf(tickingSkill -> 
+                    tickingSkill.getSkill().equals(skill.getSkillId()));
🧹 Nitpick comments (4)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (2)

39-67: Consider documenting null safety and the Changeable pattern.

The functional interfaces follow a generally good pattern, but consider these improvements:

  1. Inconsistent return type: ProjectileHitEvent returns void but provides Changeable parameters. Most other void-return events (like LivingTickEvent) don't allow modifications. Consider returning EventResult if the event is meant to be cancellable.

  2. Missing null safety documentation: Document whether parameters like Entity source in LivingEffectAddedEvent can be null, and whether Changeable targets can be set to null.

  3. Document the Changeable pattern: Add JavaDoc explaining that handlers should use Changeable.set() to modify values and that the original value is preserved for reference.

Example documentation:

/**
 * Called when a projectile hits something.
 * Handlers can modify the deflection and hit result behavior.
 * 
 * @param hitResult The hit result (never null)
 * @param projectile The projectile entity (never null)
 * @param deflection Changeable deflection behavior (never null, value may be null)
 * @param result Changeable hit result (never null, defaults to DEFAULT)
 */
@FunctionalInterface
public interface ProjectileHitEvent {
    void hit(HitResult hitResult, Projectile projectile, Changeable<ProjectileDeflection> deflection, Changeable<ProjectileHitResult> result);
}

69-74: Convert inline comments to JavaDoc for better IDE support.

The enum values have helpful inline comments, but converting them to JavaDoc would provide better IDE integration and documentation generation.

     public enum ProjectileHitResult {
-        DEFAULT, // Hit, damage + possibly continue
-        HIT, // Hit + damage
-        HIT_NO_DAMAGE, // Hit
-        PASS // Pass through
+        /** Hit with damage, and possibly continue through the target. */
+        DEFAULT,
+        /** Hit with damage and stop. */
+        HIT,
+        /** Hit without dealing damage. */
+        HIT_NO_DAMAGE,
+        /** Pass through without hitting. */
+        PASS
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1)

26-112: Consider adding error handling for skill event interactions.

The event handlers invoke skill instance methods (e.g., onEffectAdded, onBeingTargeted, onDamageEntity) without catching potential exceptions. If a skill's implementation throws an unexpected exception, it could interrupt the entire event processing chain and affect other skills or game systems.

While adding try-catch blocks to every handler would add verbosity, consider wrapping the skill interaction calls in a helper method that logs and suppresses exceptions, or document that skill implementations must not throw unchecked exceptions. For example:

private static boolean safeSkillInteraction(ManasSkillInstance instance, 
                                           Supplier<Boolean> interaction, 
                                           String eventName) {
    try {
        return interaction.get();
    } catch (Exception e) {
        log.error("Error in skill " + instance.getSkillId() + " during " + eventName, e);
        return true; // or false, depending on desired behavior
    }
}

Then use it in handlers:

if (!safeSkillInteraction(instance, 
    () -> instance.onEffectAdded(entity, source, changeableTarget),
    "EFFECT_ADDED")) {
    return EventResult.interruptFalse();
}
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

43-54: Remove redundant null check
The if (owner == null) check at line 46 can be dropped—changeableTarget.isPresent() already guarantees get() returns non-null.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9cc4696 and 3ec0700.

📒 Files selected for processing (7)
  • gradle.properties (2 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.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/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)
🚧 Files skipped from review as they are similar to previous changes (1)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java
🧰 Additional context used
🧬 Code graph analysis (5)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (5)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java (1)
  • ManasRace (59-435)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)
  • ManasRaceInstance (43-497)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (2)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (5)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-699)
🔇 Additional comments (15)
gradle.properties (2)

21-21: Verify NeoForge 21.1.209 compatibility.

We’ve confirmed that NeoForge 21.1.209 exists in the Maven repository. Review the 21.1.209 release notes for any breaking changes affecting the newly added modules.


10-10: Verify mod_version downgrade

gradle.properties:10 – The project version (mod_version) was changed from 4.0.0.0 to 3.0.2.5. Downgrading the version when adding new features is unexpected. Confirm this downgrade is intentional or update to the correct version.

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

1-20: LGTM! Clean imports and package structure.

The package declaration and imports are well-organized. All imported classes are utilized within the file.

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

145-152: LGTM: Null handling in fromNBT has been addressed.

The null check for skill lookup at line 148 properly handles the case when a skill is not found in the registry by throwing an IllegalArgumentException with a descriptive message.


180-183: LGTM: The equals/hashCode contract is now consistent.

Both methods now use skillId and registryKey, ensuring that equal objects have the same hash code.

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

169-169: LGTM: skillInstances uses a thread-safe ConcurrentHashMap.

The use of ConcurrentHashMap for skillInstances ensures thread-safe access and modification, which is appropriate given the potential for concurrent event processing.


268-274: Good: Error handling in load method prevents corruption from invalid NBT data.

The try-catch block around ManasSkillInstance.fromNBT at line 269 ensures that a single malformed skill instance won't prevent the entire storage from loading. This is a robust approach to handling corrupted or outdated NBT data.

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (8)

1-26: LGTM!

Package structure and imports are appropriate for the registry and event wiring implementation.


32-41: LGTM!

The LIVING_EFFECT_ADDED handler correctly retrieves the race instance, validates activation eligibility, and delegates to the race's effect hook with proper pass/interrupt semantics.


56-65: LGTM!

The SKILL_DAMAGE_POST_CALCULATION handler correctly identifies the attacking entity, retrieves its race, and delegates damage modification to the race's attack hook.


67-75: LGTM!

The LIVING_DAMAGE handler correctly retrieves the race of the damaged entity and delegates to the race's hurt hook with proper event flow.


77-85: LGTM!

The LIVING_DEATH handler correctly retrieves the race of the dying entity and delegates to the race's death hook with proper interrupt semantics.


87-100: Verify the conqueredEnd logic.

The handler skips attribute restoration, health reset, and spawn teleportation when conqueredEnd is true. This may be intentional (End portal return is not a "death" respawn), but ensure this aligns with the expected race behavior when a player returns from the End.

Additionally, the canActivateAbility check on line 98 gates the onRespawn hook but not the attribute/health restoration on lines 93-95. Verify if attribute restoration should also be gated by canActivateAbility.


103-104: LGTM!

The private constructor correctly enforces static-only usage of the registry class.


27-30: Approve registry key cast

The unchecked cast mirrors the established pattern in SkillRegistry and aligns with the registrar builder’s type guarantees, so no modifications are required.

Comment on lines 21 to 37
public class EntityEvents {
public static Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
public static Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();
public static Event<LivingEffectAddedEvent> LIVING_EFFECT_ADDED = EventFactory.createEventResult();
public static Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET = EventFactory.createEventResult();

public static Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
public static Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
public static Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
public static Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
public static Event<ProjectileHitEvent> PROJECTILE_HIT = EventFactory.createLoop();

public static Event<EntityEvent.LivingDeath> DEATH_EVENT_FIRST = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_HIGH = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_NORMAL = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LOW = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LAST = 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 | 🟠 Major

Add documentation to clarify event usage and ordering.

The event declarations lack JavaDoc comments explaining:

  • The execution order and purpose of similar events (e.g., LIVING_PRE_DAMAGED, LIVING_ON_BEING_DAMAGED, LIVING_HURT, LIVING_DAMAGE)
  • When to use each event type
  • The priority levels for death events

Additionally, there's an inconsistency: LIVING_PRE_DAMAGED and LIVING_ON_BEING_DAMAGED use EntityEvent.LivingHurt, while LIVING_HURT and LIVING_DAMAGE use custom interfaces. Consider using custom interfaces consistently for clarity and flexibility.

Example documentation:

+    /**
+     * Fired before damage calculation. Use {@link EntityEvent.LivingHurt}.
+     */
     public static Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
+    /**
+     * Fired during damage processing. Use {@link EntityEvent.LivingHurt}.
+     */
     public static Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
+    /**
+     * Fired when entity is hurt. Allows modification of damage amount via {@link Changeable}.
+     */
     public static Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
+    /**
+     * Fired when entity takes damage. Final chance to modify damage via {@link Changeable}.
+     */
     public static Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
📝 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 EntityEvents {
public static Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
public static Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();
public static Event<LivingEffectAddedEvent> LIVING_EFFECT_ADDED = EventFactory.createEventResult();
public static Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET = EventFactory.createEventResult();
public static Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
public static Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
public static Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
public static Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
public static Event<ProjectileHitEvent> PROJECTILE_HIT = EventFactory.createLoop();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_FIRST = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_HIGH = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_NORMAL = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LOW = EventFactory.createEventResult();
public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LAST = EventFactory.createEventResult();
/**
* Fired before damage calculation. Use {@link EntityEvent.LivingHurt}.
*/
public static Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
/**
* Fired during damage processing. Use {@link EntityEvent.LivingHurt}.
*/
public static Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
/**
* Fired when entity is hurt. Allows modification of damage amount via {@link Changeable}.
*/
public static Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
/**
* Fired when entity takes damage. Final chance to modify damage via {@link Changeable}.
*/
public static Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java
around lines 21 to 37, add JavaDoc comments above each Event field explaining
its purpose and execution ordering (e.g., pre -> on-being-damaged -> hurt ->
damage) and when to use each; document which events are loop vs result-type and
specify the intended priority/sequence for the death events (e.g., FIRST, HIGH,
NORMAL, LOW, LAST) so callers understand ordering; and make the event interface
types consistent by switching LIVING_PRE_DAMAGED and LIVING_ON_BEING_DAMAGED to
use the same custom LivingHurt-like interfaces as LIVING_HURT and LIVING_DAMAGE
(or conversely consolidate to a single custom interface for all hurt/damage
events), then update imports and any references accordingly.

Comment on lines +75 to +76
clone.subInstances = this.subInstances;
clone.parentSkill = this.parentSkill;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Create defensive copies of subInstances and parentSkill in the copy method.

The copy() method creates a defensive copy of cooldownList but directly assigns the subInstances map and parentSkill reference. This creates shared mutable state between the original instance and its copy, which could lead to unexpected behavior when one instance modifies these fields.

Apply this diff to create defensive copies:

     clone.cooldownList = new ArrayList<>(this.cooldownList);
     clone.removeTime = this.removeTime;
     clone.masteryPoint = this.masteryPoint;
     clone.toggled = this.toggled;
     if (this.tag != null) clone.tag = this.tag.copy();
-    clone.subInstances = this.subInstances;
+    clone.subInstances = new HashMap<>(this.subInstances);
     clone.parentSkill = this.parentSkill;
     return clone;

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

🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java
around lines 75-76, the copy() method assigns subInstances and parentSkill by
reference which shares mutable state; instead make defensive copies: create a
new Map and populate it with copies of each entry (e.g., iterate
this.subInstances and put(key, value.copy()) to avoid sharing nested
ManasSkillInstance objects), and set parentSkill to a copied instance (e.g.,
this.parentSkill == null ? null : this.parentSkill.copy()) rather than assigning
the original reference.

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 (13)
race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (1)

47-51: Component parameter is ignored in evolution call.

The component parameter at line 47 is accepted but not passed to setRace() at line 50. This prevents custom evolution messages from being displayed, as flagged in a previous review.

Apply this diff:

     default boolean evolveRace(@NotNull ResourceLocation raceId, @Nullable MutableComponent component) {
         ManasRace race = RaceAPI.getRaceRegistry().get(raceId);
         if (race == null) return false;
-        return setRace(race.createDefaultInstance(), true, false);
+        return setRace(race.createDefaultInstance(), true, false, component);
     }
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java (2)

34-35: Add clarifying comment for empty encode method.

The empty encode method could benefit from a comment explaining that this packet carries no data.

</review_comment_end -->


37-56: Add error handling for packet processing.

The handler lacks try-catch error handling. If onActivateAbility, checkAndMarkDirty, or race storage operations throw exceptions, they could propagate and affect the server.

Consider wrapping the logic in a try-catch block:

 public void handle(NetworkManager.PacketContext context) {
     if (context.getEnvironment() != Env.SERVER) return;
     context.queue(() -> {
         Player player = context.getPlayer();
         if(player == null) return;

+        try {
             Races storage = RaceAPI.getRaceFrom(player);
             Optional<ManasRaceInstance> optional = storage.getRace();
             if (optional.isEmpty()) return;

             ManasRaceInstance instance = optional.get();
             if (RaceEvents.ACTIVATE_ABILITY.invoker().activateAbility(instance, player).isFalse()) return;
             if (!instance.canActivateAbility(player)) return;
             if (instance.isOnCooldown()) return;

             instance.onActivateAbility(player);
             RaceStorage.tickingRaces.put(player.getUUID(), new TickingRace());
             storage.checkAndMarkDirty(instance);
+        } catch (Exception e) {
+            log.error("Failed to handle race ability activation for player {}", player.getName().getString(), e);
+        }
     });
 }

</review_comment_end -->

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (3)

118-118: Restrict system messages to Player entities only.

LivingEntity.sendSystemMessage may not behave as intended for non-player entities. Since race messages are user-facing, restrict them to players.

Apply this diff:

-        if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
+        if (raceMessage.isPresent() && getOwner() instanceof Player player) {
+            player.sendSystemMessage(raceMessage.get());
+        }

</review_comment_end -->


127-127: Fix potential NPE from auto-unboxing.

Changeable.get() returns @Nullable Boolean. Auto-unboxing teleport.get() in the condition will throw NPE if a listener sets it to null.

Apply this diff:

-        if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
+        if (Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player) {
+            SpawnPointHelper.teleportToNewSpawn(player);
+        }

</review_comment_end -->


138-142: Add error handling and attribute reapplication in load.

ManasRaceInstance.fromNBT can throw exceptions on malformed data, and loaded race attributes are not reapplied to the entity, potentially leaving the entity with stale stats.

Apply this diff:

 @Override
 public void load(CompoundTag data) {
     if (!data.contains(RACE_KEY)) return;
-    this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+    try {
+        ManasRaceInstance loaded = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+        this.raceInstance = loaded;
+        LivingEntity owner = this.getOwner();
+        if (!owner.level().isClientSide()) {
+            loaded.addAttributeModifiers(owner);
+        }
+    } catch (Exception e) {
+        log.error("Failed to load race for entity {}. Clearing race. Data: {}", this.getOwner(), data, e);
+        this.raceInstance = null;
+    }
 }

</review_comment_end -->

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

127-137: Past NPE concern is not valid for modern Java.

The previously flagged null pointer concern on line 129 is not an issue. The instanceof pattern matching (Java 14+) handles null correctly—if hitResult.getEntity() returns null, the instanceof check returns false without throwing an NPE.

Verify that the project targets Java 14 or later:

#!/bin/bash
# Check Gradle build files for Java version configuration
rg -n "sourceCompatibility|targetCompatibility|javaVersion|JavaVersion" --type gradle

23-23: Unsafe cast indicates type system issue.

The unchecked cast to ResourceKey<Registry<ManasSkill>> suggests the builder on line 22 may not be producing the expected type. This is related to the previously flagged builder syntax issue.

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

45-45: Duplicate: tickingSkills still lacks thread-safety.

The static tickingSkills multimap is accessed across event hooks (lines 62, 84-91, 161-170, 250-251) without synchronization. If any event runs off the main thread or if concurrent player actions occur, data races may arise. Use Multimaps.synchronizedMultimap() or a thread-safe alternative.


250-251: Duplicate: Type mismatch prevents ticking skill removal.

Comparing tickingSkill.getSkill() (a ManasSkill) to skill.getSkill() (also a ManasSkill) using == reference equality will fail to match unless they're the exact same object instance. If the skill instances are different objects representing the same skill type, ticking entries will leak.

Apply this diff:

             UUID ownerID = getOwner().getUUID();
             if (tickingSkills.containsKey(ownerID))
-                tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.getSkill() == skill.getSkill());
+                tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.getSkill().equals(skill.getSkill()));
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (2)

62-62: Duplicate: Unsynchronized access to tickingSkills.

Directly calling tickingSkills.put() without synchronization can cause concurrent modification issues if multiple threads access this map. This is the same concurrency concern flagged for line 45 in SkillStorage.


26-28: Validate keyNumber to prevent misuse.

The keyNumber field is read from the network without validation. Negative values or extremely large values could cause unexpected behavior in downstream code.

Add validation in the handle method:

             if (mode < 0 || mode >= skill.getModes()) return;
+            if (keyNumber < 0 || keyNumber > 10) return; // Adjust max as needed
             if (skill.onCoolDown(mode) && !skill.canIgnoreCoolDown(player, mode)) return;
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (1)

62-68: Duplicate: NPE risk from auto-unboxing null Integer.

Lines 64 and 67 call modeChangeable.get() which returns Integer. If the event listener sets it to null, auto-unboxing to int will throw NPE. Add a null check before using the mode value.

Apply this diff:

             ManasSkillInstance skill = skillChangeable.get();
-            if (skill == null || deltaChangeable.isEmpty()) return;
-            if (!skill.canScroll(player, modeChangeable.get())) return;
+            if (skill == null || deltaChangeable.isEmpty() || modeChangeable.isEmpty()) return;
+            Integer mode = modeChangeable.get();
+            if (mode == null || mode < 0) return;
+            if (!skill.canScroll(player, mode)) return;
             if (!skill.canInteractSkill(player)) return;

-            skill.onScroll(player, deltaChangeable.get(), modeChangeable.get());
+            skill.onScroll(player, deltaChangeable.get(), mode);
             storage.checkAndMarkDirty(skill);
🧹 Nitpick comments (4)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

44-60: Redundant null check after Optional.isPresent().

Lines 46-47 check if (owner == null) immediately after changeableTarget.get(), but changeableTarget.isPresent() at line 45 already guarantees a non-null value. This check is redundant and can be removed.

Apply this diff:

         EntityEvents.LIVING_CHANGE_TARGET.register((entity, changeableTarget) -> {
             if (!changeableTarget.isPresent()) return EventResult.pass();
             LivingEntity owner = changeableTarget.get();
-            if (owner == null) return EventResult.pass();
 
             Races storage = RaceAPI.getRaceFrom(entity);
race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (1)

8-12: Inconsistent null annotation libraries.

The file mixes Lombok's @NonNull (line 8) with JetBrains' @NotNull (line 11). While functionally similar, mixing annotation libraries can cause confusion and inconsistent behavior across tools.

Standardize on one library. Given that Lombok is already a project dependency, consider using @NonNull consistently:

 import lombok.NonNull;
 import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.resources.ResourceLocation;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;

Then update all @NotNull usages to @NonNull:

-    default boolean setRace(@NotNull ResourceLocation raceId, boolean teleportToSpawn) {
+    default boolean setRace(@NonNull ResourceLocation raceId, boolean teleportToSpawn) {

Also applies to lines 23, 43, 47.

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

40-40: Rename variable for clarity.

The variable owner is misleading—it represents the new target entity, not an owner. Rename to newTarget or target to improve readability.

Apply this diff:

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

             Skills storage = SkillAPI.getSkillsFrom(entity);
             for (ManasSkillInstance instance : storage.getLearnedSkills()) {
-                if (!instance.canInteractSkill(owner)) continue;
+                if (!instance.canInteractSkill(newTarget)) continue;
                 if (!instance.onBeingTargeted(changeableTarget, entity)) {
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

267-269: Document resetExistingData flag lifecycle

Explain in SkillStorage (e.g. in Javadoc or inline comments on load/saveOutdated) that the "resetExistingData" flag is only set by saveOutdated when migrating removed skills and is omitted by the normal save (fresh CompoundTag), so it only triggers a single clear.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec0700 and d00bfbd.

📒 Files selected for processing (12)
  • gradle.properties (2 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/network/c2s/RequestRaceAbilityActivationPacket.java (1 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityReleasePacket.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/network/c2s/RequestSkillActivationPacket.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)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradle.properties
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityReleasePacket.java
🧰 Additional context used
🧬 Code graph analysis (10)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (2)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1)
  • ManasCoreSkill (16-27)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-699)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java (3)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-699)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java (3)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)
  • ManasRaceInstance (43-497)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/TickingRace.java (1)
  • TickingRace (18-40)
race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java (1)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java (3)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-699)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)
  • ManasRaceInstance (43-497)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (4)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-699)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (1)
  • TickingSkill (19-47)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java (1)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (3)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
🔇 Additional comments (12)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

30-120: Consistent event handler pattern with proper state management.

The event handlers follow a clean, consistent pattern:

  1. Retrieve race storage from entity
  2. Early return if no race is present
  3. Check ability activation eligibility
  4. Invoke race instance callbacks
  5. Mark storage dirty when state changes

This design properly integrates with the race lifecycle and ensures state synchronization across client and server.

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

23-27: Proper null handling for registry lookups.

The addition of the null check at line 25 addresses the potential NullPointerException from failed registry lookups that was flagged in previous reviews. The method now safely returns false when the race ID is not found.


71-73: Efficient dirty-checking helper method.

The checkAndMarkDirty() helper cleanly encapsulates the conditional dirty-marking pattern used throughout the event handlers in RaceRegistry.java, improving code reuse and readability.

race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java (1)

48-50: Good addition of cooldown check.

The cooldown validation (Line 50) prevents activating abilities during cooldown periods, improving the activation flow.

</review_comment_end -->

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (2)

48-72: Improved dirty-marking strategy.

The refactored approach eliminates per-tick markDirty() calls for all entities (Lines 48-54) and instead marks dirty conditionally in tickRace (Line 89) and for held abilities (Line 71). This reduces unnecessary synchronization overhead.

</review_comment_end -->


110-110: Good check to avoid redundant attribute operations.

The condition instance != null && instance != race (Line 110) prevents unnecessary attribute removal and re-application when setting the same race, addressing a past concern.

</review_comment_end -->

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

173-173: Thread-safe map is good, but verify read-side visibility.

Using ConcurrentHashMap for skillInstances is appropriate for multi-threaded modifications. However, ensure that all reads (lines 180-182, 208-209, etc.) see the latest writes without additional synchronization, as ConcurrentHashMap guarantees happens-before relationships for put/get operations.


224-224: Incorrect removal from collection.

Line 224 calls this.getLearnedSkills().remove(instance) on the values collection returned by skillInstances.values(). Collections.values() returns a view that doesn't support removal by value; it only supports iterator-based removal. This will likely throw UnsupportedOperationException.

Apply this diff:

-        this.getLearnedSkills().remove(instance);
+        this.skillInstances.remove(instance.getSkillId());

Likely an incorrect or invalid review comment.

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

29-35: Documentation for sync parameter is now present.

The documentation added at lines 29-34 clearly explains the sync parameter's behavior. This addresses the previous review concern.

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

37-61: Toggle logic looks correct.

The packet handler properly:

  • Validates server-side execution (line 38)
  • Checks for null player (line 41)
  • Uses event system for interception (line 45)
  • Validates skill interaction permission (line 49)
  • Toggles state and invokes lifecycle callbacks (lines 51-57)
  • Marks storage dirty (line 58)

The previous review concerns about list size validation don't apply since this now handles a single skill.

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

57-57: Mode validation is present.

Line 57 validates that mode is within bounds [0, skill.getModes()), addressing the previous review concern about mode validation.

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

36-40: Size validation is present.

The validateList method properly limits the map to 100 entries, preventing DoS attacks via excessive skill updates.

Comment on lines +37 to +43
default boolean learnSkill(@NotNull ResourceLocation skillId) {
return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance());
}

default boolean learnSkill(@NotNull ResourceLocation skillId, MutableComponent component) {
return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance(), component);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential NPE if skill not found in registry.

Lines 38 and 42 call SkillAPI.getSkillRegistry().get(skillId), which may return null if the skill ID is not registered. Calling createDefaultInstance() on a null will throw NPE.

Add null checks or document that callers must ensure valid skill IDs:

     default boolean learnSkill(@NotNull ResourceLocation skillId) {
+        ManasSkill skill = SkillAPI.getSkillRegistry().get(skillId);
+        if (skill == null) return false;
-        return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance());
+        return learnSkill(skill.createDefaultInstance());
     }

     default boolean learnSkill(@NotNull ResourceLocation skillId, MutableComponent component) {
+        ManasSkill skill = SkillAPI.getSkillRegistry().get(skillId);
+        if (skill == null) return false;
-        return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance(), component);
+        return learnSkill(skill.createDefaultInstance(), component);
     }
📝 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
default boolean learnSkill(@NotNull ResourceLocation skillId) {
return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance());
}
default boolean learnSkill(@NotNull ResourceLocation skillId, MutableComponent component) {
return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance(), component);
}
default boolean learnSkill(@NotNull ResourceLocation skillId) {
ManasSkill skill = SkillAPI.getSkillRegistry().get(skillId);
if (skill == null) return false;
return learnSkill(skill.createDefaultInstance());
}
default boolean learnSkill(@NotNull ResourceLocation skillId, MutableComponent component) {
ManasSkill skill = SkillAPI.getSkillRegistry().get(skillId);
if (skill == null) return false;
return learnSkill(skill.createDefaultInstance(), component);
}
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java
around lines 37 to 43, the calls to
SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance() can NPE if the
registry returns null; update both default methods to first fetch the skill
entry into a local variable, check for null, and handle it consistently (e.g.,
return false or throw an IllegalArgumentException with a clear message) before
calling createDefaultInstance(); ensure both overloads use the same
null-handling behavior and include a clear log or exception message mentioning
the missing skillId.

Comment on lines +66 to +78
SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();

Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onDamageEntity(owner, target, source, amount)) {
storage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else storage.checkAndMarkDirty(instance);
}
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.

⚠️ Potential issue | 🔴 Critical

Critical: Wrong storage object used for dirty tracking.

Lines 73 and 75 call storage.checkAndMarkDirty(instance), but instance belongs to ownerStorage, not storage. This causes the target's skill storage to be marked dirty instead of the owner's storage.

Apply this diff to fix the issue:

             Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
             for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
                 if (!instance.canInteractSkill(owner)) continue;
                 if (!instance.onDamageEntity(owner, target, source, amount)) {
-                    storage.checkAndMarkDirty(instance);
+                    ownerStorage.checkAndMarkDirty(instance);
                     return EventResult.interruptFalse();
-                } else storage.checkAndMarkDirty(instance);
+                } else ownerStorage.checkAndMarkDirty(instance);
             }
📝 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
SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();
Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onDamageEntity(owner, target, source, amount)) {
storage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else storage.checkAndMarkDirty(instance);
}
return EventResult.pass();
});
SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();
Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onDamageEntity(owner, target, source, amount)) {
ownerStorage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else ownerStorage.checkAndMarkDirty(instance);
}
return EventResult.pass();
});
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java
around lines 66 to 78, the code calls storage.checkAndMarkDirty(instance) when
iterating the owner's learned skills, which incorrectly marks the target's
SkillStorage dirty; change both calls to
ownerStorage.checkAndMarkDirty(instance) so the owner's Skills storage is
updated instead, preserving the existing control flow (i.e., call
ownerStorage.checkAndMarkDirty(instance) before returning
EventResult.interruptFalse() and in the else branch).

Comment on lines +80 to +92
SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();

Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onTouchEntity(owner, target, source, amount)) {
storage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else storage.checkAndMarkDirty(instance);
}
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.

⚠️ Potential issue | 🔴 Critical

Critical: Wrong storage object used for dirty tracking.

Lines 87 and 89 have the same bug as the PRE_CALCULATION handler—storage.checkAndMarkDirty(instance) should use ownerStorage instead.

Apply this diff:

             Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
             for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
                 if (!instance.canInteractSkill(owner)) continue;
                 if (!instance.onTouchEntity(owner, target, source, amount)) {
-                    storage.checkAndMarkDirty(instance);
+                    ownerStorage.checkAndMarkDirty(instance);
                     return EventResult.interruptFalse();
-                } else storage.checkAndMarkDirty(instance);
+                } else ownerStorage.checkAndMarkDirty(instance);
             }
📝 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
SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();
Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onTouchEntity(owner, target, source, amount)) {
storage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else storage.checkAndMarkDirty(instance);
}
return EventResult.pass();
});
SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> {
if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass();
Skills ownerStorage = SkillAPI.getSkillsFrom(owner);
for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) {
if (!instance.canInteractSkill(owner)) continue;
if (!instance.onTouchEntity(owner, target, source, amount)) {
ownerStorage.checkAndMarkDirty(instance);
return EventResult.interruptFalse();
} else ownerStorage.checkAndMarkDirty(instance);
}
return EventResult.pass();
});
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java
around lines 80 to 92, the dirty-tracking calls use the wrong skills storage
variable: replace both occurrences of storage.checkAndMarkDirty(instance) with
ownerStorage.checkAndMarkDirty(instance) so the owner's Skills container is
marked dirty after the instance is modified; keep the surrounding control
flow/returns unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1)

1-92: Consider adding Javadoc for this public API.

This is a public API interface without any documentation. Consider adding Javadoc comments to describe:

  • When each event is fired in the skill lifecycle
  • The semantics of Changeable parameters (which values can be modified by handlers)
  • Thread safety and side (client/server/both) for each event
  • Return value semantics for EventResult

Example documentation structure:

/**
 * Central event hub for the Manas skill system.
 * <p>
 * Events are fired at various points in the skill lifecycle to allow mods
 * to observe and modify skill behavior.
 */
public interface SkillEvents {
    /**
     * Fired after the skill system has initialized.
     * This is a loop event (all handlers are invoked).
     */
    Event<Runnable> POST_INIT = EventFactory.createLoop();
    
    /**
     * Fired when a skill is being unlocked for a player.
     * Handlers can modify the unlock message or cancel the unlock.
     * 
     * @see UnlockSkillEvent
     */
    Event<UnlockSkillEvent> UNLOCK_SKILL = EventFactory.createEventResult();
    
    // ... document remaining events
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d00bfbd and 9bc00ca.

📒 Files selected for processing (4)
  • 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/RaceEvents.java (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/api/SkillEvents.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java
🧰 Additional context used
🧬 Code graph analysis (3)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (1)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)
  • RaceRegistry (24-125)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java (1)
  • ManasRaceNetwork (13-22)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java (1)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
🔇 Additional comments (3)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java (3)

16-31: LGTM! Event factory usage is consistent with functional interface signatures.

The event declarations correctly use createLoop() for void-returning handlers (POST_INIT, SKILL_POST_TICK) and createEventResult() for EventResult-returning handlers. The naming is clear and follows conventions.


89-92: Keep Skills storage parameter in SkillDamageCalculationEvent
All invocations of calculate(…) consistently pass a Skills instance, indicating handlers expect access to the full storage rather than a single skill.


59-67: No change needed: client-side delta remains immutable. The client scroll event only filters scroll actions and handles mode changes; mutable delta adjustments happen server-side via Changeable<Double>.

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

♻️ Duplicate comments (9)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (5)

119-119: Only send system messages to Player entities.

sendSystemMessage is available on LivingEntity but is primarily intended for players. Non-player entities (mobs) don't display chat messages, so this call will be ineffective for them and could cause issues depending on the implementation.

Apply this diff to guard the message sending:

-        if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
+        if (raceMessage.isPresent() && owner instanceof Player player) {
+            player.sendSystemMessage(raceMessage.get());
+        }

121-121: Consider guarding attribute addition when race hasn't changed.

If setRace is called with the same race instance that's already active (i.e., instance == race), line 111 correctly skips removal, but line 121 still calls addAttributeModifiers. This could lead to duplicate modifiers if the same race is set multiple times.

Consider adding a check to skip attribute reapplication when the race hasn't changed:

         if (raceMessage.isPresent() && owner instanceof Player player) {
             player.sendSystemMessage(raceMessage.get());
         }
         race.markDirty();
-        race.addAttributeModifiers(owner);
+        if (instance != race) {
+            race.addAttributeModifiers(owner);
+        }
         owner.setHealth(owner.getMaxHealth());
         race.onRaceSet(owner);

Alternatively, ensure that addAttributeModifiers is idempotent or that callers never invoke setRace with the same race instance.


74-91: Wrap race ticking in error handling to prevent server crashes.

If instance.onTick(entity) (line 88) throws an exception, it could crash the server's tick loop. Race implementations from third-party mods or custom content might have bugs that shouldn't halt the entire server.

Apply this diff to add error handling:

     private static void tickRace(LivingEntity entity, Races storage) {
         Optional<ManasRaceInstance> optional = storage.getRace();
         if (optional.isEmpty()) return;
 
         ManasRaceInstance instance = optional.get();
         if (instance.isOnCooldown()) {
             if (!RaceEvents.RACE_UPDATE_COOLDOWN.invoker().cooldown(instance, entity, instance.getCooldown()).isFalse())
                 instance.setCooldown(instance.getCooldown() - 1);
             storage.checkAndMarkDirty(instance);
         }
 
         if (!instance.canActivateAbility(entity)) return;
         if (!instance.canTick(entity)) return;
         if (RaceEvents.RACE_PRE_TICK.invoker().tick(instance, entity).isFalse()) return;
-        instance.onTick(entity);
-        RaceEvents.RACE_POST_TICK.invoker().tick(instance, entity);
-        storage.checkAndMarkDirty(instance);
+        try {
+            instance.onTick(entity);
+            RaceEvents.RACE_POST_TICK.invoker().tick(instance, entity);
+            storage.checkAndMarkDirty(instance);
+        } catch (Exception e) {
+            log.error("Error ticking race {} for entity {}: {}", instance.getRaceId(), entity, e.getMessage(), e);
+        }
     }

128-128: Fix potential NPE when reading Changeable teleport.

Changeable.get() can return null (based on the Changeable implementation provided in context). Auto-unboxing teleport.get() in the boolean condition will throw a NullPointerException if a listener sets it to null.

Apply this diff to use null-safe comparison:

-        if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
+        if (Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player) {
+            SpawnPointHelper.teleportToNewSpawn(player);
+        }

139-143: Add error handling and attribute reapplication after loading.

Two issues with the current implementation:

  1. ManasRaceInstance.fromNBT can throw a NullPointerException if the race registry lookup fails (e.g., unknown race ID, mod removed). This would crash chunk loading.
  2. After setting this.raceInstance, attribute modifiers are not reapplied. When entities are loaded from NBT (chunk load, dimension change, server restart), they will have the race instance but miss the attribute modifiers, leading to incorrect stats until the next race change.

Apply this diff to add error handling and attribute reapplication:

     @Override
     public void load(CompoundTag data) {
         if (!data.contains(RACE_KEY)) return;
-        this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+        try {
+            ManasRaceInstance loaded = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+            this.raceInstance = loaded;
+            LivingEntity owner = this.getOwner();
+            if (!owner.level().isClientSide()) {
+                loaded.addAttributeModifiers(owner);
+            }
+        } catch (Exception e) {
+            log.error("Failed to load race instance for entity {}. Clearing race. Data: {}", this.getOwner(), data, e);
+            this.raceInstance = null;
+        }
     }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (3)

348-350: Mark dirty and defend against external mutation

Line [349]: assigning the caller’s list directly creates aliasing and never flags the state change for sync, so later mutations can slip through undetected. Please copy the contents and call markDirty().

-        this.cooldownList = list;
+        this.cooldownList = new ArrayList<>(list);
+        markDirty();

145-148: Validate skillLocation before registry lookup

Line [146]: ResourceLocation.tryParse can return null for malformed data. Passing that through to the registry and later calling skill.createDefaultInstance() will still throw. Guard the parsed ID and fail fast with a descriptive exception before dereferencing it.

-        ResourceLocation skillLocation = ResourceLocation.tryParse(tag.getString("skill"));
+        ResourceLocation skillLocation = ResourceLocation.tryParse(tag.getString("skill"));
+        if (skillLocation == null) {
+            throw new IllegalArgumentException("Invalid skill ID in NBT data.");
+        }

67-77: Deep-copy sub-instances in copy()

Line [75]: assigning clone.subInstances = this.subInstances; makes the clone share mutable children with the original. Mutating a sub-skill through either instance will silently mutate the other copy. Please allocate a fresh map and clone each child instance before storing it.

-        clone.subInstances = this.subInstances;
+        clone.subInstances = new HashMap<>();
+        for (Map.Entry<ManasSkill, ManasSkillInstance> entry : this.subInstances.entrySet()) {
+            clone.subInstances.put(entry.getKey(), entry.getValue().copy());
+        }
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

45-45: This concern was already flagged in a previous review and remains unaddressed.

🧹 Nitpick comments (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1)

40-40: Consider thread safety for the public static Multimap.

tickingRaces is a public static mutable Multimap that is accessed and modified from multiple tick handlers. While Guava's ArrayListMultimap is not thread-safe by default, concurrent access from different entities ticking simultaneously could lead to ConcurrentModificationException or data races in multi-threaded server environments.

Consider using a thread-safe alternative like Multimaps.synchronizedMultimap() or document that access must be externally synchronized.

-    public static final Multimap<UUID, TickingRace> tickingRaces = ArrayListMultimap.create();
+    public static final Multimap<UUID, TickingRace> tickingRaces = Multimaps.synchronizedMultimap(ArrayListMultimap.create());

Note: You'll need to add the import:

import com.google.common.collect.Multimaps;
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

109-127: Consider removing redundant getSkill lookup.

Line 112 fetches storage.getSkill(instance.getSkill()) when instance was already obtained from storage.getLearnedSkills() at line 111. This adds unnecessary overhead in the hot path (called every 5 seconds per entity).

Apply this diff to simplify:

 private static void tickSkills(LivingEntity entity, Skills storage) {
     List<ManasSkillInstance> tickingSkills = new ArrayList<>();
     for (ManasSkillInstance instance : storage.getLearnedSkills()) {
-        Optional<ManasSkillInstance> optional = storage.getSkill(instance.getSkill());
-        if (optional.isEmpty()) continue;
-
-        ManasSkillInstance skillInstance = optional.get();
-        if (!skillInstance.canInteractSkill(entity)) continue;
-        if (!skillInstance.canTick(entity)) continue;
-        if (SkillEvents.SKILL_PRE_TICK.invoker().tick(skillInstance, entity).isFalse()) continue;
-        tickingSkills.add(skillInstance);
+        if (!instance.canInteractSkill(entity)) continue;
+        if (!instance.canTick(entity)) continue;
+        if (SkillEvents.SKILL_PRE_TICK.invoker().tick(instance, entity).isFalse()) continue;
+        tickingSkills.add(instance);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bc00ca and 2fd1983.

📒 Files selected for processing (9)
  • gradle.properties (2 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/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/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/network/c2s/RequestSkillActivationPacket.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradle.properties
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java
🧰 Additional context used
🧬 Code graph analysis (7)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (2)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java (4)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-709)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (1)
  • TickingSkill (21-85)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (5)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-709)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (2)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java (1)
  • InternalSkillPacketActions (18-54)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1)
  • SkillRegistry (20-142)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java (2)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java (1)
  • ManasSkill (56-535)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-709)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java (1)
storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java (1)
  • StorageManager (28-142)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)
  • Log4j2 (39-300)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java (1)
  • ManasRaceInstance (43-497)
🔇 Additional comments (8)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (5)

43-57: LGTM!

The initialization logic correctly registers the storage, sets up tick handlers with appropriate throttling (INSTANCE_UPDATE cadence), and ensures cleanup on player quit. The event registration follows the module's patterns.


59-66: LGTM!

The throttling logic correctly limits race ticking to once per second (every 20 ticks), reducing computational overhead. The null-check and early returns are appropriate.


99-101: LGTM!

Clean and safe implementation using Optional to handle the nullable race instance.


133-137: LGTM!

The save logic correctly handles the null case and delegates serialization to the race instance.


145-147: LGTM!

The protected helper correctly casts the holder to LivingEntity, which is safe given the registration predicate in init() ensures only LivingEntity instances use this storage.

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

48-93: LGTM: Event handler setup is well-structured.

The event handler chains are properly organized with appropriate callbacks. The PLAYER_QUIT cleanup (lines 82-92) correctly removes attribute modifiers before clearing ticking skills, preventing modifier leaks.


254-295: LGTM: Persistence implementation is well-designed.

The persistence logic correctly handles:

  • Full saves (lines 255-262)
  • Incremental loads with reset capability (lines 265-278)
  • Optimized dirty-tracking saves (lines 281-295) that distinguish between skill removals (requiring full reset) and updates (saving only dirty instances)

The error handling at lines 274-276 prevents corruption from individual skill load failures.


212-227: Critical bug: Cannot remove from Collection.values().

Line 224 attempts this.getLearnedSkills().remove(instance), but getLearnedSkills() returns skillInstances.values() (line 181), which is a Collection view that does not support removal operations. This will throw UnsupportedOperationException at runtime.

Apply this diff to fix:

     if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
     instance.onForgetSkill(this.getOwner());
     instance.markDirty();

-    this.getLearnedSkills().remove(instance);
+    this.skillInstances.remove(skillId);
     this.hasRemovedSkills = true;
     markDirty();

Likely an incorrect or invalid review comment.

Comment on lines +68 to +72
private static void handleRaceHeldTick(Player player, Races storage) {
if (!tickingRaces.containsKey(player.getUUID())) return;
tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, 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 | 🟠 Major

Only mark dirty when races are actually removed.

Calling markDirty() every tick when tickingRaces contains the player's UUID causes unnecessary network synchronization, even when no races were removed. This generates excessive sync traffic.

Apply this diff to only mark dirty when the collection is modified:

     private static void handleRaceHeldTick(Player player, Races storage) {
         if (!tickingRaces.containsKey(player.getUUID())) return;
-        tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, player));
-        storage.markDirty();
+        boolean removed = tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, player));
+        if (removed) storage.markDirty();
     }
🤖 Prompt for AI Agents
In
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java
around lines 68 to 72, the method always calls storage.markDirty() every tick,
causing unnecessary syncs; change it to only markDirty when the collection was
actually modified by storing the set/list from
tickingRaces.get(player.getUUID()), call removeIf(...) and capture its boolean
result, then call storage.markDirty() only if that result is true (i.e.,
elements were removed).

Comment on lines 234 to 252
public void handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean ignoreInteract) {
Changeable<ManasSkillInstance> changeable = Changeable.of(skillInstance);
if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return;
ManasSkillInstance skill = changeable.get();
if (skill == null) return;

if ((ignoreInteract || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) {
if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) {
skill.onRelease(getOwner(), heldTick, keyNumber, mode);
this.checkAndMarkDirty(skillInstance);
}
}

skill.removeAttributeModifiers(getOwner(), mode);
UUID ownerID = getOwner().getUUID();
if (tickingSkills.containsKey(ownerID))
tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode));
this.checkAndMarkDirty(skillInstance);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify TickingSkill.matches() implementation.

Line 250 uses tickingSkill.matches(skill.getSkill(), mode) to identify and remove the released skill from tickingSkills. Ensure the matches() method compares skill identifiers (ResourceLocation) using .equals() rather than object identity (==) to correctly match and remove entries.

A previous review flagged a type mismatch issue in this area. Please confirm the implementation avoids comparing incompatible types.

Run the following script to verify the matches method implementation:


🏁 Script executed:

#!/bin/bash
# Description: Verify TickingSkill.matches() implementation compares identifiers correctly.

# Search for TickingSkill class and its matches method
ast-grep --pattern $'class TickingSkill {
  $$$
  matches($_, $_) {
    $$$
  }
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Locate TickingSkill.java
file=$(fd -t f TickingSkill.java | head -n1)
echo "Found file: $file"

# Show class declaration
rg -nP 'class\s+TickingSkill' -n "$file"

# Show matches method implementation
rg -nP 'matches\s*\(' -C3 -n "$file"

Length of output: 1264


Use equals in TickingSkill.matches() instead of ==.

In TickingSkill.java (lines 55–61), both matches methods use this.skill == skill, which fails when different instances represent the same skill. Change these to compare by identifier (e.g. this.skill.equals(skill) or compare their registry names) to ensure reliable removal.

🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java
lines 234-252 and in
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java
lines 55-61: the TickingSkill.matches(...) methods currently use reference
equality (==) to compare skills which fails when different instances represent
the same skill; change those comparisons to use a semantic equality check (e.g.,
this.skill.equals(skill) or compare their unique identifier/registry name) and
make the comparison null-safe (check for nulls before calling equals) so removal
in SkillStorage.removeIf reliably matches equivalent skills. Ensure both
overloads of matches are updated accordingly.

@ManasMods ManasMods deleted a comment from coderabbitai bot Oct 31, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (3)

25-25: Use constant naming convention for registryId.

The variable registryId is a compile-time constant and should follow the UPPER_SNAKE_CASE naming convention.

Apply this diff:

-    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();
+    private static final ResourceLocation REGISTRY_ID = ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "races");
+    public static final Registrar<ManasRace> RACES = RegistrarManager.get(ModuleConstants.MOD_ID).<ManasRace>builder(REGISTRY_ID).syncToClients().build();

37-40: Eliminate duplicate checkAndMarkDirty calls.

The method checkAndMarkDirty is called in both the if and else branches. This pattern repeats in multiple handlers (lines 37-40, 55-58, 70-73, 84-87, 98-101).

Apply this pattern to simplify:

-            if (!instance.onEffectAdded(entity, source, changeableTarget)) {
-                storage.checkAndMarkDirty(instance);
-                return EventResult.interruptFalse();
-            } else storage.checkAndMarkDirty(instance);
+            boolean result = instance.onEffectAdded(entity, source, changeableTarget);
+            storage.checkAndMarkDirty(instance);
+            if (!result) return EventResult.interruptFalse();
             return EventResult.pass();

Apply similar changes to the other handlers at lines 55-58, 70-73, 84-87, and 98-101.


45-47: Remove redundant null check after Optional.isPresent().

Line 45 checks changeableTarget.isPresent(), which guarantees that changeableTarget.get() on line 46 will not return null (assuming correct Optional semantics). The null check on line 47 is redundant.

Apply this diff:

             if (!changeableTarget.isPresent()) return EventResult.pass();
             LivingEntity owner = changeableTarget.get();
-            if (owner == null) 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 be81457 and 93d1b64.

📒 Files selected for processing (2)
  • gradle.properties (2 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • gradle.properties
🧰 Additional context used
🧬 Code graph analysis (1)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (3)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
🔇 Additional comments (1)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

123-124: LGTM!

The private constructor correctly prevents instantiation of this utility class.

Comment on lines +111 to +119
if (!conqueredEnd) {
instance.addAttributeModifiers(newPlayer);
newPlayer.setHealth(newPlayer.getMaxHealth());
SpawnPointHelper.teleportToNewSpawn(newPlayer);
}

if (!instance.canActivateAbility(newPlayer)) return;
instance.onRespawn(newPlayer, conqueredEnd);
storage.checkAndMarkDirty(instance);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent state tracking in respawn handler.

Lines 112-114 modify entity state (addAttributeModifiers, setHealth, teleport) regardless of whether canActivateAbility returns true. However, checkAndMarkDirty at line 119 is only reached when canActivateAbility is true (line 117). This means state modifications may not be persisted if the ability cannot be activated.

Consider one of these approaches:

  • Move the canActivateAbility check before line 111 to gate all state modifications
  • Call checkAndMarkDirty after line 115 to ensure state changes are persisted

Example fix (option 2):

             ManasRaceInstance instance = optional.get();
             if (!conqueredEnd) {
                 instance.addAttributeModifiers(newPlayer);
                 newPlayer.setHealth(newPlayer.getMaxHealth());
                 SpawnPointHelper.teleportToNewSpawn(newPlayer);
+                storage.checkAndMarkDirty(instance);
             }
 
             if (!instance.canActivateAbility(newPlayer)) return;
             instance.onRespawn(newPlayer, conqueredEnd);
             storage.checkAndMarkDirty(instance);

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

🤖 Prompt for AI Agents
In
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java
around lines 111 to 119, the respawn handler updates the player state
(addAttributeModifiers, setHealth, teleport) even when
instance.canActivateAbility(newPlayer) is false, but
storage.checkAndMarkDirty(instance) only runs when the ability can be activated;
move the canActivateAbility check to before line 111 to gate the state
modifications, or (preferred minimal change) add a call to
storage.checkAndMarkDirty(instance) immediately after line 115 so any state
changes are persisted regardless of the ability check.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

105-120: Inconsistent state tracking in respawn handler.

The state modifications at lines 112-114 (addAttributeModifiers, setHealth, teleportToNewSpawn) occur regardless of whether canActivateAbility returns true. However, checkAndMarkDirty at line 119 only executes when the ability can be activated (line 117), meaning state changes may not be persisted if the ability check fails.

Consider moving the canActivateAbility check before line 111 to gate all state modifications, or add a call to storage.checkAndMarkDirty(instance) after line 115 to ensure state changes are persisted.

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

77-83: Fix duplicate AttributeModifier IDs causing collision.

Both attribute modifiers use the same ResourceLocation ID "test_critical_chance", which will cause them to collide. Each modifier must have a unique ID.

Apply this diff to fix the duplicate IDs:

     public static final RegistrySupplier<BlockItem> TEST_BLOCK_ITEM = ITEMS.register("test_block_item",
             () -> new BlockItem(RegistryTest.TEST_BLOCK.get(), new Item.Properties().arch$tab(TESTING_TAB).stacksTo(42)
                     .attributes(ItemAttributeModifiers.builder()
-                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
+                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_attr_player"),
                                     69, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
-                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"),
+                            .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_attr_all"),
                                     420, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND).build())));

177-192: Cap explosion radius to prevent excessive destruction during testing.

Line 183 creates explosions with radius 10-12 blocks, which is extreme even for testing (TNT is 4, charged creeper is 6). This could cause performance issues and unintended map destruction.

Apply this diff to use a more reasonable radius:

         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;
+                    float g = 3.0F + entity.getRandom().nextFloat() * 1.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);
                 }
             }
         }
🧹 Nitpick comments (3)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

29-103: Consider refactoring to reduce code duplication.

The five event handlers (LIVING_EFFECT_ADDED, LIVING_CHANGE_TARGET, SKILL_DAMAGE_POST_CALCULATION, LIVING_DAMAGE, LIVING_DEATH) follow nearly identical patterns. Additionally, each handler calls storage.checkAndMarkDirty(instance) in both the if and else branches, which can be simplified.

Consider extracting a common helper method and simplifying the conditional logic. For example:

private static EventResult handleRaceEvent(LivingEntity entity, 
                                          java.util.function.Predicate<ManasRaceInstance> handler) {
    Races storage = RaceAPI.getRaceFrom(entity);
    Optional<ManasRaceInstance> optional = storage.getRace();
    if (optional.isEmpty()) return EventResult.pass();
    
    ManasRaceInstance instance = optional.get();
    if (!instance.canActivateAbility(entity)) return EventResult.pass();
    
    boolean result = handler.test(instance);
    storage.checkAndMarkDirty(instance);
    return result ? EventResult.pass() : EventResult.interruptFalse();
}

Then each handler can be simplified:

EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> 
    handleRaceEvent(entity, instance -> instance.onEffectAdded(entity, source, changeableTarget))
);
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (2)

64-72: Consider more realistic test values for critical hit modifiers.

The current values create extreme multipliers:

  • 10 with ADD_MULTIPLIED_TOTAL = 1000% critical damage multiplier
  • 50 with ADD_MULTIPLIED_TOTAL = 5000% critical chance

While these may be intentional for testing, more moderate values would better exercise edge cases and ensure the system works correctly with realistic gameplay parameters.


133-152: Add null checks for storage objects and remove unused variables.

The event handler has several minor issues:

  1. No null checks on storage objects returned by SkillAPI.getSkillsFrom() and RaceAPI.getRaceFrom(), which could lead to NPEs if the storage is unavailable.
  2. Lines 137-138 declare unused variables skills and testSkill.

Consider applying these improvements:

 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 == null) {
+            ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+            return EventResult.pass();
+        }
         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);
+        if (storage == null) {
+            ManasCoreTesting.LOG.error("Failed to get skills storage for player");
+            return EventResult.pass();
+        }
         storage.forgetSkill(RegistryTest.TEST_SKILL.get(), Component.literal("Forgot Tested Skill!"));
     }  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();
+        }
         storage.setRace(RegistryTest.TEST_RACE.getId(), true, Component.literal("Set to Test Race!"));
     }

     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 93d1b64 and 65f0d1c.

📒 Files selected for processing (4)
  • gradle.properties (2 hunks)
  • race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1 hunks)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java (1 hunks)
  • testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradle.properties
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java
🧰 Additional context used
🧬 Code graph analysis (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (3)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java (9)
attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java (1)
  • ManasCoreAttributes (16-61)
config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java (1)
  • ConfigRegistry (13-100)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java (1)
  • ManasCoreTesting (19-33)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java (1)
  • TestAttributeRegistry (13-21)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java (1)
  • TestSkill (39-203)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java (1)
  • TestRace (42-155)
testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java (1)
  • TestRaceEvolved (32-82)
🔇 Additional comments (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (2)

27-27: The unsafe cast concern from the previous review has been resolved.

The current implementation uses ResourceKey.createRegistryKey(RACES.key().location()), which is type-safe and avoids the unchecked cast warning that was previously flagged.


123-125: LGTM!

The private constructor correctly prevents instantiation of this utility class.

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

♻️ Duplicate comments (8)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

106-121: State modifications before canActivateAbility check cause inconsistent dirty tracking.

Lines 112-115 modify entity state (addAttributeModifiers, setHealth, teleport) when !conqueredEnd, but checkAndMarkDirty at line 120 only runs if canActivateAbility returns true. This means attribute and health changes may not be persisted.

This was previously flagged. Consider adding storage.checkAndMarkDirty(instance) after line 115 to ensure state changes are persisted regardless of the ability check.

🔎 Suggested fix:
             ManasRaceInstance instance = optional.get();
             if (!conqueredEnd) {
                 instance.addAttributeModifiers(newPlayer);
                 newPlayer.setHealth(newPlayer.getMaxHealth());
                 SpawnPointHelper.teleportToNewSpawn(newPlayer);
+                storage.checkAndMarkDirty(instance);
             }

             if (!instance.canActivateAbility(newPlayer)) return;
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (4)

69-73: Unconditional markDirty() causes unnecessary sync traffic.

storage.markDirty() is called every tick when the player has entries in tickingRaces, even if no races were actually removed. This generates excessive network synchronization.

🔎 Only mark dirty when the collection is modified:
     private static void handleRaceHeldTick(Player player, Races storage) {
         if (!tickingRaces.containsKey(player.getUUID())) return;
-        tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, player));
-        storage.markDirty();
+        boolean removed = tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, player));
+        if (removed) storage.markDirty();
     }

104-132: Multiple issues in setRace method need attention.

  1. Line 120: sendSystemMessage is called on getOwner() which is a LivingEntity, but this method is primarily meaningful for Player entities.

  2. Line 129: teleport.get() can return null, and auto-unboxing to boolean will throw NPE.

🔎 Apply these fixes:
-        if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get());
+        if (raceMessage.isPresent() && owner instanceof Player p) {
+            p.sendSystemMessage(raceMessage.get());
+        }
         race.markDirty();
         race.addAttributeModifiers(owner);
         owner.setHealth(owner.getMaxHealth());
         race.onRaceSet(owner);

         race.learnIntrinsicSkills(owner);
         this.raceInstance = race;

-        if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player);
+        if (Boolean.TRUE.equals(teleport.get()) && getOwner() instanceof ServerPlayer player) {
+            SpawnPointHelper.teleportToNewSpawn(player);
+        }

140-144: Missing error handling and attribute reapplication in load().

  1. ManasRaceInstance.fromNBT() can throw on corrupted/invalid data, potentially crashing entity loading.
  2. After loading, attribute modifiers are not reapplied, causing stale stats after chunk reload or dimension change.
🔎 Wrap in try-catch and reapply attributes:
     @Override
     public void load(CompoundTag data) {
         if (!data.contains(RACE_KEY)) return;
-        this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+        try {
+            ManasRaceInstance loaded = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY));
+            this.raceInstance = loaded;
+            LivingEntity owner = this.getOwner();
+            if (!owner.level().isClientSide()) {
+                loaded.addAttributeModifiers(owner);
+            }
+        } catch (Exception e) {
+            log.error("Failed to load race instance for {}: {}", this.getOwner(), e.getMessage(), e);
+            this.raceInstance = null;
+        }
     }

75-92: Consider adding error handling around onTick() to prevent server crashes.

If instance.onTick(entity) throws an exception, it could crash the server tick loop. Wrapping in try-catch with logging would improve resilience.

🔎 Suggested error handling:
         if (RaceEvents.RACE_PRE_TICK.invoker().tick(instance, entity).isFalse()) return;
-        instance.onTick(entity);
+        try {
+            instance.onTick(entity);
+        } catch (Exception e) {
+            log.error("Error ticking race for entity {}: {}", entity, e.getMessage(), e);
+            return;
+        }
         RaceEvents.RACE_POST_TICK.invoker().tick(instance, entity);
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (3)

225-245: Ensure TickingSkill.matches() uses semantic equality.

Line 242 relies on tickingSkill.matches(skill.getSkill(), mode) to remove released skills. A previous review identified that matches() uses reference equality (==) instead of semantic comparison, which can fail when different instances represent the same skill.

Verify that the matches() implementation in TickingSkill.java compares skill identifiers using .equals() rather than ==.

Based on past review comments, this issue was flagged with a suggested fix to use .equals() for identifier comparison.


45-45: Use a thread-safe multimap implementation.

tickingSkills is accessed and modified from various event hooks that may execute concurrently. ArrayListMultimap is not thread-safe and can lead to data races. Wrap it with Multimaps.synchronizedMultimap() or use a concurrent collection.

🔎 Apply this diff to make the multimap thread-safe:
+import com.google.common.collect.Multimaps;
+
-public static final Multimap<UUID, TickingSkill> tickingSkills = ArrayListMultimap.create();
+public static final Multimap<UUID, TickingSkill> tickingSkills = Multimaps.synchronizedMultimap(ArrayListMultimap.create());

294-304: Ensure thread-safe access to tickingSkills.

Lines 296-302 perform multiple operations on the tickingSkills multimap without synchronization. Since tickingSkills uses a non-thread-safe ArrayListMultimap, concurrent access from different threads can cause race conditions.

This is a consequence of the thread-safety issue at line 45. Once tickingSkills is wrapped with Multimaps.synchronizedMultimap(), ensure all access blocks are properly synchronized.

🧹 Nitpick comments (3)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (1)

45-61: Redundant null check after isPresent() validation.

On line 47, owner is checked for null, but line 46 already returns early if changeableTarget.isPresent() is false. If isPresent() returns true and get() still returns null, this indicates a bug in the Changeable implementation rather than something to guard against here.

🔎 Consider simplifying:
         EntityEvents.LIVING_CHANGE_TARGET.register((entity, changeableTarget) -> {
             if (!changeableTarget.isPresent()) return EventResult.pass();
             LivingEntity owner = changeableTarget.get();
-            if (owner == null) return EventResult.pass();

             Races storage = RaceAPI.getRaceFrom(owner);
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1)

38-41: Consider restricting visibility of mutable static fields.

tickingRaces is public static and mutable, allowing external code to modify it directly. This could lead to unexpected behavior if external code adds/removes entries incorrectly.

🔎 Consider making it package-private or providing controlled access:
-    public static final Multimap<UUID, TickingRace> tickingRaces = ArrayListMultimap.create();
+    static final Multimap<UUID, TickingRace> tickingRaces = ArrayListMultimap.create();
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

220-223: Consider conditional dirty marking in forEachSkill.

Line 222 unconditionally marks the storage as dirty after iterating, even if the consumer made no modifications. For efficiency, consider marking dirty only when actual changes occur, or document that consumers must call markDirty() themselves.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65f0d1c and 80aabe0.

📒 Files selected for processing (7)
  • gradle.properties (2 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-neoforge/src/main/resources/META-INF/neoforge.mods.toml (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-neoforge/src/main/resources/META-INF/neoforge.mods.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java
  • skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml
  • race-neoforge/src/main/resources/META-INF/neoforge.mods.toml
🧰 Additional context used
🧬 Code graph analysis (2)
race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (2)
race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java (1)
  • RaceAPI (20-82)
race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java (1)
  • SpawnPointHelper (40-201)
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (6)
network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java (1)
  • Changeable (11-48)
skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java (1)
  • ManasCoreSkill (17-27)
storage-common/src/main/java/io/github/manasmods/manascore/storage/api/Storage.java (1)
  • Storage (5-70)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)
  • EntityEvents (21-75)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java (1)
  • SkillAPI (20-82)
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java (1)
  • ManasSkillInstance (32-709)
🔇 Additional comments (4)
gradle.properties (1)

21-21: Version bump to NeoForge 21.1.216 is valid.

NeoForge 21.1.216 is the latest stable version for Minecraft 1.21.1 and is actively in use with no known compatibility issues. This is a patch release upgrade (from 21.1.28), not a downgrade.

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java (2)

24-29: LGTM - Clean registry initialization pattern.

The use of createKey() helper method to derive the ResourceKey from the Registrar is a clean solution that avoids the unchecked cast warning previously flagged.


127-129: Clean helper for type-safe registry key creation.

This helper method elegantly resolves the unchecked cast issue by reconstructing the ResourceKey from the registrar's location.

race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java (1)

43-58: Good initialization pattern with proper event cleanup.

The storage registration and event handlers are well-structured. The cleanup on PLAYER_QUIT and CHANGE_DIMENSION (lines 56-57) properly removes stale entries from tickingRaces, preventing memory leaks.

org.gradle.parallel=true
# Mod properties
mod_version=4.0.0.0
mod_version=3.0.3.0
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Version downgrade contradicts semantic versioning and new feature additions.

The mod_version has been downgraded from 4.0.0.0 to 3.0.3.0, which is unusual for a PR that adds new modules (Registry, Keybinding, Skill, Config). A major version downgrade could:

  • Break existing deployments expecting version 4.x
  • Confuse users about feature availability
  • Cause issues with version-dependent integrations

Please clarify whether this downgrade is intentional or a typo.

🤖 Prompt for AI Agents
In gradle.properties at line 10 the mod_version was changed from 4.0.0.0 to
3.0.3.0; restore the correct semantic version for this PR by either reverting to
4.0.0.0 if the downgrade was accidental or bumping to an appropriate new 4.x.y.z
(e.g., 4.1.0.0) if these are new features, then update any other
files/changelogs that reference the version and add a note in the PR description
confirming the intended version choice.

Comment on lines +203 to +218
public void forgetSkill(@NotNull ResourceLocation skillId, @Nullable MutableComponent component) {
if (!this.skillInstances.containsKey(skillId)) return;
ManasSkillInstance instance = this.skillInstances.get(skillId);

Changeable<MutableComponent> forgetMessage = Changeable.of(component);
EventResult result = SkillEvents.REMOVE_SKILL.invoker().removeSkill(instance, getOwner(), forgetMessage);
if (result.isFalse()) return;

if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
instance.onForgetSkill(this.getOwner());
instance.markDirty();

this.getLearnedSkills().remove(instance);
this.hasRemovedSkills = true;
markDirty();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect removal from collection view.

Line 215 calls this.getLearnedSkills().remove(instance), but getLearnedSkills() returns skillInstances.values(), which is a view that doesn't support element removal by instance. This will fail to remove the skill from the underlying map.

🔎 Apply this diff to correctly remove the skill:
     if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
     instance.onForgetSkill(this.getOwner());
     instance.markDirty();
 
-    this.getLearnedSkills().remove(instance);
+    this.skillInstances.remove(skillId);
     this.hasRemovedSkills = true;
     markDirty();
📝 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 forgetSkill(@NotNull ResourceLocation skillId, @Nullable MutableComponent component) {
if (!this.skillInstances.containsKey(skillId)) return;
ManasSkillInstance instance = this.skillInstances.get(skillId);
Changeable<MutableComponent> forgetMessage = Changeable.of(component);
EventResult result = SkillEvents.REMOVE_SKILL.invoker().removeSkill(instance, getOwner(), forgetMessage);
if (result.isFalse()) return;
if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
instance.onForgetSkill(this.getOwner());
instance.markDirty();
this.getLearnedSkills().remove(instance);
this.hasRemovedSkills = true;
markDirty();
}
public void forgetSkill(@NotNull ResourceLocation skillId, @Nullable MutableComponent component) {
if (!this.skillInstances.containsKey(skillId)) return;
ManasSkillInstance instance = this.skillInstances.get(skillId);
Changeable<MutableComponent> forgetMessage = Changeable.of(component);
EventResult result = SkillEvents.REMOVE_SKILL.invoker().removeSkill(instance, getOwner(), forgetMessage);
if (result.isFalse()) return;
if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get());
instance.onForgetSkill(this.getOwner());
instance.markDirty();
this.skillInstances.remove(skillId);
this.hasRemovedSkills = true;
markDirty();
}
🤖 Prompt for AI Agents
In
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java
around lines 203 to 218, the code attempts to remove an instance via
this.getLearnedSkills().remove(instance) which operates on a values() view and
won't remove the entry from the underlying map; replace that call with
this.skillInstances.remove(skillId) so the map entry keyed by skillId is removed
(keep the surrounding logic: set hasRemovedSkills = true and call markDirty()).

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

🤖 Fix all issues with AI agents
In
`@command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java`:
- Around line 179-184: Change the registration of the entity-type argument to
use Holder.Reference.class instead of Holder.class: in the registry.register
call that references EntityTypeArg.class, replace the registered key type from
Holder.class to Holder.Reference.class so the handler's addValueExtractor (which
uses ResourceArgument.getResource(commandContext, argumentName,
Registries.ENTITY_TYPE)) returns and matches Holder.Reference<EntityType> like
the other resource-backed args.

In
`@skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java`:
- Around line 231-251: The method handleSkillRelease uses the Changeable result
stored in local variable skill but still calls checkAndMarkDirty with the
original parameter skillInstance; update both calls to checkAndMarkDirty to pass
the potentially modified instance (skill) instead of skillInstance so the
correct instance is marked dirty after event listeners mutate it, keeping all
other logic (attribute removal and tickingSkills removal using skill.getSkill())
unchanged.
🧹 Nitpick comments (3)
config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java (1)

9-15: Consider adding an idempotency guard to init().

If init() is called more than once (e.g., due to a bug or future refactoring), the PLAYER_JOIN event handler would be registered multiple times, causing duplicate config syncs to clients on each join.

♻️ Suggested defensive pattern
 public class ManasCoreConfig {
     public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Config");
+    private static boolean initialized = false;
+
     public static void init() {
+        if (initialized) return;
+        initialized = true;
         Config.setInsertionOrderPreserved(true);
         ManasConfigNetwork.init();
         PlayerEvent.PLAYER_JOIN.register(ManasConfigNetwork::syncToClient);
     }
 }
skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java (1)

21-40: Make this a non‑instantiable constants holder and prevent event reassignment.

EntityEvents is a static-only container; marking the class final, adding a private ctor, and making Event fields static final prevents accidental reassignment or instantiation. (Line 21)

♻️ Proposed refactor
-public class EntityEvents {
-    public static Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
-    public static Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();
-    public static Event<LivingEffectAddedEvent> LIVING_EFFECT_ADDED = EventFactory.createEventResult();
+public final class EntityEvents {
+    public static final Event<LivingTickEvent> LIVING_PRE_TICK = EventFactory.createLoop();
+    public static final Event<LivingTickEvent> LIVING_POST_TICK = EventFactory.createLoop();
+    public static final Event<LivingEffectAddedEvent> LIVING_EFFECT_ADDED = EventFactory.createEventResult();
 
-    public static Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET = EventFactory.createEventResult();
-    public static Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET_EARLY = EventFactory.createEventResult();
-    public static Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET_LATE = EventFactory.createEventResult();
+    public static final Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET = EventFactory.createEventResult();
+    public static final Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET_EARLY = EventFactory.createEventResult();
+    public static final Event<LivingChangeTargetEvent> LIVING_CHANGE_TARGET_LATE = EventFactory.createEventResult();
 
-    public static Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
-    public static Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
-    public static Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
-    public static Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
-    public static Event<ProjectileHitEvent> PROJECTILE_HIT = EventFactory.createLoop();
+    public static final Event<EntityEvent.LivingHurt> LIVING_PRE_DAMAGED = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingHurt> LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult();
+    public static final Event<LivingHurtEvent> LIVING_HURT = EventFactory.createEventResult();
+    public static final Event<LivingDamageEvent> LIVING_DAMAGE = EventFactory.createEventResult();
+    public static final Event<ProjectileHitEvent> PROJECTILE_HIT = EventFactory.createLoop();
 
-    public static Event<EntityEvent.LivingDeath> DEATH_EVENT_FIRST = EventFactory.createEventResult();
-    public static Event<EntityEvent.LivingDeath> DEATH_EVENT_HIGH = EventFactory.createEventResult();
-    public static Event<EntityEvent.LivingDeath> DEATH_EVENT_NORMAL = EventFactory.createEventResult();
-    public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LOW = EventFactory.createEventResult();
-    public static Event<EntityEvent.LivingDeath> DEATH_EVENT_LAST = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingDeath> DEATH_EVENT_FIRST = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingDeath> DEATH_EVENT_HIGH = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingDeath> DEATH_EVENT_NORMAL = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingDeath> DEATH_EVENT_LOW = EventFactory.createEventResult();
+    public static final Event<EntityEvent.LivingDeath> DEATH_EVENT_LAST = EventFactory.createEventResult();
+
+    private EntityEvents() {}
skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java (1)

226-229: Unconditional markDirty() may cause unnecessary synchronization.

markDirty() is called regardless of whether the consumer actually modified any skill instances. This could trigger unnecessary network synchronization. Consider letting the consumer handle dirty marking, or only mark dirty if a modification occurred.

Suggested approach
 public void forEachSkill(BiConsumer<SkillStorage, ManasSkillInstance> skillInstanceConsumer) {
     List.copyOf(this.skillInstances.values()).forEach(skillInstance -> skillInstanceConsumer.accept(this, skillInstance));
-    markDirty();
+    // Callers should use checkAndMarkDirty() or markDirty() as needed
 }

Alternatively, check if any instance became dirty:

public void forEachSkill(BiConsumer<SkillStorage, ManasSkillInstance> skillInstanceConsumer) {
    List.copyOf(this.skillInstances.values()).forEach(skillInstance -> {
        skillInstanceConsumer.accept(this, skillInstance);
        checkAndMarkDirty(skillInstance);
    });
}

Comment on lines +179 to +184
// Register Entity Type Argument
registry.register(Holder.class, EntityTypeArg.class, (annotation, handler) -> {
var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value();
handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENTITY_TYPE)));
handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.ENTITY_TYPE));
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the file and examine the context around lines 179-184
find . -name "ManasCoreCommand.java" -type f

Repository: ManasMods/ManasCore

Length of output: 153


🏁 Script executed:

#!/bin/bash
# Let's also check what files are in the command-common directory
find . -path "*command-common*" -type f -name "*.java" | head -20

Repository: ManasMods/ManasCore

Length of output: 2010


🏁 Script executed:

#!/bin/bash
# Examine the ManasCoreCommand.java file around lines 179-184
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | sed -n '170,195p'

Repository: ManasMods/ManasCore

Length of output: 2209


🏁 Script executed:

#!/bin/bash
# Let's see the full file to understand the structure and find all registry.register calls
wc -l ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java

Repository: ManasMods/ManasCore

Length of output: 157


🏁 Script executed:

#!/bin/bash
# Let's see all registry.register calls in the file to understand the pattern
rg -n "registry\.register" ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java

Repository: ManasMods/ManasCore

Length of output: 3567


🏁 Script executed:

#!/bin/bash
# Let's look at the imports to understand what ResourceArgument comes from
head -50 ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java

Repository: ManasMods/ManasCore

Length of output: 2442


🏁 Script executed:

#!/bin/bash
# Find ResourceArgument to understand what getResource returns
find . -name "ResourceArgument*" -type f

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for ResourceArgument in imports and usage
rg -n "ResourceArgument" ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | head -5

Repository: ManasMods/ManasCore

Length of output: 740


🏁 Script executed:

#!/bin/bash
# Let's check other usages of ResourceArgument.getResource to see what they return
rg -n "ResourceArgument.getResource" --type=java

Repository: ManasMods/ManasCore

Length of output: 538


🏁 Script executed:

#!/bin/bash
# Let's look at lines 246-249 to see the DamageArg registration pattern
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | sed -n '246,250p'

Repository: ManasMods/ManasCore

Length of output: 614


🏁 Script executed:

#!/bin/bash
# Check the EntityTypeArg annotation to understand its intended usage
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityTypeArg.java

Repository: ManasMods/ManasCore

Length of output: 698


🏁 Script executed:

#!/bin/bash
# Let's look for imports related to ResourceArgument
rg -n "import.*ResourceArgument" ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Let's check the full imports section more thoroughly
head -60 ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | tail -20

Repository: ManasMods/ManasCore

Length of output: 849


🏁 Script executed:

#!/bin/bash
# Search for usages of `@EntityTypeArg` to understand how it's used in practice
rg -n "@EntityTypeArg" --type=java -C5

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Let's search for where ResourceArgument might be coming from
rg -n "class ResourceArgument" --type=java

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check all imports to see where ResourceArgument comes from
rg -n "^import" ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | grep -i resource

Repository: ManasMods/ManasCore

Length of output: 188


🏁 Script executed:

#!/bin/bash
# Let's look at the entire imports section more thoroughly
head -50 ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | grep "^import"

Repository: ManasMods/ManasCore

Length of output: 2319


🏁 Script executed:

#!/bin/bash
# Let's find ResourceArgument in the resource parameter package
find . -path "*api/parameter/resource*" -name "*.java"

Repository: ManasMods/ManasCore

Length of output: 719


🏁 Script executed:

#!/bin/bash
# Let's search for the actual ResourceArgument definition
rg -l "class ResourceArgument" --type=java

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Let's check what's imported from net.minecraft.commands.arguments
rg "net.minecraft.commands.arguments" ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java

Repository: ManasMods/ManasCore

Length of output: 682


🏁 Script executed:

#!/bin/bash
# Let's look at one of the resource arg files to understand the pattern
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/DamageArg.java

Repository: ManasMods/ManasCore

Length of output: 703


🏁 Script executed:

#!/bin/bash
# Search for ResourceArgument usage across the codebase
rg "ResourceArgument" --type=java -A2 -B2

Repository: ManasMods/ManasCore

Length of output: 6885


🏁 Script executed:

#!/bin/bash
# Let's check lines around all the Holder-based registrations to verify the pattern
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java | sed -n '240,270p'

Repository: ManasMods/ManasCore

Length of output: 3343


🏁 Script executed:

#!/bin/bash
# Let's confirm what the CommandArgumentRegistry.register method signature expects
rg -n "register.*Holder" ./command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java -A3 -B3

Repository: ManasMods/ManasCore

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Let's check the CommandArgumentRegistry file to understand how registry works
cat -n ./command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java | head -50

Repository: ManasMods/ManasCore

Length of output: 2405


🏁 Script executed:

#!/bin/bash
# Look for the register method definition
rg -n "public.*register" ./command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java

Repository: ManasMods/ManasCore

Length of output: 289


EntityTypeArg should register Holder.Reference.class instead of Holder.class. The current registration is inconsistent with all other resource-backed arguments (DamageArg, AttributeArg, EnchantmentArg, MobEffectArg, StructureArg, FeatureArg), which use Holder.Reference.class. Both EntityTypeArg and DamageArg use the same ResourceArgument.getResource() method, which returns Holder.Reference<T>, so they must register the same type. Command parameters typed as Holder.Reference<EntityType> will not be matched by the current Holder.class registration.

🔧 Proposed fix
-            registry.register(Holder.class, EntityTypeArg.class, (annotation, handler) -> {
+            registry.register(Holder.Reference.class, EntityTypeArg.class, (annotation, handler) -> {
                 var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value();
                 handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENTITY_TYPE)));
                 handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.ENTITY_TYPE));
             });
📝 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
// Register Entity Type Argument
registry.register(Holder.class, EntityTypeArg.class, (annotation, handler) -> {
var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value();
handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENTITY_TYPE)));
handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.ENTITY_TYPE));
});
// Register Entity Type Argument
registry.register(Holder.Reference.class, EntityTypeArg.class, (annotation, handler) -> {
var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value();
handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENTITY_TYPE)));
handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.ENTITY_TYPE));
});
🤖 Prompt for AI Agents
In
`@command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java`
around lines 179 - 184, Change the registration of the entity-type argument to
use Holder.Reference.class instead of Holder.class: in the registry.register
call that references EntityTypeArg.class, replace the registered key type from
Holder.class to Holder.Reference.class so the handler's addValueExtractor (which
uses ResourceArgument.getResource(commandContext, argumentName,
Registries.ENTITY_TYPE)) returns and matches Holder.Reference<EntityType> like
the other resource-backed args.

Comment on lines +231 to +251
public void handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean heldInterrupt) {
Changeable<ManasSkillInstance> changeable = Changeable.of(skillInstance);
if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return;
ManasSkillInstance skill = changeable.get();
if (skill == null) return;

if ((heldInterrupt || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) {
if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) {
skill.onRelease(getOwner(), heldTick, keyNumber, mode);
this.checkAndMarkDirty(skillInstance);
}
}

skill.removeAttributeModifiers(getOwner(), mode);
if (!heldInterrupt) {
UUID ownerID = getOwner().getUUID();
if (tickingSkills.containsKey(ownerID))
tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode));
}
this.checkAndMarkDirty(skillInstance);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent usage of skill vs skillInstance after event modification.

Lines 239 and 244 correctly use skill (the potentially modified instance from changeable.get()), but lines 240 and 250 call checkAndMarkDirty(skillInstance) using the original parameter. If the event listener swaps the instance via Changeable, the wrong instance gets marked dirty.

Suggested fix
     public void handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean heldInterrupt) {
         Changeable<ManasSkillInstance> changeable = Changeable.of(skillInstance);
         if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return;
         ManasSkillInstance skill = changeable.get();
         if (skill == null) return;
 
         if ((heldInterrupt || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) {
             if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) {
                 skill.onRelease(getOwner(), heldTick, keyNumber, mode);
-                this.checkAndMarkDirty(skillInstance);
+                this.checkAndMarkDirty(skill);
             }
         }
 
         skill.removeAttributeModifiers(getOwner(), mode);
         if (!heldInterrupt) {
             UUID ownerID = getOwner().getUUID();
             if (tickingSkills.containsKey(ownerID))
                 tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode));
         }
-        this.checkAndMarkDirty(skillInstance);
+        this.checkAndMarkDirty(skill);
     }
📝 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 handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean heldInterrupt) {
Changeable<ManasSkillInstance> changeable = Changeable.of(skillInstance);
if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return;
ManasSkillInstance skill = changeable.get();
if (skill == null) return;
if ((heldInterrupt || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) {
if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) {
skill.onRelease(getOwner(), heldTick, keyNumber, mode);
this.checkAndMarkDirty(skillInstance);
}
}
skill.removeAttributeModifiers(getOwner(), mode);
if (!heldInterrupt) {
UUID ownerID = getOwner().getUUID();
if (tickingSkills.containsKey(ownerID))
tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode));
}
this.checkAndMarkDirty(skillInstance);
}
public void handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean heldInterrupt) {
Changeable<ManasSkillInstance> changeable = Changeable.of(skillInstance);
if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return;
ManasSkillInstance skill = changeable.get();
if (skill == null) return;
if ((heldInterrupt || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) {
if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) {
skill.onRelease(getOwner(), heldTick, keyNumber, mode);
this.checkAndMarkDirty(skill);
}
}
skill.removeAttributeModifiers(getOwner(), mode);
if (!heldInterrupt) {
UUID ownerID = getOwner().getUUID();
if (tickingSkills.containsKey(ownerID))
tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode));
}
this.checkAndMarkDirty(skill);
}
🤖 Prompt for AI Agents
In
`@skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java`
around lines 231 - 251, The method handleSkillRelease uses the Changeable result
stored in local variable skill but still calls checkAndMarkDirty with the
original parameter skillInstance; update both calls to checkAndMarkDirty to pass
the potentially modified instance (skill) instead of skillInstance so the
correct instance is marked dirty after event listeners mutate it, keeping all
other logic (attribute removal and tickingSkills removal using skill.getSkill())
unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@settings.gradle`:
- Around line 17-24: The settings.gradle registers new modules "attribute",
"config", "keybind", "race", and "skill" (in addition to the existing ones) but
the bundle publication scripts (bundle-fabric and bundle-neoforge) only include
"network", "storage", "inventory", and "command", so the new modules won't be
published; update bundle-fabric/build.gradle and bundle-neoforge/build.gradle to
add the missing projects for each bundle—specifically include the "-common",
"-fabric" and/or "-neoforge" variants as appropriate for "attribute", "config",
"keybind", "race", and "skill" (leave "testing" out if intended) so the bundles
match the modules declared in settings.gradle.
🧹 Nitpick comments (2)
bundle-neoforge/build.gradle (1)

6-37: Consider extracting shared bundle configuration.

This file is nearly identical to bundle-fabric/build.gradle. The only differences are the archive name suffix and project suffix (-neoforge vs -fabric).

If more modules are added to includedProjects in the future, both files would need to be updated in sync (already an issue as noted in the settings.gradle review).

Consider extracting the shared includedProjects list to a parent build script or gradle.properties to ensure consistency.

♻️ Example approach using gradle.properties or buildSrc

In gradle.properties or a shared script:

# Define once, use in both bundles
bundled_modules=network,storage,inventory,command,attribute,config,keybind,race,skill

Then in each bundle's build.gradle:

ext {
    includedProjects = bundled_modules.split(',').collect { ":$it" }
}
bundle-fabric/src/main/resources/fabric.mod.json (1)

10-14: Provide a valid homepage URL or remove the empty contact block.

The homepage field in the contact block is an empty string. Either populate it with a valid URL (e.g., GitHub repository or mod page) or remove the entire contact block if not currently needed. The icon asset at assets/manascore/icon.png exists and is properly referenced.

Comment on lines +17 to +24
["attribute", "command", "config", "inventory", "keybind", "network", "race", "storage", "testing", "skill"].forEach { module ->
include ":$module-common"
include ":$module-fabric"
include ":$module-neoforge"
}

include ':bundle-fabric'
include ':bundle-neoforge'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Module list mismatch between settings.gradle and bundle configurations.

The settings.gradle includes 10 modules (attribute, command, config, inventory, keybind, network, race, storage, testing, skill), but the bundle configurations (bundle-fabric/build.gradle and bundle-neoforge/build.gradle) only include 4 projects: network, storage, inventory, command.

The new modules added in this PR (attribute, config, keybind, race, skill) are missing from the bundle configurations, which means they won't be included in the published bundles.

🔧 Suggested fix for bundle-fabric/build.gradle and bundle-neoforge/build.gradle
 ext {
     includedProjects = [
+            ":attribute",
+            ":command",
+            ":config",
+            ":inventory",
+            ":keybind",
             ":network",
+            ":race",
+            ":skill",
-            ":storage",
-            ":inventory",
-            ":command"
+            ":storage"
     ]
 }

Note: testing module is likely intentionally excluded from bundles.

🤖 Prompt for AI Agents
In `@settings.gradle` around lines 17 - 24, The settings.gradle registers new
modules "attribute", "config", "keybind", "race", and "skill" (in addition to
the existing ones) but the bundle publication scripts (bundle-fabric and
bundle-neoforge) only include "network", "storage", "inventory", and "command",
so the new modules won't be published; update bundle-fabric/build.gradle and
bundle-neoforge/build.gradle to add the missing projects for each
bundle—specifically include the "-common", "-fabric" and/or "-neoforge" variants
as appropriate for "attribute", "config", "keybind", "race", and "skill" (leave
"testing" out if intended) so the bundles match the modules declared in
settings.gradle.

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.

4 participants