From 42b7d7e42eedd47a6117eb50c9b46f16e5988d8e Mon Sep 17 00:00:00 2001 From: Vidal Date: Fri, 20 Feb 2026 22:48:59 -0300 Subject: [PATCH 01/71] 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 02/71] 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 03/71] 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 04/71] 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 From e711a9012b7c7ee1c7ca8b92fb55b1ed7ed865bf Mon Sep 17 00:00:00 2001 From: Kam Date: Fri, 20 Feb 2026 23:55:32 -0500 Subject: [PATCH 05/71] Fix barrier solid/knockback behavior and dome melee targeting - Rewrite solid system to use reactive crossing detection (prevPos comparison) instead of prediction-based teleportation that caused client rubber-banding - Reduce knockback force (0.06 multiplier) so it pushes without fully blocking - Add client-side solid prediction for smooth movement blocking (local player only) - Fix dome melee from inside: canBeCollidedWith returns false when player is inside - Fix dome melee from outside: getCollisionBorderSize set to 0 to prevent raycast from returning far exit point beyond reach distance - Clean up dome bounding box: use setPosition/updateBoundingBox instead of custom setSize Co-Authored-By: Claude Opus 4.6 --- .../kamkeel/npcs/entity/EntityEnergyDome.java | 221 +++++++++++------- .../npcs/entity/EntityEnergyPanel.java | 125 ++++++---- 2 files changed, 214 insertions(+), 132 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 0ad5a4844..e4fb4a158 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -7,10 +7,12 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import noppes.npcs.CustomNpcs; import java.util.List; @@ -53,15 +55,7 @@ public class EntityEnergyDome extends EntityEnergyBarrier { @Override protected void setSize(float width, float height) { - if (width != this.width || height != this.height) { - this.width = width; - this.height = height; - this.boundingBox.maxX = this.boundingBox.minX + (double) this.width; - this.boundingBox.maxZ = this.boundingBox.minZ + (double) this.width; - this.boundingBox.maxY = this.boundingBox.minY + (double) this.height; - } - this.ySize = 0.0F; - this.yOffset = -this.height * 0.5F; + super.setSize(width, height); } @Override @@ -102,9 +96,11 @@ public class EntityEnergyDome extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback velocity push every tick - if (!worldObj.isRemote && barrierData.knockbackEnabled) { - knockbackEntities(); + // Process entity physics every tick + // Server: solid + knockback for all entities + // Client: solid prediction for local player only (smooth movement blocking) + if (barrierData.solid || barrierData.knockbackEnabled) { + processEntityPhysics(); } } @@ -210,49 +206,50 @@ public class EntityEnergyDome extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK (DBO-style motion prediction) ==================== + // ==================== ENTITY PHYSICS (Solid + Knockback) ==================== /** - * DBO Ki Shield style knockback — unified algorithm with geometric jail: + * Processes entity physics for the dome barrier. Two independent systems: *

- * When jailEnabled = false (default): - * - OUTSIDE entities: predict future position, if it would cross into dome push outward. - * If close and approaching, apply soft outward push. Full motion replacement. - * - INSIDE entities: NOT affected (free movement). Dome only blocks entry. + * SOLID: Hard wall — entities cannot cross the dome boundary in either direction. + * Uses reactive crossing detection: compares current position to previous tick + * to detect boundary crossings, then teleports back and applies velocity correction. + * Also preemptively cancels inward velocity when near the surface. *

- * When jailEnabled = true: - * - OUTSIDE entities: pushed outward (same as above, prevents entry). - * - INSIDE entities: pushed inward when near edge (geometric containment). - * Any entity inside stays inside, any entity outside stays outside. + * KNOCKBACK: Repulsion force — entities near the dome surface get pushed away. + * This is a push effect only; entities can still pass through with enough effort. + * Does NOT prevent passthrough — use solid for that. *

* For moving domes (followCaster), dome velocity is accounted for by using - * relative velocity (entity motion minus dome motion) for predictions. + * the dome's previous position for previous-tick distance calculations. */ @Override @SuppressWarnings("unchecked") - protected void knockbackEntities() { - float margin = 2.0f; + protected void processEntityPhysics() { float r = domeRadius; + boolean solid = barrierData.solid; + boolean knockback = barrierData.knockbackEnabled; float strength = barrierData.knockbackStrength; - boolean jail = barrierData.jailEnabled; - float jailThresh = barrierData.jailThreshold; - // Dome velocity for moving domes (followCaster) - double domeVelX = posX - prevPosX; - double domeVelY = posY - prevPosY; - double domeVelZ = posZ - prevPosZ; + float solidMargin = 1.5f; // Zone around surface for preemptive velocity correction + float knockbackMargin = 2.0f; // Zone around surface for knockback push AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( - posX - r - margin, posY - r - margin, posZ - r - margin, - posX + r + margin, posY + r + margin, posZ + r + margin + posX - r - knockbackMargin, posY - r - knockbackMargin, posZ - r - knockbackMargin, + posX + r + knockbackMargin, posY + r + knockbackMargin, posZ + r + knockbackMargin ); + // On client, identify local player for client-side solid prediction + EntityPlayer localPlayer = worldObj.isRemote ? CustomNpcs.proxy.getPlayer() : null; + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; + // Client-side: only process local player for solid prediction + if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + // Current position relative to dome center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; @@ -265,71 +262,76 @@ public class EntityEnergyDome extends EntityEnergyBarrier { double ny = dy / dist; double nz = dz / dist; - // Entity velocity relative to dome (accounts for moving domes) - double relVelX = ent.motionX - domeVelX; - double relVelY = ent.motionY - domeVelY; - double relVelZ = ent.motionZ - domeVelZ; - boolean isInside = dist < r; - if (isInside && jail) { - // --- INSIDE + JAIL ENABLED: push toward center when near dome edge --- - if (dist > r * jailThresh) { - double overshoot = dist - r * jailThresh; - double force = (overshoot + 0.1) * strength * 0.2; - // Set motion to dome velocity + inward push (keeps entity moving with dome) - ent.motionX = domeVelX - nx * force; - ent.motionY = domeVelY - ny * force; - ent.motionZ = domeVelZ - nz * force; + // --- SOLID: hard wall preventing boundary crossing --- + if (solid) { + // Previous tick: entity position relative to dome's previous position + double prevDx = ent.prevPosX - prevPosX; + double prevDy = (ent.prevPosY + ent.height * 0.5) - prevPosY; + double prevDz = ent.prevPosZ - prevPosZ; + double prevDist = Math.sqrt(prevDx * prevDx + prevDy * prevDy + prevDz * prevDz); + boolean wasInside = prevDist < r; + + // Radial velocity (positive = moving outward from dome center) + double radialVel = ent.motionX * nx + ent.motionY * ny + ent.motionZ * nz; + + if (wasInside != isInside) { + // Entity crossed the boundary this tick — push back to original side + double pushDist = wasInside ? (r - 0.3) : (r + 0.3); + double surfaceX = posX + nx * pushDist; + double surfaceY = posY + ny * pushDist - ent.height * 0.5; + double surfaceZ = posZ + nz * pushDist; + teleportEntity(ent, surfaceX, surfaceY, surfaceZ); + + // Cancel radial velocity and add corrective push + ent.motionX -= radialVel * nx; + ent.motionY -= radialVel * ny; + ent.motionZ -= radialVel * nz; + double pushForce = wasInside ? -0.15 : 0.15; + ent.motionX += nx * pushForce; + ent.motionZ += nz * pushForce; ent.velocityChanged = true; } else { - // Predict if entity would escape next tick (relative to dome) - double futDx = dx + relVelX; - double futDy = dy + relVelY; - double futDz = dz + relVelZ; - double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); - if (futDist >= r) { - // Would escape: cancel outward motion, match dome velocity - double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; - if (radialVel > 0) { + // Preemptive: near surface and moving toward it — cancel radial velocity + double surfaceDist = Math.abs(dist - r); + if (surfaceDist < solidMargin) { + boolean movingTowardSurface = (!isInside && radialVel < -0.01) + || (isInside && radialVel > 0.01); + if (movingTowardSurface) { ent.motionX -= radialVel * nx; ent.motionY -= radialVel * ny; ent.motionZ -= radialVel * nz; + double pushForce = isInside ? -0.05 : 0.05; + ent.motionX += nx * pushForce; + ent.motionZ += nz * pushForce; ent.velocityChanged = true; } } } - } else if (!isInside) { - // --- OUTSIDE: push outward (prevent entry) --- + } - // Predicted future distance from center (relative to dome movement) - double futDx = dx + relVelX; - double futDy = dy + relVelY; - double futDz = dz + relVelZ; - double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); + // --- KNOCKBACK: repulsion push (server only, synced via velocityChanged) --- + if (!worldObj.isRemote && knockback) { + double surfaceDist = Math.abs(dist - r); + if (surfaceDist < knockbackMargin) { + double proximity = 1.0 - (surfaceDist / knockbackMargin); + double force = proximity * strength * 0.06; - // Radial velocity relative to dome (positive = moving away) - double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; - - if (futDist < r) { - // Would cross INTO dome: hard push outward proportional to overshoot - double overshoot = r - futDist; - double force = (overshoot * 2.0 + 0.05) * strength * 0.1; - ent.motionX = domeVelX + nx * force; - ent.motionY = domeVelY + ny * force * 0.5; - ent.motionZ = domeVelZ + nz * force; - ent.velocityChanged = true; - } else if (radialVel < 0 && dist < r + margin) { - // Close and approaching: soft push outward proportional to proximity - double proximity = 1.0 - ((dist - r) / margin); - double force = proximity * strength * 0.1; - ent.motionX = domeVelX + nx * force; - ent.motionY = domeVelY + ny * force * 0.5; - ent.motionZ = domeVelZ + nz * force; + if (isInside) { + // Push toward center (away from surface) + ent.motionX += -nx * force; + ent.motionY += -ny * force * 0.5; + ent.motionZ += -nz * force; + } else { + // Push outward (away from surface) + ent.motionX += nx * force; + ent.motionY += ny * force * 0.5; + ent.motionZ += nz * force; + } ent.velocityChanged = true; } } - // else: INSIDE + no jail → do nothing (free movement) } } @@ -346,6 +348,24 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== MELEE (spherical check) ==================== + /** + * On the client side, the dome's large bounding box intercepts all raycasts from inside, + * preventing the player from targeting mobs within the dome. To fix this, return false + * when the local player is inside the dome so the raycast passes through to other entities. + * Players outside the dome can still target it for melee normally. + */ + @Override + public boolean canBeCollidedWith() { + if (!barrierData.meleeEnabled) return false; + if (worldObj.isRemote) { + EntityPlayer player = CustomNpcs.proxy.getPlayer(); + if (player != null && isEntityInside(player)) { + return false; + } + } + return true; + } + /** * Reject melee hits that land on the cubic bounding box but are outside the actual sphere. * Uses distance from the sphere surface (not center) for accurate spherical rejection. @@ -368,13 +388,14 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== BOUNDING BOX ==================== /** - * Small expansion on top of the dome-sized bounding box for comfortable - * melee targeting. The actual BB is already dome-sized (set in onUpdate), - * so this only adds a small reach margin for crosshair ray-testing. + * No expansion needed — the bounding box is already dome-sized. + * Returning 0 ensures players outside the dome sphere are also outside the + * ray-testing BB, so MC's raycast returns the near-face hit (within reach) + * rather than the far exit point (beyond reach). */ @Override public float getCollisionBorderSize() { - return 1.0f; + return 0.0f; } @Override @@ -414,7 +435,7 @@ public class EntityEnergyDome extends EntityEnergyBarrier { public void setDomeRadius(float radius) { this.domeRadius = Math.max(0.1f, radius); - this.setSize(this.domeRadius * 2, this.domeRadius * 2); + this.setPosition(this.posX, this.posY, this.posZ); } public boolean isFollowCaster() { @@ -425,6 +446,30 @@ public class EntityEnergyDome extends EntityEnergyBarrier { this.followCaster = follow; } + @Override + @SideOnly(Side.CLIENT) + public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int increments) { + // Don't do the vanilla "push up out of blocks" logic. + this.setPosition(x, y, z); + this.setRotation(yaw, pitch); + } + + @Override + public void setPosition(double x, double y, double z) { + this.posX = x; + this.posY = y; + this.posZ = z; + updateBoundingBox(); + } + + protected void updateBoundingBox() { + float r = this.domeRadius; + this.boundingBox.setBounds( + this.posX - r, this.posY - r, this.posZ - r, + this.posX + r, this.posY + r, this.posZ + r + ); + } + // ==================== NBT ==================== @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index cd33f6460..1ef8dc370 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -13,6 +13,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import noppes.npcs.CustomNpcs; import noppes.npcs.NpcDamageSource; import noppes.npcs.entity.EntityNPCInterface; @@ -119,9 +120,11 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback velocity push every tick (not during launched mode) - if (!worldObj.isRemote && barrierData.knockbackEnabled && mode != PanelMode.LAUNCHED) { - knockbackEntities(); + // Process entity physics every tick (not during launched mode) + // Server: solid + knockback for all entities + // Client: solid prediction for local player only (smooth movement blocking) + if ((barrierData.solid || barrierData.knockbackEnabled) && mode != PanelMode.LAUNCHED) { + processEntityPhysics(); } // Mode-specific updates @@ -365,21 +368,28 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK (DBO-style motion prediction) ==================== + // ==================== ENTITY PHYSICS (Solid + Knockback) ==================== /** - * DBO-inspired planar knockback: - * - Predict future position from current motion - * - Detect crossing or proximity to panel surface - * - Apply counter-force via full motion replacement - * - No separate phases — single unified algorithm + * Processes entity physics for the panel barrier. Two independent systems: + *

+ * SOLID: Hard wall — entities cannot cross the panel plane within its bounds. + * Uses reactive crossing detection: compares current side to previous tick + * to detect plane crossings, then teleports back and applies velocity correction. + * Also preemptively cancels normal velocity when near the plane. + *

+ * KNOCKBACK: Repulsion force — entities near the panel surface get pushed away. + * This is a push effect only; entities can still pass through with enough effort. + * Does NOT prevent passthrough — use solid for that. */ @Override @SuppressWarnings("unchecked") - protected void knockbackEntities() { + protected void processEntityPhysics() { float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; - float searchExtension = 5.0f; + float searchExtension = 3.0f; + boolean solid = barrierData.solid; + boolean knockback = barrierData.knockbackEnabled; float strength = barrierData.knockbackStrength; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( @@ -387,6 +397,9 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { posX + halfW + searchExtension, posY + halfH + searchExtension, posZ + halfW + searchExtension ); + // On client, identify local player for client-side solid prediction + EntityPlayer localPlayer = worldObj.isRemote ? CustomNpcs.proxy.getPlayer() : null; + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); float yawRad = (float) Math.toRadians(panelYaw); float normalX = -(float) Math.sin(yawRad); @@ -394,9 +407,11 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; + // Client-side: only process local player for solid prediction + if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + // Current position relative to panel center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; @@ -407,45 +422,67 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { float localRight = (float) (dx * (-normalZ) + dz * normalX); // Skip if outside panel bounds (with margin) - if (Math.abs(localRight) > halfW + searchExtension) continue; - if (Math.abs(dy) > halfH + searchExtension) continue; - if (Math.abs(localForward) > searchExtension) continue; - - // Skip if outside panel width/height bounds (no wall to push against) if (Math.abs(localRight) > halfW + 1.0f) continue; if (Math.abs(dy) > halfH + 1.0f) continue; - - // Predicted future position - double futDx = (ent.posX + ent.motionX) - posX; - double futDz = (ent.posZ + ent.motionZ) - posZ; - float futLocalForward = (float) (futDx * normalX + futDz * normalZ); - - // Normal velocity component (positive = moving in normal direction) - double normalVel = ent.motionX * normalX + ent.motionZ * normalZ; + if (Math.abs(localForward) > searchExtension) continue; // Determine which side the entity is on float side = localForward >= 0 ? 1.0f : -1.0f; - boolean movingTowardPanel = (localForward >= 0 && normalVel < 0) || (localForward < 0 && normalVel > 0); - // Check if predicted position would cross the plane (sign change) - boolean wouldCross = (localForward > 0 && futLocalForward <= 0) || (localForward < 0 && futLocalForward >= 0); + // Check if within panel width/height bounds + boolean inBounds = Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH; - if (wouldCross && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { - // Hard block: full motion replacement to prevent crossing - // Push back along normal, proportional to how far past the surface they'd go - double overshoot = Math.abs(futLocalForward); - double pushForce = (overshoot + 0.05) * strength * 0.1; - ent.motionX = normalX * side * pushForce; - ent.motionZ = normalZ * side * pushForce; - ent.velocityChanged = true; - } else if (Math.abs(localForward) < 1.0 && movingTowardPanel - && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { - // Soft push: entity is close and approaching, apply proportional force - double proximity = 1.0 - Math.abs(localForward); - double pushForce = proximity * strength * 0.1; - ent.motionX = normalX * side * pushForce; - ent.motionZ = normalZ * side * pushForce; - ent.velocityChanged = true; + // --- SOLID: hard wall preventing plane crossing --- + if (solid && inBounds) { + // Previous tick: entity position relative to panel + double prevDx = ent.prevPosX - prevPosX; + double prevDz = ent.prevPosZ - prevPosZ; + float prevLocalForward = (float) (prevDx * normalX + prevDz * normalZ); + float prevSide = prevLocalForward >= 0 ? 1.0f : -1.0f; + + // Normal velocity component (positive = moving in normal direction) + double normalVel = ent.motionX * normalX + ent.motionZ * normalZ; + + if (prevSide != side) { + // Entity crossed the panel plane this tick — push back to original side + double pushDist = 0.3; + double newX = posX + normalX * prevSide * pushDist + (localRight * (-normalZ)); + double newZ = posZ + normalZ * prevSide * pushDist + (localRight * normalX); + teleportEntity(ent, newX, ent.posY, newZ); + + // Cancel normal velocity and add corrective push + ent.motionX -= normalVel * normalX; + ent.motionZ -= normalVel * normalZ; + ent.motionX += normalX * prevSide * 0.15; + ent.motionZ += normalZ * prevSide * 0.15; + ent.velocityChanged = true; + } else { + // Preemptive: near the plane and moving toward it — cancel normal velocity + double absDist = Math.abs(localForward); + if (absDist < 1.5) { + boolean movingToward = (side > 0 && normalVel < -0.01) + || (side < 0 && normalVel > 0.01); + if (movingToward) { + ent.motionX -= normalVel * normalX; + ent.motionZ -= normalVel * normalZ; + ent.motionX += normalX * side * 0.05; + ent.motionZ += normalZ * side * 0.05; + ent.velocityChanged = true; + } + } + } + } + + // --- KNOCKBACK: repulsion push (server only, synced via velocityChanged) --- + if (!worldObj.isRemote && knockback && inBounds) { + double absDist = Math.abs(localForward); + if (absDist < searchExtension) { + double proximity = 1.0 - (absDist / searchExtension); + double force = proximity * strength * 0.06; + ent.motionX += normalX * side * force; + ent.motionZ += normalZ * side * force; + ent.velocityChanged = true; + } } } } From b3357a27512e9e6bb935f403c78026693dd9e29c Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 00:57:49 -0500 Subject: [PATCH 06/71] Fix dome melee for all radii with server-side tick detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MC's processUseEntity uses player.getDistanceSqToEntity(dome) for reach validation, which computes center-to-center distance. For domes with radius > 5, this always exceeds the 36-block threshold, making melee impossible regardless of how close the player is to the surface. Fix: server-side processMeleeHits() in onUpdate detects swinging players near the dome surface via ray-sphere intersection, then calls attackTargetEntityWithCurrentItem directly — bypassing MC's broken reach check while preserving full damage calculation (enchantments, crits, etc). Also adds adjustMeleeBoundingBox for client-side entity picking (places a small 2x2x2 BB at the nearest sphere surface point toward the player), and allows melee targeting from both inside and outside the dome. Co-Authored-By: Claude Opus 4.6 --- .../kamkeel/npcs/entity/EntityEnergyDome.java | 151 ++++++++++++++++-- 1 file changed, 138 insertions(+), 13 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index e4fb4a158..3d4fabe44 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -11,9 +11,11 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.Vec3; import net.minecraft.world.World; import noppes.npcs.CustomNpcs; +import java.util.HashSet; import java.util.List; /** @@ -29,6 +31,10 @@ public class EntityEnergyDome extends EntityEnergyBarrier { protected float targetDomeRadius = 5.0f; protected boolean followCaster = false; + // Server-side melee detection state + private boolean inTickMelee = false; + private final HashSet processedMeleeSwings = new HashSet<>(); + public EntityEnergyDome(World world) { super(world); this.noClip = true; @@ -96,12 +102,25 @@ public class EntityEnergyDome extends EntityEnergyBarrier { if (updateBarrierTick()) return; + // Server-side: detect and apply melee hits from nearby players. + // Bypasses MC's processUseEntity reach check (which uses center-to-center + // distance and fails for domes with radius > 5). + if (!worldObj.isRemote && barrierData.meleeEnabled) { + processMeleeHits(); + } + // Process entity physics every tick // Server: solid + knockback for all entities // Client: solid prediction for local player only (smooth movement blocking) if (barrierData.solid || barrierData.knockbackEnabled) { processEntityPhysics(); } + + // Client-side: adjust BB for accurate melee targeting at all approach angles. + // Must run AFTER all position/BB updates and BEFORE getMouseOver() in the render phase. + if (worldObj.isRemote && barrierData.meleeEnabled && !isCharging()) { + adjustMeleeBoundingBox(); + } } // ==================== INCOMING CHECK ==================== @@ -349,29 +368,74 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== MELEE (spherical check) ==================== /** - * On the client side, the dome's large bounding box intercepts all raycasts from inside, - * preventing the player from targeting mobs within the dome. To fix this, return false - * when the local player is inside the dome so the raycast passes through to other entities. - * Players outside the dome can still target it for melee normally. + * Controls whether MC's entity picking (crosshair raycast) can target this dome. + * Returns false when melee is disabled or the dome is still charging. + *

+ * Inside players CAN target the dome because adjustMeleeBoundingBox places a small BB + * at the nearest surface point, allowing mobs in other directions to still be targeted. */ @Override public boolean canBeCollidedWith() { if (!barrierData.meleeEnabled) return false; - if (worldObj.isRemote) { - EntityPlayer player = CustomNpcs.proxy.getPlayer(); - if (player != null && isEntityInside(player)) { - return false; - } - } + if (isCharging()) return false; return true; } /** - * Reject melee hits that land on the cubic bounding box but are outside the actual sphere. - * Uses distance from the sphere surface (not center) for accurate spherical rejection. + * Client-side: Repositions the bounding box to a small cube at the nearest sphere surface + * point toward the player. This fixes melee targeting at diagonal approach angles where the + * player would otherwise be inside the dome-sized cubic BB but outside the actual sphere, + * causing MC's AABB raycast to return the far exit point (beyond reach distance). + *

+ * With a small BB (2x2x2) at the surface point, even if the player is inside it at close + * range, the exit point is at most ~1.7 blocks away — well within melee reach. + */ + private void adjustMeleeBoundingBox() { + EntityPlayer player = CustomNpcs.proxy.getPlayer(); + if (player == null) { + updateBoundingBox(); + return; + } + + double dx = player.posX - posX; + double dy = (player.posY + player.getEyeHeight()) - posY; + double dz = player.posZ - posZ; + double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + + // Only adjust when player is within reasonable melee range of dome surface + if (dist < 0.01 || dist > domeRadius + 6.0) { + updateBoundingBox(); + return; + } + + // Nearest point on sphere surface toward player + double nx = dx / dist; + double ny = dy / dist; + double nz = dz / dist; + double surfX = posX + nx * domeRadius; + double surfY = posY + ny * domeRadius; + double surfZ = posZ + nz * domeRadius; + + float s = 1.0f; + this.boundingBox.setBounds( + surfX - s, surfY - s, surfZ - s, + surfX + s, surfY + s, surfZ + s + ); + } + + /** + * Player melee is handled by processMeleeHits (server-side tick detection) to bypass + * MC's processUseEntity reach check which uses center-to-center distance. + * Direct player melee ("player" damage type) is rejected unless inTickMelee is set, + * preventing double-hits when both the C02 path and tick detection fire. + * Non-player melee (mobs) and all other damage sources pass through with sphere validation. */ @Override public boolean attackEntityFrom(DamageSource source, float amount) { + // Player melee: only allow from tick-based detection + if (!inTickMelee && "player".equals(source.damageType)) { + return false; + } if (source.getEntity() != null) { Entity attacker = source.getEntity(); double dx = attacker.posX - this.posX; @@ -379,12 +443,73 @@ public class EntityEnergyDome extends EntityEnergyBarrier { double dz = attacker.posZ - this.posZ; double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); double surfaceDist = Math.abs(dist - domeRadius); - // Allow hit only if attacker is within melee reach of the sphere surface if (surfaceDist > 5.0) return false; } return super.attackEntityFrom(source, amount); } + /** + * Server-side melee detection. Each tick, finds players near the dome surface who are + * swinging and looking at the dome, then applies the attack via attackTargetEntityWithCurrentItem + * (preserving enchantments, crits, fire aspect, etc.). This bypasses MC's processUseEntity + * reach check which uses center-to-center distance and fails for large domes. + */ + @SuppressWarnings("unchecked") + private void processMeleeHits() { + float r = domeRadius; + float maxReach = 6.0f; + + AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( + posX - r - maxReach, posY - r - maxReach, posZ - r - maxReach, + posX + r + maxReach, posY + r + maxReach, posZ + r + maxReach + ); + + List players = worldObj.getEntitiesWithinAABB(EntityPlayer.class, searchBox); + for (EntityPlayer player : players) { + if (player.getEntityId() == ownerEntityId) continue; + if (isAllyOfOwner(player)) continue; + + int playerId = player.getEntityId(); + + if (!player.isSwingInProgress) { + processedMeleeSwings.remove(playerId); + continue; + } + + if (processedMeleeSwings.contains(playerId)) continue; + + // Check surface distance + double dx = player.posX - posX; + double dy = (player.posY + 1.62) - posY; + double dz = player.posZ - posZ; + double distSq = dx * dx + dy * dy + dz * dz; + double dist = Math.sqrt(distSq); + double surfaceDist = Math.abs(dist - r); + if (surfaceDist > maxReach) continue; + + // Ray-sphere intersection: check if look direction hits the dome + Vec3 look = player.getLook(1.0f); + double b = 2.0 * (dx * look.xCoord + dy * look.yCoord + dz * look.zCoord); + double c = distSq - (double)(r * r); + double discriminant = b * b - 4.0 * c; + if (discriminant < 0) continue; + + double sqrtDisc = Math.sqrt(discriminant); + double t1 = (-b - sqrtDisc) / 2.0; + double t2 = (-b + sqrtDisc) / 2.0; + + // Outside: t1 is entry distance. Inside: t1 < 0, t2 is exit distance. + double hitDist = t1 >= 0 ? t1 : t2; + if (hitDist < 0 || hitDist > maxReach) continue; + + // Apply the attack with full MC damage calculation + processedMeleeSwings.add(playerId); + inTickMelee = true; + player.attackTargetEntityWithCurrentItem(this); + inTickMelee = false; + } + } + // ==================== BOUNDING BOX ==================== /** From a18503dfe97698478a2aab66e2b76ad5b4106ad8 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 01:09:39 -0500 Subject: [PATCH 07/71] Fix Domes --- .../data/ability/data/EnergyBarrierData.java | 63 ++++++------------- .../data/ability/type/energy/AbilityDome.java | 9 --- .../type/energy/AbilityEnergyBarrier.java | 61 ++++++------------ .../npcs/entity/EntityEnergyBarrier.java | 52 ++++++--------- .../java/noppes/npcs/ServerEventsHandler.java | 8 +-- .../scripted/entity/ScriptEnergyBarrier.java | 16 +++-- .../assets/customnpcs/lang/en_US.lang | 15 ++--- 7 files changed, 80 insertions(+), 144 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java index 6f3610376..d54f205cc 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -8,10 +8,14 @@ import java.util.Map; /** * Groups barrier properties shared by energy barrier abilities (Dome, Wall, Shield). * Handles health/durability, duration, and per-projectile-type damage multipliers. + * + * Properties: + * - Solid: Hard wall — entities cannot pass through the barrier boundary (either direction) + * - Knockback: Repulsion field — entities near the surface get pushed away + * - Absorbing: Caster's incoming damage is redirected to the barrier + * - Allows Melee: Barrier can be hit by melee attacks (punching) */ public class EnergyBarrierData { - // Knockback target display keys - private static final String[] KNOCKBACK_TARGET_KEYS = {"gui.both", "gui.players", "gui.npcs"}; public float maxHealth = 100.0f; public boolean useHealth = true; @@ -19,23 +23,20 @@ public class EnergyBarrierData { public boolean useDuration = true; public float defaultMultiplier = 1.0f; - // Knockback settings + // Solid: entities cannot pass through the barrier boundary + public boolean solid = false; + + // Knockback: repulsion field pushing entities away from surface public boolean knockbackEnabled = false; public float knockbackStrength = 1.0f; - /** - * 0=Both, 1=Player only, 2=NPC only - */ - public int knockbackTarget = 0; - // Melee settings + // Absorbing: caster's damage is redirected to the barrier + public boolean absorbing = false; + + // Melee: barrier can be hit by melee attacks public boolean meleeEnabled = false; public float meleeDamageMultiplier = 1.0f; - // Jail settings (dome only - captures entities inside at spawn) - public boolean jailEnabled = false; - public boolean damageProtection = false; - public float jailThreshold = 0.9f; - /** * Per-projectile-type damage multipliers. * Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam"). @@ -118,24 +119,6 @@ public class EnergyBarrierData { this.defaultMultiplier = defaultMultiplier; } - // Knockback target key helpers - public static String[] getKnockbackTargetKeys() { - return KNOCKBACK_TARGET_KEYS; - } - - public String getKnockbackTargetKey() { - return KNOCKBACK_TARGET_KEYS[Math.max(0, Math.min(knockbackTarget, 2))]; - } - - public void setKnockbackTargetFromKey(String key) { - for (int i = 0; i < KNOCKBACK_TARGET_KEYS.length; i++) { - if (KNOCKBACK_TARGET_KEYS[i].equals(key)) { - knockbackTarget = i; - return; - } - } - } - // ==================== NBT ==================== public void writeNBT(NBTTagCompound nbt) { @@ -144,14 +127,12 @@ public class EnergyBarrierData { nbt.setInteger("barrierDuration", durationTicks); nbt.setBoolean("barrierUseDuration", useDuration); nbt.setFloat("barrierDefaultMult", defaultMultiplier); + nbt.setBoolean("barrierSolid", solid); nbt.setBoolean("barrierKnockback", knockbackEnabled); nbt.setFloat("barrierKnockbackStr", knockbackStrength); - nbt.setInteger("barrierKnockbackTarget", knockbackTarget); + nbt.setBoolean("barrierAbsorbing", absorbing); nbt.setBoolean("barrierMelee", meleeEnabled); nbt.setFloat("barrierMeleeMult", meleeDamageMultiplier); - nbt.setBoolean("barrierJail", jailEnabled); - nbt.setBoolean("barrierDmgProtect", damageProtection); - nbt.setFloat("barrierJailThreshold", jailThreshold); NBTTagCompound multNbt = new NBTTagCompound(); for (Map.Entry entry : damageMultipliers.entrySet()) { @@ -166,14 +147,12 @@ public class EnergyBarrierData { durationTicks = nbt.hasKey("barrierDuration") ? nbt.getInteger("barrierDuration") : 200; useDuration = !nbt.hasKey("barrierUseDuration") || nbt.getBoolean("barrierUseDuration"); defaultMultiplier = nbt.hasKey("barrierDefaultMult") ? nbt.getFloat("barrierDefaultMult") : 1.0f; + solid = nbt.hasKey("barrierSolid") && nbt.getBoolean("barrierSolid"); knockbackEnabled = nbt.hasKey("barrierKnockback") && nbt.getBoolean("barrierKnockback"); knockbackStrength = nbt.hasKey("barrierKnockbackStr") ? nbt.getFloat("barrierKnockbackStr") : 1.0f; - knockbackTarget = nbt.hasKey("barrierKnockbackTarget") ? nbt.getInteger("barrierKnockbackTarget") : 0; + absorbing = nbt.hasKey("barrierAbsorbing") && nbt.getBoolean("barrierAbsorbing"); meleeEnabled = nbt.hasKey("barrierMelee") && nbt.getBoolean("barrierMelee"); meleeDamageMultiplier = nbt.hasKey("barrierMeleeMult") ? nbt.getFloat("barrierMeleeMult") : 1.0f; - jailEnabled = nbt.hasKey("barrierJail") && nbt.getBoolean("barrierJail"); - damageProtection = nbt.hasKey("barrierDmgProtect") && nbt.getBoolean("barrierDmgProtect"); - jailThreshold = nbt.hasKey("barrierJailThreshold") ? nbt.getFloat("barrierJailThreshold") : 0.9f; damageMultipliers.clear(); if (nbt.hasKey("barrierMultipliers")) { @@ -189,14 +168,12 @@ public class EnergyBarrierData { public EnergyBarrierData copy() { EnergyBarrierData copy = new EnergyBarrierData(maxHealth, useHealth, durationTicks, useDuration); copy.defaultMultiplier = defaultMultiplier; + copy.solid = solid; copy.knockbackEnabled = knockbackEnabled; copy.knockbackStrength = knockbackStrength; - copy.knockbackTarget = knockbackTarget; + copy.absorbing = absorbing; copy.meleeEnabled = meleeEnabled; copy.meleeDamageMultiplier = meleeDamageMultiplier; - copy.jailEnabled = jailEnabled; - copy.damageProtection = damageProtection; - copy.jailThreshold = jailThreshold; copy.damageMultipliers = new HashMap<>(damageMultipliers); return copy; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index cdb8f6f2f..78a317cc2 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -135,14 +135,5 @@ public class AbilityDome extends AbilityEnergyBarrier { .range(1.0f, 50.0f)); defs.add(FieldDef.boolField("ability.followCaster", this::isFollowCaster, this::setFollowCaster) .hover("ability.hover.followCaster")); - - // Jail & protection section (dome-only: only enclosures can jail) - defs.add(FieldDef.section("ability.section.jail")); - defs.add(FieldDef.boolField("ability.jailEnabled", this::isJailEnabled, this::setJailEnabled) - .hover("ability.hover.jailEnabled")); - defs.add(FieldDef.floatField("ability.jailThreshold", this::getJailThreshold, this::setJailThreshold) - .range(0.1f, 1.0f).visibleWhen(this::isJailEnabled)); - defs.add(FieldDef.boolField("ability.damageProtection", this::isDamageProtection, this::setDamageProtection) - .hover("ability.hover.damageProtection")); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java index d3b543885..73f34d059 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java @@ -190,6 +190,15 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { return barrierData.getMultiplier(typeId); } + // Solid data + public boolean isSolid() { + return barrierData.solid; + } + + public void setSolid(boolean solid) { + barrierData.solid = solid; + } + // Knockback data public boolean isKnockbackEnabled() { return barrierData.knockbackEnabled; @@ -207,12 +216,13 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { barrierData.knockbackStrength = strength; } - public String getKnockbackTargetKey() { - return barrierData.getKnockbackTargetKey(); + // Absorbing data + public boolean isAbsorbing() { + return barrierData.absorbing; } - public void setKnockbackTargetKey(String key) { - barrierData.setKnockbackTargetFromKey(key); + public void setAbsorbing(boolean absorbing) { + barrierData.absorbing = absorbing; } // Melee data @@ -232,31 +242,6 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { barrierData.meleeDamageMultiplier = mult; } - // Jail data - public boolean isJailEnabled() { - return barrierData.jailEnabled; - } - - public void setJailEnabled(boolean enabled) { - barrierData.jailEnabled = enabled; - } - - public boolean isDamageProtection() { - return barrierData.damageProtection; - } - - public void setDamageProtection(boolean enabled) { - barrierData.damageProtection = enabled; - } - - public float getJailThreshold() { - return barrierData.jailThreshold; - } - - public void setJailThreshold(float threshold) { - barrierData.jailThreshold = Math.max(0.1f, Math.min(1.0f, threshold)); - } - // ==================== GUI ==================== @SideOnly(Side.CLIENT) @@ -275,19 +260,13 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { .range(1, 12000).visibleWhen(this::isUseDuration)); defs.add(FieldDef.floatField("ability.defaultMultiplier", this::getDefaultMultiplier, this::setDefaultMultiplier)); - // Knockback section - defs.add(FieldDef.section("ability.section.knockback")); + // Properties section + defs.add(FieldDef.section("ability.section.properties")); + defs.add(FieldDef.boolField("ability.solid", this::isSolid, this::setSolid)); defs.add(FieldDef.boolField("ability.knockbackEnabled", this::isKnockbackEnabled, this::setKnockbackEnabled)); - defs.add(FieldDef.row( - FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength) - .range(0, 10).visibleWhen(this::isKnockbackEnabled), - FieldDef.stringEnumField("ability.knockbackTarget", EnergyBarrierData.getKnockbackTargetKeys(), - this::getKnockbackTargetKey, this::setKnockbackTargetKey) - .visibleWhen(this::isKnockbackEnabled) - )); - - // Melee section - defs.add(FieldDef.section("ability.section.melee")); + defs.add(FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength) + .range(0, 10).visibleWhen(this::isKnockbackEnabled)); + defs.add(FieldDef.boolField("ability.absorbing", this::isAbsorbing, this::setAbsorbing)); defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled)); defs.add(FieldDef.floatField("ability.meleeDamageMultiplier", this::getMeleeDamageMultiplier, this::setMeleeDamageMultiplier) .range(0, 10).visibleWhen(this::isMeleeEnabled)); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java index 1027fe93e..2db51fd14 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -4,7 +4,6 @@ import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; @@ -94,9 +93,10 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { public abstract void finishCharging(); /** - * Push entities away from the barrier surface. + * Process entity physics for this barrier. + * Handles solid wall (entity repositioning) and knockback (repulsion force). */ - protected abstract void knockbackEntities(); + protected abstract void processEntityPhysics(); /** * Get the maximum extent of this barrier for distance pre-filtering. @@ -203,6 +203,16 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { return barrierData.meleeEnabled; } + @Override + public boolean isEntityInsideOpaqueBlock() { + return false; + } + + @Override + protected boolean func_145771_j(double x, double y, double z) { + return false; + } + @Override public boolean attackEntityFrom(DamageSource source, float amount) { if (worldObj.isRemote || !barrierData.meleeEnabled) return false; @@ -324,20 +334,9 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); } - protected boolean isKnockbackTarget(EntityLivingBase entity) { - switch (barrierData.knockbackTarget) { - case 1: - return entity instanceof EntityPlayer; - case 2: - return entity instanceof EntityNPCInterface; - default: - return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface; - } - } - /** * Check if an entity is an ally of this barrier's owner. - * Allies should not be knocked back by barriers. + * Allies are not affected by barrier physics. */ protected boolean isAllyOfOwner(EntityLivingBase entity) { Entity owner = getOwnerEntity(); @@ -345,7 +344,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { return AbilityTargetHelper.isAlly((EntityLivingBase) owner, entity); } - // ==================== CONTAINMENT & PROTECTION ==================== + // ==================== CONTAINMENT & ABSORBING ==================== /** * Check if an entity is geometrically inside this barrier's protected zone. @@ -356,27 +355,16 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { } /** - * Check if a damage source originates from outside this barrier. - * Uses geometry-specific containment check via isEntityInside(). + * Find a barrier that would absorb damage for the given entity (its caster). + * Returns the barrier if: absorbing is enabled AND entity is the barrier's owner. */ - public boolean isDamageFromOutside(DamageSource source) { - Entity attacker = source.getEntity(); - if (attacker == null) return true; // Environmental damage treated as outside - return !isEntityInside(attacker); - } - - /** - * Find a barrier that would protect the given entity from outside damage. - * Returns the barrier if: damageProtection is enabled AND entity is inside. - * Any entity inside a dome with damageProtection gets redirected damage. - */ - public static EntityEnergyBarrier getProtectingBarrier(Entity entity) { + public static EntityEnergyBarrier getAbsorbingBarrier(Entity entity) { if (entity == null || entity.worldObj == null) return null; List barriers = getActiveBarriers(entity.worldObj); for (EntityEnergyBarrier barrier : barriers) { if (barrier.isDead) continue; - if (!barrier.barrierData.damageProtection) continue; - if (barrier.isEntityInside(entity)) { + if (!barrier.barrierData.absorbing) continue; + if (barrier.ownerEntityId == entity.getEntityId()) { return barrier; } } diff --git a/src/main/java/noppes/npcs/ServerEventsHandler.java b/src/main/java/noppes/npcs/ServerEventsHandler.java index fb2ea3d13..a13384ada 100644 --- a/src/main/java/noppes/npcs/ServerEventsHandler.java +++ b/src/main/java/noppes/npcs/ServerEventsHandler.java @@ -153,13 +153,13 @@ public class ServerEventsHandler { } @SubscribeEvent - public void barrierDamageProtection(LivingAttackEvent event) { + public void barrierAbsorbDamage(LivingAttackEvent event) { if (event.entityLiving == null || event.entityLiving.worldObj == null || event.entityLiving.worldObj.isRemote) return; - EntityEnergyBarrier barrier = EntityEnergyBarrier.getProtectingBarrier(event.entityLiving); - if (barrier != null && barrier.isDamageFromOutside(event.source)) { - // Redirect the damage to the barrier instead of preventing it + EntityEnergyBarrier barrier = EntityEnergyBarrier.getAbsorbingBarrier(event.entityLiving); + if (barrier != null) { + // Redirect the caster's damage to their barrier barrier.absorbDamage(event.ammount); event.setCanceled(true); } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java index 29f330ff7..6669bde50 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java @@ -89,6 +89,14 @@ public class ScriptEnergyBarrier extends ScriptEn entity.getBarrierData().defaultMultiplier = multiplier; } + public boolean isSolid() { + return entity.getBarrierData().solid; + } + + public void setSolid(boolean solid) { + entity.getBarrierData().solid = solid; + } + public boolean isKnockbackEnabled() { return entity.getBarrierData().knockbackEnabled; } @@ -105,12 +113,12 @@ public class ScriptEnergyBarrier extends ScriptEn entity.getBarrierData().knockbackStrength = strength; } - public int getKnockbackTarget() { - return entity.getBarrierData().knockbackTarget; + public boolean isAbsorbing() { + return entity.getBarrierData().absorbing; } - public void setKnockbackTarget(int target) { - entity.getBarrierData().knockbackTarget = target; + public void setAbsorbing(boolean absorbing) { + entity.getBarrierData().absorbing = absorbing; } public int getBarrierType() { diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index c6d745706..c0ae3f376 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2019,7 +2019,7 @@ ability.cnpc.shield=Shield ability.cnpc.slicer=Slicer ability.section.barrier=Barrier -ability.section.knockback=Knockback +ability.section.properties=Properties ability.section.panel=Panel ability.section.launch=Launch ability.section.slicer=Slicer @@ -2032,23 +2032,16 @@ ability.defaultMultiplier=Default Multiplier ability.innerAlpha=Inner Alpha ability.heightOffset=Height Offset +ability.solid=Solid ability.knockbackEnabled=Knockback ability.knockbackStrength=Knockback Strength -ability.knockbackTarget=Target - -ability.section.melee=Melee -ability.meleeEnabled=Melee Attackable +ability.absorbing=Absorbing +ability.meleeEnabled=Allows Melee ability.meleeDamageMultiplier=Melee Multiplier ability.followCaster=Follow Caster ability.hover.followCaster=Dome stays centered on the caster as they move -ability.section.jail=Jail & Protection -ability.jailEnabled=Jail Mode -ability.hover.jailEnabled=Entities inside stay inside, entities outside stay outside -ability.jailThreshold=Jail Threshold -ability.damageProtection=Damage Protection -ability.hover.damageProtection=Outside attacks on entities inside are redirected to the dome ability.variant.small=Small ability.variant.large=Large From 94c4e76823cfecf8df195bab8b8361cc9fcf0c88 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 01:09:47 -0500 Subject: [PATCH 08/71] Update api --- src/api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api b/src/api index 72718c360..96799f68e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 72718c36016c526b00681ea2ba8c506030aca555 +Subproject commit 96799f68ecc1f529ab287b426052048695332e87 From 8648e019a95ac36658c6014227ffcba5500a1fd2 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 03:24:27 -0300 Subject: [PATCH 09/71] Changed some logic on conditions for automatically checking all filters --- .../ability/conditions/AbilityCondition.java | 22 ++++++++++------ .../conditions/ConditionHPThreshold.java | 25 +++---------------- .../ability/conditions/ConditionItem.java | 15 +---------- 3 files changed, 19 insertions(+), 43 deletions(-) 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 6fc7b8a12..6f5299ca4 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 @@ -21,14 +21,20 @@ public abstract class AbilityCondition { protected UserType userType = UserType.BOTH; protected ConditionFilter conditionFilter = ConditionFilter.CASTER; - /** - * Check if this condition is met. - * - * @param caster The entity that would use the ability (NPC or Player) - * @param target The current target (may be null for self-targeting abilities or players) - * @return true if the condition is satisfied - */ - public abstract boolean check(EntityLivingBase caster, EntityLivingBase target); + 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); + } + } + + protected abstract boolean checkEntity(EntityLivingBase entity); public boolean requiresTarget() { return false; 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 5eed753fa..fe77de17d 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 @@ -6,7 +6,7 @@ import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; -public class ConditionHPThreshold extends AbilityCondition{ +public class ConditionHPThreshold extends AbilityCondition { private float threshold = 0.5f; // 50% private boolean percent = true; private ThresholdType thresholdType = ThresholdType.ABOVE; @@ -62,26 +62,9 @@ public class ConditionHPThreshold extends AbilityCondition{ } @Override - public boolean check(EntityLivingBase caster, EntityLivingBase target) { - float casterHP = isPercent() ? caster.getHealth() / caster.getMaxHealth() : caster.getHealth(); - float targetHP = isPercent() ? target.getHealth() / target.getMaxHealth() : target.getHealth(); - - - return compare(casterHP, targetHP); - } - - public boolean compare(float casterHp, float targetHp) { - boolean casterCheck = thresholdType.test(casterHp, threshold); - boolean targetCheck = thresholdType.test(targetHp, threshold); - - switch (getFilter()) { - case BOTH: - return casterCheck && targetCheck; - case CASTER: - return casterCheck; - default: - return targetCheck; - } + protected boolean checkEntity(EntityLivingBase entity) { + float entityHP = isPercent() ? entity.getHealth() / entity.getMaxHealth() : entity.getHealth(); + return thresholdType.test(entityHP, getThreshold()); } @Override 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 index 14b751d29..d8deece10 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -46,20 +46,7 @@ public class ConditionItem extends AbilityCondition { } @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) { + protected boolean checkEntity(EntityLivingBase entity) { switch (usageType) { case HOLDING: return checkHolding(entity); From 606bc093df39ca544490efd5fab84604594331c9 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 02:36:41 -0500 Subject: [PATCH 10/71] Condition Registry [1/2] --- .../npcs/controllers/AbilityController.java | 22 ++ .../ability/conditions/ConditionHitCount.java | 6 + src/main/java/kamkeel/npcs/util/Register.java | 33 +++ .../gui/advanced/SubGuiConditionEdit.java | 219 ++++++++++++++---- 4 files changed, 232 insertions(+), 48 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 1da844451..a2a4d8b33 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -903,6 +903,28 @@ public class AbilityController implements IAbilityHandler { return conditionTypes.keySet().toArray(new String[0]); } + public List getConditionNamespaces() { + LinkedHashSet seen = new LinkedHashSet<>(); + for (String typeId : conditionTypes.keySet()) { + String[] parts = typeId.split("\\.", 3); + if (parts.length >= 2) { + seen.add(parts[1]); + } + } + return new ArrayList<>(seen); + } + + public String[] getConditionTypesByNamespace(String namespace) { + String prefix = "condition." + namespace + "."; + List result = new ArrayList<>(); + for (String typeId : conditionTypes.keySet()) { + if (typeId.startsWith(prefix)) { + result.add(typeId); + } + } + return result.toArray(new String[0]); + } + public void registerFlightChecker(Predicate checker) { flightCheckers.add(checker); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java index 22465b593..1c8dfaabf 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java @@ -18,6 +18,12 @@ public class ConditionHitCount extends AbilityCondition { this.userType = UserType.NPC_ONLY; } + @Override + protected boolean checkEntity(EntityLivingBase entity) { + // Not used — this condition overrides check() directly + return false; + } + @Override public boolean check(EntityLivingBase caster, EntityLivingBase target) { if (caster instanceof EntityNPCInterface) { diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index eab517ce9..75184778d 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; @@ -207,4 +208,36 @@ public class Register { return new Register.EffectActions(namespace); } } + + public static class Conditions extends Register { + + private Conditions(String namespace) { + super("condition", namespace); + } + + public AbilityCondition register(String name, Supplier factory) { + entries.put(registryKey + "." + namespace + "." + name, factory); + return factory.get(); + } + + public void register() { + for (Map.Entry> entry : entries.entrySet()) { + AbilityController.Instance.registerCondition(entry.getValue()); + } + } + + public static Register.Conditions create(String namespace, String displayName) { + if (!REGISTERED_NAMESPACES.containsKey("condition")) + REGISTERED_NAMESPACES.put("condition", new ArrayList<>()); + + if (REGISTERED_NAMESPACES.get("condition").contains(namespace)) { + LogWriter.error("REGISTER CONDITIONS: Namespace " + namespace + " already registered!"); + } + + REGISTERED_NAMESPACES.get("condition").add(namespace); + NAMESPACE_DISPLAY_NAMES.put(namespace, displayName); + + return new Register.Conditions(namespace); + } + } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java index d90f0b6d6..bca83c150 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java @@ -2,12 +2,15 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; +import kamkeel.npcs.util.Register; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.resources.I18n; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.util.*; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldBuilder; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -15,59 +18,138 @@ import java.util.function.Supplier; /** * SubGui for editing a single AbilityCondition. * - * Type selection stays manual (we need to swap the condition object). - * Field rendering is fully delegated to AbilityCondition#getAbilityDefinitions(), - * mirroring the pattern used in SubGuiAbilityConfig. + * Split-panel layout: + * Left – Mod namespace filter + scrollable condition type list + * Right – FieldDef configuration for the selected condition */ -public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldListener, ISubGuiListener { +public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, ICustomScrollListener { private static final int DECLARATIVE_ID_START = 1000; private static final int CLEAR_ID_START = 2000; private static final int LABEL_ID_START = 3000; - private static final int BTN_TYPE = 1; + private static final int BTN_NAMESPACE = 1; private static final int BTN_CANCEL = 10; private static final int BTN_DONE = 11; + // Left panel + private static final int LEFT_WIDTH = 110; + private static final int LEFT_MARGIN = 5; + + // Right panel + private static final int RIGHT_GAP = 5; + private AbilityCondition condition; private AbilityCondition result; + private String selectedTypeId; - private final String[] conditionTypeIds; - private final String[] conditionTypeNames; - private int selectedTypeIndex = 0; + // Namespace filter + private int namespaceFilter = 0; // 0 = ALL, 1 = cnpc, 2+ = modded namespaces + private List modNamespaces; + + // Type list + private GuiCustomScroll scroll; + private final HashMap displayNameToTypeId = new HashMap<>(); + private String[] filteredTypeIds; private AbilityFieldBuilder builder; public SubGuiConditionEdit(AbilityCondition existing) { this.result = null; - - conditionTypeIds = AbilityController.Instance.getConditionTypes(); - conditionTypeNames = new String[conditionTypeIds.length]; - for (int i = 0; i < conditionTypeIds.length; i++) { - Supplier factory = AbilityController.Instance.getConditionType(conditionTypeIds[i]); - conditionTypeNames[i] = factory != null ? factory.get().getName() : conditionTypeIds[i]; - } + this.modNamespaces = getModNamespaces(); if (existing != null) { - for (int i = 0; i < conditionTypeIds.length; i++) { - if (conditionTypeIds[i].equals(existing.getTypeId())) { - selectedTypeIndex = i; - break; - } - } this.condition = existing; + this.selectedTypeId = existing.getTypeId(); + // Set namespace filter to match existing condition + setNamespaceForTypeId(selectedTypeId); } else { - this.condition = spawnCondition(selectedTypeIndex); + // Default to first available condition + String[] allTypes = AbilityController.Instance.getConditionTypes(); + if (allTypes.length > 0) { + this.selectedTypeId = allTypes[0]; + this.condition = spawnCondition(selectedTypeId); + } } - setBackground("menubg.png"); - xSize = 240; - ySize = 180; + setBackground("menubg.png", 217); + xSize = 356; + ySize = 200; } - private AbilityCondition spawnCondition(int typeIndex) { - if (typeIndex < 0 || typeIndex >= conditionTypeIds.length) return null; - Supplier factory = AbilityController.Instance.getConditionType(conditionTypeIds[typeIndex]); + private List getModNamespaces() { + List result = new ArrayList<>(); + List registered = Register.REGISTERED_NAMESPACES.get("condition"); + if (registered != null) { + result.addAll(registered); + } + return result; + } + + private void setNamespaceForTypeId(String typeId) { + if (typeId == null) return; + String[] parts = typeId.split("\\.", 3); + if (parts.length < 2) return; + String ns = parts[1]; + + if ("cnpc".equals(ns)) { + namespaceFilter = 1; + } else { + for (int i = 0; i < modNamespaces.size(); i++) { + if (modNamespaces.get(i).equals(ns)) { + namespaceFilter = 2 + i; + return; + } + } + namespaceFilter = 0; // Fallback to ALL + } + } + + private String getNamespaceFilterLabel() { + if (namespaceFilter == 0) return "ALL"; + if (namespaceFilter == 1) return "CNPC"; + int modIdx = namespaceFilter - 2; + if (modIdx >= 0 && modIdx < modNamespaces.size()) { + String ns = modNamespaces.get(modIdx); + String display = Register.NAMESPACE_DISPLAY_NAMES.get(ns); + return display != null ? display : ns.toUpperCase(); + } + return "ALL"; + } + + private void cycleNamespaceFilter() { + namespaceFilter++; + // 0=ALL, 1=CNPC, 2..2+N-1=mod namespaces, then wrap + int max = 2 + modNamespaces.size(); + if (namespaceFilter >= max) { + namespaceFilter = 0; + } + // Skip empty mod namespace slots + if (namespaceFilter >= 2) { + int modIdx = namespaceFilter - 2; + if (modIdx >= modNamespaces.size()) { + namespaceFilter = 0; + } + } + } + + private String[] getFilteredTypes() { + if (namespaceFilter == 0) { + return AbilityController.Instance.getConditionTypes(); + } else if (namespaceFilter == 1) { + return AbilityController.Instance.getConditionTypesByNamespace("cnpc"); + } else { + int modIdx = namespaceFilter - 2; + if (modIdx >= 0 && modIdx < modNamespaces.size()) { + return AbilityController.Instance.getConditionTypesByNamespace(modNamespaces.get(modIdx)); + } + return AbilityController.Instance.getConditionTypes(); + } + } + + private AbilityCondition spawnCondition(String typeId) { + if (typeId == null) return null; + Supplier factory = AbilityController.Instance.getConditionType(typeId); return factory != null ? factory.get() : null; } @@ -76,18 +158,45 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi GuiNpcTextField.unfocus(); super.initGui(); - int swX = guiLeft + 4; - int swY = guiTop + 5; - int swW = xSize - 8; - int swH = ySize - 35; + int y = guiTop + 5; - int labelY = guiTop + 8; - addLabel(new GuiNpcLabel(0, "gui.type", guiLeft + 8, labelY + 5)); - GuiNpcButton typeBtn = new GuiNpcButton(BTN_TYPE, guiLeft + 80, labelY, 155, 20, conditionTypeNames, selectedTypeIndex); - addButton(typeBtn); + // Namespace filter button (full width at top) + addButton(new GuiNpcButton(BTN_NAMESPACE, guiLeft + LEFT_MARGIN, y, xSize - LEFT_MARGIN * 2, 20, getNamespaceFilterLabel())); + y += 24; - swY += 26; - swH -= 26; + // Build filtered type list + filteredTypeIds = getFilteredTypes(); + displayNameToTypeId.clear(); + List scrollList = new ArrayList<>(); + for (String typeId : filteredTypeIds) { + Supplier factory = AbilityController.Instance.getConditionType(typeId); + String displayName = factory != null ? I18n.format(factory.get().getName()) : typeId; + scrollList.add(displayName); + displayNameToTypeId.put(displayName, typeId); + } + + // Left panel: condition type scroll list + int scrollHeight = ySize - 58; + scroll = new GuiCustomScroll(this, 0); + scroll.guiLeft = guiLeft + LEFT_MARGIN; + scroll.guiTop = y; + scroll.setSize(LEFT_WIDTH, scrollHeight); + scroll.setUnsortedList(scrollList); + + // Pre-select current condition type in scroll + if (selectedTypeId != null) { + for (int i = 0; i < filteredTypeIds.length; i++) { + if (filteredTypeIds[i].equals(selectedTypeId)) { + scroll.selected = i; + break; + } + } + } + addScroll(scroll); + + // Right panel: condition config via FieldDef + int rightX = guiLeft + LEFT_MARGIN + LEFT_WIDTH + RIGHT_GAP; + int rightW = xSize - LEFT_MARGIN * 2 - LEFT_WIDTH - RIGHT_GAP; List fields = new ArrayList<>(); if (condition != null) { @@ -97,24 +206,20 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi builder = new AbilityFieldBuilder(this, fontRendererObj); builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); builder.startY(5); + builder.buildScrollWindow(fields, rightX, y, rightW, scrollHeight); - builder.buildScrollWindow(fields, swX, swY, swW, swH); - + // Bottom buttons int btnY = guiTop + ySize - 26; - addButton(new GuiNpcButton(BTN_CANCEL, guiLeft + 8, btnY, 60, 20, "gui.cancel")); - addButton(new GuiNpcButton(BTN_DONE, guiLeft + xSize - 68, btnY, 60, 20, "gui.done")); + addButton(new GuiNpcButton(BTN_CANCEL, guiLeft + LEFT_MARGIN, btnY, 60, 20, "gui.cancel")); + addButton(new GuiNpcButton(BTN_DONE, guiLeft + xSize - LEFT_MARGIN - 60, btnY, 60, 20, "gui.done")); } @Override public void buttonEvent(GuiButton guibutton) { int id = guibutton.id; - if (id == BTN_TYPE) { - int newIndex = ((GuiNpcButton) guibutton).getValue(); - if (newIndex != selectedTypeIndex) { - selectedTypeIndex = newIndex; - condition = spawnCondition(selectedTypeIndex); - } + if (id == BTN_NAMESPACE) { + cycleNamespaceFilter(); initGui(); return; } @@ -138,6 +243,24 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi } } + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0 && scroll.hasSelected()) { + String displayName = scroll.getSelected(); + String typeId = displayNameToTypeId.get(displayName); + if (typeId != null && !typeId.equals(selectedTypeId)) { + selectedTypeId = typeId; + condition = spawnCondition(selectedTypeId); + initGui(); + } + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + // Same as single click — user still needs to configure fields + } + @Override public void unFocused(GuiNpcTextField textField) { if (textField.id < DECLARATIVE_ID_START) return; From 75a58f7e74b4b44d793fb51059d39d631b43bc47 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:03:42 -0500 Subject: [PATCH 11/71] Condition Registry [2/2] --- .../npcs/controllers/AbilityController.java | 2 + .../ability/conditions/AbilityCondition.java | 18 ++ .../conditions/ConditionHPThreshold.java | 99 +--------- .../ability/conditions/ConditionHitCount.java | 9 +- .../ability/conditions/ConditionItem.java | 14 ++ .../conditions/ConditionQuestCompleted.java | 86 +++++++++ .../conditions/ConditionThreshold.java | 171 ++++++++++++++++++ .../gui/advanced/SubGuiAbilityConfig.java | 4 +- .../gui/advanced/SubGuiConditionEdit.java | 62 +++++-- .../client/gui/builder/GuiFieldBuilder.java | 3 + .../assets/customnpcs/lang/en_US.lang | 11 ++ 11 files changed, 367 insertions(+), 112 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index a2a4d8b33..b29bbb668 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -5,6 +5,7 @@ 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.conditions.ConditionQuestCompleted; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; import kamkeel.npcs.controllers.data.ability.type.AbilityDash; @@ -154,6 +155,7 @@ public class AbilityController implements IAbilityHandler { registerCondition(ConditionHPThreshold::new); registerCondition(ConditionHitCount::new); registerCondition(ConditionItem::new); + registerCondition(ConditionQuestCompleted::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 6f5299ca4..479776930 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 @@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.LogWriter; import noppes.npcs.client.gui.builder.FieldDef; @@ -40,6 +41,14 @@ public abstract class AbilityCondition { return false; } + /** + * Returns true if all required fields are filled in. + * Used to disable the Done button in SubGuiConditionEdit when incomplete. + */ + public boolean isConfigured() { + return true; + } + public UserType getUserType() { return userType; } @@ -63,10 +72,19 @@ public abstract class AbilityCondition { @SideOnly(Side.CLIENT) public abstract void getConditionDefinitions(List defs); + /** + * Returns a human-readable summary of this condition's configuration. + * Used for tooltips when hovering over condition buttons. + */ + @SideOnly(Side.CLIENT) + public abstract String getConditionSummary(); + @SideOnly(Side.CLIENT) public final List getAllDefinitions() { List defs = new ArrayList<>(); + defs.add(FieldDef.labelField("ability.validFor", () -> + "\u00A7e" + StatCollector.translateToLocal("ability.userType." + getUserType().name()))); defs.add(FieldDef.enumField("condition.filter", ConditionFilter.class, this::getFilter, this::setFilter)); getConditionDefinitions(defs); return defs; 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 fe77de17d..dfb997526 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 @@ -1,60 +1,8 @@ package kamkeel.npcs.controllers.data.ability.conditions; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.client.gui.builder.FieldDef; -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; - } - }, - BELOW { - @Override - public boolean test(float value, float threshold) { - return value < threshold; - } - }, - EQUAL { - @Override - public boolean test(float value, float threshold) { - return value == threshold; - } - }; - - public abstract boolean test(float value, float threshold); - - public static ThresholdType fromOrdinal(int ordinal) { - ThresholdType[] values = values(); - if (ordinal >= 0 && ordinal < values.length) { - return values[ordinal]; - } - return ABOVE; - } - - @Override - public String toString() { - switch (this) { - case ABOVE: - return "condition.hp_above"; - case BELOW: - return "condition.hp_below"; - case EQUAL: - return "condition.hp_equal"; - default: - return name(); - } - } - } +public class ConditionHPThreshold extends ConditionThreshold { public ConditionHPThreshold() { this.typeId = "condition.cnpc.hp_threshold"; @@ -62,50 +10,17 @@ public class ConditionHPThreshold extends AbilityCondition { } @Override - protected boolean checkEntity(EntityLivingBase entity) { - float entityHP = isPercent() ? entity.getHealth() / entity.getMaxHealth() : entity.getHealth(); - return thresholdType.test(entityHP, getThreshold()); + protected float getEntityValue(EntityLivingBase entity) { + return entity.getHealth(); } @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)); + protected float getEntityMaxValue(EntityLivingBase entity) { + return entity.getMaxHealth(); } @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; - } - - public void setThreshold(float threshold) { - this.threshold = Math.max(0, threshold); - } - - public ThresholdType getThresholdType() { - return thresholdType; - } - - public void setThresholdType(ThresholdType thresholdType) { - this.thresholdType = thresholdType; - } - - public boolean isPercent() { - return percent; - } - - public void setIsPercent(boolean percent) { - this.percent = percent; + protected String getStatName() { + return "HP"; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java index 1c8dfaabf..dbd71922b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java @@ -1,8 +1,8 @@ package kamkeel.npcs.controllers.data.ability.conditions; -import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.entity.EntityNPCInterface; @@ -15,7 +15,6 @@ public class ConditionHitCount extends AbilityCondition { public ConditionHitCount() { this.typeId = "condition.cnpc.hit_count"; this.name = "condition.hit_count"; - this.userType = UserType.NPC_ONLY; } @Override @@ -59,6 +58,12 @@ public class ConditionHitCount extends AbilityCondition { defs.add(FieldDef.intField("condition.within_ticks", this::getWithinTicks, this::setWithinTicks).min(0)); } + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + return "[" + filterLabel + "] Hit " + requiredHits + " times in " + withinTicks + " ticks"; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setInteger("requiredHits", requiredHits); 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 index d8deece10..9bb445043 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -4,6 +4,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.entity.EntityNPCInterface; @@ -149,6 +150,14 @@ public class ConditionItem extends AbilityCondition { // .hover("condition.hover.required_count")); } + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String usage = usageType.name(); + String item = itemName.isEmpty() ? "None" : itemName; + return "[" + filterLabel + "] " + usage + ": " + item; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setString("itemName", itemName); @@ -166,6 +175,11 @@ public class ConditionItem extends AbilityCondition { } + @Override + public boolean isConfigured() { + return itemName != null && !itemName.isEmpty(); + } + public UsageType getUsageType() { return usageType; } public void setUsageType(UsageType usageType) { this.usageType = usageType; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java new file mode 100644 index 000000000..04c1ec16a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -0,0 +1,86 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import kamkeel.npcs.controllers.data.ability.UserType; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.client.gui.select.GuiQuestSelection; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.QuestController; +import noppes.npcs.controllers.data.PlayerData; +import noppes.npcs.controllers.data.Quest; + +import java.util.List; + +public class ConditionQuestCompleted extends AbilityCondition { + private int questId = -1; + + public ConditionQuestCompleted() { + this.typeId = "condition.cnpc.quest_completed"; + this.name = "condition.quest_completed"; + this.userType = UserType.PLAYER_ONLY; + } + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + if (!(entity instanceof EntityPlayer)) return false; + if (questId < 0) return false; + + EntityPlayer player = (EntityPlayer) entity; + return PlayerData.get(player).questData.hasFinishedQuest(questId); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.subGuiField("condition.select_quest", + () -> new GuiQuestSelection(questId), + (SubGuiInterface gui) -> { + GuiQuestSelection sel = (GuiQuestSelection) gui; + if (sel.selectedQuest != null) { + questId = sel.selectedQuest.id; + } + }) + .buttonLabel(() -> { + if (questId < 0) return "None"; + Quest q = QuestController.Instance.quests.get(questId); + return q != null ? q.title : "ID:" + questId; + }) + .clearable(() -> questId = -1)); + } + + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String questName = "None"; + if (questId >= 0) { + Quest q = QuestController.Instance.quests.get(questId); + questName = q != null ? q.title : "ID:" + questId; + } + return "[" + filterLabel + "] Quest: " + questName; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setInteger("questId", questId); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + questId = nbt.getInteger("questId"); + } + + @Override + public boolean isConfigured() { + return questId >= 0; + } + + public int getQuestId() { + return questId; + } + + public void setQuestId(int questId) { + this.questId = questId; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java new file mode 100644 index 000000000..fda42ce6a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java @@ -0,0 +1,171 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; + +/** + * Abstract base for threshold-based conditions (HP, Ki, etc.). + * Provides shared percent/flat toggle, ThresholdType enum, and FieldDef generation. + */ +public abstract class ConditionThreshold extends AbilityCondition { + + public enum ThresholdType { + ABOVE { + @Override + public boolean test(float value, float threshold) { + return value >= threshold; + } + }, + BELOW { + @Override + public boolean test(float value, float threshold) { + return value <= threshold; + } + }, + EQUAL { + @Override + public boolean test(float value, float threshold) { + return value == threshold; + } + }; + + public abstract boolean test(float value, float threshold); + + public static ThresholdType fromOrdinal(int ordinal) { + ThresholdType[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return ABOVE; + } + + @Override + public String toString() { + switch (this) { + case ABOVE: + return "condition.above"; + case BELOW: + return "condition.below"; + case EQUAL: + return "condition.equal"; + default: + return name(); + } + } + } + + protected float thresholdFlat = 100f; + protected float thresholdPercent = 0.5f; + protected boolean percent = true; + protected ThresholdType thresholdType = ThresholdType.ABOVE; + + /** + * Returns the raw stat value for the entity (e.g. current HP, current Ki). + */ + protected abstract float getEntityValue(EntityLivingBase entity); + + /** + * Returns the maximum stat value for the entity (e.g. max HP, max Ki). + * Used to compute the percentage ratio. + */ + protected abstract float getEntityMaxValue(EntityLivingBase entity); + + /** + * Returns the display name for the stat being checked (e.g. "HP", "Ki"). + * Used in getConditionSummary(). + */ + protected abstract String getStatName(); + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + float value; + float threshold; + if (percent) { + float max = getEntityMaxValue(entity); + value = max > 0 ? getEntityValue(entity) / max : 0; + threshold = thresholdPercent; + } else { + value = getEntityValue(entity); + threshold = thresholdFlat; + } + return thresholdType.test(value, threshold); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.floatField("condition.threshold_flat", this::getThresholdFlat, this::setThresholdFlat) + .min(0).visibleWhen(() -> !isPercent())); + defs.add(FieldDef.floatField("condition.threshold_percent", this::getThresholdPercent, this::setThresholdPercent) + .min(0).max(1).visibleWhen(this::isPercent)); + 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("thresholdFlat", thresholdFlat); + nbt.setFloat("thresholdPercent", thresholdPercent); + nbt.setBoolean("percent", percent); + nbt.setInteger("thresholdType", thresholdType.ordinal()); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + thresholdFlat = nbt.getFloat("thresholdFlat"); + thresholdPercent = nbt.getFloat("thresholdPercent"); + percent = nbt.getBoolean("percent"); + thresholdType = ThresholdType.fromOrdinal(nbt.getInteger("thresholdType")); + } + + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String typeName = getStatName(); + String typeLabel = StatCollector.translateToLocal(thresholdType.toString()); + String value = percent + ? String.format("%.0f%%", thresholdPercent * 100) + : String.format("%.0f", thresholdFlat); + return "[" + filterLabel + "] " + typeName + " " + typeLabel + " " + value; + } + + // ═══════════════════════════════════════════════════════════════════ + // GETTERS / SETTERS + // ═══════════════════════════════════════════════════════════════════ + + public float getThresholdFlat() { + return thresholdFlat; + } + + public void setThresholdFlat(float value) { + this.thresholdFlat = Math.max(0, value); + } + + public float getThresholdPercent() { + return thresholdPercent; + } + + public void setThresholdPercent(float value) { + this.thresholdPercent = Math.max(0, Math.min(1, value)); + } + + public boolean isPercent() { + return percent; + } + + public void setIsPercent(boolean percent) { + this.percent = percent; + } + + public ThresholdType getThresholdType() { + return thresholdType; + } + + public void setThresholdType(ThresholdType type) { + this.thresholdType = type; + } +} 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 a3cee0443..544a7fefd 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -245,7 +245,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi 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)); + GuiNpcButton condBtn = new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName); + condBtn.hoverableText = cond.getConditionSummary(); + sw.addButton(condBtn); 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; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java index bca83c150..b0ed1d635 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java @@ -31,6 +31,7 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi private static final int BTN_NAMESPACE = 1; private static final int BTN_CANCEL = 10; private static final int BTN_DONE = 11; + private static final int TF_SEARCH = 12; // Left panel private static final int LEFT_WIDTH = 110; @@ -39,6 +40,8 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi // Right panel private static final int RIGHT_GAP = 5; + private String searchText = ""; + private AbilityCondition condition; private AbilityCondition result; private String selectedTypeId; @@ -158,11 +161,11 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi GuiNpcTextField.unfocus(); super.initGui(); - int y = guiTop + 5; + int leftX = guiLeft + LEFT_MARGIN; + int btnY = guiTop + ySize - 26; - // Namespace filter button (full width at top) - addButton(new GuiNpcButton(BTN_NAMESPACE, guiLeft + LEFT_MARGIN, y, xSize - LEFT_MARGIN * 2, 20, getNamespaceFilterLabel())); - y += 24; + // Namespace filter button (above left scroll only) + addButton(new GuiNpcButton(BTN_NAMESPACE, leftX, guiTop + 5, LEFT_WIDTH, 20, getNamespaceFilterLabel())); // Build filtered type list filteredTypeIds = getFilteredTypes(); @@ -171,22 +174,28 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi for (String typeId : filteredTypeIds) { Supplier factory = AbilityController.Instance.getConditionType(typeId); String displayName = factory != null ? I18n.format(factory.get().getName()) : typeId; + if (!searchText.isEmpty() && !displayName.toLowerCase().contains(searchText.toLowerCase())) continue; scrollList.add(displayName); displayNameToTypeId.put(displayName, typeId); } - // Left panel: condition type scroll list - int scrollHeight = ySize - 58; - scroll = new GuiCustomScroll(this, 0); - scroll.guiLeft = guiLeft + LEFT_MARGIN; - scroll.guiTop = y; + // Left panel: condition type scroll list (between namespace button and search bar) + int scrollTop = guiTop + 29; + int scrollBottom = btnY - 24; + int scrollHeight = scrollBottom - scrollTop; + if (scroll == null) { + scroll = new GuiCustomScroll(this, 0); + } + scroll.guiLeft = leftX; + scroll.guiTop = scrollTop; scroll.setSize(LEFT_WIDTH, scrollHeight); scroll.setUnsortedList(scrollList); // Pre-select current condition type in scroll if (selectedTypeId != null) { - for (int i = 0; i < filteredTypeIds.length; i++) { - if (filteredTypeIds[i].equals(selectedTypeId)) { + for (int i = 0; i < scrollList.size(); i++) { + String typeId = displayNameToTypeId.get(scrollList.get(i)); + if (typeId != null && typeId.equals(selectedTypeId)) { scroll.selected = i; break; } @@ -194,9 +203,16 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi } addScroll(scroll); - // Right panel: condition config via FieldDef - int rightX = guiLeft + LEFT_MARGIN + LEFT_WIDTH + RIGHT_GAP; + // Search text field (below left scroll) + GuiNpcTextField searchField = new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, leftX, scrollBottom + 2, LEFT_WIDTH, 20, searchText); + searchField.setHoverText("gui.search"); + addTextField(searchField); + + // Right panel: condition config via FieldDef (full height from top to buttons) + int rightX = leftX + LEFT_WIDTH + RIGHT_GAP; int rightW = xSize - LEFT_MARGIN * 2 - LEFT_WIDTH - RIGHT_GAP; + int rightTop = guiTop + 5; + int rightHeight = btnY - rightTop - 4; List fields = new ArrayList<>(); if (condition != null) { @@ -205,13 +221,17 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi builder = new AbilityFieldBuilder(this, fontRendererObj); builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); + builder.contentRight(rightW); builder.startY(5); - builder.buildScrollWindow(fields, rightX, y, rightW, scrollHeight); + builder.buildScrollWindow(fields, rightX, rightTop, rightW, rightHeight); // Bottom buttons - int btnY = guiTop + ySize - 26; - addButton(new GuiNpcButton(BTN_CANCEL, guiLeft + LEFT_MARGIN, btnY, 60, 20, "gui.cancel")); - addButton(new GuiNpcButton(BTN_DONE, guiLeft + xSize - LEFT_MARGIN - 60, btnY, 60, 20, "gui.done")); + addButton(new GuiNpcButton(BTN_CANCEL, leftX, btnY, 60, 20, "gui.cancel")); + GuiNpcButton doneBtn = new GuiNpcButton(BTN_DONE, guiLeft + xSize - LEFT_MARGIN - 60, btnY, 60, 20, "gui.done"); + if (condition == null || !condition.isConfigured()) { + doneBtn.setEnabled(false); + } + addButton(doneBtn); } @Override @@ -263,6 +283,14 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi @Override public void unFocused(GuiNpcTextField textField) { + if (textField.id == TF_SEARCH) { + String newText = textField.getText(); + if (!newText.equals(searchText)) { + searchText = newText; + initGui(); + } + return; + } if (textField.id < DECLARATIVE_ID_START) return; if (builder != null && builder.handleTextFieldEvent(textField.id, textField)) { initGui(); diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index bb069b8f5..8d7d88d61 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -344,6 +344,9 @@ public class GuiFieldBuilder { String translated = StatCollector.translateToLocal(def.getLabel()); int labelW = fontRenderer.getStringWidth(translated); int fieldX = colLLabel + labelW + labelPadding; + // Clamp field width so it doesn't extend under the scrollbar + int maxW = contentRight - fieldX - 15; + if (maxW > 0 && fieldW > maxW) fieldW = maxW; renderFieldAt(def, colLLabel, fieldX, fieldW, y); } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 2e222fae7..cc3f289e0 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2099,6 +2099,17 @@ condition.full_armor=Full Armor condition.item_count=Item Count condition.item_name=Item Name +# Condition Threshold +condition.above=Above +condition.below=Below +condition.equal=Equal +condition.threshold_flat=Threshold +condition.threshold_percent=Threshold % + +# Condition Quest Completed +condition.quest_completed=Quest Completed +condition.select_quest=Select Quest + # 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 f2ac1f1817eec93f283a7f56dc725f099796a99c Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:04:14 -0500 Subject: [PATCH 12/71] Fix Effects Height --- .../java/noppes/npcs/client/gui/SubGuiEffectGeneral.java | 2 +- src/main/java/noppes/npcs/client/gui/util/GuiEffectBar.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java b/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java index cfde598de..0b21d028d 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java @@ -127,7 +127,7 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi int iconWidth = effect.getWidth(); int iconHeight = effect.getHeight(); int width = data.getTotalWidth(); - int height = data.getTotalWidth(); + int height = data.getTotalHeight(); func_152125_a(x, y, iconX, iconY, iconWidth, iconHeight, iconRenderSize, iconRenderSize, width, height); diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiEffectBar.java b/src/main/java/noppes/npcs/client/gui/util/GuiEffectBar.java index a390ddfc1..29cf26134 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiEffectBar.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiEffectBar.java @@ -80,9 +80,10 @@ public class GuiEffectBar extends GuiScreen { int iconWidth = entry.effect.getWidth(); int iconHeight = entry.effect.getHeight(); int texWidth = imageData.getTotalWidth(); + int texHeight = imageData.getTotalHeight(); // Use iconYOffset to vertically center the icon - func_152125_a(x + 2, drawY + iconYOffset, iconU, iconV, iconWidth, iconHeight, iconRenderSize, iconRenderSize, texWidth, texWidth); + func_152125_a(x + 2, drawY + iconYOffset, iconU, iconV, iconWidth, iconHeight, iconRenderSize, iconRenderSize, texWidth, texHeight); GL11.glDisable(GL11.GL_DEPTH_TEST); Minecraft.getMinecraft().getTextureManager().bindTexture(specialIcons); @@ -190,7 +191,7 @@ public class GuiEffectBar extends GuiScreen { if (imageData != null && imageData.imageLoaded()) { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); imageData.bindTexture(); - func_152125_a(tooltipX + (maxWidth - iconSize) / 2, textY + 4, hoveredEffect.iconX, hoveredEffect.iconY, hoveredEffect.getWidth(), hoveredEffect.getHeight(), iconSize, iconSize, imageData.getTotalWidth(), imageData.getTotalWidth()); + func_152125_a(tooltipX + (maxWidth - iconSize) / 2, textY + 4, hoveredEffect.iconX, hoveredEffect.iconY, hoveredEffect.getWidth(), hoveredEffect.getHeight(), iconSize, iconSize, imageData.getTotalWidth(), imageData.getTotalHeight()); } textureManager.bindTexture(specialIcons); From 5c975a339d9321756ff3c79c43caccb8cdd24378 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:24:20 -0500 Subject: [PATCH 13/71] Show Attributes on Items in Auction House --- .../kamkeel/npcs/util/AttributeItemUtil.java | 5 ++- .../client/gui/player/GuiAuctionTrades.java | 45 +++++++------------ .../noppes/npcs/mixin/MixinItemStack.java | 9 +++- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java index 9cc361b5d..b3de20c9e 100644 --- a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java +++ b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java @@ -15,6 +15,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.StatCollector; import noppes.npcs.client.ClientProxy; +import noppes.npcs.client.gui.player.GuiAuctionInterface; import noppes.npcs.controllers.MagicController; import noppes.npcs.controllers.data.Magic; import org.lwjgl.input.Keyboard; @@ -230,12 +231,12 @@ public class AttributeItemUtil { * Returns the item tooltip. */ @SideOnly(Side.CLIENT) - public static List getToolTip(List original, NBTTagCompound compound) { + public static List getToolTip(List original, NBTTagCompound compound, boolean override) { List tooltip = new ArrayList<>(original); // Get the RPGCore compound and then the Attributes compound. NBTTagCompound rpgCore = compound.hasKey(TAG_RPGCORE) ? compound.getCompoundTag(TAG_RPGCORE) : new NBTTagCompound(); NBTTagCompound attrTag = rpgCore.hasKey(TAG_ATTRIBUTES) ? rpgCore.getCompoundTag(TAG_ATTRIBUTES) : new NBTTagCompound(); - if (Keyboard.isKeyDown(ClientProxy.NPCButton.getKeyCode())) { + if (Keyboard.isKeyDown(ClientProxy.NPCButton.getKeyCode()) || override) { List newTooltips = new ArrayList<>(); if (!tooltip.isEmpty()) { newTooltips.add(tooltip.get(0)); diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java index 45a527dca..05e83c5bc 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java @@ -88,10 +88,16 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { AuctionListing listing = tradesContainer.getListingAt(slot); AuctionClaim claim = tradesContainer.getClaimAt(slot); - if (listing != null && tradesContainer.isSellingAt(slot) && mouseButton == 1) { - // Right-click on own listing = show cancel - setPending(slot, PendingOp.CANCEL); - NoppesUtil.clickSound(); + if (listing != null && tradesContainer.isSellingAt(slot)) { + if (mouseButton == 0) { + // Left-click on own listing = view details + AuctionActionPacket.openBidding(listing.id); + NoppesUtil.clickSound(); + } else if (mouseButton == 1) { + // Right-click on own listing = show cancel + setPending(slot, PendingOp.CANCEL); + NoppesUtil.clickSound(); + } } else if (listing != null && tradesContainer.isBiddingAt(slot) && mouseButton == 0) { // Left-click on active bid = open bidding GUI to increase bid AuctionActionPacket.openBidding(listing.id); @@ -315,16 +321,13 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { AuctionListing listing = tradesContainer.getListingAt(slot); AuctionClaim claim = tradesContainer.getClaimAt(slot); - if (listing != null) { - if (tradesContainer.isSellingAt(slot)) { - tooltip.add(EnumChatFormatting.GOLD + StatCollector.translateToLocal("auction.trades.activeListing")); - tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.rightClickToCancel")); - } else if (tradesContainer.isBiddingAt(slot)) { - tooltip.add(EnumChatFormatting.AQUA + StatCollector.translateToLocal("auction.trades.activeBid")); - tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.winningBid")); - tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.leftClickToIncrease")); - } - } else if (claim != null) { + // Slots with items have tooltips handled by AuctionTooltipHandler via ItemTooltipEvent + if (listing != null) return; + if (claim != null && (claim.type == EnumClaimType.ITEM || + (claim.type == EnumClaimType.REFUND && claim.item != null))) return; + + // Non-item slots (currency claims, refund without item, unavailable) + if (claim != null) { switch (claim.type) { case CURRENCY: tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.soldClaim")); @@ -342,20 +345,6 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.outbidBy") + ": " + EnumChatFormatting.AQUA + claim.otherPlayerName); } tooltip.add(EnumChatFormatting.GOLD + String.format("%,d", claim.currency) + " " + AuctionClientConfig.getCurrencyName()); - // Show rebid option if item is present (active listing) - if (claim.item != null) { - tooltip.add(""); - tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.leftClickRebid")); - tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.rightClickRefund")); - return; // Skip the generic claim message - } - break; - case ITEM: - if (claim.isReturned) { - tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.expiredClaim")); - } else { - tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.wonClaim")); - } break; } tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.leftClickToClaim")); diff --git a/src/main/java/noppes/npcs/mixin/MixinItemStack.java b/src/main/java/noppes/npcs/mixin/MixinItemStack.java index 8615573c0..4e82147f1 100644 --- a/src/main/java/noppes/npcs/mixin/MixinItemStack.java +++ b/src/main/java/noppes/npcs/mixin/MixinItemStack.java @@ -1,9 +1,11 @@ package noppes.npcs.mixin; import kamkeel.npcs.util.AttributeItemUtil; +import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.player.GuiAuctionInterface; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -28,9 +30,14 @@ public abstract class MixinItemStack { @Inject(method = "getTooltip", at = @At("TAIL"), cancellable = true) public void getAttributeTooltip(EntityPlayer player, boolean advanced, CallbackInfoReturnable> cir) { + boolean override = false; + if (Minecraft.getMinecraft().currentScreen instanceof GuiAuctionInterface) { + override = true; + } + List tooltip = cir.getReturnValue(); if (hasTagCompound() && getTagCompound().hasKey(TAG_RPGCORE)) { - cir.setReturnValue(AttributeItemUtil.getToolTip(tooltip, getTagCompound())); + cir.setReturnValue(AttributeItemUtil.getToolTip(tooltip, getTagCompound(), override)); } } } From 1b5584c585d4d6425e2ead88dbfb3c46a2a6b87f Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:28:16 -0500 Subject: [PATCH 14/71] Has Effect and Class Condition --- .../npcs/controllers/AbilityController.java | 2 + .../conditions/ConditionHasEffect.java | 106 ++++++++++++++++++ .../data/ability/type/AbilityCutter.java | 10 +- .../assets/customnpcs/lang/en_US.lang | 4 + 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index b29bbb668..02c438e62 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -5,6 +5,7 @@ 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.conditions.ConditionHasEffect; import kamkeel.npcs.controllers.data.ability.conditions.ConditionQuestCompleted; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; @@ -156,6 +157,7 @@ public class AbilityController implements IAbilityHandler { registerCondition(ConditionHitCount::new); registerCondition(ConditionItem::new); registerCondition(ConditionQuestCompleted::new); + registerCondition(ConditionHasEffect::new); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java new file mode 100644 index 000000000..b0c3941ad --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java @@ -0,0 +1,106 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import kamkeel.npcs.controllers.data.ability.UserType; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.advanced.SubGuiCustomEffectSelect; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.CustomEffectController; +import noppes.npcs.controllers.data.CustomEffect; + +import java.util.HashMap; +import java.util.List; + +public class ConditionHasEffect extends AbilityCondition { + private int effectId = -1; + private int effectIndex = 0; + + public ConditionHasEffect() { + this.typeId = "condition.cnpc.has_effect"; + this.name = "condition.has_effect"; + this.userType = UserType.PLAYER_ONLY; + } + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + if (!(entity instanceof EntityPlayer)) return false; + if (effectId < 0) return false; + + EntityPlayer player = (EntityPlayer) entity; + return CustomEffectController.getInstance().hasEffect(player, effectId, effectIndex); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.subGuiField("condition.select_effect", + () -> new SubGuiCustomEffectSelect(effectId, effectIndex), + (SubGuiInterface gui) -> { + SubGuiCustomEffectSelect sel = (SubGuiCustomEffectSelect) gui; + if (sel.getSelectedEffectId() >= 0) { + effectId = sel.getSelectedEffectId(); + effectIndex = sel.getSelectedIndex(); + } + }) + .buttonLabel(() -> { + if (effectId < 0) return "None"; + CustomEffect effect = getEffect(); + return effect != null ? effect.getName() : "ID:" + effectId; + }) + .clearable(() -> { + effectId = -1; + effectIndex = 0; + })); + } + + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String effectName = "None"; + if (effectId >= 0) { + CustomEffect effect = getEffect(); + effectName = effect != null ? effect.getName() : "ID:" + effectId; + } + return "[" + filterLabel + "] Effect: " + effectName; + } + + @Override + public boolean isConfigured() { + return effectId >= 0; + } + + private CustomEffect getEffect() { + HashMap map = CustomEffectController.getInstance().getEffectMap(effectIndex); + return map != null ? map.get(effectId) : null; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setInteger("effectId", effectId); + nbt.setInteger("effectIndex", effectIndex); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + effectId = nbt.getInteger("effectId"); + effectIndex = nbt.getInteger("effectIndex"); + } + + public int getEffectId() { + return effectId; + } + + public void setEffectId(int effectId) { + this.effectId = effectId; + } + + public int getEffectIndex() { + return effectIndex; + } + + public void setEffectIndex(int effectIndex) { + this.effectIndex = effectIndex; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java index bdb90c951..6db0f832e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java @@ -164,12 +164,10 @@ public class AbilityCutter extends Ability implements IAbilityCutter { currentRotation = -arcAngle / 2.0f; activeSoundPlayed = false; - // Use the telegraph's locked yaw if available, so the sweep direction - // matches the telegraph the player/NPC was seeing during windup. - // The telegraph yaw is locked when entering ACTIVE phase via lockPosition(). - if (!telegraphInstances.isEmpty()) { - sweepBaseYaw = telegraphInstances.get(0).getYaw(); - } else if (target != null) { + // Compute sweep direction from caster/target at execution time. + // Server-side telegraph instances are not ticked, so their yaw is stale + // from windup start — use the caster's current rotation instead. + if (target != null) { double dx = target.posX - caster.posX; double dz = target.posZ - caster.posZ; if (dx * dx + dz * dz > 0.0001) { diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index cc3f289e0..40bf70930 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2110,6 +2110,10 @@ condition.threshold_percent=Threshold % condition.quest_completed=Quest Completed condition.select_quest=Select Quest +# Condition Has Effect +condition.has_effect=Has Effect +condition.select_effect=Select Effect + # 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 9dac3d01d9b5d071e01653ab565b3b8cb20c1ab1 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:31:44 -0500 Subject: [PATCH 15/71] Show Attributes on Items in Auction House --- src/main/java/kamkeel/npcs/util/AttributeItemUtil.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java index b3de20c9e..74061d29c 100644 --- a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java +++ b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java @@ -238,7 +238,7 @@ public class AttributeItemUtil { NBTTagCompound attrTag = rpgCore.hasKey(TAG_ATTRIBUTES) ? rpgCore.getCompoundTag(TAG_ATTRIBUTES) : new NBTTagCompound(); if (Keyboard.isKeyDown(ClientProxy.NPCButton.getKeyCode()) || override) { List newTooltips = new ArrayList<>(); - if (!tooltip.isEmpty()) { + if (!tooltip.isEmpty() && !override) { newTooltips.add(tooltip.get(0)); } @@ -326,7 +326,11 @@ public class AttributeItemUtil { newTooltips.addAll(buildSection(reqEntries)); } - tooltip = newTooltips; + if(override){ + tooltip.addAll(newTooltips); + } else { + tooltip = newTooltips; + } } else { String keyName = Keyboard.getKeyName(ClientProxy.NPCButton.getKeyCode()); tooltip.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.ITALIC + From f538cebd03a50d97e344502fd8fa1e5863e3a9b6 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:33:54 -0500 Subject: [PATCH 16/71] Fix Custom Effects Preview with Height --- .../java/noppes/npcs/client/gui/global/GuiNPCManageEffects.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageEffects.java b/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageEffects.java index 21ecb1b8d..57df39d20 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageEffects.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageEffects.java @@ -173,7 +173,7 @@ public class GuiNPCManageEffects extends GuiNPCInterface2 implements ICustomScro int iconWidth = effect.getWidth(); int iconHeight = effect.getHeight(); int width = data.getTotalWidth(); - int height = data.getTotalWidth(); + int height = data.getTotalHeight(); func_152125_a(x, y, iconX, iconY, iconWidth, iconHeight, iconRenderSize, iconRenderSize, width, height); From 6369cb2b651336e683873659129325ecb73ad326 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:48:50 -0500 Subject: [PATCH 17/71] Better Animation Snapping --- .../noppes/npcs/AnimationMixinFunctions.java | 40 ++++++++++++------- .../controllers/data/PlayerAbilityData.java | 12 ------ 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/main/java/noppes/npcs/AnimationMixinFunctions.java b/src/main/java/noppes/npcs/AnimationMixinFunctions.java index 4a6eb1db5..e5c6d2089 100644 --- a/src/main/java/noppes/npcs/AnimationMixinFunctions.java +++ b/src/main/java/noppes/npcs/AnimationMixinFunctions.java @@ -209,21 +209,31 @@ public class AnimationMixinFunctions { return null; } - public static void playerFullModel_head(Entity p_78088_1_, CallbackInfo callbackInfo) { - if (!DBCAddon.IsAvailable() && ClientCacheHandler.playerAnimations.containsKey(p_78088_1_.getUniqueID())) { - AnimationData animData = ClientCacheHandler.playerAnimations.get(p_78088_1_.getUniqueID()); - if (animData != null && animData.isActive()) { - Frame frame = (Frame) animData.animation.currentFrame(); - if (frame.frameParts.containsKey(EnumAnimationPart.FULL_MODEL)) { - FramePart part = frame.frameParts.get(EnumAnimationPart.FULL_MODEL); - part.interpolateOffset(); - part.interpolateAngles(); - float pi = 180 / (float) Math.PI; - GL11.glTranslatef(part.prevPivots[0], -part.prevPivots[1], part.prevPivots[2]); - GL11.glRotatef(part.prevRotations[0] * pi, 1, 0, 0); - GL11.glRotatef(part.prevRotations[1] * pi, 0, 1, 0); - GL11.glRotatef(part.prevRotations[2] * pi, 0, 0, 1); - } + public static void playerFullModel_head(Entity entity, CallbackInfo callbackInfo) { + if (!ClientCacheHandler.playerAnimations.containsKey(entity.getUniqueID())) return; + + AnimationData animData = ClientCacheHandler.playerAnimations.get(entity.getUniqueID()); + if (animData == null || !animData.isActive()) return; + + // Snap body rotation to head so animations face the look direction + if (entity instanceof EntityLivingBase) { + EntityLivingBase living = (EntityLivingBase) entity; + living.renderYawOffset = living.rotationYaw; + living.prevRenderYawOffset = living.prevRotationYaw; + } + + // FULL_MODEL GL transforms (non-DBC only) + if (!DBCAddon.IsAvailable()) { + Frame frame = (Frame) animData.animation.currentFrame(); + if (frame.frameParts.containsKey(EnumAnimationPart.FULL_MODEL)) { + FramePart part = frame.frameParts.get(EnumAnimationPart.FULL_MODEL); + part.interpolateOffset(); + part.interpolateAngles(); + float pi = 180 / (float) Math.PI; + GL11.glTranslatef(part.prevPivots[0], -part.prevPivots[1], part.prevPivots[2]); + GL11.glRotatef(part.prevRotations[0] * pi, 1, 0, 0); + GL11.glRotatef(part.prevRotations[1] * pi, 0, 1, 0); + GL11.glRotatef(part.prevRotations[2] * pi, 0, 0, 1); } } } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 1762a24c5..b8958d24a 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -298,7 +298,6 @@ 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); @@ -846,17 +845,6 @@ 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) // ═══════════════════════════════════════════════════════════════════ From 4dde214f948d79865b6f55d06d6bd9de0d41b295 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:11:28 -0500 Subject: [PATCH 18/71] Fix Persistent Ability Entities --- .../npcs/entity/EntityAbilitySweeper.java | 40 +----- .../npcs/entity/EntityAbilityZone.java | 122 +----------------- .../kamkeel/npcs/entity/EntityEnergyDome.java | 10 +- .../npcs/entity/EntityEnergyPanel.java | 18 +-- .../npcs/entity/EntityEnergyProjectile.java | 6 +- 5 files changed, 10 insertions(+), 186 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index 4323141f5..f1a591760 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -366,47 +366,11 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - readEnergyBaseNBT(nbt); - this.beamLength = sanitize(nbt.getFloat("BeamLength"), 10.0f, MAX_ENTITY_SIZE); - this.beamWidth = sanitize(nbt.getFloat("BeamWidth"), 0.3f, MAX_ENTITY_SIZE); - this.beamHeight = sanitize(nbt.getFloat("BeamHeight"), 1.0f, MAX_ENTITY_SIZE); - this.sweepSpeed = nbt.hasKey("SweepSpeed") ? nbt.getFloat("SweepSpeed") : 3.0f; - if (Float.isNaN(sweepSpeed) || Float.isInfinite(sweepSpeed) || sweepSpeed <= 0) sweepSpeed = 3.0f; - this.numberOfRotations = nbt.hasKey("NumRotations") ? nbt.getInteger("NumRotations") : 2; - if (numberOfRotations <= 0) numberOfRotations = 1; - this.completedRotations = nbt.getInteger("CompletedRotations"); - this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 400; - if (maxTicks <= 0) maxTicks = 400; - this.targetEntityId = nbt.getInteger("TargetId"); - this.currentAngle = nbt.getFloat("CurrentAngle"); - this.prevAngle = currentAngle; - this.baseYaw = nbt.getFloat("BaseYaw"); - this.deathWorldTime = nbt.getLong("DeathWorldTime"); - this.damage = nbt.hasKey("Damage") ? nbt.getFloat("Damage") : 5.0f; - if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 5.0f; - this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 5; - if (damageInterval <= 0) damageInterval = 1; - this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); - this.lockOnTarget = nbt.getBoolean("LockOnTarget"); + // Intentionally empty — ability entities are transient (not saved to world) } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - writeEnergyBaseNBT(nbt); - nbt.setFloat("BeamLength", beamLength); - nbt.setFloat("BeamWidth", beamWidth); - nbt.setFloat("BeamHeight", beamHeight); - nbt.setFloat("SweepSpeed", sweepSpeed); - nbt.setInteger("NumRotations", numberOfRotations); - nbt.setInteger("CompletedRotations", completedRotations); - nbt.setInteger("MaxTicks", maxTicks); - nbt.setInteger("TargetId", targetEntityId); - nbt.setFloat("CurrentAngle", currentAngle); - nbt.setFloat("BaseYaw", baseYaw); - nbt.setLong("DeathWorldTime", deathWorldTime); - nbt.setFloat("Damage", damage); - nbt.setInteger("DamageInterval", damageInterval); - nbt.setBoolean("Piercing", piercing); - nbt.setBoolean("LockOnTarget", lockOnTarget); + // Intentionally empty — ability entities are transient (not saved to world) } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index a3aa494b4..2a5988847 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -716,130 +716,12 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - int zoneTypeOrd = nbt.getInteger("ZoneType"); - this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP; - try { - this.shape = ZoneShape.valueOf(nbt.getString("ShapeName")); - } catch (Exception e) { - this.shape = ZoneShape.CIRCLE; - } - - this.ownerEntityId = nbt.getInteger("OwnerId"); - this.radius = nbt.hasKey("Radius") ? nbt.getFloat("Radius") : 4.0f; - this.durationTicks = nbt.hasKey("Duration") ? nbt.getInteger("Duration") : 200; - this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 200; - this.innerColor = nbt.hasKey("InnerColor") ? nbt.getInteger("InnerColor") : 0xFF6600; - this.outerColor = nbt.hasKey("OuterColor") ? nbt.getInteger("OuterColor") : 0xFF0000; - this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled"); - this.zoneHeight = nbt.hasKey("ZoneHeight") ? nbt.getFloat("ZoneHeight") : 2.0f; - this.particleDensity = nbt.hasKey("ParticleDensity") ? nbt.getFloat("ParticleDensity") : 1.0f; - this.particleScale = nbt.hasKey("ParticleScale") ? nbt.getFloat("ParticleScale") : 1.0f; - this.animSpeed = nbt.hasKey("AnimSpeed") ? nbt.getFloat("AnimSpeed") : 1.0f; - this.lightningDensity = nbt.hasKey("LightningDensity") ? nbt.getFloat("LightningDensity") : 1.0f; - this.deathWorldTime = nbt.getLong("DeathWorldTime"); - - // Visual layer fields - this.groundFill = nbt.getBoolean("GroundFill"); - this.groundAlpha = nbt.getFloat("GroundAlpha"); - this.rings = nbt.getBoolean("Rings"); - this.ringCount = nbt.getInteger("RingCount"); - this.border = nbt.getBoolean("Border"); - this.borderSpeed = nbt.getFloat("BorderSpeed"); - this.accents = nbt.getBoolean("Accents"); - this.accentStyle = nbt.getInteger("AccentStyle"); - this.lightning = nbt.getBoolean("Lightning"); - this.particles = nbt.getBoolean("Particles"); - this.particleMotion = nbt.getInteger("ParticleMotion"); - this.particleDir = nbt.getString("ParticleDir"); - this.particleSize = nbt.getInteger("ParticleSize"); - this.particleGlow = nbt.getBoolean("ParticleGlow"); - - // Trap - this.armTime = nbt.getInteger("ArmTime"); - this.triggerRadius = nbt.getFloat("TriggerRadius"); - this.maxTriggers = nbt.getInteger("MaxTriggers"); - this.triggerCooldown = nbt.getInteger("TriggerCooldown"); - this.damage = nbt.getFloat("Damage"); - this.damageRadius = nbt.getFloat("DamageRadius"); - this.knockback = nbt.getFloat("Knockback"); - this.visible = nbt.getBoolean("Visible"); - - // Hazard - this.damagePerSecond = nbt.hasKey("DamagePerSecond") ? nbt.getFloat("DamagePerSecond") : 1.0f; - this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 20; - if (damageInterval <= 0) damageInterval = 1; - this.ignoreInvulnFrames = nbt.getBoolean("IgnoreInvuln"); - this.affectsCaster = nbt.getBoolean("AffectsCaster"); - - // Effects - effects.clear(); - if (nbt.hasKey("Effects")) { - NBTTagList effectList = nbt.getTagList("Effects", 10); - for (int i = 0; i < effectList.tagCount(); i++) { - AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i)); - if (effect != null && effect.isValid()) { - effects.add(effect); - } - } - } + // Intentionally empty — ability entities are transient (not saved to world) } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - nbt.setInteger("ZoneType", zoneType.ordinal()); - nbt.setString("ShapeName", shape.name()); - nbt.setInteger("OwnerId", ownerEntityId); - nbt.setFloat("Radius", radius); - nbt.setInteger("Duration", durationTicks); - nbt.setInteger("MaxTicks", maxTicks); - nbt.setInteger("InnerColor", innerColor); - nbt.setInteger("OuterColor", outerColor); - nbt.setBoolean("OuterColorEnabled", outerColorEnabled); - nbt.setFloat("ZoneHeight", zoneHeight); - nbt.setFloat("ParticleDensity", particleDensity); - nbt.setFloat("ParticleScale", particleScale); - nbt.setFloat("AnimSpeed", animSpeed); - nbt.setFloat("LightningDensity", lightningDensity); - nbt.setLong("DeathWorldTime", deathWorldTime); - - // Visual layer fields - nbt.setBoolean("GroundFill", groundFill); - nbt.setFloat("GroundAlpha", groundAlpha); - nbt.setBoolean("Rings", rings); - nbt.setInteger("RingCount", ringCount); - nbt.setBoolean("Border", border); - nbt.setFloat("BorderSpeed", borderSpeed); - nbt.setBoolean("Accents", accents); - nbt.setInteger("AccentStyle", accentStyle); - nbt.setBoolean("Lightning", lightning); - nbt.setBoolean("Particles", particles); - nbt.setInteger("ParticleMotion", particleMotion); - nbt.setString("ParticleDir", particleDir); - nbt.setInteger("ParticleSize", particleSize); - nbt.setBoolean("ParticleGlow", particleGlow); - - // Trap - nbt.setInteger("ArmTime", armTime); - nbt.setFloat("TriggerRadius", triggerRadius); - nbt.setInteger("MaxTriggers", maxTriggers); - nbt.setInteger("TriggerCooldown", triggerCooldown); - nbt.setFloat("Damage", damage); - nbt.setFloat("DamageRadius", damageRadius); - nbt.setFloat("Knockback", knockback); - nbt.setBoolean("Visible", visible); - - // Hazard - nbt.setFloat("DamagePerSecond", damagePerSecond); - nbt.setInteger("DamageInterval", damageInterval); - nbt.setBoolean("IgnoreInvuln", ignoreInvulnFrames); - nbt.setBoolean("AffectsCaster", affectsCaster); - - // Effects - NBTTagList effectList = new NBTTagList(); - for (AbilityPotionEffect effect : effects) { - effectList.appendTag(effect.writeNBT()); - } - nbt.setTag("Effects", effectList); + // Intentionally empty — ability entities are transient (not saved to world) } // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 3d4fabe44..1697d6d9b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -599,17 +599,11 @@ public class EntityEnergyDome extends EntityEnergyBarrier { @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - readBarrierBaseNBT(nbt); - this.setDomeRadius(sanitize(nbt.getFloat("DomeRadius"), 5.0f, MAX_ENTITY_RADIUS)); - this.targetDomeRadius = sanitize(nbt.hasKey("TargetDomeRadius") ? nbt.getFloat("TargetDomeRadius") : domeRadius, 5.0f, MAX_ENTITY_RADIUS); - this.followCaster = nbt.hasKey("FollowCaster") && nbt.getBoolean("FollowCaster"); + // Intentionally empty — ability entities are transient (not saved to world) } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - writeBarrierBaseNBT(nbt); - nbt.setFloat("DomeRadius", domeRadius); - nbt.setFloat("TargetDomeRadius", targetDomeRadius); - nbt.setBoolean("FollowCaster", followCaster); + // Intentionally empty — ability entities are transient (not saved to world) } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 1ef8dc370..8c8e2288b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -556,25 +556,11 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - readBarrierBaseNBT(nbt); - this.panelYaw = nbt.getFloat("PanelYaw"); - int modeOrdinal = nbt.getInteger("PanelMode"); - this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length) - ? PanelMode.values()[modeOrdinal] : PanelMode.PLACED; - panelData.readNBT(nbt); - panelData.panelWidth = sanitize(panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); - panelData.panelHeight = sanitize(panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); - this.targetPanelWidth = sanitize(nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); - this.targetPanelHeight = sanitize(nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); + // Intentionally empty — ability entities are transient (not saved to world) } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - writeBarrierBaseNBT(nbt); - nbt.setFloat("PanelYaw", panelYaw); - nbt.setInteger("PanelMode", mode.ordinal()); - nbt.setFloat("TargetPanelWidth", targetPanelWidth); - nbt.setFloat("TargetPanelHeight", targetPanelHeight); - panelData.writeNBT(nbt); + // Intentionally empty — ability entities are transient (not saved to world) } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 5b741cc0b..89d537552 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -926,14 +926,12 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - readBaseNBT(nbt); - readProjectileNBT(nbt); + // Intentionally empty — ability entities are transient (not saved to world) } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - writeBaseNBT(nbt); - writeProjectileNBT(nbt); + // Intentionally empty — ability entities are transient (not saved to world) } /** From 5b52c817d788f5d29e144cea1f8d1787913ff3c6 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:11:39 -0500 Subject: [PATCH 19/71] Add New Dash Directional --- .../data/ability/type/AbilityDash.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index fffbf6ca5..b5342e3d8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -26,7 +26,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { */ public enum DashMode { AGGRESSIVE, - DEFENSIVE; + DEFENSIVE, + DIRECTIONAL; @Override public String toString() { @@ -35,6 +36,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { return "ability.dash.aggressive"; case DEFENSIVE: return "ability.dash.defensive"; + case DIRECTIONAL: + return "ability.dash.directional"; default: return name(); } @@ -124,16 +127,22 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { public void onExecute(EntityLivingBase caster, EntityLivingBase target) { initMovement(caster, dashDistance, dashSpeed); - // Choose random direction based on mode - DashDirection[] directions = dashMode == DashMode.AGGRESSIVE - ? AGGRESSIVE_DIRECTIONS - : DEFENSIVE_DIRECTIONS; - chosenDirection = directions[RANDOM.nextInt(directions.length)]; + if (dashMode == DashMode.DIRECTIONAL) { + // Dash straight in the caster's look direction + chosenDirection = DashDirection.FORWARD; + setDirectionFromYaw(caster.rotationYaw); + } else { + // Choose random direction based on mode + DashDirection[] directions = dashMode == DashMode.AGGRESSIVE + ? AGGRESSIVE_DIRECTIONS + : DEFENSIVE_DIRECTIONS; + chosenDirection = directions[RANDOM.nextInt(directions.length)]; - // Calculate direction: base yaw (toward target or look dir) + direction offset - float baseYaw = getBaseYaw(caster, target); - float dashYaw = baseYaw + chosenDirection.getAngleOffset(); - setDirectionFromYaw(dashYaw); + // Calculate direction: base yaw (toward target or look dir) + direction offset + float baseYaw = getBaseYaw(caster, target); + float dashYaw = baseYaw + chosenDirection.getAngleOffset(); + setDirectionFromYaw(dashYaw); + } // Small upward impulse for a skip/hop arc (gravity handles the descent) caster.motionY = 0.2; From aa58cf804184f8f414cc6998e5bef3bf0b9eec60 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:41:14 -0500 Subject: [PATCH 20/71] Fix DBC Turbo Bounceback in Liquid and Lava --- .../data/ability/type/AbilityCharge.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 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 bc2da7cae..4f7021705 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 @@ -66,26 +66,25 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { } /** - * Called on the first tick of windup - lock direction here so it matches telegraph. + * During windup, NPCs face toward their target for animation purposes. + * Players aim freely — the telegraph follows their look direction on the client, + * and direction is locked at the end of windup in onExecute(). */ @Override public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - if (tick == 1) { - lockDirection(caster, target); + if (!isPlayerCaster(caster) && target != null) { + lockDirectionToTarget(caster, target); + enforceLockedRotation(caster); } - enforceLockedRotation(caster); } @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { + // Lock final direction at end of windup so it matches the telegraph + lockDirection(caster, target); initMovement(caster, maxRange, chargeSpeed); hitEntities.clear(); - // If direction wasn't set during windup (shouldn't happen), set it now - if (movementDirection == null) { - lockDirection(caster, target); - } - enforceLockedRotation(caster); } From e210bac2bfb7c6a9fb6573805882951710a24eeb Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:41:51 -0500 Subject: [PATCH 21/71] Fix Caster Positoning and Lock / Movement Dashing Abilities --- .../controllers/data/ability/Ability.java | 12 ++++-- .../data/ability/type/AbilityMovement.java | 38 +++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 5d793f5ec..5517caef2 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -863,11 +863,17 @@ public abstract class Ability implements IAbility, IAbilityAction { // AOE_SELF abilities: telegraph follows caster during windup instance.setEntityIdToFollow(caster.getEntityId()); - // For LINE/CONE telegraphs: track target direction during windup + // For LINE/CONE telegraphs: track direction during windup // Only track if rotation is NOT locked during windup (NPC can still turn) - if ((telegraphType == TelegraphType.LINE || telegraphType == TelegraphType.CONE) && target != null) { + if (telegraphType == TelegraphType.LINE || telegraphType == TelegraphType.CONE) { if (!isRotationLockedDuringWindup()) { - instance.setTargetEntityId(target.getEntityId()); + if (target != null) { + // Face the target entity + instance.setTargetEntityId(target.getEntityId()); + } else { + // No target (AOE_SELF etc.): track caster's own rotationYaw + instance.setTrackFollowedEntityYaw(true); + } } // If rotation is locked, yaw stays fixed at creation time } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index b54056146..331b91920 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -2,6 +2,8 @@ package kamkeel.npcs.controllers.data.ability.type; import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.S12PacketEntityVelocity; import net.minecraft.util.Vec3; /** @@ -213,6 +215,24 @@ public abstract class AbilityMovement extends Ability { // VELOCITY APPLICATION // ═══════════════════════════════════════════════════════════════════ + /** + * Send an authoritative velocity packet to a player. + *

+ * The ability tick runs BEFORE moveEntityWithHeading (at PlayerTickEvent.START). + * If we use velocityChanged, the entity tracker sends S12 AFTER friction has + * reduced the motion (~55% of set value). The client then moves at reduced speed, + * reports a short position via C04, and the server resets to the client's position. + *

+ * By sending S12 manually here — before friction — the client receives the full + * velocity and moves at the correct speed. + */ + private void sendPlayerVelocity(EntityLivingBase caster) { + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + } + } + /** * Apply movement velocity to the caster (horizontal only, Y unchanged). */ @@ -220,7 +240,11 @@ public abstract class AbilityMovement extends Ability { caster.motionX = movementDirection.xCoord * speed; caster.motionZ = movementDirection.zCoord * speed; if (!isPreview()) { - caster.velocityChanged = true; + if (caster instanceof EntityPlayerMP) { + sendPlayerVelocity(caster); + } else { + caster.velocityChanged = true; + } } } @@ -233,7 +257,11 @@ public abstract class AbilityMovement extends Ability { caster.motionY = 0; caster.motionZ = movementDirection.zCoord * speed; if (!isPreview()) { - caster.velocityChanged = true; + if (caster instanceof EntityPlayerMP) { + sendPlayerVelocity(caster); + } else { + caster.velocityChanged = true; + } } } @@ -244,7 +272,11 @@ public abstract class AbilityMovement extends Ability { caster.motionX = 0; caster.motionZ = 0; if (!isPreview()) { - caster.velocityChanged = true; + if (caster instanceof EntityPlayerMP) { + sendPlayerVelocity(caster); + } else { + caster.velocityChanged = true; + } } } From 3921b3e6542d555d5fc5e842c0648a5ee0d6fa45 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:42:00 -0500 Subject: [PATCH 22/71] Dash Directional Key --- src/main/resources/assets/customnpcs/lang/en_US.lang | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 40bf70930..8e2aa437c 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1562,6 +1562,7 @@ ability.sweep.spin=Spin # Dash Mode ability.dash.aggressive=Aggressive ability.dash.defensive=Defensive +ability.dash.directional=Directional # Counter Type ability.counter.flat=Flat @@ -1995,7 +1996,7 @@ ability.hover.speed=Movement speed of projectile ability.hover.lifetime=Maximum ticks before despawning ability.hover.knockback=Push force applied to targets ability.hover.boomerang=Projectile returns to caster after reaching max distance -ability.hover.dashMode=Aggressive dashes toward target, Defensive dashes away +ability.hover.dashMode=Aggressive dashes toward target, Defensive dashes away, Directional dashes in looking direction ability.hover.aoe=Affects all valid targets in range instead of just one ability.hover.dmgOnPull=Deal damage continuously while pulling targets ability.hover.lineOfSight=Requires clear line of sight to the target From 06809933edb8beddaf866a63b520187fd9088334 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:42:07 -0500 Subject: [PATCH 23/71] Update API --- src/api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api b/src/api index 96799f68e..a9c77d20e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 96799f68ecc1f529ab287b426052048695332e87 +Subproject commit a9c77d20eb83be6a3168480c0d62fa78419e9469 From 12c50d5f6a7dbe31bbe8b510527f32b9b0384c83 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 04:56:53 -0500 Subject: [PATCH 24/71] Whoops Forgot to Sync NBTs --- .../npcs/entity/EntityAbilitySweeper.java | 46 +++++++++++++++++++ .../npcs/entity/EntityAbilityZone.java | 14 +++--- .../npcs/entity/EntityEnergyAbility.java | 27 +++++++++-- .../kamkeel/npcs/entity/EntityEnergyDome.java | 16 +++++++ .../npcs/entity/EntityEnergyPanel.java | 24 ++++++++++ .../npcs/entity/EntityEnergyProjectile.java | 12 +++++ 6 files changed, 128 insertions(+), 11 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index f1a591760..f08ffeb2a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -373,4 +373,50 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { protected void writeEntityToNBT(NBTTagCompound nbt) { // Intentionally empty — ability entities are transient (not saved to world) } + + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + nbt.setFloat("BeamLength", beamLength); + nbt.setFloat("BeamWidth", beamWidth); + nbt.setFloat("BeamHeight", beamHeight); + nbt.setFloat("SweepSpeed", sweepSpeed); + nbt.setInteger("NumRotations", numberOfRotations); + nbt.setInteger("CompletedRotations", completedRotations); + nbt.setInteger("MaxTicks", maxTicks); + nbt.setInteger("TargetId", targetEntityId); + nbt.setFloat("CurrentAngle", currentAngle); + nbt.setFloat("BaseYaw", baseYaw); + nbt.setLong("DeathWorldTime", deathWorldTime); + nbt.setFloat("Damage", damage); + nbt.setInteger("DamageInterval", damageInterval); + nbt.setBoolean("Piercing", piercing); + nbt.setBoolean("LockOnTarget", lockOnTarget); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + this.beamLength = sanitize(nbt.getFloat("BeamLength"), 10.0f, MAX_ENTITY_SIZE); + this.beamWidth = sanitize(nbt.getFloat("BeamWidth"), 0.3f, MAX_ENTITY_SIZE); + this.beamHeight = sanitize(nbt.getFloat("BeamHeight"), 1.0f, MAX_ENTITY_SIZE); + this.sweepSpeed = nbt.hasKey("SweepSpeed") ? nbt.getFloat("SweepSpeed") : 3.0f; + if (Float.isNaN(sweepSpeed) || Float.isInfinite(sweepSpeed) || sweepSpeed <= 0) sweepSpeed = 3.0f; + this.numberOfRotations = nbt.hasKey("NumRotations") ? nbt.getInteger("NumRotations") : 2; + if (numberOfRotations <= 0) numberOfRotations = 1; + this.completedRotations = nbt.getInteger("CompletedRotations"); + this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 400; + if (maxTicks <= 0) maxTicks = 400; + this.targetEntityId = nbt.getInteger("TargetId"); + this.currentAngle = nbt.getFloat("CurrentAngle"); + this.prevAngle = currentAngle; + this.baseYaw = nbt.getFloat("BaseYaw"); + this.deathWorldTime = nbt.getLong("DeathWorldTime"); + this.damage = nbt.hasKey("Damage") ? nbt.getFloat("Damage") : 5.0f; + if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 5.0f; + this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 5; + if (damageInterval <= 0) damageInterval = 1; + this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); + this.lockOnTarget = nbt.getBoolean("LockOnTarget"); + } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 2a5988847..a5225d620 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -1,6 +1,6 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.common.network.ByteBufUtils; +import kamkeel.npcs.util.ByteBufUtils; import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -731,7 +731,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override public void writeSpawnData(ByteBuf buffer) { buffer.writeInt(zoneType.ordinal()); - ByteBufUtils.writeUTF8String(buffer, shape.name()); + ByteBufUtils.writeString(buffer, shape.name()); buffer.writeInt(ownerEntityId); buffer.writeFloat(radius); buffer.writeInt(durationTicks); @@ -757,7 +757,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD buffer.writeBoolean(lightning); buffer.writeBoolean(particles); buffer.writeInt(particleMotion); - ByteBufUtils.writeUTF8String(buffer, particleDir); + ByteBufUtils.writeString(buffer, particleDir); buffer.writeInt(particleSize); buffer.writeBoolean(particleGlow); @@ -772,14 +772,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD effectList.appendTag(effect.writeNBT()); } effectsNbt.setTag("Effects", effectList); - ByteBufUtils.writeTag(buffer, effectsNbt); + ByteBufUtils.writeNBT(buffer, effectsNbt); } @Override public void readSpawnData(ByteBuf buffer) { int zoneTypeOrd = buffer.readInt(); this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP; - String shapeName = ByteBufUtils.readUTF8String(buffer); + String shapeName = ByteBufUtils.readString(buffer); try { this.shape = ZoneShape.valueOf(shapeName); } catch (Exception e) { @@ -810,7 +810,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD this.lightning = buffer.readBoolean(); this.particles = buffer.readBoolean(); this.particleMotion = buffer.readInt(); - this.particleDir = ByteBufUtils.readUTF8String(buffer); + this.particleDir = ByteBufUtils.readString(buffer); this.particleSize = buffer.readInt(); this.particleGlow = buffer.readBoolean(); @@ -818,7 +818,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD this.visible = buffer.readBoolean(); // Effects - NBTTagCompound effectsNbt = ByteBufUtils.readTag(buffer); + NBTTagCompound effectsNbt = ByteBufUtils.readNBT(buffer); effects.clear(); if (effectsNbt != null && effectsNbt.hasKey("Effects")) { NBTTagList effectList = effectsNbt.getTagList("Effects", 10); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index 2546fb8a2..585eadc7d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -7,6 +7,7 @@ import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -314,12 +315,30 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit // ==================== SPAWN DATA ==================== + /** + * Write entity data for client sync. Subclasses override this + * to serialize their fields. Separate from writeEntityToNBT + * which is intentionally empty (entities are transient, not saved to world). + */ + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + } + + /** + * Read entity data from client sync. Subclasses override this + * to deserialize their fields. Separate from readEntityFromNBT + * which is intentionally empty (entities are transient, not saved to world). + */ + protected void readSpawnNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + } + @Override public void writeSpawnData(ByteBuf buffer) { try { NBTTagCompound compound = new NBTTagCompound(); - this.writeEntityToNBT(compound); - cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); + this.writeSpawnNBT(compound); + ByteBufUtils.writeNBT(buffer, compound); } catch (Exception e) { noppes.npcs.LogWriter.error("Error writing energy ability spawn data", e); } @@ -328,9 +347,9 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit @Override public void readSpawnData(ByteBuf buffer) { try { - NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); + NBTTagCompound compound = ByteBufUtils.readNBT(buffer); if (compound != null) { - this.readEntityFromNBT(compound); + this.readSpawnNBT(compound); } } catch (Exception e) { noppes.npcs.LogWriter.error("Error reading energy ability spawn data", e); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 1697d6d9b..68fa9ec8b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -606,4 +606,20 @@ public class EntityEnergyDome extends EntityEnergyBarrier { protected void writeEntityToNBT(NBTTagCompound nbt) { // Intentionally empty — ability entities are transient (not saved to world) } + + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeBarrierBaseNBT(nbt); + nbt.setFloat("DomeRadius", domeRadius); + nbt.setFloat("TargetDomeRadius", targetDomeRadius); + nbt.setBoolean("FollowCaster", followCaster); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readBarrierBaseNBT(nbt); + this.setDomeRadius(sanitize(nbt.getFloat("DomeRadius"), 5.0f, MAX_ENTITY_RADIUS)); + this.targetDomeRadius = sanitize(nbt.hasKey("TargetDomeRadius") ? nbt.getFloat("TargetDomeRadius") : domeRadius, 5.0f, MAX_ENTITY_RADIUS); + this.followCaster = nbt.hasKey("FollowCaster") && nbt.getBoolean("FollowCaster"); + } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 8c8e2288b..1ef117d94 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -563,4 +563,28 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { protected void writeEntityToNBT(NBTTagCompound nbt) { // Intentionally empty — ability entities are transient (not saved to world) } + + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeBarrierBaseNBT(nbt); + nbt.setFloat("PanelYaw", panelYaw); + nbt.setInteger("PanelMode", mode.ordinal()); + nbt.setFloat("TargetPanelWidth", targetPanelWidth); + nbt.setFloat("TargetPanelHeight", targetPanelHeight); + panelData.writeNBT(nbt); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readBarrierBaseNBT(nbt); + this.panelYaw = nbt.getFloat("PanelYaw"); + int modeOrdinal = nbt.getInteger("PanelMode"); + this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length) + ? PanelMode.values()[modeOrdinal] : PanelMode.PLACED; + panelData.readNBT(nbt); + panelData.panelWidth = sanitize(panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); + panelData.panelHeight = sanitize(panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); + this.targetPanelWidth = sanitize(nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); + this.targetPanelHeight = sanitize(nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); + } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 89d537552..f3a203001 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -934,6 +934,18 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Intentionally empty — ability entities are transient (not saved to world) } + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeBaseNBT(nbt); + writeProjectileNBT(nbt); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readBaseNBT(nbt); + readProjectileNBT(nbt); + } + /** * Read base projectile properties from NBT. */ From 3e58013ad66f219e6e4cba322523d6c1f32ba0e6 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 05:09:00 -0500 Subject: [PATCH 25/71] =?UTF-8?q?Various=20Fixes=20=F0=9F=90=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/renderer/RenderEnergyPanel.java | 14 ++++- .../controllers/data/ability/Ability.java | 55 +++++++++++++++++- .../data/ability/type/AbilityZone.java | 17 ++++++ .../type/energy/AbilityEnergyBarrier.java | 16 ++++++ .../type/energy/AbilityEnergyProjectile.java | 19 +++++++ .../ability/type/energy/AbilitySweeper.java | 21 +++++++ .../npcs/entity/EntityAbilityZone.java | 13 ++++- .../npcs/entity/EntityEnergyPanel.java | 7 +++ .../packets/player/SpecialKeyStatePacket.java | 11 ++-- .../noppes/npcs/AbstractDataAbilities.java | 56 +++++++++++++++++++ .../controllers/data/PlayerAbilityData.java | 47 ++++++++++++++++ .../assets/customnpcs/lang/en_US.lang | 2 + 12 files changed, 268 insertions(+), 10 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java index 38bdd57b2..24c9ea241 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java @@ -21,9 +21,17 @@ public class RenderEnergyPanel extends RenderEnergyBarrier { float width = panel.getPanelData().panelWidth; float height = panel.getPanelData().panelHeight; - float panelYaw = panel.getPanelYaw(); float healthPercent = panel.getHealthPercent(); + // Interpolate panel yaw for smooth rotation between ticks + float prevYaw = panel.getPrevPanelYaw(); + float currentYaw = panel.getPanelYaw(); + float yawDelta = currentYaw - prevYaw; + // Wrap angle delta to -180..180 for shortest rotation path + while (yawDelta > 180.0f) yawDelta -= 360.0f; + while (yawDelta < -180.0f) yawDelta += 360.0f; + float renderYaw = prevYaw + yawDelta * partialTicks; + setupRenderState(); GL11.glPushMatrix(); @@ -34,8 +42,8 @@ public class RenderEnergyPanel extends RenderEnergyBarrier { renderAttachedLightning(panel, 0.9f, Math.max(width, height) * 0.5f); } - // Rotate panel to face the correct direction - GL11.glRotatef(-panelYaw, 0.0f, 1.0f, 0.0f); + // Rotate panel to face the correct direction (interpolated) + GL11.glRotatef(-renderYaw, 0.0f, 1.0f, 0.0f); // Hit flash float flashAlpha = computeFlashAlpha(panel); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 5517caef2..d7ca77eac 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -114,6 +114,10 @@ public abstract class Ability implements IAbility, IAbilityAction { // instead of sharing the global cooldown with other abilities protected boolean perAbilityCooldown = false; + // Free on Cast: if true, ability enters cooldown immediately after entities are spawned, + // allowing the caster to use other abilities while summoned entities remain active + protected boolean freeOnCast = false; + // ═══════════════════════════════════════════════════════════════════ // BUILT-IN (set in constructor via configureAsBuiltIn, NOT persisted) // ═══════════════════════════════════════════════════════════════════ @@ -255,6 +259,22 @@ public abstract class Ability implements IAbility, IAbilityAction { return false; } + /** + * Whether this ability type supports the Free on Cast option. + * Override to return true for abilities that spawn persistent entities (projectiles, barriers, zones, sweeper). + */ + public boolean allowFreeOnCast() { + return false; + } + + /** + * Detach all spawned entities (let them live independently) without killing them. + * Called during signalCompletion() when freeOnCast is enabled. + * Override in subclasses to null entity references without calling setDead(). + */ + public void detach() { + } + // ═══════════════════════════════════════════════════════════════════ // TOGGLE CALLBACKS (optional overrides for toggleable abilities) // ═══════════════════════════════════════════════════════════════════ @@ -612,6 +632,10 @@ public abstract class Ability implements IAbility, IAbilityAction { FieldDef.intField("ability.cooldownTicks", this::getCooldownTicks, this::setCooldownTicks).range(0, 10000), FieldDef.boolField("ability.perAbilityCooldown", this::isPerAbilityCooldown, this::setPerAbilityCooldown) ).tab("General")); + if (allowFreeOnCast()) { + defs.add(FieldDef.boolField("ability.freeOnCast", this::isFreeOnCast, this::setFreeOnCast) + .tab("General").hover("ability.hover.freeOnCast")); + } defs.add(FieldDef.section("ability.section.movement").tab("General")); defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMovementType.getDisplayKeys(), () -> this.getLockMovement().getDisplayKey(), @@ -1072,7 +1096,12 @@ public abstract class Ability implements IAbility, IAbilityAction { // Final completion - let burst entities die naturally (don't force-kill) burstEntities.clear(); - cleanup(); + if (freeOnCast) { + // Free on Cast: detach entities (let them live independently) instead of killing + detach(); + } else { + cleanup(); + } phase = AbilityPhase.IDLE; currentTick = 0; return true; @@ -1101,6 +1130,20 @@ public abstract class Ability implements IAbility, IAbilityAction { telegraphInstances.clear(); } + /** + * Cancel this ability (voluntary player action). Goes directly to IDLE, skipping DAZED. + * Entities are killed via cleanup(). Unlike interrupt(), this never enters DAZED phase. + */ + public void cancel() { + cleanupBurstEntities(); + cleanup(); + phase = AbilityPhase.IDLE; + currentTick = 0; + burstIndex = 0; + currentTarget = null; + telegraphInstances.clear(); + } + /** * Check if executing */ @@ -1298,6 +1341,7 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setInteger("allowedBy", allowedBy.ordinal()); nbt.setBoolean("ignoreCooldown", ignoreCooldown); nbt.setBoolean("perAbilityCooldown", perAbilityCooldown); + nbt.setBoolean("freeOnCast", freeOnCast); // Toggle nbt.setInteger("toggleStates", toggleStates); @@ -1380,6 +1424,7 @@ public abstract class Ability implements IAbility, IAbilityAction { allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy")); ignoreCooldown = nbt.getBoolean("ignoreCooldown"); perAbilityCooldown = nbt.getBoolean("perAbilityCooldown"); + freeOnCast = nbt.getBoolean("freeOnCast"); // Toggle toggleStates = nbt.getInteger("toggleStates"); @@ -1976,6 +2021,14 @@ public abstract class Ability implements IAbility, IAbilityAction { this.perAbilityCooldown = perAbilityCooldown; } + public boolean isFreeOnCast() { + return freeOnCast; + } + + public void setFreeOnCast(boolean freeOnCast) { + this.freeOnCast = freeOnCast; + } + public boolean isToggleable() { return toggleStates > 0; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java index 7f2a92329..a1d1af290 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java @@ -75,6 +75,17 @@ public abstract class AbilityZone extends Ability { return false; } + @Override + public boolean allowFreeOnCast() { + return true; + } + + @Override + public void detach() { + activeEntities.clear(); + preCalculatedPositions.clear(); + } + @Override public boolean isTargetingModeLocked() { return true; @@ -130,6 +141,12 @@ public abstract class AbilityZone extends Ability { @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { + // Free on Cast: complete immediately — zones live independently + if (isFreeOnCast()) { + signalCompletion(); + return; + } + Iterator it = activeEntities.iterator(); while (it.hasNext()) { EntityAbilityZone entity = it.next(); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java index 73f34d059..3366290e9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java @@ -63,6 +63,16 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { return false; // Barriers are defensive } + @Override + public boolean allowFreeOnCast() { + return true; + } + + @Override + public void detach() { + barrierEntity = null; + } + @Override public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) { if (caster.worldObj.isRemote) return; @@ -97,6 +107,12 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { return; } + // Free on Cast: complete immediately after barrier becomes active + if (isFreeOnCast()) { + signalCompletion(); + return; + } + // Duration tracked by entity itself, but check here too as a failsafe if (barrierData.useDuration && tick >= barrierData.durationTicks) { cleanup(); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index da3e95c81..50b68554e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -244,6 +244,16 @@ public abstract class AbilityEnergyProjectile return true; } + @Override + public boolean allowFreeOnCast() { + return true; + } + + @Override + public void detach() { + entities = null; + } + @Override public boolean isReadyForBurstCompletion(int activeTick) { return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1); @@ -338,6 +348,15 @@ public abstract class AbilityEnergyProjectile } } + // Free on Cast: complete once all projectiles have been fired + if (isFreeOnCast()) { + int lastFireTick = fireDelay > 0 ? fireDelay * (projectileCount - 1) : 0; + if (tick >= lastFireTick) { + signalCompletion(); + return; + } + } + // Check if all entities are dead or unreachable boolean allDead = true; for (E entity : entities) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index a1340ae39..2cc64bde4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -62,6 +62,21 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { return false; } + @Override + public boolean isConcurrentCapable() { + return true; + } + + @Override + public boolean allowFreeOnCast() { + return true; + } + + @Override + public void detach() { + activeEntity = null; + } + @Override public boolean isTargetingModeLocked() { return true; @@ -97,6 +112,12 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { if (activeEntity == null || activeEntity.isDead) { activeEntity = null; signalCompletion(); + return; + } + + // Free on Cast: complete immediately — sweeper lives independently + if (isFreeOnCast()) { + signalCompletion(); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index a5225d620..129b87495 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -772,7 +772,11 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD effectList.appendTag(effect.writeNBT()); } effectsNbt.setTag("Effects", effectList); - ByteBufUtils.writeNBT(buffer, effectsNbt); + try { + ByteBufUtils.writeNBT(buffer, effectsNbt); + } catch (java.io.IOException e) { + noppes.npcs.LogWriter.error("Error writing zone effects spawn data", e); + } } @Override @@ -818,7 +822,12 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD this.visible = buffer.readBoolean(); // Effects - NBTTagCompound effectsNbt = ByteBufUtils.readNBT(buffer); + NBTTagCompound effectsNbt = null; + try { + effectsNbt = ByteBufUtils.readNBT(buffer); + } catch (java.io.IOException e) { + noppes.npcs.LogWriter.error("Error reading zone effects spawn data", e); + } effects.clear(); if (effectsNbt != null && effectsNbt.hasKey("Effects")) { NBTTagList effectList = effectsNbt.getTagList("Effects", 10); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 1ef117d94..1afc1f2bc 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -36,6 +36,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { protected EnergyPanelData panelData = new EnergyPanelData(); protected PanelMode mode = PanelMode.PLACED; protected float panelYaw = 0.0f; // Rotation of the panel face (degrees) + protected float prevPanelYaw = 0.0f; // Previous tick yaw for smooth interpolation // ==================== CHARGING (panel-specific targets) ==================== protected float targetPanelWidth; @@ -54,6 +55,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { this.setPosition(x, y + panel.heightOffset, z); this.ownerEntityId = owner.getEntityId(); this.panelYaw = yaw; + this.prevPanelYaw = yaw; this.mode = mode; this.displayData = display; this.lightningData = lightning; @@ -99,6 +101,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { this.prevPosX = this.posX; this.prevPosY = this.posY; this.prevPosZ = this.posZ; + this.prevPanelYaw = this.panelYaw; super.onUpdate(); @@ -548,6 +551,10 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { return panelYaw; } + public float getPrevPanelYaw() { + return prevPanelYaw; + } + public EnergyPanelData getPanelData() { return panelData; } diff --git a/src/main/java/kamkeel/npcs/network/packets/player/SpecialKeyStatePacket.java b/src/main/java/kamkeel/npcs/network/packets/player/SpecialKeyStatePacket.java index 212e816b4..3592e1a9a 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/SpecialKeyStatePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/SpecialKeyStatePacket.java @@ -57,16 +57,19 @@ public final class SpecialKeyStatePacket extends AbstractPacket { boolean wasDown = data.isSpecialKeyDown(); data.setSpecialKeyDown(keyDown); - // Trigger ability activation on key press (rising edge) + // Trigger ability activation or cancel on key press (rising edge) if (keyDown && !wasDown) { - // Don't activate if player has a GUI open (not the default inventory) - // Don't activate if player is riding an entity (mount) if (player.ridingEntity != null) { return; } - data.abilityData.activateAbility(player); + // If currently executing an ability, try to cancel via double-press + if (data.abilityData.isExecutingAbility() || data.abilityData.isExecutingChain()) { + data.abilityData.tryCancelAbility(); + } else { + data.abilityData.activateAbility(player); + } } } } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index b78ca37e7..b63481c21 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -1034,6 +1034,62 @@ public abstract class AbstractDataAbilities { protected void onInterruptComplete() { } + /** + * Cancel the currently executing ability (voluntary player action). + * Unlike interrupt, this always goes directly to IDLE (never DAZED), + * immediately rolls cooldown, and clears state. + * Also handles cancellation during chain delay (between chain entries). + */ + public void cancelCurrentAbility() { + boolean hasExecutingAbility = currentAbility != null && currentAbility.isExecuting(); + boolean isInChainDelay = currentChain != null && chainDelayRemaining > 0; + + if (!hasExecutingAbility && !isInChainDelay) return; + + if (hasExecutingAbility) { + AbilityPhase phase = currentAbility.getPhase(); + // Cannot cancel during DAZED (already interrupted) + if (phase == AbilityPhase.DAZED) return; + // Only allow cancel during active phases + if (phase != AbilityPhase.WINDUP && phase != AbilityPhase.ACTIVE + && phase != AbilityPhase.BURST_DELAY) return; + + // Remove telegraph + removeTelegraph(currentAbility); + + // Fire extender complete hook (cancelled = interrupted) + AbilityController.Instance.fireOnAbilityComplete(currentAbility, getEntity(), getTarget(), true); + + // Fire interrupt event (source=null, damage=0 for voluntary cancel) + fireInterruptEvent(currentAbility, getTarget(), null, 0); + + // Cancel the ability (cleanup + go directly to IDLE, no DAZED) + currentAbility.onInterrupt(getEntity(), null, 0); + currentAbility.cancel(); + + // Release locks and stop animation + stopAbilityAnimation(); + releaseRotationControl(); + releaseLockedPosition(); + } + + // Roll cooldown + if (currentChain != null) { + rollChainCooldown(currentChain); + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; + } else if (currentAbility != null) { + rollCooldown(currentAbility); + } + + // Interrupt all concurrent slots + interruptConcurrentSlots(); + + // Clear state via the normal completion path + onAbilityComplete(); + } + // ═══════════════════════════════════════════════════════════════════ // CHAINED ABILITY EXECUTION // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index b8958d24a..79890baeb 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -91,6 +91,16 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA */ private transient byte lastSyncedFlags = 0; + /** + * World time when the last ability was activated (for double-press cancel detection). + */ + private transient long lastAbilityActivationTime = -1; + + /** + * Ticks after activation during which a second key press will cancel the ability. + */ + private static final int CANCEL_WINDOW_TICKS = 10; + // ═══════════════════════════════════════════════════════════════════ // CONSTRUCTOR // ═══════════════════════════════════════════════════════════════════ @@ -258,6 +268,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentAbility = null; currentAbilityKey = null; currentTarget = null; + lastAbilityActivationTime = -1; syncAbilityStateClear(playerData.player); } @@ -273,6 +284,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentAbility = null; currentAbilityKey = null; currentTarget = null; + lastAbilityActivationTime = -1; syncAbilityStateClear(playerData.player); } @@ -378,6 +390,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentAbilityKey = key; currentTarget = null; + lastAbilityActivationTime = getWorldTime(); return startChain((ChainedAbility) action, null); } @@ -413,6 +426,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentAbility = ability; currentAbilityKey = key; currentTarget = null; // Players don't have auto-targets + lastAbilityActivationTime = getWorldTime(); ability.start(null); if (ability.getPhase() == AbilityPhase.ACTIVE) { @@ -662,6 +676,38 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA interruptCurrentAbility(null, 0); } + /** + * Try to cancel the current ability via double-press. + * Only succeeds if within the cancel window and not in DAZED phase. + * Also handles cancellation during chain delay (between chain entries). + * + * @return true if the ability was cancelled + */ + public boolean tryCancelAbility() { + boolean hasExecutingAbility = currentAbility != null && currentAbility.isExecuting(); + boolean isInChainDelay = currentChain != null && chainDelayRemaining > 0; + + if (!hasExecutingAbility && !isInChainDelay) return false; + + // Don't allow cancel during DAZED + if (hasExecutingAbility) { + AbilityPhase phase = currentAbility.getPhase(); + if (phase == AbilityPhase.DAZED) return false; + if (phase != AbilityPhase.WINDUP && phase != AbilityPhase.ACTIVE + && phase != AbilityPhase.BURST_DELAY) return false; + } + + long worldTime = getWorldTime(); + if (lastAbilityActivationTime < 0 || (worldTime - lastAbilityActivationTime) > CANCEL_WINDOW_TICKS) { + // Outside cancel window — update time so a quick follow-up press can cancel + lastAbilityActivationTime = worldTime; + return false; + } + + cancelCurrentAbility(); + return true; + } + // ═══════════════════════════════════════════════════════════════════ // ENTITY RECONSTRUCTION RESET // ═══════════════════════════════════════════════════════════════════ @@ -701,6 +747,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentAbility = null; currentAbilityKey = null; currentTarget = null; + lastAbilityActivationTime = -1; if (clearCooldowns) { cooldownEndTime = 0; diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 8e2aa437c..dbe0442cd 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1674,6 +1674,8 @@ ability.weight=Weight ability.windUpTicks=Wind Up ability.cooldownTicks=Cooldown ability.perAbilityCooldown=Per-Ability +ability.freeOnCast=Free on Cast +ability.hover.freeOnCast=Ability enters cooldown immediately after entities are spawned, allowing the caster to use other abilities while summoned entities remain active ability.lockMovement=Lock Movement ability.rotationMode=Rotation ability.rotationPhase=Phase From 04679a34062b873c8854a7d5e147e169a7b883af Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 05:24:33 -0500 Subject: [PATCH 26/71] Disable Projectile and Slicer --- .../java/kamkeel/npcs/controllers/AbilityController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 02c438e62..572303052 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -124,19 +124,23 @@ public class AbilityController implements IAbilityHandler { private void registerBuiltinTypes() { registerType("cnpc:slam", AbilitySlam::new); + registerType("cnpc:charge", AbilityCharge::new); + registerType("cnpc:heavy_hit", AbilityHeavyHit::new); registerType("cnpc:cutter", AbilityCutter::new); + // registerType("cnpc:projectile", AbilityProjectile::new); + registerType("cnpc:sweeper", AbilitySweeper::new); - registerType("cnpc:projectile", AbilityProjectile::new); registerType("cnpc:orb", AbilityOrb::new); registerType("cnpc:disc", AbilityDisc::new); registerType("cnpc:laser_shot", AbilityLaserShot::new); registerType("cnpc:beam", AbilityBeam::new); + // registerType("cnpc:slicer", AbilitySlicer::new); - registerType("cnpc:charge", AbilityCharge::new); registerType("cnpc:dash", AbilityDash::new); registerType("cnpc:teleport", AbilityTeleport::new); + registerType("cnpc:vortex", AbilityVortex::new); registerType("cnpc:shockwave", AbilityShockwave::new); @@ -149,7 +153,6 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:dome", AbilityDome::new); registerType("cnpc:wall", AbilityWall::new); registerType("cnpc:shield", AbilityShield::new); - registerType("cnpc:slicer", AbilitySlicer::new); } private void registerBuiltinConditionTypes() { From 72c6c004b0189f05df67172345c0d8423b2a7a9a Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 11:58:53 -0300 Subject: [PATCH 27/71] Changed condition summary to use filter names --- .../controllers/data/ability/conditions/ConditionHasEffect.java | 2 +- .../controllers/data/ability/conditions/ConditionHitCount.java | 2 +- .../npcs/controllers/data/ability/conditions/ConditionItem.java | 2 +- .../data/ability/conditions/ConditionQuestCompleted.java | 2 +- .../controllers/data/ability/conditions/ConditionThreshold.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java index b0c3941ad..124b34bfb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java @@ -57,7 +57,7 @@ public class ConditionHasEffect extends AbilityCondition { @Override public String getConditionSummary() { - String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String filterLabel = StatCollector.translateToLocal(getFilter().toString()); String effectName = "None"; if (effectId >= 0) { CustomEffect effect = getEffect(); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java index dbd71922b..8a326a16c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java @@ -60,7 +60,7 @@ public class ConditionHitCount extends AbilityCondition { @Override public String getConditionSummary() { - String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String filterLabel = StatCollector.translateToLocal(getFilter().toString()); return "[" + filterLabel + "] Hit " + requiredHits + " times in " + withinTicks + " ticks"; } 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 index 9bb445043..a137304ef 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -152,7 +152,7 @@ public class ConditionItem extends AbilityCondition { @Override public String getConditionSummary() { - String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String filterLabel = StatCollector.translateToLocal(getFilter().toString()); String usage = usageType.name(); String item = itemName.isEmpty() ? "None" : itemName; return "[" + filterLabel + "] " + usage + ": " + item; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java index 04c1ec16a..213358ca5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -52,7 +52,7 @@ public class ConditionQuestCompleted extends AbilityCondition { @Override public String getConditionSummary() { - String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String filterLabel = StatCollector.translateToLocal(getFilter().toString()); String questName = "None"; if (questId >= 0) { Quest q = QuestController.Instance.quests.get(questId); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java index fda42ce6a..aaa4a6b06 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java @@ -124,7 +124,7 @@ public abstract class ConditionThreshold extends AbilityCondition { @Override public String getConditionSummary() { - String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String filterLabel = StatCollector.translateToLocal(getFilter().toString()); String typeName = getStatName(); String typeLabel = StatCollector.translateToLocal(thresholdType.toString()); String value = percent From 7d5ad7abfe4f1f737d5e7af097ae2ad7f5f534e7 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 13:21:16 -0300 Subject: [PATCH 28/71] Energy charge data for future implementation --- .../data/ability/data/EnergyChargingData.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java new file mode 100644 index 000000000..c12cec47f --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java @@ -0,0 +1,83 @@ +package kamkeel.npcs.controllers.data.ability.data; + +import net.minecraft.nbt.NBTTagCompound; + +public class EnergyChargingData { + + public int chargeDuration = 60; + public int chargeDelay = 0; + public int maxCharge = 100; + public boolean hasCharge = true; + public boolean chargeWindUpSync = true; + + public EnergyChargingData() { + } + + public EnergyChargingData(int chargeDuration, int chargeDelay, int maxCharge, boolean hasCharge, boolean chargeWindUpSync) { + this.chargeDuration = chargeDuration; + this.chargeDelay = chargeDelay; + this.maxCharge = maxCharge; + this.hasCharge = hasCharge; + this.chargeWindUpSync = chargeWindUpSync; + } + + public boolean hasCharge() { + return hasCharge; + } + + public void setHasCharge(boolean hasCharge) { + this.hasCharge = hasCharge; + } + + public boolean isChargeWindUpSync() { + return chargeWindUpSync; + } + + public void setChargeWindUpSync(boolean chargeWindUpSync) { + this.chargeWindUpSync = chargeWindUpSync; + } + + public int getChargeDuration() { + return chargeDuration; + } + + public void setChargeDuration(int chargeDuration) { + this.chargeDuration = chargeDuration; + } + + public int getChargeDelay() { + return chargeDelay; + } + + public void setChargeDelay(int chargeDelay) { + this.chargeDelay = chargeDelay; + } + + public int getMaxCharge() { + return maxCharge; + } + + public void setMaxCharge(int maxCharge) { + this.maxCharge = Math.max(0, maxCharge); + } + + public void writeNBT(NBTTagCompound nbt) { + nbt.setInteger("chargeDuration", chargeDuration); + nbt.setInteger("chargeDelay", chargeDelay); + nbt.setInteger("maxCharge", maxCharge); + nbt.setBoolean("hasCharge", hasCharge); + nbt.setBoolean("chargeWindUpSync", chargeWindUpSync); + } + + public void readNBT(NBTTagCompound nbt) { + chargeDuration = nbt.hasKey("chargeDuration") ? nbt.getInteger("chargeDuration") : 60; + chargeDelay = nbt.hasKey("chargeDelay") ? nbt.getInteger("chargeDelay") : 0; + maxCharge = nbt.hasKey("maxCharge") ? nbt.getInteger("maxCharge") : 100; + hasCharge = !nbt.hasKey("hasCharge") || nbt.getBoolean("hasCharge"); + chargeWindUpSync = !nbt.hasKey("chargeWindUpSync") || nbt.getBoolean("chargeWindUpSync"); + } + + public EnergyChargingData copy() { + return new EnergyChargingData(chargeDuration, chargeDelay, maxCharge, hasCharge, chargeWindUpSync); + } +} From ecfedff9d26dc864652c789f7efa0a3f4bbf760e Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 14:40:14 -0500 Subject: [PATCH 29/71] - Barrier Health Scaling with DBC Addon - Healing Health Scaling with DBC Addon - Ki Attacks collided with Barriers - Remove Ability Slot (Deprecated) - Refactored Naming Scheme for Barrier Abilities - Fixed Player Settings for Ignore Dex, DBC Stats, etc. --- ...EnergyDome.java => RenderAbilityDome.java} | 6 +- ...ergyPanel.java => RenderAbilityPanel.java} | 6 +- .../client/renderer/RenderEnergyBarrier.java | 4 +- .../npcs/controllers/AbilityController.java | 23 +++ .../controllers/data/ability/Ability.java | 22 +-- .../data/ability/AbilityAction.java | 2 +- .../controllers/data/ability/AbilitySlot.java | 163 ------------------ .../data/ability/IAbilityExtender.java | 28 +++ .../data/ability/type/AbilityEffect.java | 12 +- ...EnergyBarrier.java => AbilityBarrier.java} | 23 ++- .../data/ability/type/energy/AbilityDome.java | 10 +- .../ability/type/energy/AbilityShield.java | 12 +- .../data/ability/type/energy/AbilityWall.java | 16 +- ...Barrier.java => EntityAbilityBarrier.java} | 32 ++-- ...EnergyDome.java => EntityAbilityDome.java} | 12 +- .../npcs/entity/EntityAbilityLaser.java | 10 +- ...ergyPanel.java => EntityAbilityPanel.java} | 14 +- .../npcs/entity/EntityEnergyProjectile.java | 4 +- src/main/java/noppes/npcs/CustomNpcs.java | 11 +- .../java/noppes/npcs/ServerEventsHandler.java | 11 +- .../java/noppes/npcs/client/ClientProxy.java | 12 +- .../java/noppes/npcs/scripted/NpcAPI.java | 24 ++- .../scripted/entity/ScriptEnergyBarrier.java | 10 +- .../scripted/entity/ScriptEnergyDome.java | 4 +- .../scripted/entity/ScriptEnergyPanel.java | 6 +- 25 files changed, 201 insertions(+), 276 deletions(-) rename src/main/java/kamkeel/npcs/client/renderer/{RenderEnergyDome.java => RenderAbilityDome.java} (96%) rename src/main/java/kamkeel/npcs/client/renderer/{RenderEnergyPanel.java => RenderAbilityPanel.java} (96%) delete mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java rename src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/{AbilityEnergyBarrier.java => AbilityBarrier.java} (90%) rename src/main/java/kamkeel/npcs/entity/{EntityEnergyBarrier.java => EntityAbilityBarrier.java} (92%) rename src/main/java/kamkeel/npcs/entity/{EntityEnergyDome.java => EntityAbilityDome.java} (98%) rename src/main/java/kamkeel/npcs/entity/{EntityEnergyPanel.java => EntityAbilityPanel.java} (97%) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java similarity index 96% rename from src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java rename to src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java index 3e05168d9..beebe98f3 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java @@ -2,7 +2,7 @@ package kamkeel.npcs.client.renderer; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityAbilityDome; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; @@ -13,11 +13,11 @@ import org.lwjgl.opengl.GL11; * Optional skybox texture rendered on the interior surface. */ @SideOnly(Side.CLIENT) -public class RenderEnergyDome extends RenderEnergyBarrier { +public class RenderAbilityDome extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { - EntityEnergyDome dome = (EntityEnergyDome) entity; + EntityAbilityDome dome = (EntityAbilityDome) entity; float radius = dome.getDomeRadius(); float healthPercent = dome.getHealthPercent(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java similarity index 96% rename from src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java rename to src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java index 24c9ea241..1261aaf1e 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java @@ -2,7 +2,7 @@ package kamkeel.npcs.client.renderer; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityPanel; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; @@ -13,11 +13,11 @@ import org.lwjgl.opengl.GL11; * Voxel-blocky style with inner/outer layers. */ @SideOnly(Side.CLIENT) -public class RenderEnergyPanel extends RenderEnergyBarrier { +public class RenderAbilityPanel extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { - EntityEnergyPanel panel = (EntityEnergyPanel) entity; + EntityAbilityPanel panel = (EntityAbilityPanel) entity; float width = panel.getPanelData().panelWidth; float height = panel.getPanelData().panelHeight; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java index 6f4e1e89f..676440035 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java @@ -2,7 +2,7 @@ package kamkeel.npcs.client.renderer; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.entity.EntityEnergyBarrier; +import kamkeel.npcs.entity.EntityAbilityBarrier; /** * Intermediate renderer base for energy barrier entities (Dome, Panel). @@ -15,7 +15,7 @@ public abstract class RenderEnergyBarrier extends RenderEnergyAbility { * Compute the alpha boost from a hit flash effect. * Flash value decays from 4 to 0, producing alpha from 0.3 to 0. */ - protected float computeFlashAlpha(EntityEnergyBarrier barrier) { + protected float computeFlashAlpha(EntityAbilityBarrier barrier) { byte flash = barrier.getHitFlash(); if (flash <= 0) return 0.0f; return flash / 4.0f * 0.3f; diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 02c438e62..a908e77f2 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -1002,6 +1002,29 @@ public class AbilityController implements IAbilityHandler { return damage; } + /** + * Fire modifyBarrierHealth on all extenders. Cumulative — each extender's output feeds the next. + */ + public float fireModifyBarrierHealth(Ability ability, EntityLivingBase caster, float baseHealth) { + float health = baseHealth; + for (IAbilityExtender ext : extenders) { + health = ext.modifyBarrierHealth(ability, caster, health); + } + return health; + } + + /** + * Fire onAbilityHeal on all extenders. Chain of responsibility — first true wins. + */ + public boolean fireOnAbilityHeal(Ability ability, EntityLivingBase caster, EntityLivingBase target, float healAmount) { + for (IAbilityExtender ext : extenders) { + if (ext.onAbilityHeal(ability, caster, target, healAmount)) { + return true; + } + } + return false; + } + // ═══════════════════════════════════════════════════════════════════════════ // IAbilityHandler // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index d7ca77eac..9d61188c5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -7,6 +7,9 @@ import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityDome; +import kamkeel.npcs.entity.EntityAbilityPanel; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -20,9 +23,6 @@ import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.StatCollector; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import kamkeel.npcs.entity.EntityEnergyBarrier; -import kamkeel.npcs.entity.EntityEnergyDome; -import kamkeel.npcs.entity.EntityEnergyPanel; import noppes.npcs.DataAbilities; import noppes.npcs.NpcDamageSource; import noppes.npcs.api.INbt; @@ -2264,9 +2264,9 @@ public abstract class Ability implements IAbility, IAbilityAction { double maxZ = Math.max(caster.posZ, target.posZ) + searchRange; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(minX, minY, minZ, maxX, maxY, maxZ); - List barriers = world.getEntitiesWithinAABB(EntityEnergyBarrier.class, searchBox); + List barriers = world.getEntitiesWithinAABB(EntityAbilityBarrier.class, searchBox); - for (EntityEnergyBarrier barrier : barriers) { + for (EntityAbilityBarrier barrier : barriers) { if (barrier.isDead) continue; if (barrier.isCharging()) continue; @@ -2277,10 +2277,10 @@ public abstract class Ability implements IAbility, IAbilityAction { if (AbilityTargetHelper.isAlly(caster, (EntityLivingBase) barrierOwner)) continue; } - if (barrier instanceof EntityEnergyDome) { - if (isDomeBlocking((EntityEnergyDome) barrier, caster, target)) return true; - } else if (barrier instanceof EntityEnergyPanel) { - if (isPanelBlocking((EntityEnergyPanel) barrier, caster, target)) return true; + if (barrier instanceof EntityAbilityDome) { + if (isDomeBlocking((EntityAbilityDome) barrier, caster, target)) return true; + } else if (barrier instanceof EntityAbilityPanel) { + if (isPanelBlocking((EntityAbilityPanel) barrier, caster, target)) return true; } } @@ -2291,7 +2291,7 @@ public abstract class Ability implements IAbility, IAbilityAction { * Check if a dome blocks an attack from caster to target. * Blocks when caster is outside and target is inside the dome. */ - private static boolean isDomeBlocking(EntityEnergyDome dome, EntityLivingBase caster, EntityLivingBase target) { + private static boolean isDomeBlocking(EntityAbilityDome dome, EntityLivingBase caster, EntityLivingBase target) { float radius = dome.getDomeRadius(); double cdx = caster.posX - dome.posX; @@ -2312,7 +2312,7 @@ public abstract class Ability implements IAbility, IAbilityAction { * Check if a panel blocks an attack from caster to target. * Checks if the line from caster to target crosses through the panel bounds. */ - private static boolean isPanelBlocking(EntityEnergyPanel panel, EntityLivingBase caster, EntityLivingBase target) { + private static boolean isPanelBlocking(EntityAbilityPanel panel, EntityLivingBase caster, EntityLivingBase target) { float halfW = panel.getPanelData().panelWidth * 0.5f; float halfH = panel.getPanelData().panelHeight * 0.5f; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java index 77370b58c..60a64369d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -5,7 +5,7 @@ import net.minecraft.nbt.NBTTagCompound; /** * Unified slot for storing any combat action: inline ability, ability reference, or chain reference. - * Replaces {@link AbilitySlot} with support for all {@link IAbilityAction} types. + * Support for all {@link IAbilityAction} types. *

* References are lazy-resolved via their respective controllers and cached until * the controller revision changes. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java deleted file mode 100644 index 1afbf0906..000000000 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java +++ /dev/null @@ -1,163 +0,0 @@ -package kamkeel.npcs.controllers.data.ability; - -import kamkeel.npcs.controllers.AbilityController; -import net.minecraft.nbt.NBTTagCompound; - -/** - * Wrapper that stores either an inline ability (full NBT) or a reference - * to a built-in/custom ability (by name or UUID). - *

- * References are lazy-resolved via {@link AbilityController#resolveAbility(String)} - * and cached until the controller version changes. - * - * @deprecated Use {@link AbilityAction} instead, which supports abilities and chained abilities. - */ -@Deprecated -public class AbilitySlot { - - private Ability inlineAbility; - private String referenceId; - - /** - * Per-slot enabled override for reference slots (null = use master's default). - */ - private Boolean enabledOverride; - - private transient Ability cachedAbility; - private transient int cachedVersion = -1; - - private AbilitySlot() { - } - - /** - * Create a slot with an inline (embedded) ability. - */ - public static AbilitySlot inline(Ability ability) { - AbilitySlot slot = new AbilitySlot(); - slot.inlineAbility = ability; - slot.referenceId = null; - return slot; - } - - /** - * Create a slot that references a built-in ability (by name) or custom ability (by name). - */ - public static AbilitySlot reference(String referenceId) { - AbilitySlot slot = new AbilitySlot(); - slot.inlineAbility = null; - slot.referenceId = referenceId; - return slot; - } - - public boolean isReference() { - return referenceId != null; - } - - public String getReferenceId() { - return referenceId; - } - - /** - * Get the resolved ability. For inline slots, returns the ability directly. - * For reference slots, lazily resolves from AbilityController and caches. - * Returns null if the reference cannot be resolved (broken ref). - */ - public Ability getAbility() { - if (!isReference()) { - return inlineAbility; - } - - // Lazy resolve with version-based cache invalidation - AbilityController controller = AbilityController.Instance; - if (controller == null) return null; - - int currentVersion = controller.getCustomAbilityRevision(); - if (cachedAbility != null && cachedVersion == currentVersion) { - return cachedAbility; - } - - cachedAbility = controller.resolveAbility(referenceId); - // Apply per-slot enabled override so NPC-specific enable/disable persists - if (cachedAbility != null && enabledOverride != null) { - cachedAbility.setEnabled(enabledOverride); - } - cachedVersion = currentVersion; - return cachedAbility; - } - - /** - * Set the per-slot enabled override for reference slots. - * For inline slots, delegates directly to the ability. - */ - public void setEnabled(boolean enabled) { - if (!isReference()) { - if (inlineAbility != null) { - inlineAbility.setEnabled(enabled); - } - } else { - this.enabledOverride = enabled; - // Also update the cached ability immediately if present - if (cachedAbility != null) { - cachedAbility.setEnabled(enabled); - } - } - } - - /** - * Get the inline ability directly (null for reference slots). - */ - public Ability getInlineAbility() { - return inlineAbility; - } - - /** - * Convert this reference slot to an inline slot by copying the resolved ability. - * No-op if already inline. Returns false if resolution fails. - */ - public boolean convertToInline() { - if (!isReference()) return true; - - Ability resolved = getAbility(); - if (resolved == null) return false; - - // Deep copy via NBT round-trip - inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT()); - referenceId = null; - cachedAbility = null; - cachedVersion = -1; - return true; - } - - // ═══════════════════════════════════════════════════════════════════ - // NBT - // ═══════════════════════════════════════════════════════════════════ - - public NBTTagCompound writeNBT() { - if (isReference()) { - NBTTagCompound nbt = new NBTTagCompound(); - nbt.setString("Reference", referenceId); - if (enabledOverride != null) { - nbt.setBoolean("RefEnabled", enabledOverride); - } - return nbt; - } - return inlineAbility != null ? inlineAbility.writeNBT() : new NBTTagCompound(); - } - - public static AbilitySlot fromNBT(NBTTagCompound nbt) { - if (nbt == null) return null; - - if (nbt.hasKey("Reference")) { - AbilitySlot slot = reference(nbt.getString("Reference")); - if (nbt.hasKey("RefEnabled")) { - slot.enabledOverride = nbt.getBoolean("RefEnabled"); - } - return slot; - } - - // Inline ability - Ability ability = AbilityController.Instance.fromNBT(nbt); - if (ability == null) return null; - return inline(ability); - } -} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java b/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java index 2bb179cc2..f3e7ccfd8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java @@ -87,4 +87,32 @@ public interface IAbilityExtender { default float modifyProjectileDamage(Ability ability, EntityLivingBase caster, float baseDamage) { return baseDamage; } + + /** + * Called when a barrier entity is about to be created, allowing extenders to modify its max health. + * Cumulative: all extenders apply in sequence, each receiving the previous output. + * + * @param ability The barrier ability being executed + * @param caster The entity creating the barrier + * @param baseHealth The base max health from EnergyBarrierData.maxHealth + * @return The modified max health value + */ + default float modifyBarrierHealth(Ability ability, EntityLivingBase caster, float baseHealth) { + return baseHealth; + } + + /** + * Called when an ability is about to heal an entity. + * Return true to handle the healing (skip default entity.heal()). + * Chain of responsibility: first returning true wins. + * + * @param ability The ability performing the heal + * @param caster The entity casting the ability + * @param target The entity being healed + * @param healAmount The heal amount calculated by the ability + * @return true if healing was handled (skip default), false to pass to next extender or default + */ + default boolean onAbilityHeal(Ability ability, EntityLivingBase caster, EntityLivingBase target, float healAmount) { + return false; + } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java index c81e97ced..d4f9c474b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java @@ -131,7 +131,7 @@ public class AbilityEffect extends Ability implements IAbilityEffect { if (instantHeal) { for (EntityLivingBase entity : getAffectedEntities()) { - healEntity(entity); + healEntity(caster, entity); applyAllEffects(caster, entity); spawnHealParticles(caster.worldObj, entity); } @@ -159,7 +159,9 @@ public class AbilityEffect extends Ability implements IAbilityEffect { } if (totalTickHeal > 0) { - entity.heal(totalTickHeal); + if (!AbilityController.Instance.fireOnAbilityHeal(this, caster, entity, totalTickHeal)) { + entity.heal(totalTickHeal); + } } if (tick % 20 == 0) { @@ -226,13 +228,15 @@ public class AbilityEffect extends Ability implements IAbilityEffect { } } - private void healEntity(EntityLivingBase entity) { + private void healEntity(EntityLivingBase caster, EntityLivingBase entity) { float totalHeal = healAmount; if (healPercent > 0) { totalHeal += entity.getMaxHealth() * healPercent; } if (totalHeal > 0) { - entity.heal(totalHeal); + if (!AbilityController.Instance.fireOnAbilityHeal(this, caster, entity, totalHeal)) { + entity.heal(totalHeal); + } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java similarity index 90% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index 3366290e9..841750d1c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -2,9 +2,10 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.entity.EntityEnergyBarrier; +import kamkeel.npcs.entity.EntityAbilityBarrier; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; @@ -16,14 +17,14 @@ import java.util.List; * Abstract base for energy barrier abilities (Dome, Wall, Shield). * Handles shared visual data, barrier configuration, and lifecycle management. */ -public abstract class AbilityEnergyBarrier extends AbilityEnergy { +public abstract class AbilityBarrier extends AbilityEnergy { protected final EnergyBarrierData barrierData; // Runtime entity tracking - protected transient EntityEnergyBarrier barrierEntity; + protected transient EntityAbilityBarrier barrierEntity; - protected AbilityEnergyBarrier(EnergyDisplayData displayData, EnergyBarrierData barrierData) { + protected AbilityBarrier(EnergyDisplayData displayData, EnergyBarrierData barrierData) { super(displayData); this.barrierData = barrierData; } @@ -33,7 +34,7 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { /** * Create and spawn the barrier entity during execution. */ - protected abstract EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target); + protected abstract EntityAbilityBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target); /** * Add type-specific GUI field definitions. @@ -80,6 +81,7 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { if (tick == 1) { barrierEntity = createBarrierEntity(caster, target); if (barrierEntity != null) { + applyBarrierHealthModifiers(caster); barrierEntity.setupCharging(getWindUpTicks()); spawnAbilityEntity(barrierEntity); } @@ -95,6 +97,7 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { // No windup or entity died — create fresh barrierEntity = createBarrierEntity(caster, target); if (barrierEntity != null) { + applyBarrierHealthModifiers(caster); spawnAbilityEntity(barrierEntity); } } @@ -124,6 +127,16 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { public void onComplete(EntityLivingBase caster, EntityLivingBase target) { } + /** + * Allow extenders to modify the barrier's max health at spawn time. + */ + private void applyBarrierHealthModifiers(EntityLivingBase caster) { + float modHealth = AbilityController.Instance.fireModifyBarrierHealth(this, caster, barrierData.maxHealth); + if (modHealth != barrierData.maxHealth) { + barrierEntity.setBarrierMaxHealth(modHealth); + } + } + @Override public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { cleanup(); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index 78a317cc2..1663f2447 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -8,8 +8,8 @@ import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; -import kamkeel.npcs.entity.EntityEnergyBarrier; -import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityDome; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -22,7 +22,7 @@ import java.util.List; * Blocks incoming energy projectiles with configurable damage multipliers. * Duration and/or HP based. */ -public class AbilityDome extends AbilityEnergyBarrier { +public class AbilityDome extends AbilityBarrier { private float domeRadius = 5.0f; private boolean followCaster = false; @@ -47,8 +47,8 @@ public class AbilityDome extends AbilityEnergyBarrier { // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { - EntityEnergyDome dome = new EntityEnergyDome( + protected EntityAbilityBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + EntityAbilityDome dome = new EntityAbilityDome( caster.worldObj, caster, caster.posX, caster.posY, caster.posZ, domeRadius, displayData.copy(), lightningData.copy(), barrierData.copy() diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java index f19f9844f..2d9c515a4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java @@ -10,8 +10,8 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; -import kamkeel.npcs.entity.EntityEnergyBarrier; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityPanel; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -24,7 +24,7 @@ import java.util.List; * The shield follows the caster's position and rotates with their look direction. * Cannot be placed — it's always held. */ -public class AbilityShield extends AbilityEnergyBarrier { +public class AbilityShield extends AbilityBarrier { private float shieldWidth = 2.5f; private float shieldHeight = 2.5f; @@ -50,7 +50,7 @@ public class AbilityShield extends AbilityEnergyBarrier { // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + protected EntityAbilityBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { EnergyPanelData panelData = new EnergyPanelData(shieldWidth, shieldHeight, 0.0f); float frontDist = 1.5f; @@ -59,10 +59,10 @@ public class AbilityShield extends AbilityEnergyBarrier { double spawnY = caster.posY + (caster.height * 0.5f); double spawnZ = caster.posZ + (Math.cos(yawRad) * frontDist); - EntityEnergyPanel panel = new EntityEnergyPanel( + EntityAbilityPanel panel = new EntityAbilityPanel( caster.worldObj, caster, spawnX, spawnY, spawnZ, caster.rotationYaw, - EntityEnergyPanel.PanelMode.HELD, + EntityAbilityPanel.PanelMode.HELD, displayData.copy(), lightningData.copy(), barrierData.copy(), panelData ); panel.setSourceAbility(this); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index 4320177c1..5237485b8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -9,8 +9,8 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; -import kamkeel.npcs.entity.EntityEnergyBarrier; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityPanel; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -23,7 +23,7 @@ import java.util.List; * Can float above ground (configurable height offset). * Optional launching mode fires the wall forward with damage/knockback. */ -public class AbilityWall extends AbilityEnergyBarrier { +public class AbilityWall extends AbilityBarrier { private final EnergyPanelData panelData; @@ -55,7 +55,7 @@ public class AbilityWall extends AbilityEnergyBarrier { // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + protected EntityAbilityBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { // Place wall between caster and target double placeX, placeY, placeZ; float yaw; @@ -87,11 +87,11 @@ public class AbilityWall extends AbilityEnergyBarrier { yaw = caster.rotationYaw; } - EntityEnergyPanel.PanelMode mode = panelData.launching - ? EntityEnergyPanel.PanelMode.LAUNCHED - : EntityEnergyPanel.PanelMode.PLACED; + EntityAbilityPanel.PanelMode mode = panelData.launching + ? EntityAbilityPanel.PanelMode.LAUNCHED + : EntityAbilityPanel.PanelMode.PLACED; - EntityEnergyPanel panel = new EntityEnergyPanel( + EntityAbilityPanel panel = new EntityAbilityPanel( caster.worldObj, caster, placeX, placeY, placeZ, yaw, mode, displayData.copy(), lightningData.copy(), barrierData.copy(), panelData.copy() diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java similarity index 92% rename from src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java rename to src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java index 2db51fd14..02fbbdc4e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java @@ -22,24 +22,24 @@ import java.util.List; * and barrier lifecycle management. * Extends EntityEnergyAbility for shared visual/owner/charging state. */ -public abstract class EntityEnergyBarrier extends EntityEnergyAbility { +public abstract class EntityAbilityBarrier extends EntityEnergyAbility { // ==================== ACTIVE BARRIER REGISTRY ==================== - private static final List> activeBarriers = new ArrayList<>(); + private static final List> activeBarriers = new ArrayList<>(); private boolean tracked = false; - public static void trackBarrier(EntityEnergyBarrier barrier) { + public static void trackBarrier(EntityAbilityBarrier barrier) { activeBarriers.add(new WeakReference<>(barrier)); } /** * Get all living barriers in the given world. Auto-cleans dead refs. */ - public static List getActiveBarriers(World world) { - List result = new ArrayList<>(); - Iterator> it = activeBarriers.iterator(); + public static List getActiveBarriers(World world) { + List result = new ArrayList<>(); + Iterator> it = activeBarriers.iterator(); while (it.hasNext()) { - EntityEnergyBarrier b = it.next().get(); + EntityAbilityBarrier b = it.next().get(); if (b == null || b.isDead) { it.remove(); } else if (b.worldObj == world) { @@ -62,7 +62,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { protected static final int DW_HEALTH_PERCENT = 21; protected static final int DW_HIT_FLASH = 22; - public EntityEnergyBarrier(World world) { + public EntityAbilityBarrier(World world) { super(world); this.noClip = true; } @@ -358,10 +358,10 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { * Find a barrier that would absorb damage for the given entity (its caster). * Returns the barrier if: absorbing is enabled AND entity is the barrier's owner. */ - public static EntityEnergyBarrier getAbsorbingBarrier(Entity entity) { + public static EntityAbilityBarrier getAbsorbingBarrier(Entity entity) { if (entity == null || entity.worldObj == null) return null; - List barriers = getActiveBarriers(entity.worldObj); - for (EntityEnergyBarrier barrier : barriers) { + List barriers = getActiveBarriers(entity.worldObj); + for (EntityAbilityBarrier barrier : barriers) { if (barrier.isDead) continue; if (!barrier.barrierData.absorbing) continue; if (barrier.ownerEntityId == entity.getEntityId()) { @@ -425,6 +425,16 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { syncHealthPercent(); } + /** + * Override the barrier's max health and set current health to match. + * Used by extenders (e.g., DBC scaling) to dynamically set barrier HP at spawn time. + */ + public void setBarrierMaxHealth(float maxHealth) { + this.barrierData.setMaxHealth(maxHealth); + this.currentHealth = maxHealth; + syncHealthPercent(); + } + public int getTicksAlive() { return ticksAlive; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java similarity index 98% rename from src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java rename to src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java index 68fa9ec8b..149475d0d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java @@ -22,9 +22,9 @@ import java.util.List; * Energy Dome entity - a spherical barrier that blocks incoming energy projectiles. * Centered on the caster's position at time of casting. * Only blocks incoming attacks (not outgoing from allies inside). - * Extends EntityEnergyBarrier for shared barrier logic. + * Extends EntityAbilityBarrier for shared barrier logic. */ -public class EntityEnergyDome extends EntityEnergyBarrier { +public class EntityAbilityDome extends EntityAbilityBarrier { // ==================== DOME-SPECIFIC PROPERTIES ==================== protected float domeRadius = 5.0f; @@ -35,16 +35,16 @@ public class EntityEnergyDome extends EntityEnergyBarrier { private boolean inTickMelee = false; private final HashSet processedMeleeSwings = new HashSet<>(); - public EntityEnergyDome(World world) { + public EntityAbilityDome(World world) { super(world); this.noClip = true; this.stepHeight = 0.0F; this.setSize(1.0f, 1.0f); } - public EntityEnergyDome(World world, EntityLivingBase owner, double x, double y, double z, - float domeRadius, EnergyDisplayData display, EnergyLightningData lightning, - EnergyBarrierData barrier) { + public EntityAbilityDome(World world, EntityLivingBase owner, double x, double y, double z, + float domeRadius, EnergyDisplayData display, EnergyLightningData lightning, + EnergyBarrierData barrier) { this(world); this.noClip = true; this.stepHeight = 0.0F; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 8f9df2bb5..6a568a89d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -382,8 +382,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { protected boolean checkBarrierCollision() { if (currentLength <= 0 || fullyExtended) return false; - List barriers = EntityEnergyBarrier.getActiveBarriers(worldObj); - for (EntityEnergyBarrier barrier : barriers) { + List barriers = EntityAbilityBarrier.getActiveBarriers(worldObj); + for (EntityAbilityBarrier barrier : barriers) { if (barrier.isDead || barrier.isCharging()) continue; if (barrier.getOwnerEntityId() == this.ownerEntityId) continue; @@ -396,8 +396,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } } - if (barrier instanceof EntityEnergyDome) { - EntityEnergyDome dome = (EntityEnergyDome) barrier; + if (barrier instanceof EntityAbilityDome) { + EntityAbilityDome dome = (EntityAbilityDome) barrier; float intersectDist = getLineSphereIntersection(dome); if (intersectDist >= 0) { // Truncate laser at dome surface @@ -431,7 +431,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { * Returns the distance along the laser direction to the first entry point, * or -1 if no intersection from outside. */ - private float getLineSphereIntersection(EntityEnergyDome dome) { + private float getLineSphereIntersection(EntityAbilityDome dome) { double cx = dome.posX; double cy = dome.posY; double cz = dome.posZ; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java similarity index 97% rename from src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java rename to src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index 1afc1f2bc..f1a89012e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -22,9 +22,9 @@ import java.util.List; /** * Energy Panel entity - a flat rectangular barrier used by Wall and Shield abilities. * Supports three modes: PLACED (stationary), HELD (tracks caster), LAUNCHED (moves forward). - * Extends EntityEnergyBarrier for shared barrier logic. + * Extends EntityAbilityBarrier for shared barrier logic. */ -public class EntityEnergyPanel extends EntityEnergyBarrier { +public class EntityAbilityPanel extends EntityAbilityBarrier { public enum PanelMode { PLACED, // Stationary wall @@ -42,15 +42,15 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { protected float targetPanelWidth; protected float targetPanelHeight; - public EntityEnergyPanel(World world) { + public EntityAbilityPanel(World world) { super(world); this.setSize(0.5f, 0.5f); } - public EntityEnergyPanel(World world, EntityLivingBase owner, double x, double y, double z, - float yaw, PanelMode mode, - EnergyDisplayData display, EnergyLightningData lightning, - EnergyBarrierData barrier, EnergyPanelData panel) { + public EntityAbilityPanel(World world, EntityLivingBase owner, double x, double y, double z, + float yaw, PanelMode mode, + EnergyDisplayData display, EnergyLightningData lightning, + EnergyBarrierData barrier, EnergyPanelData panel) { this(world); this.setPosition(x, y + panel.heightOffset, z); this.ownerEntityId = owner.getEntityId(); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index f3a203001..5e48a468f 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -318,8 +318,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * @return true if projectile was absorbed (caller should stop processing) */ protected boolean checkBarrierCollision() { - List barriers = EntityEnergyBarrier.getActiveBarriers(worldObj); - for (EntityEnergyBarrier barrier : barriers) { + List barriers = EntityAbilityBarrier.getActiveBarriers(worldObj); + for (EntityAbilityBarrier barrier : barriers) { if (barrier.isDead) continue; // Quick distance pre-filter diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 5cdd71bff..d7f951b1e 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -28,14 +28,15 @@ import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.TelegraphController; import kamkeel.npcs.controllers.data.profile.CNPCData; import kamkeel.npcs.developer.Developer; +import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.entity.EntityAbilityPanel; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; -import kamkeel.npcs.entity.EntityEnergyDome; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityEnergySlicer; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.util.BukkitUtil; @@ -297,8 +298,8 @@ public class CustomNpcs { registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 3, true); registerNewEntity(EntityAbilitySweeper.class, "abilitysweeper", 64, 3, true); registerNewEntity(EntityAbilityZone.class, "abilityzone", 64, 3, true); - registerNewEntity(EntityEnergyDome.class, "energydome", 64, 3, true); - registerNewEntity(EntityEnergyPanel.class, "energypanel", 64, 3, true); + registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); + registerNewEntity(EntityAbilityPanel.class, "energypanel", 64, 3, true); registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); new RecipeController(); @@ -430,7 +431,7 @@ public class CustomNpcs { } kamkeel.npcs.entity.EntityEnergyProjectile.clearAllProjectiles(); - kamkeel.npcs.entity.EntityEnergyBarrier.clearAllBarriers(); + EntityAbilityBarrier.clearAllBarriers(); if (FMLCommonHandler.instance().getSide().isClient()) clientJaninoCompiler = null; diff --git a/src/main/java/noppes/npcs/ServerEventsHandler.java b/src/main/java/noppes/npcs/ServerEventsHandler.java index a13384ada..0f9fa1d3e 100644 --- a/src/main/java/noppes/npcs/ServerEventsHandler.java +++ b/src/main/java/noppes/npcs/ServerEventsHandler.java @@ -4,6 +4,8 @@ import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSoundOperation; import kamkeel.npcs.network.enums.EnumSyncAction; @@ -30,7 +32,6 @@ import net.minecraft.util.MathHelper; import net.minecraft.util.StatCollector; import net.minecraft.village.MerchantRecipeList; import net.minecraftforge.event.entity.EntityJoinWorldEvent; -import kamkeel.npcs.entity.EntityEnergyBarrier; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingEvent; @@ -157,7 +158,7 @@ public class ServerEventsHandler { if (event.entityLiving == null || event.entityLiving.worldObj == null || event.entityLiving.worldObj.isRemote) return; - EntityEnergyBarrier barrier = EntityEnergyBarrier.getAbsorbingBarrier(event.entityLiving); + EntityAbilityBarrier barrier = EntityAbilityBarrier.getAbsorbingBarrier(event.entityLiving); if (barrier != null) { // Redirect the caster's damage to their barrier barrier.absorbDamage(event.ammount); @@ -189,9 +190,9 @@ public class ServerEventsHandler { } if (!blacklisted) return; - List barriers = EntityEnergyBarrier.getActiveBarriers(player.worldObj); - for (EntityEnergyBarrier barrier : barriers) { - if (barrier instanceof kamkeel.npcs.entity.EntityEnergyDome && barrier.isEntityInside(player)) { + List barriers = EntityAbilityBarrier.getActiveBarriers(player.worldObj); + for (EntityAbilityBarrier barrier : barriers) { + if (barrier instanceof EntityAbilityDome && barrier.isEntityInside(player)) { event.setCanceled(true); return; } diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index c0a113a96..a221e35ae 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -12,8 +12,8 @@ import kamkeel.npcs.client.renderer.RenderAbilityLaser; import kamkeel.npcs.client.renderer.RenderAbilityOrb; import kamkeel.npcs.client.renderer.RenderAbilitySweeper; import kamkeel.npcs.client.renderer.RenderAbilityZone; -import kamkeel.npcs.client.renderer.RenderEnergyDome; -import kamkeel.npcs.client.renderer.RenderEnergyPanel; +import kamkeel.npcs.client.renderer.RenderAbilityDome; +import kamkeel.npcs.client.renderer.RenderAbilityPanel; import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.client.renderer.lightning.LightningHandler; @@ -24,8 +24,8 @@ import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; -import kamkeel.npcs.entity.EntityEnergyDome; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityDome; +import kamkeel.npcs.entity.EntityAbilityPanel; import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -278,8 +278,8 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityAbilityBeam.class, new RenderAbilityBeam()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilitySweeper.class, new RenderAbilitySweeper()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilityZone.class, new RenderAbilityZone()); - RenderingRegistry.registerEntityRenderingHandler(EntityEnergyDome.class, new RenderEnergyDome()); - RenderingRegistry.registerEntityRenderingHandler(EntityEnergyPanel.class, new RenderEnergyPanel()); + RenderingRegistry.registerEntityRenderingHandler(EntityAbilityDome.class, new RenderAbilityDome()); + RenderingRegistry.registerEntityRenderingHandler(EntityAbilityPanel.class, new RenderAbilityPanel()); RenderingRegistry.registerEntityRenderingHandler(EntityEnergySlicer.class, new RenderEnergySlicer()); RenderingRegistry.registerEntityRenderingHandler(EntityCustomNpc.class, new RenderCustomNpc()); diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index aebc9321d..8cb554c3e 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -9,9 +9,13 @@ import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.TelegraphController; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; +import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.entity.EntityAbilityPanel; +import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityEnergyProjectile; +import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.block.Block; import net.minecraft.command.CommandHandler; import net.minecraft.entity.Entity; @@ -126,9 +130,13 @@ import noppes.npcs.scripted.entity.ScriptArrow; import noppes.npcs.scripted.entity.ScriptDBCPlayer; import noppes.npcs.scripted.entity.ScriptEnergyBeam; import noppes.npcs.scripted.entity.ScriptEnergyDisc; +import noppes.npcs.scripted.entity.ScriptEnergyDome; import noppes.npcs.scripted.entity.ScriptEnergyLaser; import noppes.npcs.scripted.entity.ScriptEnergyOrb; +import noppes.npcs.scripted.entity.ScriptEnergyPanel; import noppes.npcs.scripted.entity.ScriptEnergyProjectile; +import noppes.npcs.scripted.entity.ScriptEnergySlicer; +import noppes.npcs.scripted.entity.ScriptEnergySweeper; import noppes.npcs.scripted.entity.ScriptEntity; import noppes.npcs.scripted.entity.ScriptEntityItem; import noppes.npcs.scripted.entity.ScriptFishHook; @@ -472,16 +480,16 @@ public class NpcAPI extends AbstractNpcAPI { data = new ScriptEntityData(new ScriptEnergyDisc<>((EntityAbilityDisc) entity)); else if (entity instanceof EntityAbilityLaser) data = new ScriptEntityData(new ScriptEnergyLaser<>((EntityAbilityLaser) entity)); - else if (entity instanceof kamkeel.npcs.entity.EntityEnergySlicer) - data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergySlicer<>((kamkeel.npcs.entity.EntityEnergySlicer) entity)); + else if (entity instanceof EntityEnergySlicer) + data = new ScriptEntityData(new ScriptEnergySlicer<>((EntityEnergySlicer) entity)); else if (entity instanceof EntityEnergyProjectile) data = new ScriptEntityData(new ScriptEnergyProjectile((EntityEnergyProjectile) entity)); - else if (entity instanceof kamkeel.npcs.entity.EntityEnergyDome) - data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyDome<>((kamkeel.npcs.entity.EntityEnergyDome) entity)); - else if (entity instanceof kamkeel.npcs.entity.EntityEnergyPanel) - data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyPanel<>((kamkeel.npcs.entity.EntityEnergyPanel) entity)); - else if (entity instanceof kamkeel.npcs.entity.EntityAbilitySweeper) - data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergySweeper<>((kamkeel.npcs.entity.EntityAbilitySweeper) entity)); + else if (entity instanceof EntityAbilityDome) + data = new ScriptEntityData(new ScriptEnergyDome<>((EntityAbilityDome) entity)); + else if (entity instanceof EntityAbilityPanel) + data = new ScriptEntityData(new ScriptEnergyPanel<>((EntityAbilityPanel) entity)); + else if (entity instanceof EntityAbilitySweeper) + data = new ScriptEntityData(new ScriptEnergySweeper<>((EntityAbilitySweeper) entity)); else data = new ScriptEntityData(new ScriptEntity<>(entity)); entity.registerExtendedProperties("ScriptedObject", data); diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java index 6669bde50..a015a06ad 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java @@ -1,15 +1,15 @@ package noppes.npcs.scripted.entity; -import kamkeel.npcs.entity.EntityEnergyBarrier; -import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityAbilityBarrier; +import kamkeel.npcs.entity.EntityAbilityDome; import noppes.npcs.api.entity.IEnergyBarrier; import noppes.npcs.scripted.constants.EntityType; /** * Base script wrapper for energy barrier entities (Dome, Panel). - * All shared methods delegate directly to EntityEnergyBarrier base. + * All shared methods delegate directly to EntityAbilityBarrier base. */ -public class ScriptEnergyBarrier extends ScriptEnergyAbility implements IEnergyBarrier { +public class ScriptEnergyBarrier extends ScriptEnergyAbility implements IEnergyBarrier { public ScriptEnergyBarrier(T entity) { super(entity); @@ -122,6 +122,6 @@ public class ScriptEnergyBarrier extends ScriptEn } public int getBarrierType() { - return entity instanceof EntityEnergyDome ? 0 : 1; + return entity instanceof EntityAbilityDome ? 0 : 1; } } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java index e022118f7..9bcceefbc 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java @@ -1,10 +1,10 @@ package noppes.npcs.scripted.entity; -import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityAbilityDome; import noppes.npcs.api.entity.IEnergyDome; import noppes.npcs.scripted.constants.EntityType; -public class ScriptEnergyDome extends ScriptEnergyBarrier implements IEnergyDome { +public class ScriptEnergyDome extends ScriptEnergyBarrier implements IEnergyDome { public ScriptEnergyDome(T entity) { super(entity); diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java index 04c730fad..fa929b4a8 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java @@ -1,10 +1,10 @@ package noppes.npcs.scripted.entity; -import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityAbilityPanel; import noppes.npcs.api.entity.IEnergyPanel; import noppes.npcs.scripted.constants.EntityType; -public class ScriptEnergyPanel extends ScriptEnergyBarrier implements IEnergyPanel { +public class ScriptEnergyPanel extends ScriptEnergyBarrier implements IEnergyPanel { public ScriptEnergyPanel(T entity) { super(entity); @@ -52,6 +52,6 @@ public class ScriptEnergyPanel extends ScriptEnergy } public boolean isLaunched() { - return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED; + return entity.getMode() == EntityAbilityPanel.PanelMode.LAUNCHED; } } From 55370b26a0182a520d96b32840737a73436046c0 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 14:47:26 -0500 Subject: [PATCH 30/71] 1.11 Beta 6 --- build.gradle | 2 +- src/main/java/noppes/npcs/CustomNpcs.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index caec58fb6..626fa347b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'idea' } -version = "1.11-beta5" +version = "1.11-beta6" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index d7f951b1e..cf305f68b 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -122,7 +122,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta5") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta6") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy") From e194bd71cd1b37d2d4699839e62bc3d64d6205ae Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 21 Feb 2026 17:13:42 -0300 Subject: [PATCH 31/71] Fixed setRace --- src/main/java/noppes/npcs/scripted/entity/ScriptDBCPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptDBCPlayer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptDBCPlayer.java index c1f4f9bb2..fb596b677 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptDBCPlayer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptDBCPlayer.java @@ -789,7 +789,7 @@ public class ScriptDBCPlayer extends ScriptPlayer i } public void setRace(byte race) { - if (race < 0 || race > 1) + if (race < 0 || race > 5) return; player.getEntityData().getCompoundTag("PlayerPersisted").setByte("jrmcRace", race); From 8873705ce7d0e37664f35b4be29c85666bb25c81 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 15:28:59 -0500 Subject: [PATCH 32/71] Fix Some Flying Logic --- .../noppes/npcs/entity/EntityNPCFlying.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java index 18c730dd3..8c062fdda 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java @@ -62,15 +62,16 @@ public abstract class EntityNPCFlying extends EntityNPCInterface { } else if (heightOffGround > this.ais.flyHeightLimit) { aboveLimit = true; } - if (aboveLimit && this.ais.hasFlyLimit || (heightOffGround < Math.ceil(this.height) && this.motionY == 0)) { + if (aboveLimit && this.ais.hasFlyLimit) { this.flyLimitAllow = false; - if (!this.getNavigator().noPath() && this.motionY > 0) { + if (this.motionY > 0) { this.motionY = 0; - } else { - setNpcFlyingState(false); - super.moveEntityWithHeading(p_70612_1_, p_70612_2_); - return; } + } else if (heightOffGround < Math.ceil(this.height) && this.motionY == 0) { + this.flyLimitAllow = false; + setNpcFlyingState(false); + super.moveEntityWithHeading(p_70612_1_, p_70612_2_); + return; } this.flyLimitAllow = true; setNpcFlyingState(true); @@ -120,15 +121,16 @@ public abstract class EntityNPCFlying extends EntityNPCInterface { } else if (heightOffGround > this.ais.flyHeightLimit) { aboveLimit = true; } - if (aboveLimit && this.ais.hasFlyLimit || (heightOffGround < Math.ceil(this.height) && this.motionY == 0)) { + if (aboveLimit && this.ais.hasFlyLimit) { this.flyLimitAllow = false; - if (!this.getNavigator().noPath() && this.motionY > 0) { + if (this.motionY > 0) { this.motionY = 0; - } else { - setNpcFlyingState(false); - super.performMountedMovement(strafe, forward, moveSpeed); - return; } + } else if (heightOffGround < Math.ceil(this.height) && this.motionY == 0) { + this.flyLimitAllow = false; + setNpcFlyingState(false); + super.performMountedMovement(strafe, forward, moveSpeed); + return; } this.flyLimitAllow = true; setNpcFlyingState(true); From 3b198c927cb1a5645cc0abe736bf51abe13cb062 Mon Sep 17 00:00:00 2001 From: somehussar <148253652+somehussar@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:39:12 +0100 Subject: [PATCH 33/71] Fix crash --- .../network/packets/data/RequestProperSpawnData.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java index afd013a74..af2eeb418 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java @@ -28,9 +28,16 @@ public class RequestProperSpawnData extends AbstractPacket { private static final int BATCH_LIMIT = 5; @SideOnly(Side.CLIENT) - private static final Set entitiesToFix = new HashSet<>(); + private static final Set entitiesToFix; @SideOnly(Side.CLIENT) - private static long lastBatchAttemptMillis = 0; + private static long lastBatchAttemptMillis; + + static { + if (FMLCommonHandler.instance().getEffectiveSide().isClient()) { + entitiesToFix = new HashSet<>(); + lastBatchAttemptMillis = 0; + } + } private int entityId; From 64e7a5153e0eb3886e1b68c71a95edfb6e5b442c Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 16:49:21 -0500 Subject: [PATCH 34/71] Fix Crosshair Alignment --- .../npcs/entity/EntityAbilityLaser.java | 62 ++++++++++++------- .../npcs/entity/EntityEnergyProjectile.java | 15 +++++ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 6a568a89d..f3a899181 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -8,6 +8,7 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; @@ -172,18 +173,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { EntityLivingBase livingOwner = (EntityLivingBase) owner; - // Update origin from anchor point - if (anchorData != null) { - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData); - startX = pos.xCoord; - startY = pos.yCoord; - startZ = pos.zCoord; - } else { - startX = owner.posX; - startY = owner.posY + owner.height * 0.7; - startZ = owner.posZ; - } - // Update direction from owner's current rotation float yaw = (float) Math.toRadians(owner.rotationYaw); if (lockVerticalDirection) { @@ -199,6 +188,25 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { dirZ = Math.cos(yaw) * Math.cos(pitch); } + // Update origin: for player casters, snap to look vector so laser aligns with crosshair. + // For NPCs, use the configured anchor point. + if (owner instanceof EntityPlayer) { + double eyeY = owner.posY + owner.getEyeHeight(); + float frontDist = Math.max(0.5f, laserWidth * 0.5f); + startX = owner.posX + dirX * frontDist; + startY = eyeY + dirY * frontDist; + startZ = owner.posZ + dirZ * frontDist; + } else if (anchorData != null) { + Vec3 pos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData); + startX = pos.xCoord; + startY = pos.yCoord; + startZ = pos.zCoord; + } else { + startX = owner.posX; + startY = owner.posY + owner.height * 0.7; + startZ = owner.posZ; + } + // Keep entity positioned at origin prevPosX = startX; prevPosY = startY; @@ -209,12 +217,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { public void startMoving(EntityLivingBase target) { setCharging(false); - // Initialize from current anchor position - // updateLaserOriginAndDirection() will keep these updated each tick - startX = posX; - startY = posY; - startZ = posZ; - // Set initial direction from owner's rotation Entity owner = getOwnerEntity(); if (owner != null) { @@ -224,9 +226,9 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.dirY = -Math.sin(pitch); this.dirZ = Math.cos(yaw) * Math.cos(pitch); } else if (target != null) { - double dx = target.posX - startX; - double dy = (target.posY + target.getEyeHeight() - 0.4) - startY; - double dz = target.posZ - startZ; + double dx = target.posX - posX; + double dy = (target.posY + target.getEyeHeight() - 0.4) - posY; + double dz = target.posZ - posZ; double len = Math.sqrt(dx * dx + dy * dy + dz * dz); if (len > 0) { this.dirX = dx / len; @@ -235,6 +237,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } } + // For player casters, snap origin to look vector so laser aligns with crosshair. + // For NPCs, use current anchor position. + if (owner instanceof EntityPlayer) { + double eyeY = owner.posY + owner.getEyeHeight(); + float frontDist = Math.max(0.5f, laserWidth * 0.5f); + startX = owner.posX + dirX * frontDist; + startY = eyeY + dirY * frontDist; + startZ = owner.posZ + dirZ * frontDist; + setPosition(startX, startY, startZ); + prevPosX = startX; + prevPosY = startY; + prevPosZ = startZ; + } else { + startX = posX; + startY = posY; + startZ = posZ; + } + // Initialize end point at start (will expand from here) this.endX = startX; this.endY = startY; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 5e48a468f..4a33e2489 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -132,6 +132,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { public EntityEnergyProjectile(World world) { super(world); this.setSize(0.5f, 0.5f); + this.yOffset = this.height / 2.0f; this.noClip = false; } @@ -363,6 +364,20 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @Override public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { + // If position changed significantly (e.g. snap from anchor to look vector), + // skip interpolation and jump directly to prevent visual offset from crosshair + double dx = x - this.posX; + double dy = y - this.posY; + double dz = z - this.posZ; + if (dx * dx + dy * dy + dz * dz > 1.0) { + this.setPosition(x, y, z); + this.prevPosX = x; + this.prevPosY = y; + this.prevPosZ = z; + this.interpSteps = 0; + return; + } + this.interpTargetX = x; this.interpTargetY = y; this.interpTargetZ = z; From 8e734284589d3fef2d590abbbe6619275ece5ba3 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 16:50:23 -0500 Subject: [PATCH 35/71] Update RequestProperSpawnData.java --- .../npcs/network/packets/data/RequestProperSpawnData.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java index af2eeb418..76e010660 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java @@ -28,7 +28,8 @@ public class RequestProperSpawnData extends AbstractPacket { private static final int BATCH_LIMIT = 5; @SideOnly(Side.CLIENT) - private static final Set entitiesToFix; + private static Set entitiesToFix; + @SideOnly(Side.CLIENT) private static long lastBatchAttemptMillis; From 61d253638ae707f96f85d3f21a2403bd0bc111e7 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 17:10:48 -0500 Subject: [PATCH 36/71] Fix Server Side Crash --- .../data/ability/conditions/ConditionHasEffect.java | 7 +++++-- .../data/ability/conditions/ConditionHitCount.java | 4 ++++ .../controllers/data/ability/conditions/ConditionItem.java | 4 ++++ .../data/ability/conditions/ConditionQuestCompleted.java | 7 +++++-- .../data/ability/conditions/ConditionThreshold.java | 4 ++++ src/main/java/noppes/npcs/client/gui/builder/FieldDef.java | 7 ++++--- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java index 124b34bfb..bf98c73c9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -7,7 +9,6 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.advanced.SubGuiCustomEffectSelect; import noppes.npcs.client.gui.builder.FieldDef; -import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.controllers.CustomEffectController; import noppes.npcs.controllers.data.CustomEffect; @@ -33,11 +34,12 @@ public class ConditionHasEffect extends AbilityCondition { return CustomEffectController.getInstance().hasEffect(player, effectId, effectIndex); } + @SideOnly(Side.CLIENT) @Override public void getConditionDefinitions(List defs) { defs.add(FieldDef.subGuiField("condition.select_effect", () -> new SubGuiCustomEffectSelect(effectId, effectIndex), - (SubGuiInterface gui) -> { + gui -> { SubGuiCustomEffectSelect sel = (SubGuiCustomEffectSelect) gui; if (sel.getSelectedEffectId() >= 0) { effectId = sel.getSelectedEffectId(); @@ -55,6 +57,7 @@ public class ConditionHasEffect extends AbilityCondition { })); } + @SideOnly(Side.CLIENT) @Override public String getConditionSummary() { String filterLabel = StatCollector.translateToLocal(getFilter().toString()); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java index 8a326a16c..5678e66df 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; @@ -52,12 +54,14 @@ public class ConditionHitCount extends AbilityCondition { this.withinTicks = Math.max(0, withinTicks); } + @SideOnly(Side.CLIENT) @Override public void getConditionDefinitions(List defs) { defs.add(FieldDef.intField("condition.required_hits", this::getRequiredHits, this::setRequiredHits).min(1)); defs.add(FieldDef.intField("condition.within_ticks", this::getWithinTicks, this::setWithinTicks).min(0)); } + @SideOnly(Side.CLIENT) @Override public String getConditionSummary() { String filterLabel = StatCollector.translateToLocal(getFilter().toString()); 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 index a137304ef..f7827d1c3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -127,6 +129,7 @@ public class ConditionItem extends AbilityCondition { return itemName.equals(registryName); } + @SideOnly(Side.CLIENT) @Override public void getConditionDefinitions(List defs) { defs.add(FieldDef.enumField("condition.usage_type", UsageType.class, @@ -150,6 +153,7 @@ public class ConditionItem extends AbilityCondition { // .hover("condition.hover.required_count")); } + @SideOnly(Side.CLIENT) @Override public String getConditionSummary() { String filterLabel = StatCollector.translateToLocal(getFilter().toString()); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java index 213358ca5..d47e19506 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -7,7 +9,6 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.select.GuiQuestSelection; -import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.controllers.QuestController; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.controllers.data.Quest; @@ -32,11 +33,12 @@ public class ConditionQuestCompleted extends AbilityCondition { return PlayerData.get(player).questData.hasFinishedQuest(questId); } + @SideOnly(Side.CLIENT) @Override public void getConditionDefinitions(List defs) { defs.add(FieldDef.subGuiField("condition.select_quest", () -> new GuiQuestSelection(questId), - (SubGuiInterface gui) -> { + gui -> { GuiQuestSelection sel = (GuiQuestSelection) gui; if (sel.selectedQuest != null) { questId = sel.selectedQuest.id; @@ -50,6 +52,7 @@ public class ConditionQuestCompleted extends AbilityCondition { .clearable(() -> questId = -1)); } + @SideOnly(Side.CLIENT) @Override public String getConditionSummary() { String filterLabel = StatCollector.translateToLocal(getFilter().toString()); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java index aaa4a6b06..148b0f7b5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; @@ -95,6 +97,7 @@ public abstract class ConditionThreshold extends AbilityCondition { return thresholdType.test(value, threshold); } + @SideOnly(Side.CLIENT) @Override public void getConditionDefinitions(List defs) { defs.add(FieldDef.floatField("condition.threshold_flat", this::getThresholdFlat, this::setThresholdFlat) @@ -122,6 +125,7 @@ public abstract class ConditionThreshold extends AbilityCondition { thresholdType = ThresholdType.fromOrdinal(nbt.getInteger("thresholdType")); } + @SideOnly(Side.CLIENT) @Override public String getConditionSummary() { String filterLabel = StatCollector.translateToLocal(getFilter().toString()); diff --git a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java index 66e433f45..757c37470 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java +++ b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java @@ -118,10 +118,11 @@ public class FieldDef { return def; } - public static FieldDef subGuiField(String label, Supplier factory, Consumer resultHandler) { + @SuppressWarnings("unchecked") + public static FieldDef subGuiField(String label, Supplier factory, Consumer resultHandler) { FieldDef def = new FieldDef(label, FieldType.SUB_GUI); - def.subGuiFactory = factory; - def.subGuiResultHandler = resultHandler; + def.subGuiFactory = (Supplier) (Supplier) factory; + def.subGuiResultHandler = (Consumer) (Consumer) resultHandler; return def; } From 26da3ae2f90b835272ad46e01a4c0befa7a7263f Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 17:43:41 -0500 Subject: [PATCH 37/71] 1.11 Beta 6.1 --- build.gradle | 2 +- src/main/java/noppes/npcs/CustomNpcs.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 626fa347b..2a9f8b1da 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'idea' } -version = "1.11-beta6" +version = "1.11-beta6.1" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index cf305f68b..4fd5b3fdf 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -122,7 +122,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta6") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta6.1") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy") From b4334c28357b16ab09011ce2d941fbfafa39f974 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 18:18:56 -0500 Subject: [PATCH 38/71] Fix Chained Abilities not Saving --- .../client/gui/advanced/SubGuiChainedAbilityConfig.java | 6 ++++++ .../java/noppes/npcs/client/gui/util/GuiMenuTopButton.java | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) 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 d1946e2e2..804f7e663 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -563,6 +563,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return true; case 2: // Up if (entryIndex > 0) { + // Commit any focused delay field before reordering + GuiNpcTextField.unfocus(); ChainedAbilityEntry entry = entries.remove(entryIndex); entries.add(entryIndex - 1, entry); // Can't be concurrent at position 0 @@ -574,12 +576,16 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return true; case 3: // Down if (entryIndex < entries.size() - 1) { + // Commit any focused delay field before reordering + GuiNpcTextField.unfocus(); ChainedAbilityEntry entry = entries.remove(entryIndex); entries.add(entryIndex + 1, entry); initGui(); } return true; case 4: // Delete + // Commit any focused delay field before removing + GuiNpcTextField.unfocus(); entries.remove(entryIndex); initGui(); return true; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java index 8c8eca60d..9d972be3d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java @@ -96,7 +96,10 @@ public class GuiMenuTopButton extends GuiNpcButton { @Override public boolean mousePressed(Minecraft minecraft, int i, int j) { - boolean bo = !active && getVisible() && hover; + int h = this.height - (active ? 0 : 2); + boolean bo = !active && getVisible() + && i >= xPosition && j >= yPosition + && i < xPosition + getWidth() && j < yPosition + h; if (bo && listener != null) { listener.actionPerformed(this); return false; From b6749e306b331d53daddaf971181787617a5a85d Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 20:39:16 -0500 Subject: [PATCH 39/71] Add Hit Type to Projectiles --- src/api | 2 +- .../controllers/data/ability/HitType.java | 32 ++++ .../data/ability/data/EnergyCombatData.java | 13 +- .../type/energy/AbilityEnergyProjectile.java | 26 +++ .../ability/type/energy/AbilityLaserShot.java | 17 +- .../ability/type/energy/AbilitySlicer.java | 18 +- .../npcs/entity/EntityAbilityBeam.java | 43 +---- .../npcs/entity/EntityAbilityDisc.java | 52 +----- .../npcs/entity/EntityAbilityLaser.java | 41 +---- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 53 +----- .../npcs/entity/EntityEnergyProjectile.java | 156 ++++++++++++++++-- .../npcs/entity/EntityEnergySlicer.java | 71 +------- .../entity/ScriptEnergyProjectile.java | 16 ++ .../scripted/entity/ScriptEnergySlicer.java | 8 - .../assets/customnpcs/lang/en_US.lang | 9 +- 15 files changed, 265 insertions(+), 292 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java diff --git a/src/api b/src/api index a9c77d20e..0d31b56a1 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit a9c77d20eb83be6a3168480c0d62fa78419e9469 +Subproject commit 0d31b56a12dd18f611505047402d2251a6dd0906 diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java new file mode 100644 index 000000000..3892e9362 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java @@ -0,0 +1,32 @@ +package kamkeel.npcs.controllers.data.ability; + +/** + * Shared hit behavior for energy projectiles. + */ +public enum HitType { + SINGLE, + PIERCE, + MULTI; + + public static HitType fromOrdinal(int ordinal) { + HitType[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return SINGLE; + } + + @Override + public String toString() { + switch (this) { + case SINGLE: + return "ability.hitType.single"; + case PIERCE: + return "ability.hitType.pierce"; + case MULTI: + return "ability.hitType.multi"; + default: + return name(); + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java index ad3d603e5..e6026e18e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability.data; +import kamkeel.npcs.controllers.data.ability.HitType; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyCombatData; @@ -14,6 +15,8 @@ public class EnergyCombatData implements IEnergyCombatData { public boolean explosive = false; public float explosionRadius = 3.0f; public float explosionDamageFalloff = 0.5f; + public HitType hitType = HitType.SINGLE; + public int multiHitDelayTicks = 5; public EnergyCombatData() { } @@ -95,6 +98,8 @@ public class EnergyCombatData implements IEnergyCombatData { nbt.setBoolean("explosive", explosive); nbt.setFloat("explosionRadius", explosionRadius); nbt.setFloat("explosionDamageFalloff", explosionDamageFalloff); + nbt.setInteger("hitType", hitType.ordinal()); + nbt.setInteger("multiHitDelayTicks", multiHitDelayTicks); } public void readNBT(NBTTagCompound nbt) { @@ -104,15 +109,21 @@ public class EnergyCombatData implements IEnergyCombatData { explosive = nbt.hasKey("explosive") && nbt.getBoolean("explosive"); explosionRadius = nbt.hasKey("explosionRadius") ? nbt.getFloat("explosionRadius") : 3.0f; explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f; + hitType = HitType.fromOrdinal(nbt.hasKey("hitType") ? nbt.getInteger("hitType") : 0); + multiHitDelayTicks = nbt.hasKey("multiHitDelayTicks") ? nbt.getInteger("multiHitDelayTicks") : 5; // Sanitize if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f; if (Float.isNaN(knockback) || Float.isInfinite(knockback) || knockback < 0) knockback = 1.0f; if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f; + if (multiHitDelayTicks < 1) multiHitDelayTicks = 1; } public EnergyCombatData copy() { - return new EnergyCombatData(damage, knockback, knockbackUp, + EnergyCombatData copy = new EnergyCombatData(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff); + copy.hitType = hitType; + copy.multiHitDelayTicks = multiHitDelayTicks; + return copy; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 50b68554e..40ea82080 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.HitType; import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; @@ -557,6 +558,22 @@ public abstract class AbilityEnergyProjectile combatData.explosionDamageFalloff = falloff; } + public int getHitType() { + return combatData.hitType.ordinal(); + } + + public void setHitType(int hitType) { + this.combatData.hitType = HitType.fromOrdinal(hitType); + } + + public int getMultiHitDelayTicks() { + return combatData.multiHitDelayTicks; + } + + public void setMultiHitDelayTicks(int delay) { + this.combatData.multiHitDelayTicks = Math.max(1, delay); + } + // Lifespan data @Override public float getMaxDistance() { @@ -696,6 +713,15 @@ public abstract class AbilityEnergyProjectile @SideOnly(Side.CLIENT) @Override public final void getAbilityDefinitions(List defs) { + defs.add(FieldDef.enumField("ability.hitType", HitType.class, + () -> HitType.fromOrdinal(getHitType()), + (v) -> setHitType(v.ordinal())) + .hover("ability.hover.hitType")); + defs.add(FieldDef.intField("ability.multiHitDelay", this::getMultiHitDelayTicks, this::setMultiHitDelayTicks) + .range(1, 200) + .visibleWhen(() -> getHitType() == HitType.MULTI.ordinal()) + .hover("ability.hover.multiHitDelay")); + // Type-specific fields first addTypeDefinitions(defs); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 0ffa52946..30e99fedb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -25,8 +25,8 @@ import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; /** - * Laser Shot ability: Fast expanding thin line that pierces through targets. - * Travels in a straight line from origin, damaging all entities it passes through. + * Laser Shot ability: Fast expanding thin line. + * Travels in a straight line from origin. * Single-projectile only (always projectileCount=1). */ public class AbilityLaserShot extends AbilityEnergyProjectile implements IAbilityLaserShot { @@ -34,7 +34,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; - private boolean piercing = true; public AbilitySlicer() { super( @@ -62,8 +61,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, sliceWidth, sliceThickness, - resolved, combatData, homingData, lightningData, lifespanData, trajectoryData, - piercing + resolved, combatData, homingData, lightningData, lifespanData, trajectoryData ); } @@ -122,14 +120,12 @@ public class AbilitySlicer extends AbilityEnergyProjectile { protected void writeTypeSpecificNBT(NBTTagCompound nbt) { nbt.setFloat("sliceWidth", sliceWidth); nbt.setFloat("sliceThickness", sliceThickness); - nbt.setBoolean("piercing", piercing); } @Override protected void readTypeSpecificNBT(NBTTagCompound nbt) { this.sliceWidth = nbt.hasKey("sliceWidth") ? nbt.getFloat("sliceWidth") : 3.0f; this.sliceThickness = nbt.hasKey("sliceThickness") ? nbt.getFloat("sliceThickness") : 0.15f; - this.piercing = !nbt.hasKey("piercing") || nbt.getBoolean("piercing"); } // ==================== TYPE-SPECIFIC GETTERS ==================== @@ -158,14 +154,6 @@ public class AbilitySlicer extends AbilityEnergyProjectile { this.sliceThickness = Math.max(0.05f, thickness); } - public boolean isPiercing() { - return piercing; - } - - public void setPiercing(boolean piercing) { - this.piercing = piercing; - } - // ==================== TYPE-SPECIFIC GUI ==================== @SideOnly(Side.CLIENT) @@ -186,8 +174,6 @@ public class AbilitySlicer extends AbilityEnergyProjectile { FieldDef.floatField("gui.width", this::getSliceWidth, this::setSliceWidth).range(0.5f, 100.0f), FieldDef.floatField("gui.thickness", this::getSliceThickness, this::setSliceThickness).range(0.05f, 100.0f) )); - defs.add(FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing) - .hover("ability.hover.piercing")); defs.add(FieldDef.row( FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).range(1.0f, 500.0f), FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index b80926aab..44589a842 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -13,10 +13,8 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; import java.util.ArrayList; @@ -546,24 +544,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { double prevHeadWorldY = startY + prevHeadOffsetY; double prevHeadWorldZ = startZ + prevHeadOffsetZ; - Vec3 currentPos = Vec3.createVectorHelper(prevHeadWorldX, prevHeadWorldY, prevHeadWorldZ); - Vec3 nextPos = Vec3.createVectorHelper(headX, headY, headZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(prevHeadWorldX, prevHeadWorldY, prevHeadWorldZ, headX, headY, headZ), true); } private void checkEntityCollision(double headX, double headY, double headZ) { @@ -577,24 +558,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - if (DEBUG_LOGGING) { - LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted); + if (processEntityHit(entity, headX, headY, headZ)) { + if (DEBUG_LOGGING) { + LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted); + } + return; } - hasHit = true; - - if (isExplosive()) { - posX = headX; - posY = headY; - posZ = headZ; - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index d16fc7709..b5daa8701 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -12,12 +12,8 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.List; /** * Disc projectile - flat spinning disc with optional boomerang behavior. @@ -185,7 +181,6 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { // Calculate velocity toward target Entity owner = getOwnerEntity(); - if (target != null) { double dx = target.posX - posX; double dy = (target.posY + target.getEyeHeight()) - posY; @@ -248,15 +243,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } // Check wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } @Override @@ -352,24 +339,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } private void checkBlockCollision() { - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), true); } private void checkEntityCollision() { @@ -381,23 +351,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { posX + halfWidth, posY + halfHeight, posZ + halfWidth ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - hasHit = true; - - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; - } + processEntitiesInHitBox(hitBox, posX, posY, posZ); } /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index f3a899181..ec851f802 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -17,12 +17,10 @@ import net.minecraft.world.World; import noppes.npcs.EventHooks; import noppes.npcs.entity.EntityNPCInterface; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** - * Laser projectile - fast expanding thin line that pierces through multiple targets. + * Laser projectile - fast expanding thin line. * No homing, travels in a straight line from origin to max distance. *

* Design inspired by LouisXIV's energy attack system. @@ -45,12 +43,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { // Lock vertical direction after firing (only update yaw, keep pitch fixed) private boolean lockVerticalDirection = false; - // Whether to die on first entity impact (hit-scan mode) - private boolean dieOnImpact = false; - - // Track hit entities to avoid double-damage - private Set hitEntities = new HashSet<>(); - // End point for rendering private double endX, endY, endZ; @@ -217,8 +209,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { public void startMoving(EntityLivingBase target) { setCharging(false); - // Set initial direction from owner's rotation Entity owner = getOwnerEntity(); + // Set initial direction from owner's rotation if (owner != null) { float yaw = (float) Math.toRadians(owner.rotationYaw); float pitch = (float) Math.toRadians(owner.rotationPitch); @@ -262,10 +254,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } private void checkBlockCollision() { - Vec3 start = Vec3.createVectorHelper(startX, startY, startZ); - Vec3 end = Vec3.createVectorHelper(endX, endY, endZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(start, end, false, true, false); + MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ); if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { if (!worldObj.isRemote) { @@ -315,15 +304,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { for (EntityLivingBase entity : entities) { if (shouldIgnoreEntity(entity)) continue; - if (hitEntities.contains(entity.getEntityId())) continue; + if (!canHitEntityNow(entity)) continue; // Check if entity's bounding box intersects with the laser line if (isEntityOnLine(entity)) { - hitEntities.add(entity.getEntityId()); - applyDamage(entity); - - if (dieOnImpact) { - // Stop at impact point and begin linger/fade + if (processEntityHit(entity, entity.posX, entity.posY + entity.height * 0.5, entity.posZ)) { + // Stop at impact point and terminate double dx = entity.posX - startX; double dy = (entity.posY + entity.height * 0.5) - startY; double dz = entity.posZ - startZ; @@ -332,11 +318,10 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { endX = startX + dirX * currentLength; endY = startY + dirY * currentLength; endZ = startZ + dirZ * currentLength; - fullyExtended = true; - return; // Stop checking further entities + return; } - // Piercing - don't stop, continue to next entity + // PIERCE/MULTI continue through remaining entities. } } } @@ -517,14 +502,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.lingerTicks = ticks; } - public boolean isDieOnImpact() { - return dieOnImpact; - } - - public void setDieOnImpact(boolean dieOnImpact) { - this.dieOnImpact = dieOnImpact; - } - public void setDirection(double x, double y, double z) { this.dirX = x; this.dirY = y; @@ -593,7 +570,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f; this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10; if (lingerTicks <= 0) lingerTicks = 1; - this.dieOnImpact = nbt.getBoolean("DieOnImpact"); this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir"); this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended"); this.dirX = nbt.getDouble("DirX"); @@ -613,7 +589,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { nbt.setFloat("LaserWidth", laserWidth); nbt.setFloat("ExpansionSpeed", expansionSpeed); nbt.setInteger("LingerTicks", lingerTicks); - nbt.setBoolean("DieOnImpact", dieOnImpact); nbt.setBoolean("LockVerticalDir", lockVerticalDirection); nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension); nbt.setDouble("DirX", dirX); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 82447edd2..efb1db6a9 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -12,12 +12,8 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.List; /** * Orb projectile - spherical homing energy ball. @@ -140,7 +136,6 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { // Calculate velocity toward target Entity owner = getOwnerEntity(); - if (target != null) { double dx = target.posX - posX; double dy = (target.posY + target.getEyeHeight()) - posY; @@ -185,15 +180,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } // Check wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } /** @@ -233,24 +220,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } private void checkBlockCollision() { - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), true); } private void checkEntityCollision() { @@ -259,24 +229,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { posX - hitSize, posY - hitSize, posZ - hitSize, posX + hitSize, posY + hitSize, posZ + hitSize ); - - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - hasHit = true; - - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; - } + processEntitiesInHitBox(hitBox, posX, posY, posZ); } // ==================== GETTERS ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 4a33e2489..9dfa105d6 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.HitType; import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; @@ -20,6 +21,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; import noppes.npcs.EventHooks; @@ -35,9 +37,11 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** * Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer). @@ -112,6 +116,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== STATE ==================== protected boolean hasHit = false; + protected final Set hitOnceEntities = new HashSet(); + protected final Map lastHitTickByEntity = new HashMap(); // ==================== CHARGING STATE (projectile-specific) ==================== protected float targetSize = 1.0f; @@ -364,20 +370,6 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @Override public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { - // If position changed significantly (e.g. snap from anchor to look vector), - // skip interpolation and jump directly to prevent visual offset from crosshair - double dx = x - this.posX; - double dy = y - this.posY; - double dz = z - this.posZ; - if (dx * dx + dy * dy + dz * dz > 1.0) { - this.setPosition(x, y, z); - this.prevPosX = x; - this.prevPosY = y; - this.prevPosZ = z; - this.interpSteps = 0; - return; - } - this.interpTargetX = x; this.interpTargetY = y; this.interpTargetZ = z; @@ -861,6 +853,142 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { combatData.setExplosionDamageFalloff(falloff); } + public int getHitType() { + return combatData.hitType.ordinal(); + } + + public void setHitType(int hitType) { + combatData.hitType = HitType.fromOrdinal(hitType); + } + + public int getMultiHitDelayTicks() { + return combatData.multiHitDelayTicks; + } + + public void setMultiHitDelayTicks(int delayTicks) { + combatData.multiHitDelayTicks = Math.max(1, delayTicks); + } + + protected boolean canHitEntityNow(EntityLivingBase entity) { + if (entity == null) return false; + + int entityId = entity.getEntityId(); + switch (combatData.hitType) { + case PIERCE: + return !hitOnceEntities.contains(entityId); + case MULTI: + Integer lastHitTick = lastHitTickByEntity.get(entityId); + if (lastHitTick == null) return true; + int delay = Math.max(1, combatData.multiHitDelayTicks); + return ticksExisted - lastHitTick >= delay; + case SINGLE: + default: + return !hasHit; + } + } + + protected void recordEntityHit(EntityLivingBase entity) { + if (entity == null) return; + int entityId = entity.getEntityId(); + hitOnceEntities.add(entityId); + lastHitTickByEntity.put(entityId, ticksExisted); + } + + protected boolean shouldTerminateAfterHit() { + return combatData.hitType == HitType.SINGLE; + } + + /** + * Shared block raytrace helper used by most projectile types. + */ + protected MovingObjectPosition rayTraceBlocks(double fromX, double fromY, double fromZ, double toX, double toY, double toZ) { + Vec3 currentPos = Vec3.createVectorHelper(fromX, fromY, fromZ); + Vec3 nextPos = Vec3.createVectorHelper(toX, toY, toZ); + return worldObj.func_147447_a(currentPos, nextPos, false, true, false); + } + + /** + * Shared block-hit handling: fires block impact hook, optional explosion, and kills projectile. + */ + protected boolean handleBlockImpact(MovingObjectPosition blockHit, boolean explodeAtHitPoint) { + if (blockHit == null || blockHit.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) return false; + + if (!worldObj.isRemote) { + EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); + } + hasHit = true; + + if (isExplosive()) { + if (explodeAtHitPoint && blockHit.hitVec != null) { + posX = blockHit.hitVec.xCoord; + posY = blockHit.hitVec.yCoord; + posZ = blockHit.hitVec.zCoord; + } + doExplosion(); + } + + this.setDead(); + return true; + } + + /** + * Apply projectile hit behavior to a target and return true if the projectile terminated. + */ + protected boolean processEntityHit(EntityLivingBase entity, double impactX, double impactY, double impactZ) { + if (entity == null || shouldIgnoreEntity(entity) || !canHitEntityNow(entity)) return false; + + recordEntityHit(entity); + if (isExplosive()) { + double oldX = posX; + double oldY = posY; + double oldZ = posZ; + posX = impactX; + posY = impactY; + posZ = impactZ; + doExplosion(); + posX = oldX; + posY = oldY; + posZ = oldZ; + } else { + applyDamage(entity); + } + + if (shouldTerminateAfterHit()) { + hasHit = true; + this.setDead(); + return true; + } + + return false; + } + + /** + * Scan entities in a hitbox and process the first/each valid hit based on hit mode. + */ + protected boolean processEntitiesInHitBox(AxisAlignedBB hitBox, double impactX, double impactY, double impactZ) { + @SuppressWarnings("unchecked") + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); + for (EntityLivingBase entity : entities) { + if (processEntityHit(entity, impactX, impactY, impactZ)) { + return true; + } + } + return false; + } + + protected boolean handleSolidCollisionTermination() { + if (!this.isCollidedHorizontally && !this.isCollidedVertically) return false; + + if (!worldObj.isRemote) { + hasHit = true; + if (isExplosive()) { + doExplosion(); + } + } + this.setDead(); + return true; + } + // ==================== HOMING GETTERS & SETTERS ==================== public boolean isHoming() { diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 29d0ac39a..1c2a868a8 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -11,25 +11,18 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.ArrayList; -import java.util.List; /** * Energy Slicer entity - a thin, wide blade projectile that flies in a straight line. - * JJK Cleave/Dismantle inspired. Piercing by default (passes through entities). + * JJK Cleave/Dismantle inspired. * Extends EntityEnergyAbility to leverage the existing projectile infrastructure. */ public class EntityEnergySlicer extends EntityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; - private boolean piercing = true; - private List hitEntities = new ArrayList<>(); public EntityEnergySlicer(World world) { super(world); @@ -43,15 +36,13 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { float sliceWidth, float sliceThickness, EnergyDisplayData display, EnergyCombatData combat, EnergyHomingData homing, EnergyLightningData lightning, - EnergyLifespanData lifespan, EnergyTrajectoryData trajectory, - boolean piercing) { + EnergyLifespanData lifespan, EnergyTrajectoryData trajectory) { super(world); initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan, trajectory); this.homingData = homing; this.sliceWidth = sliceWidth; this.sliceThickness = sliceThickness; - this.piercing = piercing; calculateInitialVelocity(owner, target, x, y, z); } @@ -196,15 +187,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { } // Wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } @Override @@ -223,19 +206,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { } private void checkBlockCollision() { - if (piercing) return; // Piercing slicers pass through blocks - - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), false); } private void checkEntityCollision() { @@ -246,29 +217,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { posX + halfW, posY + halfH, posZ + halfW ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - if (hitEntities.contains(entity.getEntityId())) continue; // Already hit this entity - - if (piercing) { - // Pierce: damage but keep flying - applyDamage(entity); - hitEntities.add(entity.getEntityId()); - } else { - // Non-piercing: damage and die - hasHit = true; - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - this.setDead(); - return; - } - } + processEntitiesInHitBox(hitBox, posX, posY, posZ); } /** @@ -315,14 +264,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { this.sliceThickness = thickness; } - public boolean isPiercing() { - return piercing; - } - - public void setPiercing(boolean piercing) { - this.piercing = piercing; - } - // ==================== NBT ==================== @Override @@ -330,7 +271,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { readChargingNBT(nbt); this.sliceWidth = sanitize(nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f, 3.0f, MAX_ENTITY_SIZE); this.sliceThickness = sanitize(nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f, 0.15f, MAX_ENTITY_SIZE); - this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); this.targetSliceWidth = sanitize(nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth, sliceWidth, MAX_ENTITY_SIZE); this.targetSliceThickness = sanitize(nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness, sliceThickness, MAX_ENTITY_SIZE); } @@ -340,7 +280,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { writeChargingNBT(nbt); nbt.setFloat("SliceWidth", sliceWidth); nbt.setFloat("SliceThickness", sliceThickness); - nbt.setBoolean("Piercing", piercing); nbt.setFloat("TargetSliceWidth", targetSliceWidth); nbt.setFloat("TargetSliceThickness", targetSliceThickness); } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index 38a95db0c..de9c8036e 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -161,6 +161,22 @@ public class ScriptEnergyProjectile extends Sc entity.setExplosionDamageFalloff(falloff); } + public int getHitType() { + return entity.getHitType(); + } + + public void setHitType(int hitType) { + entity.setHitType(hitType); + } + + public int getMultiHitDelayTicks() { + return entity.getMultiHitDelayTicks(); + } + + public void setMultiHitDelayTicks(int delayTicks) { + entity.setMultiHitDelayTicks(delayTicks); + } + // ==================== MOVEMENT ==================== public float getSpeed() { diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java index f0105cdd9..f120350c4 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java @@ -45,14 +45,6 @@ public class ScriptEnergySlicer extends ScriptEner entity.setSliceThickness(thickness); } - public boolean isPiercing() { - return entity.isPiercing(); - } - - public void setPiercing(boolean piercing) { - entity.setPiercing(piercing); - } - // ==================== FIRE ==================== @Override diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index dbe0442cd..0a7b1ecdc 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1783,6 +1783,11 @@ gui.applyPreset=Apply Preset ability.radius=Radius ability.knockback=Knockback ability.knockbackUp=Knockback Up +ability.hitType=Hit Type +ability.hitType.single=Single +ability.hitType.pierce=Pierce +ability.hitType.multi=Multi +ability.multiHitDelay=Multi-Hit Delay ability.distance=Distance ability.maxDistance=Max Distance ability.lifetime=Lifetime @@ -1985,6 +1990,8 @@ ability.hover.windUpColor=Color of telegraph during charge-up phase ability.hover.activeColor=Color of telegraph when ability is active ability.hover.homing=Projectile tracks toward target ability.hover.homingStr=How aggressively the projectile tracks (higher = tighter turns) +ability.hover.hitType=Single: stops on first hit. Pierce: can hit each entity once. Multi: can re-hit entities using the delay below. +ability.hover.multiHitDelay=Ticks between repeat hits on the same entity when Hit Type is Multi (20 ticks = 1 second) ability.hover.explosive=Creates explosion on impact ability.hover.explosionRad=Size of explosion radius ability.hover.piercing=Hits multiple targets instead of stopping on first hit @@ -2064,8 +2071,6 @@ ability.validFor=Valid For ability.userType.NPC_ONLY=NPCs ability.userType.PLAYER_ONLY=Players ability.userType.BOTH=Both -ability.dieOnImpact=Die on Impact - ability.icon.section.uv=UV Coordinates ability.icon.x=Icon X ability.icon.y=Icon Y From 18968bf7c787c978cb52e4d29d7f01dd36de0fe8 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 03:07:37 -0500 Subject: [PATCH 40/71] Fix Firing Energy Projectiles and Make Charging State purely visual --- .../renderer/EnergyChargePreviewRenderer.java | 38 +++ .../type/energy/AbilityEnergyProjectile.java | 228 ++++++++++++---- .../ability/type/energy/AbilityLaserShot.java | 2 +- .../EnergyChargePreviewManager.java | 68 +++++ .../npcs/entity/EntityAbilityBeam.java | 256 ++++++++++-------- .../npcs/entity/EntityAbilityDisc.java | 58 +--- .../npcs/entity/EntityAbilityLaser.java | 199 ++++++++------ .../kamkeel/npcs/entity/EntityAbilityOrb.java | 156 +++++++---- .../npcs/entity/EntityEnergyAbility.java | 22 ++ .../npcs/entity/EntityEnergyProjectile.java | 253 ++++++++++++++++- .../npcs/entity/EntityEnergySlicer.java | 47 +--- .../kamkeel/npcs/network/PacketHandler.java | 4 + .../npcs/network/enums/EnumDataPacket.java | 4 + .../EnergyChargeRemovePacket.java | 60 ++++ .../energycharge/EnergyChargeSpawnPacket.java | 108 ++++++++ .../java/noppes/npcs/client/ClientProxy.java | 6 + .../noppes/npcs/client/ClientTickHandler.java | 9 + .../npcs/entity/EntityNPCInterface.java | 7 +- 18 files changed, 1086 insertions(+), 439 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java diff --git a/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java new file mode 100644 index 000000000..66bf4869b --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java @@ -0,0 +1,38 @@ +package kamkeel.npcs.client.renderer; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +import kamkeel.npcs.entity.EntityEnergyProjectile; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.event.RenderWorldLastEvent; + +/** + * Renders packet-driven charging previews through RenderManager. + */ +public class EnergyChargePreviewRenderer { + + @SubscribeEvent + public void onRenderWorldLast(RenderWorldLastEvent event) { + if (EnergyChargePreviewManager.ClientInstance == null || !EnergyChargePreviewManager.ClientInstance.hasPreviews()) { + return; + } + + Minecraft mc = Minecraft.getMinecraft(); + EntityPlayer player = mc.thePlayer; + if (player == null) return; + + float partialTicks = event.partialTicks; + + for (EntityEnergyProjectile entity : EnergyChargePreviewManager.ClientInstance.getPreviews()) { + if (entity == null || entity.isDead) continue; + + double x = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks - RenderManager.renderPosX; + double y = entity.prevPosY + (entity.posY - entity.prevPosY) * partialTicks - RenderManager.renderPosY; + double z = entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks - RenderManager.renderPosZ; + + RenderManager.instance.renderEntityWithPosYaw(entity, x, y, z, entity.rotationYaw, partialTicks); + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 40ea82080..3cb81720d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -15,6 +15,8 @@ import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityEnergyProjectile; +import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket; +import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -55,6 +57,10 @@ public abstract class AbilityEnergyProjectile // Transient entity state protected transient E[] entities; + protected transient boolean[] projectileSpawned; + protected transient int spawnedCount; + protected transient String[] chargeVisualIds; + protected transient EntityLivingBase chargeVisualCaster; // ==================== CONSTRUCTOR ==================== @@ -77,7 +83,7 @@ public abstract class AbilityEnergyProjectile /** * Create a single entity for the given projectile index. - * Called during windup (tick 1) and burst refire (onExecute when entities are null). + * Called when a projectile actually fires. */ protected abstract E createEntity(EntityLivingBase caster, EntityLivingBase target, Vec3 spawnPos, EnergyDisplayData resolved, int index); @@ -209,32 +215,99 @@ public abstract class AbilityEnergyProjectile // ==================== ENTITY CREATION ==================== - /** - * Create all entities for this ability. - * Sets effects, source ability, and sibling links. - */ - protected E[] createAllEntities(EntityLivingBase caster, EntityLivingBase target) { - E[] newEntities = createEntityArray(projectileCount); - for (int i = 0; i < projectileCount; i++) { - Vec3 spawnPos = getSpawnPosition(caster, i); - EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); - newEntities[i] = createEntity(caster, target, spawnPos, resolved, i); - newEntities[i].setEffects(this.effects); - newEntities[i].setSourceAbility(this); + protected void initRuntimeState(EntityLivingBase caster) { + entities = createEntityArray(projectileCount); + projectileSpawned = new boolean[projectileCount]; + spawnedCount = 0; + chargeVisualIds = new String[projectileCount]; + chargeVisualCaster = caster; + } + + protected E createProjectileEntity(EntityLivingBase caster, EntityLivingBase target, int index) { + Vec3 spawnPos = getSpawnPosition(caster, index); + EnergyDisplayData resolved = projectiles[index].resolveDisplay(displayData); + E entity = createEntity(caster, target, spawnPos, resolved, index); + entity.setEffects(this.effects); + entity.setSourceAbility(this); + return entity; + } + + protected void spawnProjectileEntity(E entity, int index) { + if (entity == null) return; + spawnAbilityEntity(entity); + entities[index] = entity; + if (!projectileSpawned[index]) { + projectileSpawned[index] = true; + spawnedCount++; } - return newEntities; + } + + protected void spawnAndFireProjectile(EntityLivingBase caster, EntityLivingBase target, int index) { + if (entities != null && entities[index] != null && !entities[index].isDead) return; + E entity = createProjectileEntity(caster, target, index); + + // Initialize launch position/motion BEFORE spawn so the + // initial spawn packet already has centered look-vector data on all clients. + fireEntitySafe(entity, caster, target); + spawnProjectileEntity(entity, index); + } + + /** + * Hook for launch target selection. + * Default returns null so projectiles launch along owner look vector. + */ + protected EntityLivingBase getLaunchTarget(EntityLivingBase caster, EntityLivingBase target) { + return null; } /** * Fire an entity if it's valid. * Clears charging state first so the entity can move and check collisions. */ - protected void fireEntitySafe(E entity, EntityLivingBase target) { + protected void fireEntitySafe(E entity, EntityLivingBase caster, EntityLivingBase target) { if (entity != null && !entity.isDead) { if (entity.isCharging()) { entity.setCharging(false); } - fireEntity(entity, target); + fireEntity(entity, getLaunchTarget(caster, target)); + } + } + + protected String getChargeVisualId(EntityLivingBase caster, int index) { + return "energy_charge:" + caster.getEntityId() + ":" + executionStartTime + ":" + burstIndex + ":" + index; + } + + protected void spawnChargeVisual(EntityLivingBase caster, EntityLivingBase target, int index, int chargeDuration) { + if (isPreview() || caster == null || caster.worldObj == null || caster.worldObj.isRemote) return; + if (chargeVisualIds == null || index < 0 || index >= chargeVisualIds.length) return; + + E previewEntity = createProjectileEntity(caster, target, index); + previewEntity.setPreviewMode(true); + previewEntity.setPreviewOwner(caster); + setupEntityCharging(previewEntity, projectiles[index], index); + previewEntity.setChargeDuration(Math.max(1, chargeDuration)); + + String id = getChargeVisualId(caster, index); + chargeVisualIds[index] = id; + chargeVisualCaster = caster; + EnergyChargeSpawnPacket.sendToTracking(id, previewEntity, caster); + } + + protected void removeChargeVisual(EntityLivingBase caster, int index) { + if (isPreview() || caster == null || caster.worldObj == null || caster.worldObj.isRemote) return; + if (chargeVisualIds == null || index < 0 || index >= chargeVisualIds.length) return; + + String id = chargeVisualIds[index]; + if (id != null && !id.isEmpty()) { + EnergyChargeRemovePacket.sendToTracking(id, caster); + chargeVisualIds[index] = null; + } + } + + protected void removeAllChargeVisuals(EntityLivingBase caster) { + if (chargeVisualIds == null || caster == null) return; + for (int i = 0; i < chargeVisualIds.length; i++) { + removeChargeVisual(caster, i); } } @@ -253,6 +326,8 @@ public abstract class AbilityEnergyProjectile @Override public void detach() { entities = null; + projectileSpawned = null; + spawnedCount = 0; } @Override @@ -275,21 +350,31 @@ public abstract class AbilityEnergyProjectile if (caster.worldObj.isRemote && !isPreview()) return; if (tick == 1) { - entities = createAllEntities(caster, target); - for (int i = 0; i < projectileCount; i++) { - EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); - if (isPreview()) { - setupEntityPreview(entities[i], caster, resolved, projectiles[i], i); - } else { - setupEntityCharging(entities[i], projectiles[i], i); + initRuntimeState(caster); + + if (isPreview()) { + for (int i = 0; i < projectileCount; i++) { + E previewEntity = createProjectileEntity(caster, target, i); + EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); + setupEntityPreview(previewEntity, caster, resolved, projectiles[i], i); + spawnAbilityEntity(previewEntity); + entities[i] = previewEntity; + projectileSpawned[i] = true; + spawnedCount++; } - spawnAbilityEntity(entities[i]); + return; + } + + for (int i = 0; i < projectileCount; i++) { + spawnChargeVisual(caster, target, i, windUpTicks); } } } @Override public void resetForBurst() { + removeAllChargeVisuals(chargeVisualCaster); + // Non-overlap: kill old entities before next burst creates new ones if (!burstOverlap) { cleanup(); @@ -297,54 +382,71 @@ public abstract class AbilityEnergyProjectile // Clear reference so the next iteration creates fresh entities. // For overlap mode, old entities stay alive (tracked in burstEntities). entities = null; + projectileSpawned = null; + spawnedCount = 0; } @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { - // Create entities if they don't exist yet. - // They'll already exist if onWindUpTick ran (windup replay), otherwise we create here. - if (entities == null) { - entities = createAllEntities(caster, target); - - // If there's a fire delay, put unfired entities in charging state - // to prevent collision checks before they're actually fired. - // Entity[0] will be fired immediately below, so skip it. - if (fireDelay > 0 && projectileCount > 1) { - for (int i = 1; i < projectileCount; i++) { - if (entities[i] != null) { - entities[i].setCharging(true); - } + // GUI preview keeps the original non-world preview entity flow. + if (isPreview()) { + if (entities == null) { + initRuntimeState(caster); + for (int i = 0; i < projectileCount; i++) { + E previewEntity = createProjectileEntity(caster, target, i); + EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); + setupEntityPreview(previewEntity, caster, resolved, projectiles[i], i); + spawnAbilityEntity(previewEntity); + entities[i] = previewEntity; + projectileSpawned[i] = true; + spawnedCount++; } } - for (E entity : entities) { - spawnAbilityEntity(entity); + fireEntitySafe(entities[0], caster, target); + if (fireDelay <= 0) { + for (int i = 1; i < projectileCount; i++) { + fireEntitySafe(entities[i], caster, target); + } } + return; } - // Fire first projectile immediately - fireEntitySafe(entities[0], target); + if (entities == null || projectileSpawned == null || entities.length != projectileCount) { + initRuntimeState(caster); + } - // Fire remaining projectiles if no delay + removeAllChargeVisuals(caster); + + // Fire first projectile immediately. + spawnAndFireProjectile(caster, target, 0); + + // Fire remaining projectiles immediately if there is no stagger delay. if (fireDelay <= 0) { for (int i = 1; i < projectileCount; i++) { - fireEntitySafe(entities[i], target); + spawnAndFireProjectile(caster, target, i); + } + } else { + // During active stagger, keep client-only charging visuals for unfired projectiles. + for (int i = 1; i < projectileCount; i++) { + spawnChargeVisual(caster, target, i, fireDelay * i); } } } @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - if (entities == null) { + if (entities == null || projectileSpawned == null) { signalCompletion(); return; } - // Fire staggered projectiles + // Fire staggered projectiles: spawn real world entity only when it is time to fire. if (fireDelay > 0) { for (int i = 1; i < projectileCount; i++) { if (tick == fireDelay * i) { - fireEntitySafe(entities[i], target); + removeChargeVisual(caster, i); + spawnAndFireProjectile(caster, target, i); } } } @@ -358,21 +460,21 @@ public abstract class AbilityEnergyProjectile } } - // Check if all entities are dead or unreachable - boolean allDead = true; - for (E entity : entities) { - if (entity == null || entity.isDead) continue; + // Before all projectiles have fired, the ability is still active by definition. + boolean allDead = spawnedCount >= projectileCount; + if (allDead) { + for (E entity : entities) { + if (entity == null || entity.isDead) continue; - // Check if entity was removed from world (chunk unload, etc.) - // Skip this check for preview entities - they're not registered in the world - if (!isPreview() && tick > 5 && entity.worldObj != null - && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { - entity.setDead(); - continue; + if (!isPreview() && tick > 5 && entity.worldObj != null + && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { + entity.setDead(); + continue; + } + + allDead = false; + break; } - - allDead = false; - break; } // Failsafe timeout: force completion if active phase exceeded max possible flight time @@ -392,15 +494,19 @@ public abstract class AbilityEnergyProjectile @Override public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + removeAllChargeVisuals(caster); } @Override public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { + removeAllChargeVisuals(caster); cleanup(); } @Override public void cleanup() { + removeAllChargeVisuals(chargeVisualCaster); + if (entities != null) { for (int i = 0; i < entities.length; i++) { if (entities[i] != null && !entities[i].isDead) { @@ -410,6 +516,10 @@ public abstract class AbilityEnergyProjectile } } entities = null; + projectileSpawned = null; + spawnedCount = 0; + chargeVisualIds = null; + chargeVisualCaster = null; } // ==================== TELEGRAPH ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 30e99fedb..dafbc2200 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -139,7 +139,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile previews = new ConcurrentHashMap(); + + public static void initClient() { + ClientInstance = new EnergyChargePreviewManager(); + } + + public void addPreview(String instanceId, EntityEnergyProjectile entity) { + if (instanceId == null || entity == null) return; + previews.put(instanceId, entity); + } + + public void removePreview(String instanceId) { + if (instanceId == null) return; + previews.remove(instanceId); + } + + public Collection getPreviews() { + return previews.values(); + } + + public boolean hasPreviews() { + return !previews.isEmpty(); + } + + public void clear() { + previews.clear(); + } + + /** + * Tick all preview entities and remove invalid/expired entries. + */ + public void tick(World world) { + if (world == null) { + clear(); + return; + } + + Iterator iterator = previews.values().iterator(); + while (iterator.hasNext()) { + EntityEnergyProjectile entity = iterator.next(); + if (entity == null || entity.isDead || entity.worldObj != world) { + iterator.remove(); + continue; + } + entity.onUpdate(); + if (entity.isDead) { + iterator.remove(); + } + } + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 44589a842..d040d87e4 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -18,6 +18,7 @@ import net.minecraft.world.World; import noppes.npcs.LogWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -31,6 +32,11 @@ import java.util.List; */ public class EntityAbilityBeam extends EntityEnergyProjectile { + private enum BeamMode { + ANCHORED, + FREE_TRAIL + } + // Beam shape properties private float beamWidth = 0.3f; private float headSize = 0.5f; @@ -39,22 +45,18 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { private List trailPoints = new ArrayList<>(); private static final int MAX_TRAIL_POINTS = 200; private static final double MIN_POINT_DISTANCE = 0.2; + private static final int TRAIL_COMPACT_THRESHOLD = 128; + private int trailStartIndex = 0; // Head position relative to origin private double headOffsetX, headOffsetY, headOffsetZ; private double prevHeadOffsetX, prevHeadOffsetY, prevHeadOffsetZ; - // Origin stays fixed (or follows owner) - private boolean attachedToOwner = true; - - // Whether to render tail orb (only when anchored) - private boolean renderTailOrb = true; + // Beam behavior mode (single source of truth). + private BeamMode beamMode = BeamMode.ANCHORED; // Charging state (beam-specific) private float chargeOffsetDistance = 1.0f; - - // Trail fading for non-anchored beams (comet effect) - private boolean fadeTrail = false; private int trailFadeTime = 20; // Ticks for trail to fully fade private List trailPointAges = new ArrayList<>(); @@ -65,6 +67,46 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { super(world); } + private static BeamMode modeFromAnchored(boolean anchored) { + return anchored ? BeamMode.ANCHORED : BeamMode.FREE_TRAIL; + } + + private boolean isAnchoredMode() { + return beamMode == BeamMode.ANCHORED; + } + + private boolean isFadingMode() { + return beamMode == BeamMode.FREE_TRAIL; + } + + private int getActiveTrailSize() { + return Math.max(0, trailPoints.size() - trailStartIndex); + } + + private void resetTrailStorage() { + trailPoints.clear(); + trailPointAges.clear(); + trailStartIndex = 0; + } + + private void setBeamMode(BeamMode mode) { + beamMode = mode != null ? mode : BeamMode.ANCHORED; + if (isFadingMode()) { + while (trailPointAges.size() < trailPoints.size()) { + trailPointAges.add(0); + } + while (trailPointAges.size() > trailPoints.size()) { + trailPointAges.remove(trailPointAges.size() - 1); + } + } else { + if (trailStartIndex > 0 && trailStartIndex < trailPoints.size()) { + trailPoints = new ArrayList(trailPoints.subList(trailStartIndex, trailPoints.size())); + } + trailStartIndex = 0; + trailPointAges.clear(); + } + } + /** * Full constructor with all parameters using data classes. * @@ -89,9 +131,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.beamWidth = beamWidth; this.headSize = headSize; - // Anchored mode controls whether origin follows owner and tail orb is rendered - this.attachedToOwner = anchoredMode; - this.renderTailOrb = anchoredMode; + // Beam mode controls tail attachment/orb/fading behavior. + setBeamMode(modeFromAnchored(anchoredMode)); // Initialize head offset at origin (0,0,0 relative) this.headOffsetX = 0; @@ -118,7 +159,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.chargeTick = 0; this.chargeOffsetDistance = chargeOffsetDistance; this.anchorData = anchor; - this.fadeTrail = !attachedToOwner; this.motionX = 0; this.motionY = 0; this.motionZ = 0; @@ -160,10 +200,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.prevHeadOffsetY = 0; this.prevHeadOffsetZ = 0; - // Attach to owner for anchor following - this.attachedToOwner = true; - this.renderTailOrb = true; - // Clear motion this.motionX = 0; this.motionY = 0; @@ -174,12 +210,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Origin (tail) stays at the charged position - startX = posX; - startY = posY; - startZ = posZ; + beginLookVectorLaunch(true); // Reset head offset to origin headOffsetX = 0; @@ -189,51 +220,21 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { prevHeadOffsetY = 0; prevHeadOffsetZ = 0; - // Origin no longer follows owner after firing - attachedToOwner = false; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - // Reset trail - trailPoints.clear(); - trailPointAges.clear(); + resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); + if (isFadingMode()) trailPointAges.add(0); - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - motionX = -Math.sin(yaw) * getSpeed(); - motionY = 0; - motionZ = Math.cos(yaw) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + // Fire along look vector for parity with real launch behavior. + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); } /** * Start the beam firing (exit charging mode). * Called by ability when windup ends. - *

- * For anchored beams: origin follows owner, head starts at charged position - * For non-anchored beams: origin fixed at charged position, head starts there */ public void startFiring(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Origin (tail) stays at the charged position - where the orb was - // This is the same for both anchored and non-anchored modes - startX = posX; - startY = posY; - startZ = posZ; + beginLookVectorLaunch(false); // Head starts at the origin (tail position) headOffsetX = 0; @@ -243,36 +244,13 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { prevHeadOffsetY = 0; prevHeadOffsetZ = 0; - // Origin no longer follows owner after firing starts - // (for anchored mode, tail is fixed in space; for non-anchored, there's no tail) - attachedToOwner = false; - // Initialize trail with just the origin point - trailPoints.clear(); - trailPointAges.clear(); + resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); - if (fadeTrail) trailPointAges.add(0); + if (isFadingMode()) trailPointAges.add(0); - // Calculate velocity toward target (head starts at origin = startX/Y/Z) - Entity owner = getOwnerEntity(); - - if (target != null) { - double dx = target.posX - startX; - double dy = (target.posY + target.getEyeHeight()) - startY; - double dz = target.posZ - startZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + // Calculate velocity toward target when provided; otherwise along look vector. + setMotionTowardTargetOrLookVector(target, startX, startY, startZ, getSpeed(), getSpeed(), 0, 0); if (DEBUG_LOGGING && !worldObj.isRemote) { LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ + @@ -310,7 +288,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { prevHeadOffsetZ = headOffsetZ; // Age trail points for fading effect - if (fadeTrail) { + if (isFadingMode()) { ageTrailPoints(); } @@ -344,7 +322,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { headOffsetZ += motionZ; // Update origin if attached to owner - if (attachedToOwner) { + if (isAnchoredMode()) { Entity owner = getOwnerEntity(); if (owner != null) { startX = owner.posX; @@ -372,7 +350,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { String.format("%.2f", headOffsetZ) + ") origin=(" + String.format("%.2f", startX) + "," + String.format("%.2f", startY) + "," + - String.format("%.2f", startZ) + ") trail=" + trailPoints.size()); + String.format("%.2f", startZ) + ") trail=" + getActiveTrailSize()); } // Skip collision checks on first few ticks @@ -418,25 +396,43 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Age trail points and remove old ones (for comet effect). */ private void ageTrailPoints() { - // Age all trail points - for (int i = 0; i < trailPointAges.size(); i++) { + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + return; + } + + // Age all active trail points. + for (int i = trailStartIndex; i < trailPointAges.size(); i++) { trailPointAges.set(i, trailPointAges.get(i) + 1); } - // Remove trail points that have fully faded - while (!trailPointAges.isEmpty() && trailPointAges.get(0) >= trailFadeTime) { - trailPointAges.remove(0); - if (!trailPoints.isEmpty()) { - trailPoints.remove(0); - } + // Move logical start index forward for fully faded points. + while (trailStartIndex < trailPointAges.size() && trailPointAges.get(trailStartIndex) >= trailFadeTime) { + trailStartIndex++; + } + + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + return; + } + + // Compact stale prefix to avoid unbounded growth of dead entries. + if (trailStartIndex >= TRAIL_COMPACT_THRESHOLD && trailStartIndex * 2 >= trailPoints.size()) { + trailPoints = new ArrayList(trailPoints.subList(trailStartIndex, trailPoints.size())); + trailPointAges = new ArrayList(trailPointAges.subList(trailStartIndex, trailPointAges.size())); + trailStartIndex = 0; } } private void addTrailPoint() { + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + } + // Trail points are RELATIVE to origin if (trailPoints.isEmpty()) { trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ)); - if (fadeTrail) { + if (isFadingMode()) { trailPointAges.add(0); } return; @@ -451,12 +447,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { if (dist >= MIN_POINT_DISTANCE) { trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ)); - if (fadeTrail) { + if (isFadingMode()) { trailPointAges.add(0); } // Limit trail length (only if not using fading - fading handles its own cleanup) - if (!fadeTrail) { + if (!isFadingMode()) { while (trailPoints.size() > MAX_TRAIL_POINTS) { trailPoints.remove(0); } @@ -474,7 +470,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { headOffsetZ += motionZ; // Update origin if attached to owner - if (attachedToOwner) { + if (isAnchoredMode()) { Entity owner = getOwnerEntity(); if (owner != null) { startX = owner.posX; @@ -586,7 +582,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public void setAttachedToOwner(boolean attached) { - this.attachedToOwner = attached; + setBeamMode(modeFromAnchored(attached)); } /** @@ -609,7 +605,13 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Get trail points. These are RELATIVE to origin (startX/Y/Z). */ public List getTrailPoints() { - return trailPoints; + if (trailStartIndex <= 0) { + return trailPoints; + } + if (trailStartIndex >= trailPoints.size()) { + return Collections.emptyList(); + } + return trailPoints.subList(trailStartIndex, trailPoints.size()); } public double getOriginX() { @@ -628,28 +630,34 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Whether the tail orb should be rendered (only true in anchored mode). */ public boolean shouldRenderTailOrb() { - return renderTailOrb; + return isAnchoredMode(); } /** * Whether the beam is attached to its owner (anchored mode). */ public boolean isAttachedToOwner() { - return attachedToOwner; + return isAnchoredMode(); } /** * Whether trail should fade (comet effect for non-anchored beams). */ public boolean hasFadingTrail() { - return fadeTrail; + return isFadingMode(); } /** * Get trail point ages for fading calculation. */ public List getTrailPointAges() { - return trailPointAges; + if (trailStartIndex <= 0) { + return trailPointAges; + } + if (trailStartIndex >= trailPointAges.size()) { + return Collections.emptyList(); + } + return trailPointAges.subList(trailStartIndex, trailPointAges.size()); } /** @@ -671,17 +679,26 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.prevHeadOffsetX = this.headOffsetX; this.prevHeadOffsetY = this.headOffsetY; this.prevHeadOffsetZ = this.headOffsetZ; - this.attachedToOwner = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner"); - this.renderTailOrb = !nbt.hasKey("RenderTailOrb") || nbt.getBoolean("RenderTailOrb"); readChargingNBT(nbt); this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f; - this.fadeTrail = nbt.hasKey("FadeTrail") && nbt.getBoolean("FadeTrail"); this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20; + boolean attachedLegacy = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner"); + BeamMode loadedMode = modeFromAnchored(attachedLegacy); + if (nbt.hasKey("BeamMode")) { + try { + loadedMode = BeamMode.valueOf(nbt.getString("BeamMode")); + } catch (IllegalArgumentException ignored) { + loadedMode = modeFromAnchored(attachedLegacy); + } + } else if (nbt.hasKey("FadeTrail")) { + loadedMode = nbt.getBoolean("FadeTrail") ? BeamMode.FREE_TRAIL : modeFromAnchored(attachedLegacy); + } // Read trail points (relative to origin) trailPoints.clear(); trailPointAges.clear(); + trailStartIndex = 0; if (nbt.hasKey("Trail")) { NBTTagList trailList = nbt.getTagList("Trail", 10); for (int i = 0; i < trailList.tagCount(); i++) { @@ -701,10 +718,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { trailPointAges.add(age); } } - // If trail points exist but ages don't (legacy data), initialize ages to 0 - while (trailPointAges.size() < trailPoints.size()) { - trailPointAges.add(0); - } + setBeamMode(loadedMode); } @Override @@ -714,16 +728,18 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { nbt.setDouble("HeadOffsetX", headOffsetX); nbt.setDouble("HeadOffsetY", headOffsetY); nbt.setDouble("HeadOffsetZ", headOffsetZ); - nbt.setBoolean("AttachedToOwner", attachedToOwner); - nbt.setBoolean("RenderTailOrb", renderTailOrb); + nbt.setBoolean("AttachedToOwner", isAnchoredMode()); + nbt.setBoolean("RenderTailOrb", isAnchoredMode()); + nbt.setString("BeamMode", beamMode.name()); writeChargingNBT(nbt); nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance); - nbt.setBoolean("FadeTrail", fadeTrail); + nbt.setBoolean("FadeTrail", isFadingMode()); nbt.setInteger("TrailFadeTime", trailFadeTime); // Write trail points NBTTagList trailList = new NBTTagList(); - for (Vec3 point : trailPoints) { + for (int i = trailStartIndex; i < trailPoints.size(); i++) { + Vec3 point = trailPoints.get(i); NBTTagCompound pointNbt = new NBTTagCompound(); pointNbt.setDouble("X", point.xCoord); pointNbt.setDouble("Y", point.yCoord); @@ -733,9 +749,11 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { nbt.setTag("Trail", trailList); // Write trail point ages for fading trail sync - int[] ages = new int[trailPointAges.size()]; - for (int i = 0; i < trailPointAges.size(); i++) { - ages[i] = trailPointAges.get(i); + int activeSize = isFadingMode() ? Math.max(0, trailPoints.size() - trailStartIndex) : 0; + int[] ages = new int[activeSize]; + for (int i = 0; i < activeSize; i++) { + int idx = trailStartIndex + i; + ages[i] = idx < trailPointAges.size() ? trailPointAges.get(idx) : 0; } nbt.setIntArray("TrailAges", ages); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index b5daa8701..16a1423f0 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -137,31 +137,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = 0; // Fire horizontally - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + beginLookVectorLaunch(true); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); } /** @@ -169,35 +146,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Calculate velocity toward target - Entity owner = getOwnerEntity(); - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); } @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index ec851f802..67440bf07 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -1,5 +1,6 @@ package kamkeel.npcs.entity; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; @@ -8,7 +9,6 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; @@ -156,13 +156,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } /** - * Update laser origin and direction from the owner's current anchor position and rotation. - * Called each tick so the laser follows the NPC when tracking a target. + * Update laser origin and direction from the owner's current look rotation. + * Called each tick so the laser origin stays centered on look vector. */ private void updateLaserOriginAndDirection() { Entity owner = getOwnerEntity(); if (owner == null || !(owner instanceof EntityLivingBase)) return; - EntityLivingBase livingOwner = (EntityLivingBase) owner; // Update direction from owner's current rotation @@ -180,71 +179,51 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { dirZ = Math.cos(yaw) * Math.cos(pitch); } - // Update origin: for player casters, snap to look vector so laser aligns with crosshair. - // For NPCs, use the configured anchor point. - if (owner instanceof EntityPlayer) { - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, laserWidth * 0.5f); - startX = owner.posX + dirX * frontDist; - startY = eyeY + dirY * frontDist; - startZ = owner.posZ + dirZ * frontDist; - } else if (anchorData != null) { - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData); - startX = pos.xCoord; - startY = pos.yCoord; - startZ = pos.zCoord; + // Keep direction normalized for consistent expansion and collision math. + Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ); + double dirLen = Math.sqrt(direction.xCoord * direction.xCoord + direction.yCoord * direction.yCoord + direction.zCoord * direction.zCoord); + if (dirLen > 0.0001) { + direction = Vec3.createVectorHelper( + direction.xCoord / dirLen, + direction.yCoord / dirLen, + direction.zCoord / dirLen + ); } else { - startX = owner.posX; - startY = owner.posY + owner.height * 0.7; - startZ = owner.posZ; - } - - // Keep entity positioned at origin - prevPosX = startX; - prevPosY = startY; - prevPosZ = startZ; - setPosition(startX, startY, startZ); - } - - public void startMoving(EntityLivingBase target) { - setCharging(false); - - Entity owner = getOwnerEntity(); - // Set initial direction from owner's rotation - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - this.dirX = -Math.sin(yaw) * Math.cos(pitch); - this.dirY = -Math.sin(pitch); - this.dirZ = Math.cos(yaw) * Math.cos(pitch); - } else if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight() - 0.4) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - this.dirX = dx / len; - this.dirY = dy / len; - this.dirZ = dz / len; + Vec3 look = livingOwner.getLookVec(); + if (look != null) { + direction = look; } } - // For player casters, snap origin to look vector so laser aligns with crosshair. - // For NPCs, use current anchor position. - if (owner instanceof EntityPlayer) { - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, laserWidth * 0.5f); - startX = owner.posX + dirX * frontDist; - startY = eyeY + dirY * frontDist; - startZ = owner.posZ + dirZ * frontDist; - setPosition(startX, startY, startZ); - prevPosX = startX; - prevPosY = startY; - prevPosZ = startZ; - } else { - startX = posX; - startY = posY; - startZ = posZ; + dirX = direction.xCoord; + dirY = direction.yCoord; + dirZ = direction.zCoord; + + // Keep origin centered on look vector while active and clear owner bbox. + setLookVectorLaunchPosition(livingOwner, direction, false); + syncStartPositionToCurrent(); + syncPositionStateToCurrent(false); + } + + public void startMoving(EntityLivingBase target) { + beginLookVectorLaunch(false); + + currentLength = 0.0f; + fullyExtended = false; + ticksSinceFullExtension = 0; + + // Set initial direction: target-based when available, otherwise owner look vector. + Vec3 look = getOwnerLookVector(); + if (!setDirectionTowardTarget(target, startX, startY, startZ)) { + if (look != null) { + dirX = look.xCoord; + dirY = look.yCoord; + dirZ = look.zCoord; + } else { + dirX = 1.0; + dirY = 0.0; + dirZ = 0.0; + } } // Initialize end point at start (will expand from here) @@ -253,6 +232,20 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.endZ = startZ; } + private boolean setDirectionTowardTarget(EntityLivingBase target, double sourceX, double sourceY, double sourceZ) { + if (target == null) return false; + double dx = target.posX - sourceX; + double dy = (target.posY + target.getEyeHeight() - 0.4) - sourceY; + double dz = target.posZ - sourceZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len <= 0.0001) return false; + + dirX = dx / len; + dirY = dy / len; + dirZ = dz / len; + return true; + } + private void checkBlockCollision() { MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ); @@ -342,8 +335,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { double dirLenXZ = Math.sqrt(dirX * dirX + dirZ * dirZ); double dot; if (dirLenXZ > 0.001) { - dot = (vx * dirX + vz * dirZ) / (dirLenXZ * dirLenXZ) * dirLenXZ; - // Remap to full 3D parameter dot = (vx * dirX + vz * dirZ + (entity.posY + entity.height * 0.5 - startY) * dirY); } else { // Laser fires nearly straight up/down - use full 3D projection @@ -561,6 +552,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks)); } + @Override + protected float getLaunchClearanceRadius() { + return Math.max(0.1f, laserWidth * 0.5f); + } + + @Override + public void setupCharging(EnergyAnchorData anchor, int chargeDuration) { + // Ensure charging visuals grow from a small orb based on laser width. + this.size = laserWidth; + this.currentLength = 0.0f; + this.fullyExtended = false; + this.ticksSinceFullExtension = 0; + super.setupCharging(anchor, chargeDuration); + this.endX = posX; + this.endY = posY; + this.endZ = posZ; + } + // ==================== NBT ==================== @Override @@ -605,10 +614,11 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { /** * Setup this laser in preview mode for GUI display. - * Laser doesn't have charging state - spawns at active phase and fires immediately. + * Uses charging-orb visuals during windup before firing. */ public void setupPreview(EntityLivingBase owner, float laserWidth, EnergyDisplayData display, - EnergyLightningData lightning, float expansionSpeed, float maxDistance) { + EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, + float expansionSpeed, float maxDistance) { this.setPreviewMode(true); this.setPreviewOwner(owner); @@ -619,28 +629,37 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.expansionSpeed = expansionSpeed; this.lifespanData.maxDistance = Math.min(maxDistance, 5.0f); // Limit for GUI preview this.lightningData = lightning; + this.currentLength = 0.0f; + this.fullyExtended = false; + this.ticksSinceFullExtension = 0; - // Position at chest height - double x = owner.posX; - double y = owner.posY + owner.height * 0.7; - double z = owner.posZ; - this.setPosition(x, y, z); - this.prevPosX = x; - this.prevPosY = y; - this.prevPosZ = z; - this.startX = x; - this.startY = y; - this.startZ = z; + // Charge as an orb at the configured anchor, matching other energy projectiles. + setupCharging(anchor, chargeDuration); + Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); + this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + this.prevPosX = pos.xCoord; + this.prevPosY = pos.yCoord; + this.prevPosZ = pos.zCoord; + this.startX = pos.xCoord; + this.startY = pos.yCoord; + this.startZ = pos.zCoord; + this.endX = pos.xCoord; + this.endY = pos.yCoord; + this.endZ = pos.zCoord; - // Fire in owner's facing direction - float yaw = (float) Math.toRadians(owner.rotationYaw); - this.dirX = -Math.sin(yaw); - this.dirY = 0; - this.dirZ = Math.cos(yaw); - - // Initialize end point (same as start, will expand) - this.endX = x; - this.endY = y; - this.endZ = z; + // Precompute initial direction from owner look for launch. + Vec3 look = owner.getLookVec(); + if (look == null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + look = Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } + this.dirX = look.xCoord; + this.dirY = look.yCoord; + this.dirZ = look.zCoord; } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index efb1db6a9..6a12bf9ab 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -14,6 +14,9 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.Vec3; import net.minecraft.world.World; +import noppes.npcs.LogWriter; + +import java.util.Locale; /** * Orb projectile - spherical homing energy ball. @@ -22,6 +25,10 @@ import net.minecraft.world.World; * Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityOrb extends EntityEnergyProjectile { + private static final int HOMING_STARTUP_TICKS = 4; + private static final int HOMING_RAMP_TICKS = 8; + private static final boolean DEBUG_ORB_FIRE_TRACE = false; + private static final int DEBUG_TRACE_TICKS = 14; public EntityAbilityOrb(World world) { super(world); @@ -71,7 +78,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { this.renderCurrentSize = 0.01f; this.prevRenderSize = 0.01f; - // Initial position at anchor point + // Initial position follows anchor helper during charging. Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); this.prevPosX = pos.xCoord; @@ -91,32 +98,8 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(0); // Fire horizontally - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } else { - // Default: fire forward (positive X in model space) - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + beginLookVectorLaunch(true); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); } /** @@ -124,35 +107,28 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Calculate velocity toward target + // Launch strictly along owner's look vector using shared launch origin. Entity owner = getOwnerEntity(); - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); + if (owner instanceof EntityLivingBase) { + Vec3 look = getOwnerLookVector(); + if (look != null) { + debugTrace("startMoving:before", look, -1.0f); + if (beginLookVectorLaunch((EntityLivingBase) owner, look, false)) { + debugTrace("setLaunchPosition", look, -1.0f); + } + motionX = look.xCoord * getSpeed(); + motionY = look.yCoord * getSpeed(); + motionZ = look.zCoord * getSpeed(); + debugTrace("startMoving:after", look, -1.0f); + return; } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); } + + // Fallback if owner is unavailable. + setCharging(false); + syncStartPositionToCurrent(); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + debugTrace("startMoving:fallback", null, -1.0f); } @Override @@ -170,13 +146,17 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } if (worldObj.isRemote) { + if (shouldDebugTick()) debugTrace("client:beforeInterpolation", null, -1.0f); handleClientInterpolation(); + if (shouldDebugTick()) debugTrace("client:afterInterpolation", null, -1.0f); } else { // Server-side logic + if (shouldDebugTick()) debugTrace("server:beforeMovement", null, -1.0f); updateMovement(); checkBlockCollision(); checkEntityCollision(); this.moveEntity(motionX, motionY, motionZ); + if (shouldDebugTick()) debugTrace("server:afterMove", null, -1.0f); } // Check wall collision @@ -197,10 +177,28 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { if (!isTrajectoryConcluded()) { updateTrajectory(); } else { - updateHoming(); + if (ticksExisted > HOMING_STARTUP_TICKS) { + int homingTicks = ticksExisted - HOMING_STARTUP_TICKS; + if (homingTicks >= HOMING_RAMP_TICKS) { + if (shouldDebugTick()) debugTrace("updateMovement:homingFull", null, 1.0f); + updateHoming(); + } else { + float ramp = homingTicks / (float) HOMING_RAMP_TICKS; + if (shouldDebugTick()) debugTrace("updateMovement:homingRamp", null, ramp); + updateHomingWithRamp(ramp); + } + } } } + private void updateHomingWithRamp(float ramp) { + if (ramp <= 0.0f) return; + float originalStrength = homingData.homingStrength; + homingData.homingStrength = originalStrength * ramp; + updateHoming(); + homingData.homingStrength = originalStrength; + } + private void updateTrajectory() { if (isTrajectoryConcluded()) return; // i really fucking hate math @@ -224,12 +222,54 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } private void checkEntityCollision() { - double hitSize = size * 0.5; + // Swept collision to avoid fast orbs skipping targets between ticks. + double nextX = posX + motionX; + double nextY = posY + motionY; + double nextZ = posZ + motionZ; + // Keep collision radius proportional to rendered orb size. + double hitSize = Math.max(0.05, size * 0.5); AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( - posX - hitSize, posY - hitSize, posZ - hitSize, - posX + hitSize, posY + hitSize, posZ + hitSize + Math.min(posX, nextX) - hitSize, Math.min(posY, nextY) - hitSize, Math.min(posZ, nextZ) - hitSize, + Math.max(posX, nextX) + hitSize, Math.max(posY, nextY) + hitSize, Math.max(posZ, nextZ) + hitSize ); - processEntitiesInHitBox(hitBox, posX, posY, posZ); + processEntitiesInHitBox(hitBox, nextX, nextY, nextZ); + } + + private boolean shouldDebugTick() { + return DEBUG_ORB_FIRE_TRACE && !isCharging() && !previewMode && ticksExisted <= DEBUG_TRACE_TICKS; + } + + private void debugTrace(String stage, Vec3 look, float ramp) { + if (!DEBUG_ORB_FIRE_TRACE || worldObj == null) return; + + String side = worldObj.isRemote ? "CLIENT" : "SERVER"; + Entity owner = getOwnerEntity(); + String ownerInfo = owner == null + ? "null" + : owner.getEntityId() + "@(" + fmt(owner.posX) + "," + fmt(owner.posY) + "," + fmt(owner.posZ) + ")"; + String lookInfo = look == null + ? "null" + : "(" + fmt(look.xCoord) + "," + fmt(look.yCoord) + "," + fmt(look.zCoord) + ")"; + + LogWriter.info( + "[OrbFireTrace][" + side + "][" + stage + "] " + + "id=" + getEntityId() + + " tick=" + ticksExisted + + " charging=" + isCharging() + + " start=(" + fmt(startX) + "," + fmt(startY) + "," + fmt(startZ) + ")" + + " pos=(" + fmt(posX) + "," + fmt(posY) + "," + fmt(posZ) + ")" + + " prev=(" + fmt(prevPosX) + "," + fmt(prevPosY) + "," + fmt(prevPosZ) + ")" + + " motion=(" + fmt(motionX) + "," + fmt(motionY) + "," + fmt(motionZ) + ")" + + " size=" + fmt(size) + + " speed=" + fmt(getSpeed()) + + " ramp=" + (ramp < 0 ? "n/a" : fmt(ramp)) + + " owner=" + ownerInfo + + " look=" + lookInfo + ); + } + + private static String fmt(double v) { + return String.format(Locale.ROOT, "%.4f", v); } // ==================== GETTERS ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index 585eadc7d..3b1bc0cc1 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -83,6 +83,10 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit } } + public void setChargeDuration(int duration) { + this.chargeDuration = duration; + } + public float getChargeProgress() { if (chargeDuration <= 0) return 1.0f; return Math.min(1.0f, (float) chargeTick / chargeDuration); @@ -333,6 +337,24 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit readEnergyBaseNBT(nbt); } + /** + * Export spawn NBT for non-world preview rendering sync. + */ + public final NBTTagCompound exportSpawnNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + writeSpawnNBT(nbt); + return nbt; + } + + /** + * Import spawn NBT for non-world preview rendering sync. + */ + public final void importSpawnNBT(NBTTagCompound nbt) { + if (nbt != null) { + readSpawnNBT(nbt); + } + } + @Override public void writeSpawnData(ByteBuf buffer) { try { diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 9dfa105d6..fae432710 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -516,27 +516,237 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== LAUNCH HELPERS ==================== /** - * For player casters, snap the projectile to a position along the player's look vector. - * This ensures projectiles launch aligned with the crosshair rather than from the hand anchor. - * Only affects player casters; NPC projectiles launch from their anchor position as configured. + * Snaps the projectile position to the owner's look vector on launch. + * This ensures projectiles launch aligned with the owner's facing direction + * rather than from the last anchor point position. * Should be called in startMoving/startFiring BEFORE setting startX/Y/Z. */ - protected void snapToPlayerLookVector() { + protected void snapToLookVector() { + setLookVectorLaunchPosition(true); + } + + /** + * Shared launch transition: exit charging, snap to look-vector launch origin, + * sync start position, and optionally reset interpolation state. + * + * @param resetInterpolationState true to reset prev/lastTick/interp state to launch origin. + * @return true when look-vector launch origin was resolved from owner/look data. + */ + protected boolean beginLookVectorLaunch(boolean resetInterpolationState) { + setCharging(false); + boolean positioned = setLookVectorLaunchPosition(true); + syncStartPositionToCurrent(); + if (resetInterpolationState) { + syncPositionStateToCurrent(true); + } + return positioned; + } + + /** + * Shared launch transition with explicit owner/look vector data. + */ + protected boolean beginLookVectorLaunch(EntityLivingBase owner, Vec3 look, boolean resetInterpolationState) { + setCharging(false); + boolean positioned = setLookVectorLaunchPosition(owner, look, true); + syncStartPositionToCurrent(); + if (resetInterpolationState) { + syncPositionStateToCurrent(true); + } + return positioned; + } + + /** + * Resolve the owner's look vector. + * Falls back to yaw/pitch math if look vector is unavailable. + */ + protected Vec3 getOwnerLookVector() { Entity owner = getOwnerEntity(); - if (!(owner instanceof EntityPlayer)) return; + if (!(owner instanceof EntityLivingBase)) return null; Vec3 look = owner.getLookVec(); - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, size * 0.5f); + if (look != null) return look; - double newX = owner.posX + look.xCoord * frontDist; - double newY = eyeY + look.yCoord * frontDist; - double newZ = owner.posZ + look.zCoord * frontDist; + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + return Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } + + /** + * Shared launch origin: owner position + chest height + look-vector offset. + * + * @param syncPositionState When true, sync prev/lastTick/interp targets to this position. + */ + protected boolean setLookVectorLaunchPosition(boolean syncPositionState) { + Entity owner = getOwnerEntity(); + if (!(owner instanceof EntityLivingBase)) return false; + Vec3 look = getOwnerLookVector(); + if (look == null) return false; + return setLookVectorLaunchPosition((EntityLivingBase) owner, look, syncPositionState); + } + + /** + * Shared launch origin using provided owner/look data. + */ + protected boolean setLookVectorLaunchPosition(EntityLivingBase owner, Vec3 look, boolean syncPositionState) { + if (owner == null || look == null) return false; + + double originX = owner.posX; + double originY = owner.posY + owner.height * 0.7; + double originZ = owner.posZ; + + float frontDist = computeLaunchFrontDistance(owner, look, originX, originY, originZ); + double newX = originX + look.xCoord * frontDist; + double newY = originY + look.yCoord * frontDist; + double newZ = originZ + look.zCoord * frontDist; setPosition(newX, newY, newZ); - prevPosX = newX; - prevPosY = newY; - prevPosZ = newZ; + if (syncPositionState) { + syncPositionState(newX, newY, newZ, true); + } + return true; + } + + /** + * Radius used to clear the caster hitbox on launch. + * Subclasses may override if their effective launch footprint differs from {@code size}. + */ + protected float getLaunchClearanceRadius() { + return Math.max(0.1f, size * 0.5f); + } + + private float computeLaunchFrontDistance(EntityLivingBase owner, Vec3 look, double originX, double originY, double originZ) { + // Legacy baseline offset retained as lower bound. + float baseline = size * 0.4f; + float clearance = getLaunchClearanceRadius() + 0.05f; + + AxisAlignedBB bb = owner.boundingBox; + if (bb == null) { + return Math.max(baseline, owner.width * 0.5f + clearance); + } + + double dx = look.xCoord; + double dy = look.yCoord; + double dz = look.zCoord; + + double tx = computeRayExitT(originX, dx, bb.minX, bb.maxX); + double ty = computeRayExitT(originY, dy, bb.minY, bb.maxY); + double tz = computeRayExitT(originZ, dz, bb.minZ, bb.maxZ); + + double tExit = Math.min(tx, Math.min(ty, tz)); + if (Double.isInfinite(tExit) || tExit < 0) { + return Math.max(baseline, owner.width * 0.5f + clearance); + } + + return (float) Math.max(baseline, tExit + clearance); + } + + private double computeRayExitT(double origin, double dir, double min, double max) { + final double EPS = 1.0e-6; + if (dir > EPS) { + return (max - origin) / dir; + } else if (dir < -EPS) { + return (min - origin) / dir; + } + return Double.POSITIVE_INFINITY; + } + + /** + * Set projectile motion along owner look vector. + * + * @return true if owner/look were available. + */ + protected boolean setMotionAlongLookVector(float speed) { + Vec3 look = getOwnerLookVector(); + if (look == null) return false; + + motionX = look.xCoord * speed; + motionY = look.yCoord * speed; + motionZ = look.zCoord * speed; + return true; + } + + /** + * Set motion along owner look vector or apply explicit fallback values. + */ + protected void setMotionAlongLookVectorOrFallback(float speed, double fallbackX, double fallbackY, double fallbackZ) { + if (!setMotionAlongLookVector(speed)) { + motionX = fallbackX; + motionY = fallbackY; + motionZ = fallbackZ; + } + } + + /** + * Set projectile motion toward a target from a given source point. + * + * @return true if target was valid and motion was updated. + */ + protected boolean setMotionTowardTarget(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed) { + if (target == null) return false; + + double dx = target.posX - sourceX; + double dy = (target.posY + target.getEyeHeight()) - sourceY; + double dz = target.posZ - sourceZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len <= 0.0001) return false; + + motionX = (dx / len) * speed; + motionY = (dy / len) * speed; + motionZ = (dz / len) * speed; + return true; + } + + /** + * Set motion toward target from source point; when target is unavailable, + * fall back to owner look vector, then explicit fallback values. + */ + protected void setMotionTowardTargetOrLookVector(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed, + double fallbackX, double fallbackY, double fallbackZ) { + if (!setMotionTowardTarget(target, sourceX, sourceY, sourceZ, speed)) { + setMotionAlongLookVectorOrFallback(speed, fallbackX, fallbackY, fallbackZ); + } + } + + /** + * Sync start position (distance origin) to the current entity position. + */ + protected void syncStartPositionToCurrent() { + startX = posX; + startY = posY; + startZ = posZ; + } + + /** + * Sync prev/lastTick and optional interpolation state to an explicit position. + */ + protected void syncPositionState(double x, double y, double z, boolean resetInterpolation) { + prevPosX = x; + prevPosY = y; + prevPosZ = z; + lastTickPosX = x; + lastTickPosY = y; + lastTickPosZ = z; + + if (resetInterpolation) { + interpTargetX = x; + interpTargetY = y; + interpTargetZ = z; + interpTargetMotionX = motionX; + interpTargetMotionY = motionY; + interpTargetMotionZ = motionZ; + interpSteps = 0; + } + } + + /** + * Sync prev/lastTick and optional interpolation state to current entity position. + */ + protected void syncPositionStateToCurrent(boolean resetInterpolation) { + syncPositionState(posX, posY, posZ, resetInterpolation); } // ==================== CHARGING METHODS (projectile-specific) ==================== @@ -1118,6 +1328,23 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.motionY = nbt.getDouble("MotionY"); this.motionZ = nbt.getDouble("MotionZ"); + // Authoritative launch origin comes from spawn NBT (set during fire/startMoving). + // Sync full client position state to this origin to avoid first-tick spawn/interp pops. + this.setPosition(this.startX, this.startY, this.startZ); + this.prevPosX = this.startX; + this.prevPosY = this.startY; + this.prevPosZ = this.startZ; + this.lastTickPosX = this.startX; + this.lastTickPosY = this.startY; + this.lastTickPosZ = this.startZ; + this.interpTargetX = this.startX; + this.interpTargetY = this.startY; + this.interpTargetZ = this.startZ; + this.interpTargetMotionX = this.motionX; + this.interpTargetMotionY = this.motionY; + this.interpTargetMotionZ = this.motionZ; + this.interpSteps = 0; + // Always initialize render size from NBT size so entities // loading in from outside render distance render correctly this.renderCurrentSize = this.size; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 1c2a868a8..4a8c3fbc2 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -102,55 +102,16 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { * Start preview firing. */ public void startPreviewFiring() { - setCharging(false); - startX = posX; - startY = posY; - startZ = posZ; - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - motionX = -Math.sin(yaw) * getSpeed(); - motionY = 0; - motionZ = Math.cos(yaw) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + beginLookVectorLaunch(true); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); } /** * Start the slicer moving (exit charging mode). */ public void startMoving(EntityLivingBase target) { - setCharging(false); - snapToPlayerLookVector(); - startX = posX; - startY = posY; - startZ = posZ; - - Entity owner = getOwnerEntity(); - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); } @Override diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 8e784b5b8..8b57a070c 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -36,6 +36,8 @@ import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayClosePacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket; import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket; +import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket; +import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import kamkeel.npcs.network.packets.player.AuctionActionPacket; @@ -585,6 +587,8 @@ public class PacketHandler { // Data | Ability Packets DATA_PACKET.registerPacket(new TelegraphSpawnPacket()); DATA_PACKET.registerPacket(new TelegraphRemovePacket()); + DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket()); + DATA_PACKET.registerPacket(new EnergyChargeRemovePacket()); DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket()); DATA_PACKET.registerPacket(new PlayerAbilityStatePacket()); DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index ec351a2c9..938453b31 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -70,4 +70,8 @@ public enum EnumDataPacket { // Auction System AUCTION_DATA, + + // Energy Charge Preview + ENERGY_CHARGE_SPAWN, + ENERGY_CHARGE_REMOVE, } diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java new file mode 100644 index 000000000..e9a3a4db6 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java @@ -0,0 +1,60 @@ +package kamkeel.npcs.network.packets.data.energycharge; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; + +import java.io.IOException; + +/** + * Removes a client-only charging preview entity. + */ +public final class EnergyChargeRemovePacket extends AbstractPacket { + public static final String packetName = "Data|EnergyChargeRemove"; + + private String instanceId; + + public EnergyChargeRemovePacket() { + } + + public EnergyChargeRemovePacket(String instanceId) { + this.instanceId = instanceId; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_CHARGE_REMOVE; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + String id = ByteBufUtils.readString(in); + if (id != null && EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.removePreview(id); + } + } + + public static void sendToTracking(String instanceId, Entity trackingEntity) { + if (instanceId == null || trackingEntity == null) return; + PacketHandler.Instance.sendTracking(new EnergyChargeRemovePacket(instanceId), trackingEntity); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java new file mode 100644 index 000000000..16957aaaa --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java @@ -0,0 +1,108 @@ +package kamkeel.npcs.network.packets.data.energycharge; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +import kamkeel.npcs.entity.EntityEnergyProjectile; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +import java.io.IOException; +import java.lang.reflect.Constructor; + +/** + * Spawns a client-only charging preview entity from serialized spawn data. + */ +public final class EnergyChargeSpawnPacket extends AbstractPacket { + public static final String packetName = "Data|EnergyChargeSpawn"; + + private String instanceId; + private String entityClassName; + private NBTTagCompound spawnNbt; + + public EnergyChargeSpawnPacket() { + } + + public EnergyChargeSpawnPacket(String instanceId, String entityClassName, NBTTagCompound spawnNbt) { + this.instanceId = instanceId; + this.entityClassName = entityClassName; + this.spawnNbt = spawnNbt; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_CHARGE_SPAWN; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + ByteBufUtils.writeString(out, entityClassName); + ByteBufUtils.writeNBT(out, spawnNbt); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + String id = ByteBufUtils.readString(in); + String className = ByteBufUtils.readString(in); + NBTTagCompound nbt = ByteBufUtils.readNBT(in); + + if (id == null || className == null || nbt == null) return; + if (EnergyChargePreviewManager.ClientInstance == null) return; + + World world = Minecraft.getMinecraft().theWorld; + if (world == null) return; + + EntityEnergyProjectile preview = createPreviewEntity(className, world); + if (preview == null) return; + + preview.setPreviewMode(true); + preview.importSpawnNBT(nbt); + + // Ensure the preview follows current owner immediately on first render frame. + Entity owner = preview.getOwnerEntity(); + if (owner instanceof net.minecraft.entity.EntityLivingBase) { + preview.setPreviewOwner((net.minecraft.entity.EntityLivingBase) owner); + } + + EnergyChargePreviewManager.ClientInstance.addPreview(id, preview); + } + + @SideOnly(Side.CLIENT) + private static EntityEnergyProjectile createPreviewEntity(String className, World world) { + try { + Class clazz = Class.forName(className); + if (!EntityEnergyProjectile.class.isAssignableFrom(clazz)) { + return null; + } + + Constructor constructor = clazz.getConstructor(World.class); + Object instance = constructor.newInstance(world); + return (EntityEnergyProjectile) instance; + } catch (Exception ignored) { + return null; + } + } + + public static void sendToTracking(String instanceId, EntityEnergyProjectile previewEntity, Entity trackingEntity) { + if (previewEntity == null || trackingEntity == null) return; + String className = previewEntity.getClass().getName(); + NBTTagCompound nbt = previewEntity.exportSpawnNBT(); + PacketHandler.Instance.sendTracking(new EnergyChargeSpawnPacket(instanceId, className, nbt), trackingEntity); + } +} diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index a221e35ae..180a93194 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -14,9 +14,11 @@ import kamkeel.npcs.client.renderer.RenderAbilitySweeper; import kamkeel.npcs.client.renderer.RenderAbilityZone; import kamkeel.npcs.client.renderer.RenderAbilityDome; import kamkeel.npcs.client.renderer.RenderAbilityPanel; +import kamkeel.npcs.client.renderer.EnergyChargePreviewRenderer; import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.client.renderer.lightning.LightningHandler; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; @@ -356,6 +358,10 @@ public class ClientProxy extends CommonProxy { TelegraphManager.initClient(); MinecraftForge.EVENT_BUS.register(new TelegraphRenderer()); + // Energy charge preview rendering system + EnergyChargePreviewManager.initClient(); + MinecraftForge.EVENT_BUS.register(new EnergyChargePreviewRenderer()); + // Lightning effect rendering system MinecraftForge.EVENT_BUS.register(new LightningHandler()); diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 58d6f5a3f..3a9416563 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -8,6 +8,7 @@ import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.client.renderer.lightning.LightningBolt; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.data.RequestProperSpawnData; @@ -62,6 +63,9 @@ public class ClientTickHandler { ClientCacheHandler.clearCache(); ClientAbilityState.reset(); RequestProperSpawnData.clear(); + if (EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.clear(); + } } this.prevWorld = mc.theWorld; } @@ -164,6 +168,11 @@ public class ClientTickHandler { TelegraphManager.ClientInstance.tick(mc.theWorld); } + // Tick packet-driven energy charge previews + if (EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.tick(mc.theWorld); + } + // Update lightning bolts for ability effects LightningBolt.updateAll(); diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 4834504b3..5c759d59c 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -1834,12 +1834,15 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti try { readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { - RequestProperSpawnData.reportMissingData(this); + if(this.worldObj != null && this.worldObj.isRemote){ + RequestProperSpawnData.reportMissingData(this); + } } } public void readSpawnData(NBTTagCompound compound) { - if ((compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { + if (this.worldObj != null && this.worldObj.isRemote && + (compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { RequestProperSpawnData.reportMissingData(this); return; } From f58a6010d90643c0e8a3a2474095ca773ab0422c Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 03:28:56 -0500 Subject: [PATCH 41/71] Reorganize --- .../kamkeel/npcs/command/AbilityCommand.java | 4 +- .../npcs/controllers/AbilityController.java | 13 +++-- .../npcs/controllers/SyncController.java | 2 +- .../controllers/data/ability/Ability.java | 37 ++++++++++----- .../data/ability/AbilityAction.java | 2 + .../data/ability/ConcurrentSlot.java | 1 + .../controllers/data/ability/LockType.java | 47 ------------------- .../ability/conditions/AbilityCondition.java | 2 +- .../ability/{ => conditions}/Condition.java | 2 +- .../ability/conditions/ConditionFilter.java | 2 - .../conditions/ConditionHasEffect.java | 2 +- .../conditions/ConditionQuestCompleted.java | 2 +- .../ability/{ => data}/AbilityIconData.java | 3 +- .../ability/{ => data}/ChainedAbility.java | 5 +- .../ability/{ => data}/IAbilityAction.java | 5 +- .../data/ability/data/ProjectileData.java | 4 +- .../effect}/AbilityCustomEffect.java | 2 +- .../effect}/AbilityPotionEffect.java | 2 +- .../{ => data/effect}/IEffectAction.java | 2 +- .../data/{ => energy}/EnergyAnchorData.java | 4 +- .../data/{ => energy}/EnergyBarrierData.java | 2 +- .../data/{ => energy}/EnergyChargingData.java | 2 +- .../data/{ => energy}/EnergyCombatData.java | 4 +- .../data/{ => energy}/EnergyDisplayData.java | 2 +- .../data/{ => energy}/EnergyHomingData.java | 2 +- .../data/{ => energy}/EnergyLifespanData.java | 2 +- .../{ => energy}/EnergyLightningData.java | 2 +- .../data/{ => energy}/EnergyPanelData.java | 2 +- .../{ => energy}/EnergyTrajectoryData.java | 2 +- .../entry}/AbilityEffectActionEntry.java | 3 +- .../entry/AbilityToggleEntry.java} | 8 ++-- .../{ => data/entry}/ChainedAbilityEntry.java | 4 +- .../ability/{ => enums}/AbilityPhase.java | 2 +- .../data/ability/{ => enums}/AnchorPoint.java | 2 +- .../data/ability/{ => enums}/HitType.java | 2 +- .../LockMode.java} | 10 ++-- .../ability/{ => enums}/RotationMode.java | 4 +- .../ability/{ => enums}/TargetFilter.java | 2 +- .../ability/{ => enums}/TargetingMode.java | 2 +- .../data/ability/{ => enums}/UserType.java | 2 +- .../{ => extender}/IAbilityExtender.java | 4 +- .../data/ability/gui/AbilityFieldBuilder.java | 8 ++-- .../data/ability/gui/AbilityFieldDefs.java | 6 +-- .../ability/gui}/GuiAbilityInterface.java | 6 ++- .../{ => gui}/IAbilityFieldProvider.java | 3 +- .../IChainedAbilityFieldProvider.java | 3 +- .../preview}/AbilityPreviewExecutor.java | 10 ++-- .../{ => preview}/PreviewEntityHandler.java | 2 +- .../data/ability/type/AbilityCharge.java | 10 ++-- .../data/ability/type/AbilityCutter.java | 10 ++-- .../data/ability/type/AbilityDash.java | 6 +-- .../data/ability/type/AbilityEffect.java | 14 +++--- .../data/ability/type/AbilityGuard.java | 10 ++-- .../data/ability/type/AbilityHazard.java | 2 +- .../data/ability/type/AbilityHeavyHit.java | 10 ++-- .../data/ability/type/AbilityProjectile.java | 6 +-- .../data/ability/type/AbilityShockwave.java | 10 ++-- .../data/ability/type/AbilitySlam.java | 10 ++-- .../data/ability/type/AbilityTeleport.java | 8 ++-- .../data/ability/type/AbilityTrap.java | 4 +- .../data/ability/type/AbilityVortex.java | 10 ++-- .../data/ability/type/AbilityZone.java | 12 ++--- .../ability/type/energy/AbilityBarrier.java | 4 +- .../data/ability/type/energy/AbilityBeam.java | 18 +++---- .../data/ability/type/energy/AbilityDisc.java | 14 +++--- .../data/ability/type/energy/AbilityDome.java | 10 ++-- .../ability/type/energy/AbilityEnergy.java | 4 +- .../type/energy/AbilityEnergyProjectile.java | 16 +++---- .../ability/type/energy/AbilityLaserShot.java | 20 ++++---- .../data/ability/type/energy/AbilityOrb.java | 16 +++---- .../ability/type/energy/AbilityShield.java | 14 +++--- .../ability/type/energy/AbilitySlicer.java | 14 +++--- .../ability/type/energy/AbilitySweeper.java | 8 ++-- .../data/ability/type/energy/AbilityWall.java | 12 ++--- .../{ => util}/AbilityTargetHelper.java | 5 +- .../npcs/entity/EntityAbilityBarrier.java | 4 +- .../npcs/entity/EntityAbilityBeam.java | 14 +++--- .../npcs/entity/EntityAbilityDisc.java | 14 +++--- .../npcs/entity/EntityAbilityDome.java | 6 +-- .../npcs/entity/EntityAbilityLaser.java | 12 ++--- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 14 +++--- .../npcs/entity/EntityAbilityPanel.java | 8 ++-- .../npcs/entity/EntityAbilitySweeper.java | 3 +- .../npcs/entity/EntityAbilityZone.java | 6 +-- .../npcs/entity/EntityEnergyAbility.java | 4 +- .../npcs/entity/EntityEnergyProjectile.java | 20 ++++---- .../npcs/entity/EntityEnergySlicer.java | 14 +++--- .../ability/AbilityHotbarSelectPacket.java | 4 -- .../ability/ChainedAbilityGetPacket.java | 2 +- .../ability/ChainedAbilitySavePacket.java | 2 +- .../kamkeel/npcs/util/AnchorPointHelper.java | 4 +- src/main/java/kamkeel/npcs/util/Register.java | 2 +- .../noppes/npcs/AbstractDataAbilities.java | 34 +++++++------- src/main/java/noppes/npcs/DataAbilities.java | 11 ++--- .../client/gui/advanced/GuiNPCAbilities.java | 4 +- .../advanced/SubGuiChainedAbilityConfig.java | 10 ++-- .../gui/global/GuiNpcManageAbilities.java | 6 +-- .../hud/ability/AbilityHotbarComponent.java | 2 +- .../ability/AbilityHotbarSlotRenderer.java | 4 +- .../client/gui/hud/ability/AbilityIcon.java | 6 +-- .../gui/player/inventory/GuiAbilities.java | 4 +- .../inventory/SubGuiSelectAbilityForSlot.java | 2 +- .../util/IChainedAbilityConfigCallback.java | 2 +- .../controllers/data/PlayerAbilityData.java | 11 ++--- .../data/PlayerAbilityHotbarData.java | 1 - .../java/noppes/npcs/items/ItemStaff.java | 16 +++---- 106 files changed, 377 insertions(+), 392 deletions(-) delete mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => conditions}/Condition.java (99%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data}/AbilityIconData.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data}/ChainedAbility.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data}/IAbilityAction.java (84%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data/effect}/AbilityCustomEffect.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data/effect}/AbilityPotionEffect.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data/effect}/IEffectAction.java (95%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyAnchorData.java (95%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyBarrierData.java (99%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyChargingData.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyCombatData.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyDisplayData.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyHomingData.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyLifespanData.java (96%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyLightningData.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyPanelData.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/data/{ => energy}/EnergyTrajectoryData.java (99%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data/entry}/AbilityEffectActionEntry.java (95%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ToggleEntry.java => data/entry/AbilityToggleEntry.java} (79%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => data/entry}/ChainedAbilityEntry.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/AbilityPhase.java (85%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/AnchorPoint.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/HitType.java (92%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{LockMovementType.java => enums/LockMode.java} (87%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/RotationMode.java (92%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/TargetFilter.java (92%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/TargetingMode.java (93%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => enums}/UserType.java (90%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => extender}/IAbilityExtender.java (96%) rename src/main/java/{noppes/npcs/client/gui/util => kamkeel/npcs/controllers/data/ability/gui}/GuiAbilityInterface.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => gui}/IAbilityFieldProvider.java (89%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => gui}/IChainedAbilityFieldProvider.java (88%) rename src/main/java/{noppes/npcs/client/gui/util => kamkeel/npcs/controllers/data/ability/preview}/AbilityPreviewExecutor.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => preview}/PreviewEntityHandler.java (86%) rename src/main/java/kamkeel/npcs/controllers/data/ability/{ => util}/AbilityTargetHelper.java (95%) diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 9be809697..f40c266cb 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -2,8 +2,8 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; import noppes.npcs.NoppesUtilServer; diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 55baf60e6..9a55175df 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -1,12 +1,21 @@ package kamkeel.npcs.controllers; -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.conditions.ConditionHasEffect; import kamkeel.npcs.controllers.data.ability.conditions.ConditionQuestCompleted; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.extender.IAbilityExtender; +import kamkeel.npcs.controllers.data.ability.gui.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; import kamkeel.npcs.controllers.data.ability.type.AbilityDash; @@ -14,7 +23,6 @@ import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.ability.type.AbilityHazard; import kamkeel.npcs.controllers.data.ability.type.AbilityEffect; import kamkeel.npcs.controllers.data.ability.type.AbilityHeavyHit; -import kamkeel.npcs.controllers.data.ability.type.AbilityProjectile; import kamkeel.npcs.controllers.data.ability.type.AbilityShockwave; import kamkeel.npcs.controllers.data.ability.type.AbilitySlam; import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport; @@ -26,7 +34,6 @@ import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaserShot; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityOrb; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield; -import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySlicer; import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index af7366a3f..f27428807 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -6,7 +6,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import kamkeel.npcs.addon.DBCAddon; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.LargeAbstractPacket; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSyncAction; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 9d61188c5..8bafe49f0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -3,6 +3,17 @@ package kamkeel.npcs.controllers.data.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.preview.PreviewEntityHandler; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.gui.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.enums.RotationMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -76,9 +87,9 @@ public abstract class Ability implements IAbility, IAbilityAction { protected boolean interruptible = true; // Feedback - protected LockMovementType lockMovement = LockMovementType.WINDUP; + protected LockMode lockMovement = LockMode.WINDUP; protected RotationMode rotationMode = RotationMode.FREE; - protected LockMovementType rotationPhase = LockMovementType.WINDUP_AND_ACTIVE; + protected LockMode rotationPhase = LockMode.WINDUP_AND_ACTIVE; protected int windUpColor = 0x80FF4400; // Telegraph color during wind up protected int activeColor = 0xC0FF0000; // Telegraph warning/active color @@ -637,10 +648,10 @@ public abstract class Ability implements IAbility, IAbilityAction { .tab("General").hover("ability.hover.freeOnCast")); } defs.add(FieldDef.section("ability.section.movement").tab("General")); - defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMovementType.getDisplayKeys(), + defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMode.getDisplayKeys(), () -> this.getLockMovement().getDisplayKey(), v -> { - for (LockMovementType t : LockMovementType.values()) { + for (LockMode t : LockMode.values()) { if (t.getDisplayKey().equals(v)) { this.setLockMovement(t); break; @@ -664,7 +675,7 @@ public abstract class Ability implements IAbility, IAbilityAction { FieldDef.stringEnumField("ability.rotationPhase", getRotationPhaseKeys(), () -> this.getRotationPhase().getDisplayKey(), v -> { - for (LockMovementType t : LockMovementType.values()) { + for (LockMode t : LockMode.values()) { if (t.getDisplayKey().equals(v)) { this.setRotationPhase(t); break; @@ -1400,9 +1411,9 @@ public abstract class Ability implements IAbility, IAbilityAction { syncWindupWithAnimation = nbt.hasKey("syncWindup") ? nbt.getBoolean("syncWindup") : true; dazedTicks = nbt.hasKey("recovery") ? nbt.getInteger("recovery") : 80; interruptible = nbt.hasKey("interruptible") ? nbt.getBoolean("interruptible") : true; - lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement")); + lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement")); rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode")); - rotationPhase = LockMovementType.fromOrdinal(nbt.getInteger("rotationPhase")); + rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase")); windUpColor = nbt.getInteger("windUpColor"); activeColor = nbt.getInteger("activeColor"); windUpSound = nbt.getString("windUpSound"); @@ -1653,11 +1664,11 @@ public abstract class Ability implements IAbility, IAbilityAction { this.interruptible = interruptible; } - public LockMovementType getLockMovement() { + public LockMode getLockMovement() { return lockMovement; } - public void setLockMovement(LockMovementType lockMovement) { + public void setLockMovement(LockMode lockMovement) { this.lockMovement = lockMovement; } @@ -1669,11 +1680,11 @@ public abstract class Ability implements IAbility, IAbilityAction { this.rotationMode = rotationMode; } - public LockMovementType getRotationPhase() { + public LockMode getRotationPhase() { return rotationPhase; } - public void setRotationPhase(LockMovementType rotationPhase) { + public void setRotationPhase(LockMode rotationPhase) { this.rotationPhase = rotationPhase; } @@ -1694,7 +1705,7 @@ public abstract class Ability implements IAbility, IAbilityAction { */ @Override public void setLockMovementType(int type) { - this.lockMovement = LockMovementType.fromOrdinal(type); + this.lockMovement = LockMode.fromOrdinal(type); } /** @@ -1734,7 +1745,7 @@ public abstract class Ability implements IAbility, IAbilityAction { */ @Override public void setRotationPhaseType(int type) { - this.rotationPhase = LockMovementType.fromOrdinal(type); + this.rotationPhase = LockMode.fromOrdinal(type); } /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java index 60a64369d..aadca44a3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -1,6 +1,8 @@ package kamkeel.npcs.controllers.data.ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java index cdfead9c0..af9e31ba0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import net.minecraft.entity.EntityLivingBase; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java deleted file mode 100644 index 904a6c876..000000000 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java +++ /dev/null @@ -1,47 +0,0 @@ -package kamkeel.npcs.controllers.data.ability; - -/** - * Defines what an ability locks when Lock Position is enabled. - */ -public enum LockType { - MOVEMENT, - ROTATION, - BOTH; - - public boolean locksMovement() { - return this == MOVEMENT || this == BOTH; - } - - public boolean locksRotation() { - return this == ROTATION || this == BOTH; - } - - public String getDisplayKey() { - switch (this) { - case MOVEMENT: - return "ability.lockType.movement"; - case ROTATION: - return "ability.lockType.rotation"; - case BOTH: - return "ability.lockType.both"; - default: - return "ability.lockType.both"; - } - } - - public static String[] getDisplayKeys() { - return new String[]{ - "ability.lockType.movement", - "ability.lockType.rotation", - "ability.lockType.both" - }; - } - - public static LockType fromOrdinal(int ordinal) { - LockType[] values = values(); - if (ordinal >= 0 && ordinal < values.length) { - return values[ordinal]; - } - return BOTH; - } -} 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 479776930..5b3fc38ea 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 @@ -3,7 +3,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java index 1183fe858..2dbb0d6c0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.conditions; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java index 6c8e0c24e..08c5b5da2 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java @@ -1,7 +1,5 @@ package kamkeel.npcs.controllers.data.ability.conditions; -import kamkeel.npcs.controllers.data.ability.UserType; - public enum ConditionFilter { CASTER, TARGET, diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java index bf98c73c9..eca7aded0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java index d47e19506..263cd28af 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java index 2120e9ca6..77dd746ea 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; +import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java index 5a80166c7..f9927d42e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java @@ -1,6 +1,9 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java similarity index 84% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java index 612cd9145..59a296f7b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java @@ -1,5 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java index 94850470f..acb16a2a9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java @@ -1,6 +1,8 @@ package kamkeel.npcs.controllers.data.ability.data; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java index 22428b704..4ded168d5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java index cb2984946..7fe6422f0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java index 2f0323c76..b674bd51f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java index 99c51b23c..b4ceb830f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java @@ -1,6 +1,6 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyAnchorData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java index d54f205cc..130b7d2ba 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java index c12cec47f..ec53aaeb5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java index e6026e18e..b494a8b15 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java @@ -1,6 +1,6 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; -import kamkeel.npcs.controllers.data.ability.HitType; +import kamkeel.npcs.controllers.data.ability.enums.HitType; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyCombatData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java index 6363a69cb..f15f31b6d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyDisplayData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java index 4f75c0841..a0825fac1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyHomingData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java similarity index 96% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java index 2c8d1f6a1..fcb516242 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyLifespanData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java index b8423e7aa..72a412cfd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyLightningData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java index 3a5206f21..c57e0bbf5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java index 10014fb5f..f888472d7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.Constants; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java index 245d153b2..6540e1f5e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java @@ -1,6 +1,7 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java similarity index 79% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java index 4a758bead..d200fefa6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java @@ -1,4 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; + +import kamkeel.npcs.controllers.data.ability.Ability; /** * Tracks the state of a single active toggle for an entity. @@ -7,12 +9,12 @@ package kamkeel.npcs.controllers.data.ability; * State is 1-based: state 1 = first ON state, state 2 = second, etc. * State 0 means OFF (entry should not exist in the map). */ -public class ToggleEntry { +public class AbilityToggleEntry { private final Ability ability; private int tickCount; private int state; - public ToggleEntry(Ability ability, int state) { + public AbilityToggleEntry(Ability ability, int state) { this.ability = ability; this.tickCount = 0; this.state = Math.max(1, state); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java index a5bb7571b..85fa819ff 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java @@ -1,6 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java similarity index 85% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java index af1bd8d30..a0549b09a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; public enum AbilityPhase { IDLE, // Not executing diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java index 959aa2cf4..567713d34 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines anchor points for ability charging effects. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java similarity index 92% rename from src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java index 3892e9362..ad1f07d0e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/HitType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Shared hit behavior for energy projectiles. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java similarity index 87% rename from src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java index 1b81a3bb3..47d7b1d1d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java @@ -1,9 +1,9 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines when an ability locks NPC movement and rotation. */ -public enum LockMovementType { +public enum LockMode { /** * No movement lock - NPC can move freely during both phases. */ @@ -71,10 +71,10 @@ public enum LockMovementType { } /** - * Get LockMovementType from ordinal, with fallback to WINDUP. + * Get LockMode from ordinal, with fallback to WINDUP. */ - public static LockMovementType fromOrdinal(int ordinal) { - LockMovementType[] values = values(); + public static LockMode fromOrdinal(int ordinal) { + LockMode[] values = values(); if (ordinal >= 0 && ordinal < values.length) { return values[ordinal]; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java similarity index 92% rename from src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java index e1142b571..f8f1ec860 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java @@ -1,8 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines what happens to NPC rotation during ability phases. - * Used with a LockMovementType to control WHEN it applies. + * Used with a LockMode to control WHEN it applies. */ public enum RotationMode { /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java similarity index 92% rename from src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java index 0247cc170..91d5b2434 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Filter for determining which entities an ability should affect in AOE mode. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java similarity index 93% rename from src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java index 7e84c6a14..b32b04c9a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines how an ability selects its target(s). diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java similarity index 90% rename from src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java index d68693f59..7055174f8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines which entity types can use an ability. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java b/src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java similarity index 96% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java index f3e7ccfd8..7f76919cb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java @@ -1,5 +1,7 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.extender; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.entity.EntityLivingBase; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java index 4aa9d54ae..0a05bc3d6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.gui; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.gui.advanced.SubGuiCustomEffectSelect; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java index d34620c75..3bbd80f9b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java @@ -2,9 +2,9 @@ package kamkeel.npcs.controllers.data.ability.gui; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.builder.FieldType; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java similarity index 98% rename from src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java index 4bf2ffd58..0ff9b31f1 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java @@ -1,4 +1,4 @@ -package noppes.npcs.client.gui.util; +package kamkeel.npcs.controllers.data.ability.gui; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -11,6 +11,8 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.client.ClientEventHandler; +import noppes.npcs.client.gui.util.GuiNPCInterface2; +import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.entity.EntityNPCInterface; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; @@ -40,7 +42,7 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { /** * NPC facing direction - affects anchor points and visual facing */ - protected static float NPC_FACING_YAW = 160.0f; + public static float NPC_FACING_YAW = 160.0f; /** * Camera pitch (slight downward angle) */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java similarity index 89% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java index f196b307b..615234a7c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.gui; +import kamkeel.npcs.controllers.data.ability.Ability; import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java similarity index 88% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java index 95e8b70e0..8eec036f5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.gui; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; diff --git a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java similarity index 98% rename from src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java index bde8ae194..930a1a198 100644 --- a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java @@ -1,16 +1,16 @@ -package noppes.npcs.client.gui.util; +package kamkeel.npcs.controllers.data.ability.preview; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; -import kamkeel.npcs.controllers.data.ability.PreviewEntityHandler; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import kamkeel.npcs.controllers.data.ability.gui.GuiAbilityInterface; import noppes.npcs.controllers.data.Animation; import noppes.npcs.controllers.data.AnimationData; import noppes.npcs.entity.EntityCustomNpc; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java similarity index 86% rename from src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java index 13c8bc526..d5917260c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.preview; import net.minecraft.entity.Entity; 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 4f7021705..c5aedfb39 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 @@ -2,10 +2,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -43,7 +43,7 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; this.minRange = 4.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 20; // LINE telegraph showing charge path diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java index 6db0f832e..c7b88961e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -71,7 +71,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter { this.name = "Cutter"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 8.0f; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; this.cooldownTicks = 0; this.windUpTicks = 20; this.telegraphType = TelegraphType.CONE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index b5342e3d8..fb228fc3e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -2,8 +2,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -98,7 +98,7 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; this.cooldownTicks = 0; this.windUpTicks = 5; // No telegraph for dash - it's a quick evasive move diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java index d4f9c474b..9a1a2c180 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java @@ -4,13 +4,13 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -73,7 +73,7 @@ public class AbilityEffect extends Ability implements IAbilityEffect { this.typeId = "ability.cnpc.effect"; this.name = "Effect"; this.targetingMode = TargetingMode.SELF; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.NONE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java index 1a1555f11..5224f5bc7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -52,7 +52,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard { this.typeId = "ability.cnpc.guard"; this.name = "Guard"; this.targetingMode = TargetingMode.SELF; - this.lockMovement = LockMovementType.ACTIVE; + this.lockMovement = LockMode.ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 0; this.interruptible = false; // Hard to interrupt while guarding diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index a9c505a56..64638510a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java index 7e61f04c0..03299a67f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -38,7 +38,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit { this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 5.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.LINE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java index 1aba9ff3c..2c9945299 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -42,7 +42,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { this.maxRange = 20.0f; this.cooldownTicks = 0; this.windUpTicks = 15; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; // No telegraph for projectile - it's a ranged attack this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index d600e006d..b04dd2766 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -35,7 +35,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { this.name = "Shockwave"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 8.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 25; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index 079bcbf0f..84afb11b6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -68,7 +68,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam { this.targetingMode = TargetingMode.AOE_SELF; // Can also be AOE_TARGET to leap to target this.windUpTicks = 30; this.cooldownTicks = 0; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.minRange = 2.0f; this.maxRange = 15.0f; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index cc2deb4ea..1319e07ca 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -3,9 +3,9 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.block.Block; @@ -78,7 +78,7 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 10; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; // No telegraph for teleport - it's instant repositioning this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index 64e3e5b46..acd0e1378 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -2,8 +2,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index 59d2a280f..c20871dd6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -54,7 +54,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex { this.name = "Vortex"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 15.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java index a1d1af290..968ff03ba 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java @@ -3,11 +3,11 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.entity.EntityAbilityZone; @@ -65,7 +65,7 @@ public abstract class AbilityZone extends Ability { this.colorData = defaultColors; this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.allowedBy = UserType.BOTH; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index 841750d1c..980a1dfeb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.entity.EntityAbilityBarrier; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; 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 0feda043a..a529ac488 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 @@ -3,13 +3,13 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.RotationMode; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.RotationMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -47,9 +47,9 @@ public class AbilityBeam extends AbilityEnergyProjectile impl this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 40; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.rotationMode = RotationMode.LOCKED; - this.rotationPhase = LockMovementType.ACTIVE; + this.rotationPhase = LockMode.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/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index 1a9194282..bc5883906 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -3,12 +3,12 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -49,7 +49,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 60; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Disc_Windup"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index 1663f2447..99b431477 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityDome; @@ -37,7 +37,7 @@ public class AbilityDome extends AbilityBarrier { this.targetingMode = TargetingMode.SELF; this.cooldownTicks = 100; this.windUpTicks = 30; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.RING; this.showTelegraph = true; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java index 59917889e..461f3b50d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 3cb81720d..cfe9457a6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -2,14 +2,14 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.HitType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.HitType; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index dafbc2200..ba1f08fba 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -2,14 +2,14 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.RotationMode; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.RotationMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; @@ -49,9 +49,9 @@ public class AbilityLaserShot extends AbilityEnergyProjectile implem this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 30; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Orb_Windup"; @@ -117,7 +117,7 @@ public class AbilityOrb extends AbilityEnergyProjectile implem orb.setKnockbackUp(0.0f); orb.setProjectileCount(2); orb.setFireDelay(2); - a.setLockMovement(LockMovementType.WINDUP_AND_ACTIVE); + a.setLockMovement(LockMode.WINDUP_AND_ACTIVE); a.setMaxRange(75.0f); a.setBurstEnabled(true); a.setBurstAmount(15); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java index 2d9c515a4..f7cbaa8fd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java @@ -3,12 +3,12 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityPanel; @@ -39,7 +39,7 @@ public class AbilityShield extends AbilityBarrier { this.targetingMode = TargetingMode.SELF; this.cooldownTicks = 60; this.windUpTicks = 10; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java index 4d7a54b65..ca9cbc7eb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java @@ -3,12 +3,12 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -45,7 +45,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { this.minRange = 3.0f; this.cooldownTicks = 40; this.windUpTicks = 15; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.LINE; this.showTelegraph = true; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index 2cc64bde4..942036d41 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -2,9 +2,9 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -49,7 +49,7 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 15.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 60; // Circle telegraph to show range diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index 5237485b8..1d820851d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -3,11 +3,11 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityPanel; @@ -40,7 +40,7 @@ public class AbilityWall extends AbilityBarrier { this.minRange = 2.0f; this.cooldownTicks = 80; this.windUpTicks = 20; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.LINE; this.showTelegraph = true; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java index b45bd2b02..7f4c772c7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.util; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -84,7 +85,7 @@ public class AbilityTargetHelper { * @return true if the target should be affected */ public static boolean shouldAffect(EntityLivingBase caster, Entity target, - TargetFilter filter, boolean includeSelf) { + TargetFilter filter, boolean includeSelf) { if (target == caster) return includeSelf; if (!(target instanceof EntityLivingBase)) return false; if (!target.isEntityAlive()) return false; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java index 02fbbdc4e..f1309bb97 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java @@ -1,7 +1,7 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayerMP; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index d040d87e4..ff0de17df 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -1,12 +1,12 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 16a1423f0..556f4e68c 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -1,12 +1,12 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java index 149475d0d..8c0fc8fae 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java @@ -2,9 +2,9 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 67440bf07..aef9a7adb 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -1,11 +1,11 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 6a12bf9ab..c203c61a5 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -1,12 +1,12 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index f1a89012e..582151239 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -2,10 +2,10 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index f08ffeb2a..7efeec93b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -2,7 +2,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -10,7 +10,6 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; -import noppes.npcs.LogWriter; import noppes.npcs.NpcDamageSource; import noppes.npcs.controllers.PartyController; import noppes.npcs.controllers.data.Party; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 129b87495..a3c034fcb 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -6,9 +6,9 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.TargetFilter; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index 3b1bc0cc1..01b39f80d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -5,8 +5,8 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index fae432710..718fe580f 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -3,16 +3,16 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.HitType; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.HitType; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 4a8c3fbc2..b8b636171 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -1,12 +1,12 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java index 7a4e183a2..6b6262e8b 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java @@ -1,9 +1,6 @@ package kamkeel.npcs.network.packets.player.ability; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; @@ -11,7 +8,6 @@ import kamkeel.npcs.network.enums.EnumPlayerPacket; import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.player.EntityPlayer; import noppes.npcs.controllers.PlayerDataController; -import noppes.npcs.controllers.data.AbilityHotbarData; import noppes.npcs.controllers.data.PlayerData; import java.io.IOException; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java index da8ca9d9f..2bcc48601 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java index ca9925bbf..e4564e8f0 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java index 6a1a17c76..2da72ac7c 100644 --- a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java +++ b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java @@ -1,7 +1,7 @@ package kamkeel.npcs.util; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.Vec3; diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index 75184778d..a01ef84c1 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -3,7 +3,7 @@ package kamkeel.npcs.util; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index b63481c21..a2b94fdc9 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -2,11 +2,11 @@ package noppes.npcs; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.ConcurrentSlot; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; @@ -296,7 +296,7 @@ public abstract class AbstractDataAbilities { /** * Currently active toggles. Key = ability key, Value = toggle entry with state and tick counter. */ - protected Map activeToggles = new LinkedHashMap<>(); + protected Map activeToggles = new LinkedHashMap<>(); /** * Cycle a toggle ability to its next state. @@ -306,7 +306,7 @@ public abstract class AbstractDataAbilities { * @return The new state (0 = off, 1+ = active state number) */ public int toggleAbility(String key) { - ToggleEntry existing = activeToggles.get(key); + AbilityToggleEntry existing = activeToggles.get(key); if (existing != null) { int currentState = existing.getState(); int maxStates = existing.getAbility().getToggleStates(); @@ -326,7 +326,7 @@ public abstract class AbstractDataAbilities { * @return 0 if not active, 1+ for active state */ public int getToggleState(String key) { - ToggleEntry entry = activeToggles.get(key); + AbilityToggleEntry entry = activeToggles.get(key); return entry != null ? entry.getState() : 0; } @@ -340,7 +340,7 @@ public abstract class AbstractDataAbilities { } return; } - ToggleEntry existing = activeToggles.get(key); + AbilityToggleEntry existing = activeToggles.get(key); if (existing != null) { int currentState = existing.getState(); if (currentState != state) { @@ -373,7 +373,7 @@ public abstract class AbstractDataAbilities { if (fireToggleEvent(ability, 0, state)) return false; - ToggleEntry entry = new ToggleEntry(ability, state); + AbilityToggleEntry entry = new AbilityToggleEntry(ability, state); activeToggles.put(key, entry); ability.onToggleOn(getEntity()); ability.onToggleStateChanged(getEntity(), 0, state); @@ -381,7 +381,7 @@ public abstract class AbstractDataAbilities { return true; } - private int cycleToggleState(String key, ToggleEntry entry, int oldState, int newState) { + private int cycleToggleState(String key, AbilityToggleEntry entry, int oldState, int newState) { Ability ability = entry.getAbility(); if (newState < 1 || newState > ability.getToggleStates()) return oldState; @@ -394,7 +394,7 @@ public abstract class AbstractDataAbilities { } private void deactivateToggle(String key) { - ToggleEntry entry = activeToggles.get(key); + AbilityToggleEntry entry = activeToggles.get(key); if (entry == null) return; int oldState = entry.getState(); @@ -418,8 +418,8 @@ public abstract class AbstractDataAbilities { EntityLivingBase entity = getEntity(); List toRemove = null; - for (Map.Entry mapEntry : activeToggles.entrySet()) { - ToggleEntry entry = mapEntry.getValue(); + for (Map.Entry mapEntry : activeToggles.entrySet()) { + AbilityToggleEntry entry = mapEntry.getValue(); entry.incrementTick(); if (entry.getAbility().hasActiveToggle()) { @@ -441,7 +441,7 @@ public abstract class AbstractDataAbilities { if (toRemove != null) { for (String key : toRemove) { - ToggleEntry entry = activeToggles.remove(key); + AbilityToggleEntry entry = activeToggles.remove(key); if (entry != null) { int oldState = entry.getState(); entry.getAbility().onToggleOff(entity); @@ -483,8 +483,8 @@ public abstract class AbstractDataAbilities { protected void clearActiveToggles() { if (activeToggles.isEmpty()) return; EntityLivingBase entity = getEntity(); - for (Map.Entry mapEntry : activeToggles.entrySet()) { - ToggleEntry entry = mapEntry.getValue(); + for (Map.Entry mapEntry : activeToggles.entrySet()) { + AbilityToggleEntry entry = mapEntry.getValue(); entry.getAbility().onToggleOff(entity); entry.getAbility().onToggleStateChanged(entity, entry.getState(), 0); } @@ -502,7 +502,7 @@ public abstract class AbstractDataAbilities { Ability ability = AbilityController.Instance != null ? AbilityController.Instance.resolveAbility(key) : null; if (ability != null && ability.isToggleable()) { - activeToggles.put(key, new ToggleEntry(ability, state)); + activeToggles.put(key, new AbilityToggleEntry(ability, state)); } } else { activeToggles.remove(key); diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 548939a54..68d1231d7 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -3,10 +3,10 @@ package noppes.npcs; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; @@ -14,7 +14,6 @@ import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; -import net.minecraft.nbt.NBTTagString; import net.minecraft.util.DamageSource; import noppes.npcs.controllers.data.Animation; import noppes.npcs.entity.EntityNPCInterface; @@ -1095,7 +1094,7 @@ public class DataAbilities extends AbstractDataAbilities { // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (Map.Entry entry : activeToggles.entrySet()) { + for (Map.Entry entry : activeToggles.entrySet()) { NBTTagCompound toggleNbt = new NBTTagCompound(); toggleNbt.setString("Key", entry.getKey()); toggleNbt.setInteger("State", entry.getValue().getState()); diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 85ef3e5d8..23d18ad20 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -4,8 +4,8 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket; 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 804f7e663..11a194d29 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -4,11 +4,11 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; -import kamkeel.npcs.controllers.data.ability.AbilityIconData; -import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index f6562e7a5..95cce7cf9 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -3,7 +3,7 @@ package noppes.npcs.client.gui.global; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket; @@ -24,8 +24,8 @@ import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect; import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect; import noppes.npcs.client.gui.advanced.SubGuiChainedAbilityConfig; import noppes.npcs.client.gui.advanced.SubGuiDuplicateNameConfirm; -import noppes.npcs.client.gui.util.AbilityPreviewExecutor; -import noppes.npcs.client.gui.util.GuiAbilityInterface; +import kamkeel.npcs.controllers.data.ability.preview.AbilityPreviewExecutor; +import kamkeel.npcs.controllers.data.ability.gui.GuiAbilityInterface; import noppes.npcs.client.gui.util.GuiCustomScroll; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index 4b3be2d6a..3bcf1a3c3 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; import net.minecraft.client.Minecraft; diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java index c91ad016b..44d81c441 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java @@ -3,8 +3,8 @@ package noppes.npcs.client.gui.hud.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java index 1371bcc2a..c36d06de2 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java @@ -3,9 +3,9 @@ package noppes.npcs.client.gui.hud.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityIconData; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.renderer.Tessellator; diff --git a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java index 1d0180463..36def1d43 100644 --- a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java @@ -2,8 +2,8 @@ package noppes.npcs.client.gui.player.inventory; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSavePacket; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; diff --git a/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java b/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java index a3d85f7e4..dfef9e88b 100644 --- a/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java +++ b/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java @@ -2,7 +2,7 @@ package noppes.npcs.client.gui.player.inventory; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.ClientCacheHandler; import noppes.npcs.client.gui.util.GuiCustomScroll; diff --git a/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java index f714ea2c7..4584b7072 100644 --- a/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java +++ b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java @@ -1,6 +1,6 @@ package noppes.npcs.client.gui.util; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; /** * Callback for when a chained ability is saved from the config SubGui. diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 79890baeb..18a58783a 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -2,10 +2,10 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket; @@ -23,7 +23,6 @@ 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; @@ -995,7 +994,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (Map.Entry entry : activeToggles.entrySet()) { + for (Map.Entry entry : activeToggles.entrySet()) { NBTTagCompound toggleNbt = new NBTTagCompound(); toggleNbt.setString("Key", entry.getKey()); toggleNbt.setInteger("State", entry.getValue().getState()); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java index f062d891c..abfea96e3 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java @@ -2,7 +2,6 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; diff --git a/src/main/java/noppes/npcs/items/ItemStaff.java b/src/main/java/noppes/npcs/items/ItemStaff.java index 5891df2e1..1f9a45381 100644 --- a/src/main/java/noppes/npcs/items/ItemStaff.java +++ b/src/main/java/noppes/npcs/items/ItemStaff.java @@ -1,13 +1,13 @@ package noppes.npcs.items; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.entity.EntityAbilityOrb; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; From 7b6e378121356628cf7ed9275755a2d5d21e0e84 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 05:41:28 -0500 Subject: [PATCH 42/71] Energy Explosions --- .../renderer/RenderEnergyExplosion.java | 180 ++++++++++++++ .../npcs/entity/EntityEnergyExplosion.java | 126 ++++++++++ .../npcs/entity/EntityEnergyProjectile.java | 224 ++++++++++++++++++ src/main/java/noppes/npcs/CustomNpcs.java | 2 + .../java/noppes/npcs/client/ClientProxy.java | 3 + .../java/noppes/npcs/config/ConfigEnergy.java | 13 + 6 files changed, 548 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java new file mode 100644 index 000000000..7f502c575 --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java @@ -0,0 +1,180 @@ +package kamkeel.npcs.client.renderer; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.entity.EntityEnergyExplosion; +import net.minecraft.entity.Entity; +import org.lwjgl.opengl.GL11; + +/** + * Voxel-style renderer for {@link EntityEnergyExplosion}. + * Draws expanding cubic shells with a short fade-out. + */ +@SideOnly(Side.CLIENT) +public class RenderEnergyExplosion extends RenderEnergyAbility { + + private static final float MAX_OUTER_ALPHA = 0.85f; + private static final float MAX_INNER_ALPHA = 1.0f; + private static final float MAX_SMOKE_CUBE_ALPHA = 0.55f; + + @Override + public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (!(entity instanceof EntityEnergyExplosion)) return; + EntityEnergyExplosion explosion = (EntityEnergyExplosion) entity; + + setupRenderState(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + float radius = explosion.getInterpolatedRadius(partialTicks); + float life = explosion.getLifeProgress(partialTicks); + if (radius > 0.01f && life < 1.0f) { + renderVoxelBurst(explosion, radius, life); + if (explosion.hasLightningEffect()) { + renderAttachedLightning(explosion, 0.55f, Math.max(0.7f, radius * 0.45f)); + } + } + + GL11.glPopMatrix(); + restoreRenderState(); + } + + private void renderVoxelBurst(EntityEnergyExplosion explosion, float radius, float life) { + int outerColor = explosion.getOuterColor(); + int innerColor = explosion.getInnerColor(); + boolean hasOuter = explosion.isOuterColorEnabled(); + + float fade = 1.0f - life; + float outerAlpha = Math.min(MAX_OUTER_ALPHA, (0.25f + fade * 0.75f) * explosion.getOuterColorAlpha()); + float innerAlpha = Math.min(MAX_INNER_ALPHA, (0.30f + fade * 0.80f) * explosion.getInnerAlpha()); + + int steps = Math.max(2, Math.min(6, (int) Math.ceil(radius * 0.65f) + 1)); + float cell = radius / steps; + float cubeHalf = Math.max(0.04f, cell * 0.22f * (0.90f - life * 0.25f)); + long seed = explosion.getRenderSeed(); + + GL11.glDepthMask(false); + renderSmokeVoxelShell(radius, life, seed); + + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue; + + float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f; + float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f; + float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f; + + GL11.glPushMatrix(); + GL11.glTranslatef(px, py, pz); + + if (hasOuter) { + GL11.glPushMatrix(); + float outerScale = 1.0f + explosion.getOuterColorWidth() * 0.35f; + GL11.glScalef(outerScale, outerScale, outerScale); + renderCube(outerColor, outerAlpha * fade, cubeHalf); + GL11.glPopMatrix(); + } + + renderCube(innerColor, innerAlpha * fade, cubeHalf * 0.9f); + GL11.glPopMatrix(); + } + } + } + renderCoreFlash(innerColor, innerAlpha, radius, cubeHalf, life); + GL11.glDepthMask(true); + } + + /** + * Adds large translucent white/gray cubes to fake a smoky volumetric expansion. + */ + private void renderSmokeVoxelShell(float radius, float life, long seed) { + int smokeSteps = Math.max(2, Math.min(5, (int) Math.ceil(radius * 0.5f) + 1)); + float smokeCell = radius / smokeSteps * 1.2f; + float smokeJitter = smokeCell * 0.28f; + float smokeCubeHalf = Math.max(0.07f, smokeCell * 0.24f * (0.95f - life * 0.25f)); + float smokeAlpha = Math.min(MAX_SMOKE_CUBE_ALPHA, (0.15f + (1.0f - life) * 0.55f)); + + for (int ix = -smokeSteps; ix <= smokeSteps; ix++) { + for (int iy = -smokeSteps; iy <= smokeSteps; iy++) { + for (int iz = -smokeSteps; iz <= smokeSteps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge < smokeSteps - 1) continue; + + int h = hash(ix * 5, iy * 7, iz * 11, seed ^ 0x4f1bbcdcL); + if ((h & 3) != 0) continue; + if (life > 0.72f && ((h >>> 5) & 7) > 2) continue; + + float px = ix * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 1) * smokeJitter; + float py = iy * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 2) * smokeJitter; + float pz = iz * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 3) * smokeJitter; + + int smokeColor = ((h >>> 9) & 1) == 0 ? 0xFFFFFF : 0xDCDCDC; + float alpha = smokeAlpha * (0.78f + (((h >>> 10) & 15) / 15.0f) * 0.22f); + + GL11.glPushMatrix(); + GL11.glTranslatef(px, py, pz); + renderCube(smokeColor, alpha, smokeCubeHalf); + GL11.glPopMatrix(); + } + } + } + } + + /** + * White-hot core flash pass for the initial frames. + */ + private void renderCoreFlash(int innerColor, float innerAlpha, float radius, float cubeHalf, float life) { + float flash = saturate((0.60f - life) / 0.60f); + if (flash <= 0.0f) return; + + float coreHalf = Math.max(cubeHalf, radius * 0.07f) * (1.0f + flash * 0.55f); + float whiteAlpha = Math.min(1.0f, 0.25f + flash * 0.95f); + float innerFlashAlpha = Math.min(1.0f, innerAlpha * (0.45f + flash * 0.95f)); + + GL11.glPushMatrix(); + renderCube(0xFFFFFF, whiteAlpha, coreHalf * 1.45f); + GL11.glScalef(0.82f, 0.82f, 0.82f); + renderCube(innerColor, innerFlashAlpha, coreHalf); + GL11.glPopMatrix(); + } + + private float saturate(float value) { + if (value < 0.0f) return 0.0f; + if (value > 1.0f) return 1.0f; + return value; + } + + private boolean shouldRenderVoxel(int ix, int iy, int iz, int steps, long seed, float life) { + int h = hash(ix, iy, iz, seed); + + int keepMask = steps >= 5 ? 3 : 1; + if ((h & keepMask) != 0) return false; + + if (life > 0.55f) { + int decay = (h >>> 3) & 7; + if (decay > 2) return false; + } + + return true; + } + + private float jitter(int ix, int iy, int iz, long seed, int channel) { + int h = hash(ix + channel * 17, iy - channel * 31, iz + channel * 47, seed); + return (((h >>> 8) & 1023) / 1023.0f) - 0.5f; + } + + private int hash(int x, int y, int z, long seed) { + long h = seed; + h ^= (long) x * 73428767L; + h ^= (long) y * 912931L; + h ^= (long) z * 19349663L; + h ^= (h >>> 33); + h *= 0xff51afd7ed558ccdL; + h ^= (h >>> 33); + return (int) h; + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java new file mode 100644 index 000000000..0132ba2d1 --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java @@ -0,0 +1,126 @@ +package kamkeel.npcs.entity; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; + +/** + * Short-lived visual explosion entity for energy projectile impacts. + * This is render-only and never applies damage itself. + */ +public class EntityEnergyExplosion extends EntityEnergyAbility { + + private float maxRadius = 2.0f; + private int durationTicks = 10; + private float prevRenderRadius = 0.0f; + private float renderRadius = 0.0f; + private long renderSeed = 0L; + + public EntityEnergyExplosion(World world) { + super(world); + this.noClip = true; + this.setSize(0.1f, 0.1f); + } + + public EntityEnergyExplosion(World world, EntityEnergyProjectile source, float radius) { + this(world); + + if (source != null) { + this.ownerEntityId = source.getOwnerEntityId(); + EnergyDisplayData sourceDisplay = source.getDisplayData(); + this.displayData = sourceDisplay != null ? sourceDisplay.copy() : new EnergyDisplayData(); + EnergyLightningData sourceLightning = source.getLightningData(); + this.lightningData = sourceLightning != null ? sourceLightning.copy() : new EnergyLightningData(); + this.setPosition(source.posX, source.posY, source.posZ); + } + + setExplosionRadius(radius); + this.renderSeed = world != null ? world.rand.nextLong() : 0L; + } + + @Override + protected void entityInit() { + super.entityInit(); + } + + @Override + public void onUpdate() { + this.prevRenderRadius = this.renderRadius; + + super.onUpdate(); + + float progress = getLifeProgress(0.0f); + float eased = 1.0f - (1.0f - progress) * (1.0f - progress); + this.renderRadius = maxRadius * eased; + + if (ticksExisted >= durationTicks) { + setDead(); + } + } + + public void setExplosionRadius(float radius) { + this.maxRadius = Math.max(0.5f, sanitize(radius, 2.0f, MAX_ENTITY_RADIUS)); + this.durationTicks = Math.max(6, Math.min(18, (int) (6 + this.maxRadius * 1.8f))); + this.setSize(this.maxRadius * 2.0f, this.maxRadius * 2.0f); + } + + public float getInterpolatedRadius(float partialTicks) { + return this.prevRenderRadius + (this.renderRadius - this.prevRenderRadius) * partialTicks; + } + + public float getLifeProgress(float partialTicks) { + if (durationTicks <= 0) return 1.0f; + float age = (ticksExisted + partialTicks) / durationTicks; + return MathHelper.clamp_float(age, 0.0f, 1.0f); + } + + public long getRenderSeed() { + return renderSeed; + } + + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + double d = Math.max(16.0D, maxRadius * 64.0D); + return distance < d * d; + } + + @Override + public boolean canBeCollidedWith() { + return false; + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt) { + // Intentionally empty - transient visual entity. + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt) { + // Intentionally empty - transient visual entity. + } + + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + nbt.setFloat("ExplosionRadius", maxRadius); + nbt.setInteger("ExplosionDuration", durationTicks); + nbt.setLong("ExplosionSeed", renderSeed); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + setExplosionRadius(nbt.hasKey("ExplosionRadius") ? nbt.getFloat("ExplosionRadius") : 2.0f); + this.durationTicks = nbt.hasKey("ExplosionDuration") ? nbt.getInteger("ExplosionDuration") : durationTicks; + if (durationTicks <= 0) durationTicks = 10; + this.renderSeed = nbt.hasKey("ExplosionSeed") ? nbt.getLong("ExplosionSeed") : 0L; + this.renderRadius = 0.0f; + this.prevRenderRadius = 0.0f; + } +} + diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 718fe580f..72d35520c 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -14,19 +14,26 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.MathHelper; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; +import net.minecraft.world.Explosion; import net.minecraft.world.World; +import net.minecraft.world.WorldServer; import noppes.npcs.EventHooks; import noppes.npcs.NpcDamageSource; import noppes.npcs.api.entity.IEntity; +import noppes.npcs.config.ConfigEnergy; import noppes.npcs.controllers.PartyController; import noppes.npcs.controllers.data.Party; import noppes.npcs.controllers.data.PlayerData; @@ -485,6 +492,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (previewMode) return; // Skip explosion in preview mode float explosionRad = getExplosionRadius(); if (Float.isNaN(explosionRad) || explosionRad <= 0) return; + spawnExplosionRenderEntity(explosionRad); + spawnExplosionVisuals(explosionRad); worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); Entity owner = getOwnerEntity(); @@ -511,6 +520,221 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { applyDamage(target, getDamage() * falloff, getKnockback() * falloff); } } + + tryDestroyTerrain(explosionRad); + } + + /** + * Spawn a short-lived voxel render entity so explosions are visible as geometry, + * not only as particles. + */ + protected void spawnExplosionRenderEntity(float explosionRad) { + if (worldObj == null || worldObj.isRemote) return; + float renderRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + EntityEnergyExplosion fx = new EntityEnergyExplosion(worldObj, this, renderRad); + worldObj.spawnEntityInWorld(fx); + } + + /** + * Shared voxel-style explosion VFX for all energy projectile abilities. + */ + protected void spawnExplosionVisuals(float explosionRad) { + if (worldObj == null) return; + + float visualRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + int coreBursts = Math.max(1, Math.min(4, Math.round(visualRad * 0.35f))); + int shellCount = Math.max(1, Math.min(3, Math.round(visualRad / 3.5f) + 1)); + + // Core flash. + emitRandomBurstParticle("largeexplode", coreBursts, visualRad * 0.10, visualRad * 0.08, visualRad * 0.10, 0.01); + + // Expanding voxel shells. + for (int i = 1; i <= shellCount; i++) { + double frac = i / (double) shellCount; + double shellRadius = visualRad * (0.35D + 0.65D * frac); + spawnVoxelShell(shellRadius, i, visualRad); + } + + // Residual smoke volume. + int smokeCount = Math.max(6, Math.min(40, Math.round(visualRad * 4.5f))); + emitRandomBurstParticle("smoke", smokeCount, visualRad * 0.45, visualRad * 0.30, visualRad * 0.45, 0.01); + + // Pull block fragments for stronger voxel feel. + spawnVoxelDebris(visualRad); + } + + /** + * Emit random burst particles centered around this explosion. + */ + protected void emitRandomBurstParticle(String particle, int count, + double spreadX, double spreadY, double spreadZ, + double speed) { + if (worldObj == null || count <= 0 || particle == null || particle.isEmpty()) return; + + if (worldObj.isRemote) { + for (int i = 0; i < count; i++) { + double px = posX + (rand.nextDouble() - 0.5D) * 2.0D * spreadX; + double py = posY + (rand.nextDouble() - 0.5D) * 2.0D * spreadY; + double pz = posZ + (rand.nextDouble() - 0.5D) * 2.0D * spreadZ; + double mx = rand.nextGaussian() * speed; + double my = rand.nextGaussian() * speed; + double mz = rand.nextGaussian() * speed; + worldObj.spawnParticle(particle, px, py, pz, mx, my, mz); + } + return; + } + + if (worldObj instanceof WorldServer) { + ((WorldServer) worldObj).func_147487_a(particle, posX, posY, posZ, count, spreadX, spreadY, spreadZ, speed); + } + } + + /** + * Emits a single directed voxel particle. + * Uses count=0 packet mode on server so motion values are preserved. + */ + protected void emitVoxelParticle(String particle, double x, double y, double z, + double motionX, double motionY, double motionZ) { + if (worldObj == null || particle == null || particle.isEmpty()) return; + + if (worldObj.isRemote) { + worldObj.spawnParticle(particle, x, y, z, motionX, motionY, motionZ); + return; + } + + if (worldObj instanceof WorldServer) { + ((WorldServer) worldObj).func_147487_a(particle, x, y, z, 0, motionX, motionY, motionZ, 1.0D); + } + } + + /** + * Spawn one cubic shell of explosion particles to get a blocky/voxel silhouette. + */ + protected void spawnVoxelShell(double shellRadius, int shellIndex, float visualRad) { + int steps = Math.max(2, Math.min(4, (int) Math.ceil(shellRadius * 0.55D))); + double cell = shellRadius / steps; + double jitter = Math.min(0.15D, cell * 0.20D); + double shellSpeed = 0.03D + (shellRadius / Math.max(1.0D, visualRad)) * 0.05D; + + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (((ix + iy + iz + shellIndex) & 1) != 0) continue; + + double px = posX + ix * cell + (rand.nextDouble() - 0.5D) * jitter; + double py = posY + iy * cell + (rand.nextDouble() - 0.5D) * jitter; + double pz = posZ + iz * cell + (rand.nextDouble() - 0.5D) * jitter; + + double nx = ix; + double ny = iy; + double nz = iz; + double len = Math.sqrt(nx * nx + ny * ny + nz * nz); + if (len < 0.001D) { + ny = 1.0D; + len = 1.0D; + } + nx /= len; + ny /= len; + nz /= len; + + emitVoxelParticle("explode", px, py, pz, nx * shellSpeed, ny * shellSpeed, nz * shellSpeed); + + if (((ix + iy + iz + shellIndex) % 3) == 0) { + emitVoxelParticle("smoke", px, py, pz, nx * shellSpeed * 0.35D, ny * shellSpeed * 0.35D, nz * shellSpeed * 0.35D); + } + } + } + } + } + + /** + * Spawn debris particles using nearby block textures. + */ + protected void spawnVoxelDebris(float visualRad) { + if (worldObj == null) return; + + int count = Math.max(4, Math.min(24, Math.round(visualRad * 2.5f))); + for (int i = 0; i < count; i++) { + double ox = (rand.nextDouble() - 0.5D) * visualRad * 1.2D; + double oy = (rand.nextDouble() - 0.35D) * visualRad * 0.9D; + double oz = (rand.nextDouble() - 0.5D) * visualRad * 1.2D; + + int bx = MathHelper.floor_double(posX + ox); + int by = MathHelper.floor_double(posY + oy); + int bz = MathHelper.floor_double(posZ + oz); + + Block block = worldObj.getBlock(bx, by, bz); + if (block == null || block == Blocks.air) continue; + + int blockId = Block.getIdFromBlock(block); + if (blockId <= 0) continue; + int meta = worldObj.getBlockMetadata(bx, by, bz); + + String particle = "blockcrack_" + blockId + "_" + Math.max(0, meta); + emitVoxelParticle( + particle, + posX + ox * 0.55D, + posY + oy * 0.55D, + posZ + oz * 0.55D, + ox * 0.03D, oy * 0.03D, oz * 0.03D + ); + } + } + + /** + * Optional terrain damage for energy explosions. + * Controlled server-side by ConfigEnergy.EnableEnergyExplosionBlockDamage. + */ + protected void tryDestroyTerrain(float explosionRad) { + if (worldObj == null || worldObj.isRemote) return; + if (!ConfigEnergy.EnableEnergyExplosionBlockDamage) return; + if (!worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")) return; + + float terrainRad = Math.max(0.5f, Math.min(explosionRad, 12.0f)); + int minX = MathHelper.floor_double(posX - terrainRad); + int maxX = MathHelper.floor_double(posX + terrainRad); + int minY = MathHelper.floor_double(posY - terrainRad); + int maxY = MathHelper.floor_double(posY + terrainRad); + int minZ = MathHelper.floor_double(posZ - terrainRad); + int maxZ = MathHelper.floor_double(posZ + terrainRad); + float resistanceCutoff = Math.max(4.0f, terrainRad * 6.0f); + + Explosion context = new Explosion(worldObj, this, posX, posY, posZ, terrainRad); + context.isFlaming = false; + context.isSmoking = true; + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + double cx = x + 0.5D - posX; + double cy = y + 0.5D - posY; + double cz = z + 0.5D - posZ; + + // Chebyshev metric yields a voxel/cubic blast shape. + double chebyshev = Math.max(Math.abs(cx), Math.max(Math.abs(cy), Math.abs(cz))); + if (chebyshev > terrainRad) continue; + + // Soften cube corners a bit for a less artificial crater edge. + double normalized = chebyshev / terrainRad; + if (normalized > 0.72D && rand.nextDouble() < (normalized - 0.72D) * 1.4D) continue; + + Block block = worldObj.getBlock(x, y, z); + if (block == null || block == Blocks.air || block == Blocks.bedrock) continue; + Material material = block.getMaterial(); + if (material == null || material == Material.air) continue; + + float resistance = block.getExplosionResistance(this, worldObj, x, y, z, posX, posY, posZ); + if (resistance > resistanceCutoff) continue; + + try { + block.onBlockExploded(worldObj, x, y, z, context); + } catch (Exception ignored) { + } + } + } + } } // ==================== LAUNCH HELPERS ==================== diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 4fd5b3fdf..d8b731322 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -37,6 +37,7 @@ import kamkeel.npcs.entity.EntityAbilityPanel; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityDome; +import kamkeel.npcs.entity.EntityEnergyExplosion; import kamkeel.npcs.entity.EntityEnergySlicer; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.util.BukkitUtil; @@ -301,6 +302,7 @@ public class CustomNpcs { registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); registerNewEntity(EntityAbilityPanel.class, "energypanel", 64, 3, true); registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); + registerNewEntity(EntityEnergyExplosion.class, "energyexplosion", 64, 3, true); new RecipeController(); diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 180a93194..1cff1e838 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -15,6 +15,7 @@ import kamkeel.npcs.client.renderer.RenderAbilityZone; import kamkeel.npcs.client.renderer.RenderAbilityDome; import kamkeel.npcs.client.renderer.RenderAbilityPanel; import kamkeel.npcs.client.renderer.EnergyChargePreviewRenderer; +import kamkeel.npcs.client.renderer.RenderEnergyExplosion; import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.client.renderer.lightning.LightningHandler; @@ -28,6 +29,7 @@ import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityAbilityPanel; +import kamkeel.npcs.entity.EntityEnergyExplosion; import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -283,6 +285,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityAbilityDome.class, new RenderAbilityDome()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilityPanel.class, new RenderAbilityPanel()); RenderingRegistry.registerEntityRenderingHandler(EntityEnergySlicer.class, new RenderEnergySlicer()); + RenderingRegistry.registerEntityRenderingHandler(EntityEnergyExplosion.class, new RenderEnergyExplosion()); RenderingRegistry.registerEntityRenderingHandler(EntityCustomNpc.class, new RenderCustomNpc()); diff --git a/src/main/java/noppes/npcs/config/ConfigEnergy.java b/src/main/java/noppes/npcs/config/ConfigEnergy.java index f3b36a948..197bb4968 100644 --- a/src/main/java/noppes/npcs/config/ConfigEnergy.java +++ b/src/main/java/noppes/npcs/config/ConfigEnergy.java @@ -10,8 +10,14 @@ public class ConfigEnergy { public static Configuration config; public final static String DOMES = "Domes"; + public final static String EXPLOSIONS = "Explosions"; public static String[] DomeItemBlacklist; + /** + * Server-side toggle for energy projectile terrain destruction. + * Disabled by default to avoid block grief. + */ + public static boolean EnableEnergyExplosionBlockDamage; public static void init(File configFile) { config = new Configuration(configFile); @@ -26,6 +32,13 @@ public class ConfigEnergy { + "Format: modid:itemname (e.g., minecraft:ender_pearl)" ); + EnableEnergyExplosionBlockDamage = config.getBoolean( + "Enable Energy Explosion Block Damage", EXPLOSIONS, + false, + "If true, explosive ENERGY projectile abilities can destroy terrain.\n" + + "This is intended for server-side use and is disabled by default." + ); + } catch (Exception e) { FMLLog.log(Level.ERROR, e, "CustomNPC+ has had a problem loading its energy configuration"); } finally { From d3ca0622e4d55ea4eacd553a5d37956d041d040e Mon Sep 17 00:00:00 2001 From: Vidal Date: Sun, 22 Feb 2026 15:15:21 -0300 Subject: [PATCH 43/71] Modified base energy abilities hit types --- .../data/ability/data/energy/EnergyCombatData.java | 13 +++++++++++++ .../data/ability/type/energy/AbilityBeam.java | 3 ++- .../data/ability/type/energy/AbilityDisc.java | 3 ++- .../data/ability/type/energy/AbilityLaserShot.java | 7 ++----- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java index b494a8b15..0825fcfef 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java @@ -21,6 +21,19 @@ public class EnergyCombatData implements IEnergyCombatData { public EnergyCombatData() { } + public EnergyCombatData(float damage, float knockback, float knockbackUp, + boolean explosive, float explosionRadius, float explosionDamageFalloff, + HitType hitType, int multiHitDelay) { + this.damage = damage; + this.knockback = knockback; + this.knockbackUp = knockbackUp; + this.explosive = explosive; + this.explosionRadius = explosionRadius; + this.explosionDamageFalloff = explosionDamageFalloff; + this.hitType = hitType; + this.multiHitDelayTicks = multiHitDelay; + } + public EnergyCombatData(float damage, float knockback, float knockbackUp, boolean explosive, float explosionRadius, float explosionDamageFalloff) { this.damage = damage; 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 a529ac488..9fd5ad973 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 @@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.enums.HitType; import kamkeel.npcs.controllers.data.ability.enums.LockMode; import kamkeel.npcs.controllers.data.ability.enums.RotationMode; import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; @@ -36,7 +37,7 @@ public class AbilityBeam extends AbilityEnergyProjectile impl public AbilityBeam() { super( new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f), - new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f), + new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f, HitType.MULTI, 5), new EnergyHomingData(0.4f, true, 0.1f, 15.0f), new EnergyLifespanData(150.0f, 200) ); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index bc5883906..0d2d2fc99 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.enums.HitType; import kamkeel.npcs.controllers.data.ability.enums.LockMode; import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; @@ -38,7 +39,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl public AbilityDisc() { super( new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f), - new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f), + new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f, HitType.PIERCE, 5), new EnergyHomingData(0.6f, true, 0.12f, 18.0f), new EnergyLifespanData(150.0f, 200) ); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index ba1f08fba..522890d47 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -2,10 +2,7 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.enums.LockMode; -import kamkeel.npcs.controllers.data.ability.enums.RotationMode; -import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.*; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; @@ -38,7 +35,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile Date: Sun, 22 Feb 2026 14:09:33 -0500 Subject: [PATCH 44/71] Explosion Client-Side only --- .../renderer/RenderEnergyExplosion.java | 2 + .../npcs/entity/EntityEnergyExplosion.java | 14 +++- .../npcs/entity/EntityEnergyProjectile.java | 48 ++++++++++-- .../kamkeel/npcs/network/PacketHandler.java | 2 + .../npcs/network/enums/EnumDataPacket.java | 3 + .../EnergyExplosionSpawnPacket.java | 74 +++++++++++++++++++ src/main/java/noppes/npcs/CustomNpcs.java | 2 - .../java/noppes/npcs/config/ConfigClient.java | 13 ++++ 8 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java index 7f502c575..4128aef59 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.entity.EntityEnergyExplosion; import net.minecraft.entity.Entity; +import noppes.npcs.config.ConfigClient; import org.lwjgl.opengl.GL11; /** @@ -20,6 +21,7 @@ public class RenderEnergyExplosion extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { if (!(entity instanceof EntityEnergyExplosion)) return; + if (ConfigClient.LowResExplosion) return; EntityEnergyExplosion explosion = (EntityEnergyExplosion) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java index 0132ba2d1..d90295912 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java @@ -107,6 +107,9 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { @Override protected void writeSpawnNBT(NBTTagCompound nbt) { writeEnergyBaseNBT(nbt); + nbt.setDouble("PosX", this.posX); + nbt.setDouble("PosY", this.posY); + nbt.setDouble("PosZ", this.posZ); nbt.setFloat("ExplosionRadius", maxRadius); nbt.setInteger("ExplosionDuration", durationTicks); nbt.setLong("ExplosionSeed", renderSeed); @@ -115,6 +118,16 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { @Override protected void readSpawnNBT(NBTTagCompound nbt) { readEnergyBaseNBT(nbt); + double x = nbt.hasKey("PosX") ? nbt.getDouble("PosX") : this.posX; + double y = nbt.hasKey("PosY") ? nbt.getDouble("PosY") : this.posY; + double z = nbt.hasKey("PosZ") ? nbt.getDouble("PosZ") : this.posZ; + this.setPosition(x, y, z); + this.prevPosX = x; + this.prevPosY = y; + this.prevPosZ = z; + this.lastTickPosX = x; + this.lastTickPosY = y; + this.lastTickPosZ = z; setExplosionRadius(nbt.hasKey("ExplosionRadius") ? nbt.getFloat("ExplosionRadius") : 2.0f); this.durationTicks = nbt.hasKey("ExplosionDuration") ? nbt.getInteger("ExplosionDuration") : durationTicks; if (durationTicks <= 0) durationTicks = 10; @@ -123,4 +136,3 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { this.prevRenderRadius = 0.0f; } } - diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 72d35520c..634bdcd90 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -13,11 +13,13 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; +import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.EnumCreatureType; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; @@ -49,6 +51,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; /** * Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer). @@ -425,6 +428,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void applyDamage(EntityLivingBase target, float dmg, float kb) { if (previewMode) return; // Skip damage in preview mode + if (target == null || shouldIgnoreEntity(target)) return; // Fire entity impact event (may cancel or modify damage) if (!worldObj.isRemote) { @@ -437,6 +441,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Check for ability extenders (e.g., DBC Addon damage routing) boolean handled = false; + boolean defaultDamageApplied = false; if (sourceAbility != null && owner instanceof EntityLivingBase) { double dx = target.posX - posX; double dz = target.posZ - posZ; @@ -449,16 +454,19 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (!handled) { // Default damage path if (owner instanceof EntityNPCInterface) { - target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); } else if (owner instanceof EntityPlayer) { - target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); } else if (owner instanceof EntityLivingBase) { - target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); } else { - target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); } } + boolean allowSecondaryEffects = handled || dmg <= 0 || defaultDamageApplied; + if (!allowSecondaryEffects) return; + if (kb > 0) { double dx = target.posX - posX; double dz = target.posZ - posZ; @@ -508,6 +516,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { for (EntityLivingBase target : targets) { if (target == owner) continue; + if (shouldIgnoreExplosionTarget(target)) continue; double dist = Math.sqrt( Math.pow(target.posX - posX, 2) + @@ -525,14 +534,41 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } /** - * Spawn a short-lived voxel render entity so explosions are visible as geometry, + * Explosion safety filter. + * Reuses standard projectile-friendly checks and blocks passive non-player entities. + */ + protected boolean shouldIgnoreExplosionTarget(EntityLivingBase target) { + if (target == null) return true; + if (shouldIgnoreEntity(target)) return true; + return isPassiveNonPlayerEntity(target); + } + + /** + * Passive mobs (animals/ambient/water creatures) are never damaged by energy explosions. + * Players and hostile mobs remain valid unless other friendly-fire checks reject them. + */ + protected boolean isPassiveNonPlayerEntity(EntityLivingBase target) { + if (target instanceof EntityPlayer) return false; + if (target instanceof EntityNPCInterface) return false; + if (target.isCreatureType(EnumCreatureType.monster, false)) return false; + return target.isCreatureType(EnumCreatureType.creature, false) + || target.isCreatureType(EnumCreatureType.ambient, false) + || target.isCreatureType(EnumCreatureType.waterCreature, false); + } + + /** + * Spawn a packet-driven client preview so explosions are visible as geometry, * not only as particles. */ protected void spawnExplosionRenderEntity(float explosionRad) { if (worldObj == null || worldObj.isRemote) return; float renderRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); EntityEnergyExplosion fx = new EntityEnergyExplosion(worldObj, this, renderRad); - worldObj.spawnEntityInWorld(fx); + EnergyExplosionSpawnPacket.sendToTracking(getExplosionVisualInstanceId(), fx, this); + } + + protected String getExplosionVisualInstanceId() { + return "energy_explosion_" + UUID.randomUUID(); } /** diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 8b57a070c..991b2115d 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -38,6 +38,7 @@ import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket; import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket; +import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import kamkeel.npcs.network.packets.player.AuctionActionPacket; @@ -589,6 +590,7 @@ public class PacketHandler { DATA_PACKET.registerPacket(new TelegraphRemovePacket()); DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket()); DATA_PACKET.registerPacket(new EnergyChargeRemovePacket()); + DATA_PACKET.registerPacket(new EnergyExplosionSpawnPacket()); DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket()); DATA_PACKET.registerPacket(new PlayerAbilityStatePacket()); DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index 938453b31..6d9a6f598 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -74,4 +74,7 @@ public enum EnumDataPacket { // Energy Charge Preview ENERGY_CHARGE_SPAWN, ENERGY_CHARGE_REMOVE, + + // Energy Explosion Preview + ENERGY_EXPLOSION_SPAWN, } diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java new file mode 100644 index 000000000..e227b8562 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java @@ -0,0 +1,74 @@ +package kamkeel.npcs.network.packets.data.energyexplosion; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.entity.EntityEnergyExplosion; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +import java.io.IOException; + +/** + * Spawns a client-side explosion visual entity from serialized spawn data. + */ +public final class EnergyExplosionSpawnPacket extends AbstractPacket { + public static final String packetName = "Data|EnergyExplosionSpawn"; + + private String instanceId; + private NBTTagCompound spawnNbt; + + public EnergyExplosionSpawnPacket() { + } + + public EnergyExplosionSpawnPacket(String instanceId, NBTTagCompound spawnNbt) { + this.instanceId = instanceId; + this.spawnNbt = spawnNbt; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_EXPLOSION_SPAWN; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + ByteBufUtils.writeNBT(out, spawnNbt); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + ByteBufUtils.readString(in); // Keep wire compatibility for future instance tracking. + NBTTagCompound nbt = ByteBufUtils.readNBT(in); + + if (nbt == null) return; + + World world = Minecraft.getMinecraft().theWorld; + if (world == null) return; + + EntityEnergyExplosion preview = new EntityEnergyExplosion(world); + preview.importSpawnNBT(nbt); + world.spawnEntityInWorld(preview); + } + + public static void sendToTracking(String instanceId, EntityEnergyExplosion previewEntity, Entity trackingEntity) { + if (instanceId == null || previewEntity == null || trackingEntity == null) return; + NBTTagCompound nbt = previewEntity.exportSpawnNBT(); + PacketHandler.Instance.sendTracking(new EnergyExplosionSpawnPacket(instanceId, nbt), trackingEntity); + } +} diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index d8b731322..4fd5b3fdf 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -37,7 +37,6 @@ import kamkeel.npcs.entity.EntityAbilityPanel; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityDome; -import kamkeel.npcs.entity.EntityEnergyExplosion; import kamkeel.npcs.entity.EntityEnergySlicer; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.util.BukkitUtil; @@ -302,7 +301,6 @@ public class CustomNpcs { registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); registerNewEntity(EntityAbilityPanel.class, "energypanel", 64, 3, true); registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); - registerNewEntity(EntityEnergyExplosion.class, "energyexplosion", 64, 3, true); new RecipeController(); diff --git a/src/main/java/noppes/npcs/config/ConfigClient.java b/src/main/java/noppes/npcs/config/ConfigClient.java index 3616627f4..79a647026 100644 --- a/src/main/java/noppes/npcs/config/ConfigClient.java +++ b/src/main/java/noppes/npcs/config/ConfigClient.java @@ -17,6 +17,7 @@ public class ConfigClient { public final static String MODEL = "Model"; public final static String TEXTURE = "Texture"; public final static String HUD = "Hud"; + public final static String RENDERING = "Rendering"; /** @@ -52,6 +53,10 @@ public class ConfigClient { public static Property HideEffectsBarProperty; public static boolean HideEffectsBar = false; + // Rendering Properties + public static Property LowResExplosionProperty; + public static boolean LowResExplosion = false; + /** * Questing Properties **/ @@ -277,6 +282,14 @@ public class ConfigClient { HideEffectsBarProperty = config.get(VISUAL, "Hide Effects Bar", false, "Hides CNPC+ Inventory Effects Bar"); HideEffectsBar = HideEffectsBarProperty.getBoolean(false); + LowResExplosionProperty = config.get( + RENDERING, + "Low Res Explosion", + false, + "Disables most Energy Explosion voxel rendering and keeps particle effects only." + ); + LowResExplosion = LowResExplosionProperty.getBoolean(false); + DialogSpeedProperty = config.get(VISUAL, "Dialog Speed", true, "Only set for gradual dialogs"); DialogSpeed = DialogSpeedProperty.getInt(10); From f818568bd30b6137ac55a02209d937b36c4f552a Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 14:16:40 -0500 Subject: [PATCH 45/71] Fix Panel Held / Anchored Mode --- .../java/kamkeel/npcs/entity/EntityAbilityPanel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index 582151239..653d336a2 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -168,6 +168,17 @@ public class EntityAbilityPanel extends EntityAbilityBarrier { this.prevPosZ = owner.prevPosZ + (Math.cos(prevYawRad) * frontDist); } + @Override + public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { + // HELD panels are fully owner-driven each tick via updateHeld(). + // Applying network position lerp on top of owner-following causes visible jitter/stutter + // when the caster is moving, so ignore remote corrections in this mode. + if (worldObj != null && worldObj.isRemote && mode == PanelMode.HELD && getOwnerEntity() != null) { + return; + } + super.setPositionAndRotation2(x, y, z, yaw, pitch, posRotationIncrements); + } + private void updateLaunched() { // Move forward on both sides for smooth client rendering this.posX += motionX; From de92d57f1362c5867f731a21e2f5d5c90ad7a338 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 15:02:14 -0500 Subject: [PATCH 46/71] Fix some Projectile Firing --- .../npcs/entity/EntityAbilityBeam.java | 83 +++------- .../npcs/entity/EntityAbilityDisc.java | 40 +---- .../npcs/entity/EntityAbilityLaser.java | 50 +++--- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 111 +------------ .../npcs/entity/EntityEnergyProjectile.java | 155 ++++++++++++++++-- .../npcs/entity/EntityEnergySlicer.java | 33 +--- src/main/java/noppes/npcs/CustomNpcs.java | 10 +- 7 files changed, 207 insertions(+), 275 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index ff0de17df..ab374bf41 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -83,6 +83,15 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { return Math.max(0, trailPoints.size() - trailStartIndex); } + private void resetHeadOffsets() { + headOffsetX = 0; + headOffsetY = 0; + headOffsetZ = 0; + prevHeadOffsetX = 0; + prevHeadOffsetY = 0; + prevHeadOffsetZ = 0; + } + private void resetTrailStorage() { trailPoints.clear(); trailPointAges.clear(); @@ -135,12 +144,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { setBeamMode(modeFromAnchored(anchoredMode)); // Initialize head offset at origin (0,0,0 relative) - this.headOffsetX = 0; - this.headOffsetY = 0; - this.headOffsetZ = 0; - this.prevHeadOffsetX = 0; - this.prevHeadOffsetY = 0; - this.prevHeadOffsetZ = 0; + resetHeadOffsets(); // Add initial trail point at origin (relative 0,0,0) trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); @@ -154,79 +158,42 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) { - setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; + setupChargingState(anchor, chargeDuration); this.chargeOffsetDistance = chargeOffsetDistance; - this.anchorData = anchor; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + clearMotion(); } public void setupPreview(EntityLivingBase owner, float beamWidth, float headSize, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); + setupPreviewState(owner, display, lightning, anchor, chargeDuration); // Set visual properties this.beamWidth = beamWidth; this.headSize = headSize; - this.size = headSize; - this.displayData = display; - this.lightningData = lightning; + setVisualSize(headSize); - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; this.chargeOffsetDistance = chargeOffsetDistance; - this.anchorData = anchor; - - // Initial position at anchor point - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData, chargeOffsetDistance); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; + setChargeOriginFromAnchor(owner, anchorData, chargeOffsetDistance); // Initialize head offsets - this.headOffsetX = 0; - this.headOffsetY = 0; - this.headOffsetZ = 0; - this.prevHeadOffsetX = 0; - this.prevHeadOffsetY = 0; - this.prevHeadOffsetZ = 0; + resetHeadOffsets(); // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - beginLookVectorLaunch(true); + startPreviewFiringDefault(); // Reset head offset to origin - headOffsetX = 0; - headOffsetY = 0; - headOffsetZ = 0; - prevHeadOffsetX = 0; - prevHeadOffsetY = 0; - prevHeadOffsetZ = 0; + resetHeadOffsets(); // Reset trail resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); if (isFadingMode()) trailPointAges.add(0); - - // Fire along look vector for parity with real launch behavior. - setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); } /** @@ -234,24 +201,16 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startFiring(EntityLivingBase target) { - beginLookVectorLaunch(false); + startMovingTowardTargetFromStartDefault(target); // Head starts at the origin (tail position) - headOffsetX = 0; - headOffsetY = 0; - headOffsetZ = 0; - prevHeadOffsetX = 0; - prevHeadOffsetY = 0; - prevHeadOffsetZ = 0; + resetHeadOffsets(); // Initialize trail with just the origin point resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); if (isFadingMode()) trailPointAges.add(0); - // Calculate velocity toward target when provided; otherwise along look vector. - setMotionTowardTargetOrLookVector(target, startX, startY, startZ, getSpeed(), getSpeed(), 0, 0); - if (DEBUG_LOGGING && !worldObj.isRemote) { LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ + ") headOffset=(" + headOffsetX + "," + headOffsetY + "," + headOffsetZ + diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 556f4e68c..e8b8acc58 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -94,18 +94,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Can be fired when transitioning to active phase. */ public void setupPreview(EntityLivingBase owner, float discRadius, float discThickness, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, boolean vertical) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - - // Set visual properties - this.displayData = display; - this.lightningData = lightning; - - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); this.vertical = vertical; // Store target size and start at 0 for grow effect @@ -113,32 +102,16 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { this.targetDiscThickness = discThickness; this.discRadius = 0.01f; this.discThickness = 0.01f; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - // Initial position at anchor point - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - - // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - beginLookVectorLaunch(true); - setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + startPreviewFiringDefault(); } /** @@ -146,8 +119,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - beginLookVectorLaunch(false); - setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); + startMovingTowardTargetDefault(target); } @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index aef9a7adb..56048d973 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -6,7 +6,6 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; -import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -619,44 +618,39 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { public void setupPreview(EntityLivingBase owner, float laserWidth, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, float expansionSpeed, float maxDistance) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); + setupPreviewState(owner, display, lightning, anchor, chargeDuration); - // Set visual properties + // Laser-specific visual/behavior state. this.laserWidth = laserWidth; - this.size = laserWidth; - this.displayData = display; this.expansionSpeed = expansionSpeed; this.lifespanData.maxDistance = Math.min(maxDistance, 5.0f); // Limit for GUI preview - this.lightningData = lightning; this.currentLength = 0.0f; this.fullyExtended = false; this.ticksSinceFullExtension = 0; - // Charge as an orb at the configured anchor, matching other energy projectiles. - setupCharging(anchor, chargeDuration); - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - this.endX = pos.xCoord; - this.endY = pos.yCoord; - this.endZ = pos.zCoord; + // Charge as an orb from tiny size to laser width while anchored. + this.targetSize = laserWidth; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); + this.endX = posX; + this.endY = posY; + this.endZ = posZ; // Precompute initial direction from owner look for launch. - Vec3 look = owner.getLookVec(); + Vec3 look = owner == null ? null : owner.getLookVec(); if (look == null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - look = Vec3.createVectorHelper( - -Math.sin(yaw) * Math.cos(pitch), - -Math.sin(pitch), - Math.cos(yaw) * Math.cos(pitch) - ); + if (owner != null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + look = Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } else { + look = Vec3.createVectorHelper(1.0, 0.0, 0.0); + } } this.dirX = look.xCoord; this.dirY = look.yCoord; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index c203c61a5..407012efd 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -7,16 +7,10 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; -import kamkeel.npcs.util.AnchorPointHelper; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.LogWriter; - -import java.util.Locale; /** * Orb projectile - spherical homing energy ball. @@ -27,8 +21,6 @@ import java.util.Locale; public class EntityAbilityOrb extends EntityEnergyProjectile { private static final int HOMING_STARTUP_TICKS = 4; private static final int HOMING_RAMP_TICKS = 8; - private static final boolean DEBUG_ORB_FIRE_TRACE = false; - private static final int DEBUG_TRACE_TICKS = 14; public EntityAbilityOrb(World world) { super(world); @@ -59,47 +51,20 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Can be fired when transitioning to active phase. */ public void setupPreview(EntityLivingBase owner, float orbSize, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - - // Set visual properties - this.displayData = display; - this.lightningData = lightning; - - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); // Store target size and start at 0 for grow effect this.targetSize = orbSize; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - // Initial position follows anchor helper during charging. - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - - // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - beginLookVectorLaunch(true); - setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + startPreviewFiringDefault(); } /** @@ -107,28 +72,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - // Launch strictly along owner's look vector using shared launch origin. - Entity owner = getOwnerEntity(); - if (owner instanceof EntityLivingBase) { - Vec3 look = getOwnerLookVector(); - if (look != null) { - debugTrace("startMoving:before", look, -1.0f); - if (beginLookVectorLaunch((EntityLivingBase) owner, look, false)) { - debugTrace("setLaunchPosition", look, -1.0f); - } - motionX = look.xCoord * getSpeed(); - motionY = look.yCoord * getSpeed(); - motionZ = look.zCoord * getSpeed(); - debugTrace("startMoving:after", look, -1.0f); - return; - } - } - - // Fallback if owner is unavailable. - setCharging(false); - syncStartPositionToCurrent(); - setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); - debugTrace("startMoving:fallback", null, -1.0f); + startMovingAlongLookVectorDefault(); } @Override @@ -146,17 +90,13 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } if (worldObj.isRemote) { - if (shouldDebugTick()) debugTrace("client:beforeInterpolation", null, -1.0f); handleClientInterpolation(); - if (shouldDebugTick()) debugTrace("client:afterInterpolation", null, -1.0f); } else { // Server-side logic - if (shouldDebugTick()) debugTrace("server:beforeMovement", null, -1.0f); updateMovement(); checkBlockCollision(); checkEntityCollision(); this.moveEntity(motionX, motionY, motionZ); - if (shouldDebugTick()) debugTrace("server:afterMove", null, -1.0f); } // Check wall collision @@ -180,11 +120,9 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { if (ticksExisted > HOMING_STARTUP_TICKS) { int homingTicks = ticksExisted - HOMING_STARTUP_TICKS; if (homingTicks >= HOMING_RAMP_TICKS) { - if (shouldDebugTick()) debugTrace("updateMovement:homingFull", null, 1.0f); updateHoming(); } else { float ramp = homingTicks / (float) HOMING_RAMP_TICKS; - if (shouldDebugTick()) debugTrace("updateMovement:homingRamp", null, ramp); updateHomingWithRamp(ramp); } } @@ -235,43 +173,6 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { processEntitiesInHitBox(hitBox, nextX, nextY, nextZ); } - private boolean shouldDebugTick() { - return DEBUG_ORB_FIRE_TRACE && !isCharging() && !previewMode && ticksExisted <= DEBUG_TRACE_TICKS; - } - - private void debugTrace(String stage, Vec3 look, float ramp) { - if (!DEBUG_ORB_FIRE_TRACE || worldObj == null) return; - - String side = worldObj.isRemote ? "CLIENT" : "SERVER"; - Entity owner = getOwnerEntity(); - String ownerInfo = owner == null - ? "null" - : owner.getEntityId() + "@(" + fmt(owner.posX) + "," + fmt(owner.posY) + "," + fmt(owner.posZ) + ")"; - String lookInfo = look == null - ? "null" - : "(" + fmt(look.xCoord) + "," + fmt(look.yCoord) + "," + fmt(look.zCoord) + ")"; - - LogWriter.info( - "[OrbFireTrace][" + side + "][" + stage + "] " - + "id=" + getEntityId() - + " tick=" + ticksExisted - + " charging=" + isCharging() - + " start=(" + fmt(startX) + "," + fmt(startY) + "," + fmt(startZ) + ")" - + " pos=(" + fmt(posX) + "," + fmt(posY) + "," + fmt(posZ) + ")" - + " prev=(" + fmt(prevPosX) + "," + fmt(prevPosY) + "," + fmt(prevPosZ) + ")" - + " motion=(" + fmt(motionX) + "," + fmt(motionY) + "," + fmt(motionZ) + ")" - + " size=" + fmt(size) - + " speed=" + fmt(getSpeed()) - + " ramp=" + (ramp < 0 ? "n/a" : fmt(ramp)) - + " owner=" + ownerInfo - + " look=" + lookInfo - ); - } - - private static String fmt(double v) { - return String.format(Locale.ROOT, "%.4f", v); - } - // ==================== GETTERS ==================== // Legacy getter for renderer compatibility diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 634bdcd90..278fb8001 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -61,7 +61,6 @@ import java.util.UUID; * Design inspired by LouisXIV's energy attack system. */ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { - // ==================== ACTIVE PROJECTILE TRACKING ==================== private static final Map>> activeProjectiles = new HashMap<>(); @@ -380,10 +379,42 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @Override public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { + int steps = Math.max(1, posRotationIncrements); + double dx = x - this.posX; + double dy = y - this.posY; + double dz = z - this.posZ; + double dotToCurrentMotion = dx * this.motionX + dy * this.motionY + dz * this.motionZ; + + // Spawn-time and large correction snaps: avoid visible startup rubber-banding. + if (worldObj != null && worldObj.isRemote) { + double distSq = dx * dx + dy * dy + dz * dz; + // Launch-time stale tracker packets can arrive one update late and point behind + // an already-predicted projectile. Skipping these avoids visible backward "bounce". + if (ticksExisted <= 24 && distSq <= 4.0D && dotToCurrentMotion < -0.0025D) { + return; + } + if (ticksExisted <= 2 || distSq > 16.0D) { + this.setPosition(x, y, z); + syncPositionState(x, y, z, true); + return; + } + } + this.interpTargetX = x; this.interpTargetY = y; this.interpTargetZ = z; - this.interpSteps = posRotationIncrements; + if (worldObj != null && worldObj.isRemote && ticksExisted <= 24 && dotToCurrentMotion < 0.0D) { + // Keep existing velocity when correction points opposite current travel + // to prevent launch-time reverse lerp. + this.interpTargetMotionX = this.motionX; + this.interpTargetMotionY = this.motionY; + this.interpTargetMotionZ = this.motionZ; + } else { + this.interpTargetMotionX = dx / steps; + this.interpTargetMotionY = dy / steps; + this.interpTargetMotionZ = dz / steps; + } + this.interpSteps = steps; } @Override @@ -815,6 +846,38 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { return positioned; } + /** + * Default preview launch: snap to look-vector origin and fire along look. + */ + protected void startPreviewFiringDefault() { + beginLookVectorLaunch(true); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch: snap to look-vector origin and fire toward target when available. + */ + protected void startMovingTowardTargetDefault(EntityLivingBase target) { + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch from fixed origin fields (used by beam-like projectiles). + */ + protected void startMovingTowardTargetFromStartDefault(EntityLivingBase target) { + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, startX, startY, startZ, getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch strictly along look vector. + */ + protected void startMovingAlongLookVectorDefault() { + beginLookVectorLaunch(false); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + } + /** * Resolve the owner's look vector. * Falls back to yaw/pitch math if look vector is unavailable. @@ -836,7 +899,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } /** - * Shared launch origin: owner position + chest height + look-vector offset. + * Shared launch origin: owner eye position + look-vector offset. * * @param syncPositionState When true, sync prev/lastTick/interp targets to this position. */ @@ -855,7 +918,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (owner == null || look == null) return false; double originX = owner.posX; - double originY = owner.posY + owner.height * 0.7; + double originY = owner.posY + owner.getEyeHeight(); double originZ = owner.posZ; float frontDist = computeLaunchFrontDistance(owner, look, originX, originY, originZ); @@ -1011,23 +1074,87 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== CHARGING METHODS (projectile-specific) ==================== + /** + * Shared charging-state bootstrap used by both world and preview setup paths. + */ + protected void setupChargingState(EnergyAnchorData anchor, int chargeDuration) { + setCharging(true); + this.chargeDuration = chargeDuration; + this.chargeTick = 0; + this.anchorData = anchor; + } + + /** + * Shared preview bootstrap used by projectile preview setup methods. + */ + protected void setupPreviewState(EntityLivingBase owner, EnergyDisplayData display, + EnergyLightningData lightning, EnergyAnchorData anchor, + int chargeDuration) { + this.setPreviewMode(true); + this.setPreviewOwner(owner); + this.displayData = display; + this.lightningData = lightning; + setupChargingState(anchor, chargeDuration); + } + + /** + * Set size and render-size state to a single value. + */ + protected void setVisualSize(float value) { + this.size = value; + this.renderCurrentSize = value; + this.prevRenderSize = value; + } + + /** + * Set entity/start state to a fixed position used as charging/launch origin. + */ + protected void setChargeOrigin(Vec3 pos) { + if (pos == null) return; + this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + this.prevPosX = pos.xCoord; + this.prevPosY = pos.yCoord; + this.prevPosZ = pos.zCoord; + this.startX = pos.xCoord; + this.startY = pos.yCoord; + this.startZ = pos.zCoord; + } + + /** + * Resolve and apply charging/launch origin from anchor position. + */ + protected void setChargeOriginFromAnchor(EntityLivingBase owner, EnergyAnchorData anchor) { + if (owner == null || anchor == null) return; + setChargeOrigin(AnchorPointHelper.calculateAnchorPosition(owner, anchor)); + } + + /** + * Resolve and apply charging/launch origin from anchor position with forward offset. + */ + protected void setChargeOriginFromAnchor(EntityLivingBase owner, EnergyAnchorData anchor, float offsetDistance) { + if (owner == null || anchor == null) return; + setChargeOrigin(AnchorPointHelper.calculateAnchorPosition(owner, anchor, offsetDistance)); + } + + /** + * Zero current motion. + */ + protected void clearMotion() { + this.motionX = 0; + this.motionY = 0; + this.motionZ = 0; + } + /** * Setup this entity in charging mode (for windup phase). * Grows from 0 to current size over chargeDuration ticks. * Subclasses with different charging behavior should override or use their own method. */ public void setupCharging(EnergyAnchorData anchor, int chargeDuration) { - setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupChargingState(anchor, chargeDuration); this.targetSize = this.size; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + clearMotion(); } /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index b8b636171..bc429f939 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -68,50 +68,29 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { public void setupPreview(EntityLivingBase owner, float width, float thickness, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - this.displayData = display; - this.lightningData = lightning; - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); this.targetSliceWidth = width; this.targetSliceThickness = thickness; this.sliceWidth = 0.01f; this.sliceThickness = 0.01f; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing. */ public void startPreviewFiring() { - beginLookVectorLaunch(true); - setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + startPreviewFiringDefault(); } /** * Start the slicer moving (exit charging mode). */ public void startMoving(EntityLivingBase target) { - beginLookVectorLaunch(false); - setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); + startMovingTowardTargetDefault(target); } @Override diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 4fd5b3fdf..90321b1b5 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -292,15 +292,15 @@ public class CustomNpcs { registerNewEntity(EntityChairMount.class, "CustomNpcChairMount", 64, 10, false); registerNewEntity(EntityProjectile.class, "throwableitem", 64, 3, true); registerNewEntity(EntityMagicProjectile.class, "magicprojectile", 64, 3, true); - registerNewEntity(EntityAbilityOrb.class, "abilityorb", 64, 3, true); - registerNewEntity(EntityAbilityDisc.class, "abilitydisc", 64, 3, true); - registerNewEntity(EntityAbilityLaser.class, "abilitylaser", 64, 3, true); - registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 3, true); + registerNewEntity(EntityAbilityOrb.class, "abilityorb", 64, 1, true); + registerNewEntity(EntityAbilityDisc.class, "abilitydisc", 64, 1, true); + registerNewEntity(EntityAbilityLaser.class, "abilitylaser", 64, 1, true); + registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 1, true); registerNewEntity(EntityAbilitySweeper.class, "abilitysweeper", 64, 3, true); registerNewEntity(EntityAbilityZone.class, "abilityzone", 64, 3, true); registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); registerNewEntity(EntityAbilityPanel.class, "energypanel", 64, 3, true); - registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); + registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 1, true); new RecipeController(); From 71dbf77def65b59785eb16e2290a8f972a544562 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 15:39:24 -0500 Subject: [PATCH 47/71] Skip Tick 0 for Rendering --- .../npcs/client/renderer/RenderAbilityBeam.java | 3 +++ .../npcs/client/renderer/RenderAbilityDisc.java | 3 +++ .../npcs/client/renderer/RenderAbilityDome.java | 3 +++ .../npcs/client/renderer/RenderAbilityLaser.java | 3 +++ .../npcs/client/renderer/RenderAbilityOrb.java | 4 ++++ .../npcs/client/renderer/RenderAbilityPanel.java | 3 +++ .../npcs/client/renderer/RenderEnergyAbility.java | 11 +++++++++++ .../npcs/client/renderer/RenderEnergyExplosion.java | 1 + .../npcs/client/renderer/RenderEnergySlicer.java | 3 +++ 9 files changed, 34 insertions(+) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 8f87ff331..e9868854a 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -27,6 +27,9 @@ public class RenderAbilityBeam extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityBeam beam = (EntityAbilityBeam) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index d013dd306..cfa52a55a 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -17,6 +17,9 @@ public class RenderAbilityDisc extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityDisc disc = (EntityAbilityDisc) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java index beebe98f3..dcf3e5c06 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java @@ -17,6 +17,9 @@ public class RenderAbilityDome extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityDome dome = (EntityAbilityDome) entity; float radius = dome.getDomeRadius(); float healthPercent = dome.getHealthPercent(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index e38764bfb..155c439b8 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -23,6 +23,9 @@ public class RenderAbilityLaser extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityLaser laser = (EntityAbilityLaser) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java index a3adf71a0..c49ca5f15 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java @@ -19,6 +19,10 @@ public class RenderAbilityOrb extends RenderEnergyAbility { public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { EntityAbilityOrb orb = (EntityAbilityOrb) entity; + if (shouldSkipInitialActiveRender(entity)) { + return; + } + setupRenderState(); GL11.glPushMatrix(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java index 1261aaf1e..d341082b5 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java @@ -17,6 +17,9 @@ public class RenderAbilityPanel extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityPanel panel = (EntityAbilityPanel) entity; float width = panel.getPanelData().panelWidth; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java index 9496f06a5..ac88c47e8 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java @@ -22,11 +22,22 @@ import org.lwjgl.opengl.GL12; public abstract class RenderEnergyAbility extends Render { protected static final ResourceLocation WHITE_TEXTURE = new ResourceLocation("customnpcs", "textures/entity/white.png"); + protected static final int HIDE_INITIAL_ACTIVE_TICKS = 1; public RenderEnergyAbility() { this.shadowSize = 0.0f; } + /** + * Hide the first active tick to avoid the visible spawn-frame pause at fire time. + * Charging previews are never hidden. + */ + protected boolean shouldSkipInitialActiveRender(Entity entity) { + if (!(entity instanceof EntityEnergyAbility)) return false; + EntityEnergyAbility ability = (EntityEnergyAbility) entity; + return !ability.isPreviewMode() && !ability.isCharging() && ability.ticksExisted <= HIDE_INITIAL_ACTIVE_TICKS; + } + /** * Setup GL state for translucent, self-illuminated rendering. * Forces full brightness so world lighting does not affect projectiles. diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java index 4128aef59..850d857a4 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java @@ -20,6 +20,7 @@ public class RenderEnergyExplosion extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) return; if (!(entity instanceof EntityEnergyExplosion)) return; if (ConfigClient.LowResExplosion) return; EntityEnergyExplosion explosion = (EntityEnergyExplosion) entity; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java index e2aa0868f..be0d75748 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java @@ -17,6 +17,9 @@ public class RenderEnergySlicer extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityEnergySlicer slicer = (EntityEnergySlicer) entity; float width = slicer.getSliceWidth(); From 4500b10d7fa095eb940f0767b37c4188ad37437d Mon Sep 17 00:00:00 2001 From: Vidal Date: Sun, 22 Feb 2026 18:03:37 -0300 Subject: [PATCH 48/71] getColor and setColor to skin overlays --- src/api | 2 +- .../java/noppes/npcs/client/model/ModelMPM.java | 10 +++++++++- .../npcs/client/renderer/RenderCNPCPlayer.java | 10 +++++++++- .../npcs/client/renderer/RenderNPCInterface.java | 10 +++++++++- .../noppes/npcs/controllers/data/SkinOverlay.java | 13 +++++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/api b/src/api index 0d31b56a1..658da549e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 0d31b56a12dd18f611505047402d2251a6dd0906 +Subproject commit 658da549eec622ed7bcfb331f655938e8841520a diff --git a/src/main/java/noppes/npcs/client/model/ModelMPM.java b/src/main/java/noppes/npcs/client/model/ModelMPM.java index 7a8273a77..76bf0e591 100644 --- a/src/main/java/noppes/npcs/client/model/ModelMPM.java +++ b/src/main/java/noppes/npcs/client/model/ModelMPM.java @@ -422,7 +422,7 @@ public class ModelMPM extends ModelNPCMale { Minecraft.getMinecraft().entityRenderer.disableLightmap((double) 0); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.getAlpha()); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!npc.isInvisible()); @@ -522,6 +522,14 @@ public class ModelMPM extends ModelNPCMale { } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + @Override public void setRotationAngles(float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, diff --git a/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java b/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java index 7f532c2ad..d1042628e 100644 --- a/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java +++ b/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java @@ -79,7 +79,7 @@ public class RenderCNPCPlayer extends RenderPlayer { RenderHelper.disableStandardItemLighting(); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.alpha); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!player.isInvisible()); @@ -97,6 +97,14 @@ public class RenderCNPCPlayer extends RenderPlayer { return true; } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + public void postRenderOverlay() { GL11.glPopMatrix(); diff --git a/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java b/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java index 2d1d1f258..f3c91d547 100644 --- a/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java +++ b/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java @@ -482,7 +482,7 @@ public class RenderNPCInterface extends RenderLiving { Minecraft.getMinecraft().entityRenderer.disableLightmap((double) 0); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.getAlpha()); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!npc.isInvisible()); @@ -521,6 +521,14 @@ public class RenderNPCInterface extends RenderLiving { } } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + @Override protected float handleRotationFloat(EntityLivingBase par1EntityLiving, float par2) { EntityNPCInterface npc = (EntityNPCInterface) par1EntityLiving; diff --git a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java index 4b0cde375..bc08a8033 100644 --- a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java +++ b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java @@ -10,8 +10,10 @@ public class SkinOverlay implements ISkinOverlay { public boolean glow = true; public boolean blend = true; + public float alpha = 1.0F; public float size = 1.0F; + public int color = 0xFFFFFF; public float speedX = 0.0F; public float speedY = 0.0F; @@ -78,6 +80,15 @@ public class SkinOverlay implements ISkinOverlay { return size; } + public void setColor(int color) { + this.color = Math.max(0, color); + this.updateClient(); + } + + public int getColor() { + return color; + } + public void setTextureScale(float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; @@ -131,6 +142,7 @@ public class SkinOverlay implements ISkinOverlay { this.blend = compound.getBoolean("SkinOverlayBlend"); this.alpha = compound.getFloat("SkinOverlayAlpha"); this.size = compound.getFloat("SkinOverlaySize"); + this.color = compound.getInteger("SkinOverlayColor"); this.speedX = compound.getFloat("SkinOverlaySpeedX"); this.speedY = compound.getFloat("SkinOverlaySpeedY"); this.scaleX = compound.getFloat("SkinOverlayScaleX"); @@ -146,6 +158,7 @@ public class SkinOverlay implements ISkinOverlay { compound.setBoolean("SkinOverlayBlend", this.getBlend()); compound.setFloat("SkinOverlayAlpha", this.getAlpha()); compound.setFloat("SkinOverlaySize", this.getSize()); + compound.setInteger("SkinOverlayColor", this.getColor()); compound.setFloat("SkinOverlaySpeedX", this.getSpeedX()); compound.setFloat("SkinOverlaySpeedY", this.getSpeedY()); compound.setFloat("SkinOverlayScaleX", this.getTextureScaleX()); From 3de8cd49b61e3c063cc96816bf4e939dc337b62a Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 16:33:01 -0500 Subject: [PATCH 49/71] Better Filename Handling for Abilities, Dash Directional Improvements, Fixed Duplicate Ability Name Handling --- .../npcs/controllers/AbilityController.java | 291 +++++++++++++----- .../controllers/data/ability/Ability.java | 69 ++++- .../data/ability/data/ChainedAbility.java | 12 +- .../data/ability/enums/InvulnerableMode.java | 66 ++++ .../data/ability/type/AbilityDash.java | 41 ++- .../data/ability/type/AbilityMovement.java | 37 +-- .../ability/PlayerAbilityStatePacket.java | 2 + .../kamkeel/npcs/util/FileNameHelper.java | 97 ++++++ .../noppes/npcs/AbstractDataAbilities.java | 9 + src/main/java/noppes/npcs/DataAbilities.java | 9 +- .../noppes/npcs/ScriptPlayerEventHandler.java | 7 +- .../npcs/client/ClientAbilityState.java | 3 + .../noppes/npcs/client/ClientTickHandler.java | 30 +- .../advanced/SubGuiAbilitySaveConfirm.java | 6 +- .../advanced/SubGuiDuplicateNameConfirm.java | 34 +- .../client/gui/builder/GuiFieldBuilder.java | 3 + .../gui/global/GuiNpcManageAbilities.java | 50 +-- .../hud/ability/AbilityHotbarComponent.java | 24 +- .../ability/AbilityHotbarSlotRenderer.java | 57 +++- .../npcs/client/gui/util/GuiNpcTextField.java | 67 +++- .../controllers/data/PlayerAbilityData.java | 6 + .../npcs/entity/EntityNPCInterface.java | 3 + .../assets/customnpcs/lang/en_US.lang | 7 + 23 files changed, 752 insertions(+), 178 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java create mode 100644 src/main/java/kamkeel/npcs/util/FileNameHelper.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 9a55175df..1591c2514 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -11,6 +11,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.extender.IAbilityExtender; @@ -45,6 +46,7 @@ import noppes.npcs.api.handler.IAbilityHandler; import noppes.npcs.controllers.PlayerDataController; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.util.NBTJsonUtil; +import kamkeel.npcs.util.FileNameHelper; import java.io.File; import java.util.*; @@ -213,6 +215,7 @@ public class AbilityController implements IAbilityHandler { customAbilities.clear(); customAbilitiesById.clear(); int migrated = 0; + Map migratedAbilityNames = new HashMap(); File dir = getDir(); File[] files = dir.exists() ? dir.listFiles() : null; @@ -221,7 +224,7 @@ public class AbilityController implements IAbilityHandler { if (!file.isFile() || !file.getName().endsWith(".json")) continue; try { String filename = file.getName(); - String key = filename.substring(0, filename.length() - 5); + String fileKey = filename.substring(0, filename.length() - 5); NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); Ability ability = fromNBT(nbt); @@ -234,48 +237,55 @@ public class AbilityController implements IAbilityHandler { continue; } - // Legacy migration: UUID-named files get renamed to name-based - if (UUID_PATTERN.matcher(key).matches()) { - String name = ability.getName(); - if (name == null || name.isEmpty()) { - name = key; // Fallback to UUID if no name stored - } + String storedName = ability.getName(); + String preferredName; + if (UUID_PATTERN.matcher(fileKey).matches()) { + preferredName = (storedName != null && !storedName.isEmpty()) ? storedName : fileKey; + } else { + preferredName = fileKey; + } + String name = FileNameHelper.sanitizeName(preferredName, "Ability"); + name = makeUniqueNameForLoad(customAbilities, dir, file, name, fileKey); - // Ensure unique name - while (hasCustomAbilityName(name) || nameFileExists(dir, name)) { - name = name + "_"; - } - ability.setName(name); - - // Rename file from UUID.json to name.json - File newFile = new File(dir, name + ".json"); - if (file.renameTo(newFile)) { - LogWriter.info("Migrated ability file: " + key + ".json -> " + name + ".json"); + boolean dirty = false; + if (!name.equals(fileKey)) { + File renamedFile = new File(dir, name + ".json"); + if (file.renameTo(renamedFile)) { + LogWriter.info("Migrated ability file: " + fileKey + ".json -> " + name + ".json"); + file = renamedFile; + migrated++; + dirty = true; } else { - LogWriter.error("Failed to rename ability file: " + key + ".json -> " + name + ".json"); + LogWriter.error("Failed to rename ability file: " + fileKey + ".json -> " + name + ".json"); } + } - key = name; - migrated++; + ability.setName(name); + if (storedName != null && !storedName.equals(name)) { + dirty = true; + if (!storedName.isEmpty()) { + migratedAbilityNames.put(storedName, name); + } + } + if (!fileKey.equals(name)) { + migratedAbilityNames.put(fileKey, name); } // Ensure ability has a UUID; generate one for legacy abilities String uuid = ability.getId(); - if (uuid == null || uuid.isEmpty() || uuid.equals(key)) { + if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) { // Legacy: id was the name or empty — generate a real UUID uuid = UUID.randomUUID().toString(); ability.setId(uuid); - // Re-save file with the new UUID - try { - NBTJsonUtil.SaveFile(file, ability.writeNBT()); - } catch (Exception ex) { - LogWriter.error("Failed to save UUID for ability: " + key, ex); - } migrated++; + dirty = true; } - ability.setName(key); - customAbilities.put(key, ability); + if (dirty) { + NBTJsonUtil.SaveFile(file, ability.writeNBT()); + } + + customAbilities.put(name, ability); customAbilitiesById.put(uuid, ability); } catch (Exception e) { LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e); @@ -285,23 +295,68 @@ public class AbilityController implements IAbilityHandler { customAbilityRevision++; if (migrated > 0) { - LogWriter.info("Migrated " + migrated + " abilities (UUID-named files or missing UUIDs)"); + LogWriter.info("Migrated " + migrated + " abilities (filename normalization / UUID repair)"); } LogWriter.info("Loaded " + customAbilities.size() + " custom abilities"); - loadChainedAbilities(); + loadChainedAbilities(migratedAbilityNames); } - private boolean nameFileExists(File dir, String name) { - return new File(dir, name + ".json").exists(); + private boolean nameFileExists(File dir, String name, File ignoredFile) { + File existing = new File(dir, name + ".json"); + if (!existing.exists()) return false; + return ignoredFile == null || !existing.getAbsolutePath().equals(ignoredFile.getAbsolutePath()); + } + + private String makeUniqueNameForLoad(Map loadedMap, File dir, File currentFile, String baseName, String selfKey) { + String candidate = baseName; + while ((loadedMap.containsKey(candidate) && !candidate.equals(selfKey)) + || nameFileExists(dir, candidate, currentFile)) { + candidate = candidate + "_"; + } + return candidate; + } + + private String makeUniqueNameForMap(Map map, String baseName, String selfName) { + String candidate = baseName; + while (map.containsKey(candidate) && (selfName == null || !candidate.equals(selfName))) { + candidate = candidate + "_"; + } + return candidate; + } + + private boolean remapChainEntryReferences(ChainedAbility chain, Map migratedAbilityNames) { + if (chain == null || migratedAbilityNames == null || migratedAbilityNames.isEmpty()) return false; + boolean changed = false; + for (ChainedAbilityEntry entry : chain.getEntries()) { + if (entry == null || !entry.isReference()) continue; + String ref = entry.getAbilityReference(); + if (ref == null || ref.isEmpty()) continue; + + String mapped = migratedAbilityNames.get(ref); + if (mapped == null) { + for (Map.Entry e : migratedAbilityNames.entrySet()) { + if (e.getKey().equalsIgnoreCase(ref)) { + mapped = e.getValue(); + break; + } + } + } + + if (mapped != null && !mapped.equals(ref)) { + entry.setAbilityReference(mapped); + changed = true; + } + } + return changed; } public boolean saveCustomAbility(Ability ability) { if (ability == null) return false; if (ability.isBuiltIn()) return false; - String name = ability.getName(); - if (name == null || name.isEmpty()) return false; + String name = FileNameHelper.sanitizeName(ability.getName(), "Ability"); + ability.setName(name); String uuid = ability.getId(); boolean isNew = (uuid == null || uuid.isEmpty()); @@ -312,26 +367,17 @@ public class AbilityController implements IAbilityHandler { ability.setId(uuid); // Ensure unique name - while (hasCustomAbilityName(name)) { - name = name + "_"; - } + name = makeUniqueNameForMap(customAbilities, name, null); ability.setName(name); } else { // Existing ability — check for rename via UUID lookup Ability existing = customAbilitiesById.get(uuid); if (existing != null) { String oldName = existing.getName(); - if (!oldName.equals(name)) { - // Name changed! Ensure new name is unique (excluding self) - String testName = name; - while (customAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - ability.setName(name); - } + name = makeUniqueNameForMap(customAbilities, name, oldName); + ability.setName(name); + if (!oldName.equals(name)) { // Delete old file and remove old map entry File oldFile = new File(getDir(), oldName + ".json"); if (oldFile.exists()) { @@ -339,6 +385,9 @@ public class AbilityController implements IAbilityHandler { } customAbilities.remove(oldName); } + } else { + name = makeUniqueNameForMap(customAbilities, name, null); + ability.setName(name); } } @@ -374,6 +423,15 @@ public class AbilityController implements IAbilityHandler { if (name == null || name.isEmpty()) return false; Ability removed = customAbilities.remove(name); + if (removed == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + removed = customAbilities.remove(sanitized); + if (removed != null) { + name = sanitized; + } + } + } if (removed == null) return false; // Also remove from UUID index @@ -412,7 +470,13 @@ public class AbilityController implements IAbilityHandler { } public Ability getCustomAbility(String name) { - return customAbilities.get(name); + Ability ability = customAbilities.get(name); + if (ability != null) return ability; + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + return customAbilities.get(sanitized); + } + return null; } public Set getCustomAbilityNames() { @@ -452,9 +516,10 @@ public class AbilityController implements IAbilityHandler { // CHAINED ABILITIES // ═══════════════════════════════════════════════════════════════════════════ - private void loadChainedAbilities() { + private void loadChainedAbilities(Map migratedAbilityNames) { chainedAbilities.clear(); chainedAbilitiesById.clear(); + int migrated = 0; File dir = getChainedDir(); File[] files = dir.exists() ? dir.listFiles() : null; @@ -463,19 +528,51 @@ public class AbilityController implements IAbilityHandler { if (!file.isFile() || !file.getName().endsWith(".json")) continue; try { String filename = file.getName(); - String key = filename.substring(0, filename.length() - 5); + String fileKey = filename.substring(0, filename.length() - 5); NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); ChainedAbility chain = new ChainedAbility(); chain.readNBT(nbt); - chain.setName(key); - chainedAbilities.put(key, chain); + String storedName = chain.getName(); + String name = FileNameHelper.sanitizeName(fileKey, "Chain"); + name = makeUniqueNameForLoad(chainedAbilities, dir, file, name, fileKey); + + boolean dirty = false; + if (!name.equals(fileKey)) { + File renamedFile = new File(dir, name + ".json"); + if (file.renameTo(renamedFile)) { + LogWriter.info("Migrated chained ability file: " + fileKey + ".json -> " + name + ".json"); + file = renamedFile; + dirty = true; + } else { + LogWriter.error("Failed to rename chained ability file: " + fileKey + ".json -> " + name + ".json"); + } + } + + chain.setName(name); + if (storedName != null && !storedName.equals(name)) { + dirty = true; + } + + if (remapChainEntryReferences(chain, migratedAbilityNames)) { + dirty = true; + } String uuid = chain.getId(); - if (uuid != null && !uuid.isEmpty()) { - chainedAbilitiesById.put(uuid, chain); + if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) { + uuid = UUID.randomUUID().toString(); + chain.setId(uuid); + dirty = true; } + + if (dirty) { + NBTJsonUtil.SaveFile(file, chain.writeNBT()); + migrated++; + } + + chainedAbilities.put(name, chain); + chainedAbilitiesById.put(uuid, chain); } catch (Exception e) { LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e); } @@ -483,14 +580,17 @@ public class AbilityController implements IAbilityHandler { } chainedAbilityRevision++; + if (migrated > 0) { + LogWriter.info("Migrated " + migrated + " chained abilities"); + } LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities"); } public boolean saveChainedAbility(ChainedAbility chain) { if (chain == null) return false; - String name = chain.getName(); - if (name == null || name.isEmpty()) return false; + String name = FileNameHelper.sanitizeName(chain.getName(), "Chain"); + chain.setName(name); String uuid = chain.getId(); boolean isNew = (uuid == null || uuid.isEmpty()); @@ -499,30 +599,25 @@ public class AbilityController implements IAbilityHandler { uuid = UUID.randomUUID().toString(); chain.setId(uuid); - while (hasChainedAbilityName(name)) { - name = name + "_"; - } + name = makeUniqueNameForMap(chainedAbilities, name, null); chain.setName(name); } else { ChainedAbility existing = chainedAbilitiesById.get(uuid); if (existing != null) { String oldName = existing.getName(); - if (!oldName.equals(name)) { - String testName = name; - while (chainedAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - chain.setName(name); - } + name = makeUniqueNameForMap(chainedAbilities, name, oldName); + chain.setName(name); + if (!oldName.equals(name)) { File oldFile = new File(getChainedDir(), oldName + ".json"); if (oldFile.exists()) { oldFile.delete(); } chainedAbilities.remove(oldName); } + } else { + name = makeUniqueNameForMap(chainedAbilities, name, null); + chain.setName(name); } } @@ -558,6 +653,15 @@ public class AbilityController implements IAbilityHandler { if (name == null || name.isEmpty()) return false; ChainedAbility removed = chainedAbilities.remove(name); + if (removed == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + removed = chainedAbilities.remove(sanitized); + if (removed != null) { + name = sanitized; + } + } + } if (removed == null) return false; String uuid = removed.getId(); @@ -608,6 +712,12 @@ public class AbilityController implements IAbilityHandler { } } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key)) { + ChainedAbility sanitized = chainedAbilities.get(sanitizedKey); + if (sanitized != null) return sanitized.deepCopy(); + } + return null; } @@ -618,12 +728,20 @@ public class AbilityController implements IAbilityHandler { for (String name : chainedAbilities.keySet()) { if (name.equalsIgnoreCase(key)) return true; } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key) && chainedAbilities.containsKey(sanitizedKey)) return true; return false; } @Override public ChainedAbility getChainedAbility(String name) { ChainedAbility chain = chainedAbilities.get(name); + if (chain == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + chain = chainedAbilities.get(sanitized); + } + } return chain != null ? chain.deepCopy() : null; } @@ -646,7 +764,9 @@ public class AbilityController implements IAbilityHandler { @Override public boolean hasChainedAbilityName(String name) { - return chainedAbilities.containsKey(name); + if (chainedAbilities.containsKey(name)) return true; + String sanitized = FileNameHelper.sanitizeTextInput(name); + return !sanitized.equals(name) && chainedAbilities.containsKey(sanitized); } @Override @@ -734,6 +854,12 @@ public class AbilityController implements IAbilityHandler { } } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key)) { + Ability sanitized = customAbilities.get(sanitizedKey); + if (sanitized != null) return sanitized.deepCopy(); + } + return null; } @@ -778,8 +904,14 @@ public class AbilityController implements IAbilityHandler { } public boolean hasAbility(String key) { - return builtAbilities.containsKey(key) || customAbilities.containsKey(key) - || customAbilitiesById.containsKey(key); + if (builtAbilities.containsKey(key) || customAbilities.containsKey(key) + || customAbilitiesById.containsKey(key)) { + return true; + } + String sanitized = FileNameHelper.sanitizeTextInput(key); + return !sanitized.equals(key) + && (builtAbilities.containsKey(sanitized) || customAbilities.containsKey(sanitized) + || customAbilitiesById.containsKey(sanitized)); } public Ability getCustomAbilityByUUID(String uuid) { @@ -787,7 +919,13 @@ public class AbilityController implements IAbilityHandler { } public Ability getCustomAbilityByName(String name) { - return customAbilities.get(name); + Ability ability = customAbilities.get(name); + if (ability != null) return ability; + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + return customAbilities.get(sanitized); + } + return null; } /** @@ -821,6 +959,9 @@ public class AbilityController implements IAbilityHandler { if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) return true; } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key) && customAbilities.containsKey(sanitizedKey)) return true; + return false; } @@ -1128,7 +1269,9 @@ public class AbilityController implements IAbilityHandler { @Override public boolean hasCustomAbilityName(String name) { - return customAbilities.containsKey(name); + if (customAbilities.containsKey(name)) return true; + String sanitized = FileNameHelper.sanitizeTextInput(name); + return !sanitized.equals(name) && customAbilities.containsKey(sanitized); } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 8bafe49f0..4a8558218 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -7,6 +7,7 @@ import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.InvulnerableMode; import kamkeel.npcs.controllers.data.ability.enums.LockMode; import kamkeel.npcs.controllers.data.ability.preview.PreviewEntityHandler; import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; @@ -21,6 +22,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityAbilityPanel; +import kamkeel.npcs.util.FileNameHelper; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -85,6 +87,7 @@ public abstract class Ability implements IAbility, IAbilityAction { // Interruption protected boolean interruptible = true; + protected InvulnerableMode invulnerableMode = InvulnerableMode.NONE; // Feedback protected LockMode lockMovement = LockMode.WINDUP; @@ -687,10 +690,24 @@ public abstract class Ability implements IAbility, IAbilityAction { ).tab("General")); defs.add(FieldDef.row( FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible) - .hover("ability.hover.interruptible"), + .hover("ability.hover.interruptible") + .enabledWhen(() -> !invulnerableMode.invulnerableDuringWindup()), FieldDef.intField("ability.dazedTicks", this::getDazedTicks, this::setDazedTicks) - .range(0, 1000).visibleWhen(this::isInterruptible) + .range(0, 1000) + .visibleWhen(() -> isInterruptible() && !invulnerableMode.invulnerableDuringWindup()) ).tab("General")); + defs.add(FieldDef.stringEnumField("ability.invulnerable", InvulnerableMode.getDisplayKeys(), + () -> this.getInvulnerableMode().getDisplayKey(), + v -> { + for (InvulnerableMode mode : InvulnerableMode.values()) { + if (mode.getDisplayKey().equals(v)) { + this.setInvulnerableMode(mode); + break; + } + } + }) + .hover("ability.hover.invulnerable") + .tab("General")); // ── Burst section ──────────────────────────────────────────── if (allowBurst()) { @@ -1174,6 +1191,9 @@ public abstract class Ability implements IAbility, IAbilityAction { if (!interruptible || (phase != AbilityPhase.WINDUP && phase != AbilityPhase.BURST_DELAY)) { return false; } + if (isInvulnerableForCurrentPhase()) { + return false; + } // Only direct physical hits can interrupt, not magic, fire, or other indirect damage if (source == null) { @@ -1332,6 +1352,7 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setBoolean("syncWindup", syncWindupWithAnimation); nbt.setInteger("recovery", dazedTicks); nbt.setBoolean("interruptible", interruptible); + nbt.setInteger("invulnerableMode", invulnerableMode.ordinal()); nbt.setInteger("lockMovement", lockMovement.ordinal()); nbt.setInteger("rotationMode", rotationMode.ordinal()); nbt.setInteger("rotationPhase", rotationPhase.ordinal()); @@ -1411,6 +1432,9 @@ public abstract class Ability implements IAbility, IAbilityAction { syncWindupWithAnimation = nbt.hasKey("syncWindup") ? nbt.getBoolean("syncWindup") : true; dazedTicks = nbt.hasKey("recovery") ? nbt.getInteger("recovery") : 80; interruptible = nbt.hasKey("interruptible") ? nbt.getBoolean("interruptible") : true; + invulnerableMode = nbt.hasKey("invulnerableMode") + ? InvulnerableMode.fromOrdinal(nbt.getInteger("invulnerableMode")) + : InvulnerableMode.NONE; lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement")); rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode")); rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase")); @@ -1501,7 +1525,12 @@ public abstract class Ability implements IAbility, IAbilityAction { } public void setName(String name) { - this.name = name; + if (builtIn) { + this.name = name != null ? name : ""; + return; + } + String fallback = (this.name != null && !this.name.isEmpty()) ? this.name : "Ability"; + this.name = FileNameHelper.sanitizeName(name, fallback); } /** @@ -1510,7 +1539,9 @@ public abstract class Ability implements IAbility, IAbilityAction { * Converts & color codes to § for rendering. */ public String getDisplayName() { - String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) + ? displayName + : FileNameHelper.toDisplayName(name); return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } @@ -1664,6 +1695,22 @@ public abstract class Ability implements IAbility, IAbilityAction { this.interruptible = interruptible; } + public InvulnerableMode getInvulnerableMode() { + return invulnerableMode; + } + + public void setInvulnerableMode(InvulnerableMode invulnerableMode) { + this.invulnerableMode = invulnerableMode != null ? invulnerableMode : InvulnerableMode.NONE; + } + + public boolean isInvulnerableDuringWindup() { + return invulnerableMode.invulnerableDuringWindup(); + } + + public boolean isInvulnerableDuringActive() { + return invulnerableMode.invulnerableDuringActive(); + } + public LockMode getLockMovement() { return lockMovement; } @@ -1796,6 +1843,20 @@ public abstract class Ability implements IAbility, IAbilityAction { } } + /** + * Check if this ability grants invulnerability during its current execution phase. + */ + public boolean isInvulnerableForCurrentPhase() { + return invulnerableMode.isInvulnerableInPhase(phase); + } + + /** + * Check if this ability grants invulnerability for a specific phase. + */ + public boolean isInvulnerableForPhase(AbilityPhase phase) { + return invulnerableMode.isInvulnerableInPhase(phase); + } + /** * Check if rotation should be locked during the current phase. */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java index f9927d42e..fe684da20 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java @@ -9,6 +9,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import noppes.npcs.api.ability.IChainedAbility; +import kamkeel.npcs.util.FileNameHelper; import java.util.ArrayList; import java.util.List; @@ -77,7 +78,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { } public ChainedAbility(String name) { - this.name = name != null ? name : ""; + setName(name); } // ═══════════════════════════════════════════════════════════════════ @@ -97,7 +98,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { } public void setName(String name) { - this.name = name != null ? name : ""; + String fallback = (this.name != null && !this.name.isEmpty()) ? this.name : "Chain"; + this.name = FileNameHelper.sanitizeName(name, fallback); } /** @@ -106,7 +108,9 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { * Converts & color codes to § for rendering. */ public String getDisplayName() { - String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) + ? displayName + : FileNameHelper.toDisplayName(name); return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } @@ -406,7 +410,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { public void readNBT(NBTTagCompound nbt) { id = nbt.getString("Id"); - name = nbt.getString("Name"); + setName(nbt.getString("Name")); displayName = nbt.getString("DisplayName"); enabled = !nbt.hasKey("Enabled") || nbt.getBoolean("Enabled"); weight = nbt.hasKey("Weight") ? nbt.getInteger("Weight") : 10; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java new file mode 100644 index 000000000..fc6dc5169 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java @@ -0,0 +1,66 @@ +package kamkeel.npcs.controllers.data.ability.enums; + +/** + * Defines when a caster is invulnerable while an ability is executing. + */ +public enum InvulnerableMode { + NONE, + WINDUP, + ACTIVE, + BOTH; + + public boolean invulnerableDuringWindup() { + return this == WINDUP || this == BOTH; + } + + public boolean invulnerableDuringActive() { + return this == ACTIVE || this == BOTH; + } + + /** + * Treat BURST_DELAY as windup-like for interruption/damage immunity checks. + */ + public boolean isInvulnerableInPhase(AbilityPhase phase) { + if (phase == null) return false; + switch (phase) { + case WINDUP: + case BURST_DELAY: + return invulnerableDuringWindup(); + case ACTIVE: + return invulnerableDuringActive(); + default: + return false; + } + } + + public String getDisplayKey() { + switch (this) { + case WINDUP: + return "ability.invulnerable.windup"; + case ACTIVE: + return "ability.invulnerable.active"; + case BOTH: + return "ability.invulnerable.both"; + case NONE: + default: + return "ability.invulnerable.none"; + } + } + + public static String[] getDisplayKeys() { + return new String[]{ + "ability.invulnerable.none", + "ability.invulnerable.windup", + "ability.invulnerable.active", + "ability.invulnerable.both" + }; + } + + public static InvulnerableMode fromOrdinal(int ordinal) { + InvulnerableMode[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return NONE; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index fb228fc3e..26600ef0c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -91,6 +91,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { // Type-specific runtime state private transient DashDirection chosenDirection; + private transient double preDashMotionX; + private transient double preDashMotionZ; public AbilityDash() { this.typeId = "ability.cnpc.dash"; @@ -125,6 +127,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { + preDashMotionX = caster.motionX; + preDashMotionZ = caster.motionZ; initMovement(caster, dashDistance, dashSpeed); if (dashMode == DashMode.DIRECTIONAL) { @@ -151,37 +155,46 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { if (checkTimeout(tick)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } if (movementDirection == null) { + finishDash(caster); signalCompletion(); return; } if (checkStall(caster, tick)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } updatePrevPosition(caster); if (getDistanceTraveledSq(caster) >= (double) dashDistance * dashDistance) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } if (checkBlocked(caster, dashSpeed)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } - // Move caster (motionY left to gravity for skip arc) - applyVelocity(caster, dashSpeed); + // Move caster (motionY left to gravity for skip arc). + // Directional mode layers the dash over incoming movement so momentum + // entering/exiting the dash stays fluid. + if (dashMode == DashMode.DIRECTIONAL) { + applyHorizontalMomentum(caster, + preDashMotionX + movementDirection.xCoord * dashSpeed, + preDashMotionZ + movementDirection.zCoord * dashSpeed); + } else { + applyVelocity(caster, dashSpeed); + } if (!isPreview()) { // Trail particles @@ -191,24 +204,28 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onComplete(EntityLivingBase caster, EntityLivingBase target) { - stopMomentum(caster); + finishDash(caster); } @Override public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { - stopMomentum(caster); + finishDash(caster); } @Override public void cleanup() { super.cleanup(); chosenDirection = null; + preDashMotionX = 0; + preDashMotionZ = 0; } @Override public void resetForBurst() { super.resetForBurst(); chosenDirection = null; + preDashMotionX = 0; + preDashMotionZ = 0; } @Override @@ -279,6 +296,14 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { return chosenDirection; } + private void finishDash(EntityLivingBase caster) { + if (dashMode == DashMode.DIRECTIONAL) { + applyHorizontalMomentum(caster, preDashMotionX, preDashMotionZ); + } else { + stopMomentum(caster); + } + } + @SideOnly(Side.CLIENT) @Override public void getAbilityDefinitions(List defs) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index 331b91920..07810bfe3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -226,7 +226,7 @@ public abstract class AbilityMovement extends Ability { * By sending S12 manually here — before friction — the client receives the full * velocity and moves at the correct speed. */ - private void sendPlayerVelocity(EntityLivingBase caster) { + protected void sendPlayerVelocity(EntityLivingBase caster) { if (caster instanceof EntityPlayerMP) { ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( new S12PacketEntityVelocity(caster)); @@ -234,11 +234,11 @@ public abstract class AbilityMovement extends Ability { } /** - * Apply movement velocity to the caster (horizontal only, Y unchanged). + * Apply horizontal momentum and sync to client. */ - protected void applyVelocity(EntityLivingBase caster, float speed) { - caster.motionX = movementDirection.xCoord * speed; - caster.motionZ = movementDirection.zCoord * speed; + protected void applyHorizontalMomentum(EntityLivingBase caster, double motionX, double motionZ) { + caster.motionX = motionX; + caster.motionZ = motionZ; if (!isPreview()) { if (caster instanceof EntityPlayerMP) { sendPlayerVelocity(caster); @@ -248,36 +248,27 @@ public abstract class AbilityMovement extends Ability { } } + /** + * Apply movement velocity to the caster (horizontal only, Y unchanged). + */ + protected void applyVelocity(EntityLivingBase caster, float speed) { + applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); + } + /** * Apply movement velocity with zero vertical motion. * Used for ground-locked movement like Charge. */ protected void applyVelocityFlat(EntityLivingBase caster, float speed) { - caster.motionX = movementDirection.xCoord * speed; + applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); caster.motionY = 0; - caster.motionZ = movementDirection.zCoord * speed; - if (!isPreview()) { - if (caster instanceof EntityPlayerMP) { - sendPlayerVelocity(caster); - } else { - caster.velocityChanged = true; - } - } } /** * Stop all horizontal momentum. */ protected void stopMomentum(EntityLivingBase caster) { - caster.motionX = 0; - caster.motionZ = 0; - if (!isPreview()) { - if (caster instanceof EntityPlayerMP) { - sendPlayerVelocity(caster); - } else { - caster.velocityChanged = true; - } - } + applyHorizontalMomentum(caster, 0, 0); } // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java index 256a38299..5d62b1bcf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java @@ -22,6 +22,7 @@ import java.io.IOException; * bit 1: rotation locked (freeze yaw/pitch) * bit 2: has ability movement (ability controls motion — suppress WASD but don't zero) * bit 3: position locked (freeze position) + * bit 5: ability is in ACTIVE phase */ public final class PlayerAbilityStatePacket extends AbstractPacket { public static final String packetName = "Data|PlayerAbilityState"; @@ -31,6 +32,7 @@ public final class PlayerAbilityStatePacket extends AbstractPacket { public static final byte FLAG_HAS_ABILITY_MOVEMENT = 4; public static final byte FLAG_POSITION_LOCKED = 8; public static final byte FLAG_WAS_FLYING_AT_LOCK = 16; + public static final byte FLAG_ACTIVE_PHASE = 32; private byte flags; private float lockedYaw; diff --git a/src/main/java/kamkeel/npcs/util/FileNameHelper.java b/src/main/java/kamkeel/npcs/util/FileNameHelper.java new file mode 100644 index 000000000..6248600fb --- /dev/null +++ b/src/main/java/kamkeel/npcs/util/FileNameHelper.java @@ -0,0 +1,97 @@ +package kamkeel.npcs.util; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Utility helpers for filename-safe ability/chained names. + * Allowed characters are letters, digits, underscore, and dash. + */ +public final class FileNameHelper { + + private static final Set WINDOWS_RESERVED_NAMES = new HashSet(Arrays.asList( + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" + )); + + private FileNameHelper() { + } + + /** + * Sanitize a name to a filename-safe identifier. + * Whitespace becomes underscores; unsupported chars are removed. + */ + public static String sanitizeName(String input, String fallback) { + String sanitized = sanitizeInternal(input); + if (sanitized.isEmpty()) { + sanitized = sanitizeInternal(fallback); + } + if (sanitized.isEmpty()) { + sanitized = "Ability"; + } + if (isReservedWindowsName(sanitized)) { + sanitized = "_" + sanitized; + } + return sanitized; + } + + /** + * Sanitize textfield input without forcing a fallback. + */ + public static String sanitizeTextInput(String input) { + return sanitizeInternal(input); + } + + /** + * Quick check for compatibility with sanitized identifier rules. + */ + public static boolean isFileNameCompatible(String value) { + if (value == null || value.isEmpty()) return false; + if (isReservedWindowsName(value)) return false; + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_' && c != '-') { + return false; + } + } + return true; + } + + /** + * Human-readable fallback display text for sanitized names. + */ + public static String toDisplayName(String value) { + if (value == null || value.isEmpty()) return ""; + return value.replace('_', ' '); + } + + private static String sanitizeInternal(String raw) { + if (raw == null) return ""; + String trimmed = raw.trim(); + if (trimmed.isEmpty()) return ""; + + StringBuilder out = new StringBuilder(trimmed.length()); + + for (int i = 0; i < trimmed.length(); i++) { + char c = trimmed.charAt(i); + if (Character.isLetterOrDigit(c)) { + out.append(c); + } else if (c == '_' || c == '-') { + out.append(c); + } else if (Character.isWhitespace(c)) { + if (out.length() == 0) continue; + char last = out.charAt(out.length() - 1); + if (last == '_') continue; + out.append('_'); + } + } + + return out.toString(); + } + + private static boolean isReservedWindowsName(String value) { + return WINDOWS_RESERVED_NAMES.contains(value.toUpperCase()); + } +} diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index a2b94fdc9..92103a102 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -268,6 +268,15 @@ public abstract class AbstractDataAbilities { return currentAbility != null && currentAbility.isExecuting(); } + /** + * True when an executing ability grants invulnerability for its current phase. + */ + public boolean isCurrentAbilityInvulnerable() { + return currentAbility != null + && currentAbility.isExecuting() + && currentAbility.isInvulnerableForCurrentPhase(); + } + /** * Get the currently executing ability. */ diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 68d1231d7..a21eace79 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -555,12 +555,15 @@ public class DataAbilities extends AbstractDataAbilities { * @return true if ability was interrupted */ public boolean onDamage(DamageSource source, float amount) { - // Track hit for hit count condition - recordHit(); - if (currentAbility == null || !currentAbility.isExecuting()) { return false; } + if (currentAbility.isInvulnerableForCurrentPhase()) { + return false; + } + + // Track hit for hit count condition + recordHit(); net.minecraft.entity.Entity sourceEntity = source.getEntity(); EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 2cadbfd9f..5b5efccbe 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -629,7 +629,12 @@ public class ScriptPlayerEventHandler { // Handle ability damage interactions (Guard counter, damage reduction) PlayerData pData = PlayerData.get((EntityPlayer) event.entityLiving); if (pData != null && pData.abilityData != null && pData.abilityData.isExecutingAbility()) { - event.ammount = pData.abilityData.onDamage(event.source, event.ammount); + if (pData.abilityData.isCurrentAbilityInvulnerable()) { + event.ammount = 0; + cancel = true; + } else { + event.ammount = pData.abilityData.onDamage(event.source, event.ammount); + } } PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) event.entityLiving); diff --git a/src/main/java/noppes/npcs/client/ClientAbilityState.java b/src/main/java/noppes/npcs/client/ClientAbilityState.java index c737024fd..fb3d88c6e 100644 --- a/src/main/java/noppes/npcs/client/ClientAbilityState.java +++ b/src/main/java/noppes/npcs/client/ClientAbilityState.java @@ -16,6 +16,7 @@ public class ClientAbilityState { public static boolean hasAbilityMovement = false; public static boolean positionLocked = false; public static boolean wasFlyingAtLock = false; + public static boolean activePhase = false; public static float lockedYaw = 0; public static float lockedPitch = 0; @@ -28,6 +29,7 @@ public class ClientAbilityState { hasAbilityMovement = (flags & PlayerAbilityStatePacket.FLAG_HAS_ABILITY_MOVEMENT) != 0; positionLocked = (flags & PlayerAbilityStatePacket.FLAG_POSITION_LOCKED) != 0; wasFlyingAtLock = (flags & PlayerAbilityStatePacket.FLAG_WAS_FLYING_AT_LOCK) != 0; + activePhase = (flags & PlayerAbilityStatePacket.FLAG_ACTIVE_PHASE) != 0; lockedYaw = yaw; lockedPitch = pitch; } @@ -56,6 +58,7 @@ public class ClientAbilityState { hasAbilityMovement = false; positionLocked = false; wasFlyingAtLock = false; + activePhase = false; lockedYaw = 0; lockedPitch = 0; } diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 3a9416563..dbe6e691d 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -54,6 +54,7 @@ public class ClientTickHandler { private long buttonTime = 0L; private final int[] ignoreKeys = new int[]{157, 29, 54, 42, 184, 56, 220, 219}; private boolean lastSpecialKeyDown = false; + private boolean wasMovementSuppressed = false; @SubscribeEvent(priority = EventPriority.LOWEST) public void onClientTick(TickEvent.ClientTickEvent event) { @@ -84,9 +85,15 @@ public class ClientTickHandler { lastSpecialKeyDown = specialKeyDown; } + boolean suppressMovement = mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput(); + if (!suppressMovement && wasMovementSuppressed) { + syncMovementKeyStates(mc); + } + wasMovementSuppressed = suppressMovement; + // Suppress player input during ability-controlled phases. // Only suppress when no GUI screen is open (screens already capture input). - if (mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput()) { + if (suppressMovement) { // Unpress movement keybinds at the source BEFORE updatePlayerMoveState() reads them. KeyBinding.setKeyBindState(mc.gameSettings.keyBindForward.getKeyCode(), false); KeyBinding.setKeyBindState(mc.gameSettings.keyBindBack.getKeyCode(), false); @@ -301,6 +308,27 @@ public class ClientTickHandler { || keyCode == mc.gameSettings.keyBindSprint.getKeyCode(); } + private void syncMovementKeyStates(Minecraft mc) { + syncKeyBindingState(mc.gameSettings.keyBindForward); + syncKeyBindingState(mc.gameSettings.keyBindBack); + syncKeyBindingState(mc.gameSettings.keyBindLeft); + syncKeyBindingState(mc.gameSettings.keyBindRight); + syncKeyBindingState(mc.gameSettings.keyBindJump); + syncKeyBindingState(mc.gameSettings.keyBindSneak); + syncKeyBindingState(mc.gameSettings.keyBindSprint); + } + + private void syncKeyBindingState(KeyBinding keyBinding) { + int keyCode = keyBinding.getKeyCode(); + boolean pressed; + if (keyCode < 0) { + pressed = Mouse.isButtonDown(keyCode + 100); + } else { + pressed = Keyboard.isKeyDown(keyCode); + } + KeyBinding.setKeyBindState(keyCode, pressed); + } + private boolean isIgnoredKey(int key) { int[] var2 = this.ignoreKeys; int var3 = var2.length; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java index 57a363e1f..a70b0318d 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java @@ -84,8 +84,12 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui @Override public void subGuiClosed(SubGuiInterface subgui) { if (subgui instanceof SubGuiDuplicateNameConfirm) { - if (((SubGuiDuplicateNameConfirm) subgui).isConfirmed()) { + SubGuiDuplicateNameConfirm confirm = (SubGuiDuplicateNameConfirm) subgui; + if (confirm.isConfirmed()) { doSave(); + } else if (confirm.isBack()) { + // Keep this save-confirm dialog open. + return; } else { close(); } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java index b16386c32..5d316f416 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java @@ -10,7 +10,13 @@ import noppes.npcs.client.gui.util.SubGuiInterface; */ public class SubGuiDuplicateNameConfirm extends SubGuiInterface { - private boolean confirmed = false; + public enum Result { + CONTINUE, + BACK, + CANCEL + } + + private Result result = Result.CANCEL; public SubGuiDuplicateNameConfirm() { setBackground("menubg.png"); @@ -26,22 +32,38 @@ public class SubGuiDuplicateNameConfirm extends SubGuiInterface { addLabel(new GuiNpcLabel(0, "ability.duplicate.name", guiLeft + 10, y)); y += 30; - addButton(new GuiNpcButton(0, guiLeft + 20, y, 80, 20, "gui.continue")); - addButton(new GuiNpcButton(1, guiLeft + 120, y, 80, 20, "gui.cancel")); + addButton(new GuiNpcButton(0, guiLeft + 10, y, 65, 20, "gui.continue")); + addButton(new GuiNpcButton(1, guiLeft + 78, y, 65, 20, "gui.back")); + addButton(new GuiNpcButton(2, guiLeft + 146, y, 65, 20, "gui.cancel")); } @Override public void buttonEvent(GuiButton guibutton) { if (guibutton.id == 0) { - confirmed = true; + result = Result.CONTINUE; close(); } else if (guibutton.id == 1) { - confirmed = false; + result = Result.BACK; + close(); + } else if (guibutton.id == 2) { + result = Result.CANCEL; close(); } } + public Result getResult() { + return result; + } + public boolean isConfirmed() { - return confirmed; + return result == Result.CONTINUE; + } + + public boolean isBack() { + return result == Result.BACK; + } + + public boolean isCancelled() { + return result == Result.CANCEL; } } diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index 8d7d88d61..64b81624d 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -245,6 +245,9 @@ public class GuiFieldBuilder { case STRING: { String sVal = def.getValue() != null ? def.getValue().toString() : ""; GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, sVal); + if ("gui.name".equals(def.getLabel())) { + tf.setFileNameSafe(); + } if (!def.isEnabled()) tf.setEnabled(false); if (hover != null) tf.setHoverText(hover); sw.addTextField(tf); diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index 95cce7cf9..64be9e499 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -104,7 +104,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface // ==================== PENDING SAVE ==================== private Ability pendingSaveAbility = null; - private boolean pendingNewCreation = false; // ==================== PENDING VARIANT ==================== private String pendingTypeId = null; @@ -511,13 +510,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface Ability newAbility = AbilityController.Instance.create(pendingTypeId); if (newAbility != null) { gui.getVariants().get(idx).apply(newAbility); - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - pendingTypeId = null; - setSubGui(new SubGuiDuplicateNameConfirm()); - return true; - } pendingTypeId = null; openConfig(newAbility); return true; @@ -544,12 +536,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface if (variants.size() == 1) { variants.get(0).apply(newAbility); } - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - setSubGui(new SubGuiDuplicateNameConfirm()); - return true; - } openConfig(newAbility); return true; } @@ -561,27 +547,22 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface * @return true if a SubGui was opened (caller should return early, skipping initGui) */ private boolean handleDuplicateNameClosed(SubGuiDuplicateNameConfirm gui) { - if (gui.isConfirmed() && pendingSaveAbility != null) { - if (pendingNewCreation) { - openConfig(pendingSaveAbility); - pendingSaveAbility = null; - pendingNewCreation = false; - return true; - } else { - PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); - pendingSaveAbility = null; - } - } else if (pendingSaveAbility != null) { - if (pendingNewCreation) { - pendingSaveAbility = null; - pendingNewCreation = false; - } else { - Ability ability = pendingSaveAbility; - pendingSaveAbility = null; - setSubGui(ability.createConfigGui(this)); - return true; - } + if (pendingSaveAbility == null) return false; + + if (gui.isConfirmed()) { + PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); + pendingSaveAbility = null; + return false; } + + if (gui.isBack()) { + Ability ability = pendingSaveAbility; + pendingSaveAbility = null; + setSubGui(ability.createConfigGui(this)); + return true; + } + + pendingSaveAbility = null; return false; } @@ -590,7 +571,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface */ private boolean handlePendingSave() { if (hasDuplicateName(pendingSaveAbility)) { - pendingNewCreation = false; setSubGui(new SubGuiDuplicateNameConfirm()); return true; } diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index 3bcf1a3c3..eaadbbc7b 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -9,6 +9,7 @@ import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; import net.minecraft.client.Minecraft; import noppes.npcs.client.ClientProxy; +import noppes.npcs.client.ClientAbilityState; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.ScaledResolution; @@ -189,6 +190,7 @@ public class AbilityHotbarComponent extends HudComponent { int centerX = overlayWidth / 2; int centerY = overlayHeight / 2; + boolean activePhase = ClientAbilityState.activePhase; // Compute text visibility: 0=Shown, 1=Hidden, 2=Held boolean showText; @@ -246,16 +248,16 @@ public class AbilityHotbarComponent extends HudComponent { if (rawSlotIndex == -1) { // Deselect slot — draw empty marker - drawDeselectSlot(cx, cy, scaledSize, isCenter, slotAlpha); + drawDeselectSlot(cx, cy, scaledSize, isCenter, slotAlpha, activePhase); } else { - slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha, showText); + slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha, showText, activePhase); } } GL11.glPopMatrix(); } - private void drawDeselectSlot(int cx, int cy, int size, boolean isCenter, float alpha) { + private void drawDeselectSlot(int cx, int cy, int size, boolean isCenter, float alpha, boolean activePhase) { float radius = size / 2f; float baseAlpha = isCenter ? 0.9f : 0.6f; float a = baseAlpha * alpha; @@ -268,7 +270,13 @@ public class AbilityHotbarComponent extends HudComponent { GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - if (isCenter) { + if (activePhase) { + if (isCenter) { + GL11.glColor4f(0.65f, 0.14f, 0.14f, a); + } else { + GL11.glColor4f(0.38f, 0.10f, 0.10f, a); + } + } else if (isCenter) { GL11.glColor4f(0.5f, 0.2f, 0.2f, a); } else { GL11.glColor4f(0.3f, 0.3f, 0.3f, a); @@ -292,7 +300,13 @@ public class AbilityHotbarComponent extends HudComponent { } // Draw X mark - if (isCenter) { + if (activePhase) { + if (isCenter) { + GL11.glColor4f(1.0f, 0.45f, 0.35f, alpha); + } else { + GL11.glColor4f(0.75f, 0.30f, 0.30f, alpha * 0.8f); + } + } else if (isCenter) { GL11.glColor4f(0.8f, 0.4f, 0.4f, alpha); } else { GL11.glColor4f(0.5f, 0.5f, 0.5f, alpha * 0.8f); diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java index 44d81c441..ab55c9560 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java @@ -48,7 +48,7 @@ public class AbilityHotbarSlotRenderer extends Gui { public void drawCarousel(Minecraft mc, ScaledResolution sr, float cooldownProgress, int cx, int cy, int size, boolean isCenter, float slotAlpha, - boolean showText) { + boolean showText, boolean activePhase) { float nameAlpha = this.nameAlpha; boolean isHorizontal = ConfigClient.AbilityHotbarHorizontal; if (size <= 0 || slotAlpha <= 0) return; @@ -61,21 +61,54 @@ public class AbilityHotbarSlotRenderer extends Gui { boolean altTexture = ConfigClient.AbilityHotbarAltTexture; - if (altTexture) { - drawRoundedRect(-radius, -radius, radius, radius, 0.6f, 0.6f, 0.6f, 0.75f * alpha, false); + float fillR; + float fillG; + float fillB; + float outlineR; + float outlineG; + float outlineB; + + if (activePhase) { if (isCenter) { - drawRoundedRect(-radius, -radius, radius, radius, 0.8f, 0.8f, 0.2f, alpha, true); + fillR = 0.95f; + fillG = 0.26f; + fillB = 0.22f; + outlineR = 1.0f; + outlineG = 0.52f; + outlineB = 0.28f; } else { - drawRoundedRect(-radius, -radius, radius, radius, 0.4f, 0.4f, 0.4f, alpha * 0.8f, true); + fillR = 0.36f; + fillG = 0.14f; + fillB = 0.14f; + outlineR = 0.65f; + outlineG = 0.22f; + outlineB = 0.20f; } + } else if (isCenter) { + fillR = 0.8f; + fillG = 0.8f; + fillB = 0.2f; + outlineR = 0.8f; + outlineG = 0.8f; + outlineB = 0.2f; } else { - if (isCenter) { - drawCircle(radius, 0.8f, 0.8f, 0.2f, 0.66f * alpha, false); - drawCircle(radius, 0.8f, 0.8f, 0.2f, alpha, true); - } else { - drawCircle(radius, 1f, 1f, 1f, 0.66f * alpha, false); - drawCircle(radius, 0.4f, 0.4f, 0.4f, alpha * 0.8f, true); - } + fillR = 0.4f; + fillG = 0.4f; + fillB = 0.4f; + outlineR = 1f; + outlineG = 1f; + outlineB = 1f; + } + + if (altTexture) { + float baseR = activePhase ? 0.30f : 0.6f; + float baseG = activePhase ? 0.12f : 0.6f; + float baseB = activePhase ? 0.12f : 0.6f; + drawRoundedRect(-radius, -radius, radius, radius, baseR, baseG, baseB, 0.75f * alpha, false); + drawRoundedRect(-radius, -radius, radius, radius, fillR, fillG, fillB, alpha * 0.8f, true); + } else { + drawCircle(radius, outlineR, outlineG, outlineB, 0.66f * alpha, false); + drawCircle(radius, fillR, fillG, fillB, alpha * 0.8f, true); } // Ability icon diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java index ffe6718ee..1d82deb61 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java @@ -1,6 +1,7 @@ package noppes.npcs.client.gui.util; import kamkeel.npcs.util.TextSplitter; +import kamkeel.npcs.util.FileNameHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; @@ -26,6 +27,7 @@ public class GuiNpcTextField extends GuiTextField { public float minFloat = 0, maxFloat = Float.MAX_VALUE, defFloat = 0; protected static GuiNpcTextField activeTextfield = null; public boolean canEdit = true; + private boolean fileNameSafe = false; public String hoverableText = ""; private boolean wasHovered = false; private int hoverCount = 0; @@ -71,6 +73,11 @@ public class GuiNpcTextField extends GuiTextField { public boolean textboxKeyTyped(char c, int i) { if (!canEdit || !isFocused()) return false; + + if (fileNameSafe && !GuiScreen.isCtrlKeyDown() && !isFileNameCharAllowed(c, i)) { + return false; + } + // Allow Ctrl shortcuts (select all, copy, paste, cut) to bypass char filter if (GuiScreen.isCtrlKeyDown()) { // For Ctrl+V (paste) on numeric fields, filter clipboard to valid chars @@ -87,7 +94,17 @@ public class GuiNpcTextField extends GuiTextField { } return true; } - return super.textboxKeyTyped(c, i); + if (i == 47 && fileNameSafe) { + String clipboard = GuiScreen.getClipboardString(); + if (clipboard != null) { + String filtered = filterFileNameClipboard(clipboard); + if (!filtered.isEmpty()) + writeText(filtered); + } + return true; + } + boolean handled = super.textboxKeyTyped(c, i); + return handled; } if (!charAllowed(c, i)) return false; @@ -179,6 +196,9 @@ public class GuiNpcTextField extends GuiTextField { else if (getFloat() > maxFloat) setText(maxFloat + ""); } + if (fileNameSafe) { + normalizeFileNameText(); + } setCursorPositionZero(); if (listener != null) listener.unFocused(this); @@ -238,6 +258,51 @@ public class GuiNpcTextField extends GuiTextField { return this; } + public GuiNpcTextField setFileNameSafe() { + fileNameSafe = true; + normalizeFileNameText(); + return this; + } + + private boolean isSpecialNavigationKey(int keyCode) { + for (int allowed : allowedSpecialChars) { + if (allowed == keyCode) { + return true; + } + } + return false; + } + + private boolean isFileNameCharAllowed(char c, int keyCode) { + if (isSpecialNavigationKey(keyCode) || c < 32) { + return true; + } + return Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == ' '; + } + + private String filterFileNameClipboard(String clipboard) { + StringBuilder filtered = new StringBuilder(clipboard.length()); + for (int idx = 0; idx < clipboard.length(); idx++) { + char c = clipboard.charAt(idx); + if (Character.isLetterOrDigit(c) || c == '_' || c == '-') { + filtered.append(c); + } else if (Character.isWhitespace(c)) { + filtered.append(' '); + } + } + return filtered.toString(); + } + + private void normalizeFileNameText() { + String current = getText(); + String sanitized = FileNameHelper.sanitizeTextInput(current); + if (sanitized.equals(current)) return; + + int cursor = getCursorPosition(); + setText(sanitized); + setCursorPosition(Math.min(cursor, sanitized.length())); + } + public void setHoverText(String text) { this.hoverableText = StatCollector.translateToLocal(text); } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 18a58783a..fa439578e 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -806,6 +806,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (currentAbility == null || !currentAbility.isExecuting()) { return amount; } + if (currentAbility.isInvulnerableForCurrentPhase()) { + return 0; + } net.minecraft.entity.Entity sourceEntity = source.getEntity(); EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; @@ -937,6 +940,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (wasFlyingAtLock) { flags |= PlayerAbilityStatePacket.FLAG_WAS_FLYING_AT_LOCK; } + if (currentAbility.getPhase() == AbilityPhase.ACTIVE) { + flags |= PlayerAbilityStatePacket.FLAG_ACTIVE_PHASE; + } return flags; } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 5c759d59c..d6ae75a6f 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -659,6 +659,9 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti if (damagesource.damageType != null && damagesource.damageType.equals("outOfWorld") && isKilled()) { reset(); } + if (this.abilities != null && this.abilities.isCurrentAbilityInvulnerable()) { + return false; + } // Check for custom weapon attack speed - bypass immunity if enough time has passed Entity sourceEntity = damagesource.getEntity(); diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 0a7b1ecdc..90d877011 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -167,6 +167,7 @@ gui.unused=Unused gui.apply=Apply gui.cancel=Cancel gui.continue=Continue +gui.back=Back gui.yes=Yes gui.abilities=Abilities gui.toggles=Toggles @@ -1933,6 +1934,12 @@ ability.hover.burstReplay=Replay windup and active animations for each burst ite ability.save.confirm=Save ability preset ability.save.overwrite=Overwrite existing preset ability.duplicate.name=A preset with this name already exists +ability.invulnerable=Invulnerable +ability.invulnerable.none=None +ability.invulnerable.windup=Windup +ability.invulnerable.active=Active +ability.invulnerable.both=Both +ability.hover.invulnerable=Ignores all incoming damage and hit reactions during selected phases. Windup/Both also disable interrupts. ability.load.select=Select ability to load: ability.load.mode=How should this ability be loaded? ability.clone=Clone From 0c4238dfb40f09a7c44ca8e01e8540deb06e92aa Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 16:35:04 -0500 Subject: [PATCH 50/71] Update SkinOverlay.java --- src/main/java/noppes/npcs/controllers/data/SkinOverlay.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java index bc08a8033..01c236999 100644 --- a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java +++ b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java @@ -142,7 +142,11 @@ public class SkinOverlay implements ISkinOverlay { this.blend = compound.getBoolean("SkinOverlayBlend"); this.alpha = compound.getFloat("SkinOverlayAlpha"); this.size = compound.getFloat("SkinOverlaySize"); - this.color = compound.getInteger("SkinOverlayColor"); + if(compound.hasKey("SkinOverlayColor")){ + this.color = compound.getInteger("SkinOverlayColor"); + } else { + this.color = 0xFFFFFF; + } this.speedX = compound.getFloat("SkinOverlaySpeedX"); this.speedY = compound.getFloat("SkinOverlaySpeedY"); this.scaleX = compound.getFloat("SkinOverlayScaleX"); From 2a3417e31bbfa4e739264d0bd32148af9c1159a8 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 16:42:49 -0500 Subject: [PATCH 51/71] Beta 7 --- build.gradle | 2 +- src/main/java/noppes/npcs/CustomNpcs.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2a9f8b1da..00d0ef7ae 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'idea' } -version = "1.11-beta6.1" +version = "1.11-beta7" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 90321b1b5..718793ddd 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -122,7 +122,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta6.1") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta7") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy") From c081efd42ec885f71899dca45ea3c5a2924aedbf Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 16:46:05 -0500 Subject: [PATCH 52/71] Remove this Profile Block --- .../java/kamkeel/npcs/controllers/ProfileController.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/ProfileController.java b/src/main/java/kamkeel/npcs/controllers/ProfileController.java index 103da1309..f7ac06bb1 100644 --- a/src/main/java/kamkeel/npcs/controllers/ProfileController.java +++ b/src/main/java/kamkeel/npcs/controllers/ProfileController.java @@ -832,15 +832,6 @@ public class ProfileController implements IProfileHandler { else compound = (NBTTagCompound) compound.copy(); playerData.setNBT(compound); - - // Trade data is shared across all profile slots - copy from player's live data - PlayerData liveData = PlayerData.get(player); - if (liveData != null) { - NBTTagCompound tradeCompound = new NBTTagCompound(); - liveData.tradeData.writeToNBT(tradeCompound); - playerData.tradeData.readFromNBT(tradeCompound); - } - return playerData; } From 57528f95902364b7c0c5da5577d796270049f34a Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 16:57:49 -0500 Subject: [PATCH 53/71] Disable Request Spawn Data --- .../java/kamkeel/npcs/network/PacketHandler.java | 2 +- .../noppes/npcs/client/ClientTickHandler.java | 8 +++----- .../noppes/npcs/entity/EntityNPCInterface.java | 16 ++++++++-------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 991b2115d..93f978bf9 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -569,7 +569,7 @@ public class PacketHandler { DATA_PACKET.registerPacket(new DialogPacket()); DATA_PACKET.registerPacket(new RolePacket()); DATA_PACKET.registerPacket(new WeaponNpcPacket()); - DATA_PACKET.registerPacket(new RequestProperSpawnData()); + // DATA_PACKET.registerPacket(new RequestProperSpawnData()); // Data | Script Packets DATA_PACKET.registerPacket(new ScriptedParticlePacket()); diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index dbe6e691d..3e2742c37 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -7,11 +7,9 @@ import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.client.renderer.lightning.LightningBolt; -import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; -import kamkeel.npcs.network.packets.data.RequestProperSpawnData; import kamkeel.npcs.network.packets.player.CheckPlayerValue; import kamkeel.npcs.network.packets.player.InputDevicePacket; import kamkeel.npcs.network.packets.player.ScreenSizePacket; @@ -63,7 +61,7 @@ public class ClientTickHandler { if (mc.theWorld == null) { ClientCacheHandler.clearCache(); ClientAbilityState.reset(); - RequestProperSpawnData.clear(); + // RequestProperSpawnData.clear(); if (EnergyChargePreviewManager.ClientInstance != null) { EnergyChargePreviewManager.ClientInstance.clear(); } @@ -218,8 +216,8 @@ public class ClientTickHandler { return; if (mc.theWorld.getTotalWorldTime() % 20 == 0) { // Update every second - if (RequestProperSpawnData.canDoBatchUpdate()) - RequestProperSpawnData.handleBacklog(); +// if (RequestProperSpawnData.canDoBatchUpdate()) +// RequestProperSpawnData.handleBacklog(); updateCompassMarks(); } } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index d6ae75a6f..a1daf1eb6 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -1837,18 +1837,18 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti try { readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { - if(this.worldObj != null && this.worldObj.isRemote){ - RequestProperSpawnData.reportMissingData(this); - } +// if(this.worldObj != null && this.worldObj.isRemote){ +// RequestProperSpawnData.reportMissingData(this); +// } } } public void readSpawnData(NBTTagCompound compound) { - if (this.worldObj != null && this.worldObj.isRemote && - (compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { - RequestProperSpawnData.reportMissingData(this); - return; - } +// if (this.worldObj != null && this.worldObj.isRemote && +// (compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { +// RequestProperSpawnData.reportMissingData(this); +// return; +// } immediateSpawnDataFixAttempts = 0; stats.maxHealth = compound.getDouble("MaxHealth"); From 0f00bd6ba3d88fac2facabe1b1ff96ed49660eec Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 17:16:23 -0500 Subject: [PATCH 54/71] Why --- build.gradle | 2 +- .../kamkeel/npcs/network/PacketHandler.java | 2 +- .../npcs/network/enums/EnumDataPacket.java | 2 +- .../packets/data/RequestProperSpawnData.java | 252 +++++++++--------- .../noppes/npcs/client/ClientTickHandler.java | 2 +- .../npcs/entity/EntityNPCInterface.java | 11 +- 6 files changed, 134 insertions(+), 137 deletions(-) diff --git a/build.gradle b/build.gradle index 00d0ef7ae..0ba382948 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'idea' } -version = "1.11-beta7" +version = "1.11-beta7.2" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 93f978bf9..e6a525952 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -569,7 +569,7 @@ public class PacketHandler { DATA_PACKET.registerPacket(new DialogPacket()); DATA_PACKET.registerPacket(new RolePacket()); DATA_PACKET.registerPacket(new WeaponNpcPacket()); - // DATA_PACKET.registerPacket(new RequestProperSpawnData()); +// DATA_PACKET.registerPacket(new RequestProperSpawnData()); // Data | Script Packets DATA_PACKET.registerPacket(new ScriptedParticlePacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index 6d9a6f598..181956757 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -23,7 +23,7 @@ public enum EnumDataPacket { DELETE_NPC, CLONE_NPC, WEAPON_NPC, - REQUEST_SPAWN_DATA, + // REQUEST_SPAWN_DATA, // Data SCROLL_LIST, diff --git a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java index 76e010660..d2ad045d1 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java @@ -1,127 +1,125 @@ -package kamkeel.npcs.network.packets.data; - -import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; -import io.netty.buffer.ByteBuf; -import kamkeel.npcs.network.AbstractPacket; -import kamkeel.npcs.network.PacketChannel; -import kamkeel.npcs.network.PacketClient; -import kamkeel.npcs.network.PacketHandler; -import kamkeel.npcs.network.enums.EnumDataPacket; -import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; -import net.minecraft.client.Minecraft; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.LogWriter; -import noppes.npcs.entity.EntityNPCInterface; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -public class RequestProperSpawnData extends AbstractPacket { - public static final String packetName = "Data|SpawnData"; - private static final int BATCH_LIMIT = 5; - - @SideOnly(Side.CLIENT) - private static Set entitiesToFix; - - @SideOnly(Side.CLIENT) - private static long lastBatchAttemptMillis; - - static { - if (FMLCommonHandler.instance().getEffectiveSide().isClient()) { - entitiesToFix = new HashSet<>(); - lastBatchAttemptMillis = 0; - } - } - - private int entityId; - - public RequestProperSpawnData() {} - - @SideOnly(Side.CLIENT) - public RequestProperSpawnData(int entityId) { - this.entityId = entityId; - } - - public static void clear() { - entitiesToFix.clear(); - } - - @Override - public Enum getType() { - return EnumDataPacket.REQUEST_SPAWN_DATA; - } - - @Override - public PacketChannel getChannel() { - return PacketHandler.DATA_PACKET; - } - - @Override - @SideOnly(Side.CLIENT) - public void sendData(ByteBuf out) throws IOException { - out.writeInt(this.entityId); - } - - @Override - public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { - if (FMLCommonHandler.instance().getEffectiveSide().isClient()) - return; - - int entityId = in.readInt(); - Entity entity = Minecraft.getMinecraft().theWorld.getEntityByID(entityId); - if (entity instanceof EntityNPCInterface) { - EntityNPCInterface npc = (EntityNPCInterface) entity; - NBTTagCompound compound = npc.writeSpawnData(); - compound.setInteger("EntityId", npc.getEntityId()); - PacketHandler.Instance.sendToPlayer(new UpdateNpcPacket(compound), (EntityPlayerMP) player); - } - } - - @SideOnly(Side.CLIENT) - public static void reportMissingData(EntityNPCInterface npc) { - int entityID = npc.getEntityId(); - - if (npc.immediateSpawnDataFixAttempts++ < 3) { - PacketClient.sendClient(new RequestProperSpawnData(entityID)); - } else { - entitiesToFix.add(entityID); - } - } - - @SideOnly(Side.CLIENT) - public static boolean canDoBatchUpdate() { - return System.currentTimeMillis() - lastBatchAttemptMillis >= 5000; - } - - @SideOnly(Side.CLIENT) - public static void handleBacklog() { - Minecraft mc = Minecraft.getMinecraft(); - if (mc == null || mc.theWorld == null || mc.thePlayer == null) - return; - - if (entitiesToFix.isEmpty()) - return; - // Remove entity checks if they're no longer loaded - entitiesToFix.removeIf( value -> !(Minecraft.getMinecraft().theWorld.getEntityByID(value) instanceof EntityNPCInterface) ); - - Iterator it = entitiesToFix.iterator(); - int count = 0; - - while (it.hasNext() && count < BATCH_LIMIT) { - PacketClient.sendClient(new RequestProperSpawnData(it.next())); - it.remove(); - count++; - } - - if (count != 0) { - lastBatchAttemptMillis = System.currentTimeMillis(); - } - } -} +//package kamkeel.npcs.network.packets.data; +// +//import cpw.mods.fml.relauncher.Side; +//import cpw.mods.fml.relauncher.SideOnly; +//import io.netty.buffer.ByteBuf; +//import kamkeel.npcs.network.AbstractPacket; +//import kamkeel.npcs.network.PacketChannel; +//import kamkeel.npcs.network.PacketClient; +//import kamkeel.npcs.network.PacketHandler; +//import kamkeel.npcs.network.enums.EnumDataPacket; +//import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; +//import net.minecraft.client.Minecraft; +//import net.minecraft.entity.Entity; +//import net.minecraft.entity.player.EntityPlayer; +//import net.minecraft.entity.player.EntityPlayerMP; +//import net.minecraft.nbt.NBTTagCompound; +//import noppes.npcs.entity.EntityNPCInterface; +// +//import java.io.IOException; +//import java.util.HashSet; +//import java.util.Iterator; +//import java.util.Set; +// +//public class RequestProperSpawnData extends AbstractPacket { +// public static final String packetName = "Data|SpawnData"; +// private static final int BATCH_LIMIT = 5; +// +// @SideOnly(Side.CLIENT) +// private static final Set entitiesToFix = new HashSet<>(); +// +// @SideOnly(Side.CLIENT) +// private static long lastBatchAttemptMillis = 0L; +// +// private int entityId; +// +// public RequestProperSpawnData() {} +// +// @SideOnly(Side.CLIENT) +// public RequestProperSpawnData(int entityId) { +// this.entityId = entityId; +// } +// +// @SideOnly(Side.CLIENT) +// public static void clear() { +// entitiesToFix.clear(); +// lastBatchAttemptMillis = 0L; +// } +// +// @Override +// public Enum getType() { +// return EnumDataPacket.REQUEST_SPAWN_DATA; +// } +// +// @Override +// public PacketChannel getChannel() { +// return PacketHandler.DATA_PACKET; +// } +// +// @Override +// @SideOnly(Side.CLIENT) +// public void sendData(ByteBuf out) throws IOException { +// out.writeInt(this.entityId); +// } +// +// @Override +// public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { +// if (!(player instanceof EntityPlayerMP) || player.worldObj == null || player.worldObj.isRemote) +// return; +// +// int entityId = in.readInt(); +// Entity entity = player.worldObj.getEntityByID(entityId); +// if (entity instanceof EntityNPCInterface) { +// EntityNPCInterface npc = (EntityNPCInterface) entity; +// NBTTagCompound compound = npc.writeSpawnData(); +// compound.setInteger("EntityId", npc.getEntityId()); +// PacketHandler.Instance.sendToPlayer(new UpdateNpcPacket(compound), (EntityPlayerMP) player); +// } +// } +// +// @SideOnly(Side.CLIENT) +// public static void reportMissingData(EntityNPCInterface npc) { +// if (npc == null || npc.worldObj == null || !npc.worldObj.isRemote) +// return; +// +// int entityID = npc.getEntityId(); +// if (entityID <= 0) +// return; +// +// if (npc.immediateSpawnDataFixAttempts++ < 3) { +// PacketClient.sendClient(new RequestProperSpawnData(entityID)); +// } else { +// entitiesToFix.add(entityID); +// } +// } +// +// @SideOnly(Side.CLIENT) +// public static boolean canDoBatchUpdate() { +// return System.currentTimeMillis() - lastBatchAttemptMillis >= 5000; +// } +// +// @SideOnly(Side.CLIENT) +// public static void handleBacklog() { +// Minecraft mc = Minecraft.getMinecraft(); +// if (mc == null || mc.theWorld == null || mc.thePlayer == null) +// return; +// +// if (entitiesToFix.isEmpty()) +// return; +// // Remove entity checks if they're no longer loaded +// entitiesToFix.removeIf(value -> !(mc.theWorld.getEntityByID(value) instanceof EntityNPCInterface)); +// +// Iterator it = entitiesToFix.iterator(); +// int count = 0; +// +// while (it.hasNext() && count < BATCH_LIMIT) { +// PacketClient.sendClient(new RequestProperSpawnData(it.next())); +// it.remove(); +// count++; +// } +// +// if (count != 0) { +// lastBatchAttemptMillis = System.currentTimeMillis(); +// } +// } +//} diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 3e2742c37..bfd3b0cae 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -61,7 +61,7 @@ public class ClientTickHandler { if (mc.theWorld == null) { ClientCacheHandler.clearCache(); ClientAbilityState.reset(); - // RequestProperSpawnData.clear(); +// RequestProperSpawnData.clear(); if (EnergyChargePreviewManager.ClientInstance != null) { EnergyChargePreviewManager.ClientInstance.clear(); } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index a1daf1eb6..24579e4ab 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -11,7 +11,6 @@ import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSoundOperation; import kamkeel.npcs.network.packets.data.ChatBubblePacket; import kamkeel.npcs.network.packets.data.QuestCompletionPacket; -import kamkeel.npcs.network.packets.data.RequestProperSpawnData; import kamkeel.npcs.network.packets.data.SoundManagementPacket; import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket; @@ -231,8 +230,8 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti public FlyingMoveHelper flyMoveHelper = new FlyingMoveHelper(this); public PathNavigate flyNavigator = new PathNavigateFlying(this, worldObj); - @SideOnly(Side.CLIENT) - public int immediateSpawnDataFixAttempts = 0; +// @SideOnly(Side.CLIENT) +// public int immediateSpawnDataFixAttempts = 0; public EntityNPCInterface(World world) { super(world); @@ -1837,7 +1836,7 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti try { readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { -// if(this.worldObj != null && this.worldObj.isRemote){ +// if (this.worldObj != null && this.worldObj.isRemote) { // RequestProperSpawnData.reportMissingData(this); // } } @@ -1849,8 +1848,8 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti // RequestProperSpawnData.reportMissingData(this); // return; // } - - immediateSpawnDataFixAttempts = 0; +// +// immediateSpawnDataFixAttempts = 0; stats.maxHealth = compound.getDouble("MaxHealth"); ais.setWalkingSpeed(compound.getInteger("Speed")); stats.hideKilledBody = compound.getBoolean("DeadBody"); From ec60a9bb21a8b9c4b3381615b02e64e578654853 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 18:24:36 -0500 Subject: [PATCH 55/71] Request Client Packet Fix NPC [Invisibility Patch] --- .../kamkeel/npcs/network/PacketHandler.java | 2 +- .../npcs/network/enums/EnumDataPacket.java | 1 - .../npcs/network/enums/EnumPlayerPacket.java | 3 + .../packets/data/RequestProperSpawnData.java | 250 +++++++++--------- .../noppes/npcs/client/ClientTickHandler.java | 7 +- .../npcs/entity/EntityNPCInterface.java | 32 ++- 6 files changed, 153 insertions(+), 142 deletions(-) diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index e6a525952..b71021b37 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -569,7 +569,6 @@ public class PacketHandler { DATA_PACKET.registerPacket(new DialogPacket()); DATA_PACKET.registerPacket(new RolePacket()); DATA_PACKET.registerPacket(new WeaponNpcPacket()); -// DATA_PACKET.registerPacket(new RequestProperSpawnData()); // Data | Script Packets DATA_PACKET.registerPacket(new ScriptedParticlePacket()); @@ -628,6 +627,7 @@ public class PacketHandler { PLAYER_PACKET.registerPacket(new SpecialKeyStatePacket()); PLAYER_PACKET.registerPacket(new SyncRevisionInfoPacket()); + PLAYER_PACKET.registerPacket(new RequestProperSpawnData()); // CustomGUI Packets PLAYER_PACKET.registerPacket(new CustomGuiButtonPacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index 181956757..10ae639e9 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -23,7 +23,6 @@ public enum EnumDataPacket { DELETE_NPC, CLONE_NPC, WEAPON_NPC, - // REQUEST_SPAWN_DATA, // Data SCROLL_LIST, diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumPlayerPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumPlayerPacket.java index e1587467b..3453b6eb4 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumPlayerPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumPlayerPacket.java @@ -42,4 +42,7 @@ public enum EnumPlayerPacket { AbilityHotbarSave, AbilityHotbarSelect, AbilityToggle, + + // NPC Spawn Data Fix + RequestSpawnData, } diff --git a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java index d2ad045d1..386a9000b 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/RequestProperSpawnData.java @@ -1,125 +1,125 @@ -//package kamkeel.npcs.network.packets.data; -// -//import cpw.mods.fml.relauncher.Side; -//import cpw.mods.fml.relauncher.SideOnly; -//import io.netty.buffer.ByteBuf; -//import kamkeel.npcs.network.AbstractPacket; -//import kamkeel.npcs.network.PacketChannel; -//import kamkeel.npcs.network.PacketClient; -//import kamkeel.npcs.network.PacketHandler; -//import kamkeel.npcs.network.enums.EnumDataPacket; -//import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; -//import net.minecraft.client.Minecraft; -//import net.minecraft.entity.Entity; -//import net.minecraft.entity.player.EntityPlayer; -//import net.minecraft.entity.player.EntityPlayerMP; -//import net.minecraft.nbt.NBTTagCompound; -//import noppes.npcs.entity.EntityNPCInterface; -// -//import java.io.IOException; -//import java.util.HashSet; -//import java.util.Iterator; -//import java.util.Set; -// -//public class RequestProperSpawnData extends AbstractPacket { -// public static final String packetName = "Data|SpawnData"; -// private static final int BATCH_LIMIT = 5; -// -// @SideOnly(Side.CLIENT) -// private static final Set entitiesToFix = new HashSet<>(); -// -// @SideOnly(Side.CLIENT) -// private static long lastBatchAttemptMillis = 0L; -// -// private int entityId; -// -// public RequestProperSpawnData() {} -// -// @SideOnly(Side.CLIENT) -// public RequestProperSpawnData(int entityId) { -// this.entityId = entityId; -// } -// -// @SideOnly(Side.CLIENT) -// public static void clear() { -// entitiesToFix.clear(); -// lastBatchAttemptMillis = 0L; -// } -// -// @Override -// public Enum getType() { -// return EnumDataPacket.REQUEST_SPAWN_DATA; -// } -// -// @Override -// public PacketChannel getChannel() { -// return PacketHandler.DATA_PACKET; -// } -// -// @Override -// @SideOnly(Side.CLIENT) -// public void sendData(ByteBuf out) throws IOException { -// out.writeInt(this.entityId); -// } -// -// @Override -// public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { -// if (!(player instanceof EntityPlayerMP) || player.worldObj == null || player.worldObj.isRemote) -// return; -// -// int entityId = in.readInt(); -// Entity entity = player.worldObj.getEntityByID(entityId); -// if (entity instanceof EntityNPCInterface) { -// EntityNPCInterface npc = (EntityNPCInterface) entity; -// NBTTagCompound compound = npc.writeSpawnData(); -// compound.setInteger("EntityId", npc.getEntityId()); -// PacketHandler.Instance.sendToPlayer(new UpdateNpcPacket(compound), (EntityPlayerMP) player); -// } -// } -// -// @SideOnly(Side.CLIENT) -// public static void reportMissingData(EntityNPCInterface npc) { -// if (npc == null || npc.worldObj == null || !npc.worldObj.isRemote) -// return; -// -// int entityID = npc.getEntityId(); -// if (entityID <= 0) -// return; -// -// if (npc.immediateSpawnDataFixAttempts++ < 3) { -// PacketClient.sendClient(new RequestProperSpawnData(entityID)); -// } else { -// entitiesToFix.add(entityID); -// } -// } -// -// @SideOnly(Side.CLIENT) -// public static boolean canDoBatchUpdate() { -// return System.currentTimeMillis() - lastBatchAttemptMillis >= 5000; -// } -// -// @SideOnly(Side.CLIENT) -// public static void handleBacklog() { -// Minecraft mc = Minecraft.getMinecraft(); -// if (mc == null || mc.theWorld == null || mc.thePlayer == null) -// return; -// -// if (entitiesToFix.isEmpty()) -// return; -// // Remove entity checks if they're no longer loaded -// entitiesToFix.removeIf(value -> !(mc.theWorld.getEntityByID(value) instanceof EntityNPCInterface)); -// -// Iterator it = entitiesToFix.iterator(); -// int count = 0; -// -// while (it.hasNext() && count < BATCH_LIMIT) { -// PacketClient.sendClient(new RequestProperSpawnData(it.next())); -// it.remove(); -// count++; -// } -// -// if (count != 0) { -// lastBatchAttemptMillis = System.currentTimeMillis(); -// } -// } -//} +package kamkeel.npcs.network.packets.data; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumPlayerPacket; +import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.entity.EntityNPCInterface; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class RequestProperSpawnData extends AbstractPacket { + public static final String packetName = "Player|SpawnData"; + private static final int BATCH_LIMIT = 5; + + @SideOnly(Side.CLIENT) + private static final Set entitiesToFix = new HashSet<>(); + + @SideOnly(Side.CLIENT) + private static long lastBatchAttemptMillis = 0L; + + private int entityId; + + public RequestProperSpawnData() {} + + @SideOnly(Side.CLIENT) + public RequestProperSpawnData(int entityId) { + this.entityId = entityId; + } + + @SideOnly(Side.CLIENT) + public static void clear() { + entitiesToFix.clear(); + lastBatchAttemptMillis = 0L; + } + + @Override + public Enum getType() { + return EnumPlayerPacket.RequestSpawnData; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.PLAYER_PACKET; + } + + @Override + @SideOnly(Side.CLIENT) + public void sendData(ByteBuf out) throws IOException { + out.writeInt(this.entityId); + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP) || player.worldObj == null || player.worldObj.isRemote) + return; + + int entityId = in.readInt(); + Entity entity = player.worldObj.getEntityByID(entityId); + if (entity instanceof EntityNPCInterface) { + EntityNPCInterface npc = (EntityNPCInterface) entity; + NBTTagCompound compound = npc.writeSpawnData(); + compound.setInteger("EntityId", npc.getEntityId()); + PacketHandler.Instance.sendToPlayer(new UpdateNpcPacket(compound), (EntityPlayerMP) player); + } + } + + @SideOnly(Side.CLIENT) + public static void reportMissingData(EntityNPCInterface npc) { + if (npc == null || npc.worldObj == null || !npc.worldObj.isRemote) + return; + + int entityID = npc.getEntityId(); + if (entityID <= 0) + return; + + if (npc.clientFixAttempts++ < 3) { + PacketClient.sendClient(new RequestProperSpawnData(entityID)); + } else { + entitiesToFix.add(entityID); + } + } + + @SideOnly(Side.CLIENT) + public static boolean canDoBatchUpdate() { + return System.currentTimeMillis() - lastBatchAttemptMillis >= 5000; + } + + @SideOnly(Side.CLIENT) + public static void handleBacklog() { + Minecraft mc = Minecraft.getMinecraft(); + if (mc == null || mc.theWorld == null || mc.thePlayer == null) + return; + + if (entitiesToFix.isEmpty()) + return; + // Remove entity checks if they're no longer loaded + entitiesToFix.removeIf(value -> !(mc.theWorld.getEntityByID(value) instanceof EntityNPCInterface)); + + Iterator it = entitiesToFix.iterator(); + int count = 0; + + while (it.hasNext() && count < BATCH_LIMIT) { + PacketClient.sendClient(new RequestProperSpawnData(it.next())); + it.remove(); + count++; + } + + if (count != 0) { + lastBatchAttemptMillis = System.currentTimeMillis(); + } + } +} diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index bfd3b0cae..3636f48fa 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -10,6 +10,7 @@ import kamkeel.npcs.client.renderer.lightning.LightningBolt; import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.data.RequestProperSpawnData; import kamkeel.npcs.network.packets.player.CheckPlayerValue; import kamkeel.npcs.network.packets.player.InputDevicePacket; import kamkeel.npcs.network.packets.player.ScreenSizePacket; @@ -61,7 +62,7 @@ public class ClientTickHandler { if (mc.theWorld == null) { ClientCacheHandler.clearCache(); ClientAbilityState.reset(); -// RequestProperSpawnData.clear(); + RequestProperSpawnData.clear(); if (EnergyChargePreviewManager.ClientInstance != null) { EnergyChargePreviewManager.ClientInstance.clear(); } @@ -216,8 +217,8 @@ public class ClientTickHandler { return; if (mc.theWorld.getTotalWorldTime() % 20 == 0) { // Update every second -// if (RequestProperSpawnData.canDoBatchUpdate()) -// RequestProperSpawnData.handleBacklog(); + if (RequestProperSpawnData.canDoBatchUpdate()) + RequestProperSpawnData.handleBacklog(); updateCompassMarks(); } } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 24579e4ab..fb1c1c6f8 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -11,6 +11,7 @@ import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSoundOperation; import kamkeel.npcs.network.packets.data.ChatBubblePacket; import kamkeel.npcs.network.packets.data.QuestCompletionPacket; +import kamkeel.npcs.network.packets.data.RequestProperSpawnData; import kamkeel.npcs.network.packets.data.SoundManagementPacket; import kamkeel.npcs.network.packets.data.npc.UpdateNpcPacket; import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket; @@ -230,8 +231,8 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti public FlyingMoveHelper flyMoveHelper = new FlyingMoveHelper(this); public PathNavigate flyNavigator = new PathNavigateFlying(this, worldObj); -// @SideOnly(Side.CLIENT) -// public int immediateSpawnDataFixAttempts = 0; + // For Client-Side Use Only + public int clientFixAttempts = 0; public EntityNPCInterface(World world) { super(world); @@ -1836,20 +1837,18 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti try { readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { -// if (this.worldObj != null && this.worldObj.isRemote) { -// RequestProperSpawnData.reportMissingData(this); -// } + if (this.worldObj != null && this.worldObj.isRemote) { + RequestProperSpawnData.reportMissingData(this); + } } } public void readSpawnData(NBTTagCompound compound) { -// if (this.worldObj != null && this.worldObj.isRemote && -// (compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { -// RequestProperSpawnData.reportMissingData(this); -// return; -// } -// -// immediateSpawnDataFixAttempts = 0; + if (this.worldObj != null && this.worldObj.isRemote) { + if(requestClientFix(compound)) + return; + } + stats.maxHealth = compound.getDouble("MaxHealth"); ais.setWalkingSpeed(compound.getInteger("Speed")); stats.hideKilledBody = compound.getBoolean("DeadBody"); @@ -1927,6 +1926,15 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti } } + public boolean requestClientFix(NBTTagCompound compound){ + if((compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))){ + RequestProperSpawnData.reportMissingData(this); + return true; + } + clientFixAttempts = 0; + return false; + } + public boolean canBreatheUnderwater() { return stats.drowningType != 1; } From d2d5bcc59b478a995e5220facbda4eef94825d16 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 21:19:44 -0500 Subject: [PATCH 56/71] Various Fixes to CNPC Abilities - super slow speed orb has weird properites, after awhile it flies weirdly - Binding HUD scroll key to middle mouse button crashes client - Zero Knockback still does knockback to CNPCs - Charge: the telegraph is where you are looking but you actuually charge where your current body rotation is instead - using slam mid air makes it not work correctly - Beam disappears after going out of the render distance but still fires - Player vortex can only pull one target Slam now takes current height when being used to calculate damage --- src/api | 2 +- .../controllers/data/ability/Ability.java | 12 +- .../data/ability/type/AbilityMovement.java | 4 +- .../data/ability/type/AbilityShockwave.java | 112 +++++++++------ .../data/ability/type/AbilitySlam.java | 130 ++++++++++++++++-- .../data/ability/type/AbilityVortex.java | 53 +++---- .../ability/type/energy/AbilityBarrier.java | 12 +- .../npcs/entity/EntityAbilityBeam.java | 12 ++ .../npcs/entity/EntityAbilityDome.java | 4 +- .../npcs/entity/EntityAbilityPanel.java | 4 +- .../npcs/entity/EntityEnergyProjectile.java | 38 ++++- src/main/java/noppes/npcs/CustomNpcs.java | 2 +- .../noppes/npcs/ScriptPlayerEventHandler.java | 15 +- .../npcs/client/ClientEventHandler.java | 8 +- .../assets/customnpcs/lang/en_US.lang | 4 + 15 files changed, 317 insertions(+), 95 deletions(-) diff --git a/src/api b/src/api index 658da549e..2438b2110 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 658da549eec622ed7bcfb331f655938e8841520a +Subproject commit 2438b2110e74bfd49125cf74254d98c8b18e2331 diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 4a8558218..8c8f16efe 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -465,6 +465,11 @@ public abstract class Ability implements IAbility, IAbilityAction { } } + // Save velocity before damage to cancel vanilla knockback when ability knockback is zero + double prevMotionX = hitEntity.motionX; + double prevMotionY = hitEntity.motionY; + double prevMotionZ = hitEntity.motionZ; + // Apply damage if (damage > 0) { // Check for ability extenders (e.g., DBC Addon damage routing) @@ -483,9 +488,14 @@ public abstract class Ability implements IAbility, IAbilityAction { } } - // Apply knockback if any + // Apply knockback if any, otherwise restore velocity to cancel vanilla knockback if (knockback > 0 || knockbackUp > 0) { applyKnockback(hitEntity, knockbackDirX, knockbackDirZ, knockback, knockbackUp); + } else { + hitEntity.motionX = prevMotionX; + hitEntity.motionY = prevMotionY; + hitEntity.motionZ = prevMotionZ; + hitEntity.velocityChanged = true; } return true; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index 07810bfe3..266a0d6a4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -258,10 +258,12 @@ public abstract class AbilityMovement extends Ability { /** * Apply movement velocity with zero vertical motion. * Used for ground-locked movement like Charge. + * motionY is set BEFORE sending velocity packet to prevent client-server desync + * (which causes "moved too fast" kicks when used in mid-air). */ protected void applyVelocityFlat(EntityLivingBase caster, float speed) { - applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); caster.motionY = 0; + applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); } /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index b04dd2766..b4173bb1a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -27,7 +27,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { private float pushRadius = 8.0f; private float pushStrength = 1.5f; private float damage = 8.0f; - private int maxTargets = 10; + private boolean aoe = true; private int activeDisplayTicks = 10; public AbilityShockwave() { @@ -70,47 +70,79 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { @SuppressWarnings("unchecked") List entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box); - int count = 0; - for (EntityLivingBase entity : entities) { - if (entity == caster) continue; - if (entity.isDead) continue; - if (!AbilityTargetHelper.shouldAffect(caster, entity, TargetFilter.ENEMIES, false)) continue; + if (aoe) { + // ALL: push every valid enemy in range + for (EntityLivingBase entity : entities) { + if (entity == caster) continue; + if (entity.isDead) continue; + if (!AbilityTargetHelper.shouldAffect(caster, entity, TargetFilter.ENEMIES, false)) continue; - double dist = caster.getDistanceToEntity(entity); - if (dist > pushRadius) continue; + double dist = caster.getDistanceToEntity(entity); + if (dist > pushRadius) continue; - count++; - if (count > maxTargets) break; - - // Calculate push direction (away from caster) - double dx = entity.posX - caster.posX; - double dz = entity.posZ - caster.posZ; - double len = Math.sqrt(dx * dx + dz * dz); - - if (len > 0) { - dx /= len; - dz /= len; - } else { - // Entity is directly on top of caster, push in random direction - double angle = Math.random() * Math.PI * 2; - dx = Math.cos(angle); - dz = Math.sin(angle); + applyShockwavePush(caster, entity, dist); + } + } else { + // SINGULAR: push one target + // NPC: push aggro target if valid, otherwise nearest enemy + // Player: push nearest enemy in range + if (!isPlayerCaster(caster) && target != null && !target.isDead) { + double dist = caster.getDistanceToEntity(target); + if (dist <= pushRadius && AbilityTargetHelper.shouldAffect(caster, target, TargetFilter.ENEMIES, false)) { + applyShockwavePush(caster, target, dist); + return; + } } - // Scale knockback by distance (closer = stronger) - float distFactor = 1.0f - (float) (dist / pushRadius) * 0.5f; - float finalPush = pushStrength * distFactor; - // Apply damage with custom knockback direction - boolean wasHit = applyAbilityDamageWithDirection(caster, entity, damage * distFactor, finalPush, dx, dz); + // Find nearest valid enemy + EntityLivingBase nearest = null; + double nearestDist = Double.MAX_VALUE; + for (EntityLivingBase entity : entities) { + if (entity == caster) continue; + if (entity.isDead) continue; + if (!AbilityTargetHelper.shouldAffect(caster, entity, TargetFilter.ENEMIES, false)) continue; - // Apply effects if hit connected - if (wasHit) { - applyEffects(entity); + double dist = caster.getDistanceToEntity(entity); + if (dist <= pushRadius && dist < nearestDist) { + nearest = entity; + nearestDist = dist; + } + } + if (nearest != null) { + applyShockwavePush(caster, nearest, nearestDist); } } } } + private void applyShockwavePush(EntityLivingBase caster, EntityLivingBase entity, double dist) { + // Calculate push direction (away from caster) + double dx = entity.posX - caster.posX; + double dz = entity.posZ - caster.posZ; + double len = Math.sqrt(dx * dx + dz * dz); + + if (len > 0) { + dx /= len; + dz /= len; + } else { + // Entity is directly on top of caster, push in random direction + double angle = Math.random() * Math.PI * 2; + dx = Math.cos(angle); + dz = Math.sin(angle); + } + + // Scale knockback by distance (closer = stronger) + float distFactor = 1.0f - (float) (dist / pushRadius) * 0.5f; + float finalPush = pushStrength * distFactor; + // Apply damage with custom knockback direction + boolean wasHit = applyAbilityDamageWithDirection(caster, entity, damage * distFactor, finalPush, dx, dz); + + // Apply effects if hit connected + if (wasHit) { + applyEffects(entity); + } + } + @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { if (tick >= activeDisplayTicks) @@ -122,7 +154,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { nbt.setFloat("pushRadius", pushRadius); nbt.setFloat("pushStrength", pushStrength); nbt.setFloat("damage", damage); - nbt.setInteger("maxTargets", maxTargets); + nbt.setBoolean("aoe", aoe); nbt.setInteger("activeDisplayTicks", activeDisplayTicks); } @@ -131,7 +163,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { this.pushRadius = nbt.getFloat("pushRadius"); this.pushStrength = nbt.getFloat("pushStrength"); this.damage = nbt.getFloat("damage"); - this.maxTargets = nbt.getInteger("maxTargets"); + this.aoe = !nbt.hasKey("aoe") || nbt.getBoolean("aoe"); this.activeDisplayTicks = nbt.hasKey("activeDisplayTicks") ? nbt.getInteger("activeDisplayTicks") : 10; } @@ -160,12 +192,12 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { this.damage = damage; } - public int getMaxTargets() { - return maxTargets; + public boolean isAoe() { + return aoe; } - public void setMaxTargets(int maxTargets) { - this.maxTargets = maxTargets; + public void setAoe(boolean aoe) { + this.aoe = aoe; } public int getActiveDisplayTicks() { @@ -186,7 +218,9 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { FieldDef.floatField("gui.radius", this::getPushRadius, this::setPushRadius), FieldDef.floatField("gui.strength", this::getPushStrength, this::setPushStrength) ), - FieldDef.intField("ability.maxTargets", this::getMaxTargets, this::setMaxTargets), + FieldDef.section("ability.section.aoe"), + FieldDef.boolField("gui.enabled", this::isAoe, this::setAoe) + .hover("ability.hover.aoe"), FieldDef.intField("ability.activeDisplayTicks", this::getActiveDisplayTicks, this::setActiveDisplayTicks).range(1, 200), AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects) )); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index 84afb11b6..c557c4b89 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -13,7 +13,9 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.play.server.S12PacketEntityVelocity; import net.minecraft.util.DamageSource; import net.minecraft.world.World; import noppes.npcs.api.ability.type.IAbilitySlam; @@ -61,6 +63,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam { private transient boolean hasRisen = false; // Whether entity has risen above launch position private transient int airTicks = 0; private transient int maxAirTicks = 60; // Timeout to prevent stuck in air + private transient boolean airSlam = false; // True when slam initiated while already in the air + private transient boolean wasFlying = false; // Saved flight state to restore after air slam public AbilitySlam() { this.typeId = "ability.cnpc.slam"; @@ -165,22 +169,59 @@ public class AbilitySlam extends Ability implements IAbilitySlam { } /** - * Player slam: Launch in look direction using same ballistic arc as NPC slam. - * AOE damage triggers wherever the player lands. + * Player slam: behavior depends on whether the player is on the ground or in the air. + *

+ * On ground: Launch in look direction using ballistic arc, full damage on landing. + * In air: Slam straight down from current height, damage scales with fall distance. + * Works regardless of flying state — just checks height above ground. */ private void executePlayerSlam(EntityLivingBase caster) { - // Calculate target position along player's look direction (horizontal only) - float yawRad = (float) Math.toRadians(caster.rotationYaw); - double dirX = -Math.sin(yawRad); - double dirZ = Math.cos(yawRad); + if (!caster.onGround) { + // Air slam: player is already airborne (flying, falling, jumping) + airSlam = true; + targetX = caster.posX; + targetY = caster.posY; + targetZ = caster.posZ; - double launchDist = Math.max(4.0, maxRange * 0.5); + // Temporarily disable flight so gravity works during descent + if (caster instanceof EntityPlayerMP) { + EntityPlayerMP mp = (EntityPlayerMP) caster; + wasFlying = mp.capabilities.isFlying; + if (wasFlying) { + mp.capabilities.isFlying = false; + mp.sendPlayerAbilities(); + } + } - targetX = caster.posX + dirX * launchDist; - targetY = caster.posY; - targetZ = caster.posZ + dirZ * launchDist; + // Push downward — no horizontal movement, no upward arc + caster.motionX = 0; + caster.motionZ = 0; + caster.motionY = -0.5; + hasLaunched = true; + hasRisen = true; // Skip rise detection — we're already above ground - launchTowardTarget(caster); + if (!isPreview() && caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + caster.worldObj.playSoundAtEntity(caster, "mob.irongolem.throw", 0.8f, 0.8f); + } + } else { + // Ground slam: normal ballistic arc + airSlam = false; + + // Calculate target position along player's look direction (horizontal only) + float yawRad = (float) Math.toRadians(caster.rotationYaw); + double dirX = -Math.sin(yawRad); + double dirZ = Math.cos(yawRad); + + double launchDist = Math.max(4.0, maxRange * 0.5); + + targetX = caster.posX + dirX * launchDist; + targetY = caster.posY; + targetZ = caster.posZ + dirZ * launchDist; + + launchTowardTarget(caster); + } } /** @@ -244,7 +285,12 @@ public class AbilitySlam extends Ability implements IAbilitySlam { ((EntityNPCInterface) caster).setNpcJumpingState(true); } if (!isPreview()) { - caster.velocityChanged = true; + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + } else { + caster.velocityChanged = true; + } caster.worldObj.playSoundAtEntity(caster, "mob.irongolem.throw", 0.8f, 0.8f); } return; @@ -292,7 +338,14 @@ public class AbilitySlam extends Ability implements IAbilitySlam { ((EntityNPCInterface) caster).setNpcJumpingState(true); } if (!isPreview()) { - caster.velocityChanged = true; + // Use direct S12 packet for players to ensure velocity reaches client + // before flight code can override it + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + } else { + caster.velocityChanged = true; + } } // Face the target @@ -313,6 +366,15 @@ public class AbilitySlam extends Ability implements IAbilitySlam { caster.fallDistance = 0; } + // Air slam: accelerate downward, prevent horizontal drift + if (airSlam && !isPreview()) { + caster.motionX = 0; + caster.motionZ = 0; + if (caster.motionY > -1.5) { + caster.motionY -= 0.15; + } + } + // Prevent NPC navigator from interfering with ballistic arc. // lockMovement=WINDUP means applyMovementControl() doesn't clear // the navigator during ACTIVE, so AI pathfinding could add motion. @@ -375,6 +437,13 @@ public class AbilitySlam extends Ability implements IAbilitySlam { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + // Restore flight if it was active before an air slam + if (airSlam && wasFlying && caster instanceof EntityPlayerMP) { + EntityPlayerMP mp = (EntityPlayerMP) caster; + mp.capabilities.isFlying = true; + mp.sendPlayerAbilities(); + } + // Signal that the ability has completed its active phase signalCompletion(); @@ -394,6 +463,16 @@ public class AbilitySlam extends Ability implements IAbilitySlam { // Play slam impact sound on landing world.playSoundAtEntity(caster, "random.explode", 1.0f, 1.0f); + // Scale damage based on fall distance for air slams + float effectiveDamage = damage; + if (airSlam) { + double fallDistance = startY - caster.posY; + if (fallDistance < 0) fallDistance = 0; + // Scale linearly: full damage at leapHeight distance, minimum 25% for tiny falls + float heightFactor = (float) Math.min(1.0, fallDistance / Math.max(1.0, leapHeight)); + effectiveDamage = damage * Math.max(0.25f, heightFactor); + } + // Find all entities in radius @SuppressWarnings("unchecked") List entities = world.getEntitiesWithinAABBExcludingEntity( @@ -409,7 +488,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam { double dz = livingTarget.posZ - caster.posZ; if (dx * dx + dz * dz <= radius * radius) { // Apply damage with scripted event support - boolean wasHit = applyAbilityDamage(caster, livingTarget, damage, knockbackStrength); + boolean wasHit = applyAbilityDamage(caster, livingTarget, effectiveDamage, knockbackStrength); if (wasHit) { applyEffects(livingTarget); } @@ -462,10 +541,13 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; + airSlam = false; + wasFlying = false; } @Override @@ -473,10 +555,26 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; + airSlam = false; + wasFlying = false; + } + + /** + * Restore flight state if it was temporarily disabled for an air slam. + */ + private void restoreFlightIfNeeded(EntityLivingBase caster) { + if (airSlam && wasFlying && caster instanceof EntityPlayerMP) { + EntityPlayerMP mp = (EntityPlayerMP) caster; + if (!mp.capabilities.isFlying) { + mp.capabilities.isFlying = true; + mp.sendPlayerAbilities(); + } + } } @Override @@ -485,6 +583,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam { hasLanded = false; hasRisen = false; airTicks = 0; + airSlam = false; + wasFlying = false; } @Override @@ -494,6 +594,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam { hasLanded = false; hasRisen = false; airTicks = 0; + airSlam = false; + wasFlying = false; } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index c20871dd6..890f51511 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -32,8 +32,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex { private float pullStrength = 0.8f; private float damage = 0.0f; private float knockback = 0.0f; - private boolean aoe = false; - private int maxTargets = 5; + private boolean aoe = true; private boolean damageOnPull = false; private float pullDamage = 0.0f; @@ -84,12 +83,12 @@ public class AbilityVortex extends Ability implements IAbilityVortex { ticksSincePullDamage = 0; if (!isPreview()) { - if (aoe) { - AxisAlignedBB box = caster.boundingBox.expand(pullRadius, pullRadius / 2, pullRadius); - @SuppressWarnings("unchecked") - List entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box); + AxisAlignedBB box = caster.boundingBox.expand(pullRadius, pullRadius / 2, pullRadius); + @SuppressWarnings("unchecked") + List entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box); - int count = 0; + if (aoe) { + // ALL: pull every valid enemy in range for (EntityLivingBase entity : entities) { if (entity == caster) continue; if (entity.isDead) continue; @@ -98,19 +97,37 @@ public class AbilityVortex extends Ability implements IAbilityVortex { double dist = caster.getDistanceToEntity(entity); if (dist <= pullRadius) { getPulledEntities().add(entity.getUniqueID()); - count++; - if (count >= maxTargets) break; } } } else { - // NPC single-target mode: pull the aggro target - // Player: single-target mode has no effect (no target to pull — use AOE mode instead) + // SINGULAR: pull one target + // NPC: pull aggro target if valid, otherwise nearest enemy + // Player: pull nearest enemy in range if (!isPlayerCaster(caster) && target != null && !target.isDead) { double dist = caster.getDistanceToEntity(target); if (dist <= pullRadius) { getPulledEntities().add(target.getUniqueID()); + return; } } + + // Find nearest valid enemy + EntityLivingBase nearest = null; + double nearestDist = Double.MAX_VALUE; + for (EntityLivingBase entity : entities) { + if (entity == caster) continue; + if (entity.isDead) continue; + if (!AbilityTargetHelper.shouldAffect(caster, entity, TargetFilter.ENEMIES, false)) continue; + + double dist = caster.getDistanceToEntity(entity); + if (dist <= pullRadius && dist < nearestDist) { + nearest = entity; + nearestDist = dist; + } + } + if (nearest != null) { + getPulledEntities().add(nearest.getUniqueID()); + } } } } @@ -229,7 +246,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex { nbt.setFloat("damage", damage); nbt.setFloat("knockback", knockback); nbt.setBoolean("aoe", aoe); - nbt.setInteger("maxTargets", maxTargets); nbt.setBoolean("damageOnPull", damageOnPull); nbt.setFloat("pullDamage", pullDamage); } @@ -240,8 +256,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex { this.pullStrength = nbt.getFloat("pullStrength"); this.damage = nbt.getFloat("damage"); this.knockback = nbt.getFloat("knockback"); - this.aoe = nbt.getBoolean("aoe"); - this.maxTargets = nbt.getInteger("maxTargets"); + this.aoe = !nbt.hasKey("aoe") || nbt.getBoolean("aoe"); this.damageOnPull = nbt.getBoolean("damageOnPull"); this.pullDamage = nbt.getFloat("pullDamage"); } @@ -287,14 +302,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex { this.aoe = aoe; } - public int getMaxTargets() { - return maxTargets; - } - - public void setMaxTargets(int maxTargets) { - this.maxTargets = maxTargets; - } - public boolean isDamageOnPull() { return damageOnPull; } @@ -327,8 +334,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex { FieldDef.section("ability.section.aoe"), FieldDef.boolField("gui.enabled", this::isAoe, this::setAoe) .hover("ability.hover.aoe"), - FieldDef.intField("ability.maxTargets", this::getMaxTargets, this::setMaxTargets) - .visibleWhen(this::isAoe), FieldDef.section("ability.section.pullDamage"), FieldDef.boolField("gui.enabled", this::isDamageOnPull, this::setDamageOnPull) .hover("ability.hover.dmgOnPull"), diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index 980a1dfeb..21a2a4390 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -291,12 +291,16 @@ public abstract class AbilityBarrier extends AbilityEnergy { // Properties section defs.add(FieldDef.section("ability.section.properties")); - defs.add(FieldDef.boolField("ability.solid", this::isSolid, this::setSolid)); - defs.add(FieldDef.boolField("ability.knockbackEnabled", this::isKnockbackEnabled, this::setKnockbackEnabled)); + defs.add(FieldDef.boolField("ability.solid", this::isSolid, this::setSolid) + .hover("ability.hover.solid")); + defs.add(FieldDef.boolField("ability.knockbackEnabled", this::isKnockbackEnabled, this::setKnockbackEnabled) + .hover("ability.hover.knockbackEnabled")); defs.add(FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength) .range(0, 10).visibleWhen(this::isKnockbackEnabled)); - defs.add(FieldDef.boolField("ability.absorbing", this::isAbsorbing, this::setAbsorbing)); - defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled)); + defs.add(FieldDef.boolField("ability.absorbing", this::isAbsorbing, this::setAbsorbing) + .hover("ability.hover.absorbing")); + defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled) + .hover("ability.hover.meleeEnabled")); defs.add(FieldDef.floatField("ability.meleeDamageMultiplier", this::getMeleeDamageMultiplier, this::setMeleeDamageMultiplier) .range(0, 10).visibleWhen(this::isMeleeEnabled)); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index ab374bf41..979ee4b55 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -1,5 +1,7 @@ package kamkeel.npcs.entity; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; @@ -67,6 +69,16 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { super(world); } + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + // Beam can extend far from entity position (head); use a generous render distance + // that accounts for the full beam length (origin to head) + double headDist = Math.sqrt(headOffsetX * headOffsetX + headOffsetY * headOffsetY + headOffsetZ * headOffsetZ); + double range = Math.max(128.0D, headDist * 2.0D + 64.0D); + return distance < range * range; + } + private static BeamMode modeFromAnchored(boolean anchored) { return anchored ? BeamMode.ANCHORED : BeamMode.FREE_TRAIL; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java index 8c0fc8fae..cf20be91e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java @@ -264,11 +264,13 @@ public class EntityAbilityDome extends EntityAbilityBarrier { List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (isAllyOfOwner(ent)) continue; // Client-side: only process local player for solid prediction + // Must be checked before isAllyOfOwner which uses server-only PlayerData if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + if (isAllyOfOwner(ent)) continue; + // Current position relative to dome center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index 653d336a2..a0092a2d7 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -421,11 +421,13 @@ public class EntityAbilityPanel extends EntityAbilityBarrier { for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (isAllyOfOwner(ent)) continue; // Client-side: only process local player for solid prediction + // Must be checked before isAllyOfOwner which uses server-only PlayerData if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + if (isAllyOfOwner(ent)) continue; + // Current position relative to panel center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 278fb8001..e866db2a0 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -432,9 +432,23 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { */ protected void handleClientInterpolation() { if (this.interpSteps > 0) { - double newX = this.posX + (this.interpTargetX - this.posX) / this.interpSteps; - double newY = this.posY + (this.interpTargetY - this.posY) / this.interpSteps; - double newZ = this.posZ + (this.interpTargetZ - this.posZ) / this.interpSteps; + // For very slow / stationary projectiles, snap directly to avoid jitter + double dx = this.interpTargetX - this.posX; + double dy = this.interpTargetY - this.posY; + double dz = this.interpTargetZ - this.posZ; + double distSq = dx * dx + dy * dy + dz * dz; + if (distSq < 0.04) { + this.setPosition(this.interpTargetX, this.interpTargetY, this.interpTargetZ); + this.motionX = this.interpTargetMotionX; + this.motionY = this.interpTargetMotionY; + this.motionZ = this.interpTargetMotionZ; + this.interpSteps = 0; + return; + } + + double newX = this.posX + dx / this.interpSteps; + double newY = this.posY + dy / this.interpSteps; + double newZ = this.posZ + dz / this.interpSteps; this.motionX = this.motionX + (this.interpTargetMotionX - this.motionX) / this.interpSteps; this.motionY = this.motionY + (this.interpTargetMotionY - this.motionY) / this.interpSteps; @@ -443,6 +457,11 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.setPosition(newX, newY, newZ); this.interpSteps--; } else { + // Skip motion application for nearly-stationary projectiles to prevent drift + double speedSq = this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ; + if (speedSq < 0.0001) { + return; + } this.posX += this.motionX; this.posY += this.motionY; this.posZ += this.motionZ; @@ -549,10 +568,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (target == owner) continue; if (shouldIgnoreExplosionTarget(target)) continue; + // Measure distance to nearest point on entity's bounding box, not feet position. + // Using feet position (target.posY) causes explosions at impact height to miss + // entities standing below — e.g. a projectile hitting at eye height (~1.62 above feet) + // would measure dist=1.62, failing a radius<=1.6 check despite a direct hit. + double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX)); + double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY)); + double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ)); double dist = Math.sqrt( - Math.pow(target.posX - posX, 2) + - Math.pow(target.posY - posY, 2) + - Math.pow(target.posZ - posZ, 2) + (closestX - posX) * (closestX - posX) + + (closestY - posY) * (closestY - posY) + + (closestZ - posZ) * (closestZ - posZ) ); if (dist <= explosionRad) { diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 718793ddd..93ec65bb6 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -295,7 +295,7 @@ public class CustomNpcs { registerNewEntity(EntityAbilityOrb.class, "abilityorb", 64, 1, true); registerNewEntity(EntityAbilityDisc.class, "abilitydisc", 64, 1, true); registerNewEntity(EntityAbilityLaser.class, "abilitylaser", 64, 1, true); - registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 1, true); + registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 160, 1, true); registerNewEntity(EntityAbilitySweeper.class, "abilitysweeper", 64, 3, true); registerNewEntity(EntityAbilityZone.class, "abilityzone", 64, 3, true); registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 5b5efccbe..31a15551a 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -10,6 +10,7 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.util.AttributeAttackUtil; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; @@ -105,7 +106,8 @@ public class ScriptPlayerEventHandler { // Skip zeroing if the ability has its own movement (Charge/Dash/Slam) if (playerData.abilityData.isMovementLocked()) { Ability current = playerData.abilityData.getCurrentAbility(); - if (current == null || !current.hasAbilityMovement()) { + if (current == null || !current.hasAbilityMovement() + || current.getPhase() != AbilityPhase.ACTIVE) { boolean sFlying = playerData.abilityData.wasFlyingAtLock() || AbilityController.Instance.isPlayerFlying(player); @@ -120,6 +122,17 @@ public class ScriptPlayerEventHandler { } } + // Zero WASD input during ability-controlled movement to prevent + // moveEntityWithHeading from interfering with ability velocity + { + Ability current = playerData.abilityData.getCurrentAbility(); + if (current != null && current.hasAbilityMovement() + && current.getPhase() == AbilityPhase.ACTIVE) { + player.moveForward = 0; + player.moveStrafing = 0; + } + } + if (playerData.updateClient) { SyncController.syncPlayerData((EntityPlayerMP) player, true); playerData.updateClient = false; diff --git a/src/main/java/noppes/npcs/client/ClientEventHandler.java b/src/main/java/noppes/npcs/client/ClientEventHandler.java index 6eebfd5d5..b5e37503a 100644 --- a/src/main/java/noppes/npcs/client/ClientEventHandler.java +++ b/src/main/java/noppes/npcs/client/ClientEventHandler.java @@ -102,7 +102,13 @@ public class ClientEventHandler { // Ability Hotbar: Hold HUD key + scroll wheel to cycle if (event.dwheel != 0 && ClientProxy.AbilityHudKey != null) { int hudKeyCode = ClientProxy.AbilityHudKey.getKeyCode(); - if (hudKeyCode != 0 && org.lwjgl.input.Keyboard.isKeyDown(hudKeyCode)) { + boolean hudKeyHeld = false; + if (hudKeyCode > 0) { + hudKeyHeld = org.lwjgl.input.Keyboard.isKeyDown(hudKeyCode); + } else if (hudKeyCode < 0) { + hudKeyHeld = org.lwjgl.input.Mouse.isButtonDown(hudKeyCode + 100); + } + if (hudKeyHeld) { AbilityHotbarComponent comp = (AbilityHotbarComponent) ClientHudManager.getInstance() .getHudComponents().get(EnumHudComponent.AbilityHotbar); if (comp != null && comp.hasAnyAbilities()) { diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 90d877011..61553093e 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2058,6 +2058,10 @@ ability.meleeDamageMultiplier=Melee Multiplier ability.followCaster=Follow Caster ability.hover.followCaster=Dome stays centered on the caster as they move +ability.hover.solid=Entities cannot pass through the barrier boundary +ability.hover.knockbackEnabled=Repulsion field pushing entities away from barrier surface +ability.hover.absorbing=Damage dealt to the caster is redirected to the barrier's health +ability.hover.meleeEnabled=Barrier can be hit and damaged by melee attacks ability.variant.small=Small From feecf8d991285458ccc02d15b2e1d715ec199a3f Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 21:35:43 -0500 Subject: [PATCH 57/71] Slam while flying --- .../data/ability/type/AbilitySlam.java | 80 ++++++++----------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index c557c4b89..4f07d1cca 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -64,7 +64,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam { private transient int airTicks = 0; private transient int maxAirTicks = 60; // Timeout to prevent stuck in air private transient boolean airSlam = false; // True when slam initiated while already in the air - private transient boolean wasFlying = false; // Saved flight state to restore after air slam + private transient double groundY = 0; // Cached ground level for air slams public AbilitySlam() { this.typeId = "ability.cnpc.slam"; @@ -173,7 +173,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam { *

* On ground: Launch in look direction using ballistic arc, full damage on landing. * In air: Slam straight down from current height, damage scales with fall distance. - * Works regardless of flying state — just checks height above ground. + * Works regardless of flying state — position is forced downward each tick. */ private void executePlayerSlam(EntityLivingBase caster) { if (!caster.onGround) { @@ -183,26 +183,21 @@ public class AbilitySlam extends Ability implements IAbilitySlam { targetY = caster.posY; targetZ = caster.posZ; - // Temporarily disable flight so gravity works during descent - if (caster instanceof EntityPlayerMP) { - EntityPlayerMP mp = (EntityPlayerMP) caster; - wasFlying = mp.capabilities.isFlying; - if (wasFlying) { - mp.capabilities.isFlying = false; - mp.sendPlayerAbilities(); + // Find ground level below player (scan to world bottom) + int bx = (int) Math.floor(caster.posX); + int bz = (int) Math.floor(caster.posZ); + groundY = 0; + for (int by = (int) Math.floor(caster.posY) - 1; by >= 0; by--) { + if (caster.worldObj.getBlock(bx, by, bz).getMaterial().isSolid()) { + groundY = by + 1; + break; } } - // Push downward — no horizontal movement, no upward arc - caster.motionX = 0; - caster.motionZ = 0; - caster.motionY = -0.5; hasLaunched = true; hasRisen = true; // Skip rise detection — we're already above ground - if (!isPreview() && caster instanceof EntityPlayerMP) { - ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( - new S12PacketEntityVelocity(caster)); + if (!isPreview()) { caster.worldObj.playSoundAtEntity(caster, "mob.irongolem.throw", 0.8f, 0.8f); } } else { @@ -366,12 +361,31 @@ public class AbilitySlam extends Ability implements IAbilitySlam { caster.fallDistance = 0; } - // Air slam: accelerate downward, prevent horizontal drift - if (airSlam && !isPreview()) { + // Air slam: force descent via position update (works regardless of flight state) + if (airSlam && !isPreview() && !hasLanded) { + double descentSpeed = Math.min(1.5, 0.3 + airTicks * 0.12); + double nextY = caster.posY - descentSpeed; + + if (nextY <= groundY) { + // Reached ground — land + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).setPositionAndUpdate(caster.posX, groundY, caster.posZ); + } else { + caster.setPosition(caster.posX, groundY, caster.posZ); + } + caster.onGround = true; + onLanding(caster, caster.worldObj); + return; + } + + // Continue descent caster.motionX = 0; caster.motionZ = 0; - if (caster.motionY > -1.5) { - caster.motionY -= 0.15; + caster.motionY = -descentSpeed; + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).setPositionAndUpdate(caster.posX, nextY, caster.posZ); + } else { + caster.setPosition(caster.posX, nextY, caster.posZ); } } @@ -437,13 +451,6 @@ public class AbilitySlam extends Ability implements IAbilitySlam { ((EntityNPCInterface) caster).setNpcJumpingState(false); } - // Restore flight if it was active before an air slam - if (airSlam && wasFlying && caster instanceof EntityPlayerMP) { - EntityPlayerMP mp = (EntityPlayerMP) caster; - mp.capabilities.isFlying = true; - mp.sendPlayerAbilities(); - } - // Signal that the ability has completed its active phase signalCompletion(); @@ -541,13 +548,11 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } - restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; airSlam = false; - wasFlying = false; } @Override @@ -555,26 +560,11 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } - restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; airSlam = false; - wasFlying = false; - } - - /** - * Restore flight state if it was temporarily disabled for an air slam. - */ - private void restoreFlightIfNeeded(EntityLivingBase caster) { - if (airSlam && wasFlying && caster instanceof EntityPlayerMP) { - EntityPlayerMP mp = (EntityPlayerMP) caster; - if (!mp.capabilities.isFlying) { - mp.capabilities.isFlying = true; - mp.sendPlayerAbilities(); - } - } } @Override @@ -584,7 +574,6 @@ public class AbilitySlam extends Ability implements IAbilitySlam { hasRisen = false; airTicks = 0; airSlam = false; - wasFlying = false; } @Override @@ -595,7 +584,6 @@ public class AbilitySlam extends Ability implements IAbilitySlam { hasRisen = false; airTicks = 0; airSlam = false; - wasFlying = false; } @Override From 25320f8de11b1670725f0b2cc1994c25e7259b6f Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 21:51:10 -0500 Subject: [PATCH 58/71] Fix Remote Checks --- .../data/ability/type/AbilityHeavyHit.java | 2 +- .../data/ability/type/AbilityShockwave.java | 2 +- .../data/ability/type/AbilityVortex.java | 4 +++- .../npcs/entity/EntityEnergyProjectile.java | 20 +++---------------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java index 03299a67f..510193d54 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java @@ -90,7 +90,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit { return; } - if (tick == hitDelayTicks) { + if (tick == hitDelayTicks && !caster.worldObj.isRemote) { // Calculate forward and right vectors from caster yaw float yawRad = (float) Math.toRadians(caster.rotationYaw); double forwardX = -Math.sin(yawRad); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index b4173bb1a..c77994789 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -62,7 +62,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { - if (!isPreview()) { + if (!isPreview() && !caster.worldObj.isRemote) { // Shockwave is instant - apply effect immediately after windup // Get all entities in radius diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index 890f51511..0608d6926 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -82,7 +82,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex { pullComplete = false; ticksSincePullDamage = 0; - if (!isPreview()) { + if (!isPreview() && !caster.worldObj.isRemote) { AxisAlignedBB box = caster.boundingBox.expand(pullRadius, pullRadius / 2, pullRadius); @SuppressWarnings("unchecked") List entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box); @@ -145,6 +145,8 @@ public class AbilityVortex extends Ability implements IAbilityVortex { return; } + if (caster.worldObj.isRemote) return; + double destX = caster.posX; double destY = caster.posY; double destZ = caster.posZ; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index e866db2a0..77ec1c7ca 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -19,7 +19,6 @@ import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.EnumCreatureType; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; @@ -548,6 +547,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void doExplosion() { if (previewMode) return; // Skip explosion in preview mode + if (worldObj.isRemote) return; // Explosions are server-side only float explosionRad = getExplosionRadius(); if (Float.isNaN(explosionRad) || explosionRad <= 0) return; spawnExplosionRenderEntity(explosionRad); @@ -592,25 +592,11 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { /** * Explosion safety filter. - * Reuses standard projectile-friendly checks and blocks passive non-player entities. + * Reuses standard projectile-friendly checks (owner, faction, party). */ protected boolean shouldIgnoreExplosionTarget(EntityLivingBase target) { if (target == null) return true; - if (shouldIgnoreEntity(target)) return true; - return isPassiveNonPlayerEntity(target); - } - - /** - * Passive mobs (animals/ambient/water creatures) are never damaged by energy explosions. - * Players and hostile mobs remain valid unless other friendly-fire checks reject them. - */ - protected boolean isPassiveNonPlayerEntity(EntityLivingBase target) { - if (target instanceof EntityPlayer) return false; - if (target instanceof EntityNPCInterface) return false; - if (target.isCreatureType(EnumCreatureType.monster, false)) return false; - return target.isCreatureType(EnumCreatureType.creature, false) - || target.isCreatureType(EnumCreatureType.ambient, false) - || target.isCreatureType(EnumCreatureType.waterCreature, false); + return shouldIgnoreEntity(target); } /** From b5534b4a399fe675383684fe92b5f726ff660635 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 22 Feb 2026 22:39:33 -0500 Subject: [PATCH 59/71] Lot's of Weird Ability Interaction Fixes --- .../data/ability/type/AbilitySlam.java | 129 ++++++++++----- .../data/ability/type/energy/AbilityBeam.java | 4 +- .../data/ability/type/energy/AbilityDisc.java | 2 +- .../type/energy/AbilityEnergyProjectile.java | 5 +- .../data/ability/type/energy/AbilityOrb.java | 2 +- .../npcs/entity/EntityAbilityLaser.java | 151 ++++++++++-------- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 2 +- .../npcs/entity/EntityEnergyProjectile.java | 16 +- .../hud/ability/AbilityHotbarComponent.java | 33 ++-- 9 files changed, 212 insertions(+), 132 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index 4f07d1cca..21177a650 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -64,7 +64,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam { private transient int airTicks = 0; private transient int maxAirTicks = 60; // Timeout to prevent stuck in air private transient boolean airSlam = false; // True when slam initiated while already in the air - private transient double groundY = 0; // Cached ground level for air slams + private transient double airSlamStartY = 0; // Start height for air-slam damage scaling + private transient boolean wasFlying = false; // Restore player flight after air slam public AbilitySlam() { this.typeId = "ability.cnpc.slam"; @@ -179,30 +180,46 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!caster.onGround) { // Air slam: player is already airborne (flying, falling, jumping) airSlam = true; + airSlamStartY = caster.posY; targetX = caster.posX; targetY = caster.posY; targetZ = caster.posZ; - // Find ground level below player (scan to world bottom) - int bx = (int) Math.floor(caster.posX); - int bz = (int) Math.floor(caster.posZ); - groundY = 0; - for (int by = (int) Math.floor(caster.posY) - 1; by >= 0; by--) { - if (caster.worldObj.getBlock(bx, by, bz).getMaterial().isSolid()) { - groundY = by + 1; - break; + // If the player is flying, temporarily disable it so gravity/velocity apply normally. + if (caster instanceof EntityPlayerMP) { + EntityPlayerMP mp = (EntityPlayerMP) caster; + wasFlying = mp.capabilities.isFlying; + if (wasFlying) { + mp.capabilities.isFlying = false; + mp.sendPlayerAbilities(); } } hasLaunched = true; hasRisen = true; // Skip rise detection — we're already above ground + // Start descent smoothly; physics/collision handles exact landing surface. + // Only damp horizontal drift when movement is locked during ACTIVE. + if (isMovementLockedDuringActive()) { + caster.motionX *= 0.25; + caster.motionZ *= 0.25; + } + caster.motionY = Math.min(caster.motionY, -0.2); + if (!isPreview()) { + if (caster instanceof EntityPlayerMP) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + } else { + caster.velocityChanged = true; + } caster.worldObj.playSoundAtEntity(caster, "mob.irongolem.throw", 0.8f, 0.8f); } } else { // Ground slam: normal ballistic arc airSlam = false; + airSlamStartY = 0; + wasFlying = false; // Calculate target position along player's look direction (horizontal only) float yawRad = (float) Math.toRadians(caster.rotationYaw); @@ -354,6 +371,10 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!hasLaunched) return; if (hasLanded) return; + boolean playerCaster = isPlayerCaster(caster); + boolean activeMovementLocked = isMovementLockedDuringActive(); + boolean forceHorizontalControl = !playerCaster || activeMovementLocked; + airTicks++; // Continuously reset fall distance during slam to prevent fall damage @@ -361,31 +382,22 @@ public class AbilitySlam extends Ability implements IAbilitySlam { caster.fallDistance = 0; } - // Air slam: force descent via position update (works regardless of flight state) + // Air slam: accelerate descent smoothly via velocity; landing uses normal collision. if (airSlam && !isPreview() && !hasLanded) { - double descentSpeed = Math.min(1.5, 0.3 + airTicks * 0.12); - double nextY = caster.posY - descentSpeed; - - if (nextY <= groundY) { - // Reached ground — land - if (caster instanceof EntityPlayerMP) { - ((EntityPlayerMP) caster).setPositionAndUpdate(caster.posX, groundY, caster.posZ); - } else { - caster.setPosition(caster.posX, groundY, caster.posZ); - } - caster.onGround = true; - onLanding(caster, caster.worldObj); - return; + if (forceHorizontalControl) { + caster.motionX *= 0.2; + caster.motionZ *= 0.2; } - - // Continue descent - caster.motionX = 0; - caster.motionZ = 0; - caster.motionY = -descentSpeed; + double nextMotionY = caster.motionY - 0.18; + caster.motionY = Math.max(-1.6, nextMotionY); if (caster instanceof EntityPlayerMP) { - ((EntityPlayerMP) caster).setPositionAndUpdate(caster.posX, nextY, caster.posZ); + // Only push authoritative velocity every tick when ACTIVE movement is locked. + if (forceHorizontalControl) { + ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( + new S12PacketEntityVelocity(caster)); + } } else { - caster.setPosition(caster.posX, nextY, caster.posZ); + caster.velocityChanged = true; } } @@ -427,16 +439,19 @@ public class AbilitySlam extends Ability implements IAbilitySlam { return; } - // Face toward target destination while in air (both NPC and Player) + // Face toward destination while in air unless player ACTIVE movement is unlocked. + // Unlocked players keep free horizontal drift/look control. if (!isPreview()) { - double dx = targetX - caster.posX; - double dz = targetZ - caster.posZ; - if (dx * dx + dz * dz > 0.25) { - float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0D / Math.PI); - caster.rotationYaw = targetYaw; - caster.rotationYawHead = targetYaw; - if (isPlayerCaster(caster)) { - caster.velocityChanged = true; + if (forceHorizontalControl) { + double dx = targetX - caster.posX; + double dz = targetZ - caster.posZ; + if (dx * dx + dz * dz > 0.25) { + float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0D / Math.PI); + caster.rotationYaw = targetYaw; + caster.rotationYawHead = targetYaw; + if (playerCaster) { + caster.velocityChanged = true; + } } } } @@ -450,13 +465,16 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + restoreFlightIfNeeded(caster); // Signal that the ability has completed its active phase signalCompletion(); - // Stop horizontal momentum - caster.motionX = 0; - caster.motionZ = 0; + // Stop horizontal momentum only when movement is controlled by the ability. + if (!isPlayerCaster(caster) || isMovementLockedDuringActive()) { + caster.motionX = 0; + caster.motionZ = 0; + } if (!isPreview()) { // Reset fall distance to prevent fall damage on landing @@ -473,7 +491,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam { // Scale damage based on fall distance for air slams float effectiveDamage = damage; if (airSlam) { - double fallDistance = startY - caster.posY; + double fallStartY = airSlamStartY > 0 ? airSlamStartY : startY; + double fallDistance = fallStartY - caster.posY; if (fallDistance < 0) fallDistance = 0; // Scale linearly: full damage at leapHeight distance, minimum 25% for tiny falls float heightFactor = (float) Math.min(1.0, fallDistance / Math.max(1.0, leapHeight)); @@ -548,11 +567,14 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; airSlam = false; + airSlamStartY = 0; + wasFlying = false; } @Override @@ -560,20 +582,26 @@ public class AbilitySlam extends Ability implements IAbilitySlam { if (!isPreview() && caster instanceof EntityNPCInterface) { ((EntityNPCInterface) caster).setNpcJumpingState(false); } + restoreFlightIfNeeded(caster); hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; airSlam = false; + airSlamStartY = 0; + wasFlying = false; } @Override public void cleanup() { + // Caster may be unavailable here; flight restoration is handled in onComplete/onInterrupt. hasLaunched = false; hasLanded = false; hasRisen = false; airTicks = 0; airSlam = false; + airSlamStartY = 0; + wasFlying = false; } @Override @@ -584,6 +612,21 @@ public class AbilitySlam extends Ability implements IAbilitySlam { hasRisen = false; airTicks = 0; airSlam = false; + airSlamStartY = 0; + wasFlying = false; + } + + /** + * Restore player flight if it was disabled for an air slam. + */ + private void restoreFlightIfNeeded(EntityLivingBase caster) { + if (airSlam && wasFlying && caster instanceof EntityPlayerMP) { + EntityPlayerMP mp = (EntityPlayerMP) caster; + if (!mp.capabilities.isFlying) { + mp.capabilities.isFlying = true; + mp.sendPlayerAbilities(); + } + } } @Override 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 9fd5ad973..b8cf779be 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 @@ -36,8 +36,8 @@ public class AbilityBeam extends AbilityEnergyProjectile impl public AbilityBeam() { super( - new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f), - new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f, HitType.MULTI, 5), + new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 10.0f), + new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f, HitType.SINGLE, 5), new EnergyHomingData(0.4f, true, 0.1f, 15.0f), new EnergyLifespanData(150.0f, 200) ); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index 0d2d2fc99..d8cb38e4f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -38,7 +38,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl public AbilityDisc() { super( - new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f), + new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 10.0f), new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f, HitType.PIERCE, 5), new EnergyHomingData(0.6f, true, 0.12f, 18.0f), new EnergyLifespanData(150.0f, 200) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index cfe9457a6..92e53f10b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -254,10 +254,11 @@ public abstract class AbilityEnergyProjectile /** * Hook for launch target selection. - * Default returns null so projectiles launch along owner look vector. + * Players launch along their look vector (crosshair-aligned), so target is ignored. + * NPCs use their resolved target so launch math can track the target immediately. */ protected EntityLivingBase getLaunchTarget(EntityLivingBase caster, EntityLivingBase target) { - return null; + return isPlayerCaster(caster) ? null : target; } /** 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 dac288a6c..da515f7d2 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 @@ -32,7 +32,7 @@ public class AbilityOrb extends AbilityEnergyProjectile implem public AbilityOrb() { super( - new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f), + new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 10.0f), new EnergyCombatData(), new EnergyHomingData(), new EnergyLifespanData() diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 56048d973..1ff3ece70 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -81,12 +81,19 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.dirZ = dz / len; } } else { - // Fire in NPC's facing direction - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - this.dirX = -Math.sin(yaw) * Math.cos(pitch); - this.dirY = -Math.sin(pitch); - this.dirZ = Math.cos(yaw) * Math.cos(pitch); + // Fall back to owner look direction so launch matches visual aim. + Vec3 look = owner == null ? null : owner.getLookVec(); + if (look != null) { + this.dirX = look.xCoord; + this.dirY = look.yCoord; + this.dirZ = look.zCoord; + } else { + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + this.dirX = -Math.sin(yaw) * Math.cos(pitch); + this.dirY = -Math.sin(pitch); + this.dirZ = Math.cos(yaw) * Math.cos(pitch); + } } // Initialize end point @@ -134,6 +141,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { // Check for block collision along the new segment if (!worldObj.isRemote) { checkBlockCollision(); + // Re-evaluate barriers against the current beam segment before entity hits. + // This prevents first-tick pass-through where the pre-update barrier check + // still sees a zero-length beam. + if (checkBarrierCollisionForCurrentSegment()) { + return; + } checkEntityCollisionAlongLine(); } } else { @@ -163,19 +176,33 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { if (owner == null || !(owner instanceof EntityLivingBase)) return; EntityLivingBase livingOwner = (EntityLivingBase) owner; - // Update direction from owner's current rotation - float yaw = (float) Math.toRadians(owner.rotationYaw); - if (lockVerticalDirection) { - // Only update horizontal direction (yaw), keep vertical (pitch) fixed - double horizontalLen = Math.sqrt(dirX * dirX + dirZ * dirZ); - if (horizontalLen < 0.001) horizontalLen = 1.0; - dirX = -Math.sin(yaw) * horizontalLen; - dirZ = Math.cos(yaw) * horizontalLen; - } else { + // Use owner look vector (head/eye aim) for direction updates so hit logic matches visuals. + Vec3 look = livingOwner.getLookVec(); + if (look == null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); float pitch = (float) Math.toRadians(owner.rotationPitch); - dirX = -Math.sin(yaw) * Math.cos(pitch); - dirY = -Math.sin(pitch); - dirZ = Math.cos(yaw) * Math.cos(pitch); + look = Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } + + if (lockVerticalDirection) { + // Keep current vertical component, but align horizontal direction to current look. + double lookHorizontalLen = Math.sqrt(look.xCoord * look.xCoord + look.zCoord * look.zCoord); + if (lookHorizontalLen > 0.0001) { + double currentHorizontalLen = Math.sqrt(dirX * dirX + dirZ * dirZ); + if (currentHorizontalLen < 0.0001) { + currentHorizontalLen = Math.sqrt(Math.max(0.0001, 1.0 - dirY * dirY)); + } + dirX = (look.xCoord / lookHorizontalLen) * currentHorizontalLen; + dirZ = (look.zCoord / lookHorizontalLen) * currentHorizontalLen; + } + } else { + dirX = look.xCoord; + dirY = look.yCoord; + dirZ = look.zCoord; } // Keep direction normalized for consistent expansion and collision math. @@ -188,10 +215,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { direction.zCoord / dirLen ); } else { - Vec3 look = livingOwner.getLookVec(); - if (look != null) { - direction = look; - } + direction = look != null ? look : Vec3.createVectorHelper(1.0, 0.0, 0.0); } dirX = direction.xCoord; @@ -320,49 +344,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { /** * Check if an entity's bounding box intersects with the laser line. - * Uses separate XZ (horizontal) and Y (vertical) checks against - * the entity's full bounding box rather than just its center point. + * Uses a swept segment vs expanded AABB intercept test for reliable hits. */ private boolean isEntityOnLine(EntityLivingBase entity) { - double ex = entity.posX; - double ez = entity.posZ; + AxisAlignedBB bb = entity.boundingBox; + if (bb == null) return false; - // Project entity XZ position onto the laser line direction - double vx = ex - startX; - double vz = ez - startZ; - // Project using horizontal direction components only - double dirLenXZ = Math.sqrt(dirX * dirX + dirZ * dirZ); - double dot; - if (dirLenXZ > 0.001) { - dot = (vx * dirX + vz * dirZ + (entity.posY + entity.height * 0.5 - startY) * dirY); - } else { - // Laser fires nearly straight up/down - use full 3D projection - dot = vx * dirX + (entity.posY + entity.height * 0.5 - startY) * dirY + vz * dirZ; - } - dot = Math.max(0, Math.min(dot, currentLength)); + // Minimum effective width keeps thin lasers from visually clipping targets without hitting. + double effectiveWidth = Math.max(laserWidth, 0.65); + double expand = effectiveWidth * 0.5; + AxisAlignedBB expanded = bb.expand(expand, expand, expand); - // Closest point on laser line at this parameter - double closestX = startX + dirX * dot; - double closestY = startY + dirY * dot; - double closestZ = startZ + dirZ * dot; + Vec3 segmentStart = Vec3.createVectorHelper(startX, startY, startZ); + Vec3 segmentEnd = Vec3.createVectorHelper(endX, endY, endZ); + MovingObjectPosition intercept = expanded.calculateIntercept(segmentStart, segmentEnd); + if (intercept != null) return true; - // XZ distance check: entity center vs laser line point - // Use minimum collision width so visually thin lasers still hit reliably - double xzDistSq = (ex - closestX) * (ex - closestX) + (ez - closestZ) * (ez - closestZ); - double effectiveWidth = Math.max(laserWidth, 0.5); - double hitRadiusXZ = effectiveWidth * 0.5 + entity.width * 0.5; - if (xzDistSq > hitRadiusXZ * hitRadiusXZ) return false; - - // Y overlap check: laser point vs entity's full height range - double entityMinY = entity.boundingBox.minY; - double entityMaxY = entity.boundingBox.maxY; - double laserHalfWidth = effectiveWidth * 0.5; - - // Check if laser Y is within entity's height range (expanded by laser half-width) - if (closestY < entityMinY - laserHalfWidth) return false; - if (closestY > entityMaxY + laserHalfWidth) return false; - - return true; + // Handle edge case where the segment starts or ends already inside the target volume. + return expanded.isVecInside(segmentStart) || expanded.isVecInside(segmentEnd); } // ==================== BARRIER COLLISION ==================== @@ -375,7 +374,17 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { @Override @SuppressWarnings("unchecked") protected boolean checkBarrierCollision() { - if (currentLength <= 0 || fullyExtended) return false; + return checkBarrierCollisionInternal(false); + } + + private boolean checkBarrierCollisionForCurrentSegment() { + return checkBarrierCollisionInternal(true); + } + + @SuppressWarnings("unchecked") + private boolean checkBarrierCollisionInternal(boolean allowWhenFullyExtended) { + if (currentLength <= 0) return false; + if (fullyExtended && !allowWhenFullyExtended) return false; List barriers = EntityAbilityBarrier.getActiveBarriers(worldObj); for (EntityAbilityBarrier barrier : barriers) { @@ -404,11 +413,25 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { float damage = getModifiedDamage(); dome.onProjectileHit(this, damage); - return false; // Don't kill laser — it's truncated and will linger + // Block this tick so entity collision doesn't continue through the dome. + // Laser remains alive and lingers at truncated length. + return true; } } else { - // Non-dome barriers: fall through to standard check - if (barrier.isIncomingProjectile(this)) { + // For line-based lasers, use a swept segment against generic barrier checks. + // Panel/Dome logic expects previous+current positions and motion; map those + // to [start -> end] each tick. + double segMotionX = endX - startX; + double segMotionY = endY - startY; + double segMotionZ = endZ - startZ; + boolean incoming = barrier.isIncomingGenericProjectile( + endX, endY, endZ, + segMotionX, segMotionY, segMotionZ, + startX, startY, startZ, + this.ownerEntityId + ); + + if (incoming) { float damage = getModifiedDamage(); if (barrier.onProjectileHit(this, damage)) { hasHit = true; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 407012efd..3e8b9af39 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -72,7 +72,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - startMovingAlongLookVectorDefault(); + startMovingTowardTargetDefault(target); } @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 77ec1c7ca..aa50745ea 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -837,7 +837,11 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { */ protected boolean beginLookVectorLaunch(boolean resetInterpolationState) { setCharging(false); - boolean positioned = setLookVectorLaunchPosition(true); + Entity owner = getOwnerEntity(); + boolean positioned = false; + if (owner instanceof EntityLivingBase && shouldSnapLaunchToLookVector((EntityLivingBase) owner)) { + positioned = setLookVectorLaunchPosition((EntityLivingBase) owner, getOwnerLookVector(), true); + } syncStartPositionToCurrent(); if (resetInterpolationState) { syncPositionStateToCurrent(true); @@ -850,7 +854,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { */ protected boolean beginLookVectorLaunch(EntityLivingBase owner, Vec3 look, boolean resetInterpolationState) { setCharging(false); - boolean positioned = setLookVectorLaunchPosition(owner, look, true); + boolean positioned = shouldSnapLaunchToLookVector(owner) && setLookVectorLaunchPosition(owner, look, true); syncStartPositionToCurrent(); if (resetInterpolationState) { syncPositionStateToCurrent(true); @@ -858,6 +862,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { return positioned; } + /** + * Only player-cast projectiles should be recentered to look-vector launch origin. + * NPC projectiles keep their anchor charge origin so casts originate from the configured anchor. + */ + protected boolean shouldSnapLaunchToLookVector(EntityLivingBase owner) { + return owner instanceof EntityPlayer; + } + /** * Default preview launch: snap to look-vector origin and fire along look. */ diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index eaadbbc7b..90ca61ea6 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -31,6 +31,7 @@ public class AbilityHotbarComponent extends HudComponent { private static final int BASE_SLOT_SIZE = 24; private static final int BASE_SPACING = 28; + private static final int EDIT_HANDLE_SIZE = 10; private AbilityHotbarSlotRenderer[] slots = new AbilityHotbarSlotRenderer[TOTAL_SLOTS]; @@ -430,25 +431,18 @@ public class AbilityHotbarComponent extends HudComponent { int modeLabelW = fr.getStringWidth(modeLabel); fr.drawStringWithShadow(modeLabel, overlayWidth / 2 - modeLabelW / 2, overlayHeight + 2, 0xAA888888); - GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - GL11.glColor4f(0.2f, 0.8f, 0.2f, 0.8f); - GL11.glLineWidth(1.0f); - GL11.glBegin(GL11.GL_LINE_LOOP); - GL11.glVertex2f(0, 0); - GL11.glVertex2f(overlayWidth, 0); - GL11.glVertex2f(overlayWidth, overlayHeight); - GL11.glVertex2f(0, overlayHeight); - GL11.glEnd(); + net.minecraft.client.gui.Gui.drawRect(0, 0, overlayWidth, overlayHeight, 0x22000000); + drawRectOutline(0, 0, overlayWidth, overlayHeight, 0xCC66CC66); - GL11.glColor4f(0.8f, 0.8f, 0.8f, 0.9f); - GL11.glBegin(GL11.GL_QUADS); - GL11.glVertex2f(overlayWidth - 10, overlayHeight - 10); - GL11.glVertex2f(overlayWidth, overlayHeight - 10); - GL11.glVertex2f(overlayWidth, overlayHeight); - GL11.glVertex2f(overlayWidth - 10, overlayHeight); - GL11.glEnd(); + int handleLeft = overlayWidth - EDIT_HANDLE_SIZE; + int handleTop = overlayHeight - EDIT_HANDLE_SIZE; + net.minecraft.client.gui.Gui.drawRect(handleLeft, handleTop, overlayWidth, overlayHeight, 0xFFE0E0E0); + drawRectOutline(handleLeft, handleTop, overlayWidth, overlayHeight, 0xFF5A5A5A); + net.minecraft.client.gui.Gui.drawRect(handleLeft + 2, handleTop + 7, handleLeft + 8, handleTop + 8, 0xAA666666); + net.minecraft.client.gui.Gui.drawRect(handleLeft + 4, handleTop + 5, handleLeft + 8, handleTop + 6, 0xAA666666); + net.minecraft.client.gui.Gui.drawRect(handleLeft + 6, handleTop + 3, handleLeft + 8, handleTop + 4, 0xAA666666); GL11.glDisable(GL11.GL_BLEND); GL11.glEnable(GL11.GL_TEXTURE_2D); @@ -694,4 +688,11 @@ public class AbilityHotbarComponent extends HudComponent { updateAbilities(); lastUpdateTime = Minecraft.getSystemTime(); } + + private void drawRectOutline(int left, int top, int right, int bottom, int color) { + net.minecraft.client.gui.Gui.drawRect(left, top, right, top + 1, color); + net.minecraft.client.gui.Gui.drawRect(left, bottom - 1, right, bottom, color); + net.minecraft.client.gui.Gui.drawRect(left, top, left + 1, bottom, color); + net.minecraft.client.gui.Gui.drawRect(right - 1, top, right, bottom, color); + } } From adf9cd7012f11f23388974717fb3dc847eac1339 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 00:17:34 -0500 Subject: [PATCH 60/71] Add Barrier Reflection! --- .../client/renderer/RenderEnergyAbility.java | 13 + .../data/energy/EnergyBarrierData.java | 26 ++ .../ability/data/energy/EnergyCombatData.java | 14 +- .../ability/type/energy/AbilityBarrier.java | 21 + .../data/ability/type/energy/AbilityBeam.java | 4 +- .../data/ability/type/energy/AbilityDisc.java | 4 +- .../type/energy/AbilityEnergyProjectile.java | 4 +- .../ability/type/energy/AbilityLaserShot.java | 4 +- .../data/ability/type/energy/AbilityOrb.java | 4 +- .../ability/type/energy/AbilitySlicer.java | 4 +- .../npcs/entity/EntityAbilityBarrier.java | 111 ++++- .../npcs/entity/EntityAbilityBeam.java | 26 +- .../npcs/entity/EntityAbilityDisc.java | 40 +- .../npcs/entity/EntityAbilityLaser.java | 141 ++++++- .../npcs/entity/EntityEnergyProjectile.java | 395 +++++++++++++++++- .../npcs/client/ClientEventHandler.java | 8 +- .../hud/ability/AbilityHotbarComponent.java | 15 +- .../scripted/entity/ScriptEnergyBarrier.java | 16 + .../assets/customnpcs/lang/en_US.lang | 3 + 19 files changed, 792 insertions(+), 61 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java index ac88c47e8..e87e24d51 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer; import kamkeel.npcs.entity.EntityEnergyAbility; +import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.Render; @@ -296,6 +297,18 @@ public abstract class RenderEnergyAbility extends Render { int innerColor = entity.getInnerColor(); int fadeTime = entity.getLightningFadeTime(); + // Barrier impact spark: briefly force brighter white crackles at the contact point. + if (entity instanceof EntityEnergyProjectile) { + int sparkTicks = ((EntityEnergyProjectile) entity).getBarrierSparkTicks(); + if (sparkTicks > 0) { + density = Math.max(density, 4.0f + sparkTicks * 0.6f); + radius = Math.max(radius, innerRadius + Math.max(0.35f, baseSize * 0.65f)); + outerColor = 0xF0F6FF; + innerColor = 0xFFFFFF; + fadeTime = Math.max(fadeTime, 6); + } + } + state.update(density, radius, outerColor, innerColor, fadeTime); state.render(); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java index 130b7d2ba..1217c85d5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java @@ -37,6 +37,10 @@ public class EnergyBarrierData { public boolean meleeEnabled = false; public float meleeDamageMultiplier = 1.0f; + // Reflection: rebound projectiles that fail to break the barrier + public boolean reflect = false; + public float reflectStrengthPct = 25.0f; + /** * Per-projectile-type damage multipliers. * Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam"). @@ -119,6 +123,22 @@ public class EnergyBarrierData { this.defaultMultiplier = defaultMultiplier; } + public boolean isReflect() { + return reflect; + } + + public void setReflect(boolean reflect) { + this.reflect = reflect; + } + + public float getReflectStrengthPct() { + return reflectStrengthPct; + } + + public void setReflectStrengthPct(float reflectStrengthPct) { + this.reflectStrengthPct = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct)); + } + // ==================== NBT ==================== public void writeNBT(NBTTagCompound nbt) { @@ -133,6 +153,8 @@ public class EnergyBarrierData { nbt.setBoolean("barrierAbsorbing", absorbing); nbt.setBoolean("barrierMelee", meleeEnabled); nbt.setFloat("barrierMeleeMult", meleeDamageMultiplier); + nbt.setBoolean("barrierReflect", reflect); + nbt.setFloat("barrierReflectStrPct", reflectStrengthPct); NBTTagCompound multNbt = new NBTTagCompound(); for (Map.Entry entry : damageMultipliers.entrySet()) { @@ -153,6 +175,8 @@ public class EnergyBarrierData { absorbing = nbt.hasKey("barrierAbsorbing") && nbt.getBoolean("barrierAbsorbing"); meleeEnabled = nbt.hasKey("barrierMelee") && nbt.getBoolean("barrierMelee"); meleeDamageMultiplier = nbt.hasKey("barrierMeleeMult") ? nbt.getFloat("barrierMeleeMult") : 1.0f; + reflect = nbt.hasKey("barrierReflect") && nbt.getBoolean("barrierReflect"); + setReflectStrengthPct(nbt.hasKey("barrierReflectStrPct") ? nbt.getFloat("barrierReflectStrPct") : 25.0f); damageMultipliers.clear(); if (nbt.hasKey("barrierMultipliers")) { @@ -174,6 +198,8 @@ public class EnergyBarrierData { copy.absorbing = absorbing; copy.meleeEnabled = meleeEnabled; copy.meleeDamageMultiplier = meleeDamageMultiplier; + copy.reflect = reflect; + copy.reflectStrengthPct = reflectStrengthPct; copy.damageMultipliers = new HashMap<>(damageMultipliers); return copy; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java index 0825fcfef..1c8418b96 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java @@ -9,6 +9,8 @@ import noppes.npcs.api.ability.data.IEnergyCombatData; * Used as a parameter object for entity constructors and ability configuration. */ public class EnergyCombatData implements IEnergyCombatData { + public static final float MAX_EXPLOSION_RADIUS = 15.0f; + public float damage = 7.0f; public float knockback = 1.0f; public float knockbackUp = 0.1f; @@ -28,7 +30,7 @@ public class EnergyCombatData implements IEnergyCombatData { this.knockback = knockback; this.knockbackUp = knockbackUp; this.explosive = explosive; - this.explosionRadius = explosionRadius; + this.explosionRadius = clampExplosionRadius(explosionRadius); this.explosionDamageFalloff = explosionDamageFalloff; this.hitType = hitType; this.multiHitDelayTicks = multiHitDelay; @@ -40,7 +42,7 @@ public class EnergyCombatData implements IEnergyCombatData { this.knockback = knockback; this.knockbackUp = knockbackUp; this.explosive = explosive; - this.explosionRadius = explosionRadius; + this.explosionRadius = clampExplosionRadius(explosionRadius); this.explosionDamageFalloff = explosionDamageFalloff; } @@ -91,7 +93,7 @@ public class EnergyCombatData implements IEnergyCombatData { @Override public void setExplosionRadius(float explosionRadius) { - this.explosionRadius = Math.max(0, explosionRadius); + this.explosionRadius = clampExplosionRadius(explosionRadius); } @Override @@ -129,9 +131,15 @@ public class EnergyCombatData implements IEnergyCombatData { if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f; if (Float.isNaN(knockback) || Float.isInfinite(knockback) || knockback < 0) knockback = 1.0f; if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f; + explosionRadius = clampExplosionRadius(explosionRadius); if (multiHitDelayTicks < 1) multiHitDelayTicks = 1; } + private static float clampExplosionRadius(float explosionRadius) { + if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius)) return 0.0f; + return Math.max(0.0f, Math.min(MAX_EXPLOSION_RADIUS, explosionRadius)); + } + public EnergyCombatData copy() { EnergyCombatData copy = new EnergyCombatData(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index 21a2a4390..d1054e858 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -254,6 +254,23 @@ public abstract class AbilityBarrier extends AbilityEnergy { barrierData.absorbing = absorbing; } + // Reflection data + public boolean isReflect() { + return barrierData.reflect; + } + + public void setReflect(boolean reflect) { + barrierData.reflect = reflect; + } + + public float getReflectStrengthPct() { + return barrierData.reflectStrengthPct; + } + + public void setReflectStrengthPct(float strengthPct) { + barrierData.setReflectStrengthPct(strengthPct); + } + // Melee data public boolean isMeleeEnabled() { return barrierData.meleeEnabled; @@ -299,6 +316,10 @@ public abstract class AbilityBarrier extends AbilityEnergy { .range(0, 10).visibleWhen(this::isKnockbackEnabled)); defs.add(FieldDef.boolField("ability.absorbing", this::isAbsorbing, this::setAbsorbing) .hover("ability.hover.absorbing")); + defs.add(FieldDef.boolField("ability.reflect", this::isReflect, this::setReflect) + .hover("ability.hover.reflect")); + defs.add(FieldDef.floatField("ability.reflectStrength", this::getReflectStrengthPct, this::setReflectStrengthPct) + .range(0, 100).visibleWhen(this::isReflect)); defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled) .hover("ability.hover.meleeEnabled")); defs.add(FieldDef.floatField("ability.meleeDamageMultiplier", this::getMeleeDamageMultiplier, this::setMeleeDamageMultiplier) 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 b8cf779be..6e1305ed8 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 @@ -200,7 +200,9 @@ public class AbilityBeam extends AbilityEnergyProjectile impl )); defs.add(FieldDef.section("ability.section.explosive")); defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive")); - defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive)); + defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius) + .range(0.0f, EnergyCombatData.MAX_EXPLOSION_RADIUS) + .visibleWhen(this::isExplosive)); defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index d8cb38e4f..f264f2802 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -226,7 +226,9 @@ public class AbilityDisc extends AbilityEnergyProjectile impl defs.add(FieldDef.intField("gui.delay", this::getBoomerangDelay, this::setBoomerangDelay).visibleWhen(this::isBoomerang)); defs.add(FieldDef.section("ability.section.explosive")); defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive")); - defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive)); + defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius) + .range(0.0f, EnergyCombatData.MAX_EXPLOSION_RADIUS) + .visibleWhen(this::isExplosive)); defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 92e53f10b..4318c88eb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -651,12 +651,12 @@ public abstract class AbilityEnergyProjectile @Override public float getExplosionRadius() { - return combatData.explosionRadius; + return combatData.getExplosionRadius(); } @Override public void setExplosionRadius(float explosionRadius) { - combatData.explosionRadius = explosionRadius; + combatData.setExplosionRadius(explosionRadius); } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 522890d47..49efb5ad1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -213,7 +213,9 @@ public class AbilityLaserShot extends AbilityEnergyProjectile implem defs.add(FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming)); defs.add(FieldDef.section("ability.section.explosive")); defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive")); - defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive)); + defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius) + .range(0.0f, EnergyCombatData.MAX_EXPLOSION_RADIUS) + .visibleWhen(this::isExplosive)); defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java index ca9cbc7eb..c38131e9f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java @@ -180,7 +180,9 @@ public class AbilitySlicer extends AbilityEnergyProjectile { )); defs.add(FieldDef.section("ability.section.explosive")); defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive")); - defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive)); + defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius) + .range(0.0f, EnergyCombatData.MAX_EXPLOSION_RADIUS) + .visibleWhen(this::isExplosive)); defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java index f1309bb97..6c5fcc76c 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java @@ -24,6 +24,55 @@ import java.util.List; */ public abstract class EntityAbilityBarrier extends EntityEnergyAbility { + /** + * Outcome of a projectile vs barrier interaction. + */ + public enum ProjectileHitResult { + PASS, + BLOCKED, + BROKEN; + + public boolean isAbsorbed() { + return this == BLOCKED || this == BROKEN; + } + } + + /** + * Full result of a projectile vs barrier hit after multiplier/event processing. + */ + public static class ProjectileHitOutcome { + public final ProjectileHitResult result; + public final float barrierHealthBefore; + public final float barrierHealthAfter; + public final float appliedBarrierDamage; + public final float remainingProjectileDamage; + public final boolean reflectEnabled; + public final float reflectStrengthPct; + public final boolean useHealth; + + public ProjectileHitOutcome(ProjectileHitResult result, + float barrierHealthBefore, + float barrierHealthAfter, + float appliedBarrierDamage, + float remainingProjectileDamage, + boolean reflectEnabled, + float reflectStrengthPct, + boolean useHealth) { + this.result = result; + this.barrierHealthBefore = barrierHealthBefore; + this.barrierHealthAfter = barrierHealthAfter; + this.appliedBarrierDamage = appliedBarrierDamage; + this.remainingProjectileDamage = remainingProjectileDamage; + this.reflectEnabled = reflectEnabled; + this.reflectStrengthPct = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct)); + this.useHealth = useHealth; + } + + public boolean shouldReflect() { + return reflectEnabled && useHealth && result == ProjectileHitResult.BLOCKED; + } + } + // ==================== ACTIVE BARRIER REGISTRY ==================== private static final List> activeBarriers = new ArrayList<>(); private boolean tracked = false; @@ -164,6 +213,20 @@ public abstract class EntityAbilityBarrier extends EntityEnergyAbility { * Returns true if the barrier absorbed the hit (projectile should be destroyed). */ public boolean onProjectileHit(EntityEnergyProjectile projectile, float baseDamage) { + return onProjectileHitDetailed(projectile, baseDamage).isAbsorbed(); + } + + /** + * Apply damage to this barrier from a projectile and return detailed outcome. + */ + public ProjectileHitResult onProjectileHitDetailed(EntityEnergyProjectile projectile, float baseDamage) { + return onProjectileHitResolved(projectile, baseDamage).result; + } + + /** + * Apply damage to this barrier from a projectile and return full hit context. + */ + public ProjectileHitOutcome onProjectileHitResolved(EntityEnergyProjectile projectile, float baseDamage) { // Get damage multiplier for this projectile type String typeId = ""; if (projectile.getSourceAbility() != null) { @@ -175,25 +238,67 @@ public abstract class EntityAbilityBarrier extends EntityEnergyAbility { // Fire hit event (may cancel or modify damage) if (!worldObj.isRemote) { float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage); - if (eventDamage < 0) return false; // Event cancelled — don't block + if (eventDamage < 0) { + return new ProjectileHitOutcome( + ProjectileHitResult.PASS, + currentHealth, + currentHealth, + 0.0f, + 0.0f, + barrierData.reflect, + barrierData.reflectStrengthPct, + barrierData.useHealth + ); + } damage = eventDamage; } triggerHitFlash(); + float healthBefore = currentHealth; + if (!barrierData.useHealth) { - return true; // Duration-only mode: block but don't take damage + return new ProjectileHitOutcome( + ProjectileHitResult.BLOCKED, + healthBefore, + healthBefore, + 0.0f, + 0.0f, + false, + barrierData.reflectStrengthPct, + false + ); } currentHealth -= damage; syncHealthPercent(); if (currentHealth <= 0) { + float remaining = Math.max(0.0f, damage - healthBefore); onBarrierDestroyed(); this.setDead(); + return new ProjectileHitOutcome( + ProjectileHitResult.BROKEN, + healthBefore, + 0.0f, + damage, + remaining, + barrierData.reflect, + barrierData.reflectStrengthPct, + true + ); } - return true; + return new ProjectileHitOutcome( + ProjectileHitResult.BLOCKED, + healthBefore, + currentHealth, + damage, + 0.0f, + barrierData.reflect, + barrierData.reflectStrengthPct, + true + ); } // ==================== MELEE DAMAGE ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 979ee4b55..1822e0c99 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -515,22 +515,22 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } private void checkEntityCollision(double headX, double headY, double headZ) { - double hitSize = headSize * 0.5; + // Swept head hitbox prevents misses when beam head moves quickly between ticks. + double prevHeadWorldX = startX + prevHeadOffsetX; + double prevHeadWorldY = startY + prevHeadOffsetY; + double prevHeadWorldZ = startZ + prevHeadOffsetZ; + double hitSize = Math.max(0.05, headSize * 0.5); AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( - headX - hitSize, headY - hitSize, headZ - hitSize, - headX + hitSize, headY + hitSize, headZ + hitSize + Math.min(prevHeadWorldX, headX) - hitSize, + Math.min(prevHeadWorldY, headY) - hitSize, + Math.min(prevHeadWorldZ, headZ) - hitSize, + Math.max(prevHeadWorldX, headX) + hitSize, + Math.max(prevHeadWorldY, headY) + hitSize, + Math.max(prevHeadWorldZ, headZ) + hitSize ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (processEntityHit(entity, headX, headY, headZ)) { - if (DEBUG_LOGGING) { - LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted); - } - return; - } + if (processEntitiesInHitBox(hitBox, headX, headY, headZ) && DEBUG_LOGGING) { + LogWriter.info("[Beam] DEAD: Entity collision at tick " + ticksExisted); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index e8b8acc58..503d64f3d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -265,15 +265,43 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } private void checkEntityCollision() { - // Disc hitbox: wider but thinner - double halfWidth = discRadius * 0.5; - double halfHeight = discThickness * 0.5; + // Swept disc hitbox to avoid miss-through at high speed. + double nextX = posX + motionX; + double nextY = posY + motionY; + double nextZ = posZ + motionZ; + + double halfRadius = Math.max(0.05, discRadius * 0.5); + double halfThickness = Math.max(0.03, discThickness * 0.5); + double halfX; + double halfY; + double halfZ; + + if (vertical) { + // Vertical disc: thin axis follows horizontal travel direction, tall on Y. + double horizLen = Math.sqrt(motionX * motionX + motionZ * motionZ); + if (horizLen > 1.0e-5) { + double nx = motionX / horizLen; + double nz = motionZ / horizLen; + halfX = Math.abs(nx) * halfThickness + Math.abs(nz) * halfRadius; + halfZ = Math.abs(nz) * halfThickness + Math.abs(nx) * halfRadius; + } else { + halfX = halfRadius; + halfZ = halfRadius; + } + halfY = halfRadius; + } else { + // Horizontal disc: wide on XZ, thin on Y. + halfX = halfRadius; + halfY = halfThickness; + halfZ = halfRadius; + } + AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( - posX - halfWidth, posY - halfHeight, posZ - halfWidth, - posX + halfWidth, posY + halfHeight, posZ + halfWidth + Math.min(posX, nextX) - halfX, Math.min(posY, nextY) - halfY, Math.min(posZ, nextZ) - halfZ, + Math.max(posX, nextX) + halfX, Math.max(posY, nextY) + halfY, Math.max(posZ, nextZ) + halfZ ); - processEntitiesInHitBox(hitBox, posX, posY, posZ); + processEntitiesInHitBox(hitBox, nextX, nextY, nextZ); } /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 1ff3ece70..a632b16ee 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -41,6 +41,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { // Lock vertical direction after firing (only update yaw, keep pitch fixed) private boolean lockVerticalDirection = false; + private int reflectedLockTicks = 0; // End point for rendering private double endX, endY, endZ; @@ -120,9 +121,14 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { return; } - // Track owner's anchor position and rotation each tick - // so the laser follows the NPC's facing direction - updateLaserOriginAndDirection(); + // After reflection, hold trajectory briefly so the rebound is visible. + if (reflectedLockTicks > 0) { + reflectedLockTicks--; + } else { + // Track owner's anchor position and rotation each tick + // so the laser follows the NPC's facing direction + updateLaserOriginAndDirection(); + } if (!fullyExtended) { // Expand the laser @@ -404,18 +410,23 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { EntityAbilityDome dome = (EntityAbilityDome) barrier; float intersectDist = getLineSphereIntersection(dome); if (intersectDist >= 0) { - // Truncate laser at dome surface - currentLength = intersectDist; - endX = startX + dirX * currentLength; - endY = startY + dirY * currentLength; - endZ = startZ + dirZ * currentLength; - fullyExtended = true; - float damage = getModifiedDamage(); - dome.onProjectileHit(this, damage); - // Block this tick so entity collision doesn't continue through the dome. - // Laser remains alive and lingers at truncated length. - return true; + EntityAbilityBarrier.ProjectileHitOutcome hitOutcome = dome.onProjectileHitResolved(this, damage); + if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) { + continue; + } + + if (hitOutcome.result != EntityAbilityBarrier.ProjectileHitResult.BROKEN) { + currentLength = Math.max(0.0f, intersectDist); + endX = startX + dirX * currentLength; + endY = startY + dirY * currentLength; + endZ = startZ + dirZ * currentLength; + } + + if (handleBarrierHitOutcome(dome, hitOutcome)) { + return true; + } + return false; } } else { // For line-based lasers, use a swept segment against generic barrier checks. @@ -433,17 +444,113 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { if (incoming) { float damage = getModifiedDamage(); - if (barrier.onProjectileHit(this, damage)) { - hasHit = true; - this.setDead(); + EntityAbilityBarrier.ProjectileHitOutcome hitOutcome = barrier.onProjectileHitResolved(this, damage); + if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) { + continue; + } + + if (hitOutcome.result != EntityAbilityBarrier.ProjectileHitResult.BROKEN) { + // Keep current segment length at the exact collision tick position. + endX = startX + dirX * currentLength; + endY = startY + dirY * currentLength; + endZ = startZ + dirZ * currentLength; + } + + if (handleBarrierHitOutcome(barrier, hitOutcome)) { return true; } + return false; } } } return false; } + @Override + protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) { + if (barrier == null) { + return false; + } + + double vx = dirX; + double vy = dirY; + double vz = dirZ; + double vLenSq = vx * vx + vy * vy + vz * vz; + if (vLenSq < 1.0e-8) { + return false; + } + + double[] normal = getBarrierImpactNormal(barrier, vx, vy, vz); + if (normal == null) { + return false; + } + + double dot = vx * normal[0] + vy * normal[1] + vz * normal[2]; + if (dot > 0.0) { + normal[0] = -normal[0]; + normal[1] = -normal[1]; + normal[2] = -normal[2]; + dot = vx * normal[0] + vy * normal[1] + vz * normal[2]; + } + + double rx = vx - 2.0 * dot * normal[0]; + double ry = vy - 2.0 * dot * normal[1]; + double rz = vz - 2.0 * dot * normal[2]; + if (ry < -0.06) { + ry = Math.max(0.08, Math.abs(ry) * 0.35); + } + double rLen = Math.sqrt(rx * rx + ry * ry + rz * rz); + if (rLen < 1.0e-8) { + rx = -vx; + ry = -vy; + rz = -vz; + rLen = Math.sqrt(rx * rx + ry * ry + rz * rz); + if (rLen < 1.0e-8) { + return false; + } + } + + dirX = rx / rLen; + dirY = ry / rLen; + dirZ = rz / rLen; + + // Start a fresh segment at the impact edge to make the rebound visible. + startX = endX; + startY = endY; + startZ = endZ; + setPosition(startX, startY, startZ); + syncPositionStateToCurrent(true); + + currentLength = 0.0f; + fullyExtended = false; + ticksSinceFullExtension = 0; + endX = startX; + endY = startY; + endZ = startZ; + + reflectedLockTicks = Math.max(reflectedLockTicks, 10); + barrierImpactPauseTicks = 0; + barrierImpactDestroyOnResume = false; + + Entity barrierOwner = barrier.getOwnerEntity(); + if (barrierOwner != null) { + setOwnerEntityId(barrierOwner.getEntityId()); + trackProjectile(this); + } + setTargetEntityId(-1); + + setInnerColor(barrier.getInnerColor()); + setOuterColor(barrier.getOuterColor()); + + float clampedStrength = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct)); + float reducedDamage = getDamage() * (1.0f - clampedStrength / 100.0f); + setCombatDamage(Math.max(0.0f, reducedDamage)); + + hitOnceEntities.clear(); + lastHitTickByEntity.clear(); + return true; + } + /** * Line-sphere intersection between the laser line segment and a dome. * Returns the distance along the laser direction to the first entry point, diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index aa50745ea..aab79a444 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -126,6 +126,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected boolean hasHit = false; protected final Set hitOnceEntities = new HashSet(); protected final Map lastHitTickByEntity = new HashMap(); + protected static final int BARRIER_IMPACT_PAUSE_TICKS = 10; + protected static final int BARRIER_BREAK_SPARK_TICKS = 10; + protected static final int DW_BARRIER_SPARK_TICKS = 21; + protected static final int DW_SYNC_INNER_COLOR = 22; + protected static final int DW_SYNC_OUTER_COLOR = 23; + protected int barrierImpactPauseTicks = 0; + protected boolean barrierImpactDestroyOnResume = false; + protected double pausedMotionX, pausedMotionY, pausedMotionZ; // ==================== CHARGING STATE (projectile-specific) ==================== protected float targetSize = 1.0f; @@ -150,6 +158,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.noClip = false; } + @Override + protected void entityInit() { + super.entityInit(); // DW_CHARGING = 20 + this.dataWatcher.addObject(DW_BARRIER_SPARK_TICKS, 0); + // displayData is not guaranteed to be initialized yet during Entity construction. + this.dataWatcher.addObject(DW_SYNC_INNER_COLOR, 0xFFFFFF); + this.dataWatcher.addObject(DW_SYNC_OUTER_COLOR, 0xFFFFFF); + } + /** * Initialize common properties using data classes. Call from subclass constructors. */ @@ -174,6 +191,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.lifespanData = lifespan; // deathWorldTime will be set on first tick when world is available this.lightningData = lightning; this.trajectoryData = trajectory; + syncProjectileColorWatchers(); // Initialize render size this.renderCurrentSize = size; @@ -209,12 +227,21 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } } + if (!previewMode) { + syncDisplayColorsFromWatchers(); + } + // Update rotation updateRotation(); // Lerp render size toward actual size this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f; + // Server-synced visual timer used for brief barrier impact lightning. + if (!previewMode && !worldObj.isRemote) { + tickBarrierSparkTimer(); + } + // Skip lifetime/distance checks in preview mode if (!previewMode) { // Only self-destruct if the owner is confirmed dead/killed. @@ -276,10 +303,16 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } } + if (!previewMode && !worldObj.isRemote && !isCharging()) { + if (tickBarrierImpactPause()) { + return; + } + } + // Check barrier collisions (server-side, non-preview, non-charging) if (!previewMode && !worldObj.isRemote && !isCharging()) { if (checkBarrierCollision()) { - return; // Projectile was absorbed by a barrier + return; // Barrier interaction handled (pause + destroy/continue) } } @@ -330,7 +363,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * Check for collision with energy barrier entities. * Barriers only block incoming projectiles from enemies. * - * @return true if projectile was absorbed (caller should stop processing) + * @return true if barrier interaction was handled (caller should stop processing this tick) */ protected boolean checkBarrierCollision() { List barriers = EntityAbilityBarrier.getActiveBarriers(worldObj); @@ -347,17 +380,355 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (barrier.isIncomingProjectile(this)) { float damage = getModifiedDamage(); - if (barrier.onProjectileHit(this, damage)) { - hasHit = true; - this.setDead(); + EntityAbilityBarrier.ProjectileHitOutcome hitOutcome = barrier.onProjectileHitResolved(this, damage); + if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) { + continue; + } + if (handleBarrierHitOutcome(barrier, hitOutcome)) { return true; } + return false; } } return false; } + /** + * Handle the result of a projectile/barrier interaction. + * + * @return true if caller should stop processing this tick. + */ + protected boolean handleBarrierHitOutcome(EntityAbilityBarrier barrier, EntityAbilityBarrier.ProjectileHitOutcome hitOutcome) { + if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) { + return false; + } + + if (hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.BROKEN) { + if (hitOutcome.remainingProjectileDamage <= 0.0f) { + setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS)); + hasHit = true; + return true; + } + + setCombatDamage(hitOutcome.remainingProjectileDamage); + setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS)); + return false; + } + + if (hitOutcome.shouldReflect()) { + if (!reflectFromBarrier(barrier, hitOutcome.reflectStrengthPct)) { + beginBarrierImpactPause(true, barrier); + } + return true; + } + + beginBarrierImpactPause(true, barrier); + return true; + } + + protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) { + if (barrier == null) { + return false; + } + + double vx = motionX; + double vy = motionY; + double vz = motionZ; + double vLenSq = vx * vx + vy * vy + vz * vz; + if (vLenSq < 1.0e-8) { + return false; + } + + double[] normal = getBarrierImpactNormal(barrier, vx, vy, vz); + if (normal == null) { + return false; + } + + double dot = vx * normal[0] + vy * normal[1] + vz * normal[2]; + if (dot > 0.0) { + normal[0] = -normal[0]; + normal[1] = -normal[1]; + normal[2] = -normal[2]; + dot = vx * normal[0] + vy * normal[1] + vz * normal[2]; + } + + double rx = vx - 2.0 * dot * normal[0]; + double ry = vy - 2.0 * dot * normal[1]; + double rz = vz - 2.0 * dot * normal[2]; + double rLenSq = rx * rx + ry * ry + rz * rz; + if (rLenSq < 1.0e-8) { + rx = -vx; + ry = -vy; + rz = -vz; + rLenSq = rx * rx + ry * ry + rz * rz; + } + + // Prefer rebounding out and away from ground instead of immediately diving downward. + if (ry < -0.06) { + double speed = Math.sqrt(Math.max(rLenSq, 1.0e-8)); + ry = Math.max(0.08, Math.abs(ry) * 0.35); + double newLen = Math.sqrt(rx * rx + ry * ry + rz * rz); + if (newLen > 1.0e-8) { + double scale = speed / newLen; + rx *= scale; + ry *= scale; + rz *= scale; + } + } + + // Push slightly outside the collision boundary so the reflected motion + // starts cleanly from the barrier surface instead of re-colliding in place. + snapOutsideBarrierForPause(barrier); + + motionX = rx; + motionY = ry; + motionZ = rz; + pausedMotionX = rx; + pausedMotionY = ry; + pausedMotionZ = rz; + barrierImpactPauseTicks = 0; + barrierImpactDestroyOnResume = false; + + Entity barrierOwner = barrier.getOwnerEntity(); + if (barrierOwner != null) { + setOwnerEntityId(barrierOwner.getEntityId()); + // Ensure ownership-sensitive systems can query this projectile under the new owner. + trackProjectile(this); + } + // Old homing target may now be the new owner or ally; clear it after reflection. + setTargetEntityId(-1); + + setInnerColor(barrier.getInnerColor()); + setOuterColor(barrier.getOuterColor()); + + float clampedStrength = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct)); + float reducedDamage = getDamage() * (1.0f - clampedStrength / 100.0f); + setCombatDamage(Math.max(0.0f, reducedDamage)); + + hitOnceEntities.clear(); + lastHitTickByEntity.clear(); + return true; + } + + protected double[] getBarrierImpactNormal(EntityAbilityBarrier barrier, double velocityX, double velocityY, double velocityZ) { + if (barrier instanceof EntityAbilityDome) { + EntityAbilityDome dome = (EntityAbilityDome) barrier; + return normalizeVector(posX - dome.posX, posY - dome.posY, posZ - dome.posZ, velocityX, velocityY, velocityZ); + } + + if (barrier instanceof EntityAbilityPanel) { + EntityAbilityPanel panel = (EntityAbilityPanel) barrier; + float yawRad = (float) Math.toRadians(panel.getPanelYaw()); + double nx = -Math.sin(yawRad); + double nz = Math.cos(yawRad); + double relX = posX - panel.posX; + double relZ = posZ - panel.posZ; + double signedDist = relX * nx + relZ * nz; + double side = signedDist; + if (Math.abs(side) < 1.0e-5) { + side = velocityX * nx + velocityZ * nz; + } + if (side < 0.0) { + nx = -nx; + nz = -nz; + } + return normalizeVector(nx, 0.0, nz, velocityX, velocityY, velocityZ); + } + + return normalizeVector(posX - barrier.posX, posY - barrier.posY, posZ - barrier.posZ, velocityX, velocityY, velocityZ); + } + + protected double[] normalizeVector(double x, double y, double z, double fallbackX, double fallbackY, double fallbackZ) { + double lenSq = x * x + y * y + z * z; + if (lenSq > 1.0e-8) { + double invLen = 1.0 / Math.sqrt(lenSq); + return new double[]{x * invLen, y * invLen, z * invLen}; + } + + double fallbackLenSq = fallbackX * fallbackX + fallbackY * fallbackY + fallbackZ * fallbackZ; + if (fallbackLenSq > 1.0e-8) { + double invFallbackLen = 1.0 / Math.sqrt(fallbackLenSq); + return new double[]{-fallbackX * invFallbackLen, -fallbackY * invFallbackLen, -fallbackZ * invFallbackLen}; + } + + return new double[]{0.0, 1.0, 0.0}; + } + + protected void beginBarrierImpactPause(boolean destroyOnResume, EntityAbilityBarrier barrier) { + if (barrierImpactPauseTicks <= 0) { + pausedMotionX = motionX; + pausedMotionY = motionY; + pausedMotionZ = motionZ; + } + + barrierImpactDestroyOnResume = destroyOnResume; + barrierImpactPauseTicks = BARRIER_IMPACT_PAUSE_TICKS; + motionX = 0; + motionY = 0; + motionZ = 0; + + int sparkTicks = getBarrierSparkTicks(); + if (sparkTicks < BARRIER_IMPACT_PAUSE_TICKS) { + setBarrierSparkTicks(BARRIER_IMPACT_PAUSE_TICKS); + } + } + + protected float getBarrierPauseOutsideDistance() { + // Keep the paused projectile just outside the surface without a visible "fling". + return Math.max(0.06f, Math.min(0.22f, size * 0.18f + 0.03f)); + } + + protected void snapOutsideBarrierForPause(EntityAbilityBarrier barrier) { + if (barrier == null) return; + + float bias = getBarrierPauseOutsideDistance(); + if (barrier instanceof EntityAbilityDome) { + EntityAbilityDome dome = (EntityAbilityDome) barrier; + double nx = posX - dome.posX; + double ny = posY - dome.posY; + double nz = posZ - dome.posZ; + double len = Math.sqrt(nx * nx + ny * ny + nz * nz); + + if (len < 1.0e-5) { + double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ); + if (vLen > 1.0e-5) { + nx = -motionX / vLen; + ny = -motionY / vLen; + nz = -motionZ / vLen; + len = 1.0; + } else { + nx = 0.0; + ny = 1.0; + nz = 0.0; + len = 1.0; + } + } + + double invLen = 1.0 / len; + double target = dome.getDomeRadius() + bias; + setPosition( + dome.posX + nx * invLen * target, + dome.posY + ny * invLen * target, + dome.posZ + nz * invLen * target + ); + return; + } + + if (barrier instanceof EntityAbilityPanel) { + EntityAbilityPanel panel = (EntityAbilityPanel) barrier; + float yawRad = (float) Math.toRadians(panel.getPanelYaw()); + double normalX = -Math.sin(yawRad); + double normalZ = Math.cos(yawRad); + double relX = posX - panel.posX; + double relZ = posZ - panel.posZ; + double signedDist = relX * normalX + relZ * normalZ; + double halfThickness = 0.25; + double side; + if (Math.abs(signedDist) > 0.035) { + side = signedDist >= 0 ? 1.0 : -1.0; + } else { + // Near the plane, use incoming direction so we don't snap through and "fling". + double vDot = motionX * normalX + motionZ * normalZ; + if (Math.abs(vDot) > 1.0e-5) { + side = vDot < 0 ? 1.0 : -1.0; + } else { + side = signedDist >= 0 ? 1.0 : -1.0; + } + } + double targetDist = side * (halfThickness + bias); + double delta = targetDist - signedDist; + setPosition(posX + normalX * delta, posY, posZ + normalZ * delta); + return; + } + + double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ); + if (vLen > 1.0e-5) { + setPosition( + posX - (motionX / vLen) * bias, + posY - (motionY / vLen) * bias, + posZ - (motionZ / vLen) * bias + ); + } + } + + protected boolean tickBarrierImpactPause() { + if (barrierImpactPauseTicks <= 0) { + return false; + } + + motionX = 0; + motionY = 0; + motionZ = 0; + barrierImpactPauseTicks--; + + if (barrierImpactPauseTicks <= 0) { + if (barrierImpactDestroyOnResume) { + hasHit = true; + this.setDead(); + } else { + motionX = pausedMotionX; + motionY = pausedMotionY; + motionZ = pausedMotionZ; + } + barrierImpactDestroyOnResume = false; + } + + return true; + } + + protected void tickBarrierSparkTimer() { + int sparkTicks = getBarrierSparkTicks(); + if (sparkTicks > 0) { + setBarrierSparkTicks(sparkTicks - 1); + } + } + + protected void setBarrierSparkTicks(int ticks) { + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_BARRIER_SPARK_TICKS, Math.max(0, ticks)); + } + } + + public int getBarrierSparkTicks() { + if (this.dataWatcher == null) { + return 0; + } + return Math.max(0, this.dataWatcher.getWatchableObjectInt(DW_BARRIER_SPARK_TICKS)); + } + + protected void syncProjectileColorWatchers() { + if (this.dataWatcher == null) { + return; + } + this.dataWatcher.updateObject(DW_SYNC_INNER_COLOR, this.displayData.getInnerColor()); + this.dataWatcher.updateObject(DW_SYNC_OUTER_COLOR, this.displayData.getOuterColor()); + } + + protected void syncDisplayColorsFromWatchers() { + if (this.dataWatcher == null) { + return; + } + this.displayData.setInnerColor(this.dataWatcher.getWatchableObjectInt(DW_SYNC_INNER_COLOR)); + this.displayData.setOuterColor(this.dataWatcher.getWatchableObjectInt(DW_SYNC_OUTER_COLOR)); + } + + @Override + public void setInnerColor(int color) { + super.setInnerColor(color); + if (!previewMode) { + syncProjectileColorWatchers(); + } + } + + @Override + public void setOuterColor(int color) { + super.setOuterColor(color); + if (!previewMode) { + syncProjectileColorWatchers(); + } + } + /** * Get the projectile damage modified by extenders (e.g. DBC damage scaling). * Falls back to base getDamage() when no sourceAbility or no extenders modify it. @@ -605,7 +976,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { */ protected void spawnExplosionRenderEntity(float explosionRad) { if (worldObj == null || worldObj.isRemote) return; - float renderRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + float renderRad = Math.max(0.75f, Math.min(explosionRad, EnergyCombatData.MAX_EXPLOSION_RADIUS)); EntityEnergyExplosion fx = new EntityEnergyExplosion(worldObj, this, renderRad); EnergyExplosionSpawnPacket.sendToTracking(getExplosionVisualInstanceId(), fx, this); } @@ -620,7 +991,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void spawnExplosionVisuals(float explosionRad) { if (worldObj == null) return; - float visualRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + float visualRad = Math.max(0.75f, Math.min(explosionRad, EnergyCombatData.MAX_EXPLOSION_RADIUS)); int coreBursts = Math.max(1, Math.min(4, Math.round(visualRad * 0.35f))); int shellCount = Math.max(1, Math.min(3, Math.round(visualRad / 3.5f) + 1)); @@ -771,7 +1142,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (!ConfigEnergy.EnableEnergyExplosionBlockDamage) return; if (!worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")) return; - float terrainRad = Math.max(0.5f, Math.min(explosionRad, 12.0f)); + float terrainRad = Math.max(0.5f, Math.min(explosionRad, EnergyCombatData.MAX_EXPLOSION_RADIUS)); int minX = MathHelper.floor_double(posX - terrainRad); int maxX = MathHelper.floor_double(posX + terrainRad); int minY = MathHelper.floor_double(posY - terrainRad); @@ -1459,7 +1830,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } public float getExplosionRadius() { - return combatData.explosionRadius; + return combatData.getExplosionRadius(); } public void setExplosionRadius(float radius) { @@ -1686,6 +2057,11 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { return hasHit; } + @Override + public boolean hasLightningEffect() { + return super.hasLightningEffect() || getBarrierSparkTicks() > 0; + } + // ==================== NBT ==================== @Override @@ -1766,6 +2142,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { homingData.readNBT(nbt); lifespanData.readNBT(nbt); trajectoryData.readNBT(nbt); + syncProjectileColorWatchers(); } /** diff --git a/src/main/java/noppes/npcs/client/ClientEventHandler.java b/src/main/java/noppes/npcs/client/ClientEventHandler.java index b5e37503a..1e8d7a56d 100644 --- a/src/main/java/noppes/npcs/client/ClientEventHandler.java +++ b/src/main/java/noppes/npcs/client/ClientEventHandler.java @@ -104,9 +104,13 @@ public class ClientEventHandler { int hudKeyCode = ClientProxy.AbilityHudKey.getKeyCode(); boolean hudKeyHeld = false; if (hudKeyCode > 0) { - hudKeyHeld = org.lwjgl.input.Keyboard.isKeyDown(hudKeyCode); + hudKeyHeld = hudKeyCode < org.lwjgl.input.Keyboard.getKeyCount() + && org.lwjgl.input.Keyboard.isKeyDown(hudKeyCode); } else if (hudKeyCode < 0) { - hudKeyHeld = org.lwjgl.input.Mouse.isButtonDown(hudKeyCode + 100); + int mouseButton = hudKeyCode + 100; + hudKeyHeld = mouseButton >= 0 + && mouseButton < org.lwjgl.input.Mouse.getButtonCount() + && org.lwjgl.input.Mouse.isButtonDown(mouseButton); } if (hudKeyHeld) { AbilityHotbarComponent comp = (AbilityHotbarComponent) ClientHudManager.getInstance() diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index 90ca61ea6..fda6547c5 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -604,7 +604,20 @@ public class AbilityHotbarComponent extends HudComponent { private boolean isHudKeyHeld() { if (ClientProxy.AbilityHudKey == null) return false; int keyCode = ClientProxy.AbilityHudKey.getKeyCode(); - return keyCode != 0 && org.lwjgl.input.Keyboard.isKeyDown(keyCode); + if (keyCode == 0 || keyCode == -1) return false; + + // Mouse bindings are encoded as negative key codes (offset by 100). + if (keyCode < -1) { + int mouseButton = keyCode + 100; + return mouseButton >= 0 + && mouseButton < org.lwjgl.input.Mouse.getButtonCount() + && org.lwjgl.input.Mouse.isButtonDown(mouseButton); + } + + // Guard against invalid/out-of-range keyboard codes to avoid Buffer.checkIndex crashes. + return keyCode > 0 + && keyCode < org.lwjgl.input.Keyboard.getKeyCount() + && org.lwjgl.input.Keyboard.isKeyDown(keyCode); } public boolean hasAnyAbilities() { diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java index a015a06ad..fe02608e1 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java @@ -121,6 +121,22 @@ public class ScriptEnergyBarrier extends ScriptE entity.getBarrierData().absorbing = absorbing; } + public boolean isReflect() { + return entity.getBarrierData().reflect; + } + + public void setReflect(boolean reflect) { + entity.getBarrierData().reflect = reflect; + } + + public float getReflectStrengthPct() { + return entity.getBarrierData().reflectStrengthPct; + } + + public void setReflectStrengthPct(float strengthPct) { + entity.getBarrierData().setReflectStrengthPct(strengthPct); + } + public int getBarrierType() { return entity instanceof EntityAbilityDome ? 0 : 1; } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 61553093e..35d0ac83d 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2053,6 +2053,8 @@ ability.solid=Solid ability.knockbackEnabled=Knockback ability.knockbackStrength=Knockback Strength ability.absorbing=Absorbing +ability.reflect=Reflect +ability.reflectStrength=Strength (%) ability.meleeEnabled=Allows Melee ability.meleeDamageMultiplier=Melee Multiplier @@ -2061,6 +2063,7 @@ ability.hover.followCaster=Dome stays centered on the caster as they move ability.hover.solid=Entities cannot pass through the barrier boundary ability.hover.knockbackEnabled=Repulsion field pushing entities away from barrier surface ability.hover.absorbing=Damage dealt to the caster is redirected to the barrier's health +ability.hover.reflect=Rebounds non-breaking projectiles and reduces their damage by Strength ability.hover.meleeEnabled=Barrier can be hit and damaged by melee attacks From 20dc8e0efc8c6a694615007f2f3d9f0745156de0 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 04:11:07 -0500 Subject: [PATCH 61/71] Fix Flying Gravity Syncing Issue --- .../noppes/npcs/entity/EntityNPCFlying.java | 20 +++++++++---------- .../npcs/entity/EntityNPCInterface.java | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java index 8c062fdda..c41e0e843 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java @@ -76,13 +76,13 @@ public abstract class EntityNPCFlying extends EntityNPCInterface { this.flyLimitAllow = true; setNpcFlyingState(true); - double d3 = this.motionY; - super.moveEntityWithHeading(p_70612_1_, p_70612_2_); - this.motionY = d3; - + double desiredMotionY = this.motionY; if (this.getNavigator().noPath()) { - this.motionY = -Math.abs(this.ais.flyGravity); + desiredMotionY = -Math.abs(this.ais.flyGravity); } + this.motionY = desiredMotionY; + super.moveEntityWithHeading(p_70612_1_, p_70612_2_); + this.motionY = desiredMotionY; this.updateLimbSwing(); } @@ -140,13 +140,13 @@ public abstract class EntityNPCFlying extends EntityNPCInterface { return; } - double d3 = this.motionY; - super.performMountedMovement(strafe, forward, moveSpeed); - this.motionY = d3; - + double desiredMotionY = this.motionY; if (this.getNavigator().noPath()) { - this.motionY = -Math.abs(this.ais.flyGravity); + desiredMotionY = -Math.abs(this.ais.flyGravity); } + this.motionY = desiredMotionY; + super.performMountedMovement(strafe, forward, moveSpeed); + this.motionY = desiredMotionY; this.updateLimbSwing(); } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index fb1c1c6f8..7a978eb89 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -1806,6 +1806,11 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti compound.setBoolean("DeadBody", stats.hideKilledBody); compound.setInteger("StandingState", ais.standingType.ordinal()); compound.setInteger("MovingState", ais.movingType.ordinal()); + compound.setInteger("MovingType", ais.movementType); + compound.setDouble("FlySpeed", ais.flySpeed); + compound.setDouble("FlyGravity", ais.flyGravity); + compound.setBoolean("HasFlyLimit", ais.hasFlyLimit); + compound.setInteger("FlyHeightLimit", ais.flyHeightLimit); compound.setInteger("Orientation", ais.orientation); compound.setFloat("OffsetY", ais.bodyOffsetY); compound.setInteger("Role", advanced.role.ordinal()); @@ -1854,6 +1859,21 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti stats.hideKilledBody = compound.getBoolean("DeadBody"); ais.standingType = EnumStandingType.values()[compound.getInteger("StandingState") % EnumStandingType.values().length]; ais.movingType = EnumMovingType.values()[compound.getInteger("MovingState") % EnumMovingType.values().length]; + if (compound.hasKey("MovingType", Constants.NBT.TAG_INT)) { + ais.movementType = compound.getInteger("MovingType"); + } + if (compound.hasKey("FlySpeed", Constants.NBT.TAG_DOUBLE)) { + ais.flySpeed = compound.getDouble("FlySpeed"); + } + if (compound.hasKey("FlyGravity", Constants.NBT.TAG_DOUBLE)) { + ais.flyGravity = compound.getDouble("FlyGravity"); + } + if (compound.hasKey("HasFlyLimit", Constants.NBT.TAG_BYTE)) { + ais.hasFlyLimit = compound.getBoolean("HasFlyLimit"); + } + if (compound.hasKey("FlyHeightLimit", Constants.NBT.TAG_INT)) { + ais.flyHeightLimit = compound.getInteger("FlyHeightLimit"); + } ais.orientation = compound.getInteger("Orientation"); ais.bodyOffsetY = compound.getFloat("OffsetY"); From 960b22315552fe8fd88a9df6b976f72842156ac4 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 04:30:24 -0500 Subject: [PATCH 62/71] Better Diagonal Flight Pathing @c0508383 --- .../npcs/ai/pathfinder/FlyPathFinder.java | 37 +++++++++++++++++-- .../npcs/ai/pathfinder/FlyingMoveHelper.java | 24 +++++++----- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java b/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java index ebe00c28e..cc5758ce6 100644 --- a/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java +++ b/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java @@ -25,6 +25,9 @@ import java.util.Map; import java.util.Set; public class FlyPathFinder extends PathFinder { + private static final float STRAIGHT_COST = 1.0F; + private static final float DIAGONAL_2D_COST = 1.4142135F; + private static final float DIAGONAL_3D_COST = 1.7320508F; /** * Used to find obstacles */ @@ -141,7 +144,7 @@ public class FlyPathFinder extends PathFinder { private NPCPath addToPath(Entity p_75861_1_, NPCPathPoint p_75861_2_, NPCPathPoint p_75861_3_, NPCPathPoint p_75861_4_, float p_75861_5_) { p_75861_2_.totalPathDistance = 0.0F; - p_75861_2_.distanceToNext = p_75861_2_.distanceToSquared(p_75861_3_); + p_75861_2_.distanceToNext = this.estimateCostToTarget(p_75861_2_, p_75861_3_); p_75861_2_.distanceToTarget = p_75861_2_.distanceToNext; this.path.clearPath(); this.path.addPoint(p_75861_2_); @@ -161,7 +164,7 @@ public class FlyPathFinder extends PathFinder { return this.createEntityPath(p_75861_2_, p_75861_3_); } - if (pathpoint4.distanceToSquared(p_75861_3_) < pathpoint3.distanceToSquared(p_75861_3_)) { + if (this.estimateCostToTarget(pathpoint4, p_75861_3_) < this.estimateCostToTarget(pathpoint3, p_75861_3_)) { pathpoint3 = pathpoint4; } @@ -170,12 +173,12 @@ public class FlyPathFinder extends PathFinder { for (int j = 0; j < i; ++j) { NPCPathPoint pathpoint5 = this.pathOptions[j]; - float f1 = pathpoint4.totalPathDistance + pathpoint4.distanceToSquared(pathpoint5); + float f1 = pathpoint4.totalPathDistance + this.getTraversalCost(pathpoint4, pathpoint5); if (!pathpoint5.isAssigned() || f1 < pathpoint5.totalPathDistance) { pathpoint5.previous = pathpoint4; pathpoint5.totalPathDistance = f1; - pathpoint5.distanceToNext = pathpoint5.distanceToSquared(p_75861_3_); + pathpoint5.distanceToNext = this.estimateCostToTarget(pathpoint5, p_75861_3_); if (pathpoint5.isAssigned()) { this.path.changeDistance(pathpoint5, pathpoint5.totalPathDistance + pathpoint5.distanceToNext); @@ -194,6 +197,32 @@ public class FlyPathFinder extends PathFinder { } } + private float getTraversalCost(NPCPathPoint from, NPCPathPoint to) { + int dx = Math.abs(to.xCoord - from.xCoord); + int dy = Math.abs(to.yCoord - from.yCoord); + int dz = Math.abs(to.zCoord - from.zCoord); + int changedAxes = dx + dy + dz; + if (changedAxes <= 1) { + return STRAIGHT_COST; + } + if (changedAxes == 2) { + return DIAGONAL_2D_COST; + } + return DIAGONAL_3D_COST; + } + + private float estimateCostToTarget(NPCPathPoint from, NPCPathPoint to) { + float dx = Math.abs(to.xCoord - from.xCoord); + float dy = Math.abs(to.yCoord - from.yCoord); + float dz = Math.abs(to.zCoord - from.zCoord); + + float max = Math.max(dx, Math.max(dy, dz)); + float min = Math.min(dx, Math.min(dy, dz)); + float mid = dx + dy + dz - max - min; + + return DIAGONAL_3D_COST * min + DIAGONAL_2D_COST * (mid - min) + STRAIGHT_COST * (max - mid); + } + /** * populates pathOptions with available points and returns the number of options found (args: unused1, currentPoint, * unused2, targetPoint, maxDistance) diff --git a/src/main/java/noppes/npcs/ai/pathfinder/FlyingMoveHelper.java b/src/main/java/noppes/npcs/ai/pathfinder/FlyingMoveHelper.java index dd59a22de..18129de11 100644 --- a/src/main/java/noppes/npcs/ai/pathfinder/FlyingMoveHelper.java +++ b/src/main/java/noppes/npcs/ai/pathfinder/FlyingMoveHelper.java @@ -7,6 +7,8 @@ import noppes.npcs.entity.EntityNPCFlying; import noppes.npcs.entity.EntityNPCInterface; public class FlyingMoveHelper extends EntityMoveHelper { + private static final double CLOSE_ENOUGH_SQ = 0.01D; + private static final float MAX_TURN_PER_TICK = 35.0F; private final EntityNPCInterface entity; private double posX; @@ -38,22 +40,24 @@ public class FlyingMoveHelper extends EntityMoveHelper { double d5 = MathHelper.sqrt_double(d4); speed = Math.min(d5 / 5.0D, speed); - // its here louis! - // This is the hurt time that stuns the NPC!!!!!! - // if (this.entity.hurtTime == 0 && d4 > 0.5D) { - if (d4 >= 0.5D) { - this.entity.motionX += (speed * (d0 / d5) - this.entity.motionX) * speed; - this.entity.motionZ += (speed * (d2 / d5) - this.entity.motionZ) * speed; + if (d4 > CLOSE_ENOUGH_SQ && d5 > 1.0E-4D) { + double targetMotionX = speed * (d0 / d5); + double targetMotionZ = speed * (d2 / d5); + double horizontalLerp = Math.min(1.0D, Math.max(0.12D, speed * 0.6D)); + this.entity.motionX += (targetMotionX - this.entity.motionX) * horizontalLerp; + this.entity.motionZ += (targetMotionZ - this.entity.motionZ) * horizontalLerp; if (((EntityNPCFlying) this.entity).flyLimitAllow || !this.entity.ais.hasFlyLimit) { - this.entity.motionY = verticalSpeed * (d1 / d5); - if (this.entity.motionY > 0) { - this.entity.motionY += 0.1D; + double targetMotionY = verticalSpeed * (d1 / d5); + if (targetMotionY > 0.0D) { + targetMotionY += 0.05D; } + double verticalLerp = Math.min(1.0D, Math.max(0.15D, verticalSpeed * 0.85D)); + this.entity.motionY += (targetMotionY - this.entity.motionY) * verticalLerp; } this.entity.velocityChanged = true; - this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), 20.0F); + this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), MAX_TURN_PER_TICK); } } } From a79ba3a0e479e810f259d70869a54407b6250b7a Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 04:38:02 -0500 Subject: [PATCH 63/71] Smoother + Most-Cost Effective Flight Pathing --- .../npcs/ai/pathfinder/FlyPathFinder.java | 21 +++- .../ai/pathfinder/PathNavigateFlying.java | 101 ++++++++++++++++-- .../noppes/npcs/entity/EntityNPCFlying.java | 6 +- 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java b/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java index cc5758ce6..af80c4a28 100644 --- a/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java +++ b/src/main/java/noppes/npcs/ai/pathfinder/FlyPathFinder.java @@ -173,7 +173,8 @@ public class FlyPathFinder extends PathFinder { for (int j = 0; j < i; ++j) { NPCPathPoint pathpoint5 = this.pathOptions[j]; - float f1 = pathpoint4.totalPathDistance + this.getTraversalCost(pathpoint4, pathpoint5); + float f1 = pathpoint4.totalPathDistance + this.getTraversalCost(pathpoint4, pathpoint5) + + this.getDirectionalPenalty(pathpoint4, pathpoint5, p_75861_3_); if (!pathpoint5.isAssigned() || f1 < pathpoint5.totalPathDistance) { pathpoint5.previous = pathpoint4; @@ -211,6 +212,24 @@ public class FlyPathFinder extends PathFinder { return DIAGONAL_3D_COST; } + private float getDirectionalPenalty(NPCPathPoint from, NPCPathPoint to, NPCPathPoint goal) { + int stepX = to.xCoord - from.xCoord; + int stepY = to.yCoord - from.yCoord; + int stepZ = to.zCoord - from.zCoord; + int goalX = goal.xCoord - from.xCoord; + int goalY = goal.yCoord - from.yCoord; + int goalZ = goal.zCoord - from.zCoord; + int stepLen = Math.abs(stepX) + Math.abs(stepY) + Math.abs(stepZ); + int goalLen = Math.abs(goalX) + Math.abs(goalY) + Math.abs(goalZ); + + if (stepLen == 0 || goalLen == 0) { + return 0.0F; + } + + float alignment = (float) (stepX * goalX + stepY * goalY + stepZ * goalZ) / (float) (stepLen * goalLen); + return (1.0F - alignment) * 0.04F; + } + private float estimateCostToTarget(NPCPathPoint from, NPCPathPoint to) { float dx = Math.abs(to.xCoord - from.xCoord); float dy = Math.abs(to.yCoord - from.yCoord); diff --git a/src/main/java/noppes/npcs/ai/pathfinder/PathNavigateFlying.java b/src/main/java/noppes/npcs/ai/pathfinder/PathNavigateFlying.java index df2e4c868..90856dc79 100644 --- a/src/main/java/noppes/npcs/ai/pathfinder/PathNavigateFlying.java +++ b/src/main/java/noppes/npcs/ai/pathfinder/PathNavigateFlying.java @@ -21,6 +21,10 @@ import noppes.npcs.entity.EntityNPCInterface; import javax.annotation.Nullable; public class PathNavigateFlying extends PathNavigate { + private static final long MIN_REPATH_INTERVAL_TICKS = 8L; + private static final long MAX_REPATH_INTERVAL_TICKS = 30L; + private static final int PATH_REUSE_TARGET_DELTA_SQ = 2; + private static final int FINAL_NODE_REUSE_DELTA_SQ = 3; private EntityNPCInterface theEntity; private World worldObj; @@ -83,16 +87,21 @@ public class PathNavigateFlying extends PathNavigate { } public void updatePath() { - if (this.worldObj.getTotalWorldTime() - this.lastTimeUpdated > 20L) { - if (this.targetPos != null) { - this.currentPath = null; - this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ()); - this.lastTimeUpdated = this.worldObj.getTotalWorldTime(); - this.tryUpdatePath = false; - } - } else { - this.tryUpdatePath = true; + if (this.targetPos == null) { + this.tryUpdatePath = false; + return; } + + long now = this.worldObj.getTotalWorldTime(); + if (now - this.lastTimeUpdated < this.getAdaptiveRepathInterval()) { + this.tryUpdatePath = true; + return; + } + + this.currentPath = null; + this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ()); + this.lastTimeUpdated = now; + this.tryUpdatePath = false; } public void setAvoidsWater(boolean p_75491_1_) { @@ -156,7 +165,8 @@ public class PathNavigateFlying extends PathNavigate { BlockPos pos = new BlockPos(x, y, z); if (!this.canNavigate()) { return null; - } else if (this.currentPath != null && !this.currentPath.isFinished() && pos.equals(this.targetPos)) { + } else if (this.canReuseCurrentPath(pos)) { + this.targetPos = pos; return this.currentPath; } else { this.targetPos = pos; @@ -180,7 +190,8 @@ public class PathNavigateFlying extends PathNavigate { return null; } else { BlockPos blockpos = new BlockPos(entity); - if (this.currentPath != null && !this.currentPath.isFinished() && blockpos.equals(this.targetPos)) { + if (this.canReuseCurrentPath(blockpos)) { + this.targetPos = blockpos; return this.currentPath; } else { this.targetPos = blockpos; @@ -215,6 +226,8 @@ public class PathNavigateFlying extends PathNavigate { this.removeSunnyPath(); } + this.smoothCurrentPath(); + if (this.currentPath.getCurrentPathLength() == 0) { return false; } else { @@ -242,6 +255,72 @@ public class PathNavigateFlying extends PathNavigate { return !this.isInLiquid() || (this.canSwim && this.isInLiquid()); } + private long getAdaptiveRepathInterval() { + if (this.targetPos == null) { + return MAX_REPATH_INTERVAL_TICKS; + } + double distance = this.theEntity.getDistance(this.targetPos.getX() + 0.5D, this.targetPos.getY(), this.targetPos.getZ() + 0.5D); + return (long) MathHelper.clamp_double(distance * 0.6D, MIN_REPATH_INTERVAL_TICKS, MAX_REPATH_INTERVAL_TICKS); + } + + private boolean canReuseCurrentPath(BlockPos newTarget) { + if (this.currentPath == null || this.currentPath.isFinished() || this.targetPos == null) { + return false; + } + + if (this.getBlockDistanceSq(this.targetPos, newTarget) <= PATH_REUSE_TARGET_DELTA_SQ) { + return true; + } + + NPCPathPoint finalPoint = this.currentPath.getFinalPathPoint(); + return finalPoint != null && this.getPointDistanceSq(finalPoint, newTarget) <= FINAL_NODE_REUSE_DELTA_SQ; + } + + private int getBlockDistanceSq(BlockPos a, BlockPos b) { + int dx = a.getX() - b.getX(); + int dy = a.getY() - b.getY(); + int dz = a.getZ() - b.getZ(); + return dx * dx + dy * dy + dz * dz; + } + + private int getPointDistanceSq(NPCPathPoint point, BlockPos blockPos) { + int dx = point.xCoord - blockPos.getX(); + int dy = point.yCoord - blockPos.getY(); + int dz = point.zCoord - blockPos.getZ(); + return dx * dx + dy * dy + dz * dz; + } + + private void smoothCurrentPath() { + if (this.currentPath == null) { + return; + } + int pathLength = this.currentPath.getCurrentPathLength(); + if (pathLength < 3) { + return; + } + + int width = MathHelper.ceiling_double_int(this.theEntity.width); + int height = MathHelper.ceiling_double_int(this.theEntity.height + 1.0F); + NPCPathPoint[] points = this.currentPath.points; + int writeIndex = 1; + int anchorIndex = 0; + + for (int readIndex = 2; readIndex < pathLength; readIndex++) { + Vec3 anchor = this.currentPath.getVectorFromIndex(this.theEntity, anchorIndex); + Vec3 probe = this.currentPath.getVectorFromIndex(this.theEntity, readIndex); + if (!this.isDirectPathBetweenPoints(anchor, probe, width, height)) { + points[writeIndex++] = points[readIndex - 1]; + anchorIndex = readIndex - 1; + } + } + + points[writeIndex++] = points[pathLength - 1]; + this.currentPath.setCurrentPathLength(writeIndex); + if (this.currentPath.getCurrentPathIndex() >= writeIndex) { + this.currentPath.setCurrentPathIndex(Math.max(0, writeIndex - 1)); + } + } + public void onUpdateNavigation() { ++this.totalTicks; diff --git a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java index c41e0e843..6e5923e7d 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCFlying.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCFlying.java @@ -91,11 +91,13 @@ public abstract class EntityNPCFlying extends EntityNPCInterface { this.prevLimbSwingAmount = this.limbSwingAmount; double distanceX = this.posX - this.prevPosX; double distanceZ = this.posZ - this.prevPosZ; - float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ) * 4.0F; + float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ); + float swingScale = this.getNavigator().noPath() ? 0.35F : 0.9F; + distance *= swingScale; if (distance > 1.0F) { distance = 1.0F; } - this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.4F; + this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.18F; this.limbSwing += this.limbSwingAmount; } From 27e9f9886281df13e18247341838b98a00b4a529 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 05:03:01 -0500 Subject: [PATCH 64/71] Natural Spawn Patch 1 --- src/main/java/noppes/npcs/NPCSpawning.java | 57 ++++++++++++++++--- .../npcs/controllers/SpawnController.java | 7 ++- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/main/java/noppes/npcs/NPCSpawning.java b/src/main/java/noppes/npcs/NPCSpawning.java index ef143024c..61f9d6ccb 100644 --- a/src/main/java/noppes/npcs/NPCSpawning.java +++ b/src/main/java/noppes/npcs/NPCSpawning.java @@ -35,6 +35,8 @@ import java.util.Set; public class NPCSpawning { private static Set eligibleChunksForSpawning = Sets.newHashSet(); + private static final byte CHUNK_SPAWN_RADIUS = 7; + private static final int SPAWN_ATTEMPTS_PER_CHUNK = 3; private static boolean animalSpawn; private static boolean monsterSpawn; @@ -49,7 +51,7 @@ public class NPCSpawning { EntityPlayer entityplayer = (EntityPlayer) world.playerEntities.get(i); int j = MathHelper.floor_double(entityplayer.posX / 16.0D); int k = MathHelper.floor_double(entityplayer.posZ / 16.0D); - byte size = 7; + byte size = CHUNK_SPAWN_RADIUS; for (int x = -size; x <= size; ++x) { for (int z = -size; z <= size; ++z) { @@ -60,7 +62,9 @@ public class NPCSpawning { } } } - if (countNPCs(world) > eligibleChunksForSpawning.size()) { + int cap = eligibleChunksForSpawning.size(); + int npcCount = countNPCs(world); + if (npcCount >= cap) { return; } ArrayList tmp = new ArrayList(eligibleChunksForSpawning); @@ -68,6 +72,9 @@ public class NPCSpawning { Iterator iterator = tmp.iterator(); while (iterator.hasNext()) { + if (npcCount >= cap) { + return; + } ChunkCoordIntPair chunkcoordintpair1 = iterator.next(); ChunkPosition chunkposition = getChunk(world, chunkcoordintpair1.chunkXPos, chunkcoordintpair1.chunkZPos); @@ -75,7 +82,10 @@ public class NPCSpawning { int y = chunkposition.chunkPosY; int l1 = chunkposition.chunkPosZ; - for (int i = 0; i < 3; i++) { + for (int i = 0; i < SPAWN_ATTEMPTS_PER_CHUNK; i++) { + if (npcCount >= cap) { + return; + } int x = j1; int z = l1; byte b1 = 6; @@ -85,10 +95,12 @@ public class NPCSpawning { String name = world.getBiomeGenForCoords(x, z).biomeName; SpawnData data = SpawnController.Instance.getRandomSpawnData(name, world.provider.dimensionId); - if (data == null || !canCreatureTypeSpawnAtLocation(data, world, x, y, z) || world.getClosestPlayer(x, y, z, 24.0D) != null) + if (data == null || y < data.spawnHeightMin || y > data.spawnHeightMax || !canCreatureTypeSpawnAtLocation(data, world, x, y, z) || world.getClosestPlayer(x, y, z, 24.0D) != null) continue; - spawnData(data, world, x, y, z); + if (spawnData(data, world, x, y, z)) { + npcCount++; + } } } } @@ -112,9 +124,34 @@ public class NPCSpawning { return new ChunkPosition(k, i1, l); } + private static int getCap(World world) { + Set chunkSet = Sets.newHashSet(); + for (int i = 0; i < world.playerEntities.size(); ++i) { + EntityPlayer entityplayer = (EntityPlayer) world.playerEntities.get(i); + int j = MathHelper.floor_double(entityplayer.posX / 16.0D); + int k = MathHelper.floor_double(entityplayer.posZ / 16.0D); + byte size = CHUNK_SPAWN_RADIUS; + + for (int x = -size; x <= size; ++x) { + for (int z = -size; z <= size; ++z) { + chunkSet.add(new ChunkCoordIntPair(x + j, z + k)); + } + } + } + return chunkSet.size(); + } + public static void performWorldGenSpawning(World world, int x, int z, Random rand) { + int cap = getCap(world); + int npcCount = countNPCs(world); + if (npcCount >= cap) { + return; + } BiomeGenBase biome = world.getBiomeGenForCoords(x + 8, z + 8); while (rand.nextFloat() < biome.getSpawningChance()) { + if (npcCount >= cap) { + return; + } SpawnData data = SpawnController.Instance.getRandomSpawnData(biome.biomeName, world.provider.dimensionId); if (data == null) continue; @@ -127,6 +164,9 @@ public class NPCSpawning { int i2 = k1; for (int k2 = 0; k2 < 4; ++k2) { + if (npcCount >= cap) { + return; + } int l2 = world.getTopSolidOrLiquidBlock(j1, k1); if (l2 > data.spawnHeightMax || l2 < data.spawnHeightMin) { continue; @@ -140,8 +180,10 @@ public class NPCSpawning { for (k1 += rand.nextInt(5) - rand.nextInt(5); j1 < x || j1 >= x + size || k1 < z || k1 >= z + size; k1 = i2 + rand.nextInt(5) - rand.nextInt(5)) { j1 = l1 + rand.nextInt(5) - rand.nextInt(5); } - } else if (spawnData(data, world, j1, l2, k1)) + } else if (spawnData(data, world, j1, l2, k1)) { + npcCount++; break; + } } } @@ -184,6 +226,8 @@ public class NPCSpawning { if (spawnEntity instanceof EntityCustomNpc) { EntityCustomNpc npc = (EntityCustomNpc) spawnEntity; npc.stats.spawnCycle = 3; + npc.stats.canDespawn = true; + npc.stats.playerSetCanDespawn = true; npc.ais.returnToStart = false; npc.ais.startPos = new int[]{x, y, z}; npc.updateAI = true; @@ -239,4 +283,3 @@ public class NPCSpawning { return animalSpawn || monsterSpawn || caveSpawn || liquidSpawn; } } - diff --git a/src/main/java/noppes/npcs/controllers/SpawnController.java b/src/main/java/noppes/npcs/controllers/SpawnController.java index 7a04d788d..ce9d784e4 100644 --- a/src/main/java/noppes/npcs/controllers/SpawnController.java +++ b/src/main/java/noppes/npcs/controllers/SpawnController.java @@ -223,11 +223,14 @@ public class SpawnController implements INaturalSpawnsHandler { } public INaturalSpawn[] getSpawns(String biome) { - return getSpawnList(biome).toArray(new INaturalSpawn[]{}); + List biomeSpawns = getSpawnList(biome); + if (biomeSpawns == null) + return new INaturalSpawn[]{}; + return biomeSpawns.toArray(new INaturalSpawn[]{}); } public void addSpawn(INaturalSpawn spawn) { - ((SpawnData) spawn).id = 0; + ((SpawnData) spawn).id = -1; this.saveSpawnData((SpawnData) spawn); } From 5c42308ca338a47b078652718d12e344049e53e4 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 05:21:51 -0500 Subject: [PATCH 65/71] Natural Spawns PR2 Improvements to GUI Functionality --- src/api | 2 +- src/main/java/noppes/npcs/NPCSpawning.java | 123 +++++++++++++++++- .../client/gui/SubGuiSpawningOptions.java | 112 +++++++++++++--- .../gui/global/GuiNpcNaturalSpawns.java | 15 ++- .../npcs/client/gui/util/GuiNpcButton.java | 22 +++- .../npcs/client/gui/util/GuiNpcTextField.java | 31 ++++- .../npcs/client/gui/util/GuiScrollWindow.java | 7 +- .../npcs/controllers/data/SpawnData.java | 69 ++++++++++ .../assets/customnpcs/lang/en_US.lang | 30 +++++ 9 files changed, 378 insertions(+), 33 deletions(-) diff --git a/src/api b/src/api index 2438b2110..db7173c4e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 2438b2110e74bfd49125cf74254d98c8b18e2331 +Subproject commit db7173c4ec6b01ffb4ab23fad130164c5815f7dc diff --git a/src/main/java/noppes/npcs/NPCSpawning.java b/src/main/java/noppes/npcs/NPCSpawning.java index 61f9d6ccb..9e9699915 100644 --- a/src/main/java/noppes/npcs/NPCSpawning.java +++ b/src/main/java/noppes/npcs/NPCSpawning.java @@ -28,8 +28,10 @@ import noppes.npcs.scripted.event.CustomNPCsEvent; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Set; @@ -37,6 +39,10 @@ public class NPCSpawning { private static Set eligibleChunksForSpawning = Sets.newHashSet(); private static final byte CHUNK_SPAWN_RADIUS = 7; private static final int SPAWN_ATTEMPTS_PER_CHUNK = 3; + private static final String NATURAL_SPAWN_ID_TAG = "CNPCNaturalSpawnId"; + private static final String NATURAL_SPAWN_NAME_TAG = "CNPCNaturalSpawnName"; + private static final String NATURAL_SPAWN_TIME_TAG = "CNPCNaturalSpawnTime"; + private static final Map SPAWN_COOLDOWNS = new HashMap(); private static boolean animalSpawn; private static boolean monsterSpawn; @@ -67,6 +73,7 @@ public class NPCSpawning { if (npcCount >= cap) { return; } + HashMap spawnEntryCounts = getNaturalSpawnEntryCounts(world); ArrayList tmp = new ArrayList(eligibleChunksForSpawning); Collections.shuffle(tmp); Iterator iterator = tmp.iterator(); @@ -95,10 +102,10 @@ public class NPCSpawning { String name = world.getBiomeGenForCoords(x, z).biomeName; SpawnData data = SpawnController.Instance.getRandomSpawnData(name, world.provider.dimensionId); - if (data == null || y < data.spawnHeightMin || y > data.spawnHeightMax || !canCreatureTypeSpawnAtLocation(data, world, x, y, z) || world.getClosestPlayer(x, y, z, 24.0D) != null) + if (data == null) continue; - if (spawnData(data, world, x, y, z)) { + if (trySpawnWithEntryConfig(data, world, x, y, z, world.rand, spawnEntryCounts)) { npcCount++; } } @@ -141,12 +148,108 @@ public class NPCSpawning { return chunkSet.size(); } + private static HashMap getNaturalSpawnEntryCounts(World world) { + HashMap counts = new HashMap(); + List list = world.loadedEntityList; + for (Entity entity : list) { + if (entity == null || entity.isDead) { + continue; + } + NBTTagCompound entityData = entity.getEntityData(); + if (!entityData.hasKey(NATURAL_SPAWN_ID_TAG)) { + continue; + } + int spawnId = entityData.getInteger(NATURAL_SPAWN_ID_TAG); + Integer count = counts.get(spawnId); + counts.put(spawnId, count == null ? 1 : count + 1); + } + return counts; + } + + private static long getSpawnCooldownKey(World world, int spawnId) { + return ((long) world.provider.dimensionId << 32) ^ (spawnId & 0xffffffffL); + } + + private static boolean isSpawnOnCooldown(World world, SpawnData data) { + if (data.cooldownTicks <= 0 || data.id < 0) { + return false; + } + long key = getSpawnCooldownKey(world, data.id); + Long lastSpawnTick = SPAWN_COOLDOWNS.get(key); + if (lastSpawnTick == null) { + return false; + } + long tickDelta = world.getWorldInfo().getWorldTotalTime() - lastSpawnTick; + return tickDelta < data.cooldownTicks; + } + + private static void markSpawnCooldown(World world, SpawnData data) { + if (data.cooldownTicks <= 0 || data.id < 0) { + return; + } + long key = getSpawnCooldownKey(world, data.id); + SPAWN_COOLDOWNS.put(key, world.getWorldInfo().getWorldTotalTime()); + } + + private static boolean isSpawnEntryAtCapacity(SpawnData data, Map spawnEntryCounts) { + if (data.maxAlive <= 0 || data.id < 0) { + return false; + } + Integer currentCount = spawnEntryCounts.get(data.id); + return currentCount != null && currentCount >= data.maxAlive; + } + + private static void incrementSpawnEntryCount(SpawnData data, Map spawnEntryCounts) { + if (data.id < 0) { + return; + } + Integer currentCount = spawnEntryCounts.get(data.id); + spawnEntryCounts.put(data.id, currentCount == null ? 1 : currentCount + 1); + } + + private static boolean hasNearbyPlayer(SpawnData data, World world, int x, int y, int z) { + if (data.playerMinDistance <= 0) { + return false; + } + return world.getClosestPlayer(x, y, z, data.playerMinDistance) != null; + } + + private static boolean trySpawnWithEntryConfig(SpawnData data, World world, int x, int y, int z, Random rand, Map spawnEntryCounts) { + if (y < data.spawnHeightMin || y > data.spawnHeightMax) { + return false; + } + if (isSpawnEntryAtCapacity(data, spawnEntryCounts) || isSpawnOnCooldown(world, data)) { + return false; + } + + int attempts = Math.max(1, data.attemptsPerCycle); + for (int attempt = 0; attempt < attempts; attempt++) { + int attemptX = x; + int attemptZ = z; + if (attempt > 0) { + attemptX += rand.nextInt(5) - rand.nextInt(5); + attemptZ += rand.nextInt(5) - rand.nextInt(5); + } + + if (!canCreatureTypeSpawnAtLocation(data, world, attemptX, y, attemptZ) || hasNearbyPlayer(data, world, attemptX, y, attemptZ)) { + continue; + } + if (spawnData(data, world, attemptX, y, attemptZ)) { + incrementSpawnEntryCount(data, spawnEntryCounts); + markSpawnCooldown(world, data); + return true; + } + } + return false; + } + public static void performWorldGenSpawning(World world, int x, int z, Random rand) { int cap = getCap(world); int npcCount = countNPCs(world); if (npcCount >= cap) { return; } + HashMap spawnEntryCounts = getNaturalSpawnEntryCounts(world); BiomeGenBase biome = world.getBiomeGenForCoords(x + 8, z + 8); while (rand.nextFloat() < biome.getSpawningChance()) { if (npcCount >= cap) { @@ -174,13 +277,13 @@ public class NPCSpawning { l2 = l2 + (int) ((data.spawnHeightMax - l2) * Math.random()); } - if (!canCreatureTypeSpawnAtLocation(data, world, j1, l2, k1)) { + if (!trySpawnWithEntryConfig(data, world, j1, l2, k1, rand, spawnEntryCounts)) { j1 += rand.nextInt(5) - rand.nextInt(5); for (k1 += rand.nextInt(5) - rand.nextInt(5); j1 < x || j1 >= x + size || k1 < z || k1 >= z + size; k1 = i2 + rand.nextInt(5) - rand.nextInt(5)) { j1 = l1 + rand.nextInt(5) - rand.nextInt(5); } - } else if (spawnData(data, world, j1, l2, k1)) { + } else { npcCount++; break; } @@ -226,14 +329,22 @@ public class NPCSpawning { if (spawnEntity instanceof EntityCustomNpc) { EntityCustomNpc npc = (EntityCustomNpc) spawnEntity; npc.stats.spawnCycle = 3; - npc.stats.canDespawn = true; - npc.stats.playerSetCanDespawn = true; + if (data.despawnMode == SpawnData.DESPAWN_FORCE_PERSISTENT) { + npc.stats.canDespawn = false; + npc.stats.playerSetCanDespawn = false; + } else if (data.despawnMode == SpawnData.DESPAWN_FORCE_NATURAL) { + npc.stats.canDespawn = true; + npc.stats.playerSetCanDespawn = true; + } npc.ais.returnToStart = false; npc.ais.startPos = new int[]{x, y, z}; npc.updateAI = true; npc.updateClient = true; npc.getNavigator().clearPathEntity(); } + entityliving.getEntityData().setInteger(NATURAL_SPAWN_ID_TAG, data.id); + entityliving.getEntityData().setString(NATURAL_SPAWN_NAME_TAG, data.name); + entityliving.getEntityData().setLong(NATURAL_SPAWN_TIME_TAG, world.getWorldInfo().getWorldTotalTime()); spawnEntity.setLocationAndAngles(x + 0.5, y, z + 0.5, world.rand.nextFloat() * 360.0F, 0.0F); } catch (Exception exception) { exception.printStackTrace(); diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java index 29c33352b..caabe3d11 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java @@ -5,6 +5,7 @@ import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcButtonYesNo; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.GuiScrollWindow; import noppes.npcs.client.gui.util.ITextfieldListener; import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.controllers.data.SpawnData; @@ -22,30 +23,90 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield public void initGui() { super.initGui(); - int y = guiTop - 15; + int scrollX = guiLeft + 4; + int scrollY = guiTop + 4; + int scrollW = xSize - 8; + int scrollH = ySize - 34; - addButton(new GuiNpcButtonYesNo(10, guiLeft + 120, y += 22, data.animalSpawning)); - addLabel(new GuiNpcLabel(10, "spawning.animalSpawning", guiLeft + 4, y + 5)); + GuiScrollWindow sw = new GuiScrollWindow(this, scrollX, scrollY, scrollW, scrollH, 0); + sw.drawDefaultBackground = false; + addScrollableGui(0, sw); - addButton(new GuiNpcButtonYesNo(11, guiLeft + 120, y += 22, data.monsterSpawning)); - addLabel(new GuiNpcLabel(11, "spawning.monsterSpawning", guiLeft + 4, y + 5)); + int labelX = 4; + int controlX = 132; + int y = 6; - addButton(new GuiNpcButtonYesNo(12, guiLeft + 120, y += 22, data.liquidSpawning)); - addLabel(new GuiNpcLabel(12, "spawning.liquidSpawning", guiLeft + 4, y + 5)); + sw.addButton(new GuiNpcButtonYesNo(10, controlX, y, data.animalSpawning)); + sw.addLabel(new GuiNpcLabel(10, "spawning.animalSpawning", labelX, y + 5)); + sw.getButton(10).setHoverText("tooltip.naturalspawns.options.animal"); + y += 22; - addButton(new GuiNpcButtonYesNo(13, guiLeft + 120, y += 22, data.airSpawning)); - addLabel(new GuiNpcLabel(13, "spawning.airSpawning", guiLeft + 4, y + 5)); + sw.addButton(new GuiNpcButtonYesNo(11, controlX, y, data.monsterSpawning)); + sw.addLabel(new GuiNpcLabel(11, "spawning.monsterSpawning", labelX, y + 5)); + sw.getButton(11).setHoverText("tooltip.naturalspawns.options.monster"); + y += 22; - addTextField(new GuiNpcTextField(14, this, guiLeft + 120, y += 22, 40, 20, "" + data.spawnHeightMin)); - addLabel(new GuiNpcLabel(14, "spawning.minHeight", guiLeft + 4, y + 5)); - addTextField(new GuiNpcTextField(15, this, guiLeft + 120, y += 22, 40, 20, "" + data.spawnHeightMax)); - addLabel(new GuiNpcLabel(15, "spawning.maxHeight", guiLeft + 4, y + 5)); - getTextField(14).integersOnly = true; - getTextField(14).setMinMaxDefault(-Integer.MAX_VALUE, Integer.MAX_VALUE, 0); - getTextField(15).integersOnly = true; - getTextField(15).setMinMaxDefault(-Integer.MAX_VALUE, Integer.MAX_VALUE, 0); + sw.addButton(new GuiNpcButtonYesNo(12, controlX, y, data.liquidSpawning)); + sw.addLabel(new GuiNpcLabel(12, "spawning.liquidSpawning", labelX, y + 5)); + sw.getButton(12).setHoverText("tooltip.naturalspawns.options.liquid"); + y += 22; - addButton(new GuiNpcButton(100, guiLeft + 154, guiTop + 192, 60, 20, "gui.done")); + sw.addButton(new GuiNpcButtonYesNo(13, controlX, y, data.airSpawning)); + sw.addLabel(new GuiNpcLabel(13, "spawning.airSpawning", labelX, y + 5)); + sw.getButton(13).setHoverText("tooltip.naturalspawns.options.air"); + y += 22; + + sw.addTextField(new GuiNpcTextField(14, this, controlX, y, 60, 20, "" + data.spawnHeightMin)); + sw.addLabel(new GuiNpcLabel(14, "spawning.minHeight", labelX, y + 5)); + sw.getTextField(14).integersOnly = true; + sw.getTextField(14).setMinMaxDefault(-Integer.MAX_VALUE, Integer.MAX_VALUE, 0); + sw.getTextField(14).setHoverText("tooltip.naturalspawns.options.minHeight"); + y += 22; + + sw.addTextField(new GuiNpcTextField(15, this, controlX, y, 60, 20, "" + data.spawnHeightMax)); + sw.addLabel(new GuiNpcLabel(15, "spawning.maxHeight", labelX, y + 5)); + sw.getTextField(15).integersOnly = true; + sw.getTextField(15).setMinMaxDefault(-Integer.MAX_VALUE, Integer.MAX_VALUE, 0); + sw.getTextField(15).setHoverText("tooltip.naturalspawns.options.maxHeight"); + y += 22; + + sw.addTextField(new GuiNpcTextField(16, this, controlX, y, 60, 20, "" + data.maxAlive)); + sw.addLabel(new GuiNpcLabel(16, "spawning.maxAlive", labelX, y + 5)); + sw.getTextField(16).integersOnly = true; + sw.getTextField(16).setMinMaxDefault(0, Integer.MAX_VALUE, 0); + sw.getTextField(16).setHoverText("tooltip.naturalspawns.options.maxAlive"); + y += 22; + + sw.addTextField(new GuiNpcTextField(17, this, controlX, y, 60, 20, "" + data.cooldownTicks)); + sw.addLabel(new GuiNpcLabel(17, "spawning.cooldownTicks", labelX, y + 5)); + sw.getTextField(17).integersOnly = true; + sw.getTextField(17).setMinMaxDefault(0, Integer.MAX_VALUE, 0); + sw.getTextField(17).setHoverText("tooltip.naturalspawns.options.cooldownTicks"); + y += 22; + + sw.addTextField(new GuiNpcTextField(18, this, controlX, y, 60, 20, "" + data.attemptsPerCycle)); + sw.addLabel(new GuiNpcLabel(18, "spawning.attemptsPerCycle", labelX, y + 5)); + sw.getTextField(18).integersOnly = true; + sw.getTextField(18).setMinMaxDefault(1, Integer.MAX_VALUE, 1); + sw.getTextField(18).setHoverText("tooltip.naturalspawns.options.attemptsPerCycle"); + y += 22; + + sw.addTextField(new GuiNpcTextField(19, this, controlX, y, 60, 20, "" + data.playerMinDistance)); + sw.addLabel(new GuiNpcLabel(19, "spawning.playerMinDistance", labelX, y + 5)); + sw.getTextField(19).integersOnly = true; + sw.getTextField(19).setMinMaxDefault(0, Integer.MAX_VALUE, 24); + sw.getTextField(19).setHoverText("tooltip.naturalspawns.options.playerMinDistance"); + y += 22; + + sw.addButton(new GuiNpcButton(20, controlX, y, 74, 20, + new String[]{"spawning.despawn.forceNatural", "spawning.despawn.preserve", "spawning.despawn.forcePersistent"}, data.despawnMode)); + sw.addLabel(new GuiNpcLabel(20, "spawning.despawnMode", labelX, y + 5)); + sw.getButton(20).setHoverText("tooltip.naturalspawns.options.despawnMode"); + y += 22; + + sw.maxScrollY = Math.max(0, y - sw.clipHeight + 8); + + addButton(new GuiNpcButton(100, guiLeft + xSize - 64, guiTop + ySize - 24, 60, 20, "gui.done")); } @Override @@ -64,6 +125,9 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield if (button.id == 13) { data.airSpawning = button.getValue() == 1; } + if (button.id == 20) { + data.setDespawnMode(button.getValue()); + } if (button.id == 100) { close(); } @@ -77,5 +141,17 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield if (textfield.id == 15) { data.spawnHeightMax = textfield.getInteger(); } + if (textfield.id == 16) { + data.setMaxAlive(textfield.getInteger()); + } + if (textfield.id == 17) { + data.setCooldownTicks(textfield.getInteger()); + } + if (textfield.id == 18) { + data.setAttemptsPerCycle(textfield.getInteger()); + } + if (textfield.id == 19) { + data.setPlayerMinDistance(textfield.getInteger()); + } } } diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcNaturalSpawns.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcNaturalSpawns.java index ecdec2990..9a0fa727d 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcNaturalSpawns.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcNaturalSpawns.java @@ -62,6 +62,12 @@ public class GuiNpcNaturalSpawns extends GuiNPCInterface2 implements IGuiData, I this.addButton(new GuiNpcButton(1, guiLeft + 358, guiTop + 38, 58, 20, "gui.add")); this.addButton(new GuiNpcButton(2, guiLeft + 358, guiTop + 61, 58, 20, "gui.remove")); + getButton(1).setHoverText("tooltip.naturalspawns.main.addProfile"); + getButton(2).setHoverText("tooltip.naturalspawns.main.removeProfile"); + + if (getTextField(55) != null) { + getTextField(55).setHoverText("tooltip.naturalspawns.main.search"); + } if (this.spawn.id >= 0) showSpawn(); @@ -74,15 +80,18 @@ public class GuiNpcNaturalSpawns extends GuiNPCInterface2 implements IGuiData, I int y = guiTop + 30; addLabel(new GuiNpcLabel(3, "spawning.biomes", guiLeft + 4, y)); addButton(new GuiNpcButton(3, guiLeft + 40, y - 5, 50, 20, "selectServer.edit")); + getButton(3).setHoverText("tooltip.naturalspawns.main.biomes"); addLabel(new GuiNpcLabel(11, StatCollector.translateToLocal("Dimensions"), guiLeft + 95, y)); addButton(new GuiNpcButton(11, guiLeft + 150, y - 5, 50, 20, "selectServer.edit")); + getButton(11).setHoverText("tooltip.naturalspawns.main.dimensions"); addLabel(new GuiNpcLabel(4, "spawning.options", guiLeft + 4, y += 22)); addButton(new GuiNpcButton(4, guiLeft + 90, y - 5, 50, 20, "selectServer.edit")); + getButton(4).setHoverText("tooltip.naturalspawns.main.options"); addSlider(new GuiNpcSlider(this, 5, guiLeft + 4, y += 17, 180, 20, (float) this.spawn.itemWeight / 100)); - addExtra(new GuiHoverText(1, "spawning.naturalInfo", guiLeft + 188, y + 5)); + addExtra(new GuiHoverText(1, "spawning.naturalInfoPerformance", guiLeft + 188, y + 5)); this.spawnEntryScroll.guiLeft = guiLeft + 4; this.spawnEntryScroll.guiTop = y + 40; @@ -96,13 +105,17 @@ public class GuiNpcNaturalSpawns extends GuiNPCInterface2 implements IGuiData, I addScroll(this.spawnEntryScroll); addButton(new GuiNpcButton(21, guiLeft + 6, y += 20, 20, 20, "+")); + getButton(21).setHoverText("tooltip.naturalspawns.main.addEntry"); if (this.spawnEntryScroll.hasSelected()) { int selected = Integer.parseInt(this.spawnEntryScroll.getSelected()); addButton(new GuiNpcButton(22, guiLeft + 32, y, 20, 20, "-")); + getButton(22).setHoverText("tooltip.naturalspawns.main.removeEntry"); GuiNpcTextField num = new GuiNpcTextField(25, this, guiLeft + 60, y += 10, 30, 20, "" + selected); num.integersOnly = true; addTextField(num); + getTextField(25).setHoverText("tooltip.naturalspawns.main.slot"); addButton(new GuiNpcButton(26, guiLeft + 92, y, 100, 20, this.getTitle(this.spawn.spawnCompounds.get(selected)))); + getButton(26).setHoverText("tooltip.naturalspawns.main.selectEntry"); } } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java index 4ebafd54f..2c0d51a18 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java @@ -82,7 +82,8 @@ public class GuiNpcButton extends GuiButton { } public void setHoverText(String text) { - this.hoverableText = StatCollector.translateToLocal(text); + String translated = StatCollector.translateToLocal(text); + this.hoverableText = translated == null ? "" : translated.replace("\\n", "\n"); } public int getValue() { @@ -201,13 +202,30 @@ public class GuiNpcButton extends GuiButton { Minecraft mc = Minecraft.getMinecraft(); String displayString = StatCollector.translateToLocal(hoverableText); GL11.glColor4f(1, 1, 1, 1); - List lines = TextSplitter.splitText(displayString, 30); + List lines = splitHoverText(displayString, 30); drawHoveringText(lines, i, j, mc); GL11.glDisable(GL11.GL_LIGHTING); GL11.glPopMatrix(); } } + private List splitHoverText(String text, int maxLineLength) { + List lines = new ArrayList(); + if (text == null || text.isEmpty()) + return lines; + + String normalized = text.replace("\\n", "\n"); + String[] explicitLines = normalized.split("\\r?\\n", -1); + for (String line : explicitLines) { + if (line.isEmpty()) { + lines.add(""); + continue; + } + lines.addAll(TextSplitter.splitText(line, maxLineLength)); + } + return lines; + } + protected void drawHoveringText(List textLines, int x, int y, Minecraft mc) { if (mc.fontRenderer == null || textLines.isEmpty()) return; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java index 1d82deb61..71b5ed611 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java @@ -12,6 +12,7 @@ import net.minecraft.util.StatCollector; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; +import java.util.ArrayList; import java.util.List; public class GuiNpcTextField extends GuiTextField { @@ -304,7 +305,8 @@ public class GuiNpcTextField extends GuiTextField { } public void setHoverText(String text) { - this.hoverableText = StatCollector.translateToLocal(text); + String translated = StatCollector.translateToLocal(text); + this.hoverableText = translated == null ? "" : translated.replace("\\n", "\n"); } public boolean hasHoverText() { @@ -317,10 +319,14 @@ public class GuiNpcTextField extends GuiTextField { } public void drawHover(int mouseX, int mouseY, boolean hasSubGui) { + drawHover(mouseX, mouseY, mouseX, mouseY, hasSubGui); + } + + public void drawHover(int hitMouseX, int hitMouseY, int tooltipMouseX, int tooltipMouseY, boolean hasSubGui) { if (hasSubGui || !enabled || hoverableText.isEmpty()) return; - boolean isHovered = isMouseOver(mouseX, mouseY); + boolean isHovered = isMouseOver(hitMouseX, hitMouseY); if (!isHovered) { wasHovered = false; hoverCount = 0; @@ -338,13 +344,30 @@ public class GuiNpcTextField extends GuiTextField { GL11.glPushMatrix(); Minecraft mc = Minecraft.getMinecraft(); GL11.glColor4f(1, 1, 1, 1); - List lines = TextSplitter.splitText(hoverableText, 30); - drawHoveringText(lines, mouseX, mouseY, mc); + List lines = splitHoverText(hoverableText, 30); + drawHoveringText(lines, tooltipMouseX, tooltipMouseY, mc); GL11.glDisable(GL11.GL_LIGHTING); GL11.glPopMatrix(); } } + private List splitHoverText(String text, int maxLineLength) { + List lines = new ArrayList(); + if (text == null || text.isEmpty()) + return lines; + + String normalized = text.replace("\\n", "\n"); + String[] explicitLines = normalized.split("\\r?\\n", -1); + for (String line : explicitLines) { + if (line.isEmpty()) { + lines.add(""); + continue; + } + lines.addAll(TextSplitter.splitText(line, maxLineLength)); + } + return lines; + } + protected void drawHoveringText(List textLines, int x, int y, Minecraft mc) { if (mc.fontRenderer == null || textLines.isEmpty()) return; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java index ab6e612d4..0df1c8547 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java @@ -300,9 +300,14 @@ public class GuiScrollWindow extends GuiScreen implements ITextfieldListener, IC button.drawHover(mouseX, mouseY, subGui); } } - // Label hover: convert screen mouse to scroll-local coords for hit testing int localMouseX = mouseX - xPos; int localMouseY = (int) (mouseY - yPos + scrollY); + for (GuiNpcTextField textField : textfields.values()) { + if (textField.hasHoverText()) { + textField.drawHover(localMouseX, localMouseY, mouseX, mouseY, subGui); + } + } + // Label hover: convert screen mouse to scroll-local coords for hit testing for (GuiNpcLabel label : labels.values()) { if (!label.hoverableText.isEmpty()) { label.drawHover(localMouseX, localMouseY, mouseX, mouseY, subGui, fontRendererObj); diff --git a/src/main/java/noppes/npcs/controllers/data/SpawnData.java b/src/main/java/noppes/npcs/controllers/data/SpawnData.java index f82d9b225..164f451da 100644 --- a/src/main/java/noppes/npcs/controllers/data/SpawnData.java +++ b/src/main/java/noppes/npcs/controllers/data/SpawnData.java @@ -21,6 +21,10 @@ import java.util.Map; import java.util.Set; public class SpawnData extends WeightedRandom.Item implements INaturalSpawn { + public static final int DESPAWN_FORCE_NATURAL = INaturalSpawn.DESPAWN_FORCE_NATURAL; + public static final int DESPAWN_PRESERVE_TEMPLATE = INaturalSpawn.DESPAWN_PRESERVE_TEMPLATE; + public static final int DESPAWN_FORCE_PERSISTENT = INaturalSpawn.DESPAWN_FORCE_PERSISTENT; + public List biomes = new ArrayList<>(); public HashSet dimensions = new HashSet<>(); public int id = -1; @@ -35,6 +39,11 @@ public class SpawnData extends WeightedRandom.Item implements INaturalSpawn { public int spawnHeightMin; public int spawnHeightMax = 100; + public int maxAlive = 0; + public int cooldownTicks = 0; + public int attemptsPerCycle = 1; + public int playerMinDistance = 24; + public int despawnMode = DESPAWN_FORCE_NATURAL; public SpawnData() { super(10); @@ -74,6 +83,19 @@ public class SpawnData extends WeightedRandom.Item implements INaturalSpawn { } else { spawnHeightMax = compound.getInteger("HeightMax"); } + + maxAlive = compound.hasKey("MaxAlive") ? Math.max(0, compound.getInteger("MaxAlive")) : 0; + cooldownTicks = compound.hasKey("CooldownTicks") ? Math.max(0, compound.getInteger("CooldownTicks")) : 0; + attemptsPerCycle = compound.hasKey("AttemptsPerCycle") ? Math.max(1, compound.getInteger("AttemptsPerCycle")) : 1; + playerMinDistance = compound.hasKey("PlayerMinDistance") ? Math.max(0, compound.getInteger("PlayerMinDistance")) : 24; + if (compound.hasKey("DespawnMode")) { + despawnMode = compound.getInteger("DespawnMode"); + } else { + despawnMode = DESPAWN_FORCE_NATURAL; + } + if (despawnMode < DESPAWN_FORCE_NATURAL || despawnMode > DESPAWN_FORCE_PERSISTENT) { + despawnMode = DESPAWN_FORCE_NATURAL; + } } public NBTTagCompound writeNBT(NBTTagCompound compound) { @@ -98,6 +120,11 @@ public class SpawnData extends WeightedRandom.Item implements INaturalSpawn { compound.setInteger("HeightMin", spawnHeightMin); compound.setInteger("HeightMax", spawnHeightMax); + compound.setInteger("MaxAlive", maxAlive); + compound.setInteger("CooldownTicks", cooldownTicks); + compound.setInteger("AttemptsPerCycle", attemptsPerCycle); + compound.setInteger("PlayerMinDistance", playerMinDistance); + compound.setInteger("DespawnMode", despawnMode); return compound; } @@ -204,4 +231,46 @@ public class SpawnData extends WeightedRandom.Item implements INaturalSpawn { public void setBiomes(String[] biomes) { this.biomes = new ArrayList<>(Arrays.asList(biomes)); } + + public void setMaxAlive(int maxAlive) { + this.maxAlive = Math.max(0, maxAlive); + } + + public int getMaxAlive() { + return this.maxAlive; + } + + public void setCooldownTicks(int ticks) { + this.cooldownTicks = Math.max(0, ticks); + } + + public int getCooldownTicks() { + return this.cooldownTicks; + } + + public void setAttemptsPerCycle(int attempts) { + this.attemptsPerCycle = Math.max(1, attempts); + } + + public int getAttemptsPerCycle() { + return this.attemptsPerCycle; + } + + public void setPlayerMinDistance(int distance) { + this.playerMinDistance = Math.max(0, distance); + } + + public int getPlayerMinDistance() { + return this.playerMinDistance; + } + + public void setDespawnMode(int mode) { + if (mode < DESPAWN_FORCE_NATURAL || mode > DESPAWN_FORCE_PERSISTENT) + mode = DESPAWN_FORCE_NATURAL; + this.despawnMode = mode; + } + + public int getDespawnMode() { + return this.despawnMode; + } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 35d0ac83d..ee44acef2 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -425,7 +425,37 @@ spawning.liquidSpawning=Spawns in Liquid spawning.airSpawning=Spawns in Air spawning.minHeight=Minimum Height spawning.maxHeight=Maximum Height +spawning.maxAlive=Max Alive (0 = Unlimited) +spawning.cooldownTicks=Cooldown (Ticks) +spawning.attemptsPerCycle=Attempts Per Cycle +spawning.playerMinDistance=Min Player Distance +spawning.despawnMode=Despawn Mode +spawning.despawn.forceNatural=Force Natural +spawning.despawn.preserve=Preserve Template +spawning.despawn.forcePersistent=Force Persistent spawning.naturalInfo=Add at least one spawn entry and configure spawn options for the NPC to spawn naturally +spawning.naturalInfoPerformance=Natural Spawn Setup\nBest performance baseline:\n- maxAlive: 20-60\n- cooldownTicks: 200-600\n- attemptsPerCycle: 1-2\n- playerMinDistance: 32-48\n- despawnMode: Force Natural +tooltip.naturalspawns.main.addProfile=Create a new natural spawn profile.\nBest performance: Keep profile count focused by biome/theme. +tooltip.naturalspawns.main.removeProfile=Delete the selected natural spawn profile.\nBest performance: Remove unused profiles to reduce selection overhead. +tooltip.naturalspawns.main.search=Filter spawn profiles by name.\nTip: Use biome/type prefixes for faster maintenance. +tooltip.naturalspawns.main.biomes=Choose biomes this profile can spawn in.\nBest performance: Use only needed biomes. +tooltip.naturalspawns.main.dimensions=Choose dimensions this profile can spawn in.\nBest performance: Limit to required dimensions. +tooltip.naturalspawns.main.options=Open advanced spawn controls.\nBest performance: Configure cap, cooldown, attempts, and despawn mode. +tooltip.naturalspawns.main.addEntry=Add an entry slot to this profile.\nBest performance: Keep only active entries. +tooltip.naturalspawns.main.removeEntry=Remove selected entry slot.\nBest performance: Remove empty/duplicate entries. +tooltip.naturalspawns.main.slot=Entry slot index.\nTip: Keep numbering organized for easier debugging. +tooltip.naturalspawns.main.selectEntry=Select which clone/NPC this slot spawns.\nBest performance: Avoid very heavy scripted clones in high-frequency profiles. +tooltip.naturalspawns.options.animal=Uses animal-style spawn checks for this entry.\nBest performance: Enable only for passive/ground mobs. +tooltip.naturalspawns.options.monster=Uses monster-style spawn checks for this entry.\nBest performance: Enable only for hostile/night mobs. +tooltip.naturalspawns.options.liquid=Allows water/liquid spawn validation.\nBest performance: Keep disabled unless required. +tooltip.naturalspawns.options.air=Allows spawn attempts in open air blocks.\nBest performance: Keep disabled unless required. +tooltip.naturalspawns.options.minHeight=Minimum Y for this entry.\nBest performance: Use tight vertical ranges to reduce failed attempts. +tooltip.naturalspawns.options.maxHeight=Maximum Y for this entry.\nBest performance: Use tight vertical ranges to reduce failed attempts. +tooltip.naturalspawns.options.maxAlive=Per-entry alive cap (loaded entities).\n0 = unlimited (not recommended).\nBest performance: 20-60. +tooltip.naturalspawns.options.cooldownTicks=Ticks to wait after a successful spawn for this entry.\nBest performance: 200-600. +tooltip.naturalspawns.options.attemptsPerCycle=How many positions this entry tries each spawn cycle.\nBest performance: 1-2. +tooltip.naturalspawns.options.playerMinDistance=No-spawn radius from nearest player for this entry.\nBest performance: 32-48 to reduce pop-in and retests. +tooltip.naturalspawns.options.despawnMode=How spawned CNPC despawn flags are applied.\nBest performance: Force Natural.\nPreserve/Force Persistent can increase long-term entity load. role.name=Role role.none=No Role From 6abd29b12dfca90f678f3a9ca131bfe0a93e3df0 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 05:27:45 -0500 Subject: [PATCH 66/71] Natural Spawns PR3 - Better Runtime Control of Natural Spawns --- src/main/java/noppes/npcs/NPCSpawning.java | 57 ++++++++++++++++--- .../client/gui/SubGuiSpawningOptions.java | 2 +- .../assets/customnpcs/lang/en_US.lang | 22 +++---- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/main/java/noppes/npcs/NPCSpawning.java b/src/main/java/noppes/npcs/NPCSpawning.java index 9e9699915..8b97c0307 100644 --- a/src/main/java/noppes/npcs/NPCSpawning.java +++ b/src/main/java/noppes/npcs/NPCSpawning.java @@ -29,7 +29,6 @@ import noppes.npcs.scripted.event.CustomNPCsEvent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; @@ -39,18 +38,27 @@ public class NPCSpawning { private static Set eligibleChunksForSpawning = Sets.newHashSet(); private static final byte CHUNK_SPAWN_RADIUS = 7; private static final int SPAWN_ATTEMPTS_PER_CHUNK = 3; + private static final long RUNTIME_SPAWN_TICK_INTERVAL = 20L; + private static final int RUNTIME_SPAWN_CYCLE_TICKS = 400; private static final String NATURAL_SPAWN_ID_TAG = "CNPCNaturalSpawnId"; private static final String NATURAL_SPAWN_NAME_TAG = "CNPCNaturalSpawnName"; private static final String NATURAL_SPAWN_TIME_TAG = "CNPCNaturalSpawnTime"; private static final Map SPAWN_COOLDOWNS = new HashMap(); + private static final Map RUNTIME_CHUNK_STATES = new HashMap(); private static boolean animalSpawn; private static boolean monsterSpawn; private static boolean airSpawn; private static boolean liquidSpawn; + private static class RuntimeChunkState { + public ArrayList chunks = new ArrayList(); + public int index = 0; + public int signature = 0; + } + public static void findChunksForSpawning(WorldServer world) { - if (SpawnController.Instance.data.isEmpty() || world.getWorldInfo().getWorldTotalTime() % 400L != 0L) + if (SpawnController.Instance.data.isEmpty() || world.getWorldInfo().getWorldTotalTime() % RUNTIME_SPAWN_TICK_INTERVAL != 0L) return; eligibleChunksForSpawning.clear(); for (int i = 0; i < world.playerEntities.size(); ++i) { @@ -74,15 +82,13 @@ public class NPCSpawning { return; } HashMap spawnEntryCounts = getNaturalSpawnEntryCounts(world); - ArrayList tmp = new ArrayList(eligibleChunksForSpawning); - Collections.shuffle(tmp); - Iterator iterator = tmp.iterator(); + int chunkBudget = getRuntimeChunkBudget(eligibleChunksForSpawning.size()); + List chunksToProcess = getRuntimeChunkBatch(world, chunkBudget); - while (iterator.hasNext()) { + for (ChunkCoordIntPair chunkcoordintpair1 : chunksToProcess) { if (npcCount >= cap) { return; } - ChunkCoordIntPair chunkcoordintpair1 = iterator.next(); ChunkPosition chunkposition = getChunk(world, chunkcoordintpair1.chunkXPos, chunkcoordintpair1.chunkZPos); int j1 = chunkposition.chunkPosX; @@ -112,6 +118,43 @@ public class NPCSpawning { } } + private static int getRuntimeChunkBudget(int eligibleChunkCount) { + if (eligibleChunkCount <= 0) { + return 0; + } + int runsPerCycle = Math.max(1, (int) (RUNTIME_SPAWN_CYCLE_TICKS / RUNTIME_SPAWN_TICK_INTERVAL)); + return Math.max(1, (int) Math.ceil((double) eligibleChunkCount / runsPerCycle)); + } + + private static List getRuntimeChunkBatch(World world, int chunkBudget) { + ArrayList result = new ArrayList(); + if (chunkBudget <= 0 || eligibleChunksForSpawning.isEmpty()) { + return result; + } + + int dimensionId = world.provider.dimensionId; + RuntimeChunkState state = RUNTIME_CHUNK_STATES.get(dimensionId); + if (state == null) { + state = new RuntimeChunkState(); + RUNTIME_CHUNK_STATES.put(dimensionId, state); + } + + int signature = eligibleChunksForSpawning.hashCode(); + if (state.chunks.isEmpty() || state.signature != signature || state.index >= state.chunks.size()) { + state.chunks = new ArrayList(eligibleChunksForSpawning); + Collections.shuffle(state.chunks); + state.index = 0; + state.signature = signature; + } + + int remaining = state.chunks.size() - state.index; + int count = Math.min(chunkBudget, Math.max(0, remaining)); + for (int i = 0; i < count; i++) { + result.add(state.chunks.get(state.index++)); + } + return result; + } + public static int countNPCs(World world) { int count = 0; List list = world.loadedEntityList; diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java index caabe3d11..34b3e7a6e 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java @@ -16,7 +16,7 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield public SubGuiSpawningOptions(SpawnData data) { this.data = data; setBackground("menubg.png"); - xSize = 216; + xSize = 236; ySize = 216; closeOnEsc = true; } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index ee44acef2..3f8053a06 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -445,17 +445,17 @@ tooltip.naturalspawns.main.addEntry=Add an entry slot to this profile.\nBest per tooltip.naturalspawns.main.removeEntry=Remove selected entry slot.\nBest performance: Remove empty/duplicate entries. tooltip.naturalspawns.main.slot=Entry slot index.\nTip: Keep numbering organized for easier debugging. tooltip.naturalspawns.main.selectEntry=Select which clone/NPC this slot spawns.\nBest performance: Avoid very heavy scripted clones in high-frequency profiles. -tooltip.naturalspawns.options.animal=Uses animal-style spawn checks for this entry.\nBest performance: Enable only for passive/ground mobs. -tooltip.naturalspawns.options.monster=Uses monster-style spawn checks for this entry.\nBest performance: Enable only for hostile/night mobs. -tooltip.naturalspawns.options.liquid=Allows water/liquid spawn validation.\nBest performance: Keep disabled unless required. -tooltip.naturalspawns.options.air=Allows spawn attempts in open air blocks.\nBest performance: Keep disabled unless required. -tooltip.naturalspawns.options.minHeight=Minimum Y for this entry.\nBest performance: Use tight vertical ranges to reduce failed attempts. -tooltip.naturalspawns.options.maxHeight=Maximum Y for this entry.\nBest performance: Use tight vertical ranges to reduce failed attempts. -tooltip.naturalspawns.options.maxAlive=Per-entry alive cap (loaded entities).\n0 = unlimited (not recommended).\nBest performance: 20-60. -tooltip.naturalspawns.options.cooldownTicks=Ticks to wait after a successful spawn for this entry.\nBest performance: 200-600. -tooltip.naturalspawns.options.attemptsPerCycle=How many positions this entry tries each spawn cycle.\nBest performance: 1-2. -tooltip.naturalspawns.options.playerMinDistance=No-spawn radius from nearest player for this entry.\nBest performance: 32-48 to reduce pop-in and retests. -tooltip.naturalspawns.options.despawnMode=How spawned CNPC despawn flags are applied.\nBest performance: Force Natural.\nPreserve/Force Persistent can increase long-term entity load. +tooltip.naturalspawns.options.animal=Spawn Rule: Animal\nUses animal-style spawn checks (surface + creature-valid blocks).\nUse for passive/overworld mobs.\nPerformance: Disable if not needed. +tooltip.naturalspawns.options.monster=Spawn Rule: Monster\nUses monster-style spawn checks (surface + monster-valid blocks).\nUse for hostile/night/cave mobs.\nPerformance: Disable if not needed. +tooltip.naturalspawns.options.liquid=Spawn Rule: Liquid\nAllows spawn validation in liquid/water spaces.\nUse only for aquatic entries.\nPerformance: Keep OFF unless required. +tooltip.naturalspawns.options.air=Spawn Rule: Air\nAllows spawn validation in open-air columns.\nUse only for flying/airborne entries.\nPerformance: Keep OFF unless required. +tooltip.naturalspawns.options.minHeight=Minimum Spawn Y\nLower vertical bound for this profile.\nUse with Max Height to narrow valid layers.\nPerformance: Tighter ranges = fewer failed checks. +tooltip.naturalspawns.options.maxHeight=Maximum Spawn Y\nUpper vertical bound for this profile.\nUse with Min Height to target a specific layer.\nPerformance: Tighter ranges = fewer failed checks. +tooltip.naturalspawns.options.maxAlive=Max Alive (Per Profile)\nCounts loaded entities tagged to this natural spawn profile.\n0 = Unlimited (not recommended).\nPerformance baseline: 20-60. +tooltip.naturalspawns.options.cooldownTicks=Cooldown (Ticks)\nWait time after a successful spawn before this profile can spawn again.\n20 ticks = 1 second.\nPerformance baseline: 200-600 (10-30 seconds). +tooltip.naturalspawns.options.attemptsPerCycle=Attempts Per Cycle\nHow many nearby positions this profile tests each cycle.\nHigher values increase spawn pressure and CPU checks.\nPerformance baseline: 1-2. +tooltip.naturalspawns.options.playerMinDistance=Min Player Distance\nBlocks from nearest player where spawning is blocked.\nHigher values reduce pop-in near players.\nPerformance baseline: 32-48. +tooltip.naturalspawns.options.despawnMode=Despawn Mode (CNPC entries)\nForce Natural: sets canDespawn=true on spawn. Recommended for natural ecosystems.\nPreserve Template: keeps clone/template despawn setting.\nForce Persistent: sets canDespawn=false (NPC stays loaded longer).\nPerformance recommendation: Force Natural. role.name=Role role.none=No Role From 37b38daa0018c6f5ec6f181a62a250e259b8ab39 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 05:44:45 -0500 Subject: [PATCH 67/71] Improved Natural Despawn Handling PR 4 - Natural Spawns --- src/main/java/noppes/npcs/NPCSpawning.java | 27 ++++++++++++++----- .../java/noppes/npcs/ServerEventsHandler.java | 2 +- .../npcs/entity/EntityNPCInterface.java | 24 +++++++++++++++-- .../npcs/scripted/entity/ScriptNpc.java | 1 + src/main/resources/META-INF/customnpcs_at.cfg | 2 ++ 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/main/java/noppes/npcs/NPCSpawning.java b/src/main/java/noppes/npcs/NPCSpawning.java index 8b97c0307..ed633c8f7 100644 --- a/src/main/java/noppes/npcs/NPCSpawning.java +++ b/src/main/java/noppes/npcs/NPCSpawning.java @@ -38,6 +38,7 @@ public class NPCSpawning { private static Set eligibleChunksForSpawning = Sets.newHashSet(); private static final byte CHUNK_SPAWN_RADIUS = 7; private static final int SPAWN_ATTEMPTS_PER_CHUNK = 3; + private static final int NATURAL_CAP_PER_256_CHUNKS = 70; private static final long RUNTIME_SPAWN_TICK_INTERVAL = 20L; private static final int RUNTIME_SPAWN_CYCLE_TICKS = 400; private static final String NATURAL_SPAWN_ID_TAG = "CNPCNaturalSpawnId"; @@ -76,8 +77,8 @@ public class NPCSpawning { } } } - int cap = eligibleChunksForSpawning.size(); - int npcCount = countNPCs(world); + int cap = getScaledNaturalCap(eligibleChunksForSpawning.size()); + int npcCount = countNaturalSpawnedNPCs(world); if (npcCount >= cap) { return; } @@ -155,17 +156,24 @@ public class NPCSpawning { return result; } - public static int countNPCs(World world) { + private static int countNaturalSpawnedNPCs(World world) { int count = 0; List list = world.loadedEntityList; for (Entity entity : list) { - if (entity instanceof EntityNPCInterface) { + if (entity instanceof EntityNPCInterface && !entity.isDead && entity.getEntityData().hasKey(NATURAL_SPAWN_ID_TAG)) { count++; } } return count; } + private static int getScaledNaturalCap(int eligibleChunkCount) { + if (eligibleChunkCount <= 0) { + return 0; + } + return Math.max(1, NATURAL_CAP_PER_256_CHUNKS * eligibleChunkCount / 256); + } + protected static ChunkPosition getChunk(World world, int x, int z) { Chunk chunk = world.getChunkFromChunkCoords(x, z); int k = x * 16 + world.rand.nextInt(16); @@ -188,14 +196,14 @@ public class NPCSpawning { } } } - return chunkSet.size(); + return getScaledNaturalCap(chunkSet.size()); } private static HashMap getNaturalSpawnEntryCounts(World world) { HashMap counts = new HashMap(); List list = world.loadedEntityList; for (Entity entity : list) { - if (entity == null || entity.isDead) { + if (entity == null || entity.isDead || !(entity instanceof EntityNPCInterface)) { continue; } NBTTagCompound entityData = entity.getEntityData(); @@ -223,6 +231,10 @@ public class NPCSpawning { return false; } long tickDelta = world.getWorldInfo().getWorldTotalTime() - lastSpawnTick; + if (tickDelta < 0) { + SPAWN_COOLDOWNS.remove(key); + return false; + } return tickDelta < data.cooldownTicks; } @@ -288,7 +300,7 @@ public class NPCSpawning { public static void performWorldGenSpawning(World world, int x, int z, Random rand) { int cap = getCap(world); - int npcCount = countNPCs(world); + int npcCount = countNaturalSpawnedNPCs(world); if (npcCount >= cap) { return; } @@ -379,6 +391,7 @@ public class NPCSpawning { npc.stats.canDespawn = true; npc.stats.playerSetCanDespawn = true; } + npc.syncDespawnPersistence(); npc.ais.returnToStart = false; npc.ais.startPos = new int[]{x, y, z}; npc.updateAI = true; diff --git a/src/main/java/noppes/npcs/ServerEventsHandler.java b/src/main/java/noppes/npcs/ServerEventsHandler.java index 0f9fa1d3e..43d78e6eb 100644 --- a/src/main/java/noppes/npcs/ServerEventsHandler.java +++ b/src/main/java/noppes/npcs/ServerEventsHandler.java @@ -530,7 +530,7 @@ public class ServerEventsHandler { @SubscribeEvent public void populateChunk(PopulateChunkEvent.Post event) { - NPCSpawning.performWorldGenSpawning(event.world, event.chunkX, event.chunkZ, event.rand); + NPCSpawning.performWorldGenSpawning(event.world, event.chunkX << 4, event.chunkZ << 4, event.rand); } @SubscribeEvent diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 7a978eb89..a3dcfb84c 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -255,7 +255,7 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti setFaction(faction.id); setSize(1, 1); this.updateTasks(); - this.func_110163_bv(); + this.syncDespawnPersistence(); if (!this.isRemote() && this.wrappedNPC == null) { this.wrappedNPC = new ScriptNpc<>(this); @@ -1203,7 +1203,7 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti this.getEntityAttribute(SharedMonsterAttributes.followRange).setBaseValue(ConfigMain.NpcNavRange); this.updateTasks(); - this.func_110163_bv(); + this.syncDespawnPersistence(); } @Override @@ -1404,6 +1404,26 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti field_20062_v += d1 * 0.25D; } + @Override + protected void despawnEntity() { + syncDespawnPersistence(); + super.despawnEntity(); + } + + public void syncDespawnPersistence() { + if (stats == null) { + if (!this.persistenceRequired) { + this.func_110163_bv(); + } + return; + } + if (stats.canDespawn) { + this.persistenceRequired = false; + } else if (!this.persistenceRequired) { + this.func_110163_bv(); + } + } + @Override protected boolean canDespawn() { return stats.canDespawn; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java index 25734b7e4..c362d72c6 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java @@ -1109,6 +1109,7 @@ public class ScriptNpc extends ScriptLiving imp public void setNaturallyDespawns(boolean canDespawn) { npc.stats.canDespawn = canDespawn; npc.stats.playerSetCanDespawn = canDespawn; + npc.syncDespawnPersistence(); } public boolean spawnedFromSoulStone() { diff --git a/src/main/resources/META-INF/customnpcs_at.cfg b/src/main/resources/META-INF/customnpcs_at.cfg index dbcb46481..a41eb23c9 100644 --- a/src/main/resources/META-INF/customnpcs_at.cfg +++ b/src/main/resources/META-INF/customnpcs_at.cfg @@ -18,6 +18,8 @@ public net.minecraft.client.renderer.entity.RenderPlayer func_77043_a(Lnet/minec #EntityLivingBase public net.minecraft.entity.EntityLivingBase func_82166_i()I # getArmSwingAnimationEnd public net.minecraft.entity.EntityLivingBase field_70703_bu # isJumping +#EntityLiving +public net.minecraft.entity.EntityLiving field_82179_bU # persistenceRequired # Minecraft public net.minecraft.client.Minecraft field_147126_aw # mcMusicTicker # MusicTicker From f95e7afb4e5cebbb0a2bcb212f07b0f36a350421 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 13:33:46 -0500 Subject: [PATCH 68/71] Fix Animation Offsets with Vanilla Offsets --- .../java/noppes/npcs/mixin/MixinModelRenderer.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/noppes/npcs/mixin/MixinModelRenderer.java b/src/main/java/noppes/npcs/mixin/MixinModelRenderer.java index e38805870..603b856b6 100644 --- a/src/main/java/noppes/npcs/mixin/MixinModelRenderer.java +++ b/src/main/java/noppes/npcs/mixin/MixinModelRenderer.java @@ -86,6 +86,9 @@ public abstract class MixinModelRenderer { float prevAngleX = this.rotateAngleX; float prevAngleY = this.rotateAngleY; float prevAngleZ = this.rotateAngleZ; + float prevOffsetX = this.offsetX; + float prevOffsetY = this.offsetY; + float prevOffsetZ = this.offsetZ; boolean changedAngles = false; try { @@ -149,6 +152,11 @@ public abstract class MixinModelRenderer { GL11.glPopMatrix(); } + // Mirror vanilla ModelRenderer behavior: always undo offset translation. + // Without this, models that animate via offsetX/Y/Z (e.g. witch nose while drinking) + // leak transforms into subsequent renders. + GL11.glTranslatef(-this.offsetX, -this.offsetY, -this.offsetZ); + if (changedAngles) { this.rotationPointX = prevPointX; this.rotationPointY = prevPointY; @@ -156,6 +164,9 @@ public abstract class MixinModelRenderer { this.rotateAngleX = prevAngleX; this.rotateAngleY = prevAngleY; this.rotateAngleZ = prevAngleZ; + this.offsetX = prevOffsetX; + this.offsetY = prevOffsetY; + this.offsetZ = prevOffsetZ; } } } From 21cd8e9e01f63b5594efafc94e8eaa4d5582c62a Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 13:40:24 -0500 Subject: [PATCH 69/71] Fix Hover Labels on Scroll Components --- .../noppes/npcs/client/gui/SubGuiSpawningOptions.java | 4 ++-- .../java/noppes/npcs/client/gui/util/GuiNPCInterface.java | 6 ++++++ .../java/noppes/npcs/client/gui/util/GuiScrollWindow.java | 8 +------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java index 34b3e7a6e..ddce230c3 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiSpawningOptions.java @@ -16,7 +16,7 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield public SubGuiSpawningOptions(SpawnData data) { this.data = data; setBackground("menubg.png"); - xSize = 236; + xSize = 256; ySize = 216; closeOnEsc = true; } @@ -98,7 +98,7 @@ public class SubGuiSpawningOptions extends SubGuiInterface implements ITextfield sw.getTextField(19).setHoverText("tooltip.naturalspawns.options.playerMinDistance"); y += 22; - sw.addButton(new GuiNpcButton(20, controlX, y, 74, 20, + sw.addButton(new GuiNpcButton(20, controlX, y, 100, 20, new String[]{"spawning.despawn.forceNatural", "spawning.despawn.preserve", "spawning.despawn.forcePersistent"}, data.despawnMode)); sw.addLabel(new GuiNpcLabel(20, "spawning.despawnMode", labelX, y + 5)); sw.getButton(20).setHoverText("tooltip.naturalspawns.options.despawnMode"); diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java index f8cddbf72..ccdbe845c 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java @@ -417,6 +417,12 @@ public abstract class GuiNPCInterface extends GuiScreen { } } + for(GuiScrollWindow guiScrollableComponent : scrollWindows.values()) { + if (guiScrollableComponent.isMouseOver(mouseX, mouseY)) { + guiScrollableComponent.drawHoverTexts(mouseX, mouseY); + } + } + for (GuiScreen gui : extra.values()) gui.drawScreen(i, j, f); diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java index 0df1c8547..8a20a635c 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java @@ -251,12 +251,6 @@ public class GuiScrollWindow extends GuiScreen implements ITextfieldListener, IC } GL11.glPopMatrix(); - - // Draw hover texts in screen space (after scissor/translation are undone) - // so tooltips are not clipped and are positioned correctly - if (isMouseOver(mouseX, mouseY)) { - drawHoverTexts(mouseX, mouseY); - } } public void drawScreen(int mouseX, int mouseY, float partialTicks, int mouseScroll) { @@ -293,7 +287,7 @@ public class GuiScrollWindow extends GuiScreen implements ITextfieldListener, IC * Draws hover texts for buttons in screen space (called after GL scissor/translate are undone). * Hover state was already computed during drawComponents with scroll-local coords. */ - private void drawHoverTexts(int mouseX, int mouseY) { + public void drawHoverTexts(int mouseX, int mouseY) { boolean subGui = parent.hasSubGui(); for (GuiNpcButton button : buttons.values()) { if (!button.hoverableText.isEmpty()) { From e4fe8c5112336f353670d703e973d320e1552653 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 14:21:55 -0500 Subject: [PATCH 70/71] Max Hits for Piece and Multi --- src/api | 2 +- .../ability/data/energy/EnergyCombatData.java | 26 +++++++ .../type/energy/AbilityEnergyProjectile.java | 12 ++++ .../npcs/entity/EntityEnergyProjectile.java | 69 ++++++++++++++++--- .../entity/ScriptEnergyProjectile.java | 8 +++ .../assets/customnpcs/lang/en_US.lang | 4 +- 6 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/api b/src/api index db7173c4e..788cd0826 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit db7173c4ec6b01ffb4ab23fad130164c5815f7dc +Subproject commit 788cd08264f226953571b0aa737b98a4e2d6d19c diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java index 1c8418b96..88103197f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java @@ -10,6 +10,8 @@ import noppes.npcs.api.ability.data.IEnergyCombatData; */ public class EnergyCombatData implements IEnergyCombatData { public static final float MAX_EXPLOSION_RADIUS = 15.0f; + public static final int DEFAULT_MAX_HITS = 5; + public static final int MAX_HITS = 200; public float damage = 7.0f; public float knockback = 1.0f; @@ -19,6 +21,7 @@ public class EnergyCombatData implements IEnergyCombatData { public float explosionDamageFalloff = 0.5f; public HitType hitType = HitType.SINGLE; public int multiHitDelayTicks = 5; + public int maxHits = DEFAULT_MAX_HITS; public EnergyCombatData() { } @@ -26,6 +29,12 @@ public class EnergyCombatData implements IEnergyCombatData { public EnergyCombatData(float damage, float knockback, float knockbackUp, boolean explosive, float explosionRadius, float explosionDamageFalloff, HitType hitType, int multiHitDelay) { + this(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, hitType, multiHitDelay, DEFAULT_MAX_HITS); + } + + public EnergyCombatData(float damage, float knockback, float knockbackUp, + boolean explosive, float explosionRadius, float explosionDamageFalloff, + HitType hitType, int multiHitDelay, int maxHits) { this.damage = damage; this.knockback = knockback; this.knockbackUp = knockbackUp; @@ -34,6 +43,7 @@ public class EnergyCombatData implements IEnergyCombatData { this.explosionDamageFalloff = explosionDamageFalloff; this.hitType = hitType; this.multiHitDelayTicks = multiHitDelay; + this.maxHits = clampMaxHits(maxHits); } public EnergyCombatData(float damage, float knockback, float knockbackUp, @@ -106,6 +116,14 @@ public class EnergyCombatData implements IEnergyCombatData { this.explosionDamageFalloff = explosionDamageFalloff; } + public int getMaxHits() { + return maxHits; + } + + public void setMaxHits(int maxHits) { + this.maxHits = clampMaxHits(maxHits); + } + public void writeNBT(NBTTagCompound nbt) { nbt.setFloat("damage", damage); nbt.setFloat("knockback", knockback); @@ -115,6 +133,7 @@ public class EnergyCombatData implements IEnergyCombatData { nbt.setFloat("explosionDamageFalloff", explosionDamageFalloff); nbt.setInteger("hitType", hitType.ordinal()); nbt.setInteger("multiHitDelayTicks", multiHitDelayTicks); + nbt.setInteger("maxHits", maxHits); } public void readNBT(NBTTagCompound nbt) { @@ -126,6 +145,7 @@ public class EnergyCombatData implements IEnergyCombatData { explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f; hitType = HitType.fromOrdinal(nbt.hasKey("hitType") ? nbt.getInteger("hitType") : 0); multiHitDelayTicks = nbt.hasKey("multiHitDelayTicks") ? nbt.getInteger("multiHitDelayTicks") : 5; + maxHits = nbt.hasKey("maxHits") ? nbt.getInteger("maxHits") : DEFAULT_MAX_HITS; // Sanitize if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f; @@ -133,6 +153,7 @@ public class EnergyCombatData implements IEnergyCombatData { if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f; explosionRadius = clampExplosionRadius(explosionRadius); if (multiHitDelayTicks < 1) multiHitDelayTicks = 1; + maxHits = clampMaxHits(maxHits); } private static float clampExplosionRadius(float explosionRadius) { @@ -140,11 +161,16 @@ public class EnergyCombatData implements IEnergyCombatData { return Math.max(0.0f, Math.min(MAX_EXPLOSION_RADIUS, explosionRadius)); } + private static int clampMaxHits(int maxHits) { + return Math.max(1, Math.min(MAX_HITS, maxHits)); + } + public EnergyCombatData copy() { EnergyCombatData copy = new EnergyCombatData(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff); copy.hitType = hitType; copy.multiHitDelayTicks = multiHitDelayTicks; + copy.maxHits = maxHits; return copy; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 4318c88eb..d65c6e7e6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -685,6 +685,14 @@ public abstract class AbilityEnergyProjectile this.combatData.multiHitDelayTicks = Math.max(1, delay); } + public int getMaxHits() { + return combatData.getMaxHits(); + } + + public void setMaxHits(int maxHits) { + combatData.setMaxHits(maxHits); + } + // Lifespan data @Override public float getMaxDistance() { @@ -832,6 +840,10 @@ public abstract class AbilityEnergyProjectile .range(1, 200) .visibleWhen(() -> getHitType() == HitType.MULTI.ordinal()) .hover("ability.hover.multiHitDelay")); + defs.add(FieldDef.intField("ability.maxHits", this::getMaxHits, this::setMaxHits) + .range(1, EnergyCombatData.MAX_HITS) + .visibleWhen(() -> getHitType() != HitType.SINGLE.ordinal()) + .hover("ability.hover.maxHits")); // Type-specific fields first addTypeDefinitions(defs); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index aab79a444..26d3b9d08 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -124,6 +124,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== STATE ==================== protected boolean hasHit = false; + protected int hitCount = 0; protected final Set hitOnceEntities = new HashSet(); protected final Map lastHitTickByEntity = new HashMap(); protected static final int BARRIER_IMPACT_PAUSE_TICKS = 10; @@ -508,6 +509,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { hitOnceEntities.clear(); lastHitTickByEntity.clear(); + hitCount = 0; return true; } @@ -943,13 +945,12 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Using feet position (target.posY) causes explosions at impact height to miss // entities standing below — e.g. a projectile hitting at eye height (~1.62 above feet) // would measure dist=1.62, failing a radius<=1.6 check despite a direct hit. - double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX)); - double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY)); - double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ)); + Vec3 closest = closestPointOnBoundingBox(target.boundingBox, posX, posY, posZ); + if (closest == null) continue; double dist = Math.sqrt( - (closestX - posX) * (closestX - posX) + - (closestY - posY) * (closestY - posY) + - (closestZ - posZ) * (closestZ - posZ) + (closest.xCoord - posX) * (closest.xCoord - posX) + + (closest.yCoord - posY) * (closest.yCoord - posY) + + (closest.zCoord - posZ) * (closest.zCoord - posZ) ); if (dist <= explosionRad) { @@ -970,6 +971,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { return shouldIgnoreEntity(target); } + /** + * Clamp a point to the nearest point inside the provided AABB. + */ + protected Vec3 closestPointOnBoundingBox(AxisAlignedBB box, double x, double y, double z) { + if (box == null) return null; + double closestX = Math.max(box.minX, Math.min(x, box.maxX)); + double closestY = Math.max(box.minY, Math.min(y, box.maxY)); + double closestZ = Math.max(box.minZ, Math.min(z, box.maxZ)); + return Vec3.createVectorHelper(closestX, closestY, closestZ); + } + /** * Spawn a packet-driven client preview so explosions are visible as geometry, * not only as particles. @@ -1861,8 +1873,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { combatData.multiHitDelayTicks = Math.max(1, delayTicks); } + public int getMaxHits() { + return combatData.getMaxHits(); + } + + public void setMaxHits(int maxHits) { + combatData.setMaxHits(maxHits); + } + protected boolean canHitEntityNow(EntityLivingBase entity) { if (entity == null) return false; + if (combatData.hitType != HitType.SINGLE && hasReachedMaxHits()) return false; int entityId = entity.getEntityId(); switch (combatData.hitType) { @@ -1884,10 +1905,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { int entityId = entity.getEntityId(); hitOnceEntities.add(entityId); lastHitTickByEntity.put(entityId, ticksExisted); + hitCount++; + } + + protected boolean hasReachedMaxHits() { + if (combatData.hitType == HitType.SINGLE) return false; + return hitCount >= combatData.getMaxHits(); } protected boolean shouldTerminateAfterHit() { - return combatData.hitType == HitType.SINGLE; + if (combatData.hitType == HitType.SINGLE) return true; + return hasReachedMaxHits(); } /** @@ -1931,12 +1959,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { recordEntityHit(entity); if (isExplosive()) { + Vec3 impactPoint = resolveEntityImpactPoint(entity, impactX, impactY, impactZ); double oldX = posX; double oldY = posY; double oldZ = posZ; - posX = impactX; - posY = impactY; - posZ = impactZ; + posX = impactPoint.xCoord; + posY = impactPoint.yCoord; + posZ = impactPoint.zCoord; doExplosion(); posX = oldX; posY = oldY; @@ -1954,6 +1983,26 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { return false; } + /** + * Resolve a stable impact point on the target so explosive collisions use actual contact. + */ + protected Vec3 resolveEntityImpactPoint(EntityLivingBase entity, double impactX, double impactY, double impactZ) { + if (entity == null || entity.boundingBox == null) { + return Vec3.createVectorHelper(impactX, impactY, impactZ); + } + + Vec3 segmentStart = Vec3.createVectorHelper(posX, posY, posZ); + Vec3 segmentEnd = Vec3.createVectorHelper(impactX, impactY, impactZ); + AxisAlignedBB expandedBox = entity.boundingBox.expand(1.0e-4, 1.0e-4, 1.0e-4); + MovingObjectPosition intercept = expandedBox.calculateIntercept(segmentStart, segmentEnd); + if (intercept != null && intercept.hitVec != null) { + return intercept.hitVec; + } + + Vec3 closest = closestPointOnBoundingBox(entity.boundingBox, impactX, impactY, impactZ); + return closest != null ? closest : Vec3.createVectorHelper(impactX, impactY, impactZ); + } + /** * Scan entities in a hitbox and process the first/each valid hit based on hit mode. */ diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index de9c8036e..db810848e 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -177,6 +177,14 @@ public class ScriptEnergyProjectile extends Sc entity.setMultiHitDelayTicks(delayTicks); } + public int getMaxHits() { + return entity.getMaxHits(); + } + + public void setMaxHits(int maxHits) { + entity.setMaxHits(maxHits); + } + // ==================== MOVEMENT ==================== public float getSpeed() { diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 3f8053a06..a4c8103dc 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1819,6 +1819,7 @@ ability.hitType.single=Single ability.hitType.pierce=Pierce ability.hitType.multi=Multi ability.multiHitDelay=Multi-Hit Delay +ability.maxHits=Max Hits ability.distance=Distance ability.maxDistance=Max Distance ability.lifetime=Lifetime @@ -2027,8 +2028,9 @@ ability.hover.windUpColor=Color of telegraph during charge-up phase ability.hover.activeColor=Color of telegraph when ability is active ability.hover.homing=Projectile tracks toward target ability.hover.homingStr=How aggressively the projectile tracks (higher = tighter turns) -ability.hover.hitType=Single: stops on first hit. Pierce: can hit each entity once. Multi: can re-hit entities using the delay below. +ability.hover.hitType=Single: stops on first hit. Pierce: can hit each entity once. Multi: can re-hit entities using the delay below. Pierce/Multi both respect Max Hits. ability.hover.multiHitDelay=Ticks between repeat hits on the same entity when Hit Type is Multi (20 ticks = 1 second) +ability.hover.maxHits=Maximum total hits before projectile ends in Pierce/Multi modes. Lower values reduce repeated hit spam and improve performance. ability.hover.explosive=Creates explosion on impact ability.hover.explosionRad=Size of explosion radius ability.hover.piercing=Hits multiple targets instead of stopping on first hit From 03bea7461db32d630626889d4f7858e4090ce8e6 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 14:33:02 -0500 Subject: [PATCH 71/71] Optimize Explosion Handling --- .../npcs/entity/EntityEnergyProjectile.java | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 26d3b9d08..a1d858cf4 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -923,6 +923,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (worldObj.isRemote) return; // Explosions are server-side only float explosionRad = getExplosionRadius(); if (Float.isNaN(explosionRad) || explosionRad <= 0) return; + final double explosionRadSq = explosionRad * explosionRad; + final float baseDamage = getDamage(); + final float baseKnockback = getKnockback(); + final float damageFalloff = getExplosionDamageFalloff(); spawnExplosionRenderEntity(explosionRad); spawnExplosionVisuals(explosionRad); worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); @@ -945,18 +949,22 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Using feet position (target.posY) causes explosions at impact height to miss // entities standing below — e.g. a projectile hitting at eye height (~1.62 above feet) // would measure dist=1.62, failing a radius<=1.6 check despite a direct hit. - Vec3 closest = closestPointOnBoundingBox(target.boundingBox, posX, posY, posZ); - if (closest == null) continue; - double dist = Math.sqrt( - (closest.xCoord - posX) * (closest.xCoord - posX) + - (closest.yCoord - posY) * (closest.yCoord - posY) + - (closest.zCoord - posZ) * (closest.zCoord - posZ) - ); + if (target.boundingBox == null) continue; + double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX)); + double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY)); + double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ)); + double dx = closestX - posX; + double dy = closestY - posY; + double dz = closestZ - posZ; + double distSq = dx * dx + dy * dy + dz * dz; + if (distSq > explosionRadSq) continue; - if (dist <= explosionRad) { - float falloff = 1.0f - (float) (dist / explosionRad) * getExplosionDamageFalloff(); - applyDamage(target, getDamage() * falloff, getKnockback() * falloff); + float falloff = 1.0f; + if (damageFalloff != 0.0f && distSq > 0.0D) { + float dist = (float) Math.sqrt(distSq); + falloff = 1.0f - (dist / explosionRad) * damageFalloff; } + applyDamage(target, baseDamage * falloff, baseKnockback * falloff); } tryDestroyTerrain(explosionRad); @@ -1161,11 +1169,16 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { int maxY = MathHelper.floor_double(posY + terrainRad); int minZ = MathHelper.floor_double(posZ - terrainRad); int maxZ = MathHelper.floor_double(posZ + terrainRad); + int worldMaxY = Math.max(0, worldObj.getActualHeight() - 1); + if (maxY < 0 || minY > worldMaxY) return; float resistanceCutoff = Math.max(4.0f, terrainRad * 6.0f); Explosion context = new Explosion(worldObj, this, posX, posY, posZ, terrainRad); context.isFlaming = false; context.isSmoking = true; + int lastChunkX = Integer.MIN_VALUE; + int lastChunkZ = Integer.MIN_VALUE; + boolean lastChunkLoaded = false; for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { @@ -1181,6 +1194,16 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Soften cube corners a bit for a less artificial crater edge. double normalized = chebyshev / terrainRad; if (normalized > 0.72D && rand.nextDouble() < (normalized - 0.72D) * 1.4D) continue; + if (y < 0 || y > worldMaxY) continue; + + int chunkX = x >> 4; + int chunkZ = z >> 4; + if (chunkX != lastChunkX || chunkZ != lastChunkZ) { + lastChunkX = chunkX; + lastChunkZ = chunkZ; + lastChunkLoaded = worldObj.getChunkProvider().chunkExists(chunkX, chunkZ); + } + if (!lastChunkLoaded) continue; Block block = worldObj.getBlock(x, y, z); if (block == null || block == Blocks.air || block == Blocks.bedrock) continue;