From 42b7d7e42eedd47a6117eb50c9b46f16e5988d8e Mon Sep 17 00:00:00 2001 From: Vidal Date: Fri, 20 Feb 2026 22:48:59 -0300 Subject: [PATCH 1/4] A few orb and beam changes and rotation sync for body and head during animations --- .../data/ability/type/energy/AbilityBeam.java | 4 +- .../data/ability/type/energy/AbilityOrb.java | 3 + .../controllers/data/PlayerAbilityData.java | 13 + .../animations/Ability_OrbBarrage_Active.json | 380 +++++++++--------- .../animations/Ability_OrbDual_Active.json | 4 +- 5 files changed, 210 insertions(+), 194 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java index 10643e7f2..0feda043a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java @@ -48,8 +48,8 @@ public class AbilityBeam extends AbilityEnergyProjectile impl this.cooldownTicks = 0; this.windUpTicks = 40; this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; - this.rotationMode = RotationMode.TRACK; - this.rotationPhase = LockMovementType.WINDUP_AND_ACTIVE; + this.rotationMode = RotationMode.LOCKED; + this.rotationPhase = LockMovementType.ACTIVE; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Beam_Windup"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityOrb.java index 8dd7125e6..90e0360e0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityOrb.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityOrb.java @@ -115,6 +115,9 @@ public class AbilityOrb extends AbilityEnergyProjectile implem orb.setDamage(4.0f); orb.setKnockback(0.0f); orb.setKnockbackUp(0.0f); + orb.setProjectileCount(2); + orb.setFireDelay(2); + a.setLockMovement(LockMovementType.WINDUP_AND_ACTIVE); a.setMaxRange(75.0f); a.setBurstEnabled(true); a.setBurstAmount(15); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 0e32a37e7..1762a24c5 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -23,6 +23,7 @@ import net.minecraft.util.DamageSource; import noppes.npcs.AbstractDataAbilities; import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; +import noppes.npcs.NoppesUtilPlayer; import noppes.npcs.api.ability.IPlayerAbilityData; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.controllers.ScriptController; @@ -297,6 +298,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Apply rotation and position locks after ability tick applyRotationControl(player); applyPositionLock(player); + applyAnimationRotationSync(player); // Sync lock state to client (only sends when flags change) syncAbilityStateIfNeeded(player); @@ -844,6 +846,17 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA } } + private void applyAnimationRotationSync(EntityPlayer player) { + if (rotationLocked || currentAbility == null) return; + if (currentAbility.getPhase() == AbilityPhase.IDLE || currentAbility.getPhase() == AbilityPhase.DAZED) + return; + + // TODO This is a temporary duct-tape solution to fix the animation not rotating with head rotation + if (!currentAbility.isRotationLockedForCurrentPhase() && player instanceof EntityPlayerMP) { + NoppesUtilPlayer.swingPlayerArm((EntityPlayerMP) player); + } + } + // ═══════════════════════════════════════════════════════════════════ // POSITION LOCKING (Player-specific application) // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/resources/assets/customnpcs/animations/Ability_OrbBarrage_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_OrbBarrage_Active.json index 0a0ba828c..e34dd5d21 100644 --- a/src/main/resources/assets/customnpcs/animations/Ability_OrbBarrage_Active.json +++ b/src/main/resources/assets/customnpcs/animations/Ability_OrbBarrage_Active.json @@ -1,196 +1,196 @@ { - "Loop": 1, + "Loop": 2, "Speed": 1.0f, "Frames": [ - { - "ColorMarker": 16777215, - "Comment": "", - "Speed": 3.0f, - "FrameParts": [ - { - "Pivot2": -7.0f, - "Part": "HEAD", - "Rotation1": 0.0f, - "Rotation0": 0.0f, - "Pivot0": 0.0f, - "Pivot1": 2.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": 0.0f, - "Part": "RIGHT_LEG", - "Rotation1": -31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": 18.0f - }, - { - "Pivot2": -7.0f, - "Part": "RIGHT_ARM", - "Rotation1": 0.0f, - "Rotation0": -90.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": 0.0f, - "Part": "LEFT_LEG", - "Rotation1": 31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": -18.0f - }, - { - "Pivot2": -6.0f, - "Part": "BODY", - "Rotation1": 0.0f, - "Rotation0": 25.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": -1.0f, - "Part": "LEFT_ARM", - "Rotation1": -25.0f, - "Rotation0": -90.0f, - "Pivot0": -1.0f, - "Pivot1": 1.0f, - "Rotation2": -25.0f - } - ], - "Smooth": 0b, - "Duration": 2 - }, - { - "ColorMarker": 16777215, - "Comment": "", - "Speed": 3.0f, - "FrameParts": [ - { - "Pivot2": -7.0f, - "Part": "HEAD", - "Rotation1": 0.0f, - "Rotation0": 0.0f, - "Pivot0": 0.0f, - "Pivot1": 2.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": 0.0f, - "Part": "RIGHT_LEG", - "Rotation1": -31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": 18.0f - }, - { - "Pivot2": -1.0f, - "Part": "RIGHT_ARM", - "Rotation1": 25.0f, - "Rotation0": -90.0f, - "Pivot0": 1.0f, - "Pivot1": 1.0f, - "Rotation2": 25.0f - }, - { - "Pivot2": 0.0f, - "Part": "LEFT_LEG", - "Rotation1": 31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": -18.0f - }, - { - "Pivot2": -6.0f, - "Part": "BODY", - "Rotation1": 0.0f, - "Rotation0": 25.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": -7.0f, - "Part": "LEFT_ARM", - "Rotation1": 0.0f, - "Rotation0": -90.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - } - ], - "Smooth": 0b, - "Duration": 5 - }, - { - "ColorMarker": 16777215, - "Comment": "", - "Speed": 3.0f, - "FrameParts": [ - { - "Pivot2": -7.0f, - "Part": "HEAD", - "Rotation1": 0.0f, - "Rotation0": 0.0f, - "Pivot0": 0.0f, - "Pivot1": 2.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": 0.0f, - "Part": "RIGHT_LEG", - "Rotation1": -31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": 18.0f - }, - { - "Pivot2": -7.0f, - "Part": "RIGHT_ARM", - "Rotation1": 0.0f, - "Rotation0": -90.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": 0.0f, - "Part": "LEFT_LEG", - "Rotation1": 31.0f, - "Rotation0": 6.0f, - "Pivot0": 0.0f, - "Pivot1": 0.0f, - "Rotation2": -18.0f - }, - { - "Pivot2": -6.0f, - "Part": "BODY", - "Rotation1": 0.0f, - "Rotation0": 25.0f, - "Pivot0": 0.0f, - "Pivot1": 1.0f, - "Rotation2": 0.0f - }, - { - "Pivot2": -1.0f, - "Part": "LEFT_ARM", - "Rotation1": -25.0f, - "Rotation0": -90.0f, - "Pivot0": -1.0f, - "Pivot1": 1.0f, - "Rotation2": -25.0f - } - ], - "Smooth": 0b, - "Duration": 5 - } + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": -18.0f + }, + { + "Pivot2": -6.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "RIGHT_ARM", + "Rotation1": 0.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "LEFT_ARM", + "Rotation1": -25.0f, + "Rotation0": -90.0f, + "Pivot0": -1.0f, + "Pivot1": 1.0f, + "Rotation2": -25.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": -31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 18.0f + } + ], + "Smooth": 0b, + "Duration": 5 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": -18.0f + }, + { + "Pivot2": -6.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "RIGHT_ARM", + "Rotation1": 25.0f, + "Rotation0": -90.0f, + "Pivot0": 1.0f, + "Pivot1": 1.0f, + "Rotation2": 25.0f + }, + { + "Pivot2": -7.0f, + "Part": "LEFT_ARM", + "Rotation1": 0.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": -31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 18.0f + } + ], + "Smooth": 0b, + "Duration": 5 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": -18.0f + }, + { + "Pivot2": -6.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "RIGHT_ARM", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "LEFT_ARM", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": -31.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 18.0f + } + ], + "Smooth": 0b, + "Duration": 5 + } ], "CurrentFrameTime": 0, "CurrentFrame": 0, diff --git a/src/main/resources/assets/customnpcs/animations/Ability_OrbDual_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_OrbDual_Active.json index 58a305e78..c632afc9c 100644 --- a/src/main/resources/assets/customnpcs/animations/Ability_OrbDual_Active.json +++ b/src/main/resources/assets/customnpcs/animations/Ability_OrbDual_Active.json @@ -1,5 +1,5 @@ { - "Loop": 3, + "Loop": -1, "Speed": 1.0f, "Frames": [ { @@ -252,7 +252,7 @@ } ], "Smooth": 0b, - "Duration": 3 + "Duration": 20 } ], "CurrentFrameTime": 0, From fb938c2b7d7493777a9e4e0bb78e82188bc96ac9 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 01:12:30 -0300 Subject: [PATCH 2/4] Fixed charge and orb active anim --- .../npcs/controllers/data/ability/type/AbilityCharge.java | 2 +- .../assets/customnpcs/animations/Ability_Orb_Active.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 79c16b21e..bc2da7cae 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -52,7 +52,7 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { this.windUpSound = "mob.zombie.wood"; this.activeSound = "mob.zombie.attack"; this.windUpAnimationName = "Ability_Charge_Windup"; - this.activeAnimationName = "Ability_Active_Windup"; + this.activeAnimationName = "Ability_Charge_Active"; } @Override diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Orb_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_Orb_Active.json index 994e2190c..cb8973971 100644 --- a/src/main/resources/assets/customnpcs/animations/Ability_Orb_Active.json +++ b/src/main/resources/assets/customnpcs/animations/Ability_Orb_Active.json @@ -1,11 +1,11 @@ { - "Loop": 0, + "Loop": -1, "Speed": 1.0f, "Frames": [ { "ColorMarker": 16777215, "Comment": "", - "Speed": 0.5f, + "Speed": 1.0f, "FrameParts": [ { "Pivot2": 0.0f, @@ -63,7 +63,7 @@ } ], "Smooth": 0b, - "Duration": 30 + "Duration": 20 } ], "CurrentFrameTime": 0, From 36babe51cc22e2916305c603cd628f9c479e880d Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 01:12:57 -0300 Subject: [PATCH 3/4] Added ConditionItem and increased the max amount of conditions an ability can hold to 5 --- .../npcs/controllers/AbilityController.java | 2 + .../ability/conditions/AbilityCondition.java | 4 +- .../ability/conditions/ConditionItem.java | 201 ++++++++++++++++++ .../gui/advanced/SubGuiAbilityConfig.java | 30 ++- .../advanced/SubGuiChainedAbilityConfig.java | 11 +- .../assets/customnpcs/lang/en_US.lang | 14 +- 6 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index f226a505c..1da844451 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.data.ability.*; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHPThreshold; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHitCount; +import kamkeel.npcs.controllers.data.ability.conditions.ConditionItem; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; import kamkeel.npcs.controllers.data.ability.type.AbilityDash; @@ -152,6 +153,7 @@ public class AbilityController implements IAbilityHandler { private void registerBuiltinConditionTypes() { registerCondition(ConditionHPThreshold::new); registerCondition(ConditionHitCount::new); + registerCondition(ConditionItem::new); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java index 95d38bf9b..6fc7b8a12 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java @@ -14,6 +14,8 @@ import java.util.List; import java.util.function.Supplier; public abstract class AbilityCondition { + public static final int MAX_CONDITIONS = 5; + protected String typeId = ""; protected String name = ""; protected UserType userType = UserType.BOTH; @@ -69,7 +71,7 @@ public abstract class AbilityCondition { nbt.setString("typeId", getTypeId()); nbt.setString("name", getName()); nbt.setInteger("userType", getUserType().ordinal()); - nbt.setInteger("filter", getUserType().ordinal()); + nbt.setInteger("filter", getFilter().ordinal()); writeTypeNBT(nbt); return nbt; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java new file mode 100644 index 000000000..14b751d29 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -0,0 +1,201 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.entity.EntityNPCInterface; + +import java.util.List; + +public class ConditionItem extends AbilityCondition { + + public enum UsageType { + ARMOR, HOLDING, AMMO, OFFHAND; + + public static UsageType fromOrdinal(int ordinal) { + UsageType[] values = values(); + return (ordinal >= 0 && ordinal < values.length) ? values[ordinal] : HOLDING; + } + } + + public enum ArmorSlot { + BOOTS, LEGS, CHEST, HELMET; + + public static ArmorSlot fromOrdinal(int ordinal) { + ArmorSlot[] values = values(); + return (ordinal >= 0 && ordinal < values.length) ? values[ordinal] : BOOTS; + } + } + + // Shared + private UsageType usageType = UsageType.HOLDING; + private String itemName = ""; + + // ARMOR only + private ArmorSlot armorSlot = ArmorSlot.BOOTS; + private boolean fullArmorSet = false; + + // AMMO + PLAYER only + private int requiredCount = 1; + + public ConditionItem() { + this.typeId = "condition.cnpc.item"; + this.name = "condition.item"; + } + + @Override + public boolean check(EntityLivingBase caster, EntityLivingBase target) { + switch (getFilter()) { + case CASTER: + return checkEntity(caster); + case TARGET: + return target != null && checkEntity(target); + case BOTH: + return checkEntity(caster) && (target != null && checkEntity(target)); + default: + return checkEntity(caster); + } + } + + private boolean checkEntity(EntityLivingBase entity) { + switch (usageType) { + case HOLDING: + return checkHolding(entity); + case ARMOR: + return checkArmor(entity); + case AMMO: + return checkAmmo(entity); + case OFFHAND: + return checkOffhand(entity); + default: + return false; + } + } + + private boolean checkHolding(EntityLivingBase entity) { + ItemStack held = entity.getHeldItem(); + return matchesItem(held); + } + + private boolean checkArmor(EntityLivingBase entity) { + // MC getEquipmentInSlot: 1=boots, 2=legs, 3=chest, 4=helmet + int slot = armorSlot.ordinal() + 1; // BOOTS=0, LEGS=1, CHEST=2, HELMET=3 + return matchesItem(entity.getEquipmentInSlot(slot)); + } + + private boolean checkFullArmor(EntityLivingBase entity) { + for (int i = 0; i < 4; i++) { + int slot = i + 1; + if (!matchesItem(i, entity.getEquipmentInSlot(slot))) + return false; + } + + return true; + } + + private boolean checkAmmo(EntityLivingBase entity) { + if (entity instanceof EntityPlayer) { + return checkPlayerInventoryCount((EntityPlayer) entity); + } + if (entity instanceof EntityNPCInterface) { + return checkNPCProjectile((EntityNPCInterface) entity); + } + + return false; + } + + private boolean checkOffhand(EntityLivingBase entity) { + // No offhand for players + if (entity instanceof EntityPlayer) return false; + + ItemStack item = ((EntityNPCInterface) entity).getOffHand(); + return matchesItem(item); + } + + private boolean checkPlayerInventoryCount(EntityPlayer player) { + int count = 0; + for (ItemStack stack : player.inventory.mainInventory) { + if (matchesItem(stack)) { + count += stack.stackSize; + } + } + return count >= requiredCount; + } + + private boolean checkNPCProjectile(EntityNPCInterface npc) { + ItemStack projectile = npc.inventory.getProjectile(); + return matchesItem(projectile); + } + + private boolean matchesItem(ItemStack stack) { + return matchesItem(0, stack); + } + + private boolean matchesItem(int index, ItemStack stack) { + if (stack == null || itemName == null || itemName.isEmpty()) return false; + String registryName = net.minecraft.item.Item.itemRegistry.getNameForObject(stack.getItem()); + return itemName.equals(registryName); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.enumField("condition.usage_type", UsageType.class, + this::getUsageType, this::setUsageType)); + + defs.add(FieldDef.stringField("condition.item_name", + this::getItemName, this::setItemName)); +// .hover("condition.hover.item_name")); + + + + defs.add(FieldDef.enumField("condition.armor_slot", ArmorSlot.class, + this::getArmorSlot, this::setArmorSlot) + .visibleWhen(() -> usageType == UsageType.ARMOR)); +// .hover("condition.hover.armor_slots")); + + defs.add(FieldDef.intField("condition.item_count", + this::getRequiredCount, this::setRequiredCount) + .range(1, 64) + .visibleWhen(() -> usageType == UsageType.AMMO && userType.allowsPlayer())); +// .hover("condition.hover.required_count")); + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setString("itemName", itemName); + nbt.setInteger("usageType", usageType.ordinal()); + nbt.setInteger("armorSlot", armorSlot.ordinal()); + nbt.setInteger("requiredCount", requiredCount); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + itemName = nbt.getString("itemName"); + usageType = UsageType.fromOrdinal(nbt.getInteger("usageType")); + armorSlot = ArmorSlot.fromOrdinal(nbt.getInteger("armorSlot")); + requiredCount = nbt.getInteger("requiredCount"); + } + + + public UsageType getUsageType() { return usageType; } + public void setUsageType(UsageType usageType) { this.usageType = usageType; } + + public String getItemName() { return itemName; } + public void setItemName(String itemName) { this.itemName = itemName; } + + public ArmorSlot getArmorSlot() { return armorSlot; } + public void setArmorSlot(ArmorSlot armorSlot) { this.armorSlot = armorSlot; } + + public boolean isFullArmorSet() { + return fullArmorSet; + } + + public void setFullArmorSet(boolean fullArmorSet) { + this.fullArmorSet = fullArmorSet; + } + + public int getRequiredCount() { return requiredCount; } + public void setRequiredCount(int requiredCount) { this.requiredCount = Math.max(1, requiredCount); } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java index b611e4e6b..a3cee0443 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -20,6 +20,8 @@ import noppes.npcs.client.gui.util.SubGuiInterface; import java.util.ArrayList; import java.util.List; +import static kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition.MAX_CONDITIONS; + public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener { private static final int DECLARATIVE_ID_START = 1000; @@ -42,6 +44,12 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi private static final int L_LABEL_X = 5; private static final int ROW_H = 24; + // Condition buttons + private static final int COND_BASE = 50; + private static final int COND_STRIDE = 10; + private static final int COND_END = COND_BASE + MAX_CONDITIONS * COND_STRIDE; // 50 + N*10 + private static final int BTN_ADD_COND = COND_END; + private final Ability ability; private final IAbilityConfigCallback callback; private int activeTab = TAB_GENERAL; @@ -234,17 +242,17 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi sw.addLabel(new GuiNpcLabel(labelCounter, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55)); y += 15; - for (int i = 0; i < conditions.size() && i < 3; i++) { + for (int i = 0; i < conditions.size() && i < MAX_CONDITIONS; i++) { AbilityCondition cond = conditions.get(i); String condName = getConditionDisplayName(cond); - sw.addButton(new GuiNpcButton(50 + i * 10, L_LABEL_X, y, 140, 20, condName)); - sw.addButton(new GuiNpcButton(51 + i * 10, L_LABEL_X + 145, y, 40, 20, "gui.edit")); - sw.addButton(new GuiNpcButton(52 + i * 10, L_LABEL_X + 190, y, 20, 20, "X")); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName)); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 1, L_LABEL_X + 145, y, 40, 20, "gui.edit")); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 2, L_LABEL_X + 190, y, 20, 20, "X")); y += 22; } - if (conditions.size() < 3) { - sw.addButton(new GuiNpcButton(80, L_LABEL_X, y, 50, 20, "gui.add")); + if (conditions.size() < MAX_CONDITIONS) { + sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));; y += ROW_H; } @@ -312,9 +320,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } private boolean handleConditionButton(int id) { - if (id >= 50 && id < 80) { - int condIndex = (id - 50) / 10; - int action = (id - 50) % 10; + if (id >= COND_BASE && id < COND_END) { + int condIndex = (id - COND_BASE) / COND_STRIDE; + int action = (id - COND_BASE) % COND_STRIDE; if (action == 0 || action == 1) { if (condIndex < conditions.size()) { editingConditionIndex = condIndex; @@ -328,8 +336,8 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } return true; } - if (id == 80) { - if (conditions.size() < 3) { + if (id == BTN_ADD_COND) { + if (conditions.size() < MAX_CONDITIONS) { editingConditionIndex = conditions.size(); setSubGui(new SubGuiConditionEdit(null)); } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index 9783bb9a7..d1946e2e2 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -32,6 +32,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import static kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition.MAX_CONDITIONS; /** * SubGui for editing a {@link ChainedAbility}. @@ -70,9 +71,9 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // Actual ID = COND_BASE + i * COND_STRIDE + offset // offset 0 = name/click, 1 = edit, 2 = delete private static final int COND_BASE = 50; - private static final int COND_END = 80; private static final int COND_STRIDE = 10; - private static final int BTN_ADD_COND = 80; + private static final int COND_END = COND_BASE + MAX_CONDITIONS * COND_STRIDE; // 50 + N*10 + private static final int BTN_ADD_COND = COND_END; // ── Layout constants ────────────────────────────────────────────────────── private static final int L_LABEL_X = 5; @@ -427,7 +428,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText sw.addLabel(new GuiNpcLabel(labelCounter, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55)); y += 15; - for (int i = 0; i < conditions.size() && i < 3; i++) { + for (int i = 0; i < conditions.size() && i < MAX_CONDITIONS; i++) { AbilityCondition cond = conditions.get(i); String condName = getConditionDisplayName(cond); sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName)); @@ -436,7 +437,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText y += 22; } - if (conditions.size() < 3) { + if (conditions.size() < MAX_CONDITIONS) { sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add")); y += ROW_H; } @@ -615,7 +616,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return true; } if (id == BTN_ADD_COND) { - if (conditions.size() < 3) { + if (conditions.size() < MAX_CONDITIONS) { editingConditionIndex = conditions.size(); setSubGui(new SubGuiConditionEdit(null)); } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index c6d745706..d32b62471 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2083,15 +2083,27 @@ condition.filter=Filter Type condition.filter.target=Target condition.filter.caster=Caster condition.filter.both=Both -condition.hit_count=Hit Count + +# Condition HP Threshold condition.hp_threshold=HP Threshold condition.hp_above=HP Above condition.hp_below=HP Below condition.threshold=Threshold condition.threshold_type=Threshold Type + +# Condition Hit Count +condition.hit_count=Hit Count condition.required_hits=Required Hits condition.within_ticks=Within (ticks) +# Condition Item +condition.item=Has Item +condition.usage_type=Usage Type +condition.armor_slot=Armor Slot +condition.full_armor=Full Armor +condition.item_count=Item Count +condition.item_name=Item Name + # Condition Hover Tooltips condition.hover.type=Condition type that must be met for ability to activate condition.hover.hpAbove=Activates when NPC health is above this percentage From acb99b5c48f53ea568cc6745b3347277d82e343d Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 01:21:33 -0300 Subject: [PATCH 4/4] Added percent option to ConditionHPThreshold and added EQUAL comparison type --- .../conditions/ConditionHPThreshold.java | 52 +++++++++++++------ .../assets/customnpcs/lang/en_US.lang | 2 + 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java index 803fc978c..5eed753fa 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java @@ -8,19 +8,26 @@ import java.util.List; public class ConditionHPThreshold extends AbilityCondition{ private float threshold = 0.5f; // 50% + private boolean percent = true; private ThresholdType thresholdType = ThresholdType.ABOVE; public enum ThresholdType { ABOVE { @Override public boolean test(float value, float threshold) { - return value >= threshold; + return value > threshold; } }, BELOW { @Override public boolean test(float value, float threshold) { - return value <= threshold; + return value < threshold; + } + }, + EQUAL { + @Override + public boolean test(float value, float threshold) { + return value == threshold; } }; @@ -41,6 +48,8 @@ public class ConditionHPThreshold extends AbilityCondition{ return "condition.hp_above"; case BELOW: return "condition.hp_below"; + case EQUAL: + return "condition.hp_equal"; default: return name(); } @@ -54,8 +63,8 @@ public class ConditionHPThreshold extends AbilityCondition{ @Override public boolean check(EntityLivingBase caster, EntityLivingBase target) { - float casterHP = caster.getHealth() / caster.getMaxHealth(); - float targetHP = target.getHealth() / target.getMaxHealth(); + float casterHP = isPercent() ? caster.getHealth() / caster.getMaxHealth() : caster.getHealth(); + float targetHP = isPercent() ? target.getHealth() / target.getMaxHealth() : target.getHealth(); return compare(casterHP, targetHP); @@ -75,6 +84,24 @@ public class ConditionHPThreshold extends AbilityCondition{ } } + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.floatField("condition.threshold", this::getThreshold, this::setThreshold).min(0)); + defs.add(FieldDef.boolField("condition.percent", this::isPercent, this::setIsPercent)); + defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class, + this::getThresholdType, this::setThresholdType)); + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("threshold", threshold); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + threshold = nbt.getFloat("threshold"); + } + public float getThreshold() { return threshold; } @@ -91,20 +118,11 @@ public class ConditionHPThreshold extends AbilityCondition{ this.thresholdType = thresholdType; } - @Override - public void getConditionDefinitions(List defs) { - defs.add(FieldDef.floatField("condition.threshold", this::getThreshold, this::setThreshold).min(0)); - defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class, - this::getThresholdType, this::setThresholdType)); + public boolean isPercent() { + return percent; } - @Override - public void writeTypeNBT(NBTTagCompound nbt) { - nbt.setFloat("threshold", threshold); - } - - @Override - public void readTypeNBT(NBTTagCompound nbt) { - threshold = nbt.getFloat("threshold"); + public void setIsPercent(boolean percent) { + this.percent = percent; } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index d32b62471..b3e09b08f 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2088,7 +2088,9 @@ condition.filter.both=Both condition.hp_threshold=HP Threshold condition.hp_above=HP Above condition.hp_below=HP Below +condition.hp_equal=HP Equal condition.threshold=Threshold +condition.percent=Use Percent condition.threshold_type=Threshold Type # Condition Hit Count