Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b7c196b
Added files that are necessary for all other modules to work OR are t…
maltesermailo Oct 13, 2024
29ca84c
New config Module and Feature with Architectury
Erusel Nov 20, 2024
2a1bed4
Added neoforge module
Erusel Nov 20, 2024
14137e9
Some code cleaning
Erusel Nov 20, 2024
5f76f65
Fixed dumb fail mistake
Erusel Nov 20, 2024
5b79bc2
Skill System
minheragon12345 Dec 2, 2024
ebdc01e
Merge remote-tracking branch 'origin/1.21.1/master' into 1.21.1/skills
minheragon12345 Dec 4, 2024
0bc6683
Keybinding & Registry
minheragon12345 Dec 4, 2024
4cd521f
Mob Effect Registry
minheragon12345 Dec 5, 2024
ba1bac3
Potion Registry
minheragon12345 Dec 5, 2024
ffcec17
Cooldown with modes
minheragon12345 Dec 21, 2024
183ac54
Merge branch '1.21.1/test-branch' into 1.21.1/master
minheragon12345 Dec 21, 2024
f0e357a
Merge pull request #47 from Erusel/1.21.1/master
minheragon12345 Dec 21, 2024
53efe10
Merge branch '1.21.1/skills' into 1.21.1/test-branch
minheragon12345 Dec 21, 2024
a2a5c59
Merged with Conig & Commented out attribute stuff to run Client
minheragon12345 Dec 21, 2024
2e51185
Parent folder path for Config + more test
minheragon12345 Dec 22, 2024
608a737
Revert and fixes + Icons
minheragon12345 Jan 17, 2025
6854739
Merge remote-tracking branch 'origin/1.21.1/master' into 1.21.1/minh
minheragon12345 Jan 17, 2025
e3508e4
Updated copyright year to 2024-2025
minheragon12345 Jan 17, 2025
3ae7405
Fabric works now (when exported)
minheragon12345 Jan 17, 2025
c96dcb2
Removed Registry Module and Added Arch's registry for Testing module
minheragon12345 Jan 21, 2025
75ebe0c
Fixed Keybind and Skill not working on NeoForge
minheragon12345 Jan 22, 2025
db27667
Attribute Module & Attribute Registry
minheragon12345 Jan 25, 2025
d910e45
Race Module Started
minheragon12345 Jan 26, 2025
aa7649f
Glide/Lava/Swim Speed attributes
minheragon12345 Jan 26, 2025
63ea442
Respawn Dimension for Races
minheragon12345 Jan 27, 2025
34374fb
Config Module revamped as Toml file with @Comment
minheragon12345 Mar 2, 2025
173c1eb
Field insertion order & SubConfig loading
minheragon12345 Mar 3, 2025
03f88e2
Config client-server syncing & API Comments for documenting
minheragon12345 Mar 3, 2025
4456747
Some code changes following Coderabbit's suggestions & Config Fabric fix
minheragon12345 Mar 4, 2025
7f556fd
More of Coderabbit's suggestions & Fixed Skill/Race attribute modifie…
minheragon12345 Mar 5, 2025
44852e2
More Command parameters & Minor commenting changes
minheragon12345 Mar 6, 2025
a0ee047
Code refactoring for Temporary Maven publish
minheragon12345 Mar 6, 2025
3f31b47
Attribute Registry fix
minheragon12345 Mar 7, 2025
b3e36f7
Fixed Attribute not applying on modded entities
minheragon12345 Apr 1, 2025
8b516bb
Fabric Modded Entities doesn't get modded attributes
minheragon12345 Apr 1, 2025
3beb0f4
Vanilla Mobs dont get any custom attributes.
minheragon12345 Apr 2, 2025
a99d535
ManasSkill improvement
minheragon12345 Apr 5, 2025
8a775e9
Storages doesn't update owner after cloning/respawning + Manas Skill QOL
minheragon12345 Apr 7, 2025
989d640
Race abilities' qol + Minor Skill changes + Glide events
minheragon12345 Apr 24, 2025
0674abf
Elytra not working fixed + Temporary Fabric Command fix
minheragon12345 Jun 4, 2025
85c565c
Obtained Intrinsic Skills for races
minheragon12345 Jul 9, 2025
253ddc6
More SpawnPoint/Teleportation Utils
minheragon12345 Jul 25, 2025
4ab6797
Skill Scroll with modes + Modes with instance
minheragon12345 Aug 30, 2025
4388476
Race Stat client spam
minheragon12345 Aug 31, 2025
3ae207b
Custom Attribute is overwritten by default value
minheragon12345 Sep 1, 2025
7754cae
Skill Concurrent fix + Race save attributes leaving End + Skill Maste…
minheragon12345 Sep 3, 2025
9cc4696
Skill Sub-Instances
minheragon12345 Sep 18, 2025
3ec0700
Some Event changes
minheragon12345 Sep 30, 2025
d00bfbd
Improved some syncing
minheragon12345 Oct 9, 2025
9bc00ca
Post Init events
minheragon12345 Oct 10, 2025
2fd1983
TickingSkill Improvement
minheragon12345 Oct 11, 2025
be81457
TickingSkill & Target Event fixes
minheragon12345 Oct 18, 2025
93d1b64
Race on target fix
minheragon12345 Oct 31, 2025
65f0d1c
Registry Key fix
minheragon12345 Nov 21, 2025
80aabe0
Small fixes
minheragon12345 Dec 18, 2025
0bb208b
Command Perm fixes + More command arguments + Misc changes
minheragon12345 Jan 26, 2026
996a3ff
feat: Add `bundle` modules for publishing
Charismara Feb 2, 2026
7e1968b
Merge pull request #50 from ManasMods/1.21.1/professor
minheragon12345 Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions attribute-common/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

loom {
accessWidenerPath = file('src/main/resources/manascore_attribute.accesswidener')
}

dependencies {
implementation(project(path: ":network-common", configuration: 'namedElements')) { transitive false }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.manasmods.manascore.attribute;

import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes;
import io.github.manasmods.manascore.attribute.impl.network.ManasCoreAttributeNetwork;

public class ManasCoreAttribute {
public static void init() {
ManasCoreAttributes.init();
ManasCoreAttributeRegister.init();
ManasCoreAttributeNetwork.init();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute;

import dev.architectury.injectables.annotations.ExpectPlatform;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.ai.attributes.Attribute;
import org.jetbrains.annotations.NotNull;

public class ManasCoreAttributeRegister {
/**
* Registers a player-specific attribute with the given parameters.
*
* @param modID The mod ID associated with this attribute.
* @param id The unique identifier for the attribute.
* @param name The display name of the attribute.
* @param amount The default base value of the attribute.
* @param min The minimum allowed value for the attribute.
* @param max The maximum allowed value for the attribute.
* @param syncable Whether the attribute should be synchronized between client and server.
* @param sentiment The sentiment classification of the attribute (e.g., beneficial or harmful).
* @return A {@link Holder} containing the registered player attribute.
*/
@ExpectPlatform
public static @NotNull Holder<Attribute> registerPlayerAttribute(String modID, String id, String name, double amount,
double min, double max, boolean syncable, Attribute.Sentiment sentiment) {
throw new AssertionError();
}

/**
* Registers a generic attribute that applies to multiple entity types.
*
* @param modID The mod ID associated with this attribute.
* @param id The unique identifier for the attribute.
* @param name The display name of the attribute.
* @param amount The default base value of the attribute.
* @param min The minimum allowed value for the attribute.
* @param max The maximum allowed value for the attribute.
* @param syncable Whether the attribute should be synchronized between client and server.
* @param sentiment The sentiment classification of the attribute (e.g., beneficial or harmful).
* @return A {@link Holder} containing the registered generic attribute.
*/
@ExpectPlatform
public static @NotNull Holder<Attribute> registerGenericAttribute(String modID, String id, String name, double amount,
double min, double max, boolean syncable, Attribute.Sentiment sentiment) {
throw new AssertionError();
}

@ExpectPlatform
public static void init() {
throw new AssertionError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.api;

import dev.architectury.event.Event;
import dev.architectury.event.EventFactory;
import dev.architectury.event.EventResult;
import io.github.manasmods.manascore.network.api.util.Changeable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;

public interface AttributeEvents {
Event<CriticalAttackChanceEvent> CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult();
Event<GlideEvent> START_GLIDE_EVENT = EventFactory.createEventResult();
Event<GlideEvent> CONTINUE_GLIDE_EVENT = EventFactory.createEventResult();

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

@FunctionalInterface
interface GlideEvent {
EventResult glide(LivingEntity glider, Changeable<Boolean> canGlide);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.api;

import io.github.manasmods.manascore.network.api.util.Changeable;
import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import org.jetbrains.annotations.Nullable;

public class ManasCoreAttributeUtils {
public static float getAttackDamage(Player player) {
float f = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE);
float h = player.getAttackStrengthScale(0.5F);
f *= 0.2F + h * h * 0.8F;
return f;
}

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

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

if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel)
damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage);
return damage;
Comment on lines +32 to +42
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Start from the full attack attribute value and add NPE-safe weapon bonuses

damage is initialised with a hard-coded 1F and only the single BASE_ATTACK_DAMAGE_ID modifier is added.
This ignores:

  1. The base attribute value (attack.getValue()), which already contains all relevant modifiers (weapon, enchantments, attributes from skills, etc.).
  2. A possible ItemStack.EMPTY result from attacker.getWeaponItem()modifyDamage will happily NPE on a null ItemStack in some modded environments.
-        float damage = 1F;
-        AttributeModifier modifier = attack.getModifier(Item.BASE_ATTACK_DAMAGE_ID);
-        if (modifier != null) damage += (float) modifier.amount();
-
-        if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel)
-            damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage);
+        // Start with the full (already-modified) attribute value
+        float damage = (float) attack.getValue();
+
+        if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel) {
+            var weapon = attacker.getWeaponItem();               // Never null, but can be EMPTY
+            if (!weapon.isEmpty()) {
+                damage = EnchantmentHelper.modifyDamage(serverLevel, weapon, target, source, damage);
+            }
+        }

This yields correct vanilla parity, prevents silent under-damage, and avoids a null/EMPTY edge case.

📝 Committable suggestion

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

Suggested change
public static float getWeaponDamage(LivingEntity attacker, @Nullable Entity target, @Nullable DamageSource source) {
AttributeInstance attack = attacker.getAttribute(Attributes.ATTACK_DAMAGE);
if (attack == null) return 0;
float damage = 1F;
AttributeModifier modifier = attack.getModifier(Item.BASE_ATTACK_DAMAGE_ID);
if (modifier != null) damage += (float) modifier.amount();
if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel)
damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage);
return damage;
public static float getWeaponDamage(LivingEntity attacker, @Nullable Entity target, @Nullable DamageSource source) {
AttributeInstance attack = attacker.getAttribute(Attributes.ATTACK_DAMAGE);
if (attack == null) return 0;
// Start with the full (already-modified) attribute value
float damage = (float) attack.getValue();
if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel) {
var weapon = attacker.getWeaponItem(); // Never null, but can be EMPTY
if (!weapon.isEmpty()) {
damage = EnchantmentHelper.modifyDamage(serverLevel, weapon, target, source, damage);
}
}
return damage;
}

}

public static void triggerCriticalAttackEffect(Entity target, Entity attacker) {
target.level().playSound(null, target.getX(), target.getY(), target.getZ(),
SoundEvents.PLAYER_ATTACK_CRIT, attacker.getSoundSource(), 1.0F, 1.0F);
if (target.level() instanceof ServerLevel level)
level.getChunkSource().broadcastAndSend(target, new ClientboundAnimatePacket(target, 4));
}

public static boolean canElytraGlide(LivingEntity entity, boolean additionalCheck) {
Changeable<Boolean> glide = Changeable.of(additionalCheck && !entity.onGround() && !entity.isPassenger()
&& !entity.hasEffect(MobEffects.LEVITATION) && entity.getAttributeValue(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER) > 0);
if (AttributeEvents.START_GLIDE_EVENT.invoker().glide(entity, glide).isFalse()) return false;
return glide.get();
}
Comment on lines +52 to +57
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Race condition: glide.get() can return null

Changeable<Boolean> is initialised with a possibly false value, but event listeners could set it to null.
return glide.get(); would then return null, violating the method’s boolean contract and crashing the JVM with an NPE in the caller.
Recommend enforcing non-null:

-        return glide.get();
+        Boolean result = glide.get();
+        return Boolean.TRUE.equals(result);
📝 Committable suggestion

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

Suggested change
public static boolean canElytraGlide(LivingEntity entity, boolean additionalCheck) {
Changeable<Boolean> glide = Changeable.of(additionalCheck && !entity.onGround() && !entity.isPassenger()
&& !entity.hasEffect(MobEffects.LEVITATION) && entity.getAttributeValue(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER) > 0);
if (AttributeEvents.START_GLIDE_EVENT.invoker().glide(entity, glide).isFalse()) return false;
return glide.get();
}
public static boolean canElytraGlide(LivingEntity entity, boolean additionalCheck) {
Changeable<Boolean> glide = Changeable.of(additionalCheck && !entity.onGround() && !entity.isPassenger()
&& !entity.hasEffect(MobEffects.LEVITATION) && entity.getAttributeValue(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER) > 0);
if (AttributeEvents.START_GLIDE_EVENT.invoker().glide(entity, glide).isFalse()) return false;
Boolean result = glide.get();
return Boolean.TRUE.equals(result);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.api;

import io.github.manasmods.manascore.attribute.ManasCoreAttributeRegister;
import io.github.manasmods.manascore.attribute.ModuleConstants;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.Attribute;

public class ManasCoreAttributes {
/**
* Determine how much the output damage is multiplied when the attacker does a critical attack.
*/
public static final Holder<Attribute> CRITICAL_DAMAGE_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID,
"critical_damage_multiplier", "manascore.attribute.critical_damage_multiplier",
1.5, 0, 1024, true, Attribute.Sentiment.POSITIVE);

/**
* Determine the percentage chance for the user to do a critical attack without jumping.
*/
public static final Holder<Attribute> CRITICAL_ATTACK_CHANCE = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID,
"critical_attack_chance", "manascore.attribute.critical_attack_chance",
0, 0, 100, true, Attribute.Sentiment.POSITIVE);

/**
* Determine how fast the player can elytra glide without wearing an elytra.
* Sets higher than 0 to allow the player to glide.
* This also affects the speed of normal Elytra gliding speed.
*/
public static final Holder<Attribute> GLIDE_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerPlayerAttribute(ModuleConstants.MOD_ID,
"glide_speed_multiplier", "manascore.attribute.glide_speed_multiplier",
0, 0, 1024, true, Attribute.Sentiment.POSITIVE);

/**
* Determine how fast the player can go inside Lava.
*/
public static final Holder<Attribute> LAVA_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID,
"lava_speed_multiplier", "manascore.attribute.lava_speed_multiplier",
1, 0, 1024, true, Attribute.Sentiment.POSITIVE);

/**
* Determine how fast the player can swim in Water.
* Similar to NeoForge/Forge's Swim speed instead of Vanilla's Water Movement Efficiency.
*/
public static final Holder<Attribute> SWIM_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID,
"swim_speed_multiplier", "manascore.attribute.swim_speed_multiplier",
1, 0, 1024, true, Attribute.Sentiment.POSITIVE);

public static ResourceKey<Attribute> getResourceKey(String modID, String path) {
return ResourceKey.create(Registries.ATTRIBUTE, ResourceLocation.fromNamespaceAndPath(modID, path));
}

public static void init() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.impl.network;

import io.github.manasmods.manascore.attribute.impl.network.c2s.RequestGlideStartPacket;
import io.github.manasmods.manascore.network.api.util.NetworkUtils;

public class ManasCoreAttributeNetwork {
public static void init() {
NetworkUtils.registerC2SPayload(RequestGlideStartPacket.TYPE, RequestGlideStartPacket.STREAM_CODEC, RequestGlideStartPacket::handle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.impl.network.c2s;

import dev.architectury.networking.NetworkManager;
import dev.architectury.utils.Env;
import io.github.manasmods.manascore.attribute.ModuleConstants;
import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;

public record RequestGlideStartPacket() implements CustomPacketPayload {
public static final Type<RequestGlideStartPacket> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_glide_start"));
public static final StreamCodec<FriendlyByteBuf, RequestGlideStartPacket> STREAM_CODEC = CustomPacketPayload.codec(RequestGlideStartPacket::encode, RequestGlideStartPacket::new);

public RequestGlideStartPacket(FriendlyByteBuf buf) {
this();
}

public void encode(FriendlyByteBuf buf) {
}

public void handle(NetworkManager.PacketContext context) {
if (context.getEnvironment() != Env.SERVER) return;
context.queue(() -> {
Player player = context.getPlayer();
if (player == null) return;
player.stopFallFlying();
if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid()))
player.startFallFlying();
});
}

public @NotNull Type<RequestGlideStartPacket> type() {
return TYPE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.mixin;

import io.github.manasmods.manascore.attribute.api.AttributeEvents;
import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils;
import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes;
import io.github.manasmods.manascore.network.api.util.Changeable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;

@Mixin(LivingEntity.class)
public abstract class MixinLivingEntity extends Entity {
public MixinLivingEntity(EntityType<?> entityType, Level level) {
super(entityType, level);
}

@ModifyArg(method = "updateFallFlying", at = @At(value = "INVOKE",
target = "net/minecraft/world/entity/LivingEntity.setSharedFlag(IZ)V"))
private boolean updateFallFlying(boolean value) {
LivingEntity glider = (LivingEntity) (Object) this;
Changeable<Boolean> glide = Changeable.of(value || ManasCoreAttributeUtils.canElytraGlide(glider, this.getSharedFlag(7)));
if (AttributeEvents.CONTINUE_GLIDE_EVENT.invoker().glide(glider, glide).isFalse()) return false;
return glide.get();
}

@ModifyArg(method = "travel", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;move(Lnet/minecraft/world/entity/MoverType;Lnet/minecraft/world/phys/Vec3;)V", ordinal = 2), index = 1)
public Vec3 glideSpeed(Vec3 vec3) {
LivingEntity entity = (LivingEntity) (Object) this;
AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER);
if (instance == null || instance.getValue() <= 0) return vec3;
return vec3.multiply(instance.getValue(), 1, instance.getValue());
}

@ModifyArg(method = "travel", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 1))
public float lavaSpeed(float speed) {
LivingEntity entity = (LivingEntity) (Object) this;
AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.LAVA_SPEED_MULTIPLIER);
if (instance == null) return speed;
return (float) (speed * instance.getValue());
}
Comment on lines +46 to +53
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Attribute values below zero will reverse movement

lavaSpeed (and similarly swimSpeed) multiplies the speed by instance.getValue() without clamping.
A negative or zero attribute (possible via data-pack or malicious packet) will halt or invert motion.

-        return (float) (speed * instance.getValue());
+        double modifier = Math.max(0, instance.getValue());
+        return (float) (speed * modifier);

Add safety clamps to maintain sane gameplay behaviour.

📝 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
@ModifyArg(method = "travel", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 1))
public float lavaSpeed(float speed) {
LivingEntity entity = (LivingEntity) (Object) this;
AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.LAVA_SPEED_MULTIPLIER);
if (instance == null) return speed;
return (float) (speed * instance.getValue());
}
@ModifyArg(method = "travel", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 1))
public float lavaSpeed(float speed) {
LivingEntity entity = (LivingEntity) (Object) this;
AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.LAVA_SPEED_MULTIPLIER);
if (instance == null) return speed;
double modifier = Math.max(0, instance.getValue());
return (float) (speed * modifier);
}


@ModifyArg(method = "travel", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 0))
public float swimSpeed(float speed) {
LivingEntity entity = (LivingEntity) (Object) this;
AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.SWIM_SPEED_MULTIPLIER);
if (instance == null) return speed;
return (float) (speed * instance.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2025. ManasMods
* GNU General Public License 3
*/

package io.github.manasmods.manascore.attribute.mixin;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import dev.architectury.networking.NetworkManager;
import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils;
import io.github.manasmods.manascore.attribute.impl.network.c2s.RequestGlideStartPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin({LocalPlayer.class})
public class MixinLocalPlayer {
@Inject(method = "aiStep", at = @At(value = "INVOKE_ASSIGN",
target = "net/minecraft/client/player/LocalPlayer.getItemBySlot(Lnet/minecraft/world/entity/EquipmentSlot;)Lnet/minecraft/world/item/ItemStack;"))
public void canStartGliding(CallbackInfo cb) {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) {
player.startFallFlying();
NetworkManager.sendToServer(new RequestGlideStartPacket());
}
}

@WrapOperation( method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;tryToStartFallFlying()Z"))
public boolean shouldActivateEquippedElytra(LocalPlayer player, Operation<Boolean> original) {
if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) return false;
return original.call(player);
}
}
Loading