diff --git a/src/api b/src/api index e4e2466a6..4e9642531 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit e4e2466a6ee5b7cc61243eb805627b767bfb5ea3 +Subproject commit 4e96425314a9fd2d3e48f68cff5fc7ee69f87213 diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergy.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergy.java index ce5d59669..5891b5742 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergy.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergy.java @@ -81,70 +81,95 @@ public abstract class RenderEnergy extends Render { * Render a cube centered at origin with given color and alpha. */ protected void renderCube(int color, float alpha, float halfSize) { - float[] rgb = extractRGB(color); - float r = rgb[0], g = rgb[1], b = rgb[2]; - Tessellator tess = Tessellator.instance; + float r = ((color >> 16) & 0xFF) / 255.0f; + float g = ((color >> 8) & 0xFF) / 255.0f; + float b = (color & 0xFF) / 255.0f; - // Front face (z+) tess.startDrawingQuads(); tess.setColorRGBA_F(r, g, b, alpha); + addCubeVertices(tess, 0, 0, 0, halfSize); + tess.draw(); + } + + // ==================== BATCHED CUBE RENDERING ==================== + + /** + * Begin a batched cube draw call. All cubes added via {@link #addBatchedCube} + * will be drawn in a single tessellator pass when {@link #endCubeBatch} is called. + */ + protected void beginCubeBatch() { + Tessellator.instance.startDrawingQuads(); + } + + /** + * Add a cube to the current batch at the given position with per-cube color/alpha. + * Must be called between {@link #beginCubeBatch} and {@link #endCubeBatch}. + */ + protected void addBatchedCube(float px, float py, float pz, float halfSize, + float r, float g, float b, float alpha) { + Tessellator tess = Tessellator.instance; + tess.setColorRGBA_F(r, g, b, alpha); + addCubeVertices(tess, px, py, pz, halfSize); + } + + /** + * End the batched cube draw call, flushing all queued cubes in one draw. + */ + protected void endCubeBatch() { + Tessellator.instance.draw(); + } + + /** + * Add the 24 vertices (6 faces) of a cube centered at (px,py,pz) to the tessellator. + * Color must be set before calling. Used by both single and batched cube rendering. + */ + private static void addCubeVertices(Tessellator tess, float px, float py, float pz, float h) { + float x0 = px - h, x1 = px + h; + float y0 = py - h, y1 = py + h; + float z0 = pz - h, z1 = pz + h; + + // Front (z+) tess.setNormal(0, 0, 1); - tess.addVertex(-halfSize, -halfSize, halfSize); - tess.addVertex(halfSize, -halfSize, halfSize); - tess.addVertex(halfSize, halfSize, halfSize); - tess.addVertex(-halfSize, halfSize, halfSize); - tess.draw(); + tess.addVertex(x0, y0, z1); + tess.addVertex(x1, y0, z1); + tess.addVertex(x1, y1, z1); + tess.addVertex(x0, y1, z1); - // Back face (z-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); + // Back (z-) tess.setNormal(0, 0, -1); - tess.addVertex(halfSize, -halfSize, -halfSize); - tess.addVertex(-halfSize, -halfSize, -halfSize); - tess.addVertex(-halfSize, halfSize, -halfSize); - tess.addVertex(halfSize, halfSize, -halfSize); - tess.draw(); + tess.addVertex(x1, y0, z0); + tess.addVertex(x0, y0, z0); + tess.addVertex(x0, y1, z0); + tess.addVertex(x1, y1, z0); - // Top face (y+) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); + // Top (y+) tess.setNormal(0, 1, 0); - tess.addVertex(-halfSize, halfSize, halfSize); - tess.addVertex(halfSize, halfSize, halfSize); - tess.addVertex(halfSize, halfSize, -halfSize); - tess.addVertex(-halfSize, halfSize, -halfSize); - tess.draw(); + tess.addVertex(x0, y1, z1); + tess.addVertex(x1, y1, z1); + tess.addVertex(x1, y1, z0); + tess.addVertex(x0, y1, z0); - // Bottom face (y-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); + // Bottom (y-) tess.setNormal(0, -1, 0); - tess.addVertex(-halfSize, -halfSize, -halfSize); - tess.addVertex(halfSize, -halfSize, -halfSize); - tess.addVertex(halfSize, -halfSize, halfSize); - tess.addVertex(-halfSize, -halfSize, halfSize); - tess.draw(); + tess.addVertex(x0, y0, z0); + tess.addVertex(x1, y0, z0); + tess.addVertex(x1, y0, z1); + tess.addVertex(x0, y0, z1); - // Right face (x+) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); + // Right (x+) tess.setNormal(1, 0, 0); - tess.addVertex(halfSize, -halfSize, halfSize); - tess.addVertex(halfSize, -halfSize, -halfSize); - tess.addVertex(halfSize, halfSize, -halfSize); - tess.addVertex(halfSize, halfSize, halfSize); - tess.draw(); + tess.addVertex(x1, y0, z1); + tess.addVertex(x1, y0, z0); + tess.addVertex(x1, y1, z0); + tess.addVertex(x1, y1, z1); - // Left face (x-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); + // Left (x-) tess.setNormal(-1, 0, 0); - tess.addVertex(-halfSize, -halfSize, -halfSize); - tess.addVertex(-halfSize, -halfSize, halfSize); - tess.addVertex(-halfSize, halfSize, halfSize); - tess.addVertex(-halfSize, halfSize, -halfSize); - tess.draw(); + tess.addVertex(x0, y0, z0); + tess.addVertex(x0, y0, z1); + tess.addVertex(x0, y1, z1); + tess.addVertex(x0, y1, z0); } /** diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java index 02388621a..bb1f94cab 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java @@ -10,6 +10,8 @@ import org.lwjgl.opengl.GL11; /** * Voxel-style renderer for {@link EntityEnergyExplosion}. * Draws expanding cubic shells with a short fade-out. + * Uses batched cube rendering for performance — all cubes of each layer + * are drawn in a single tessellator pass instead of per-cube draw calls. */ @SideOnly(Side.CLIENT) public class RenderEnergyExplosion extends RenderEnergy { @@ -55,44 +57,73 @@ public class RenderEnergyExplosion extends RenderEnergy { int steps = Math.max(2, Math.min(6, (int) Math.ceil(radius * 0.65f) + 1)); float cell = radius / steps; float cubeHalf = Math.max(0.04f, cell * 0.22f * (0.90f - life * 0.25f)); + float outerScale = hasOuter ? 1.0f + explosion.getOuterColorWidth() * 0.35f : 1.0f; long seed = explosion.getRenderSeed(); + // Pre-extract RGB for batched rendering + float outerR = ((outerColor >> 16) & 0xFF) / 255.0f; + float outerG = ((outerColor >> 8) & 0xFF) / 255.0f; + float outerB = (outerColor & 0xFF) / 255.0f; + float innerR = ((innerColor >> 16) & 0xFF) / 255.0f; + float innerG = ((innerColor >> 8) & 0xFF) / 255.0f; + float innerB = (innerColor & 0xFF) / 255.0f; + GL11.glDepthMask(false); + + // Smoke layer — single batched pass renderSmokeVoxelShell(radius, life, seed); - for (int ix = -steps; ix <= steps; ix++) { - for (int iy = -steps; iy <= steps; iy++) { - for (int iz = -steps; iz <= steps; iz++) { - int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); - if (edge != steps) continue; - if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue; + // Outer color layer — single batched pass + if (hasOuter) { + float outerHalf = cubeHalf * outerScale; + float oa = outerAlpha * fade; + beginCubeBatch(); + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue; - float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f; - float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f; - float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f; - - GL11.glPushMatrix(); - GL11.glTranslatef(px, py, pz); - - if (hasOuter) { - GL11.glPushMatrix(); - float outerScale = 1.0f + explosion.getOuterColorWidth() * 0.35f; - GL11.glScalef(outerScale, outerScale, outerScale); - renderCube(outerColor, outerAlpha * fade, cubeHalf); - GL11.glPopMatrix(); + float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f; + float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f; + float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f; + addBatchedCube(px, py, pz, outerHalf, outerR, outerG, outerB, oa); } - - renderCube(innerColor, innerAlpha * fade, cubeHalf * 0.9f); - GL11.glPopMatrix(); } } + endCubeBatch(); } + + // Inner color layer — single batched pass + { + float innerHalf = cubeHalf * 0.9f; + float ia = innerAlpha * fade; + beginCubeBatch(); + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue; + + float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f; + float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f; + float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f; + addBatchedCube(px, py, pz, innerHalf, innerR, innerG, innerB, ia); + } + } + } + endCubeBatch(); + } + renderCoreFlash(innerColor, innerAlpha, radius, cubeHalf, life); GL11.glDepthMask(true); } /** * Adds large translucent white/gray cubes to fake a smoky volumetric expansion. + * All smoke cubes are batched into a single draw call. */ private void renderSmokeVoxelShell(float radius, float life, long seed) { int smokeSteps = Math.max(2, Math.min(5, (int) Math.ceil(radius * 0.5f) + 1)); @@ -101,6 +132,8 @@ public class RenderEnergyExplosion extends RenderEnergy { float smokeCubeHalf = Math.max(0.07f, smokeCell * 0.24f * (0.95f - life * 0.25f)); float smokeAlpha = Math.min(MAX_SMOKE_CUBE_ALPHA, (0.15f + (1.0f - life) * 0.55f)); + // White = 1,1,1 Gray (0xDCDCDC) = 0.863,0.863,0.863 + beginCubeBatch(); for (int ix = -smokeSteps; ix <= smokeSteps; ix++) { for (int iy = -smokeSteps; iy <= smokeSteps; iy++) { for (int iz = -smokeSteps; iz <= smokeSteps; iz++) { @@ -115,16 +148,14 @@ public class RenderEnergyExplosion extends RenderEnergy { float py = iy * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 2) * smokeJitter; float pz = iz * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 3) * smokeJitter; - int smokeColor = ((h >>> 9) & 1) == 0 ? 0xFFFFFF : 0xDCDCDC; + boolean isWhite = ((h >>> 9) & 1) == 0; + float gray = isWhite ? 1.0f : 0.863f; float alpha = smokeAlpha * (0.78f + (((h >>> 10) & 15) / 15.0f) * 0.22f); - - GL11.glPushMatrix(); - GL11.glTranslatef(px, py, pz); - renderCube(smokeColor, alpha, smokeCubeHalf); - GL11.glPopMatrix(); + addBatchedCube(px, py, pz, smokeCubeHalf, gray, gray, gray, alpha); } } } + endCubeBatch(); } /** @@ -138,11 +169,8 @@ public class RenderEnergyExplosion extends RenderEnergy { float whiteAlpha = Math.min(1.0f, 0.25f + flash * 0.95f); float innerFlashAlpha = Math.min(1.0f, innerAlpha * (0.45f + flash * 0.95f)); - GL11.glPushMatrix(); renderCube(0xFFFFFF, whiteAlpha, coreHalf * 1.45f); - GL11.glScalef(0.82f, 0.82f, 0.82f); - renderCube(innerColor, innerFlashAlpha, coreHalf); - GL11.glPopMatrix(); + renderCube(innerColor, innerFlashAlpha, coreHalf * 0.82f); } private float saturate(float value) { diff --git a/src/main/java/kamkeel/npcs/controllers/EnergyController.java b/src/main/java/kamkeel/npcs/controllers/EnergyController.java index 3c3ec98c9..ffd25c400 100644 --- a/src/main/java/kamkeel/npcs/controllers/EnergyController.java +++ b/src/main/java/kamkeel/npcs/controllers/EnergyController.java @@ -9,7 +9,9 @@ import net.minecraft.world.World; import noppes.npcs.api.IEnergyController; import noppes.npcs.api.IWorld; import noppes.npcs.api.entity.*; +import noppes.npcs.api.entity.IEnergyExplosion; import noppes.npcs.scripted.NpcAPI; +import noppes.npcs.scripted.entity.ScriptEnergyExplosion; import java.util.ArrayList; import java.util.List; @@ -143,6 +145,10 @@ public class EnergyController implements IEnergyController { return entity; } + public EntityEnergyExplosion createExplosionInternal(World world, Entity owner, double x, double y, double z, float radius) { + return new EntityEnergyExplosion(world, owner, x, y, z, radius); + } + // ═══════════════════════════════════════════════════════════════════ // IEnergyController (Script API methods) // ═══════════════════════════════════════════════════════════════════ @@ -194,4 +200,10 @@ public class EnergyController implements IEnergyController { EntityEnergyPanel entity = createPanelInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z); return (IEnergyPanel) NpcAPI.Instance().getIEntity(entity); } + + @Override + public IEnergyExplosion createExplosion(IWorld world, IEntity owner, double x, double y, double z, float radius) { + EntityEnergyExplosion entity = createExplosionInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, radius); + return new ScriptEnergyExplosion<>(entity); + } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java index d20975b7d..b23f1f269 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java @@ -2,16 +2,28 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.EnergyController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.util.AttributeAttackUtil; import kamkeel.npcs.util.CNPCDebug; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.DamageSource; import net.minecraft.util.MathHelper; import net.minecraft.world.World; +import noppes.npcs.NpcDamageSource; +import noppes.npcs.entity.EntityNPCInterface; + +import java.util.List; /** - * Short-lived visual explosion entity for energy projectile impacts. - * This is render-only and never applies damage itself. + * Short-lived explosion entity for energy projectile impacts and standalone API use. + * Renders an expanding voxel shell. When damage is enabled, applies area damage on the first tick. */ public class EntityEnergyExplosion extends EntityEnergyAbility { @@ -21,12 +33,23 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { private float renderRadius = 0.0f; private long renderSeed = 0L; + // ==================== DAMAGE (for standalone API use) ==================== + private float damage = 0f; + private float knockback = 0f; + private float knockbackUp = 0.1f; + private float damageFalloff = 0.5f; + private boolean damageEnabled = false; + private boolean hasDamaged = false; + public EntityEnergyExplosion(World world) { super(world); this.noClip = true; this.setSize(0.1f, 0.1f); } + /** + * Constructor for projectile-spawned explosions (render-only, no damage). + */ public EntityEnergyExplosion(World world, EntityEnergyProjectile source, float radius) { this(world); @@ -43,6 +66,19 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { this.renderSeed = world != null ? world.rand.nextLong() : 0L; } + /** + * Constructor for standalone API-spawned explosions. + */ + public EntityEnergyExplosion(World world, Entity owner, double x, double y, double z, float radius) { + this(world); + if (owner != null) { + this.ownerEntityId = owner.getEntityId(); + } + this.setPosition(x, y, z); + setExplosionRadius(radius); + this.renderSeed = world != null ? world.rand.nextLong() : 0L; + } + @Override protected void entityInit() { super.entityInit(); @@ -58,6 +94,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { float eased = 1.0f - (1.0f - progress) * (1.0f - progress); this.renderRadius = maxRadius * eased; + // Apply damage on first server tick + if (damageEnabled && !hasDamaged && !worldObj.isRemote && ticksExisted >= 1) { + applyExplosionDamage(); + hasDamaged = true; + } + // Debug logging { boolean isClient = worldObj.isRemote; @@ -74,12 +116,110 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { } } + // ==================== DAMAGE LOGIC ==================== + + private void applyExplosionDamage() { + if (damage <= 0 || maxRadius <= 0) return; + + Entity owner = getOwnerEntity(); + double explosionRadSq = maxRadius * maxRadius; + + AxisAlignedBB explosionBox = AxisAlignedBB.getBoundingBox( + posX - maxRadius, posY - maxRadius, posZ - maxRadius, + posX + maxRadius, posY + maxRadius, posZ + maxRadius + ); + + @SuppressWarnings("unchecked") + List targets = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, explosionBox); + + for (EntityLivingBase target : targets) { + if (target == owner) continue; + if (target.boundingBox == null) continue; + + double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX)); + double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY)); + double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ)); + double dx = closestX - posX; + double dy = closestY - posY; + double dz = closestZ - posZ; + double distSq = dx * dx + dy * dy + dz * dz; + if (distSq > explosionRadSq) continue; + + float falloff = 1.0f; + if (damageFalloff != 0.0f && distSq > 0.0D) { + float dist = (float) Math.sqrt(distSq); + falloff = 1.0f - (dist / maxRadius) * damageFalloff; + } + + float finalDmg = damage * falloff; + float finalKb = knockback * falloff; + + // Route through EnergyController for addon integration (e.g. DBC) + boolean handled = false; + if (customDamageData != null && owner instanceof EntityLivingBase) { + double kbDirX = target.posX - posX; + double kbDirZ = target.posZ - posZ; + handled = EnergyController.Instance.fireOnEnergyDamage( + this, (EntityLivingBase) owner, target, + finalDmg, finalKb, knockbackUp, kbDirX, kbDirZ, 1.0f, customDamageData); + } + + int prevHurtResist = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames); + try { + if (!handled) { + float dmgToApply = finalDmg; + + // Magic pipeline + if (this.magicData != null && !this.magicData.isEmpty() && owner instanceof EntityLivingBase) { + dmgToApply = AttributeAttackUtil.calculateAbilityDamage( + (EntityLivingBase) owner, target, finalDmg, this.magicData); + } + + if (owner instanceof EntityNPCInterface) { + target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmgToApply); + } else if (owner instanceof EntityPlayer) { + target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmgToApply); + } else if (owner instanceof EntityLivingBase) { + target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmgToApply); + } else { + target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmgToApply); + } + } + } finally { + Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, prevHurtResist); + } + + // Apply knockback + if (finalKb > 0) { + double kbDx = target.posX - posX; + double kbDz = target.posZ - posZ; + double len = Math.sqrt(kbDx * kbDx + kbDz * kbDz); + if (len > 0) { + target.addVelocity((kbDx / len) * finalKb * 0.5, knockbackUp, (kbDz / len) * finalKb * 0.5); + target.velocityChanged = true; + } + } + } + + worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); + } + + // ==================== RADIUS / DURATION ==================== + public void setExplosionRadius(float radius) { this.maxRadius = Math.max(0.5f, sanitize(radius, 2.0f, MAX_ENTITY_RADIUS)); this.durationTicks = Math.max(6, Math.min(18, (int) (6 + this.maxRadius * 1.8f))); this.setSize(this.maxRadius * 2.0f, this.maxRadius * 2.0f); } + public float getExplosionRadius() { + return maxRadius; + } + + public int getDurationTicks() { + return durationTicks; + } + public float getInterpolatedRadius(float partialTicks) { return this.prevRenderRadius + (this.renderRadius - this.prevRenderRadius) * partialTicks; } @@ -94,6 +234,47 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { return renderSeed; } + // ==================== DAMAGE GETTERS/SETTERS ==================== + + public float getDamage() { + return damage; + } + + public void setDamage(float damage) { + this.damage = Math.max(0, damage); + this.damageEnabled = this.damage > 0; + } + + public float getKnockback() { + return knockback; + } + + public void setKnockback(float knockback) { + this.knockback = Math.max(0, knockback); + } + + public float getKnockbackUp() { + return knockbackUp; + } + + public void setKnockbackUp(float knockbackUp) { + this.knockbackUp = knockbackUp; + } + + public float getDamageFalloff() { + return damageFalloff; + } + + public void setDamageFalloff(float falloff) { + this.damageFalloff = MathHelper.clamp_float(falloff, 0.0f, 1.0f); + } + + public boolean isDamageEnabled() { + return damageEnabled; + } + + // ==================== RENDER / COLLISION ==================== + @Override @SideOnly(Side.CLIENT) public boolean isInRangeToRenderDist(double distance) { @@ -106,6 +287,8 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { return false; } + // ==================== NBT ==================== + @Override protected void readEntityFromNBT(NBTTagCompound nbt) { // Intentionally empty - transient visual entity. @@ -125,6 +308,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { nbt.setFloat("ExplosionRadius", maxRadius); nbt.setInteger("ExplosionDuration", durationTicks); nbt.setLong("ExplosionSeed", renderSeed); + if (damageEnabled) { + nbt.setFloat("Damage", damage); + nbt.setFloat("Knockback", knockback); + nbt.setFloat("KnockbackUp", knockbackUp); + nbt.setFloat("DamageFalloff", damageFalloff); + } } @Override @@ -146,5 +335,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility { this.renderSeed = nbt.hasKey("ExplosionSeed") ? nbt.getLong("ExplosionSeed") : 0L; this.renderRadius = 0.0f; this.prevRenderRadius = 0.0f; + if (nbt.hasKey("Damage")) { + this.damage = nbt.getFloat("Damage"); + this.knockback = nbt.getFloat("Knockback"); + this.knockbackUp = nbt.getFloat("KnockbackUp"); + this.damageFalloff = nbt.getFloat("DamageFalloff"); + this.damageEnabled = this.damage > 0; + } } } diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index c1871d9b9..fd301b802 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -16,6 +16,7 @@ import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; import kamkeel.npcs.entity.EntityEnergyPanel; import kamkeel.npcs.entity.EntityEnergySweeper; +import kamkeel.npcs.entity.EntityEnergyExplosion; import kamkeel.npcs.entity.EntityEnergyProjectile; import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.block.Block; @@ -102,6 +103,7 @@ import noppes.npcs.scripted.entity.ScriptDBCPlayer; import noppes.npcs.scripted.entity.ScriptEnergyBeam; import noppes.npcs.scripted.entity.ScriptEnergyDisc; import noppes.npcs.scripted.entity.ScriptEnergyDome; +import noppes.npcs.scripted.entity.ScriptEnergyExplosion; import noppes.npcs.scripted.entity.ScriptEnergyLaser; import noppes.npcs.scripted.entity.ScriptEnergyOrb; import noppes.npcs.scripted.entity.ScriptEnergyPanel; @@ -466,6 +468,8 @@ public class NpcAPI extends AbstractNpcAPI { data = new ScriptEntityData(new ScriptEnergyPanel<>((EntityEnergyPanel) entity)); else if (entity instanceof EntityEnergySweeper) data = new ScriptEntityData(new ScriptEnergySweeper<>((EntityEnergySweeper) entity)); + else if (entity instanceof EntityEnergyExplosion) + data = new ScriptEntityData(new ScriptEnergyExplosion<>((EntityEnergyExplosion) entity)); else if (entity instanceof EntityAbilityZone) data = new ScriptEntityData(new ScriptEnergyZone<>((EntityAbilityZone) entity)); else diff --git a/src/main/java/noppes/npcs/scripted/constants/EntityType.java b/src/main/java/noppes/npcs/scripted/constants/EntityType.java index 62cd482fa..e92babb24 100644 --- a/src/main/java/noppes/npcs/scripted/constants/EntityType.java +++ b/src/main/java/noppes/npcs/scripted/constants/EntityType.java @@ -23,4 +23,5 @@ public class EntityType { public static final int ENERGY_BARRIER = 19; public static final int ENERGY_DOME = 20; public static final int ENERGY_PANEL = 21; + public static final int ENERGY_EXPLOSION = 22; } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java index 9c5a8b8be..787c27ee3 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java @@ -60,7 +60,7 @@ public class ScriptEnergyBeam extends ScriptEnergyP // ==================== FIRE ==================== @Override - public void fire(IEntity target) { + public void fireAt(IEntity target) { ensureSpawned(); EntityLivingBase mcTarget = null; if (target != null && target.getMCEntity() instanceof EntityLivingBase) { @@ -70,7 +70,7 @@ public class ScriptEnergyBeam extends ScriptEnergyP } @Override - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { ensureSpawned(); double dx = x - entity.posX; double dy = y - entity.posY; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java index c02fdad31..34ebb6305 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java @@ -76,7 +76,7 @@ public class ScriptEnergyDisc extends ScriptEnergyP // ==================== FIRE ==================== @Override - public void fire(IEntity target) { + public void fireAt(IEntity target) { ensureSpawned(); EntityLivingBase mcTarget = null; if (target != null && target.getMCEntity() instanceof EntityLivingBase) { @@ -86,7 +86,7 @@ public class ScriptEnergyDisc extends ScriptEnergyP } @Override - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { ensureSpawned(); double dx = x - entity.posX; double dy = y - entity.posY; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyExplosion.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyExplosion.java new file mode 100644 index 000000000..8ce2e359f --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyExplosion.java @@ -0,0 +1,109 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergyExplosion; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; +import noppes.npcs.api.entity.IEnergyExplosion; +import noppes.npcs.scripted.constants.EntityType; + +import java.util.UUID; + +/** + * Script wrapper for standalone energy explosion entities. + */ +public class ScriptEnergyExplosion extends ScriptEnergyAbility implements IEnergyExplosion { + + public ScriptEnergyExplosion(T entity) { + super(entity); + } + + // ==================== TYPE ==================== + + @Override + public int getType() { + return EntityType.ENERGY_EXPLOSION; + } + + @Override + public boolean typeOf(int type) { + return type == EntityType.ENERGY_EXPLOSION || super.typeOf(type); + } + + // ==================== RADIUS & DURATION ==================== + + @Override + public float getRadius() { + return entity.getExplosionRadius(); + } + + @Override + public void setRadius(float radius) { + entity.setExplosionRadius(radius); + } + + @Override + public int getDuration() { + return entity.getDurationTicks(); + } + + // ==================== DAMAGE ==================== + + @Override + public float getDamage() { + return entity.getDamage(); + } + + @Override + public void setDamage(float damage) { + entity.setDamage(damage); + } + + @Override + public float getKnockback() { + return entity.getKnockback(); + } + + @Override + public void setKnockback(float knockback) { + entity.setKnockback(knockback); + } + + @Override + public float getKnockbackUp() { + return entity.getKnockbackUp(); + } + + @Override + public void setKnockbackUp(float knockbackUp) { + entity.setKnockbackUp(knockbackUp); + } + + @Override + public float getDamageFalloff() { + return entity.getDamageFalloff(); + } + + @Override + public void setDamageFalloff(float falloff) { + entity.setDamageFalloff(falloff); + } + + // ==================== SPAWNING ==================== + + @Override + public void spawn() { + if (entity.addedToChunk || entity.worldObj == null) return; + + if (!entity.worldObj.isRemote) { + // Send visual to clients via packet (explosion is packet-driven, not a tracked entity) + String instanceId = "energy_explosion_" + UUID.randomUUID(); + PacketHandler.Instance.sendToAll(new EnergyExplosionSpawnPacket( + instanceId, entity.exportSpawnNBT())); + + // Spawn server-side for damage logic (if damage enabled) + if (entity.isDamageEnabled()) { + entity.worldObj.spawnEntityInWorld(entity); + } + } + } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java index 6c0b8f077..eb6159bf5 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java @@ -92,7 +92,7 @@ public class ScriptEnergyLaser extends ScriptEnerg // ==================== FIRE ==================== @Override - public void fire(IEntity target) { + public void fireAt(IEntity target) { ensureSpawned(); EntityLivingBase mcTarget = null; if (target != null && target.getMCEntity() instanceof EntityLivingBase) { @@ -102,7 +102,7 @@ public class ScriptEnergyLaser extends ScriptEnerg } @Override - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { ensureSpawned(); double dx = x - entity.posX; double dy = y - entity.posY; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java index cb47a092c..3821190cb 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyOrb.java @@ -28,7 +28,7 @@ public class ScriptEnergyOrb extends ScriptEnergyPro } @Override - public void fire(IEntity target) { + public void fireAt(IEntity target) { ensureSpawned(); EntityLivingBase mcTarget = null; if (target != null && target.getMCEntity() instanceof EntityLivingBase) { @@ -38,7 +38,7 @@ public class ScriptEnergyOrb extends ScriptEnergyPro } @Override - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { ensureSpawned(); double dx = x - entity.posX; double dy = y - entity.posY; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index db810848e..a4ca6697b 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -259,7 +259,7 @@ public class ScriptEnergyProjectile extends Sc // ==================== FIRE ==================== - public void fire(IEntity target) { + public void fireAt(IEntity target) { if (target != null) { Entity mcTarget = target.getMCEntity(); setMotionToward(mcTarget.posX, mcTarget.posY + mcTarget.getEyeHeight(), mcTarget.posZ); @@ -267,12 +267,12 @@ public class ScriptEnergyProjectile extends Sc ensureSpawned(); } - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { setMotionToward(x, y, z); ensureSpawned(); } - public void fire(float yaw, float pitch) { + public void fireDirection(float yaw, float pitch) { float yawRad = (float) Math.toRadians(yaw); float pitchRad = (float) Math.toRadians(pitch); entity.motionX = -Math.sin(yawRad) * Math.cos(pitchRad) * entity.getSpeed(); @@ -281,16 +281,16 @@ public class ScriptEnergyProjectile extends Sc ensureSpawned(); } - public void fire(IEntityLivingBase caster) { + public void fireFrom(IEntityLivingBase caster) { if (caster == null) return; initFromCaster(caster); - fire(caster.getRotation(), caster.getPitch()); + fireDirection(caster.getRotation(), caster.getPitch()); } - public void fire(IEntityLivingBase caster, IEntity target) { + public void fireFrom(IEntityLivingBase caster, IEntity target) { if (caster == null) return; initFromCaster(caster); - fire(target); + fireAt(target); } /** diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java index f120350c4..7614939dd 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java @@ -48,7 +48,7 @@ public class ScriptEnergySlicer extends ScriptEner // ==================== FIRE ==================== @Override - public void fire(IEntity target) { + public void fireAt(IEntity target) { ensureSpawned(); EntityLivingBase mcTarget = null; if (target != null && target.getMCEntity() instanceof EntityLivingBase) { @@ -58,7 +58,7 @@ public class ScriptEnergySlicer extends ScriptEner } @Override - public void fire(double x, double y, double z) { + public void fireAt(double x, double y, double z) { ensureSpawned(); double dx = x - entity.posX; double dy = y - entity.posY;