Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
Kam committed 2026-02-25 04:32:24 -05:00
commit ae265c83b0
42 files changed
+1106 -478

No files matched your search

+1 -1
Submodule src/api updated: 7f95b90fc2...52cfe77bb7.
@@ -0,0 +1,42 @@
package kamkeel.npcs.client.command;
import kamkeel.npcs.util.CNPCDebug;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.ChatComponentText;
/**
* Client-only command to toggle debug logging.
* Registered via ClientCommandHandler so it runs entirely on the client
* without sending a packet to the server.
*/
public class CommandCNPCDebugger extends CommandBase {
@Override
public String getCommandName() {
return "cnpcdebugger";
}
@Override
public String getCommandUsage(ICommandSender sender) {
return "/cnpcdebugger <type> - Toggle client debug logging for a type (e.g. energy)";
}
@Override
public int getRequiredPermissionLevel() {
return 0;
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
if (args.length == 0) {
sender.addChatMessage(new ChatComponentText("\u00A7cUsage: /cnpcdebugger <type>"));
return;
}
String type = args[0].toLowerCase();
boolean newState = CNPCDebug.toggleClient(type);
String stateText = newState ? "\u00A7aENABLED" : "\u00A7cDISABLED";
sender.addChatMessage(new ChatComponentText("\u00A77[CNPC+] \u00A7eClient debug '" + type + "' " + stateText));
}
}
@@ -41,7 +41,7 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
double endY = laser.getEndY();
double endZ = laser.getEndZ();
float width = laser.getLaserWidth();
float alpha = laser.getLingerAlpha();
float alpha = 1.0f;
// Calculate render offset (entity position is used as reference)
double offsetX = x - laser.posX;
@@ -95,6 +95,25 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
double vertY = dz * horzX - dx * horzZ;
double vertZ = dx * horzY - dy * horzX;
// Rotate perpendicular vectors around beam axis (Rodrigues' rotation)
float rotationSpeed = laser.getRotationSpeed();
if (rotationSpeed != 0.0f) {
double angle = (laser.ticksExisted + partialTicks) * rotationSpeed * Math.PI / 180.0;
double cosA = Math.cos(angle);
double sinA = Math.sin(angle);
double rHorzX = horzX * cosA + vertX * sinA;
double rHorzY = horzY * cosA + vertY * sinA;
double rHorzZ = horzZ * cosA + vertZ * sinA;
double rVertX = -horzX * sinA + vertX * cosA;
double rVertY = -horzY * sinA + vertY * cosA;
double rVertZ = -horzZ * sinA + vertZ * cosA;
horzX = rHorzX; horzY = rHorzY; horzZ = rHorzZ;
vertX = rVertX; vertY = rVertY; vertZ = rVertZ;
}
// Inner scale defines the core width
float innerScale = 0.6f;
float innerWidth = width * innerScale;
@@ -134,7 +153,7 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
}
private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks) {
float headSize = laser.getLaserWidth() * 1.5f;
float headSize = laser.getLaserWidth() * 0.5f;
float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks);
float size = headSize * chargeProgress;
@@ -52,6 +52,7 @@ public class CommandKamkeel extends CommandBase {
registerCommand(new AuctionCommand());
if (ConfigMain.AttributesEnabled)
registerCommand(new AttributeCommand());
registerCommand(new DebugCommand());
}
public void registerCommand(CommandKamkeelBase command) {
@@ -0,0 +1,41 @@
package kamkeel.npcs.command;
import kamkeel.npcs.util.CNPCDebug;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import static kamkeel.npcs.util.ColorUtil.sendResult;
public class DebugCommand extends CommandKamkeelBase {
@Override
public String getCommandName() {
return "debug";
}
@Override
public String getDescription() {
return "Toggle debug logging types";
}
@Override
public String getUsage() {
return "<type>";
}
@Override
public boolean runSubCommands() {
return false;
}
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (args.length == 0) {
throw new CommandException("Usage: /kam debug <type> (e.g. energy)");
}
String type = args[0].toLowerCase();
boolean newState = CNPCDebug.toggleServer(type);
sendResult(sender, "Server debug '" + type + "' " + (newState ? "\u00A7aENABLED" : "\u00A7cDISABLED"));
}
}
@@ -32,7 +32,7 @@ import kamkeel.npcs.controllers.data.ability.type.AbilityVortex;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaserShot;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaser;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityOrb;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper;
@@ -143,7 +143,7 @@ public class AbilityController implements IAbilityHandler {
registerType("cnpc:sweeper", AbilitySweeper::new);
registerType("cnpc:orb", AbilityOrb::new);
registerType("cnpc:disc", AbilityDisc::new);
registerType("cnpc:laser_shot", AbilityLaserShot::new);
registerType("cnpc:laser_shot", AbilityLaser::new);
registerType("cnpc:beam", AbilityBeam::new);
// registerType("cnpc:slicer", AbilitySlicer::new);
@@ -1132,10 +1132,11 @@ public class AbilityController implements IAbilityHandler {
*/
public boolean fireOnAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
float damage, float knockback, float knockbackUp,
double knockbackDirX, double knockbackDirZ) {
double knockbackDirX, double knockbackDirZ,
float damageMultiplier) {
for (IAbilityExtender ext : extenders) {
if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp,
knockbackDirX, knockbackDirZ)) {
knockbackDirX, knockbackDirZ, damageMultiplier)) {
return true;
}
}
@@ -476,7 +476,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
// Check for ability extenders (e.g., DBC Addon damage routing)
boolean handled = AbilityController.Instance.fireOnAbilityDamage(
this, caster, hitEntity, damage, knockback, knockbackUp,
knockbackDirX, knockbackDirZ);
knockbackDirX, knockbackDirZ, 1.0f);
if (!handled) {
// Default damage path
if (caster instanceof EntityNPCInterface) {
@@ -26,10 +26,12 @@ public class ProjectileData {
* If colorOverride is true, returns a copy with overridden inner/outer colors.
*/
public EnergyDisplayData resolveDisplay(EnergyDisplayData primary) {
if (!colorOverride) return primary;
// Always return a copy so entity mutations never bleed back into the ability template.
EnergyDisplayData resolved = primary.copy();
resolved.innerColor = this.innerColor;
resolved.outerColor = this.outerColor;
if (colorOverride) {
resolved.innerColor = this.innerColor;
resolved.outerColor = this.outerColor;
}
return resolved;
}
@@ -69,11 +69,15 @@ public interface IAbilityExtender {
* @param knockbackUp The vertical knockback
* @param knockbackDirX The X component of knockback direction
* @param knockbackDirZ The Z component of knockback direction
* @param damageMultiplier Accumulated proportional damage multiplier (0.0-1.0).
* 1.0 means full damage (no reduction). Extenders that recalculate
* damage from stats should multiply by this value.
* @return true if damage was handled (skip default), false to pass to next extender or default
*/
default boolean onAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
float damage, float knockback, float knockbackUp,
double knockbackDirX, double knockbackDirZ) {
double knockbackDirX, double knockbackDirZ,
float damageMultiplier) {
return false;
}
@@ -128,7 +128,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
targetZ = caster.posZ;
} else if (targetingMode == TargetingMode.AOE_TARGET && target != null && !target.isDead) {
targetX = target.posX;
targetY = target.posY;
targetY = findGroundLevel(caster.worldObj, target.posX, target.posY, target.posZ, 64);
targetZ = target.posZ;
}
}
@@ -161,7 +161,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
targetZ = caster.posZ;
} else if (targetingMode == TargetingMode.AOE_TARGET && target != null && !target.isDead) {
targetX = target.posX;
targetY = target.posY;
targetY = findGroundLevel(caster.worldObj, target.posX, target.posY, target.posZ, 64);
targetZ = target.posZ;
} else {
targetX = caster.posX;
@@ -186,11 +186,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
if (isAirborne) {
double groundY = findGroundLevel(caster.worldObj, caster.posX, caster.posY, caster.posZ, 64);
highEnoughForAirSlam = (caster.posY - groundY) >= 3.0;
System.out.println("[Slam] AIR CHECK: posY=" + caster.posY + " groundY=" + groundY + " dist=" + (caster.posY - groundY) + " highEnough=" + highEnoughForAirSlam);
}
System.out.println("[Slam] EXECUTE: onGround=" + caster.onGround + " isAirborne=" + isAirborne + " highEnough=" + highEnoughForAirSlam + " -> " + (isAirborne && highEnoughForAirSlam ? "AIR SLAM" : "GROUND SLAM"));
if (isAirborne && highEnoughForAirSlam) {
// Air slam: player is already airborne (flying, falling, jumping) and high enough
airSlam = true;
@@ -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);
@@ -16,39 +16,39 @@ import kamkeel.npcs.entity.EntityAbilityLaser;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import noppes.npcs.api.ability.type.IAbilityLaserShot;
import noppes.npcs.api.ability.type.IAbilityLaser;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.List;
/**
* Laser Shot ability: Fast expanding thin line.
* Travels in a straight line from origin.
* Laser ability: Sweeping beam that follows the caster's look vector.
* Expands from origin to maxLength, then stays active until maxLifetime expires.
* Single-projectile only (always projectileCount=1).
*/
public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser> implements IAbilityLaserShot {
public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> implements IAbilityLaser {
private float laserWidth = 0.3f;
private float expansionSpeed = 3.0f;
private int lingerTicks = 8;
private float maxLength = 32.0f;
public AbilityLaserShot() {
public AbilityLaser() {
super(
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f, HitType.PIERCE, 5),
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 4.0f),
new EnergyCombatData(4.0f, 0.5f, 0.05f, false, 2.0f, 0.5f, HitType.MULTI, 5),
new EnergyHomingData(),
new EnergyLifespanData(150.0f, 100)
);
this.typeId = "ability.cnpc.laser_shot";
this.name = "Laser Shot";
this.name = "Laser";
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 35.0f;
this.maxRange = 30.0f;
this.minRange = 3.0f;
this.cooldownTicks = 0;
this.windUpTicks = 15;
this.lockMovement = LockMode.WINDUP_AND_ACTIVE;
this.rotationMode = RotationMode.TRACK;
this.rotationPhase = LockMode.WINDUP;
this.rotationPhase = LockMode.WINDUP_AND_ACTIVE;
this.telegraphType = TelegraphType.LINE;
this.showTelegraph = true;
this.windUpAnimationName = "Ability_Laser_Windup";
@@ -69,7 +69,8 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
public int getMaxPreviewDuration() {
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 60) : 60;
int lifetime = lifespanData.maxLifetime;
return lifetime > 0 ? Math.min(lifetime, 60) : 60;
}
/**
@@ -101,7 +102,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
public float getTelegraphLength() {
return lifespanData.maxDistance;
return maxLength;
}
@Override
@@ -119,13 +120,12 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
laserWidth,
resolved, combatData, lightningData, lifespanData,
expansionSpeed, lingerTicks);
expansionSpeed, maxLength);
return laser;
}
@Override
protected void fireEntity(EntityAbilityLaser laser, EntityLivingBase target) {
laser.setLockVerticalDirection(true);
laser.startMoving(target);
}
@@ -137,7 +137,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
protected void setupEntityPreview(EntityAbilityLaser laser, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
laser.setupPreview(caster, laserWidth, resolved, lightningData, projData.anchor, windUpTicks, expansionSpeed, lifespanData.maxDistance);
laser.setupPreview(caster, laserWidth, resolved, lightningData, projData.anchor, windUpTicks, expansionSpeed, maxLength);
}
@Override
@@ -156,14 +156,14 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
nbt.setFloat("laserWidth", laserWidth);
nbt.setFloat("expansionSpeed", expansionSpeed);
nbt.setInteger("lingerTicks", lingerTicks);
nbt.setFloat("maxLength", maxLength);
}
@Override
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
this.laserWidth = nbt.getFloat("laserWidth");
this.expansionSpeed = nbt.getFloat("expansionSpeed");
this.lingerTicks = nbt.getInteger("lingerTicks");
this.maxLength = nbt.hasKey("maxLength") ? nbt.getFloat("maxLength") : 32.0f;
}
// ==================== TYPE-SPECIFIC GETTERS ====================
@@ -184,12 +184,12 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
this.expansionSpeed = expansionSpeed;
}
public int getLingerTicks() {
return lingerTicks;
public float getMaxLength() {
return maxLength;
}
public void setLingerTicks(int lingerTicks) {
this.lingerTicks = lingerTicks;
public void setMaxLength(float maxLength) {
this.maxLength = maxLength;
}
// ==================== TYPE-SPECIFIC GUI ====================
@@ -207,11 +207,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
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.row(
FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks).range(1, 200),
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200)
));
defs.add(FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).range(1.0f, 500.0f));
defs.add(FieldDef.floatField("ability.maxLength", this::getMaxLength, this::setMaxLength).range(1.0f, 500.0f));
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)
@@ -2,6 +2,7 @@ package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
import kamkeel.npcs.util.CNPCDebug;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -617,4 +618,31 @@ public abstract class EntityAbilityBarrier extends EntityEnergyAbility {
this.dataWatcher.updateObject(DW_CHARGING, (byte) (charging ? 1 : 0));
barrierData.readNBT(nbt);
}
// ==================== DEBUG LOGGING ====================
/**
* Debug log called every tick for barriers. Subclasses override debugLogBarrierExtra().
*/
protected void debugLogBarrierTick() {
boolean isClient = worldObj.isRemote;
if (isClient ? !CNPCDebug.isClientEnabled("energy") : !CNPCDebug.isServerEnabled("energy"))
return;
String className = getClass().getSimpleName();
String base = String.format("[%s id=%d tick=%d] pos=(%.2f,%.2f,%.2f) charging=%b health=%.1f",
className, getEntityId(), ticksAlive,
posX, posY, posZ, isCharging(), currentHealth);
String extra = debugLogBarrierExtra();
String full = extra.isEmpty() ? base : base + " " + extra;
CNPCDebug.log("energy", isClient, full);
}
/**
* Subclass hook for barrier-specific debug data.
*/
protected String debugLogBarrierExtra() {
return "";
}
}
@@ -16,7 +16,6 @@ import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.LogWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -61,9 +60,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
private int trailFadeTime = 20; // Ticks for trail to fully fade
private List<Integer> trailPointAges = new ArrayList<>();
// Debug logging
private static final boolean DEBUG_LOGGING = false;
public EntityAbilityBeam(World world) {
super(world);
}
@@ -147,7 +143,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Beam-specific properties from homing data
this.homingData = homing;
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
this.beamWidth = beamWidth;
this.headSize = headSize;
@@ -160,10 +156,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Add initial trail point at origin (relative 0,0,0)
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
if (DEBUG_LOGGING && !world.isRemote) {
LogWriter.info("[Beam] Created at origin " + x + ", " + y + ", " + z + " maxDist=" + lifespan.maxDistance);
}
// Calculate initial velocity toward target
calculateInitialVelocity(owner, target, x, y, z);
}
@@ -222,11 +214,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
if (isFadingMode()) trailPointAges.add(0);
if (DEBUG_LOGGING && !worldObj.isRemote) {
LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ +
") headOffset=(" + headOffsetX + "," + headOffsetY + "," + headOffsetZ +
") motion=(" + motionX + "," + motionY + "," + motionZ + ")");
}
}
@Override
@@ -237,11 +224,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
headOffsetY * headOffsetY +
headOffsetZ * headOffsetZ
);
boolean exceeded = distFromOrigin >= getMaxDistance();
if (exceeded && !worldObj.isRemote && DEBUG_LOGGING) {
LogWriter.info("[Beam] DEAD: Max distance exceeded. dist=" + distFromOrigin + " max=" + getMaxDistance());
}
return exceeded;
return distFromOrigin >= getMaxDistance();
}
@Override
@@ -312,17 +295,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Add trail point
addTrailPoint();
// Debug log periodically
if (DEBUG_LOGGING && ticksExisted % 20 == 0) {
LogWriter.info("[Beam] Server tick=" + ticksExisted + " headOffset=(" +
String.format("%.2f", headOffsetX) + "," +
String.format("%.2f", headOffsetY) + "," +
String.format("%.2f", headOffsetZ) + ") origin=(" +
String.format("%.2f", startX) + "," +
String.format("%.2f", startY) + "," +
String.format("%.2f", startZ) + ") trail=" + getActiveTrailSize());
}
// Skip collision checks on first few ticks
if (ticksExisted > 2) {
checkBlockCollision(headWorldX, headWorldY, headWorldZ);
@@ -528,9 +500,27 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
Math.max(prevHeadWorldZ, headZ) + hitSize
);
if (processEntitiesInHitBox(hitBox, headX, headY, headZ) && DEBUG_LOGGING) {
LogWriter.info("[Beam] DEAD: Entity collision at tick " + ticksExisted);
}
processEntitiesInHitBox(hitBox, headX, headY, headZ);
}
// ==================== DEBUG ====================
@Override
protected String debugLogExtra() {
double headWorldX = startX + headOffsetX;
double headWorldY = startY + headOffsetY;
double headWorldZ = startZ + headOffsetZ;
return String.format("headOff=(%.2f,%.2f,%.2f) prevHead=(%.2f,%.2f,%.2f) " +
"origin=(%.2f,%.2f,%.2f) headWorld=(%.2f,%.2f,%.2f) " +
"trail=%d mode=%s anchored=%b beamW=%.2f headSz=%.2f " +
"motion=(%.3f,%.3f,%.3f)",
headOffsetX, headOffsetY, headOffsetZ,
prevHeadOffsetX, prevHeadOffsetY, prevHeadOffsetZ,
startX, startY, startZ,
headWorldX, headWorldY, headWorldZ,
getActiveTrailSize(),
beamMode.name(), isAnchoredMode(), beamWidth, headSize,
motionX, motionY, motionZ);
}
// ==================== GETTERS FOR RENDERER ====================
@@ -637,6 +627,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
@@ -64,7 +64,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
// Initialize base properties
initProjectile(owner, target, x, y, z, 1.0f, display, combat, lightning, lifespan);
this.homingData = homing;
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
this.boomerang = boomerang;
this.boomerangDelay = boomerangDelay;
@@ -330,6 +330,17 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
updateRotation();
}
// ==================== DEBUG ====================
@Override
protected String debugLogExtra() {
return String.format("motion=(%.3f,%.3f,%.3f) radius=%.2f thickness=%.2f " +
"boomerang=%b returning=%b vertical=%b ticksSinceMiss=%d",
motionX, motionY, motionZ,
discRadius, discThickness,
boomerang, returning, vertical, ticksSinceMiss);
}
// ==================== GETTERS ====================
public float getDiscRadius() {
@@ -395,6 +406,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
@@ -50,10 +50,11 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
this.stepHeight = 0.0F;
this.ownerEntityId = owner.getEntityId();
this.setDomeRadius(domeRadius);
this.displayData = display;
this.lightningData = lightning;
this.barrierData = barrier;
this.currentHealth = barrier.maxHealth;
// Defensive copy: entities must never share data objects with the source ability.
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData();
this.currentHealth = this.barrierData.maxHealth;
this.setPosition(x, y, z);
}
@@ -121,6 +122,8 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
if (worldObj.isRemote && barrierData.meleeEnabled && !isCharging()) {
adjustMeleeBoundingBox();
}
debugLogBarrierTick();
}
// ==================== INCOMING CHECK ====================
@@ -296,6 +299,14 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
double surfaceX = posX + nx * pushDist;
double surfaceY = posY + ny * pushDist - ent.height * 0.5;
double surfaceZ = posZ + nz * pushDist;
// Prevent grounded entities from being teleported below the floor.
// The spherical surface calculation can produce a Y below ground level
// near the dome's equator, and setPosition bypasses collision detection.
if (ent.onGround && surfaceY < ent.posY) {
surfaceY = ent.posY;
}
teleportEntity(ent, surfaceX, surfaceY, surfaceZ);
// Cancel radial velocity and add corrective push
@@ -547,6 +558,14 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
return surfaceDist * surfaceDist;
}
// ==================== DEBUG ====================
@Override
protected String debugLogBarrierExtra() {
return String.format("radius=%.2f targetRadius=%.2f follow=%b",
domeRadius, targetDomeRadius, followCaster);
}
// ==================== GETTERS ====================
public float getDomeRadius() {
@@ -1,5 +1,6 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
@@ -20,8 +21,10 @@ import noppes.npcs.entity.EntityNPCInterface;
import java.util.List;
/**
* Laser projectile - fast expanding thin line.
* No homing, travels in a straight line from origin to max distance.
* Laser entity - sweeping beam that follows the caster's look vector.
* Expands from origin at expansionSpeed until reaching maxLength,
* then stays active until maxLifetime expires. Always tracks the owner's
* look direction so the beam can be swept mid-shot.
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
@@ -30,23 +33,26 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// Laser-specific properties
private float laserWidth = 0.2f;
private float expansionSpeed = 2.0f; // Blocks per tick
private int lingerTicks = 10; // How long laser stays visible after reaching max
private float maxLength = 32.0f; // Maximum beam reach in blocks
// State
private float currentLength = 0.0f;
private float desiredLength = 0.0f; // Intended beam reach (grows to maxLength)
private float currentLength = 0.0f; // Actual length after block truncation (visual + collision)
private boolean fullyExtended = false;
private int ticksSinceFullExtension = 0;
// Direction (normalized)
private double dirX, dirY, dirZ;
// Lock vertical direction after firing (only update yaw, keep pitch fixed)
private boolean lockVerticalDirection = false;
private int reflectedLockTicks = 0;
// End point for rendering
private double endX, endY, endZ;
// Block hit deduplication — prevents event/explosion spam every tick
private int lastBlockHitX = Integer.MIN_VALUE;
private int lastBlockHitY = Integer.MIN_VALUE;
private int lastBlockHitZ = Integer.MIN_VALUE;
private int lastExplosionTick = -100;
private static final int EXPLOSION_COOLDOWN = 10; // ticks between explosive impacts
public EntityAbilityLaser(World world) {
super(world);
}
@@ -59,17 +65,15 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
float laserWidth,
EnergyDisplayData display, EnergyCombatData combat,
EnergyLightningData lightning, EnergyLifespanData lifespan,
float expansionSpeed, int lingerTicks) {
float expansionSpeed, float maxLength) {
super(world);
// Initialize base properties (laser doesn't rotate)
initProjectile(owner, target, x, y, z, laserWidth, display, combat, lightning, lifespan);
this.displayData.rotationSpeed = 0.0f;
// Laser-specific properties
this.laserWidth = laserWidth;
this.expansionSpeed = expansionSpeed;
this.lingerTicks = lingerTicks;
this.maxLength = maxLength;
// Calculate direction toward target or forward
if (target != null) {
@@ -117,12 +121,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
protected void updateRotation() {
// Laser doesn't rotate
// Beam rotation is handled in the renderer via perpendicular vector rotation
}
@Override
protected boolean checkMaxDistance() {
// Laser handles distance differently - it expands to max then lingers
// Laser handles distance via maxLength, not max distance
return false;
}
@@ -133,66 +137,57 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
return;
}
// After reflection, hold trajectory briefly so the rebound is visible.
if (reflectedLockTicks > 0) {
reflectedLockTicks--;
} else {
// Track owner's anchor position and rotation each tick
// so the laser follows the NPC's facing direction
// Origin/direction tracking runs on BOTH sides so the client can render the beam.
// Once reflected, never resume owner-tracking — the laser flies in a fixed direction.
if (!reflected) {
updateLaserOriginAndDirection();
}
// Expand the intended reach — runs on both sides for visual sync
if (!fullyExtended) {
// Expand the laser
currentLength += expansionSpeed;
if (currentLength >= getMaxDistance()) {
currentLength = getMaxDistance();
desiredLength += expansionSpeed;
if (desiredLength >= maxLength) {
desiredLength = maxLength;
fullyExtended = true;
}
}
// Update end point
endX = startX + dirX * currentLength;
endY = startY + dirY * currentLength;
endZ = startZ + dirZ * currentLength;
// Start from full desired length, then truncate at block hits
currentLength = desiredLength;
// Check for block collision along the new segment
if (!worldObj.isRemote) {
checkBlockCollision();
// Re-evaluate barriers against the current beam segment before entity hits.
// This prevents first-tick pass-through where the pre-update barrier check
// still sees a zero-length beam.
if (checkBarrierCollisionForCurrentSegment()) {
return;
}
checkEntityCollisionAlongLine();
// Block raytrace — truncate beam at first solid block (both sides)
checkBlockCollision();
// Update end point
updateEndPoint();
// Server-only: barrier collision, entity collision, death
if (!worldObj.isRemote) {
if (checkLaserBarrierCollision()) {
return;
}
} else {
// Laser is fully extended, count down linger time
ticksSinceFullExtension++;
// Update end point during linger (origin/direction may still change)
endX = startX + dirX * currentLength;
endY = startY + dirY * currentLength;
endZ = startZ + dirZ * currentLength;
if (ticksSinceFullExtension >= lingerTicks) {
if (!worldObj.isRemote) {
EventHooks.onEnergyProjectileExpired(this);
}
this.setDead();
}
checkEntityCollisionAlongLine();
// Death handled by base class deathWorldTime (from lifespanData.maxLifetime)
}
}
/**
* Update end point from current origin + direction * length.
*/
private void updateEndPoint() {
endX = startX + dirX * currentLength;
endY = startY + dirY * currentLength;
endZ = startZ + dirZ * currentLength;
}
/**
* Update laser origin and direction from the owner's current look rotation.
* Called each tick so the laser origin stays centered on look vector.
* Called each tick so the laser follows the caster's aim (sweeping beam).
*/
private void updateLaserOriginAndDirection() {
Entity owner = getOwnerEntity();
if (owner == null || !(owner instanceof EntityLivingBase)) {
// On client, fall back to entity tracker interpolation when owner not loaded
if (worldObj != null && worldObj.isRemote) {
handleClientInterpolation();
}
@@ -200,7 +195,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
}
EntityLivingBase livingOwner = (EntityLivingBase) owner;
// Use owner look vector (head/eye aim) for direction updates so hit logic matches visuals.
// Get owner look vector for direction
Vec3 look = livingOwner.getLookVec();
if (look == null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
@@ -212,42 +207,28 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
);
}
if (lockVerticalDirection) {
// Keep current vertical component, but align horizontal direction to current look.
double lookHorizontalLen = Math.sqrt(look.xCoord * look.xCoord + look.zCoord * look.zCoord);
if (lookHorizontalLen > 0.0001) {
double currentHorizontalLen = Math.sqrt(dirX * dirX + dirZ * dirZ);
if (currentHorizontalLen < 0.0001) {
currentHorizontalLen = Math.sqrt(Math.max(0.0001, 1.0 - dirY * dirY));
}
dirX = (look.xCoord / lookHorizontalLen) * currentHorizontalLen;
dirZ = (look.zCoord / lookHorizontalLen) * currentHorizontalLen;
}
} else {
dirX = look.xCoord;
dirY = look.yCoord;
dirZ = look.zCoord;
}
// Set direction directly from look vector (no lockVerticalDirection split)
dirX = look.xCoord;
dirY = look.yCoord;
dirZ = look.zCoord;
// Keep direction normalized for consistent expansion and collision math.
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
double dirLen = Math.sqrt(direction.xCoord * direction.xCoord + direction.yCoord * direction.yCoord + direction.zCoord * direction.zCoord);
// Keep direction normalized
double dirLen = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
if (dirLen > 0.0001) {
direction = Vec3.createVectorHelper(
direction.xCoord / dirLen,
direction.yCoord / dirLen,
direction.zCoord / dirLen
);
dirX /= dirLen;
dirY /= dirLen;
dirZ /= dirLen;
} else {
direction = look != null ? look : Vec3.createVectorHelper(1.0, 0.0, 0.0);
dirX = 1.0;
dirY = 0.0;
dirZ = 0.0;
}
dirX = direction.xCoord;
dirY = direction.yCoord;
dirZ = direction.zCoord;
// Keep origin centered on look vector while active and clear owner bbox.
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
setLookVectorLaunchPosition(livingOwner, direction, false);
// Lower beam origin slightly so it doesn't obscure the player's crosshair
posY -= 0.15;
syncStartPositionToCurrent();
syncPositionStateToCurrent(false);
}
@@ -255,22 +236,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
public void startMoving(EntityLivingBase target) {
beginLookVectorLaunch(false);
desiredLength = 0.0f;
currentLength = 0.0f;
fullyExtended = false;
ticksSinceFullExtension = 0;
lastBlockHitX = Integer.MIN_VALUE;
lastBlockHitY = Integer.MIN_VALUE;
lastBlockHitZ = Integer.MIN_VALUE;
lastExplosionTick = -100;
// Set initial direction: target-based when available, otherwise owner look vector.
// Set initial direction: owner look vector (sweeping beam always follows look)
Vec3 look = getOwnerLookVector();
if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
if (look != null) {
dirX = look.xCoord;
dirY = look.yCoord;
dirZ = look.zCoord;
} else {
dirX = 1.0;
dirY = 0.0;
dirZ = 0.0;
}
if (look != null) {
dirX = look.xCoord;
dirY = look.yCoord;
dirZ = look.zCoord;
} else if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
dirX = 1.0;
dirY = 0.0;
dirZ = 0.0;
}
// Initialize end point at start (will expand from here)
@@ -294,40 +277,69 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
}
private void checkBlockCollision() {
MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ);
if (currentLength <= 0) return;
double traceEndX = startX + dirX * currentLength;
double traceEndY = startY + dirY * currentLength;
double traceEndZ = startZ + dirZ * currentLength;
MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, traceEndX, traceEndY, traceEndZ);
if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
if (!worldObj.isRemote) {
EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ);
}
// Laser stops at block
currentLength = (float) Math.sqrt(
// Truncate beam at block hit
float hitDist = (float) Math.sqrt(
(blockHit.hitVec.xCoord - startX) * (blockHit.hitVec.xCoord - startX) +
(blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) +
(blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ)
);
endX = blockHit.hitVec.xCoord;
endY = blockHit.hitVec.yCoord;
endZ = blockHit.hitVec.zCoord;
fullyExtended = true;
if (isExplosive()) {
// Explode at hit point
double oldPosX = posX;
double oldPosY = posY;
double oldPosZ = posZ;
posX = endX;
posY = endY;
posZ = endZ;
doExplosion();
posX = oldPosX;
posY = oldPosY;
posZ = oldPosZ;
if (hitDist < currentLength) {
currentLength = hitDist;
endX = blockHit.hitVec.xCoord;
endY = blockHit.hitVec.yCoord;
endZ = blockHit.hitVec.zCoord;
}
// Server-only: fire event and explosion only when block changes or cooldown expires
if (!worldObj.isRemote) {
int bx = blockHit.blockX;
int by = blockHit.blockY;
int bz = blockHit.blockZ;
boolean newBlock = (bx != lastBlockHitX || by != lastBlockHitY || bz != lastBlockHitZ);
// Fire block impact event only when the beam hits a different block
if (newBlock) {
lastBlockHitX = bx;
lastBlockHitY = by;
lastBlockHitZ = bz;
EventHooks.onEnergyProjectileBlockImpact(this, bx, by, bz);
}
// Explosive: fire on cooldown so sweeping doesn't explode every tick
if (isExplosive() && (ticksExisted - lastExplosionTick) >= EXPLOSION_COOLDOWN) {
lastExplosionTick = ticksExisted;
double oldPosX = posX;
double oldPosY = posY;
double oldPosZ = posZ;
posX = endX;
posY = endY;
posZ = endZ;
doExplosion();
posX = oldPosX;
posY = oldPosY;
posZ = oldPosZ;
}
}
} else if (!worldObj.isRemote) {
// Beam is not hitting any block — reset tracking so next block contact triggers fresh
lastBlockHitX = Integer.MIN_VALUE;
lastBlockHitY = Integer.MIN_VALUE;
lastBlockHitZ = Integer.MIN_VALUE;
}
}
private void checkEntityCollisionAlongLine() {
if (currentLength <= 0) return;
// Expand search AABB by entity sizes to ensure we find all potential targets
double expand = Math.max(laserWidth, 1.0);
double minX = Math.min(startX, endX) - expand;
@@ -349,7 +361,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// Check if entity's bounding box intersects with the laser line
if (isEntityOnLine(entity)) {
if (processEntityHit(entity, entity.posX, entity.posY + entity.height * 0.5, entity.posZ)) {
// Stop at impact point and terminate
// SINGLE hit or maxHits reached — truncate beam at impact and die
double dx = entity.posX - startX;
double dy = (entity.posY + entity.height * 0.5) - startY;
double dz = entity.posZ - startZ;
@@ -360,8 +372,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
endZ = startZ + dirZ * currentLength;
return;
}
// PIERCE/MULTI continue through remaining entities.
// MULTI/PIERCE continue through remaining entities
}
}
}
@@ -391,24 +402,22 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// ==================== BARRIER COLLISION ====================
/**
* Override barrier collision for lasers. The base class uses projectile position/velocity
* which doesn't work for lasers (posX/Y/Z stays at origin, motionX/Y/Z is zero).
* Instead, performs line-sphere intersection against domes.
* Disable the base class barrier check — it uses position/velocity which doesn't work
* for lasers (posX/Y/Z stays at origin, motionX/Y/Z is zero). We handle barriers
* exclusively in updateProjectile() via checkLaserBarrierCollision().
*/
@Override
@SuppressWarnings("unchecked")
protected boolean checkBarrierCollision() {
return checkBarrierCollisionInternal(false);
}
private boolean checkBarrierCollisionForCurrentSegment() {
return checkBarrierCollisionInternal(true);
return false;
}
/**
* Laser-specific barrier collision using line-segment intersection.
* Called from updateProjectile() every tick.
*/
@SuppressWarnings("unchecked")
private boolean checkBarrierCollisionInternal(boolean allowWhenFullyExtended) {
private boolean checkLaserBarrierCollision() {
if (currentLength <= 0) return false;
if (fullyExtended && !allowWhenFullyExtended) return false;
List<EntityAbilityBarrier> barriers = EntityAbilityBarrier.getActiveBarriers(worldObj);
for (EntityAbilityBarrier barrier : barriers) {
@@ -441,15 +450,13 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
endZ = startZ + dirZ * currentLength;
}
if (handleBarrierHitOutcome(dome, hitOutcome)) {
if (handleBarrierHitOutcome(dome, hitOutcome, damage)) {
return true;
}
return false;
}
} else {
// For line-based lasers, use a swept segment against generic barrier checks.
// Panel/Dome logic expects previous+current positions and motion; map those
// to [start -> end] each tick.
double segMotionX = endX - startX;
double segMotionY = endY - startY;
double segMotionZ = endZ - startZ;
@@ -468,13 +475,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
}
if (hitOutcome.result != EntityAbilityBarrier.ProjectileHitResult.BROKEN) {
// Keep current segment length at the exact collision tick position.
endX = startX + dirX * currentLength;
endY = startY + dirY * currentLength;
endZ = startZ + dirZ * currentLength;
}
if (handleBarrierHitOutcome(barrier, hitOutcome)) {
if (handleBarrierHitOutcome(barrier, hitOutcome, damage)) {
return true;
}
return false;
@@ -539,17 +545,29 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
setPosition(startX, startY, startZ);
syncPositionStateToCurrent(true);
desiredLength = 0.0f;
currentLength = 0.0f;
fullyExtended = false;
ticksSinceFullExtension = 0;
endX = startX;
endY = startY;
endZ = startZ;
lastBlockHitX = Integer.MIN_VALUE;
lastBlockHitY = Integer.MIN_VALUE;
lastBlockHitZ = Integer.MIN_VALUE;
lastExplosionTick = -100;
// Reset lifetime so reflected laser gets a fresh maxLifetime from the base class
deathWorldTime = -1;
reflectedLockTicks = Math.max(reflectedLockTicks, 10);
barrierImpactPauseTicks = 0;
barrierImpactDestroyOnResume = false;
// Detach from source ability so the caster is freed from active-phase locks.
if (sourceAbility instanceof AbilityEnergyProjectile) {
((AbilityEnergyProjectile<?>) sourceAbility).detachEntity(this);
}
sourceAbility = null;
Entity barrierOwner = barrier.getOwnerEntity();
if (barrierOwner != null) {
setOwnerEntityId(barrierOwner.getEntityId());
@@ -566,6 +584,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
hitOnceEntities.clear();
lastHitTickByEntity.clear();
reflected = true;
return true;
}
@@ -588,7 +607,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
if (originDistSq < (double) radius * radius) return -1;
// Solve: |start + t*dir - C|^2 = R^2
// a*t^2 + b*t + c = 0
double a = dirX * dirX + dirY * dirY + dirZ * dirZ;
double b = 2.0 * (dirX * ocX + dirY * ocY + dirZ * ocZ);
double c = originDistSq - (double) radius * radius;
@@ -606,15 +624,18 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
return -1;
}
// ==================== GETTERS FOR RENDERER ====================
// ==================== DEBUG ====================
public void setLockVerticalDirection(boolean lock) {
this.lockVerticalDirection = lock;
@Override
protected String debugLogExtra() {
return String.format("dir=(%.3f,%.3f,%.3f) desired=%.2f current=%.2f max=%.2f " +
"fullyExtended=%b end=(%.2f,%.2f,%.2f) width=%.2f expSpd=%.2f origin=(%.2f,%.2f,%.2f)",
dirX, dirY, dirZ, desiredLength, currentLength, maxLength,
fullyExtended, endX, endY, endZ, laserWidth, expansionSpeed,
startX, startY, startZ);
}
public boolean isLockVerticalDirection() {
return lockVerticalDirection;
}
// ==================== GETTERS / SETTERS ====================
public float getLaserWidth() {
return laserWidth;
@@ -632,12 +653,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
this.expansionSpeed = speed;
}
public int getLingerTicks() {
return lingerTicks;
public float getMaxLength() {
return maxLength;
}
public void setLingerTicks(int ticks) {
this.lingerTicks = ticks;
public void setMaxLength(float maxLength) {
this.maxLength = maxLength;
}
public void setDirection(double x, double y, double z) {
@@ -690,15 +711,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
return dirZ;
}
/**
* Get the alpha for fade-out effect during linger.
*/
public float getLingerAlpha() {
if (!fullyExtended) return 1.0f;
if (lingerTicks <= 0) return 0.0f;
return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks));
}
@Override
protected float getLaunchClearanceRadius() {
return Math.max(0.1f, laserWidth * 0.5f);
@@ -706,17 +718,53 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
public void setupCharging(EnergyAnchorData anchor, int chargeDuration) {
// Ensure charging visuals grow from a small orb based on laser width.
this.size = laserWidth;
// Charge orb grows to half the beam width
this.size = laserWidth * 0.5f;
this.desiredLength = 0.0f;
this.currentLength = 0.0f;
this.fullyExtended = false;
this.ticksSinceFullExtension = 0;
super.setupCharging(anchor, chargeDuration);
this.endX = posX;
this.endY = posY;
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("DesiredLength", desiredLength);
nbt.setFloat("CurrentLength", currentLength);
nbt.setDouble("EndX", endX);
nbt.setDouble("EndY", endY);
nbt.setDouble("EndZ", endZ);
nbt.setBoolean("FullyExtended", fullyExtended);
nbt.setFloat("MaxLength", maxLength);
}
@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");
desiredLength = nbt.getFloat("DesiredLength");
currentLength = nbt.getFloat("CurrentLength");
endX = nbt.getDouble("EndX");
endY = nbt.getDouble("EndY");
endZ = nbt.getDouble("EndZ");
fullyExtended = nbt.getBoolean("FullyExtended");
if (nbt.hasKey("MaxLength")) maxLength = nbt.getFloat("MaxLength");
}
// ==================== NBT ====================
@Override
@@ -724,13 +772,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f;
if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f;
this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10;
if (lingerTicks <= 0) lingerTicks = 1;
this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir");
this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended");
this.maxLength = nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f;
if (Float.isNaN(maxLength) || Float.isInfinite(maxLength) || maxLength <= 0) maxLength = 32.0f;
this.dirX = nbt.getDouble("DirX");
this.dirY = nbt.getDouble("DirY");
this.dirZ = nbt.getDouble("DirZ");
this.desiredLength = nbt.getFloat("DesiredLength");
this.currentLength = nbt.getFloat("CurrentLength");
this.fullyExtended = nbt.getBoolean("FullyExtended");
this.endX = nbt.getDouble("EndX");
@@ -744,12 +791,11 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
protected void writeProjectileNBT(NBTTagCompound nbt) {
nbt.setFloat("LaserWidth", laserWidth);
nbt.setFloat("ExpansionSpeed", expansionSpeed);
nbt.setInteger("LingerTicks", lingerTicks);
nbt.setBoolean("LockVerticalDir", lockVerticalDirection);
nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension);
nbt.setFloat("MaxLength", maxLength);
nbt.setDouble("DirX", dirX);
nbt.setDouble("DirY", dirY);
nbt.setDouble("DirZ", dirZ);
nbt.setFloat("DesiredLength", desiredLength);
nbt.setFloat("CurrentLength", currentLength);
nbt.setBoolean("FullyExtended", fullyExtended);
nbt.setDouble("EndX", endX);
@@ -765,19 +811,19 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
*/
public void setupPreview(EntityLivingBase owner, float laserWidth, EnergyDisplayData display,
EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration,
float expansionSpeed, float maxDistance) {
float expansionSpeed, float maxLength) {
setupPreviewState(owner, display, lightning, anchor, chargeDuration);
// Laser-specific visual/behavior state.
this.laserWidth = laserWidth;
this.expansionSpeed = expansionSpeed;
this.lifespanData.maxDistance = Math.min(maxDistance, 5.0f); // Limit for GUI preview
this.maxLength = Math.min(maxLength, 5.0f); // Limit for GUI preview
this.desiredLength = 0.0f;
this.currentLength = 0.0f;
this.fullyExtended = false;
this.ticksSinceFullExtension = 0;
// Charge as an orb from tiny size to laser width while anchored.
this.targetSize = laserWidth;
// Charge as an orb from tiny size to half laser width while anchored.
this.targetSize = laserWidth * 0.5f;
setVisualSize(0.01f);
setChargeOriginFromAnchor(owner, anchorData);
clearMotion();
@@ -38,7 +38,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
// Initialize base properties
initProjectile(owner, target, x, y, z, orbSize, display, combat, lightning, lifespan);
this.homingData = homing;
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
// Calculate initial velocity toward target
calculateInitialVelocity(owner, target, x, y, z);
@@ -150,6 +150,16 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
processEntitiesInHitBox(hitBox, nextX, nextY, nextZ);
}
// ==================== DEBUG ====================
@Override
protected String debugLogExtra() {
return String.format("motion=(%.3f,%.3f,%.3f) homing=%b start=(%.2f,%.2f,%.2f)",
motionX, motionY, motionZ,
homingData != null && homingData.isHoming(),
startX, startY, startZ);
}
// ==================== GETTERS ====================
// Legacy getter for renderer compatibility
@@ -56,11 +56,12 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
this.panelYaw = yaw;
this.prevPanelYaw = yaw;
this.mode = mode;
this.displayData = display;
this.lightningData = lightning;
this.barrierData = barrier;
this.panelData = panel;
this.currentHealth = barrier.maxHealth;
// Defensive copy: entities must never share data objects with the source ability.
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData();
this.panelData = panel != null ? panel.copy() : new EnergyPanelData();
this.currentHealth = this.barrierData.maxHealth;
// For launched panels, set initial velocity
if (mode == PanelMode.LAUNCHED) {
@@ -142,6 +143,8 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
// Stationary, no update needed
break;
}
debugLogBarrierTick();
}
private void updateHeld() {
@@ -175,7 +178,10 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
if (worldObj != null && worldObj.isRemote && mode == PanelMode.HELD && getOwnerEntity() != null) {
return;
}
super.setPositionAndRotation2(x, y, z, yaw, pitch, posRotationIncrements);
// Bypass vanilla's lerp + push-out-of-blocks logic which causes
// large-BB entities (PLACED/LAUNCHED panels) to shift into the sky.
this.setPosition(x, y, z);
this.setRotation(yaw, pitch);
}
private void updateLaunched() {
@@ -544,6 +550,14 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
return df * df + dr * dr + du * du;
}
// ==================== DEBUG ====================
@Override
protected String debugLogBarrierExtra() {
return String.format("mode=%s yaw=%.1f w=%.2f h=%.2f",
mode.name(), panelYaw, panelData.panelWidth, panelData.panelHeight);
}
// ==================== GETTERS ====================
public PanelMode getMode() {
@@ -3,6 +3,7 @@ package kamkeel.npcs.entity;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
import kamkeel.npcs.util.CNPCDebug;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -79,7 +80,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
this.beamLength = beamLength;
this.beamWidth = beamWidth;
this.beamHeight = beamHeight;
this.displayData = displayData;
this.displayData = displayData != null ? displayData.copy() : new EnergyDisplayData();
this.sweepSpeed = sweepSpeed;
this.numberOfRotations = numberOfRotations;
this.damage = damage;
@@ -168,6 +169,19 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
if (!worldObj.isRemote) {
handleDamage(owner);
}
// Debug logging
{
boolean isClient = worldObj.isRemote;
if (isClient ? CNPCDebug.isClientEnabled("energy") : CNPCDebug.isServerEnabled("energy")) {
CNPCDebug.log("energy", isClient, String.format(
"[Sweeper id=%d tick=%d] pos=(%.2f,%.2f,%.2f) angle=%.1f prevAngle=%.1f " +
"speed=%.1f rotations=%d/%d length=%.2f width=%.2f",
getEntityId(), ticksExisted, posX, posY, posZ,
currentAngle, prevAngle, sweepSpeed,
completedRotations, numberOfRotations, beamLength, beamWidth));
}
}
}
/**
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
import kamkeel.npcs.util.CNPCDebug;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
@@ -57,6 +58,17 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
float eased = 1.0f - (1.0f - progress) * (1.0f - progress);
this.renderRadius = maxRadius * eased;
// Debug logging
{
boolean isClient = worldObj.isRemote;
if (isClient ? CNPCDebug.isClientEnabled("energy") : CNPCDebug.isServerEnabled("energy")) {
CNPCDebug.log("energy", isClient, String.format(
"[Explosion id=%d tick=%d] pos=(%.2f,%.2f,%.2f) renderRadius=%.2f maxRadius=%.2f progress=%.2f",
getEntityId(), ticksExisted, posX, posY, posZ,
renderRadius, maxRadius, getLifeProgress(0f)));
}
}
if (ticksExisted >= durationTicks) {
setDead();
}
@@ -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,8 +13,10 @@ 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 kamkeel.npcs.util.CNPCDebug;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
@@ -120,6 +123,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>();
@@ -183,10 +187,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// Visual
this.size = size;
this.displayData = display;
this.combatData = combat;
this.lifespanData = lifespan; // deathWorldTime will be set on first tick when world is available
this.lightningData = lightning;
// Defensive copy: entities must never share data objects with the source ability.
// Without copies, runtime mutations (e.g. barrier reflection changing colors/damage)
// bleed back into the ability template and corrupt future projectiles.
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
this.combatData = combat != null ? combat.copy() : new EnergyCombatData();
this.lifespanData = lifespan != null ? lifespan.copy() : new EnergyLifespanData();
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
syncProjectileColorWatchers();
// Initialize render size
@@ -328,6 +335,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
if (!previewMode && !worldObj.isRemote) {
EventHooks.onEnergyProjectileTick(this);
}
debugLogTick();
}
/**
@@ -343,6 +352,53 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
if (this.rotationValZ > 360.0f) this.rotationValZ -= 360.0f;
}
// ==================== DEBUG LOGGING ====================
/**
* Debug log called every tick. Logs position/size/charging state.
* Subclasses override debugLogExtra() for type-specific data.
*/
protected void debugLogTick() {
boolean isClient = worldObj.isRemote;
if (isClient ? !CNPCDebug.isClientEnabled("energy") : !CNPCDebug.isServerEnabled("energy"))
return;
String className = getClass().getSimpleName();
boolean dwCharging = isCharging(); // DataWatcher value (what client sees)
boolean localCharging = this.charging; // Local field (server truth)
String base = String.format("[%s id=%d tick=%d] pos=(%.2f,%.2f,%.2f) prev=(%.2f,%.2f,%.2f) " +
"size=%.3f renderSize=%.3f prevRenderSize=%.3f " +
"charging(dw)=%b charging(local)=%b chargeTick=%d/%d chargeProgress=%.3f",
className, getEntityId(), ticksExisted,
posX, posY, posZ, prevPosX, prevPosY, prevPosZ,
size, renderCurrentSize, prevRenderSize,
dwCharging, localCharging, chargeTick, chargeDuration, getChargeProgress());
// Flag mismatch between DataWatcher and local field
if (dwCharging != localCharging) {
base += " !!CHARGE_MISMATCH!!";
}
// Client interpolation state (only meaningful on client)
if (isClient) {
base += String.format(" interp(steps=%d target=(%.2f,%.2f,%.2f))",
interpSteps, interpTargetX, interpTargetY, interpTargetZ);
}
String extra = debugLogExtra();
String full = extra.isEmpty() ? base : base + " " + extra;
CNPCDebug.log("energy", isClient, full);
}
/**
* Subclass hook for type-specific debug data.
* Return empty string if nothing extra to log.
*/
protected String debugLogExtra() {
return "";
}
/**
* Check if projectile has exceeded max distance.
*
@@ -389,7 +445,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) {
continue;
}
if (handleBarrierHitOutcome(barrier, hitOutcome)) {
if (handleBarrierHitOutcome(barrier, hitOutcome, damage)) {
return true;
}
return false;
@@ -402,20 +458,29 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
/**
* Handle the result of a projectile/barrier interaction.
*
* @param fullDamage The DBC-scaled damage that was passed to the barrier (from getModifiedDamage).
* @return true if caller should stop processing this tick.
*/
protected boolean handleBarrierHitOutcome(EntityAbilityBarrier barrier, EntityAbilityBarrier.ProjectileHitOutcome hitOutcome) {
protected boolean handleBarrierHitOutcome(EntityAbilityBarrier barrier, EntityAbilityBarrier.ProjectileHitOutcome hitOutcome, float fullDamage) {
if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) {
return false;
}
if (hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.BROKEN) {
if (hitOutcome.remainingProjectileDamage <= 0.0f) {
damageMultiplier = 0.0f;
setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS));
hasHit = true;
return true;
}
// Accumulate barrier pass-through ratio for extender damage scaling.
// fullDamage is the DBC-scaled damage the barrier saw; remainingProjectileDamage
// is what survived. Multiply into the running ratio so multiple barriers stack.
if (fullDamage > 0.0f) {
damageMultiplier *= (hitOutcome.remainingProjectileDamage / fullDamage);
}
setCombatDamage(hitOutcome.remainingProjectileDamage);
setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS));
return false;
@@ -424,6 +489,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
if (hitOutcome.shouldReflect()) {
if (!reflectFromBarrier(barrier, hitOutcome.reflectStrengthPct)) {
beginBarrierImpactPause(true, barrier);
} else {
sendReflectionSync();
}
return true;
}
@@ -495,6 +562,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());
@@ -508,15 +583,83 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
setOuterColor(barrier.getOuterColor());
float clampedStrength = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct));
float reducedDamage = getDamage() * (1.0f - clampedStrength / 100.0f);
float reductionFactor = 1.0f - clampedStrength / 100.0f;
float reducedDamage = getDamage() * reductionFactor;
setCombatDamage(Math.max(0.0f, reducedDamage));
damageMultiplier *= reductionFactor;
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;
@@ -866,7 +1009,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
float kbUp = getKnockbackUp() > 0 ? getKnockbackUp() : 0.1f;
handled = AbilityController.Instance.fireOnAbilityDamage(
sourceAbility, (EntityLivingBase) owner, target,
dmg, kb, kbUp, dx, dz);
dmg, kb, kbUp, dx, dz, damageMultiplier);
}
if (!handled) {
@@ -911,7 +1054,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
* Set the effects list from the ability's configured effects.
*/
public void setEffects(List<AbilityPotionEffect> effects) {
this.effects = effects != null ? effects : new ArrayList<>();
if (effects == null || effects.isEmpty()) {
this.effects = new ArrayList<>();
} else {
// Deep copy: entity must not share the ability's effects list or its entries.
this.effects = new ArrayList<>(effects.size());
for (AbilityPotionEffect effect : effects) {
this.effects.add(effect.copy());
}
}
}
protected void doExplosion() {
@@ -1518,8 +1669,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
int chargeDuration) {
this.setPreviewMode(true);
this.setPreviewOwner(owner);
this.displayData = display;
this.lightningData = lightning;
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
setupChargingState(anchor, chargeDuration);
}
@@ -1826,6 +1977,18 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// ==================== COMBAT GETTERS & SETTERS ====================
/**
* Accumulated proportional damage multiplier (0.0-1.0).
* Accumulates multiplicatively when the projectile breaks through barriers, is reflected,
* or any other system reduces its effective damage. Used by ability extenders to scale
* recalculated damage proportionally.
*/
private float damageMultiplier = 1.0f;
public float getDamageMultiplier() {
return damageMultiplier;
}
public float getDamage() {
return combatData.getDamage();
}
@@ -2176,6 +2339,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");
@@ -2232,6 +2396,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);
@@ -43,7 +43,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
super(world);
initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan);
this.homingData = homing;
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
this.sliceWidth = sliceWidth;
this.sliceThickness = sliceThickness;
@@ -192,6 +192,14 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
return lastTravelPitch;
}
// ==================== DEBUG ====================
@Override
protected String debugLogExtra() {
return String.format("motion=(%.3f,%.3f,%.3f) sliceW=%.2f sliceT=%.2f",
motionX, motionY, motionZ, sliceWidth, sliceThickness);
}
// ==================== GETTERS ====================
public float getSliceWidth() {
@@ -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
);
}
}
@@ -13,13 +13,10 @@ import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.NoppesUtilServer;
import noppes.npcs.controllers.DialogController;
import noppes.npcs.controllers.QuestController;
import noppes.npcs.controllers.data.Dialog;
import noppes.npcs.controllers.data.Quest;
import java.io.IOException;
@@ -83,13 +80,6 @@ public final class DialogSavePacket extends AbstractPacket {
} else {
NoppesUtilServer.sendDialogData((EntityPlayerMP) player, dialog.category);
}
// Send full dialog data back so the client doesn't need a second round-trip
NBTTagCompound dialogData = dialog.writeToNBT(new NBTTagCompound());
Quest quest = QuestController.Instance.quests.get(dialog.quest);
if (quest != null)
dialogData.setString("DialogQuestName", quest.title);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, dialogData);
}
}
}
@@ -13,7 +13,6 @@ import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.NoppesUtilServer;
import noppes.npcs.controllers.QuestController;
@@ -80,12 +79,6 @@ public final class QuestSavePacket extends AbstractPacket {
} else {
NoppesUtilServer.sendQuestData((EntityPlayerMP) player, quest.category);
}
// Send full quest data back so the client doesn't need a second round-trip
NBTTagCompound questData = new NBTTagCompound();
if (quest.hasNewQuest())
questData.setString("NextQuestTitle", quest.getNextQuest().title);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, quest.writeToNBT(questData));
}
}
}
@@ -0,0 +1,163 @@
package kamkeel.npcs.util;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;
/**
* Debug logging system for CNPC+ entities.
* Writes to logs/cnpc_debug/[type]_[side].log (file only, no console output).
* Toggle at runtime via /cnpcdebugger [type] (client) or /kam debug [type] (server).
*/
public class CNPCDebug {
private static final String DIR = "logs/cnpc_debug";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss");
// Runtime toggle state per type, separate for client and server
private static final Map<String, Boolean> serverToggles = new ConcurrentHashMap<>();
private static final Map<String, Boolean> clientToggles = new ConcurrentHashMap<>();
// Per-type file handlers (lazily initialized)
private static final Map<String, Handler> serverHandlers = new ConcurrentHashMap<>();
private static final Map<String, Handler> clientHandlers = new ConcurrentHashMap<>();
// ==================== TOGGLE API ====================
/**
* Toggle server-side debug for given type. Returns new state.
*/
public static boolean toggleServer(String type) {
type = type.toLowerCase();
boolean newState = !isServerEnabled(type);
serverToggles.put(type, newState);
if (newState) {
ensureHandler(type, false);
}
return newState;
}
/**
* Toggle client-side debug for given type. Returns new state.
*/
public static boolean toggleClient(String type) {
type = type.toLowerCase();
boolean newState = !isClientEnabled(type);
clientToggles.put(type, newState);
if (newState) {
ensureHandler(type, true);
}
return newState;
}
public static boolean isServerEnabled(String type) {
Boolean val = serverToggles.get(type.toLowerCase());
return val != null && val;
}
public static boolean isClientEnabled(String type) {
Boolean val = clientToggles.get(type.toLowerCase());
return val != null && val;
}
// ==================== LOGGING API ====================
/**
* Log a debug message for the given type on the given side.
* Callers should guard with isServerEnabled/isClientEnabled for performance.
*/
public static void log(String type, boolean client, String msg) {
type = type.toLowerCase();
Map<String, Boolean> toggles = client ? clientToggles : serverToggles;
Boolean enabled = toggles.get(type);
if (enabled == null || !enabled) return;
Handler handler = ensureHandler(type, client);
if (handler == null) return;
String side = client ? "CLIENT" : "SERVER";
String timestamp;
synchronized (DATE_FORMAT) {
timestamp = DATE_FORMAT.format(new Date());
}
String formatted = "[" + timestamp + "][" + side + "][DEBUG-" + type.toUpperCase() + "] " + msg + System.lineSeparator();
LogRecord record = new LogRecord(Level.INFO, formatted);
handler.publish(record);
handler.flush();
}
/**
* Log on server side.
*/
public static void logServer(String type, String msg) {
log(type, false, msg);
}
/**
* Log on client side.
*/
public static void logClient(String type, String msg) {
log(type, true, msg);
}
// ==================== INTERNALS ====================
private static Handler ensureHandler(String type, boolean client) {
Map<String, Handler> handlers = client ? clientHandlers : serverHandlers;
Handler existing = handlers.get(type);
if (existing != null) return existing;
synchronized (CNPCDebug.class) {
existing = handlers.get(type);
if (existing != null) return existing;
try {
File dir = new File(DIR);
if (!dir.exists()) dir.mkdirs();
String side = client ? "client" : "server";
File file = new File(dir, type + "_" + side + ".log");
// Rotate old log
if (file.exists()) {
File backup = new File(dir, type + "_" + side + "-prev.log");
if (backup.exists()) backup.delete();
file.renameTo(backup);
}
Handler handler = new StreamHandler(new FileOutputStream(file), new Formatter() {
@Override
public String format(LogRecord record) {
return record.getMessage();
}
});
handler.setLevel(Level.ALL);
handlers.put(type, handler);
// Log header
String timestamp;
synchronized (DATE_FORMAT) {
timestamp = DATE_FORMAT.format(new Date());
}
LogRecord header = new LogRecord(Level.INFO,
"[" + timestamp + "][" + (client ? "CLIENT" : "SERVER") + "][DEBUG-" + type.toUpperCase() + "] Debug logging started" + System.lineSeparator());
handler.publish(header);
handler.flush();
return handler;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}
@@ -25,9 +25,6 @@ import java.util.Map;
import java.util.Set;
public class FlyPathFinder extends PathFinder {
private static final float STRAIGHT_COST = 1.0F;
private static final float DIAGONAL_2D_COST = 1.4142135F;
private static final float DIAGONAL_3D_COST = 1.7320508F;
/**
* Used to find obstacles
*/
@@ -144,7 +141,7 @@ public class FlyPathFinder extends PathFinder {
private NPCPath addToPath(Entity p_75861_1_, NPCPathPoint p_75861_2_, NPCPathPoint p_75861_3_, NPCPathPoint p_75861_4_, float p_75861_5_) {
p_75861_2_.totalPathDistance = 0.0F;
p_75861_2_.distanceToNext = this.estimateCostToTarget(p_75861_2_, p_75861_3_);
p_75861_2_.distanceToNext = p_75861_2_.distanceToSquared(p_75861_3_);
p_75861_2_.distanceToTarget = p_75861_2_.distanceToNext;
this.path.clearPath();
this.path.addPoint(p_75861_2_);
@@ -164,7 +161,7 @@ public class FlyPathFinder extends PathFinder {
return this.createEntityPath(p_75861_2_, p_75861_3_);
}
if (this.estimateCostToTarget(pathpoint4, p_75861_3_) < this.estimateCostToTarget(pathpoint3, p_75861_3_)) {
if (pathpoint4.distanceToSquared(p_75861_3_) < pathpoint3.distanceToSquared(p_75861_3_)) {
pathpoint3 = pathpoint4;
}
@@ -173,13 +170,12 @@ public class FlyPathFinder extends PathFinder {
for (int j = 0; j < i; ++j) {
NPCPathPoint pathpoint5 = this.pathOptions[j];
float f1 = pathpoint4.totalPathDistance + this.getTraversalCost(pathpoint4, pathpoint5)
+ this.getDirectionalPenalty(pathpoint4, pathpoint5, p_75861_3_);
float f1 = pathpoint4.totalPathDistance + pathpoint4.distanceToSquared(pathpoint5);
if (!pathpoint5.isAssigned() || f1 < pathpoint5.totalPathDistance) {
pathpoint5.previous = pathpoint4;
pathpoint5.totalPathDistance = f1;
pathpoint5.distanceToNext = this.estimateCostToTarget(pathpoint5, p_75861_3_);
pathpoint5.distanceToNext = pathpoint5.distanceToSquared(p_75861_3_);
if (pathpoint5.isAssigned()) {
this.path.changeDistance(pathpoint5, pathpoint5.totalPathDistance + pathpoint5.distanceToNext);
@@ -198,50 +194,6 @@ public class FlyPathFinder extends PathFinder {
}
}
private float getTraversalCost(NPCPathPoint from, NPCPathPoint to) {
int dx = Math.abs(to.xCoord - from.xCoord);
int dy = Math.abs(to.yCoord - from.yCoord);
int dz = Math.abs(to.zCoord - from.zCoord);
int changedAxes = dx + dy + dz;
if (changedAxes <= 1) {
return STRAIGHT_COST;
}
if (changedAxes == 2) {
return DIAGONAL_2D_COST;
}
return DIAGONAL_3D_COST;
}
private float getDirectionalPenalty(NPCPathPoint from, NPCPathPoint to, NPCPathPoint goal) {
int stepX = to.xCoord - from.xCoord;
int stepY = to.yCoord - from.yCoord;
int stepZ = to.zCoord - from.zCoord;
int goalX = goal.xCoord - from.xCoord;
int goalY = goal.yCoord - from.yCoord;
int goalZ = goal.zCoord - from.zCoord;
int stepLen = Math.abs(stepX) + Math.abs(stepY) + Math.abs(stepZ);
int goalLen = Math.abs(goalX) + Math.abs(goalY) + Math.abs(goalZ);
if (stepLen == 0 || goalLen == 0) {
return 0.0F;
}
float alignment = (float) (stepX * goalX + stepY * goalY + stepZ * goalZ) / (float) (stepLen * goalLen);
return (1.0F - alignment) * 0.04F;
}
private float estimateCostToTarget(NPCPathPoint from, NPCPathPoint to) {
float dx = Math.abs(to.xCoord - from.xCoord);
float dy = Math.abs(to.yCoord - from.yCoord);
float dz = Math.abs(to.zCoord - from.zCoord);
float max = Math.max(dx, Math.max(dy, dz));
float min = Math.min(dx, Math.min(dy, dz));
float mid = dx + dy + dz - max - min;
return DIAGONAL_3D_COST * min + DIAGONAL_2D_COST * (mid - min) + STRAIGHT_COST * (max - mid);
}
/**
* populates pathOptions with available points and returns the number of options found (args: unused1, currentPoint,
* unused2, targetPoint, maxDistance)
@@ -7,8 +7,6 @@ import noppes.npcs.entity.EntityNPCFlying;
import noppes.npcs.entity.EntityNPCInterface;
public class FlyingMoveHelper extends EntityMoveHelper {
private static final double CLOSE_ENOUGH_SQ = 0.01D;
private static final float MAX_TURN_PER_TICK = 35.0F;
private final EntityNPCInterface entity;
private double posX;
@@ -40,24 +38,22 @@ public class FlyingMoveHelper extends EntityMoveHelper {
double d5 = MathHelper.sqrt_double(d4);
speed = Math.min(d5 / 5.0D, speed);
if (d4 > CLOSE_ENOUGH_SQ && d5 > 1.0E-4D) {
double targetMotionX = speed * (d0 / d5);
double targetMotionZ = speed * (d2 / d5);
double horizontalLerp = Math.min(1.0D, Math.max(0.12D, speed * 0.6D));
this.entity.motionX += (targetMotionX - this.entity.motionX) * horizontalLerp;
this.entity.motionZ += (targetMotionZ - this.entity.motionZ) * horizontalLerp;
// its here louis!
// This is the hurt time that stuns the NPC!!!!!!
// if (this.entity.hurtTime == 0 && d4 > 0.5D) {
if (d4 >= 0.5D) {
this.entity.motionX += (speed * (d0 / d5) - this.entity.motionX) * speed;
this.entity.motionZ += (speed * (d2 / d5) - this.entity.motionZ) * speed;
if (((EntityNPCFlying) this.entity).flyLimitAllow || !this.entity.ais.hasFlyLimit) {
double targetMotionY = verticalSpeed * (d1 / d5);
if (targetMotionY > 0.0D) {
targetMotionY += 0.05D;
this.entity.motionY = verticalSpeed * (d1 / d5);
if (this.entity.motionY > 0) {
this.entity.motionY += 0.1D;
}
double verticalLerp = Math.min(1.0D, Math.max(0.15D, verticalSpeed * 0.85D));
this.entity.motionY += (targetMotionY - this.entity.motionY) * verticalLerp;
}
this.entity.velocityChanged = true;
this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), MAX_TURN_PER_TICK);
this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), 20.0F);
}
}
}
@@ -21,10 +21,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import javax.annotation.Nullable;
public class PathNavigateFlying extends PathNavigate {
private static final long MIN_REPATH_INTERVAL_TICKS = 8L;
private static final long MAX_REPATH_INTERVAL_TICKS = 30L;
private static final int PATH_REUSE_TARGET_DELTA_SQ = 2;
private static final int FINAL_NODE_REUSE_DELTA_SQ = 3;
private EntityNPCInterface theEntity;
private World worldObj;
@@ -87,21 +83,16 @@ public class PathNavigateFlying extends PathNavigate {
}
public void updatePath() {
if (this.targetPos == null) {
this.tryUpdatePath = false;
return;
}
long now = this.worldObj.getTotalWorldTime();
if (now - this.lastTimeUpdated < this.getAdaptiveRepathInterval()) {
if (this.worldObj.getTotalWorldTime() - this.lastTimeUpdated > 20L) {
if (this.targetPos != null) {
this.currentPath = null;
this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ());
this.lastTimeUpdated = this.worldObj.getTotalWorldTime();
this.tryUpdatePath = false;
}
} else {
this.tryUpdatePath = true;
return;
}
this.currentPath = null;
this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ());
this.lastTimeUpdated = now;
this.tryUpdatePath = false;
}
public void setAvoidsWater(boolean p_75491_1_) {
@@ -165,8 +156,7 @@ public class PathNavigateFlying extends PathNavigate {
BlockPos pos = new BlockPos(x, y, z);
if (!this.canNavigate()) {
return null;
} else if (this.canReuseCurrentPath(pos)) {
this.targetPos = pos;
} else if (this.currentPath != null && !this.currentPath.isFinished() && pos.equals(this.targetPos)) {
return this.currentPath;
} else {
this.targetPos = pos;
@@ -190,8 +180,7 @@ public class PathNavigateFlying extends PathNavigate {
return null;
} else {
BlockPos blockpos = new BlockPos(entity);
if (this.canReuseCurrentPath(blockpos)) {
this.targetPos = blockpos;
if (this.currentPath != null && !this.currentPath.isFinished() && blockpos.equals(this.targetPos)) {
return this.currentPath;
} else {
this.targetPos = blockpos;
@@ -226,8 +215,6 @@ public class PathNavigateFlying extends PathNavigate {
this.removeSunnyPath();
}
this.smoothCurrentPath();
if (this.currentPath.getCurrentPathLength() == 0) {
return false;
} else {
@@ -255,72 +242,6 @@ public class PathNavigateFlying extends PathNavigate {
return !this.isInLiquid() || (this.canSwim && this.isInLiquid());
}
private long getAdaptiveRepathInterval() {
if (this.targetPos == null) {
return MAX_REPATH_INTERVAL_TICKS;
}
double distance = this.theEntity.getDistance(this.targetPos.getX() + 0.5D, this.targetPos.getY(), this.targetPos.getZ() + 0.5D);
return (long) MathHelper.clamp_double(distance * 0.6D, MIN_REPATH_INTERVAL_TICKS, MAX_REPATH_INTERVAL_TICKS);
}
private boolean canReuseCurrentPath(BlockPos newTarget) {
if (this.currentPath == null || this.currentPath.isFinished() || this.targetPos == null) {
return false;
}
if (this.getBlockDistanceSq(this.targetPos, newTarget) <= PATH_REUSE_TARGET_DELTA_SQ) {
return true;
}
NPCPathPoint finalPoint = this.currentPath.getFinalPathPoint();
return finalPoint != null && this.getPointDistanceSq(finalPoint, newTarget) <= FINAL_NODE_REUSE_DELTA_SQ;
}
private int getBlockDistanceSq(BlockPos a, BlockPos b) {
int dx = a.getX() - b.getX();
int dy = a.getY() - b.getY();
int dz = a.getZ() - b.getZ();
return dx * dx + dy * dy + dz * dz;
}
private int getPointDistanceSq(NPCPathPoint point, BlockPos blockPos) {
int dx = point.xCoord - blockPos.getX();
int dy = point.yCoord - blockPos.getY();
int dz = point.zCoord - blockPos.getZ();
return dx * dx + dy * dy + dz * dz;
}
private void smoothCurrentPath() {
if (this.currentPath == null) {
return;
}
int pathLength = this.currentPath.getCurrentPathLength();
if (pathLength < 3) {
return;
}
int width = MathHelper.ceiling_double_int(this.theEntity.width);
int height = MathHelper.ceiling_double_int(this.theEntity.height + 1.0F);
NPCPathPoint[] points = this.currentPath.points;
int writeIndex = 1;
int anchorIndex = 0;
for (int readIndex = 2; readIndex < pathLength; readIndex++) {
Vec3 anchor = this.currentPath.getVectorFromIndex(this.theEntity, anchorIndex);
Vec3 probe = this.currentPath.getVectorFromIndex(this.theEntity, readIndex);
if (!this.isDirectPathBetweenPoints(anchor, probe, width, height)) {
points[writeIndex++] = points[readIndex - 1];
anchorIndex = readIndex - 1;
}
}
points[writeIndex++] = points[pathLength - 1];
this.currentPath.setCurrentPathLength(writeIndex);
if (this.currentPath.getCurrentPathIndex() >= writeIndex) {
this.currentPath.setCurrentPathIndex(Math.max(0, writeIndex - 1));
}
}
public void onUpdateNavigation() {
++this.totalTicks;
@@ -6,6 +6,7 @@ import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import kamkeel.npcs.addon.client.DBCClient;
import kamkeel.npcs.client.command.CommandCNPCDebugger;
import kamkeel.npcs.client.renderer.RenderAbilityBeam;
import kamkeel.npcs.client.renderer.RenderAbilityDisc;
import kamkeel.npcs.client.renderer.RenderAbilityLaser;
@@ -53,6 +54,7 @@ import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ReportedException;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer;
@@ -362,6 +364,9 @@ public class ClientProxy extends CommonProxy {
ClientTagMapController.Instance = new ClientTagMapController();
MinecraftForge.EVENT_BUS.register(new ClientEventHandler());
// Debug commands
ClientCommandHandler.instance.registerCommand(new CommandCNPCDebugger());
// Telegraph rendering system
TelegraphManager.initClient();
MinecraftForge.EVENT_BUS.register(new TelegraphRenderer());
@@ -411,6 +411,9 @@ public class GuiNPCManageDialogs extends GuiNPCInterface2 implements IScrollData
}
if (subgui instanceof SubGuiNpcDialog) {
saveType(true);
if (dialog != null && dialog.id >= 0) {
setPrevDialogName(dialog.title);
}
}
}
@@ -401,6 +401,9 @@ public class GuiNPCManageQuest extends GuiNPCInterface2 implements IScrollData,
}
if (subgui instanceof SubGuiNpcQuest) {
saveType(true);
if (quest != null && quest.id >= 0) {
setPrevQuestName(quest.title);
}
}
}
@@ -91,13 +91,11 @@ public abstract class EntityNPCFlying extends EntityNPCInterface {
this.prevLimbSwingAmount = this.limbSwingAmount;
double distanceX = this.posX - this.prevPosX;
double distanceZ = this.posZ - this.prevPosZ;
float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ);
float swingScale = this.getNavigator().noPath() ? 0.35F : 0.9F;
distance *= swingScale;
float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ) * 4.0F;
if (distance > 1.0F) {
distance = 1.0F;
}
this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.18F;
this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.4F;
this.limbSwing += this.limbSwingAmount;
}
@@ -45,12 +45,12 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
entity.setExpansionSpeed(speed);
}
public int getLingerTicks() {
return entity.getLingerTicks();
public float getMaxLength() {
return entity.getMaxLength();
}
public void setLingerTicks(int ticks) {
entity.setLingerTicks(ticks);
public void setMaxLength(float maxLength) {
entity.setMaxLength(maxLength);
}
public float getCurrentLength() {
@@ -89,18 +89,6 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
return entity.getEndZ();
}
public float getLingerAlpha() {
return entity.getLingerAlpha();
}
public boolean isLockVerticalDirection() {
return entity.isLockVerticalDirection();
}
public void setLockVerticalDirection(boolean lock) {
entity.setLockVerticalDirection(lock);
}
// ==================== FIRE ====================
@Override
@@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI;
public class AbilityEvent extends NpcEvent implements IAbilityEvent {
protected final Ability ability;
protected final IEntityLivingBase target;
private transient IAbility abilityCopy;
public AbilityEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
super(npc);
@@ -27,7 +28,10 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
@Override
public IAbility getAbility() {
return ability;
if (abilityCopy == null && ability != null) {
abilityCopy = ability.deepCopy();
}
return abilityCopy;
}
@Override
@@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI;
public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEvent {
protected final Ability ability;
protected final IEntityLivingBase target;
private transient IAbility abilityCopy;
public PlayerAbilityEvent(IPlayer player, Ability ability, EntityLivingBase target) {
super(player);
@@ -27,7 +28,10 @@ public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEve
@Override
public IAbility getAbility() {
return ability;
if (abilityCopy == null && ability != null) {
abilityCopy = ability.deepCopy();
}
return abilityCopy;
}
@Override
@@ -36,7 +36,12 @@ public class ScriptDataAbilities implements IDataAbilities {
@Override
public IAbility[] getAbilities() {
return data.getAbilities().toArray(new IAbility[0]);
List<Ability> live = data.getAbilities();
IAbility[] copies = new IAbility[live.size()];
for (int i = 0; i < live.size(); i++) {
copies[i] = live.get(i).deepCopy();
}
return copies;
}
@Override
@@ -73,7 +78,8 @@ public class ScriptDataAbilities implements IDataAbilities {
@Override
public IAbility getAbility(String abilityId) {
return data.getAbility(abilityId);
Ability a = data.getAbility(abilityId);
return a != null ? a.deepCopy() : null;
}
@Override
@@ -115,6 +121,17 @@ public class ScriptDataAbilities implements IDataAbilities {
@Override
public IAbility getCurrentAbility() {
Ability a = data.getCurrentAbility();
return a != null ? a.deepCopy() : null;
}
@Override
public IAbility getSourceAbility(String abilityId) {
return data.getAbility(abilityId);
}
@Override
public IAbility getSourceCurrentAbility() {
return data.getCurrentAbility();
}
@@ -85,7 +85,8 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
@Override
public IAbility getCurrentAbility() {
return data.getCurrentAbility();
Ability a = data.getCurrentAbility();
return a != null ? a.deepCopy() : null;
}
@Override
@@ -1693,7 +1693,7 @@ ability.cnpc.effect=Effect
ability.cnpc.hazard=Hazard
ability.cnpc.trap=Trap
ability.cnpc.disc=Disc
ability.cnpc.laser_shot=Laser Shot
ability.cnpc.laser_shot=Laser
ability.cnpc.sweeper=Sweeper
# ═══════════════════════════════════════════════════════════════
@@ -1822,6 +1822,7 @@ ability.multiHitDelay=Multi-Hit Delay
ability.maxHits=Max Hits
ability.distance=Distance
ability.maxDistance=Max Distance
ability.maxLength=Max Length
ability.lifetime=Lifetime
ability.stunTicks=Stun Ticks
ability.hitWidth=Hit Width
@@ -1829,7 +1830,6 @@ ability.hitLength=Hit Length
ability.effects=Effects
ability.effect.burn=Burn
ability.mode=Mode
ability.duration=Duration
ability.anchorPoint=Anchor Point
ability.anchor.offsetX=Offset X
ability.anchor.offsetY=Offset Y