From c3096b320e5f6ba199d39a0e629b4d13e8031ccd Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 3 Mar 2026 04:06:34 -0500 Subject: [PATCH] Fix Critical and Attribute Handling for CNPC+ and DBC Addon --- .../npcs/util/AttributeAttackUtil.java | 32 +++++++++++++++++-- .../noppes/npcs/ScriptPlayerEventHandler.java | 27 ++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/kamkeel/npcs/util/AttributeAttackUtil.java b/src/main/java/kamkeel/npcs/util/AttributeAttackUtil.java index f2fd8bf44..5ee635058 100644 --- a/src/main/java/kamkeel/npcs/util/AttributeAttackUtil.java +++ b/src/main/java/kamkeel/npcs/util/AttributeAttackUtil.java @@ -20,6 +20,13 @@ import static kamkeel.npcs.controllers.AttributeController.getTracker; public class AttributeAttackUtil { private static final Random random = new Random(); + + /** + * Crit decision from the last LivingAttackEvent for vanilla/modded mob targets. + * Set in LivingAttackEvent, consumed in LivingHurtEvent. + * null = no roll occurred, true = critted, false = did not crit. + */ + public static Boolean lastAttackCritted = null; // --- Helper Classes & Methods --- // Container for allocation results. @@ -168,10 +175,31 @@ public class AttributeAttackUtil { public static float applyCrit(float damage, PlayerAttributeTracker tracker) { if (ConfigMain.AttributesEnabled) { + if (rollCrit(tracker)) + damage = applyCritDamage(damage, tracker); + } + return damage; + } + + /** + * Rolls for a critical hit based on the tracker's CRITICAL_CHANCE attribute. + */ + public static boolean rollCrit(PlayerAttributeTracker tracker) { + if (ConfigMain.AttributesEnabled && tracker != null) { float critChance = tracker.getAttributeValue(CustomAttributes.CRITICAL_CHANCE); + return random.nextFloat() < (critChance / 100f); + } + return false; + } + + /** + * Applies crit damage unconditionally (no chance roll). + * Use when the crit decision was already made via {@link #rollCrit}. + */ + public static float applyCritDamage(float damage, PlayerAttributeTracker tracker) { + if (ConfigMain.AttributesEnabled && tracker != null) { float critBonus = tracker.getAttributeValue(CustomAttributes.CRITICAL_DAMAGE); - if (random.nextFloat() < (critChance / 100f)) - damage = (damage * (1 + (float) ConfigMain.AttributesCriticalBoost / 100f)) + critBonus; + damage = (damage * (1 + (float) ConfigMain.AttributesCriticalBoost / 100f)) + critBonus; } return damage; } diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 27db9ae4e..3133fb43c 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -8,6 +8,7 @@ import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.addon.DBCAddon; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; +import kamkeel.npcs.controllers.data.attribute.tracker.PlayerAttributeTracker; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.type.AbilityCounter; @@ -623,8 +624,21 @@ public class ScriptPlayerEventHandler { PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) event.source.getEntity()); float attackAmount = event.ammount; - if (ConfigMain.AttributesEnabled && !DBCAddon.IsAvailable()) - attackAmount = AttributeAttackUtil.calculateOutgoing((EntityPlayer) event.source.getEntity(), attackAmount); + AttributeAttackUtil.lastAttackCritted = null; + if (ConfigMain.AttributesEnabled && !DBCAddon.IsAvailable()) { + EntityPlayer attackPlayer = (EntityPlayer) event.source.getEntity(); + attackAmount = AttributeAttackUtil.calculateOutgoing(attackPlayer, attackAmount); + // For vanilla/modded mob targets, roll crit once and store for LivingHurtEvent + if (!(event.entityLiving instanceof EntityPlayerMP) && !(event.entityLiving instanceof EntityNPCInterface)) { + PlayerAttributeTracker tracker = AttributeController.getTracker(attackPlayer); + if (AttributeAttackUtil.rollCrit(tracker)) { + AttributeAttackUtil.lastAttackCritted = true; + attackAmount = AttributeAttackUtil.applyCritDamage(attackAmount, tracker); + } else { + AttributeAttackUtil.lastAttackCritted = false; + } + } + } noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent pevent1 = new noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent((IPlayer) NpcAPI.Instance().getIEntity((EntityPlayer) event.source.getEntity()), event.entityLiving, attackAmount, event.source); cancel = cancel || EventHooks.onPlayerAttack(handler, pevent1); @@ -663,6 +677,15 @@ public class ScriptPlayerEventHandler { event.ammount = AttributeAttackUtil.calculateDamagePlayerToPlayer((EntityPlayer) source, (EntityPlayer) event.entityLiving, event.ammount); } else if (!(event.entityLiving instanceof EntityNPCInterface) && source instanceof EntityPlayer) { event.ammount = AttributeAttackUtil.calculateOutgoing((EntityPlayer) source, event.ammount); + if (AttributeAttackUtil.lastAttackCritted != null) { + // Use the crit decision from LivingAttackEvent + if (AttributeAttackUtil.lastAttackCritted) + event.ammount = AttributeAttackUtil.applyCritDamage(event.ammount, AttributeController.getTracker((EntityPlayer) source)); + AttributeAttackUtil.lastAttackCritted = null; + } else { + // Fallback: roll independently (no preceding LivingAttackEvent) + event.ammount = AttributeAttackUtil.applyCrit(event.ammount, AttributeController.getTracker((EntityPlayer) source)); + } } }