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 fdf2d827e..e0e096b3c 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 @@ -18,6 +18,7 @@ import kamkeel.npcs.entity.EntityEnergyProjectile; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket; import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; @@ -341,6 +342,21 @@ public abstract class AbilityEnergyProjectile spawnedCount = 0; } + /** + * Remove a specific entity from this ability's tracking. + * Used when a projectile is reflected — it no longer belongs to this ability, + * so the ability should stop waiting for it and free the caster. + */ + public void detachEntity(Entity entity) { + if (entities == null || entity == null) return; + for (int i = 0; i < entities.length; i++) { + if (entities[i] == entity) { + entities[i] = null; + break; + } + } + } + @Override public boolean isReadyForBurstCompletion(int activeTick) { return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index a3f5f874a..33d6e67bc 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -637,6 +637,50 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { return trailFadeTime; } + // ==================== REFLECTION ==================== + + @Override + protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) { + boolean reflected = super.reflectFromBarrier(barrier, reflectStrengthPct); + if (reflected) { + // Switch to FREE_TRAIL: detach from caster (tail-less), trail fades out. + setBeamMode(BeamMode.FREE_TRAIL); + + // Reset origin to the reflection point (current head position). + startX = posX; + startY = posY; + startZ = posZ; + resetHeadOffsets(); + resetTrailStorage(); + + // Disable homing — reflected beams fly straight. + homingData.setHoming(false); + setTargetEntityId(-1); + } + return reflected; + } + + @Override + protected void writeProjectileReflectionData(NBTTagCompound nbt) { + nbt.setByte("BeamMode", (byte) beamMode.ordinal()); + nbt.setDouble("StartX", startX); + nbt.setDouble("StartY", startY); + nbt.setDouble("StartZ", startZ); + } + + @Override + protected void applyProjectileReflectionData(NBTTagCompound nbt) { + int mode = nbt.getByte("BeamMode"); + setBeamMode(mode >= 0 && mode < BeamMode.values().length ? BeamMode.values()[mode] : BeamMode.FREE_TRAIL); + startX = nbt.getDouble("StartX"); + startY = nbt.getDouble("StartY"); + startZ = nbt.getDouble("StartZ"); + resetHeadOffsets(); + resetTrailStorage(); + homingData.setHoming(false); + setTargetEntityId(-1); + } + // ==================== NBT ==================== @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 44ba4c69e..a7ff4ef99 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -395,6 +395,32 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { return lastTravelYaw; } + // ==================== REFLECTION ==================== + + @Override + protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) { + boolean reflected = super.reflectFromBarrier(barrier, reflectStrengthPct); + if (reflected) { + // Disable boomerang on reflected discs — the original owner is no longer + // relevant and the return-to-owner logic causes erratic flight paths. + this.boomerang = false; + this.returning = false; + } + return reflected; + } + + @Override + protected void writeProjectileReflectionData(NBTTagCompound nbt) { + nbt.setBoolean("Boomerang", boomerang); + nbt.setBoolean("Returning", returning); + } + + @Override + protected void applyProjectileReflectionData(NBTTagCompound nbt) { + boomerang = nbt.getBoolean("Boomerang"); + returning = nbt.getBoolean("Returning"); + } + // ==================== NBT ==================== @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index c1ec94d48..d573e19fb 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -134,9 +134,10 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } // After reflection, hold trajectory briefly so the rebound is visible. + // Once reflected, never resume owner-tracking — the laser flies in a fixed direction. if (reflectedLockTicks > 0) { reflectedLockTicks--; - } else { + } else if (!reflected) { // Track owner's anchor position and rotation each tick // so the laser follows the NPC's facing direction updateLaserOriginAndDirection(); @@ -717,6 +718,41 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.endZ = posZ; } + // ==================== REFLECTION SYNC ==================== + + @Override + protected void writeProjectileReflectionData(NBTTagCompound nbt) { + nbt.setDouble("DirX", dirX); + nbt.setDouble("DirY", dirY); + nbt.setDouble("DirZ", dirZ); + nbt.setDouble("StartX", startX); + nbt.setDouble("StartY", startY); + nbt.setDouble("StartZ", startZ); + nbt.setFloat("CurrentLength", currentLength); + nbt.setDouble("EndX", endX); + nbt.setDouble("EndY", endY); + nbt.setDouble("EndZ", endZ); + nbt.setInteger("ReflectedLockTicks", reflectedLockTicks); + nbt.setBoolean("FullyExtended", fullyExtended); + } + + @Override + protected void applyProjectileReflectionData(NBTTagCompound nbt) { + dirX = nbt.getDouble("DirX"); + dirY = nbt.getDouble("DirY"); + dirZ = nbt.getDouble("DirZ"); + startX = nbt.getDouble("StartX"); + startY = nbt.getDouble("StartY"); + startZ = nbt.getDouble("StartZ"); + currentLength = nbt.getFloat("CurrentLength"); + endX = nbt.getDouble("EndX"); + endY = nbt.getDouble("EndY"); + endZ = nbt.getDouble("EndZ"); + reflectedLockTicks = nbt.getInteger("ReflectedLockTicks"); + fullyExtended = nbt.getBoolean("FullyExtended"); + ticksSinceFullExtension = 0; + } + // ==================== NBT ==================== @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index bec0da39e..8e9a4de1b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -3,6 +3,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile; import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; import kamkeel.npcs.controllers.data.ability.enums.HitType; @@ -12,6 +13,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket; import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.block.Block; @@ -120,6 +122,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected int targetEntityId = -1; // ==================== STATE ==================== + protected boolean reflected = false; // Set after barrier reflection; prevents owner-tracking on reflected projectiles protected boolean hasHit = false; protected int hitCount = 0; protected final Set hitOnceEntities = new HashSet(); @@ -427,6 +430,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (hitOutcome.shouldReflect()) { if (!reflectFromBarrier(barrier, hitOutcome.reflectStrengthPct)) { beginBarrierImpactPause(true, barrier); + } else { + sendReflectionSync(); } return true; } @@ -498,6 +503,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { barrierImpactPauseTicks = 0; barrierImpactDestroyOnResume = false; + // Detach from source ability BEFORE changing ownership. + // This lets the ability see the projectile as gone and free the caster + // (e.g., release movement/rotation locks on the player). + if (sourceAbility instanceof AbilityEnergyProjectile) { + ((AbilityEnergyProjectile) sourceAbility).detachEntity(this); + } + sourceAbility = null; + Entity barrierOwner = barrier.getOwnerEntity(); if (barrierOwner != null) { setOwnerEntityId(barrierOwner.getEntityId()); @@ -517,9 +530,75 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { hitOnceEntities.clear(); lastHitTickByEntity.clear(); hitCount = 0; + reflected = true; return true; } + // ==================== REFLECTION SYNC ==================== + + /** + * Write reflection state for client sync. + * Subclasses should override writeProjectileReflectionData for type-specific fields. + */ + public NBTTagCompound writeReflectionData() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setInteger("OwnerId", ownerEntityId); + nbt.setDouble("PosX", posX); + nbt.setDouble("PosY", posY); + nbt.setDouble("PosZ", posZ); + nbt.setDouble("MotionX", motionX); + nbt.setDouble("MotionY", motionY); + nbt.setDouble("MotionZ", motionZ); + nbt.setInteger("InnerColor", getInnerColor()); + nbt.setInteger("OuterColor", getOuterColor()); + nbt.setBoolean("Reflected", reflected); + writeProjectileReflectionData(nbt); + return nbt; + } + + /** + * Apply reflection state from server sync. + * Subclasses should override applyProjectileReflectionData for type-specific fields. + */ + public void applyReflectionData(NBTTagCompound nbt) { + ownerEntityId = nbt.getInteger("OwnerId"); + double px = nbt.getDouble("PosX"); + double py = nbt.getDouble("PosY"); + double pz = nbt.getDouble("PosZ"); + setPosition(px, py, pz); + motionX = nbt.getDouble("MotionX"); + motionY = nbt.getDouble("MotionY"); + motionZ = nbt.getDouble("MotionZ"); + setInnerColor(nbt.getInteger("InnerColor")); + setOuterColor(nbt.getInteger("OuterColor")); + reflected = nbt.getBoolean("Reflected"); + syncPositionStateToCurrent(true); + hitOnceEntities.clear(); + lastHitTickByEntity.clear(); + hitCount = 0; + applyProjectileReflectionData(nbt); + } + + /** + * Subclass hook: write type-specific reflection data. + */ + protected void writeProjectileReflectionData(NBTTagCompound nbt) { + } + + /** + * Subclass hook: apply type-specific reflection data on client. + */ + protected void applyProjectileReflectionData(NBTTagCompound nbt) { + } + + /** + * Send reflection state to all tracking clients. + */ + protected void sendReflectionSync() { + if (worldObj == null || worldObj.isRemote) return; + ProjectileReflectPacket.sendToTracking(this, writeReflectionData()); + } + protected double[] getBarrierImpactNormal(EntityAbilityBarrier barrier, double velocityX, double velocityY, double velocityZ) { if (barrier instanceof EntityAbilityDome) { EntityAbilityDome dome = (EntityAbilityDome) barrier; @@ -2187,6 +2266,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.startZ = nbt.getDouble("StartZ"); this.targetEntityId = nbt.getInteger("TargetId"); + this.reflected = nbt.getBoolean("Reflected"); this.motionX = nbt.getDouble("MotionX"); this.motionY = nbt.getDouble("MotionY"); @@ -2243,6 +2323,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { nbt.setDouble("StartZ", startZ); nbt.setInteger("TargetId", targetEntityId); + nbt.setBoolean("Reflected", reflected); nbt.setDouble("MotionX", motionX); nbt.setDouble("MotionY", motionY); diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 787c6e204..a66f56fc4 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -39,6 +39,7 @@ import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket; import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket; import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; +import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import kamkeel.npcs.network.packets.player.AuctionActionPacket; @@ -592,6 +593,7 @@ public class PacketHandler { DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket()); DATA_PACKET.registerPacket(new EnergyChargeRemovePacket()); DATA_PACKET.registerPacket(new EnergyExplosionSpawnPacket()); + DATA_PACKET.registerPacket(new ProjectileReflectPacket()); DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket()); DATA_PACKET.registerPacket(new PlayerAbilityStatePacket()); DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index 10ae639e9..8a33b223e 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -76,4 +76,7 @@ public enum EnumDataPacket { // Energy Explosion Preview ENERGY_EXPLOSION_SPAWN, + + // Energy Projectile Reflection Sync + PROJECTILE_REFLECT, } diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energy/ProjectileReflectPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energy/ProjectileReflectPacket.java new file mode 100644 index 000000000..3950b0b28 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energy/ProjectileReflectPacket.java @@ -0,0 +1,79 @@ +package kamkeel.npcs.network.packets.data.energy; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.entity.EntityEnergyProjectile; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +import java.io.IOException; + +/** + * Syncs projectile reflection state to all tracking clients. + * Sent server→client after a successful barrier reflection so the client + * can update ownerEntityId, position, motion, colors, and type-specific + * reflection data (beam mode, laser direction, disc boomerang, etc.). + */ +public final class ProjectileReflectPacket extends AbstractPacket { + public static final String packetName = "Data|ProjectileReflect"; + + private int entityId; + private NBTTagCompound reflectData; + + public ProjectileReflectPacket() { + } + + public ProjectileReflectPacket(int entityId, NBTTagCompound reflectData) { + this.entityId = entityId; + this.reflectData = reflectData; + } + + @Override + public Enum getType() { + return EnumDataPacket.PROJECTILE_REFLECT; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + out.writeInt(entityId); + ByteBufUtils.writeNBT(out, reflectData); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + int id = in.readInt(); + NBTTagCompound nbt = ByteBufUtils.readNBT(in); + if (nbt == null) return; + + World world = Minecraft.getMinecraft().theWorld; + if (world == null) return; + + Entity entity = world.getEntityByID(id); + if (entity instanceof EntityEnergyProjectile) { + ((EntityEnergyProjectile) entity).applyReflectionData(nbt); + } + } + + public static void sendToTracking(EntityEnergyProjectile projectile, NBTTagCompound reflectData) { + if (projectile == null || reflectData == null) return; + PacketHandler.Instance.sendTracking( + new ProjectileReflectPacket(projectile.getEntityId(), reflectData), + projectile + ); + } +}