Refactor entity owner checks and enhance FULL_MODEL rotation support in ability entities

This commit is contained in:
Kam committed 2026-02-15 21:29:26 -05:00
1 parent ce24c0a4ff
commit eda8931b5f
6 files changed
+175 -57

No files matched your search

@@ -386,33 +386,26 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
chargeTick++;
Entity owner = getOwnerEntity();
if (owner == null || owner.isDead) {
setDead();
return;
if (owner != null) {
Vec3 pos;
if (owner instanceof EntityLivingBase) {
pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData, chargeOffsetDistance);
} else {
float yaw = (float) Math.toRadians(owner.rotationYaw);
double offsetX = -Math.sin(yaw) * chargeOffsetDistance;
double offsetZ = Math.cos(yaw) * chargeOffsetDistance;
pos = Vec3.createVectorHelper(
owner.posX + offsetX,
owner.posY + owner.getEyeHeight() * 0.7,
owner.posZ + offsetZ
);
}
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
startX = pos.xCoord;
startY = pos.yCoord;
startZ = pos.zCoord;
}
// Calculate position based on anchor point
Vec3 pos;
if (owner instanceof EntityLivingBase) {
pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData, chargeOffsetDistance);
} else {
// Fallback for non-living entities (shouldn't happen normally)
float yaw = (float) Math.toRadians(owner.rotationYaw);
double offsetX = -Math.sin(yaw) * chargeOffsetDistance;
double offsetZ = Math.cos(yaw) * chargeOffsetDistance;
pos = Vec3.createVectorHelper(
owner.posX + offsetX,
owner.posY + owner.getEyeHeight() * 0.7,
owner.posZ + offsetZ
);
}
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
// Also update origin for when firing starts
startX = pos.xCoord;
startY = pos.yCoord;
startZ = pos.zCoord;
}
/**
@@ -391,19 +391,13 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
protected void updateCharging() {
chargeTick++;
Entity owner = getOwnerEntity();
if (owner == null || owner.isDead) {
setDead();
return;
}
// Grow size based on charge progress
float progress = getChargeProgress();
this.discRadius = targetDiscRadius * progress;
this.discThickness = targetDiscThickness * progress;
this.size = this.discRadius; // Base size for interpolation
// Calculate position based on anchor point, offset downward by half radius to center
Entity owner = getOwnerEntity();
if (owner instanceof EntityLivingBase) {
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
@@ -156,8 +156,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
*/
private void updateLaserOriginAndDirection() {
Entity owner = getOwnerEntity();
if (owner == null || owner.isDead) return;
if (!(owner instanceof EntityLivingBase)) return;
if (owner == null || !(owner instanceof EntityLivingBase)) return;
EntityLivingBase livingOwner = (EntityLivingBase) owner;
@@ -184,16 +184,20 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
// Skip lifetime/distance checks in preview mode
if (!previewMode) {
// Failsafe: if owner entity is gone, dead, or NPC was killed/reset, self-destruct
// Only self-destruct if the owner is confirmed dead/killed.
// A null owner (unloaded/out of chunk) is NOT treated as dead —
// the projectile keeps acting normally and deathWorldTime handles lifetime.
if (ownerEntityId >= 0 && ticksExisted > 5) {
Entity owner = worldObj.getEntityByID(ownerEntityId);
if (owner == null || owner.isDead) {
this.setDead();
return;
}
if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
this.setDead();
return;
if (owner != null) {
if (owner.isDead) {
this.setDead();
return;
}
if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
this.setDead();
return;
}
}
}
@@ -506,16 +510,14 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
* Update during charging state - grow size and follow anchor.
* Default implementation grows {@code size} from 0 to {@code targetSize}.
* Override in subclasses for type-specific charging (Disc, Beam).
*
* Note: Owner death checks are handled by onUpdate() before this is called.
*/
protected void updateCharging() {
chargeTick++;
Entity owner = getOwnerEntity();
if (owner == null || owner.isDead) {
setDead();
return;
}
float progress = getChargeProgress();
this.size = targetSize * progress;
Entity owner = getOwnerEntity();
if (owner instanceof EntityLivingBase) {
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
@@ -144,15 +144,19 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
return;
}
// Follow owner
Entity owner = getOwner();
if (owner == null) {
if (owner != null && owner.isDead) {
this.setDead();
return;
}
if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
this.setDead();
return;
}
// Update position to follow owner
this.setPosition(owner.posX, owner.posY + beamHeight, owner.posZ);
if (owner != null) {
this.setPosition(owner.posX, owner.posY + beamHeight, owner.posZ);
}
// Check if sweep is done
if (completedRotations >= numberOfRotations) {
@@ -119,6 +119,20 @@ public class AnchorPointHelper {
y += rotatedOffset.yCoord;
z += rotatedOffset.zCoord;
// Apply FULL_MODEL rotation if active
FramePart fullModel = getFullModelPart(entity);
if (fullModel != null) {
double dx = x - entity.posX;
double dy = y - entity.posY;
double dz = z - entity.posZ;
Vec3 rotated = applyFullModelToWorldOffset(fullModel, entity, dx, dy, dz, bodyYaw, scale);
return Vec3.createVectorHelper(
entity.posX + rotated.xCoord,
entity.posY + rotated.yCoord,
entity.posZ + rotated.zCoord
);
}
return Vec3.createVectorHelper(x, y, z);
}
@@ -162,16 +176,32 @@ public class AnchorPointHelper {
/**
* Calculate hand position using animation data if available.
* Applies FULL_MODEL rotation if an active animation has it.
*/
private static Vec3 calculateHandPosition(EntityLivingBase entity, EnergyAnchorData anchor, boolean rightHand, float scale) {
// Try to get animated position first
Vec3 animatedPos = getAnimatedHandPosition(entity, anchor, rightHand, scale);
if (animatedPos != null) {
return animatedPos;
Vec3 pos = getAnimatedHandPosition(entity, anchor, rightHand, scale);
if (pos == null) {
// Fallback to static position
pos = calculateFallbackHandPosition(entity, anchor, rightHand, scale);
}
// Fallback to static position
return calculateFallbackHandPosition(entity, anchor, rightHand, scale);
// Apply FULL_MODEL rotation if active
FramePart fullModel = getFullModelPart(entity);
if (fullModel != null) {
double dx = pos.xCoord - entity.posX;
double dy = pos.yCoord - entity.posY;
double dz = pos.zCoord - entity.posZ;
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
Vec3 rotated = applyFullModelToWorldOffset(fullModel, entity, dx, dy, dz, bodyYaw, scale);
return Vec3.createVectorHelper(
entity.posX + rotated.xCoord,
entity.posY + rotated.yCoord,
entity.posZ + rotated.zCoord
);
}
return pos;
}
/**
@@ -390,4 +420,100 @@ public class AnchorPointHelper {
double worldZ = -offsetX * sin + offsetZ * cos;
return Vec3.createVectorHelper(worldX, offsetY, worldZ);
}
// ==================== FULL_MODEL ANIMATION SUPPORT ====================
/**
* Get the FULL_MODEL FramePart if the entity has an active animation with it.
* Returns null if no FULL_MODEL animation is active.
*/
private static FramePart getFullModelPart(EntityLivingBase entity) {
AnimationData animData = AnimationData.getData(entity);
if (animData == null || !animData.isActive() || animData.animation == null) {
return null;
}
Frame frame = (Frame) animData.animation.currentFrame();
if (frame == null) {
return null;
}
FramePart part = frame.frameParts.get(EnumAnimationPart.FULL_MODEL);
return part;
}
/**
* Apply FULL_MODEL rotation to a world-space offset from entity position.
* Converts the offset to model space, applies FULL_MODEL rotation and pivots,
* then converts back to world space.
*
* The rendering pipeline is: entity_pos * body_yaw * scale(-1,-1,1) * FULL_MODEL * parts
* Combined body_yaw + scale maps model space to world space:
* worldX = mx*cos(yaw) + mz*sin(yaw)
* worldY = -my (model Y-down → world Y-up)
* worldZ = mx*sin(yaw) - mz*cos(yaw)
* This transform is its own inverse for X,Z.
*
* FULL_MODEL rotation order matches renderer: vertex sees Z, then Y, then X.
*/
private static Vec3 applyFullModelToWorldOffset(FramePart fullModel, EntityLivingBase entity,
double worldDX, double worldDY, double worldDZ,
float bodyYaw, float scale) {
float[] rotations = getRotations(fullModel, entity);
float[] pivots = getPivots(fullModel, entity);
// Quick exit if no transform needed
boolean hasRotation = rotations[0] != 0 || rotations[1] != 0 || rotations[2] != 0;
boolean hasPivot = pivots[0] != 0 || pivots[1] != 0 || pivots[2] != 0;
if (!hasRotation && !hasPivot) {
return Vec3.createVectorHelper(worldDX, worldDY, worldDZ);
}
// Convert world offset to model space
// Body yaw + scale combined transform is an involution (self-inverse) for X,Z
double cosYaw = Math.cos(bodyYaw);
double sinYaw = Math.sin(bodyYaw);
double mx = worldDX * cosYaw + worldDZ * sinYaw;
double mz = worldDX * sinYaw - worldDZ * cosYaw;
double my = -worldDY; // Invert Y (world Y-up → model Y-down)
// Apply FULL_MODEL rotations (vertex order: Z first, then Y, then X)
// Matches renderer: glRotatef(X), glRotatef(Y), glRotatef(Z) → vertex sees Z,Y,X
// Z rotation
double cosZ = Math.cos(rotations[2]);
double sinZ = Math.sin(rotations[2]);
double nx = mx * cosZ - my * sinZ;
double ny = mx * sinZ + my * cosZ;
mx = nx;
my = ny;
// Y rotation
double cosY = Math.cos(rotations[1]);
double sinY = Math.sin(rotations[1]);
nx = mx * cosY + mz * sinY;
double nz = -mx * sinY + mz * cosY;
mx = nx;
mz = nz;
// X rotation
double cosX = Math.cos(rotations[0]);
double sinX = Math.sin(rotations[0]);
ny = my * cosX - mz * sinX;
nz = my * sinX + mz * cosX;
my = ny;
mz = nz;
// Apply pivots in model space (after rotation, matching GL vertex ordering)
// Renderer: glTranslatef(px, -py, pz) → pivot Y is negated for model Y-down space
mx += pivots[0] * MODEL_SCALE * scale;
my -= pivots[1] * MODEL_SCALE * scale;
mz += pivots[2] * MODEL_SCALE * scale;
// Convert back to world space (same involution for X,Z; invert Y back)
double newDX = mx * cosYaw + mz * sinYaw;
double newDZ = mx * sinYaw - mz * cosYaw;
double newDY = -my;
return Vec3.createVectorHelper(newDX, newDY, newDZ);
}
}