From b1d7082584be943194bd0bfb90f859756c29af71 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 04:11:21 -0500 Subject: [PATCH 01/12] Launch from Anchor Point Setting --- src/api | 2 +- .../ability/data/energy/EnergyAnchorData.java | 17 ++++++++++++++++- .../type/energy/AbilityEnergyProjectile.java | 19 +++++++++++++++++++ .../npcs/entity/EntityEnergyProjectile.java | 10 ++++++++-- .../assets/customnpcs/lang/en_US.lang | 2 ++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/api b/src/api index b28a7a5c4..17e883360 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit b28a7a5c4d025ec3d5207cd8d4714f14c3a5de72 +Subproject commit 17e883360462326c04e3c9651e777346b34d47ec diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java index b4ceb830f..f618ae3f7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java @@ -9,6 +9,7 @@ public class EnergyAnchorData implements IEnergyAnchorData { public float anchorOffsetX = 0; public float anchorOffsetY = 0; public float anchorOffsetZ = 0; + public boolean launchFromAnchor = false; public EnergyAnchorData() { } @@ -83,11 +84,22 @@ public class EnergyAnchorData implements IEnergyAnchorData { this.anchorOffsetZ = anchorOffsetZ; } + @Override + public boolean getLaunchFromAnchor() { + return launchFromAnchor; + } + + @Override + public void setLaunchFromAnchor(boolean launchFromAnchor) { + this.launchFromAnchor = launchFromAnchor; + } + public void writeNBT(NBTTagCompound nbt) { nbt.setInteger("anchorPoint", anchorPoint.ordinal()); nbt.setFloat("anchorOffsetX", anchorOffsetX); nbt.setFloat("anchorOffsetY", anchorOffsetY); nbt.setFloat("anchorOffsetZ", anchorOffsetZ); + nbt.setBoolean("launchFromAnchor", launchFromAnchor); } public void readNBT(NBTTagCompound nbt) { @@ -95,9 +107,12 @@ public class EnergyAnchorData implements IEnergyAnchorData { this.anchorOffsetX = nbt.getFloat("anchorOffsetX"); this.anchorOffsetY = nbt.getFloat("anchorOffsetY"); this.anchorOffsetZ = nbt.getFloat("anchorOffsetZ"); + this.launchFromAnchor = nbt.getBoolean("launchFromAnchor"); } public EnergyAnchorData copy() { - return new EnergyAnchorData(anchorPoint, anchorOffsetX, anchorOffsetY, anchorOffsetZ); + EnergyAnchorData copy = new EnergyAnchorData(anchorPoint, anchorOffsetX, anchorOffsetY, anchorOffsetZ); + copy.launchFromAnchor = this.launchFromAnchor; + 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 8d8949f07..91cf185b2 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 @@ -787,6 +787,14 @@ public abstract class AbilityEnergyProjectile projectiles[0].anchor.anchorOffsetZ = z; } + public boolean getLaunchFromAnchor() { + return projectiles[0].anchor.launchFromAnchor; + } + + public void setLaunchFromAnchor(boolean launchFromAnchor) { + projectiles[0].anchor.launchFromAnchor = launchFromAnchor; + } + @Override public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); @@ -840,6 +848,14 @@ public abstract class AbilityEnergyProjectile projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; } + public boolean getLaunchFromAnchor(int index) { + return projectiles[clampIndex(index)].anchor.launchFromAnchor; + } + + public void setLaunchFromAnchor(int index, boolean launchFromAnchor) { + projectiles[clampIndex(index)].anchor.launchFromAnchor = launchFromAnchor; + } + // ==================== GUI FIELD DEFINITIONS ==================== @SideOnly(Side.CLIENT) @@ -889,6 +905,9 @@ public abstract class AbilityEnergyProjectile ).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount)); defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ, v -> projectiles[idx].anchor.anchorOffsetZ = v) .tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount).min(Float.NEGATIVE_INFINITY)); + defs.add(FieldDef.boolField("ability.launchFromAnchor", () -> projectiles[idx].anchor.launchFromAnchor, v -> projectiles[idx].anchor.launchFromAnchor = v) + .tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount) + .hover("ability.hover.launchFromAnchor")); // Color override (only when multiple projectiles) defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index fbfc91d79..ea1b64dfb 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -1436,10 +1436,16 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } /** - * 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. + * Determines whether the projectile should snap to the look-vector launch origin on fire. + * By default, player-cast projectiles snap (launch from eye position along crosshair), + * and NPC projectiles keep their anchor charge origin. + * When {@code anchorData.launchFromAnchor} is true, snapping is disabled so the + * projectile launches from its configured anchor position instead. */ protected boolean shouldSnapLaunchToLookVector(EntityLivingBase owner) { + if (anchorData.launchFromAnchor) { + return false; + } return owner instanceof EntityPlayer; } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index bdaa2abdc..4fc5f8aa7 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1866,6 +1866,8 @@ ability.anchorPoint=Anchor Point ability.anchor.offsetX=Offset X ability.anchor.offsetY=Offset Y ability.anchor.offsetZ=Offset Z +ability.launchFromAnchor=Launch From Anchor +ability.hover.launchFromAnchor=When enabled, the projectile launches from its anchor position instead of snapping to the default eye/look-vector position. ability.vertical=Vertical ability.rotationSpeed=Rotation Speed From 40613fa56afd291d9032afa82f434223cd370723 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 04:27:12 -0500 Subject: [PATCH 02/12] Bunch of API Additions - ITintData - IHitboxData - Sound Management APIS - Tactical Chance - Swimming - Fire Reactions - Sprint - Door Interact - Walking Range --- src/api | 2 +- .../npcs/controllers/data/HitboxData.java | 3 +- .../npcs/controllers/data/TintData.java | 3 +- .../npcs/scripted/entity/ScriptNpc.java | 301 ++++++++++++++++++ 4 files changed, 306 insertions(+), 3 deletions(-) diff --git a/src/api b/src/api index 17e883360..f969b9f54 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 17e883360462326c04e3c9651e777346b34d47ec +Subproject commit f969b9f5403836489c815cef025d0a55b821eaf3 diff --git a/src/main/java/noppes/npcs/controllers/data/HitboxData.java b/src/main/java/noppes/npcs/controllers/data/HitboxData.java index 4d2f0263b..4ccf9f823 100644 --- a/src/main/java/noppes/npcs/controllers/data/HitboxData.java +++ b/src/main/java/noppes/npcs/controllers/data/HitboxData.java @@ -1,9 +1,10 @@ package noppes.npcs.controllers.data; import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.api.entity.data.IHitboxData; import noppes.npcs.config.ConfigMain; -public class HitboxData { +public class HitboxData implements IHitboxData { private float widthScale = 1f; private float heightScale = 1f; diff --git a/src/main/java/noppes/npcs/controllers/data/TintData.java b/src/main/java/noppes/npcs/controllers/data/TintData.java index 104600daf..1471d967a 100644 --- a/src/main/java/noppes/npcs/controllers/data/TintData.java +++ b/src/main/java/noppes/npcs/controllers/data/TintData.java @@ -1,8 +1,9 @@ package noppes.npcs.controllers.data; import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.api.entity.data.ITintData; -public class TintData { +public class TintData implements ITintData { private boolean tintEnabled = false; private boolean hurtTintEnabled = true; private boolean generalTintEnabled = false; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java index c362d72c6..27621f125 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java @@ -1,6 +1,7 @@ package noppes.npcs.scripted.entity; import kamkeel.npcs.entity.EntityEnergyProjectile; +import net.minecraft.entity.EnumCreatureAttribute; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import noppes.npcs.NoppesUtilServer; @@ -11,7 +12,9 @@ import noppes.npcs.api.entity.ICustomNpc; import noppes.npcs.api.entity.IEnergyProjectile; import noppes.npcs.api.entity.IEntityLivingBase; import noppes.npcs.api.entity.IPlayer; +import noppes.npcs.api.entity.data.IHitboxData; import noppes.npcs.api.entity.data.IModelData; +import noppes.npcs.api.entity.data.ITintData; import noppes.npcs.api.handler.IActionManager; import noppes.npcs.api.handler.IOverlayHandler; import noppes.npcs.api.handler.data.IAnimationData; @@ -421,6 +424,62 @@ public class ScriptNpc extends ScriptLiving imp return this.npc.advanced.killLines; } + public boolean getOrderedLines() { + return npc.advanced.orderedLines; + } + + public void setOrderedLines(boolean ordered) { + npc.advanced.orderedLines = ordered; + } + + public String getIdleSound() { + return npc.advanced.idleSound; + } + + public void setIdleSound(String sound) { + npc.advanced.idleSound = sound; + } + + public String getAngrySound() { + return npc.advanced.angrySound; + } + + public void setAngrySound(String sound) { + npc.advanced.angrySound = sound; + } + + public String getHurtSound() { + return npc.advanced.hurtSound; + } + + public void setHurtSound(String sound) { + npc.advanced.hurtSound = sound; + } + + public String getDeathSound() { + return npc.advanced.deathSound; + } + + public void setDeathSound(String sound) { + npc.advanced.deathSound = sound; + } + + public String getStepSound() { + return npc.advanced.stepSound; + } + + public void setStepSound(String sound) { + npc.advanced.stepSound = sound; + } + + public boolean getDisablePitch() { + return npc.advanced.disablePitch; + } + + public void setDisablePitch(boolean disablePitch) { + npc.advanced.disablePitch = disablePitch; + } + /** * Kill the npc, doesnt't despawn it */ @@ -848,6 +907,171 @@ public class ScriptNpc extends ScriptLiving imp return npc.ais.tacticalRadius; } + public int getTacticalChance() { + return npc.ais.tacticalChance; + } + + public void setTacticalChance(int chance) { + npc.ais.tacticalChance = chance; + } + + public boolean getCanSwim() { + return npc.ais.canSwim; + } + + public void setCanSwim(boolean canSwim) { + npc.ais.canSwim = canSwim; + } + + public boolean getReactsToFire() { + return npc.ais.reactsToFire; + } + + public void setReactsToFire(boolean reactsToFire) { + npc.ais.reactsToFire = reactsToFire; + } + + public boolean getAvoidsWater() { + return npc.ais.avoidsWater; + } + + public void setAvoidsWater(boolean avoidsWater) { + npc.ais.avoidsWater = avoidsWater; + } + + public boolean getAvoidsSun() { + return npc.ais.avoidsSun; + } + + public void setAvoidsSun(boolean avoidsSun) { + npc.ais.avoidsSun = avoidsSun; + } + + public boolean getDirectLOS() { + return npc.ais.directLOS; + } + + public void setDirectLOS(boolean directLOS) { + npc.ais.directLOS = directLOS; + } + + public int getLeapType() { + return npc.ais.leapType; + } + + public void setLeapType(int leapType) { + npc.ais.leapType = leapType; + } + + public boolean getCanSprint() { + return npc.ais.canSprint; + } + + public void setCanSprint(boolean canSprint) { + npc.ais.canSprint = canSprint; + } + + public boolean getStopAndInteract() { + return npc.ais.stopAndInteract; + } + + public void setStopAndInteract(boolean stopAndInteract) { + npc.ais.stopAndInteract = stopAndInteract; + } + + public int getDoorInteract() { + return npc.ais.doorInteract; + } + + public void setDoorInteract(int doorInteract) { + npc.ais.doorInteract = doorInteract; + } + + public int getWalkingRange() { + return npc.ais.walkingRange; + } + + public void setWalkingRange(int range) { + npc.ais.walkingRange = range; + } + + public boolean getNpcInteracting() { + return npc.ais.npcInteracting; + } + + public void setNpcInteracting(boolean npcInteracting) { + npc.ais.npcInteracting = npcInteracting; + } + + public boolean getMovingPause() { + return npc.ais.movingPause; + } + + public void setMovingPause(boolean movingPause) { + npc.ais.movingPause = movingPause; + } + + public int getMovingPattern() { + return npc.ais.movingPattern; + } + + public void setMovingPattern(int pattern) { + if (pattern < 0) pattern = 0; + if (pattern > 1) pattern = 1; + npc.ais.movingPattern = pattern; + } + + public int getCanFireIndirect() { + return npc.ais.canFireIndirect; + } + + public void setCanFireIndirect(int canFireIndirect) { + npc.ais.canFireIndirect = canFireIndirect; + } + + public int getUseRangeMelee() { + return npc.ais.useRangeMelee; + } + + public void setUseRangeMelee(int useRangeMelee) { + npc.ais.useRangeMelee = useRangeMelee; + } + + public int getDistanceToMelee() { + return npc.ais.distanceToMelee; + } + + public void setDistanceToMelee(int distance) { + npc.ais.distanceToMelee = distance; + } + + public float getBodyOffsetX() { + return npc.ais.bodyOffsetX; + } + + public void setBodyOffsetX(float offsetX) { + npc.ais.bodyOffsetX = offsetX; + npc.script.clientNeedsUpdate = true; + } + + public float getBodyOffsetY() { + return npc.ais.bodyOffsetY; + } + + public void setBodyOffsetY(float offsetY) { + npc.ais.bodyOffsetY = offsetY; + npc.script.clientNeedsUpdate = true; + } + + public float getBodyOffsetZ() { + return npc.ais.bodyOffsetZ; + } + + public void setBodyOffsetZ(float offsetZ) { + npc.ais.bodyOffsetZ = offsetZ; + npc.script.clientNeedsUpdate = true; + } + public void setIgnoreCobweb(boolean ignore) { npc.stats.ignoreCobweb = ignore; } @@ -1152,6 +1376,14 @@ public class ScriptNpc extends ScriptLiving imp return npc instanceof EntityCustomNpc ? ((EntityCustomNpc) npc).modelData : null; } + public IHitboxData getHitboxData() { + return npc.display.hitboxData; + } + + public ITintData getTintData() { + return npc.display.tintData; + } + public void setHeadScale(float x, float y, float z) { if (npc instanceof EntityCustomNpc) { ((EntityCustomNpc) this.npc).modelData.modelScale.head.scaleX = ValueUtil.clamp(x, 0.5f, 1.5f); @@ -1250,6 +1482,66 @@ public class ScriptNpc extends ScriptLiving imp return npc.stats.resistances.knockback; } + public boolean getDamageDisabled() { + return npc.stats.resistances.disableDamage; + } + + public void setDamageDisabled(boolean disabled) { + npc.stats.resistances.disableDamage = disabled; + } + + public boolean getNoFallDamage() { + return npc.stats.noFallDamage; + } + + public void setNoFallDamage(boolean noFallDamage) { + npc.stats.noFallDamage = noFallDamage; + } + + public boolean getImmuneToFire() { + return npc.stats.immuneToFire; + } + + public void setImmuneToFire(boolean immuneToFire) { + npc.stats.immuneToFire = immuneToFire; + npc.setImmuneToFire(immuneToFire); + } + + public boolean getPotionImmune() { + return npc.stats.potionImmune; + } + + public void setPotionImmune(boolean potionImmune) { + npc.stats.potionImmune = potionImmune; + } + + public boolean getBurnInSun() { + return npc.stats.burnInSun; + } + + public void setBurnInSun(boolean burnInSun) { + npc.stats.burnInSun = burnInSun; + } + + public boolean getAttackInvisible() { + return npc.stats.attackInvisible; + } + + public void setAttackInvisible(boolean attackInvisible) { + npc.stats.attackInvisible = attackInvisible; + } + + public int getCreatureType() { + return npc.stats.creatureType.ordinal(); + } + + public void setCreatureType(int type) { + EnumCreatureAttribute[] values = EnumCreatureAttribute.values(); + if (type >= 0 && type < values.length) { + npc.stats.creatureType = values[type]; + } + } + /** * @param type Retaliation type. 0:normal, 1:panic, 2:retreat, 3:nothing */ @@ -1432,6 +1724,15 @@ public class ScriptNpc extends ScriptLiving imp return npc.display.skinOverlayData.overlayList.get(0).getTexture(); } + public String getGlowTexture() { + return npc.display.glowTexture; + } + + public void setGlowTexture(String texture) { + npc.display.glowTexture = texture; + npc.script.clientNeedsUpdate = true; + } + public IOverlayHandler getOverlays() { return npc.display.skinOverlayData; } From d6eccdd556bd433efd871541e77ea8b5a5230a2d Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 04:32:25 -0500 Subject: [PATCH 03/12] Ki Redirection for Barriers, Color Shifting, Better Logic Handling for Built in DBC Ki Abilities --- .../npcs/entity/EntityEnergyBarrier.java | 91 ++++++++++++++ .../kamkeel/npcs/entity/EntityEnergyDome.java | 50 ++++++++ .../npcs/entity/EntityEnergyPanel.java | 57 +++++++++ .../npcs/entity/EntityEnergyProjectile.java | 111 +----------------- 4 files changed, 201 insertions(+), 108 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java index 82781eed7..b16448c33 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -171,6 +171,32 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { double prevPosX, double prevPosY, double prevPosZ, int ownerEntityId); + /** + * Compute the outward surface normal at a given hit point on this barrier. + * Used by external systems (e.g., mixin-based projectile reflection) that cannot + * access barrier-subtype internals. + * + * @param hitX, hitY, hitZ Position near the barrier surface + * @param velX, velY, velZ Incoming velocity (used as fallback direction) + * @return double[3] normalized surface normal pointing outward from the barrier + */ + public abstract double[] getSurfaceNormal(double hitX, double hitY, double hitZ, + double velX, double velY, double velZ); + + /** + * Compute a position just outside the barrier surface, along the outward normal + * from the given position. Used to snap paused/reflected projectiles outside + * the collision zone. + * + * @param px, py, pz Current projectile position + * @param velX, velY, velZ Current velocity (used as fallback for direction) + * @param bias Distance outside the surface to snap to + * @return double[3] position outside the barrier surface + */ + public abstract double[] getOutsideSurfacePoint(double px, double py, double pz, + double velX, double velY, double velZ, + float bias); + // ==================== GENERIC PROJECTILE HIT ==================== /** @@ -209,6 +235,71 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { return true; } + /** + * Apply damage from a generic (non-CNPC+) projectile and return full hit context. + * Same logic as onProjectileHitResolved() but for raw Entity + typeId. + * + * @param projectileEntity The entity that hit the barrier (for event context) + * @param damage Base damage to apply + * @param typeId Type identifier for damage multiplier lookup (e.g., "dbc.ki_attack") + * @return Full ProjectileHitOutcome with result, reflect data, remaining damage + */ + public ProjectileHitOutcome onGenericProjectileHitResolved(Entity projectileEntity, float damage, String typeId) { + float multiplier = barrierData.getMultiplier(typeId); + float finalDamage = damage * multiplier; + + if (!worldObj.isRemote) { + float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, finalDamage); + if (eventDamage < 0) { + return new ProjectileHitOutcome( + ProjectileHitResult.PASS, + currentHealth, currentHealth, + 0.0f, 0.0f, + barrierData.reflect, barrierData.reflectStrengthPct, + barrierData.useHealth + ); + } + finalDamage = eventDamage; + } + + triggerHitFlash(); + float healthBefore = currentHealth; + + if (!barrierData.useHealth) { + return new ProjectileHitOutcome( + ProjectileHitResult.BLOCKED, + healthBefore, healthBefore, + 0.0f, 0.0f, + barrierData.reflect, barrierData.reflectStrengthPct, + false + ); + } + + currentHealth -= finalDamage; + syncHealthPercent(); + + if (currentHealth <= 0) { + float remaining = Math.max(0.0f, finalDamage - healthBefore); + onBarrierDestroyed(); + this.setDead(); + return new ProjectileHitOutcome( + ProjectileHitResult.BROKEN, + healthBefore, 0.0f, + finalDamage, remaining, + barrierData.reflect, barrierData.reflectStrengthPct, + true + ); + } + + return new ProjectileHitOutcome( + ProjectileHitResult.BLOCKED, + healthBefore, currentHealth, + finalDamage, 0.0f, + barrierData.reflect, barrierData.reflectStrengthPct, + true + ); + } + // ==================== PROJECTILE HIT ==================== /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 0e8f0bf56..535d1cdff 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -213,6 +213,56 @@ public class EntityEnergyDome extends EntityEnergyBarrier { return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); } + @Override + public double[] getSurfaceNormal(double hitX, double hitY, double hitZ, + double velX, double velY, double velZ) { + double nx = hitX - this.posX; + double ny = hitY - this.posY; + double nz = hitZ - this.posZ; + double lenSq = nx * nx + ny * ny + nz * nz; + if (lenSq > 1.0e-8) { + double invLen = 1.0 / Math.sqrt(lenSq); + return new double[]{nx * invLen, ny * invLen, nz * invLen}; + } + // Fallback: use negative velocity direction + double vLenSq = velX * velX + velY * velY + velZ * velZ; + if (vLenSq > 1.0e-8) { + double invVLen = 1.0 / Math.sqrt(vLenSq); + return new double[]{-velX * invVLen, -velY * invVLen, -velZ * invVLen}; + } + return new double[]{0.0, 1.0, 0.0}; + } + + @Override + public double[] getOutsideSurfacePoint(double px, double py, double pz, + double velX, double velY, double velZ, + float bias) { + double nx = px - this.posX; + double ny = py - this.posY; + double nz = pz - this.posZ; + double len = Math.sqrt(nx * nx + ny * ny + nz * nz); + + if (len < 1.0e-5) { + double vLen = Math.sqrt(velX * velX + velY * velY + velZ * velZ); + if (vLen > 1.0e-5) { + nx = -velX / vLen; + ny = -velY / vLen; + nz = -velZ / vLen; + } else { + nx = 0.0; ny = 1.0; nz = 0.0; + } + len = 1.0; + } + + double invLen = 1.0 / len; + double target = domeRadius + bias; + return new double[]{ + this.posX + nx * invLen * target, + this.posY + ny * invLen * target, + this.posZ + nz * invLen * target + }; + } + @Override public float getMaxExtent() { return domeRadius; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index ad25afa0b..8ed770f8a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -362,6 +362,63 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); } + @Override + public double[] getSurfaceNormal(double hitX, double hitY, double hitZ, + double velX, double velY, double velZ) { + float yawRad = (float) Math.toRadians(panelYaw); + double nx = -Math.sin(yawRad); + double nz = Math.cos(yawRad); + + // Determine which side the hit is on + double relX = hitX - this.posX; + double relZ = hitZ - this.posZ; + double side = relX * nx + relZ * nz; + if (Math.abs(side) < 1.0e-5) { + // Near the plane: use incoming velocity to determine side + side = velX * nx + velZ * nz; + } + if (side < 0.0) { + nx = -nx; + nz = -nz; + } + + return new double[]{nx, 0.0, nz}; + } + + @Override + public double[] getOutsideSurfacePoint(double px, double py, double pz, + double velX, double velY, double velZ, + float bias) { + float yawRad = (float) Math.toRadians(panelYaw); + double normalX = -Math.sin(yawRad); + double normalZ = Math.cos(yawRad); + + double relX = px - this.posX; + double relZ = pz - this.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 { + double vDot = velX * normalX + velZ * 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; + return new double[]{ + px + normalX * delta, + py, + pz + normalZ * delta + }; + } + @Override public float getMaxExtent() { return Math.max(panelData.panelWidth, panelData.panelHeight) * 0.5f + 1.0f; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index ea1b64dfb..bba50a69d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -681,47 +681,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } protected double[] getBarrierImpactNormal(EntityEnergyBarrier barrier, double velocityX, double velocityY, double velocityZ) { - if (barrier instanceof EntityEnergyDome) { - EntityEnergyDome dome = (EntityEnergyDome) barrier; - return normalizeVector(posX - dome.posX, posY - dome.posY, posZ - dome.posZ, velocityX, velocityY, velocityZ); - } - - if (barrier instanceof EntityEnergyPanel) { - EntityEnergyPanel panel = (EntityEnergyPanel) 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}; + return barrier.getSurfaceNormal(posX, posY, posZ, velocityX, velocityY, velocityZ); } protected void beginBarrierImpactPause(boolean destroyOnResume, EntityEnergyBarrier barrier) { @@ -752,73 +712,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (barrier == null) return; float bias = getBarrierPauseOutsideDistance(); - if (barrier instanceof EntityEnergyDome) { - EntityEnergyDome dome = (EntityEnergyDome) 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 EntityEnergyPanel) { - EntityEnergyPanel panel = (EntityEnergyPanel) 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 - ); - } + double[] point = barrier.getOutsideSurfacePoint(posX, posY, posZ, motionX, motionY, motionZ, bias); + setPosition(point[0], point[1], point[2]); } protected boolean tickBarrierImpactPause() { From a479a9d9418cb5df59206795e7f7fd89763ba610 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 04:44:42 -0500 Subject: [PATCH 04/12] Fix Charging on Staff Resetting --- src/main/java/noppes/npcs/items/ItemStaff.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/noppes/npcs/items/ItemStaff.java b/src/main/java/noppes/npcs/items/ItemStaff.java index 5dc674b30..39de82e37 100644 --- a/src/main/java/noppes/npcs/items/ItemStaff.java +++ b/src/main/java/noppes/npcs/items/ItemStaff.java @@ -84,13 +84,27 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback { public void onUsingTick(ItemStack stack, EntityPlayer player, int count) { int tick = getMaxItemUseDuration(stack) - count; + int chargeTime = 20 + material.getHarvestLevel() * 8; + if (player.worldObj.isRemote) { spawnParticle(stack, player); + + // Reset charge tick on client to prevent client-side timeout + // (resetChargeTick is not synced via DataWatcher, so the client's + // chargeTick would keep climbing and falsely trigger CHARGE_TIMEOUT_GRACE) + if (tick > chargeTime && stack.stackTagCompound != null) { + Entity existing = player.worldObj + .getEntityByID(stack.stackTagCompound.getInteger("MagicProjectile")); + if (existing instanceof EntityAbilityOrb) { + EntityAbilityOrb orb = (EntityAbilityOrb) existing; + if (orb.isCharging()) { + orb.resetChargeTick(); + } + } + } return; } - int chargeTime = 20 + material.getHarvestLevel() * 8; - // Keep a fully-charged orb alive while the player is still holding if (tick > chargeTime && stack.stackTagCompound != null) { Entity existing = ((WorldServer) player.worldObj) From cf3ba63d012bd8394687b4de3c657bd5fab420aa Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 04:59:27 -0500 Subject: [PATCH 05/12] Add Ability Tab Icon --- .../assets/customnpcs/textures/gui/icons.png | Bin 11934 -> 5909 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/assets/customnpcs/textures/gui/icons.png b/src/main/resources/assets/customnpcs/textures/gui/icons.png index 6f2993cd1eeadf33e09f0723f3f0746d1ff045c6..90c64282952629219ec415137c34a3455a689875 100644 GIT binary patch literal 5909 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 MdfvjQ(f}LvFN$XuIRF3v literal 11934 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 GM*I(C=F0Z~ From de312aaccca7abcc44b611f8e247cb9c8b681778 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 05:00:12 -0500 Subject: [PATCH 06/12] Fix Orb Icon --- .../data/ability/type/energy/AbilityOrb.java | 3 +-- .../customnpcs/textures/gui/ability/orb.png | Bin 449 -> 446 bytes .../textures/gui/ability/orb_overlay.png | Bin 614 -> 0 bytes 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 src/main/resources/assets/customnpcs/textures/gui/ability/orb_overlay.png 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 7ef631dda..2d9b162e5 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 @@ -51,8 +51,7 @@ public class AbilityOrb extends AbilityEnergyProjectile implem this.activeAnimationName = "Ability_Orb_Active"; this.defaultIconLayers = new DefaultIconLayer[]{ - new DefaultIconLayer("customnpcs:textures/gui/ability/orb.png"), - new DefaultIconLayer("customnpcs:textures/gui/ability/orb_overlay.png", + new DefaultIconLayer("customnpcs:textures/gui/ability/orb.png", () -> isOuterColorEnabled() ? getOuterColor() : getInnerColor()) }; } diff --git a/src/main/resources/assets/customnpcs/textures/gui/ability/orb.png b/src/main/resources/assets/customnpcs/textures/gui/ability/orb.png index c6bf9991db26e37073b0fe178713784a998143d2..3b60cff14109799c6228807d8e65bb6dab86f19e 100644 GIT binary patch delta 407 zcmV;I0cifg1HJ>0F@JbTL_t(&f$f(;Zp0uAMSt28utG{U5vSrf?QJ+v)`%4n>l`56 z6y#7Q9bwukmER`F<1qi*Fav-f2!bF8f^b`=pP1BId$i)tx#8b8biyp$7=v|PPoISl z;JpV18yPZDOSm!Sl#|7eHo~63T6r?pRd=r)k6Pszfm;7pFk7*BCy z%)?);XJB`Lny7_qt-s{coDp50yWgDt{)W^e~T_t2c8mLkv7ChMN2H4$vb3Dif_P&{&MIJh_cLNksDF5^HIz~SfMFQ0EKB~R zD5ap3!n&^Lx(?$we$4ayS-a<3O9KG1_#_+vLI`<13`1Vi9PXUd(9A%pJ|zjKf)gno zLfFK$)(?NRIGT5Wnka?$eg7k$=DgB{CI-rb%aO$Qea{-99O*){4$#?0QCUtfjh*R2 z^ARd}R_z3LPJe2+!~hi&V|7SM}LgRG)k6!4SA7a`C zDm<+@Lm`Bhs*nG0ot6j99=Cl`mR$L$7X(2Ngj@3kM-#j8z|elRh5!Hn07*qoM6N<$ Eg5u@Dv;Y7A diff --git a/src/main/resources/assets/customnpcs/textures/gui/ability/orb_overlay.png b/src/main/resources/assets/customnpcs/textures/gui/ability/orb_overlay.png deleted file mode 100644 index d92a7361fd233db1dc74aa944a695e4492f168f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmV-s0-61ZP)Px%AW1|)RA_I4PN&bya^z?=31B%Xu=0C?}`=dSDKXFf;DlM-Ybpxx(?a4wjjaqs=ZI3m8R zT5C{8fSZVgw{810pU>GN3RwvipM6POEIx!VKab-mu3I9ZKXNB(#M9U?3;>BfK}Tte zk!%C6ki<*ErE=T%{bnPp4Dj6#Av~BEhG9{7JkE`1O{kL?eyWcI7mtWf<-` ztx(wp_)mh^1W(wb`$U!j=Ul_ZOVI9fj_4DrBb15)7r;ew_UIGY2Bav^4vyov_~j(N zZClX@M-v5g67wkV2(0N7(p+s6R0b#tWMU~VVxcLdJjp>F0WxtupPzC<62mK03^(_x zG%vBLkx{d;)=rWEUN%#3b5cdhVO1j|oO2DC$?X%;nD>63Pe(MDO$LaZJ`qBgMW>Dx z{Fuo7Nm0ZAk<%w)@qCVU_o1YLRG*NH@Nu3m+CTn9BLIm$LC=yQ@BOOXhf+6loTIN@ zcDXS-^5SdPyUQc49(TWdgtyC(NO65=U4Y^!}R|ciMcmMzZ07*qoM6N<$g5Qf0 A7XSbN From eacb3f8c5fa3dd5b9ecf911836c484434a1ad427 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 15:06:25 -0500 Subject: [PATCH 07/12] Update icons.png --- .../assets/customnpcs/textures/gui/icons.png | Bin 5909 -> 5909 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/assets/customnpcs/textures/gui/icons.png b/src/main/resources/assets/customnpcs/textures/gui/icons.png index 90c64282952629219ec415137c34a3455a689875..261b5a9540283b41d9431fa4c48b3a4ae3c30e63 100644 GIT binary patch delta 82 zcmV-Y0ImO(E|o5jMk?}Mwk|C+B4apc4GsT+%^X0Bm6VxdVrVjvQZI*09aJx|J3DsB o>;Jamk#Hmf{@Cxamy7`nGFy6}0001=bW%=JlQ9CEv&#ZX6o)<`)&Kwi delta 82 zcmV-Y0ImO(E|o5jMk@c*11>EzB4apc4GsU|R2)Eym6VxdVrVjvQZI*09aJx|J3DsB o>;LYVk#Hmf|M$(Ymy7`nx**cB0001=bW%=JlQ9CEv&#ZX6oE=2GXMYp From b406382564964e19d49257ede42bc6e17a57b71c Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 15:21:47 -0500 Subject: [PATCH 08/12] Fix Counter Damage for DBC / CNPC+ --- .../java/kamkeel/npcs/addon/DBCAddon.java | 9 +++ .../data/ability/type/AbilityCounter.java | 28 ++++---- .../data/ability/type/AbilityDefend.java | 65 +++++++++++++------ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/main/java/kamkeel/npcs/addon/DBCAddon.java b/src/main/java/kamkeel/npcs/addon/DBCAddon.java index ff290e9d2..08c493a3c 100644 --- a/src/main/java/kamkeel/npcs/addon/DBCAddon.java +++ b/src/main/java/kamkeel/npcs/addon/DBCAddon.java @@ -37,6 +37,15 @@ public class DBCAddon { public void doDBCDamage(EntityNPCInterface npc, float attackStrength, Entity receiver) { } + /** + * Returns the pre-calculated DBC attack damage of the current attacker, + * or the vanilla damage if DBC damage was not calculated. + * Used by Counter (PERCENT mode) to base counter damage on the full DBC attack, not vanilla base. + */ + public float getAttackerDBCDamage(float vanillaDamage) { + return vanillaDamage; + } + public boolean isKO(EntityNPCInterface npc, EntityPlayer player) { return false; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java index f0d5ab4b6..b13d78ce8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java @@ -9,10 +9,7 @@ import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.DamageSource; -import noppes.npcs.NpcDamageSource; import noppes.npcs.controllers.data.Animation; -import noppes.npcs.entity.EntityNPCInterface; import noppes.npcs.api.ability.type.IAbilityCounter; import noppes.npcs.client.gui.builder.FieldDef; @@ -66,27 +63,24 @@ public class AbilityCounter extends AbilityDefend implements IAbilityCounter { @Override protected float performDefend(EntityLivingBase attacker, float amount) { + // For PERCENT mode with DBC, use the full DBC-calculated damage, not vanilla base + float incomingDamage = amount; + if (DBCAddon.IsAvailable() && attacker instanceof EntityPlayer) { + incomingDamage = DBCAddon.instance.getAttackerDBCDamage(amount); + } + // Calculate counter damage float damage; if (counterType == CounterType.FLAT) { damage = counterValue; } else { - damage = amount * (counterValue / 100.0f); + damage = incomingDamage * (counterValue / 100.0f); } - // Hit back - if (caster != null && attacker.isEntityAlive()) { - if (caster instanceof EntityNPCInterface && DBCAddon.instance.canDBCAttack((EntityNPCInterface) caster, damage, attacker)) { - // NPC with DBC: dummy hit for events/knockback, then apply DBC damage with NPC's stats - attacker.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), 0.001f); - DBCAddon.instance.doDBCDamage((EntityNPCInterface) caster, damage, attacker); - } else { - // Player or non-DBC NPC: normal damage - DamageSource counterSource = caster instanceof EntityPlayer - ? DamageSource.causePlayerDamage((EntityPlayer) caster) - : DamageSource.causeMobDamage(caster); - attacker.attackEntityFrom(counterSource, damage); - } + // Counter-attack through the standard ability damage pipeline. + // DBCAbilityExtender handles DBC stat routing (ignore flags, scaling, etc.) automatically. + if (caster != null && attacker.isEntityAlive() && damage > 0) { + applyAbilityDamage(caster, attacker, damage, 0); } return 0; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java index c9f6e1e0a..7ca8aa3e7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java @@ -41,6 +41,10 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { protected transient float lastDamageTaken; protected transient Animation pendingDefendAnimation; protected transient int defendAnimEndTick = -1; + /** Prevents double-counting hits when multiple damage paths call onDefend for the same hit in a single tick. */ + protected transient long lastDefendTick = -1; + /** Defers signalCompletion to the next tick so all damage paths for the last hit still see isDefending()=true. */ + protected transient boolean pendingCompletion; // ═══════════════════════════════════════════════════════════════════ // CONSTRUCTOR DEFAULTS @@ -69,7 +73,7 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - if (tick >= durationTicks) { + if (pendingCompletion || tick >= durationTicks) { signalCompletion(); } } @@ -83,6 +87,8 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { lastDamageTaken = 0.0f; pendingDefendAnimation = null; defendAnimEndTick = -1; + lastDefendTick = -1; + pendingCompletion = false; } // ═══════════════════════════════════════════════════════════════════ @@ -99,30 +105,47 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { * @return The modified damage to apply (unchanged if not physical melee) */ public final float onDefend(EntityLivingBase attacker, DamageSource source, float amount) { - if (!isDefending()) + if (!isDefending()) { return amount; - if (attacker == null || source == null) + } + if (attacker == null || source == null) { return amount; - - // Only react to physical melee damage - if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion() || source.isProjectile()) - return amount; - - lastAttacker = attacker; - lastDamageTaken = amount; - hitCount++; - - float result = performDefend(attacker, amount); - - // Queue defend reaction animation (played on next tick by AbstractDataAbilities) - pendingDefendAnimation = getDefendAnimation(); - - // Auto-complete after max hits - if (maxHitAmount > 0 && hitCount >= maxHitAmount) { - signalCompletion(); } - return result; + // Only react to physical melee damage + if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion() || source.isProjectile()) { + return amount; + } + + // Deduplicate per tick: when multiple damage paths (e.g. vanilla + DBC) call onDefend + // for the same hit, only count it once for hitCount / signalCompletion / animation. + long currentTick = (caster != null && caster.worldObj != null) + ? caster.worldObj.getTotalWorldTime() : -1; + boolean firstCallThisTick = (currentTick < 0 || currentTick != lastDefendTick); + + // If max hits reached on a previous tick and this is a NEW hit, reject it + if (pendingCompletion && firstCallThisTick) { + return amount; + } + + if (firstCallThisTick) { + lastDefendTick = currentTick; + lastAttacker = attacker; + lastDamageTaken = amount; + hitCount++; + + // Queue defend reaction animation (played on next tick by AbstractDataAbilities) + pendingDefendAnimation = getDefendAnimation(); + + // Defer completion to next tick so all damage paths in this tick + // still see isDefending()=true and apply guard reduction. + if (maxHitAmount > 0 && hitCount >= maxHitAmount) { + pendingCompletion = true; + } + } + + // Always calculate the correct reduction for the caller's damage amount + return performDefend(attacker, amount); } /** From 6eeeddad3f607788961e9b9c3c7a789282b2bfd2 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 15:34:29 -0500 Subject: [PATCH 09/12] Sort Ordering for Concurrent and Custom --- .../client/gui/advanced/GuiNPCAbilities.java | 23 ++++++++++++++++--- .../gui/advanced/SubGuiAbilityConfig.java | 2 +- .../gui/advanced/SubGuiAbilityTypeSelect.java | 2 +- .../advanced/SubGuiChainedAbilityConfig.java | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) 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 b1e58d94a..295716a92 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -649,9 +649,11 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Search matches either the display name or the typeId (strip color codes for matching) String stripped = displayName.replaceAll("\u00A7.", ""); if (search.isEmpty() || stripped.toLowerCase().contains(search) || typeId.toLowerCase().contains(search)) { - // Lime green for concurrent-capable types, gray for other built-in types - if (AbilityController.Instance.isConcurrentCapableType(typeId)) { - displayName = "\u00A7a" + displayName; + // Pink for custom ability, light blue for concurrent-capable types, gray for other built-in types + if (typeId.equals("ability.cnpc.custom")) { + displayName = "\u00A7d" + displayName; + } else if (AbilityController.Instance.isConcurrentCapableType(typeId)) { + displayName = "\u00A7b" + displayName; } else if (AbilityController.Instance.isBuiltInType(typeId)) { displayName = "\u00A77" + displayName; } @@ -661,6 +663,21 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } Collections.sort(list, (a, b) -> String.CASE_INSENSITIVE_ORDER.compare( a.replaceAll("\u00A7.", ""), b.replaceAll("\u00A7.", ""))); + + // Pin Custom Ability to top of list + String customEntry = null; + for (String name : list) { + String tid = displayNameToTypeId.get(name); + if (tid != null && tid.equals("ability.cnpc.custom")) { + customEntry = name; + break; + } + } + if (customEntry != null) { + list.remove(customEntry); + list.add(0, customEntry); + } + return list; } 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 46447712b..5b56d0f14 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -189,7 +189,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } if (ability.isConcurrentCapable()) { sw.addLabel(new GuiNpcLabel(baseId + 3, - StatCollector.translateToLocal("ability.concurrentInfo"), 5, infoY, 0x55FF55)); + StatCollector.translateToLocal("ability.concurrentInfo"), 5, infoY, 0x55FFFF)); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java index 7d9c6cc32..46975f289 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java @@ -86,7 +86,7 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS if (typeId.equals("ability.cnpc.custom")) { displayName = "\u00A7d" + displayName; } else if (AbilityController.Instance.isConcurrentCapableType(typeId)) { - displayName = "\u00A7a" + displayName; + displayName = "\u00A7b" + displayName; } allDisplayNameToTypeId.put(displayName, typeId); } 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 99af06e1c..1fc123d37 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -402,7 +402,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // Concurrent toggle (only shown if resolved ability is concurrent-capable, never for first entry) if (i > 0 && resolvedAbility != null && resolvedAbility.isConcurrentCapable()) { - String cLabel = entry.isConcurrentEnabled() ? "\u00A7aC" : "\u00A77C"; + String cLabel = entry.isConcurrentEnabled() ? "\u00A7bC" : "\u00A77C"; GuiNpcButton cBtn = new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 5, L_LABEL_X + 288, y, 20, 20, cLabel); cBtn.setHoverText("ability.hover.concurrent"); sw.addButton(cBtn); From edb9686703749b511e535b6a4591f4edaa0afa55 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 15:49:04 -0500 Subject: [PATCH 10/12] Cancel Only during Windup --- src/main/java/noppes/npcs/AbstractDataAbilities.java | 7 ++----- .../noppes/npcs/controllers/data/PlayerAbilityData.java | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 3f44394f3..3f041ecdc 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -1113,11 +1113,8 @@ public abstract class AbstractDataAbilities { 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; + // Only allow cancel during WINDUP phase + if (phase != AbilityPhase.WINDUP) return; // Remove telegraph removeTelegraph(currentAbility); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 600926785..8cfaaa721 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -610,12 +610,10 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (!hasExecutingAbility && !isInChainDelay) return false; - // Don't allow cancel during DAZED + // Only allow cancel during WINDUP phase 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; + if (phase != AbilityPhase.WINDUP) return false; } long worldTime = getWorldTime(); From 0ad15e674a540427baea4065cbac8524028971de Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 15:55:22 -0500 Subject: [PATCH 11/12] Clear Ability on Teleport --- .../controllers/data/PlayerAbilityData.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 8cfaaa721..21e989644 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -96,6 +96,21 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA */ private static final int CANCEL_WINDOW_TICKS = 10; + /** + * Position tracking for teleport detection during ability execution. + * If the player moves more than {@link #TELEPORT_THRESHOLD_SQ} in a single tick, + * the ability is interrupted as if the player changed dimensions. + */ + private transient double lastAbilityTickX, lastAbilityTickY, lastAbilityTickZ; + private transient boolean trackingAbilityPosition = false; + + /** + * Squared distance threshold for teleport detection (48 blocks). + * Normal sprint speed is ~5.6 blocks/tick; this threshold is well above + * any legitimate per-tick movement including ability-driven dashes. + */ + private static final double TELEPORT_THRESHOLD_SQ = 48.0 * 48.0; + // ═══════════════════════════════════════════════════════════════════ // CONSTRUCTOR // ═══════════════════════════════════════════════════════════════════ @@ -257,7 +272,30 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Tick active toggles (independent of currentAbility) tickActiveToggles(); - if (chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting())) { + boolean hasActiveAbility = chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting()); + + if (hasActiveAbility) { + // Detect large position jump (teleport) while ability is executing. + // Must run BEFORE tickCurrentAbility / applyPositionLock so the raw + // teleported position is visible before any lock snaps it back. + if (trackingAbilityPosition) { + double dx = player.posX - lastAbilityTickX; + double dy = player.posY - lastAbilityTickY; + double dz = player.posZ - lastAbilityTickZ; + if (dx * dx + dy * dy + dz * dz > TELEPORT_THRESHOLD_SQ) { + // Player was teleported — clear ability state (same as dimension change) + trackingAbilityPosition = false; + resetOnTeleport(); + return; + } + } + + // Update tracked position for next tick's comparison + lastAbilityTickX = player.posX; + lastAbilityTickY = player.posY; + lastAbilityTickZ = player.posZ; + trackingAbilityPosition = true; + tickCurrentAbility(); // Apply rotation and position locks after ability tick @@ -267,6 +305,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Sync lock state to client (only sends when flags change) syncAbilityStateIfNeeded(player); } else { + trackingAbilityPosition = false; + // Safety: release orphaned locks if no ability is executing if (rotationLocked || positionLocked) { releaseRotationControl(); @@ -696,6 +736,15 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA * Keeps active toggles (dimension change shouldn't reset ongoing effects). */ public void resetOnDimensionChange() { + trackingAbilityPosition = false; + onEntityReconstructed(true, false); + } + + /** + * Reset ability state when a large-distance teleport is detected during execution. + * Same behavior as dimension change — silent cleanup, clear cooldowns, keep toggles. + */ + public void resetOnTeleport() { onEntityReconstructed(true, false); } @@ -717,6 +766,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA * The caller must trigger a full ability sync afterwards (e.g. via syncToClient). */ public void resetOnLogin() { + trackingAbilityPosition = false; + // Silently clean up any stale executing ability if (currentAbility != null && currentAbility.isExecuting()) { removeTelegraph(currentAbility); From 61d08882a9987410ff9c3d3a66b04742bdd84935 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 16:19:50 -0500 Subject: [PATCH 12/12] Lots of Fixes --- .../npcs/controllers/AbilityController.java | 10 ++++++---- .../controllers/data/ability/Ability.java | 4 ++-- .../data/ability/AbilityAction.java | 1 + .../data/ability/CustomAbility.java | 2 +- .../ability/conditions/ConditionHitCount.java | 4 ++-- .../ability/conditions/ConditionItem.java | 2 +- .../conditions/ConditionQuestCompleted.java | 4 +++- .../data/ability/data/ChainedAbility.java | 20 +++++++++++++++---- .../npcs/entity/EntityAbilityBeam.java | 1 - .../npcs/entity/EntityAbilityLaser.java | 6 ++---- .../npcs/entity/EntityAbilityZone.java | 3 ++- .../npcs/entity/EntityEnergyProjectile.java | 2 -- .../data/ability/AbilityHotbarSyncPacket.java | 4 ++++ .../ability/AbilityHotbarSavePacket.java | 5 +++++ .../ability/CustomAbilitySavePacket.java | 1 + .../request/script/AbilityScriptPacket.java | 4 +++- .../script/ChainedAbilityScriptPacket.java | 4 +++- .../gui/advanced/SubGuiAbilityConfig.java | 2 +- .../npcs/client/gui/builder/FieldDef.java | 2 +- .../npcs/scripted/entity/ScriptEnergyOrb.java | 1 + 20 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 7acfc96a9..7840621f7 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -395,7 +395,8 @@ public class AbilityController implements IAbilityHandler { // Clean up online players' unlocked ability lists if (PlayerDataController.Instance != null) { - for (PlayerData pData : PlayerDataController.Instance.getAllPlayerData()) { + List snapshot = new ArrayList<>(PlayerDataController.Instance.getAllPlayerData()); + for (PlayerData pData : snapshot) { if (pData.abilityData != null) { boolean changed = false; if (uuid != null && !uuid.isEmpty() && pData.abilityData.hasUnlockedAbility(uuid)) { @@ -437,7 +438,7 @@ public class AbilityController implements IAbilityHandler { return customAbilities; } - public void setCustomAbilities(Map synced) { + public synchronized void setCustomAbilities(Map synced) { customAbilities.clear(); customAbilitiesById.clear(); customAbilities.putAll(synced); @@ -613,7 +614,8 @@ public class AbilityController implements IAbilityHandler { // Clean up online players' unlocked ability lists (chain keys use "chain:" prefix) if (PlayerDataController.Instance != null) { - for (PlayerData pData : PlayerDataController.Instance.getAllPlayerData()) { + List snapshot = new ArrayList<>(PlayerDataController.Instance.getAllPlayerData()); + for (PlayerData pData : snapshot) { if (pData.abilityData != null) { String chainKey = "chain:" + (uuid != null && !uuid.isEmpty() ? uuid : name); if (pData.abilityData.hasUnlockedAbility(chainKey)) { @@ -722,7 +724,7 @@ public class AbilityController implements IAbilityHandler { return chainedAbilityRevision; } - public void setChainedAbilities(Map synced) { + public synchronized void setChainedAbilities(Map synced) { chainedAbilities.clear(); chainedAbilitiesById.clear(); chainedAbilities.putAll(synced); 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 0b78a5b2f..70e286298 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -1531,7 +1531,7 @@ public abstract class Ability implements IAbility, IAbilityAction { dazedAnimationId = nbt.hasKey("dazedAnimationId") ? nbt.getInteger("dazedAnimationId") : -1; windUpAnimationName = nbt.getString("windUpAnimationName"); activeAnimationName = nbt.getString("activeAnimationName"); - dazedAnimationName = nbt.getString("dazedAnimationName"); + dazedAnimationName = nbt.hasKey("dazedAnimationName") ? nbt.getString("dazedAnimationName") : "Ability_Generic_Dazed"; showTelegraph = nbt.hasKey("showTelegraph") ? nbt.getBoolean("showTelegraph") : true; try { telegraphType = TelegraphType.valueOf(nbt.getString("telegraphType")); @@ -2356,7 +2356,7 @@ public abstract class Ability implements IAbility, IAbilityAction { } // ═══════════════════════════════════════════════════════════════════ - // SHIT FROM THE ASS + // WINDUP DURATION CALCULATION // ═══════════════════════════════════════════════════════════════════ private int calculateWindupFromAnimation() { 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 571b2fd35..5c156a056 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -308,6 +308,7 @@ public class AbilityAction { } // Inline ability + if (AbilityController.Instance == null) return null; 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/CustomAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/CustomAbility.java index c59eb9765..045a12e5d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/CustomAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/CustomAbility.java @@ -126,7 +126,7 @@ public class CustomAbility extends Ability implements ICustomAbility { @Override public void readTypeNBT(NBTTagCompound nbt) { - this.durationTicks = nbt.getInteger("durationTicks"); + this.durationTicks = Math.max(0, nbt.getInteger("durationTicks")); } // ==================== GUI ==================== 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 5d02d51a7..f94d465e1 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 @@ -76,7 +76,7 @@ public class ConditionHitCount extends AbilityCondition { @Override public void readTypeNBT(NBTTagCompound nbt) { - requiredHits = nbt.getInteger("requiredHits"); - withinTicks = nbt.getInteger("withinTicks"); + requiredHits = Math.max(0, nbt.getInteger("requiredHits")); + withinTicks = Math.max(0, nbt.getInteger("withinTicks")); } } 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 552bb5de9..221a68f63 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 @@ -174,7 +174,7 @@ public class ConditionItem extends AbilityCondition { itemName = nbt.getString("itemName"); usageType = UsageType.fromOrdinal(nbt.getInteger("usageType")); armorSlot = ArmorSlot.fromOrdinal(nbt.getInteger("armorSlot")); - requiredCount = nbt.getInteger("requiredCount"); + requiredCount = Math.max(1, nbt.getInteger("requiredCount")); } 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 263cd28af..2955b7610 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 @@ -30,7 +30,9 @@ public class ConditionQuestCompleted extends AbilityCondition { if (questId < 0) return false; EntityPlayer player = (EntityPlayer) entity; - return PlayerData.get(player).questData.hasFinishedQuest(questId); + PlayerData data = PlayerData.get(player); + if (data == null || data.questData == null) return false; + return data.questData.hasFinishedQuest(questId); } @SideOnly(Side.CLIENT) 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 e3aa63346..cfc90d38f 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 @@ -289,9 +289,15 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { */ public boolean checkConditions(EntityLivingBase caster, EntityLivingBase target) { for (AbilityCondition c : conditions) { - if (!c.check(caster, target)) { - return false; + if (!c.getUserType().allowsNpc()) continue; + Boolean extendedCondition = AbilityController.Instance.fireCheckConditions(c, caster, target); + + if (extendedCondition != null) { + if (!extendedCondition) return false; + continue; } + + if (!c.check(caster, target)) return false; } return true; } @@ -301,10 +307,16 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { */ public boolean checkConditionsForPlayer(EntityLivingBase caster) { for (AbilityCondition c : conditions) { + if (!c.getUserType().allowsPlayer()) continue; if (c.requiresTarget()) continue; - if (!c.check(caster, null)) { - return false; + Boolean extendedCondition = AbilityController.Instance.fireCheckConditionsForPlayer(c, caster); + + if (extendedCondition != null) { + if (!extendedCondition) return false; + continue; } + + if (!c.check(caster, null)) return false; } return true; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 15b1e5cbf..7744fcd27 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -809,7 +809,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { nbt.setDouble("HeadOffsetY", headOffsetY); nbt.setDouble("HeadOffsetZ", headOffsetZ); nbt.setBoolean("AttachedToOwner", isAnchoredMode()); - nbt.setBoolean("RenderTailOrb", isAnchoredMode()); nbt.setString("BeamMode", beamMode.name()); writeChargingNBT(nbt); nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 4581340a9..e84e0df00 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -779,10 +779,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { @Override protected void readProjectileNBT(NBTTagCompound nbt) { this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE); - this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f; - if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f; - this.maxLength = nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f; - if (Float.isNaN(maxLength) || Float.isInfinite(maxLength) || maxLength <= 0) maxLength = 32.0f; + this.expansionSpeed = sanitize(nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f, 0.1f, MAX_ENTITY_SIZE); + this.maxLength = sanitize(nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f, 1.0f, MAX_ENTITY_SIZE); this.dirX = nbt.getDouble("DirX"); this.dirY = nbt.getDouble("DirY"); this.dirZ = nbt.getDouble("DirZ"); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index a3c034fcb..4d83945e4 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -790,7 +790,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD this.shape = ZoneShape.CIRCLE; } this.ownerEntityId = buffer.readInt(); - this.radius = buffer.readFloat(); + this.radius = Math.max(0.1f, Math.min(100.0f, buffer.readFloat())); + if (Float.isNaN(radius) || Float.isInfinite(radius)) radius = 5.0f; this.durationTicks = buffer.readInt(); this.maxTicks = buffer.readInt(); this.innerColor = buffer.readInt(); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index bba50a69d..59dd346e4 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -116,8 +116,6 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== HOMING PROPERTIES ==================== protected EnergyHomingData homingData = new EnergyHomingData(); - protected Map pathDelays; - // ==================== TRACKING ==================== protected double startX, startY, startZ; protected int targetEntityId = -1; diff --git a/src/main/java/kamkeel/npcs/network/packets/data/ability/AbilityHotbarSyncPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/ability/AbilityHotbarSyncPacket.java index a30682ecc..7ce46f97b 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/ability/AbilityHotbarSyncPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/ability/AbilityHotbarSyncPacket.java @@ -81,6 +81,10 @@ public final class AbilityHotbarSyncPacket extends AbstractPacket { if (valid && data.abilityData != null) { valid = data.abilityData.hasUnlockedAbility(slot.abilityKey); + // For chains, also check with resolved key in case key format differs + if (!valid && slot.isChainKey()) { + valid = data.abilityData.hasUnlockedAbility("chain:" + slot.getResolveKey()); + } } if (!valid) { diff --git a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSavePacket.java b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSavePacket.java index 1522763fc..687a00a1f 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSavePacket.java @@ -51,6 +51,7 @@ public final class AbilityHotbarSavePacket extends AbstractPacket { public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { int slot = in.readInt(); NBTTagCompound compound = ByteBufUtils.readNBT(in); + if (compound == null) return; if (slot < 0 || slot >= AbilityHotbarData.TOTAL_SLOTS) return; @@ -73,6 +74,10 @@ public final class AbilityHotbarSavePacket extends AbstractPacket { if (valid && data.abilityData != null) { valid = data.abilityData.hasUnlockedAbility(slotData.abilityKey); + // For chains, also check with resolved key in case key format differs + if (!valid && slotData.isChainKey()) { + valid = data.abilityData.hasUnlockedAbility("chain:" + slotData.getResolveKey()); + } } } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java index 3d91f5a86..8c075bb25 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java @@ -66,6 +66,7 @@ public final class CustomAbilitySavePacket extends AbstractPacket { return; NBTTagCompound compound = ByteBufUtils.readNBT(in); + if (compound == null) return; Ability ability = AbilityController.Instance.fromNBT(compound); if (ability != null) { AbilityController.Instance.saveCustomAbility(ability); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java index 411d98a00..ec3f6ff20 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java @@ -81,7 +81,9 @@ public final class AbilityScriptPacket extends AbstractPacket { if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT)) return; - Action requestedAction = Action.values()[in.readInt()]; + int actionOrd = in.readInt(); + if (actionOrd < 0 || actionOrd >= Action.values().length) return; + Action requestedAction = Action.values()[actionOrd]; String id = ByteBufUtils.readUTF8String(in); Ability ability = AbilityController.Instance.resolveAbility(id); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java index 2589b0e13..45640bbfb 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java @@ -81,7 +81,9 @@ public final class ChainedAbilityScriptPacket extends AbstractPacket { if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT)) return; - Action requestedAction = Action.values()[in.readInt()]; + int actionOrd = in.readInt(); + if (actionOrd < 0 || actionOrd >= Action.values().length) return; + Action requestedAction = Action.values()[actionOrd]; String id = ByteBufUtils.readUTF8String(in); ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id); 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 5b56d0f14..485d59b87 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -268,7 +268,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } if (conditions.size() < MAX_CONDITIONS) { - sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));; + sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add")); y += ROW_H; } 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 fe3267112..4d03cb2dc 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java +++ b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java @@ -309,7 +309,7 @@ public class FieldDef { } public boolean hasRange() { - return min != Float.NEGATIVE_INFINITY || max != Float.POSITIVE_INFINITY; + return min != 0 || max != Float.POSITIVE_INFINITY; } public Class> getEnumClass() { diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java index 1b6a95fb7..cb47a092c 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java @@ -49,5 +49,6 @@ public class ScriptEnergyOrb extends ScriptEnergyPro entity.motionY = (dy / len) * entity.getSpeed(); entity.motionZ = (dz / len) * entity.getSpeed(); } + entity.startMoving(null); } }