mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 17:47:32 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
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;
|
||||
|
||||
+16
@@ -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);
|
||||
|
||||
+23
-27
@@ -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
|
||||
|
||||
Reference in new issue
Block a user