From 910f3b3744a2b8399b1ec2aea3702e3149f7d6be Mon Sep 17 00:00:00 2001 From: Kam Date: Fri, 13 Feb 2026 15:01:28 -0500 Subject: [PATCH] Add NPC inline edit support and adjust ability launch mechanics --- .../controllers/data/ability/Ability.java | 14 ++++++++++ .../npcs/entity/EntityAbilityBeam.java | 3 +++ .../npcs/entity/EntityAbilityDisc.java | 3 +++ .../kamkeel/npcs/entity/EntityAbilityOrb.java | 3 +++ .../npcs/entity/EntityAbilityProjectile.java | 27 +++++++++++++++++++ .../kamkeel/npcs/util/AnchorPointHelper.java | 25 ++++++++++++++--- .../client/gui/advanced/GuiNPCAbilities.java | 4 +++ 7 files changed, 76 insertions(+), 3 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 b2743f629..f0b8ceb07 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -130,6 +130,12 @@ public abstract class Ability implements IAbility { protected transient boolean previewMode = false; protected transient PreviewEntityHandler previewEntityHandler; + // ═══════════════════════════════════════════════════════════════════ + // GUI CONTEXT (client-side only, not saved) + // ═══════════════════════════════════════════════════════════════════ + + protected transient boolean npcInlineEdit = false; + // ═══════════════════════════════════════════════════════════════════ // ABSTRACT METHODS // ═══════════════════════════════════════════════════════════════════ @@ -1282,6 +1288,14 @@ public abstract class Ability implements IAbility { return false; } + public boolean isNpcInlineEdit() { + return npcInlineEdit; + } + + public void setNpcInlineEdit(boolean npcInlineEdit) { + this.npcInlineEdit = npcInlineEdit; + } + public int getWeight() { return weight; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 1427b298f..488dfa39e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -274,6 +274,9 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { 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; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index edd7b3dff..04bf2b777 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -226,6 +226,9 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { setCharging(false); + // For player casters, snap to look vector for crosshair-aligned launch + snapToPlayerLookVector(); + // Update start position to current position startX = posX; startY = posY; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 73e8c26f3..c461e9575 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -200,6 +200,9 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { setCharging(false); + // For player casters, snap to look vector for crosshair-aligned launch + snapToPlayerLookVector(); + // Update start position to current position startX = posX; startY = posY; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java index 7080ce2ef..e70b06009 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java @@ -17,6 +17,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.Vec3; import net.minecraft.world.World; import noppes.npcs.NpcDamageSource; import noppes.npcs.api.entity.IEnergyProjectile; @@ -428,6 +429,32 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP } } + // ==================== 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. + * Should be called in startMoving/startFiring BEFORE setting startX/Y/Z. + */ + protected void snapToPlayerLookVector() { + Entity owner = getOwnerEntity(); + if (!(owner instanceof EntityPlayer)) return; + + Vec3 look = owner.getLookVec(); + double eyeY = owner.posY + owner.getEyeHeight(); + float frontDist = Math.max(0.5f, size * 0.5f); + + double newX = owner.posX + look.xCoord * frontDist; + double newY = eyeY + look.yCoord * frontDist; + double newZ = owner.posZ + look.zCoord * frontDist; + + setPosition(newX, newY, newZ); + prevPosX = newX; + prevPosY = newY; + prevPosZ = newZ; + } + // ==================== ENTITY HELPERS ==================== public Entity getOwnerEntity() { diff --git a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java index 20d557bef..802841c2a 100644 --- a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java +++ b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java @@ -2,6 +2,7 @@ package kamkeel.npcs.util; import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.Vec3; import noppes.npcs.constants.EnumAnimationPart; @@ -18,6 +19,12 @@ import noppes.npcs.entity.data.ModelScalePart; */ public class AnchorPointHelper { + // Y offset correction for the local client player. + // EntityPlayerSP.posY is at feet, but the renderer adds yOffset (1.62) visually, + // so anchor positions computed from posY appear ~1.6 blocks too high for the local player. + // Remote players and NPCs don't have this discrepancy. + private static final float CLIENT_PLAYER_Y_OFFSET = -1.6F; + // Default biped model constants (in model units, 1 unit = 1/16 block) private static final float MODEL_SCALE = 1f / 16f; @@ -68,7 +75,7 @@ public class AnchorPointHelper { float anchorZ = anchorData.anchorOffsetZ; double x = entity.posX; - double y = entity.posY; + double y = entity.posY + getClientPlayerYCorrection(entity); double z = entity.posZ; float headYaw = (float) Math.toRadians(entity.rotationYawHead); @@ -127,6 +134,18 @@ public class AnchorPointHelper { return 1f; } + /** + * Returns Y offset correction for the local client player. + * On the client, EntityPlayerSP.posY is at feet level but the renderer adds yOffset (1.62), + * causing anchor positions to appear ~1.6 blocks too high. This matches DBC's correction. + */ + private static double getClientPlayerYCorrection(EntityLivingBase entity) { + if (entity.worldObj.isRemote && entity instanceof EntityPlayerSP) { + return CLIENT_PLAYER_Y_OFFSET; + } + return 0.0; + } + // Default arm scale (no scaling) private static final ModelScalePart DEFAULT_ARM_SCALE = new ModelScalePart(); @@ -251,7 +270,7 @@ public class AnchorPointHelper { // Final world position double worldX = entity.posX + worldOffsetX; - double worldY = entity.posY + shoulderHeight - blockY; + double worldY = entity.posY + getClientPlayerYCorrection(entity) + shoulderHeight - blockY; double worldZ = entity.posZ + worldOffsetZ; // Rotate offsets relative to entity's body yaw @@ -324,7 +343,7 @@ public class AnchorPointHelper { double armLengthOffset = armScale.scaleY * 0.3 * scale; double x = entity.posX + lateralX + forwardX; - double y = entity.posY + entity.height * FALLBACK_ARM_HEIGHT + armLengthOffset; + double y = entity.posY + getClientPlayerYCorrection(entity) + entity.height * FALLBACK_ARM_HEIGHT + armLengthOffset; double z = entity.posZ + lateralZ + forwardZ; // Rotate offsets relative to entity's body yaw 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 4b9e99a13..f8ed53c7c 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -254,6 +254,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } else { Ability ability = slot.getAbility(); if (ability != null) { + ability.setNpcInlineEdit(true); setSubGui(ability.createConfigGui(this)); } } @@ -303,6 +304,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC Ability abilityToSave = npcSlots.get(selectedAbilityIndex).getAbility(); if (abilityToSave != null) { if (abilityToSave.getName() == null || abilityToSave.getName().isEmpty()) { + abilityToSave.setNpcInlineEdit(true); setSubGui(abilityToSave.createConfigGui(this)); } else { setSubGui(new SubGuiAbilitySaveConfirm(abilityToSave, null, existingPresetNames)); @@ -632,6 +634,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC setSubGui(new SubGuiAbilityEditMode()); } else { if (ability != null) { + ability.setNpcInlineEdit(true); setSubGui(ability.createConfigGui(this)); } } @@ -690,6 +693,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC Ability ability = slot.getAbility(); if (ability != null) { ability.setId(UUID.randomUUID().toString()); + ability.setNpcInlineEdit(true); setSubGui(ability.createConfigGui(this)); } updateNpcAbilitiesList();