diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index bf4df79eb..e70e5c6ba 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -3,6 +3,7 @@ package kamkeel.npcs.client.renderer; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer; +import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityLaser; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; @@ -20,11 +21,14 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { EntityAbilityLaser laser = (EntityAbilityLaser) entity; - // Don't render if length is 0 - if (laser.getCurrentLength() <= 0) return; - setupRenderState(); + if (laser.isCharging()) { + renderChargingOrb(laser, x, y, z, partialTicks); + restoreRenderState(); + return; + } + // Get laser parameters double startX = laser.getStartX(); double startY = laser.getStartY(); @@ -123,6 +127,55 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { restoreRenderState(); } + private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks) { + float headSize = laser.getLaserWidth() * 1.5f; + float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks); + float size = headSize * chargeProgress; + + if (size <= 0.01f) return; + + int innerColor = laser.getInnerColor(); + int outerColor = laser.getOuterColor(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + // Pulsing effect + float pulseTime = laser.ticksExisted + partialTicks; + float scaleModifier = (float) Math.sin(pulseTime * 0.2f) * 0.08f; + float scale = size * (1.0f + scaleModifier); + + // Render lightning during charging if enabled + if (laser.hasLightningEffect()) { + renderAttachedLightning(laser, scale); + } + + GL11.glPushMatrix(); + GL11.glScalef(scale, scale, scale); + + // Inner scale defines the core size + float innerScale = 0.6f; + + // Render outer glow only if enabled + if (laser.isOuterColorEnabled()) { + float outerScale = innerScale + laser.getOuterColorWidth(); + float outerAlpha = laser.getOuterColorAlpha(); + GL11.glDepthMask(false); + GL11.glPushMatrix(); + GL11.glScalef(outerScale, outerScale, outerScale); + renderCube(outerColor, outerAlpha, 0.5f); + GL11.glPopMatrix(); + GL11.glDepthMask(true); + } + + // Render inner core + GL11.glScalef(innerScale, innerScale, innerScale); + renderCube(innerColor, 1.0f, 0.5f); + + GL11.glPopMatrix(); + GL11.glPopMatrix(); + } + /** * Render lightning effects along the laser beam at multiple points. */ @@ -161,6 +214,22 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { GL11.glPopMatrix(); } + + private void renderAttachedLightning(EntityAbilityLaser laser, float headScale) { + AttachedLightningRenderer.LightningState state = getLightningState(laser); + + float density = laser.getLightningDensity(); + // Lightning radius extends outward from inner surface (innerScale = 0.6) + float innerRadius = 0.6f * headScale * 0.5f; + float radius = innerRadius + laser.getLightningRadius() * headScale; + int outerColor = laser.getOuterColor(); + int innerColor = laser.getInnerColor(); + int fadeTime = laser.getLightningFadeTime(); + + state.update(density, radius, outerColor, innerColor, fadeTime); + state.render(); + } + /** * Get or create the main lightning state for the laser. */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java index acf5f0aa4..5d42b23cc 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java @@ -3,18 +3,18 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyColorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.*; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityLaser; +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; +import net.minecraft.util.Vec3; import net.minecraft.world.World; import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityLaserShot; @@ -34,10 +34,11 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot { private int lingerTicks = 8; // Data classes - private EnergyColorData colorData = new EnergyColorData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f); - private EnergyCombatData combatData = new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f); - private EnergyLightningData lightningData = new EnergyLightningData(); - private EnergyLifespanData lifespanData = new EnergyLifespanData(40.0f, 100); + private final EnergyColorData colorData = new EnergyColorData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f); + private final EnergyCombatData combatData = new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f); + private final EnergyLightningData lightningData = new EnergyLightningData(); + private final EnergyLifespanData lifespanData = new EnergyLifespanData(40.0f, 100); + private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND); // Transient state for laser entity (used for movement locking) private transient EntityAbilityLaser laserEntity = null; @@ -89,27 +90,34 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot { return laserWidth * 2.0f; // Make telegraph slightly wider for visibility } + @Override + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + if (world.isRemote) return; + + if (tick == 1) { + Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData); + laserEntity = new EntityAbilityLaser( + world, caster, target, + spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, + laserWidth, + colorData, combatData, lightningData, lifespanData, + expansionSpeed, lingerTicks + ); + + laserEntity.setupCharging(anchorData, windUpTicks); + + laserEntity.setEffects(this.effects); + world.spawnEntityInWorld(laserEntity); + } + } + @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) { if (world.isRemote) { signalCompletion(); - return; } - double spawnX = caster.posX; - double spawnY = caster.posY + caster.getEyeHeight(); - double spawnZ = caster.posZ; - - laserEntity = new EntityAbilityLaser( - world, caster, target, - spawnX, spawnY, spawnZ, - laserWidth, - colorData, combatData, lightningData, lifespanData, - expansionSpeed, lingerTicks - ); - - laserEntity.setEffects(this.effects); - world.spawnEntityInWorld(laserEntity); + laserEntity.startMoving(target); // Ability stays active until entity dies (prevents firing another while projectile is alive) // Movement locking is handled separately by the base class @@ -146,6 +154,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot { nbt.setFloat("laserWidth", laserWidth); nbt.setFloat("expansionSpeed", expansionSpeed); nbt.setInteger("lingerTicks", lingerTicks); + anchorData.writeNBT(nbt); colorData.writeNBT(nbt); combatData.writeNBT(nbt); lightningData.writeNBT(nbt); @@ -157,6 +166,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot { this.laserWidth = nbt.hasKey("laserWidth") ? nbt.getFloat("laserWidth") : 0.3f; this.expansionSpeed = nbt.hasKey("expansionSpeed") ? nbt.getFloat("expansionSpeed") : 3.0f; this.lingerTicks = nbt.hasKey("lingerTicks") ? nbt.getInteger("lingerTicks") : 8; + anchorData.readNBT(nbt); colorData.readNBT(nbt); combatData.readNBT(nbt); lightningData.readNBT(nbt); @@ -210,6 +220,20 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot { public float getLightningRadius() { return lightningData.lightningRadius; } public void setLightningRadius(float lightningRadius) { this.lightningData.lightningRadius = lightningRadius; } + public AnchorPoint getAnchorPointEnum() { return anchorData.anchorPoint; } + + public float getAnchorOffsetX() { return anchorData.anchorOffsetX; } + public float getAnchorOffsetY() { return anchorData.anchorOffsetY; } + public float getAnchorOffsetZ() { return anchorData.anchorOffsetZ; } + public void setAnchorPointEnum(AnchorPoint anchorPoint) { this.anchorData.anchorPoint = anchorPoint; } + public void setAnchorOffsetX(float x) { this.anchorData.anchorOffsetX = x; } + public void setAnchorOffsetY(float y) { this.anchorData.anchorOffsetY = y; } + public void setAnchorOffsetZ(float z) { this.anchorData.anchorOffsetZ = z; } + public int getAnchorPoint() { + return anchorData.anchorPoint.ordinal(); + } + public void setAnchorPoint(int point) { this.anchorData.anchorPoint = AnchorPoint.fromOrdinal(point); } + @Override @SideOnly(Side.CLIENT) public Entity createPreviewEntity(EntityNPCInterface npc) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java index 63c7c9005..0ae1c6182 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java @@ -34,12 +34,12 @@ public class AbilityOrb extends Ability implements IAbilityOrb { private float orbSize = 1.0f; // Data classes for energy properties - public final EnergyColorData colorData = new EnergyColorData(); - public final EnergyCombatData combatData = new EnergyCombatData(); - public final EnergyHomingData homingData = new EnergyHomingData(); - public final EnergyLightningData lightningData = new EnergyLightningData(); - public final EnergyLifespanData lifespanData = new EnergyLifespanData(); - private EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND); + private final EnergyColorData colorData = new EnergyColorData(); + private final EnergyCombatData combatData = new EnergyCombatData(); + private final EnergyHomingData homingData = new EnergyHomingData(); + private final EnergyLightningData lightningData = new EnergyLightningData(); + private final EnergyLifespanData lifespanData = new EnergyLifespanData(); + private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND); // Transient state for orb entity (used during windup charging) private transient EntityAbilityOrb orbEntity = null; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 3d888c698..d8aeb725c 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -1,9 +1,8 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyColorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -43,10 +42,48 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { // End point for rendering private double endX, endY, endZ; + // Charging state (during windup) + private boolean charging = false; + private int chargeDuration = 40; + private int chargeTick = 0; + private EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.FRONT); + private float targetSize = 1.0f; // Full size to grow to during charging + + // Data watcher index for charging state (synced to clients) + private static final int DW_CHARGING = 20; + public EntityAbilityLaser(World world) { super(world); } + @Override + protected void entityInit() { + super.entityInit(); + // Register data watcher for charging state + this.dataWatcher.addObject(DW_CHARGING, (byte) 0); + } + + /** + * Check if orb is in charging state (synced via data watcher). + * In preview mode, uses local field since data watcher isn't synced. + */ + public boolean isCharging() { + if (previewMode) { + return this.charging; + } + return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1; + } + + /** + * Set charging state (server only, synced to clients via data watcher). + */ + private void setCharging(boolean value) { + this.charging = value; + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0)); + } + } + /** * Full constructor with all parameters using data classes. */ @@ -70,7 +107,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { // Calculate direction toward target or forward if (target != null) { double dx = target.posX - x; - double dy = (target.posY + target.getEyeHeight()) - y; + double dy = (target.posY + target.getEyeHeight() - 0.4) - y; double dz = target.posZ - z; double len = Math.sqrt(dx * dx + dy * dy + dz * dz); if (len > 0) { @@ -93,6 +130,25 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { this.endZ = z; } + /** + * Setup this orb in charging mode (for windup phase). + * The orb will grow from 0 to orbSize over chargeDuration ticks. + * Position follows the owner based on anchor point. + */ + public void setupCharging(EnergyAnchorData anchor, int chargeDuration) { + setCharging(true); + this.chargeDuration = chargeDuration; + this.chargeTick = 0; + this.anchorData = anchor; + this.targetSize = this.size; + this.size = 0.01f; + this.renderCurrentSize = 0.01f; + this.prevRenderSize = 0.01f; + this.motionX = 0; + this.motionY = 0; + this.motionZ = 0; + } + @Override protected void updateRotation() { // Laser doesn't rotate @@ -106,6 +162,11 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { @Override protected void updateProjectile() { + if (isCharging()) { + updateCharging(); + return; + } + if (!fullyExtended) { // Expand the laser currentLength += expansionSpeed; @@ -134,6 +195,46 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { } } + public void startMoving(EntityLivingBase target) { + if (!isCharging()) return; + + setCharging(false); + + // Update start position to current position + startX = posX; + startY = posY; + startZ = posZ; + + // Calculate velocity toward target + Entity owner = getOwner(); + + if (target != null) { + double dx = target.posX - startX; + double dy = (target.posY + target.getEyeHeight() - 0.4) - startY; + double dz = target.posZ - startZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 0) { + this.dirX = dx / len; + this.dirY = dy / len; + this.dirZ = dz / len; + } + } else if (owner != null) { + // Fire in NPC's facing direction + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + this.dirX = -Math.sin(yaw) * Math.cos(pitch); + this.dirY = -Math.sin(pitch); + this.dirZ = Math.cos(yaw) * Math.cos(pitch);; + } + + setPosition(endX, endY, endZ); + + // Initialize end point + this.endX = posX; + this.endY = posY; + this.endZ = posZ; + } + private void checkBlockCollision() { Vec3 start = Vec3.createVectorHelper(startX, startY, startZ); Vec3 end = Vec3.createVectorHelper(endX, endY, endZ); @@ -168,6 +269,44 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { } } + /** + * Update during charging state - follow owner based on anchor point. + */ + private void updateCharging() { + chargeTick++; + + Entity owner = getOwner(); + if (owner == null || owner.isDead) { + setDead(); + return; + } + + // Grow size based on charge progress + float progress = getChargeProgress(); + this.size = targetSize * progress; + + // Calculate position based on anchor point, offset downward by half size to center + if (owner instanceof EntityLivingBase) { + Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData); + setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + } + } + + /** + * Get the charge progress (0-1). + */ + public float getChargeProgress() { + if (chargeDuration <= 0) return 1.0f; + return Math.min(1.0f, (float) chargeTick / chargeDuration); + } + + public float getInterpolatedChargeProgress(float partialTicks) { + if (chargeDuration <= 0) return 1.0f; + float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration); + float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration); + return prevProgress + (currProgress - prevProgress) * partialTicks; + } + private void checkEntityCollisionAlongLine() { // Check collision along the entire laser line // Use a slightly expanded AABB for the full line @@ -190,7 +329,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { // Check if entity is close to the laser line if (isEntityOnLine(entity)) { hitEntities.add(entity.getEntityId()); - applyDamage(entity); + applyDamage(entity); // TODO find out why ts aint hittin // Piercing - don't stop, continue to next entity } @@ -305,6 +444,15 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { this.endX = nbt.getDouble("EndX"); this.endY = nbt.getDouble("EndY"); this.endZ = nbt.getDouble("EndZ"); + + // Charging state + boolean isCharging = nbt.hasKey("Charging") && nbt.getBoolean("Charging"); + this.charging = isCharging; + this.dataWatcher.updateObject(DW_CHARGING, (byte) (isCharging ? 1 : 0)); + this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40; + this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0; + this.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size; + anchorData.readNBT(nbt); } @Override @@ -320,6 +468,13 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { nbt.setDouble("EndX", endX); nbt.setDouble("EndY", endY); nbt.setDouble("EndZ", endZ); + + // Charging state + nbt.setBoolean("Charging", isCharging()); + nbt.setInteger("ChargeDuration", chargeDuration); + nbt.setInteger("ChargeTick", chargeTick); + nbt.setFloat("TargetSize", targetSize); + anchorData.writeNBT(nbt); } /** diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java index b594a83c7..71a03e97e 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java @@ -1,6 +1,7 @@ package noppes.npcs.client.gui.advanced.ability; import kamkeel.npcs.controllers.data.ability.AbilityEffect; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.type.AbilityLaserShot; import noppes.npcs.client.gui.SubGuiColorSelector; import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; @@ -99,7 +100,15 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { int col2LabelX = guiLeft + 180; int col2FieldX = guiLeft + 260; - // Row 1: Inner Color + Outer Color + // Row 1: Anchor Point + addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5)); + GuiNpcButton anchorBtn = new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), laser.getAnchorPointEnum().ordinal()); + anchorBtn.setHoverText("ability.hover.anchorPoint"); + addButton(anchorBtn); + + y += 24; + + // Row 2: Inner Color + Outer Color addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5)); String innerHex = String.format("%06X", laser.getInnerColor() & 0xFFFFFF); GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex); @@ -115,7 +124,7 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { y += 24; - // Row 2: Outer Color Enabled + Outer Color Width + // Row 3: Outer Color Enabled + Outer Color Width addLabel(new GuiNpcLabel(202, "ability.outerEnabled", labelX, y + 5)); addButton(new GuiNpcButton(202, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, laser.isOuterColorEnabled() ? 1 : 0)); @@ -126,7 +135,7 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { y += 24; - // Row 3: Lightning Effect Enabled + // Row 4: Lightning Effect Enabled addLabel(new GuiNpcLabel(204, "ability.outerAlpha", labelX, y + 5)); GuiNpcTextField alphaField = createFloatField(204, fieldX, y, 55, laser.getOuterColorAlpha()); alphaField.setEnabled(laser.isOuterColorEnabled()); @@ -142,7 +151,7 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { if (laser.hasLightningEffect()) { y += 24; - // Row 4: Density + Radius + // Row 5: Density + Radius addLabel(new GuiNpcLabel(206, "ability.lightningDensity", labelX, y + 5)); GuiNpcTextField densityField = new GuiNpcTextField(206, this, fontRendererObj, fieldX, y, 55, 18, "" + laser.getLightningDensity()); densityField.setMinMaxDefaultFloat(0.01f, 5.0f, 0.15f); @@ -189,6 +198,9 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { laser.setLightningEffect(value == 1); initGui(); break; + case 210: + laser.setAnchorPointEnum(AnchorPoint.fromOrdinal(value)); + break; } }