From 7cafa4f67720621f8418942be01823f876ffb12b Mon Sep 17 00:00:00 2001 From: KAMKEEL <28842281+KAMKEEL@users.noreply.github.com> Date: Tue, 11 Aug 2026 03:28:49 -0400 Subject: [PATCH] Make anchored lasers and rotation lock affect ability aim - Anchored lasers re-derived their direction straight at the target every tick, discarding the rate-limited look vector, so track speed did nothing and the beam was undodgeable. They now converge on the point the owner is aiming at. - Rotation-locked NPCs aimed energy projectiles and hit-scan projectiles from the target''s position, so the lock was purely visual. Both now respect the frozen heading. --- .../data/ability/type/AbilityProjectile.java | 9 +++++++ .../type/energy/AbilityEnergyProjectile.java | 7 ++++- .../npcs/entity/EntityAbilityLaser.java | 27 ++++++++++--------- 3 files changed, 30 insertions(+), 13 deletions(-) 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 104209eeb..3caabc678 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 @@ -97,6 +97,15 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { dz /= len; } + // A rotation-locked NPC cannot turn to follow the target, so it can only hit what its + // frozen heading still covers - the same cone the player hit scan uses. + if (isRotationLockedForCurrentPhase()) { + Vec3 look = caster.getLookVec(); + if (look != null && (dx * look.xCoord + dy * look.yCoord + dz * look.zCoord) < 0.95) { + return; + } + } + applyAbilityDamageWithDirection(caster, target, damage, knockback, dx, dz); world.playSoundAtEntity(caster, "random.bow", 1.0f, 0.8f); 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 7e7986d20..723781d36 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 @@ -248,7 +248,12 @@ public abstract class AbilityEnergyProjectile * NPCs use their resolved target so launch math can track the target immediately. */ protected EntityLivingBase getLaunchTarget(EntityLivingBase caster, EntityLivingBase target) { - return isPlayerCaster(caster) ? null : target; + if (isPlayerCaster(caster)) return null; + // A rotation-locked NPC cannot turn, so launching from the target's position would let + // it fire a perfectly aimed shot regardless of where it is facing. Fall back to the + // look vector, which reflects the frozen heading. + if (isRotationLockedForCurrentPhase()) return null; + return target; } /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 546457f4d..f8f5d44f5 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -264,18 +264,21 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { Vec3 anchorPos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData); setPosition(anchorPos.xCoord, anchorPos.yCoord, anchorPos.zCoord); - Entity targetEntity = getTargetEntity(); - if (targetEntity instanceof EntityLivingBase && targetEntity.isEntityAlive()) { - EntityLivingBase target = (EntityLivingBase) targetEntity; - double dx = target.posX - anchorPos.xCoord; - double dy = (target.posY + target.getEyeHeight() - 0.4) - anchorPos.yCoord; - double dz = target.posZ - anchorPos.zCoord; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0.0001) { - dirX = dx / len; - dirY = dy / len; - dirZ = dz / len; - } + // Converge on the point the owner is aiming at, not on the target itself. Aiming + // straight at the target would re-derive the direction every tick and ignore the + // rotation the owner is actually allowed to reach, which is what track speed and + // rotation lock constrain. + double aimX = livingOwner.posX + dirX * maxLength; + double aimY = livingOwner.posY + livingOwner.getEyeHeight() + dirY * maxLength; + double aimZ = livingOwner.posZ + dirZ * maxLength; + double dx = aimX - anchorPos.xCoord; + double dy = aimY - anchorPos.yCoord; + double dz = aimZ - anchorPos.zCoord; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 0.0001) { + dirX = dx / len; + dirY = dy / len; + dirZ = dz / len; } } else { Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);