Add Free Aim Functionality for Beams

This commit is contained in:
Kam committed 2026-02-28 19:25:13 -05:00
1 parent de2fcea154
commit 1a0ce41e00
4 files changed
+109 -3

No files matched your search

@@ -33,6 +33,7 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
private float beamWidth = 0.4f;
private float headSize = 0.6f;
private boolean freeAim = false;
public AbilityBeam() {
super(
@@ -67,12 +68,14 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
@Override
protected EntityAbilityBeam createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityAbilityBeam(
EntityAbilityBeam beam = new EntityAbilityBeam(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
beamWidth, headSize,
resolved, combatData, homingData, lightningData, lifespanData,
lockMovement.locksActive());
beam.setFreeAim(freeAim);
return beam;
}
@Override
@@ -100,6 +103,18 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
return new EntityAbilityBeam[size];
}
@Override
public void detach() {
if (entities != null) {
for (EntityAbilityBeam beam : entities) {
if (beam != null && !beam.isDead) {
beam.setAttachedToOwner(false);
}
}
}
super.detach();
}
@Override
protected float getProjectileTelegraphRadius() {
return headSize * 2.0f;
@@ -139,12 +154,14 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
nbt.setFloat("beamWidth", beamWidth);
nbt.setFloat("headSize", headSize);
nbt.setBoolean("freeAim", freeAim);
}
@Override
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
this.beamWidth = nbt.getFloat("beamWidth");
this.headSize = nbt.getFloat("headSize");
this.freeAim = nbt.getBoolean("freeAim");
}
// ==================== TYPE-SPECIFIC GETTERS ====================
@@ -173,6 +190,14 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
this.headSize = headSize;
}
public boolean isFreeAim() {
return freeAim;
}
public void setFreeAim(boolean freeAim) {
this.freeAim = freeAim;
}
// ==================== TYPE-SPECIFIC GUI ====================
@SideOnly(Side.CLIENT)
@@ -189,6 +214,9 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
));
defs.add(FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback));
defs.add(FieldDef.section("ability.section.beam"));
defs.add(FieldDef.boolField("ability.freeAim", this::isFreeAim, this::setFreeAim)
.hover("ability.hover.freeAim")
.enabledWhen(() -> lockMovement.locksActive() && rotationMode == RotationMode.FREE && !freeOnCast));
defs.add(FieldDef.row(
FieldDef.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth).range(0.1f, 100.0f),
FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize).range(0.1f, 100.0f)
@@ -213,7 +213,10 @@ public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> im
FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth).range(0.1f, 100.0f),
FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed).range(0.1f, 50.0f)
));
defs.add(FieldDef.floatField("ability.maxLength", this::getMaxLength, this::setMaxLength).range(1.0f, 500.0f));
defs.add(FieldDef.row(
FieldDef.floatField("ability.maxLength", this::getMaxLength, this::setMaxLength).range(1.0f, 500.0f),
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200)
));
defs.add(FieldDef.section("ability.section.explosive"));
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius)
@@ -11,6 +11,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.AxisAlignedBB;
@@ -55,6 +56,10 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Beam behavior mode (single source of truth).
private BeamMode beamMode = BeamMode.ANCHORED;
// Free Aim (players only) - steers beam toward player's look direction
private boolean freeAim = false;
private static final float FREE_AIM_STRENGTH = 0.15f;
// Charging state (beam-specific)
private float chargeOffsetDistance = 1.0f;
private int trailFadeTime = 20; // Ticks for trail to fully fade
@@ -433,8 +438,64 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
addTrailPoint();
}
/**
* Steer beam toward player's look direction (Free Aim mode).
* Target point is projected along look vector at the current head distance from origin.
*/
private void updateFreeAim() {
Entity owner = getOwnerEntity();
if (!(owner instanceof EntityPlayer)) return;
Vec3 look = getOwnerLookVector();
if (look == null) return;
double headDist = Math.sqrt(headOffsetX * headOffsetX + headOffsetY * headOffsetY + headOffsetZ * headOffsetZ);
if (headDist < 0.1) return;
// Target point along look vector at current head distance
double targetX = startX + look.xCoord * headDist;
double targetY = startY + look.yCoord * headDist;
double targetZ = startZ + look.zCoord * headDist;
// Current head world position
double headWorldX = startX + headOffsetX;
double headWorldY = startY + headOffsetY;
double headWorldZ = startZ + headOffsetZ;
double dx = targetX - headWorldX;
double dy = targetY - headWorldY;
double dz = targetZ - headWorldZ;
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < 0.01) return;
double desiredVX = (dx / dist) * getSpeed();
double desiredVY = (dy / dist) * getSpeed();
double desiredVZ = (dz / dist) * getSpeed();
motionX += (desiredVX - motionX) * FREE_AIM_STRENGTH;
motionY += (desiredVY - motionY) * FREE_AIM_STRENGTH;
motionZ += (desiredVZ - motionZ) * FREE_AIM_STRENGTH;
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
if (vLen > 0) {
motionX = (motionX / vLen) * getSpeed();
motionY = (motionY / vLen) * getSpeed();
motionZ = (motionZ / vLen) * getSpeed();
}
}
@Override
protected void updateHoming() {
if (freeAim) {
Entity owner = getOwnerEntity();
if (owner instanceof EntityPlayer) {
updateFreeAim();
return;
}
// NPCs fall through to normal homing
}
if (!isHoming()) return;
Entity target = getTargetEntity();
@@ -545,6 +606,14 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
setBeamMode(modeFromAnchored(attached));
}
public boolean isFreeAim() {
return freeAim;
}
public void setFreeAim(boolean freeAim) {
this.freeAim = freeAim;
}
/**
* Get interpolated head offset for smooth rendering.
* Returns offset RELATIVE to origin.
@@ -643,8 +712,9 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
resetHeadOffsets();
resetTrailStorage();
// Disable homing — reflected beams fly straight.
// Disable homing and free aim — reflected beams fly straight.
homingData.setHoming(false);
freeAim = false;
setTargetEntityId(-1);
}
return reflected;
@@ -668,6 +738,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
resetHeadOffsets();
resetTrailStorage();
homingData.setHoming(false);
freeAim = false;
setTargetEntityId(-1);
}
@@ -686,6 +757,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
readChargingNBT(nbt);
this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f;
this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20;
this.freeAim = nbt.getBoolean("FreeAim");
boolean attachedLegacy = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner");
BeamMode loadedMode = modeFromAnchored(attachedLegacy);
@@ -737,6 +809,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
nbt.setString("BeamMode", beamMode.name());
writeChargingNBT(nbt);
nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance);
nbt.setBoolean("FreeAim", freeAim);
nbt.setBoolean("FadeTrail", isFadingMode());
nbt.setInteger("TrailFadeTime", trailFadeTime);