Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java
T

348 lines
12 KiB
Java

package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.EventHooks;
import java.util.ArrayList;
import java.util.List;
/**
* Energy Slicer entity - a thin, wide blade projectile that flies in a straight line.
* JJK Cleave/Dismantle inspired. Piercing by default (passes through entities).
* Extends EntityEnergyAbility to leverage the existing projectile infrastructure.
*/
public class EntityEnergySlicer extends EntityEnergyProjectile {
private float sliceWidth = 3.0f;
private float sliceThickness = 0.15f;
private boolean piercing = true;
private List<Integer> hitEntities = new ArrayList<>();
public EntityEnergySlicer(World world) {
super(world);
}
/**
* Full constructor with all parameters.
*/
public EntityEnergySlicer(World world, EntityLivingBase owner, EntityLivingBase target,
double x, double y, double z,
float sliceWidth, float sliceThickness,
EnergyDisplayData display, EnergyCombatData combat,
EnergyHomingData homing, EnergyLightningData lightning,
EnergyLifespanData lifespan, EnergyTrajectoryData trajectory,
boolean piercing) {
super(world);
initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan, trajectory);
this.homingData = homing;
this.sliceWidth = sliceWidth;
this.sliceThickness = sliceThickness;
this.piercing = piercing;
calculateInitialVelocity(owner, target, x, y, z);
}
// Targets stored during charging for grow animation
private float targetSliceWidth = 3.0f;
private float targetSliceThickness = 0.15f;
/**
* Setup charging with full target dimensions.
*/
public void setupSlicerCharging(EnergyAnchorData anchor, int chargeDuration, float width, float thickness) {
this.targetSliceWidth = width;
this.targetSliceThickness = thickness;
this.sliceWidth = 0.01f;
this.sliceThickness = 0.01f;
super.setupCharging(anchor, chargeDuration);
}
/**
* Setup preview mode.
*/
public void setupPreview(EntityLivingBase owner, float width, float thickness,
EnergyDisplayData display, EnergyLightningData lightning,
EnergyAnchorData anchor, int chargeDuration) {
this.setPreviewMode(true);
this.setPreviewOwner(owner);
this.displayData = display;
this.lightningData = lightning;
this.setCharging(true);
this.chargeDuration = chargeDuration;
this.chargeTick = 0;
this.anchorData = anchor;
this.targetSliceWidth = width;
this.targetSliceThickness = thickness;
this.sliceWidth = 0.01f;
this.sliceThickness = 0.01f;
this.size = 0.01f;
this.renderCurrentSize = 0.01f;
this.prevRenderSize = 0.01f;
Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition(owner, anchorData);
this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
this.prevPosX = pos.xCoord;
this.prevPosY = pos.yCoord;
this.prevPosZ = pos.zCoord;
this.startX = pos.xCoord;
this.startY = pos.yCoord;
this.startZ = pos.zCoord;
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
}
/**
* Start preview firing.
*/
public void startPreviewFiring() {
setCharging(false);
startX = posX;
startY = posY;
startZ = posZ;
prevPosX = posX;
prevPosY = posY;
prevPosZ = posZ;
Entity owner = getOwnerEntity();
if (owner != null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
motionX = -Math.sin(yaw) * getSpeed();
motionY = 0;
motionZ = Math.cos(yaw) * getSpeed();
} else {
motionX = getSpeed();
motionY = 0;
motionZ = 0;
}
}
/**
* Start the slicer moving (exit charging mode).
*/
public void startMoving(EntityLivingBase target) {
setCharging(false);
snapToPlayerLookVector();
startX = posX;
startY = posY;
startZ = posZ;
Entity owner = getOwnerEntity();
if (target != null) {
double dx = target.posX - posX;
double dy = (target.posY + target.getEyeHeight()) - posY;
double dz = target.posZ - posZ;
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len > 0) {
motionX = (dx / len) * getSpeed();
motionY = (dy / len) * getSpeed();
motionZ = (dz / len) * getSpeed();
}
} else if (owner != null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
float pitch = (float) Math.toRadians(owner.rotationPitch);
motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
motionY = -Math.sin(pitch) * getSpeed();
motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
}
}
@Override
protected void updateRotation() {
// Slicer doesn't spin - it stays oriented perpendicular to travel direction
// No rotation update needed
}
@Override
protected void updateProjectile() {
if (isCharging()) {
updateCharging();
return;
}
if (previewMode) {
posX += motionX;
posY += motionY;
posZ += motionZ;
return;
}
if (worldObj.isRemote) {
handleClientInterpolation();
} else {
// No homing by default for slicer (straight line flight)
if (homingData.isHoming()) {
updateHoming();
}
checkBlockCollision();
checkEntityCollision();
this.moveEntity(motionX, motionY, motionZ);
}
// Wall collision
if (this.isCollidedHorizontally || this.isCollidedVertically) {
if (!worldObj.isRemote) {
hasHit = true;
if (isExplosive()) {
doExplosion();
}
}
this.setDead();
}
}
@Override
protected void updateCharging() {
chargeTick++;
float progress = getChargeProgress();
this.sliceWidth = targetSliceWidth * progress;
this.sliceThickness = targetSliceThickness * progress;
this.size = this.sliceWidth;
Entity owner = getOwnerEntity();
if (owner instanceof EntityLivingBase) {
Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
}
}
private void checkBlockCollision() {
if (piercing) return; // Piercing slicers pass through blocks
Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ);
Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ);
MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false);
if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
if (!worldObj.isRemote) {
EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ);
}
hasHit = true;
this.setDead();
}
}
private void checkEntityCollision() {
float halfW = sliceWidth * 0.5f;
float halfH = sliceThickness * 0.5f;
AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox(
posX - halfW, posY - halfH, posZ - halfW,
posX + halfW, posY + halfH, posZ + halfW
);
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox);
for (EntityLivingBase entity : entities) {
if (shouldIgnoreEntity(entity)) continue;
if (hitEntities.contains(entity.getEntityId())) continue; // Already hit this entity
if (piercing) {
// Pierce: damage but keep flying
applyDamage(entity);
hitEntities.add(entity.getEntityId());
} else {
// Non-piercing: damage and die
hasHit = true;
if (isExplosive()) {
doExplosion();
} else {
applyDamage(entity);
}
this.setDead();
return;
}
}
}
/**
* Get the yaw angle from motion vector for orientation.
*/
public float getTravelYaw() {
if (isCharging()) {
Entity owner = previewMode ? previewOwner : getOwnerEntity();
if (owner != null) return owner.rotationYaw;
}
double speed = Math.sqrt(motionX * motionX + motionZ * motionZ);
if (speed > 0.001) {
return (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI);
}
return 0.0f;
}
/**
* Get the pitch angle from motion vector.
*/
public float getTravelPitch() {
double horizSpeed = Math.sqrt(motionX * motionX + motionZ * motionZ);
if (horizSpeed > 0.001 || Math.abs(motionY) > 0.001) {
return (float) (-Math.atan2(motionY, horizSpeed) * 180.0 / Math.PI);
}
return 0.0f;
}
// ==================== GETTERS ====================
public float getSliceWidth() {
return sliceWidth;
}
public void setSliceWidth(float width) {
this.sliceWidth = width;
}
public float getSliceThickness() {
return sliceThickness;
}
public void setSliceThickness(float thickness) {
this.sliceThickness = thickness;
}
public boolean isPiercing() {
return piercing;
}
public void setPiercing(boolean piercing) {
this.piercing = piercing;
}
// ==================== NBT ====================
@Override
protected void readProjectileNBT(NBTTagCompound nbt) {
readChargingNBT(nbt);
this.sliceWidth = sanitize(nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f, 3.0f, MAX_ENTITY_SIZE);
this.sliceThickness = sanitize(nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f, 0.15f, MAX_ENTITY_SIZE);
this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing");
this.targetSliceWidth = nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth;
this.targetSliceThickness = nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness;
}
@Override
protected void writeProjectileNBT(NBTTagCompound nbt) {
writeChargingNBT(nbt);
nbt.setFloat("SliceWidth", sliceWidth);
nbt.setFloat("SliceThickness", sliceThickness);
nbt.setBoolean("Piercing", piercing);
nbt.setFloat("TargetSliceWidth", targetSliceWidth);
nbt.setFloat("TargetSliceThickness", targetSliceThickness);
}
}