Reflection Barrier Fixes

This commit is contained in:
Kam committed 2026-02-24 23:18:59 -05:00
1 parent 92cfbbcc9d
commit 47ebc2889f
8 files changed
+288 -1

No files matched your search

@@ -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<E extends EntityEnergyProjectile>
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);
@@ -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
@@ -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
@@ -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
@@ -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<Integer> hitOnceEntities = new HashSet<Integer>();
@@ -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);
@@ -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());
@@ -76,4 +76,7 @@ public enum EnumDataPacket {
// Energy Explosion Preview
ENERGY_EXPLOSION_SPAWN,
// Energy Projectile Reflection Sync
PROJECTILE_REFLECT,
}
@@ -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
);
}
}