From 0ebdfafccd16c27a150782bb4a88d41411278603 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 08:54:55 +0000 Subject: [PATCH 1/3] Add Energy Barrier and Slicer ability system Implements four new energy ability types: - Energy Dome: Spherical barrier centered on caster that blocks incoming energy projectiles. Configurable per-projectile-type damage multipliers, HP/durability and duration modes. RING telegraph. - Energy Wall: Placeable flat energy panel with configurable dimensions and height offset (can float above ground). Optional launching mode fires the wall forward with damage/knockback. - Energy Shield: Held energy panel that follows the caster and rotates with their look direction. Defensive blocking ability. - Energy Slicer: Thin wide blade projectile (JJK Cleave inspired). Extends AbstractEnergyProjectileAbility. Piercing by default. Architecture: - EnergyBarrierData: HP, duration, per-type damage multiplier map - EnergyPanelData: Panel dimensions, height offset, launch properties - AbstractEnergyBarrierAbility: Shared base for Dome/Wall/Shield - EntityEnergyDome, EntityEnergyPanel, EntityEnergySlicer entities - RenderEnergyDome, RenderEnergyPanel, RenderEnergySlicer renderers with translucent inner/outer layers (voxel-blocky aesthetic) - Projectile-barrier collision system in EntityAbilityProjectile https://claude.ai/code/session_01EV9KpiykvW4rASz7ju5iTG --- .../client/renderer/RenderEnergyDome.java | 153 ++++++ .../client/renderer/RenderEnergyPanel.java | 171 +++++++ .../client/renderer/RenderEnergySlicer.java | 163 +++++++ .../data/ability/AbilityController.java | 5 + .../data/ability/data/EnergyBarrierData.java | 115 +++++ .../data/ability/data/EnergyPanelData.java | 81 ++++ .../data/ability/type/AbilityEnergyDome.java | 122 +++++ .../ability/type/AbilityEnergyShield.java | 144 ++++++ .../ability/type/AbilityEnergySlicer.java | 176 +++++++ .../data/ability/type/AbilityEnergyWall.java | 179 +++++++ .../type/AbstractEnergyBarrierAbility.java | 224 +++++++++ .../npcs/entity/EntityAbilityProjectile.java | 53 +++ .../kamkeel/npcs/entity/EntityEnergyDome.java | 314 ++++++++++++ .../npcs/entity/EntityEnergyPanel.java | 447 ++++++++++++++++++ .../npcs/entity/EntityEnergySlicer.java | 337 +++++++++++++ src/main/java/noppes/npcs/CustomNpcs.java | 6 + .../java/noppes/npcs/client/ClientProxy.java | 9 + 17 files changed, 2699 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java create mode 100644 src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java create mode 100644 src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java new file mode 100644 index 000000000..f6a22e6ed --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java @@ -0,0 +1,153 @@ +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.EntityEnergyDome; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.Entity; +import org.lwjgl.opengl.GL11; + +/** + * Renders the Energy Dome entity as a translucent blocky hemisphere. + * Built from cube panels forming a dome shape - voxel-blocky aesthetic. + * Inner layer more opaque, outer layer translucent glow. + */ +@SideOnly(Side.CLIENT) +public class RenderEnergyDome extends RenderAbilityProjectile { + + @Override + public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + EntityEnergyDome dome = (EntityEnergyDome) entity; + float radius = dome.getDomeRadius(); + float healthPercent = dome.getHealthPercent(); + + setupRenderState(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + // Lightning + if (dome.hasLightningEffect()) { + renderAttachedLightning(dome, radius); + } + + // Hit flash effect + float flashAlpha = 0.0f; + if (dome.getHitFlash() > 0) { + flashAlpha = dome.getHitFlash() / 4.0f * 0.3f; + } + + float innerScale = 0.95f; + + // Render outer dome (translucent) + if (dome.isOuterColorEnabled()) { + float outerScale = 1.0f + dome.getOuterColorWidth() * 0.1f; + float outerAlpha = dome.getOuterColorAlpha() * healthPercent; + GL11.glDepthMask(false); + renderBlockyDome(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8); + GL11.glDepthMask(true); + } + + // Render inner dome (more opaque) + float innerAlpha = (0.5f + 0.3f * healthPercent); + renderBlockyDome(dome.getInnerColor(), innerAlpha + flashAlpha, radius * innerScale, 8); + + GL11.glPopMatrix(); + + restoreRenderState(); + } + + /** + * Render a dome made of blocky quad panels - voxel style. + * Uses latitude/longitude segments to form a hemisphere of flat panels. + */ + private void renderBlockyDome(int color, float alpha, float radius, int segments) { + float[] rgb = extractRGB(color); + float r = rgb[0], g = rgb[1], b = rgb[2]; + + Tessellator tess = Tessellator.instance; + int latSegments = segments / 2; // Half sphere + + for (int lat = 0; lat < latSegments; lat++) { + float theta1 = (float) (Math.PI * 0.5 * lat / latSegments); + float theta2 = (float) (Math.PI * 0.5 * (lat + 1) / latSegments); + + float y1 = (float) Math.sin(theta1) * radius; + float y2 = (float) Math.sin(theta2) * radius; + float r1 = (float) Math.cos(theta1) * radius; + float r2 = (float) Math.cos(theta2) * radius; + + for (int lon = 0; lon < segments; lon++) { + float phi1 = (float) (2 * Math.PI * lon / segments); + float phi2 = (float) (2 * Math.PI * (lon + 1) / segments); + + float x1 = (float) Math.cos(phi1) * r1; + float z1 = (float) Math.sin(phi1) * r1; + float x2 = (float) Math.cos(phi2) * r1; + float z2 = (float) Math.sin(phi2) * r1; + float x3 = (float) Math.cos(phi2) * r2; + float z3 = (float) Math.sin(phi2) * r2; + float x4 = (float) Math.cos(phi1) * r2; + float z4 = (float) Math.sin(phi1) * r2; + + // Normal pointing outward + float nx = (x1 + x2 + x3 + x4) * 0.25f; + float ny = (y1 + y1 + y2 + y2) * 0.25f; + float nz = (z1 + z2 + z3 + z4) * 0.25f; + float nLen = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); + if (nLen > 0) { nx /= nLen; ny /= nLen; nz /= nLen; } + + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(nx, ny, nz); + tess.addVertex(x1, y1, z1); + tess.addVertex(x2, y1, z2); + tess.addVertex(x3, y2, z3); + tess.addVertex(x4, y2, z4); + tess.draw(); + } + } + + // Bottom ring (ground level circle) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha * 0.5f); + tess.setNormal(0, -1, 0); + for (int lon = 0; lon < segments; lon++) { + float phi1 = (float) (2 * Math.PI * lon / segments); + float phi2 = (float) (2 * Math.PI * (lon + 1) / segments); + float x1 = (float) Math.cos(phi1) * radius; + float z1 = (float) Math.sin(phi1) * radius; + float x2 = (float) Math.cos(phi2) * radius; + float z2 = (float) Math.sin(phi2) * radius; + + tess.addVertex(0, 0, 0); + tess.addVertex(x1, 0, z1); + tess.addVertex(x2, 0, z2); + tess.addVertex(0, 0, 0); + } + tess.draw(); + } + + /** + * Render lightning arcs on the dome surface. + */ + private void renderAttachedLightning(EntityEnergyDome dome, float radius) { + AttachedLightningRenderer.LightningState state = getLightningState(dome); + float density = dome.getLightningDensity(); + float lightningRadius = radius + dome.getLightningRadius(); + int outerColor = dome.getOuterColor(); + int innerColor = dome.getInnerColor(); + int fadeTime = dome.getLightningFadeTime(); + + state.update(density, lightningRadius, outerColor, innerColor, fadeTime); + state.render(); + } + + private AttachedLightningRenderer.LightningState getLightningState(EntityEnergyDome dome) { + if (dome.lightningState == null) { + dome.lightningState = new AttachedLightningRenderer.LightningState(); + } + return (AttachedLightningRenderer.LightningState) dome.lightningState; + } +} diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java new file mode 100644 index 000000000..25352ec54 --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java @@ -0,0 +1,171 @@ +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.EntityEnergyPanel; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.Entity; +import org.lwjgl.opengl.GL11; + +/** + * Renders the Energy Panel entity as a flat translucent rectangle. + * Used by both Energy Wall and Energy Shield abilities. + * Voxel-blocky style with inner/outer layers. + */ +@SideOnly(Side.CLIENT) +public class RenderEnergyPanel extends RenderAbilityProjectile { + + @Override + public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + EntityEnergyPanel panel = (EntityEnergyPanel) entity; + + float width = panel.getPanelData().panelWidth; + float height = panel.getPanelData().panelHeight; + float panelYaw = panel.getPanelYaw(); + float healthPercent = panel.getHealthPercent(); + + setupRenderState(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + // Rotate panel to face the correct direction + GL11.glRotatef(-panelYaw, 0.0f, 1.0f, 0.0f); + + // Lightning (before rotation for uniform spread) + if (panel.hasLightningEffect()) { + renderAttachedLightning(panel, Math.max(width, height) * 0.5f); + } + + // Hit flash + float flashAlpha = 0.0f; + if (panel.getHitFlash() > 0) { + flashAlpha = panel.getHitFlash() / 4.0f * 0.3f; + } + + // Subtle pulsing + float pulseTime = entity.ticksExisted + partialTicks; + float pulse = (float) Math.sin(pulseTime * 0.08f) * 0.02f; + float scale = 1.0f + pulse; + + GL11.glPushMatrix(); + GL11.glScalef(scale, scale, scale); + + float innerScale = 0.9f; + float panelThickness = 0.1f; + + // Render outer panel (translucent glow) + if (panel.isOuterColorEnabled()) { + float outerScale = 1.0f + panel.getOuterColorWidth() * 0.15f; + float outerAlpha = panel.getOuterColorAlpha() * healthPercent; + GL11.glDepthMask(false); + renderPanel(panel.getOuterColor(), outerAlpha + flashAlpha, + width * 0.5f * outerScale, height * 0.5f * outerScale, panelThickness * 1.5f); + GL11.glDepthMask(true); + } + + // Render inner panel (more opaque core) + float innerAlpha = (0.6f + 0.3f * healthPercent); + renderPanel(panel.getInnerColor(), innerAlpha + flashAlpha, + width * 0.5f * innerScale, height * 0.5f * innerScale, panelThickness); + + GL11.glPopMatrix(); + GL11.glPopMatrix(); + + restoreRenderState(); + } + + /** + * Render a flat panel (thin box) centered at origin. + * Panel faces along the Z axis (width on X, height on Y). + */ + private void renderPanel(int color, float alpha, float halfWidth, float halfHeight, float halfThickness) { + float[] rgb = extractRGB(color); + float r = rgb[0], g = rgb[1], b = rgb[2]; + + Tessellator tess = Tessellator.instance; + + // Front face (z+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, 0, 1); + tess.addVertex(-halfWidth, -halfHeight, halfThickness); + tess.addVertex(halfWidth, -halfHeight, halfThickness); + tess.addVertex(halfWidth, halfHeight, halfThickness); + tess.addVertex(-halfWidth, halfHeight, halfThickness); + tess.draw(); + + // Back face (z-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, 0, -1); + tess.addVertex(halfWidth, -halfHeight, -halfThickness); + tess.addVertex(-halfWidth, -halfHeight, -halfThickness); + tess.addVertex(-halfWidth, halfHeight, -halfThickness); + tess.addVertex(halfWidth, halfHeight, -halfThickness); + tess.draw(); + + // Top edge (y+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, 1, 0); + tess.addVertex(-halfWidth, halfHeight, halfThickness); + tess.addVertex(halfWidth, halfHeight, halfThickness); + tess.addVertex(halfWidth, halfHeight, -halfThickness); + tess.addVertex(-halfWidth, halfHeight, -halfThickness); + tess.draw(); + + // Bottom edge (y-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, -1, 0); + tess.addVertex(-halfWidth, -halfHeight, -halfThickness); + tess.addVertex(halfWidth, -halfHeight, -halfThickness); + tess.addVertex(halfWidth, -halfHeight, halfThickness); + tess.addVertex(-halfWidth, -halfHeight, halfThickness); + tess.draw(); + + // Right edge (x+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(1, 0, 0); + tess.addVertex(halfWidth, -halfHeight, halfThickness); + tess.addVertex(halfWidth, -halfHeight, -halfThickness); + tess.addVertex(halfWidth, halfHeight, -halfThickness); + tess.addVertex(halfWidth, halfHeight, halfThickness); + tess.draw(); + + // Left edge (x-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(-1, 0, 0); + tess.addVertex(-halfWidth, -halfHeight, -halfThickness); + tess.addVertex(-halfWidth, -halfHeight, halfThickness); + tess.addVertex(-halfWidth, halfHeight, halfThickness); + tess.addVertex(-halfWidth, halfHeight, -halfThickness); + tess.draw(); + } + + /** + * Render lightning arcs on the panel surface. + */ + private void renderAttachedLightning(EntityEnergyPanel panel, float size) { + AttachedLightningRenderer.LightningState state = getLightningState(panel); + float density = panel.getLightningDensity(); + float radius = size + panel.getLightningRadius(); + int outerColor = panel.getOuterColor(); + int innerColor = panel.getInnerColor(); + int fadeTime = panel.getLightningFadeTime(); + + state.update(density, radius, outerColor, innerColor, fadeTime); + state.render(); + } + + private AttachedLightningRenderer.LightningState getLightningState(EntityEnergyPanel panel) { + if (panel.lightningState == null) { + panel.lightningState = new AttachedLightningRenderer.LightningState(); + } + return (AttachedLightningRenderer.LightningState) panel.lightningState; + } +} diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java new file mode 100644 index 000000000..ad48254ab --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java @@ -0,0 +1,163 @@ +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.EntityEnergySlicer; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.Entity; +import org.lwjgl.opengl.GL11; + +/** + * Renders the Energy Slicer entity as a thin wide blade. + * Oriented perpendicular to travel direction. + * Inner solid core with outer translucent glow. + */ +@SideOnly(Side.CLIENT) +public class RenderEnergySlicer extends RenderAbilityProjectile { + + @Override + public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + EntityEnergySlicer slicer = (EntityEnergySlicer) entity; + + float size = slicer.getInterpolatedSize(partialTicks); + float width = slicer.getSliceWidth(); + float thickness = slicer.getSliceThickness(); + + // Scale with interpolated size for charging grow effect + float sizeRatio = size > 0.01f ? size / Math.max(0.01f, slicer.getSliceWidth()) : 0.01f; + if (sizeRatio > 1.0f) sizeRatio = 1.0f; + + setupRenderState(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + // Lightning before rotation + if (slicer.hasLightningEffect()) { + renderAttachedLightning(slicer, width * 0.5f * sizeRatio); + } + + // Orient perpendicular to travel direction + float travelYaw = slicer.getTravelYaw(); + float travelPitch = slicer.getTravelPitch(); + GL11.glRotatef(travelYaw, 0.0f, 1.0f, 0.0f); + GL11.glRotatef(travelPitch, 1.0f, 0.0f, 0.0f); + + GL11.glPushMatrix(); + GL11.glScalef(sizeRatio, sizeRatio, sizeRatio); + + float innerScale = 0.7f; + + // Render outer blade (translucent glow) + if (slicer.isOuterColorEnabled()) { + float outerScale = 1.0f + slicer.getOuterColorWidth() * 0.2f; + GL11.glDepthMask(false); + renderBlade(slicer.getOuterColor(), slicer.getOuterColorAlpha(), + width * 0.5f * outerScale, thickness * 2.0f, 0.05f); + GL11.glDepthMask(true); + } + + // Render inner blade (solid core) + renderBlade(slicer.getInnerColor(), 1.0f, + width * 0.5f * innerScale, thickness, 0.03f); + + GL11.glPopMatrix(); + GL11.glPopMatrix(); + + restoreRenderState(); + } + + /** + * Render a thin wide blade shape. + * The blade extends along X (width), thin on Y (thickness), and very thin on Z (depth). + */ + private void renderBlade(int color, float alpha, float halfWidth, float halfHeight, float halfDepth) { + float[] rgb = extractRGB(color); + float r = rgb[0], g = rgb[1], b = rgb[2]; + + Tessellator tess = Tessellator.instance; + + // Front face (z+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, 0, 1); + tess.addVertex(-halfWidth, -halfHeight, halfDepth); + tess.addVertex(halfWidth, -halfHeight, halfDepth); + tess.addVertex(halfWidth, halfHeight, halfDepth); + tess.addVertex(-halfWidth, halfHeight, halfDepth); + tess.draw(); + + // Back face (z-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha); + tess.setNormal(0, 0, -1); + tess.addVertex(halfWidth, -halfHeight, -halfDepth); + tess.addVertex(-halfWidth, -halfHeight, -halfDepth); + tess.addVertex(-halfWidth, halfHeight, -halfDepth); + tess.addVertex(halfWidth, halfHeight, -halfDepth); + tess.draw(); + + // Top edge (y+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha * 0.8f); + tess.setNormal(0, 1, 0); + tess.addVertex(-halfWidth, halfHeight, halfDepth); + tess.addVertex(halfWidth, halfHeight, halfDepth); + tess.addVertex(halfWidth, halfHeight, -halfDepth); + tess.addVertex(-halfWidth, halfHeight, -halfDepth); + tess.draw(); + + // Bottom edge (y-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha * 0.8f); + tess.setNormal(0, -1, 0); + tess.addVertex(-halfWidth, -halfHeight, -halfDepth); + tess.addVertex(halfWidth, -halfHeight, -halfDepth); + tess.addVertex(halfWidth, -halfHeight, halfDepth); + tess.addVertex(-halfWidth, -halfHeight, halfDepth); + tess.draw(); + + // Right edge (x+) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha * 0.6f); + tess.setNormal(1, 0, 0); + tess.addVertex(halfWidth, -halfHeight, halfDepth); + tess.addVertex(halfWidth, -halfHeight, -halfDepth); + tess.addVertex(halfWidth, halfHeight, -halfDepth); + tess.addVertex(halfWidth, halfHeight, halfDepth); + tess.draw(); + + // Left edge (x-) + tess.startDrawingQuads(); + tess.setColorRGBA_F(r, g, b, alpha * 0.6f); + tess.setNormal(-1, 0, 0); + tess.addVertex(-halfWidth, -halfHeight, -halfDepth); + tess.addVertex(-halfWidth, -halfHeight, halfDepth); + tess.addVertex(-halfWidth, halfHeight, halfDepth); + tess.addVertex(-halfWidth, halfHeight, -halfDepth); + tess.draw(); + } + + /** + * Render lightning arcs on the slicer. + */ + private void renderAttachedLightning(EntityEnergySlicer slicer, float size) { + AttachedLightningRenderer.LightningState state = getLightningState(slicer); + float density = slicer.getLightningDensity(); + float radius = size + slicer.getLightningRadius(); + int outerColor = slicer.getOuterColor(); + int innerColor = slicer.getInnerColor(); + int fadeTime = slicer.getLightningFadeTime(); + + state.update(density, radius, outerColor, innerColor, fadeTime); + state.render(); + } + + private AttachedLightningRenderer.LightningState getLightningState(EntityEnergySlicer slicer) { + if (slicer.lightningState == null) { + slicer.lightningState = new AttachedLightningRenderer.LightningState(); + } + return (AttachedLightningRenderer.LightningState) slicer.lightningState; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index c492cf50b..7d6cca477 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -99,6 +99,11 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:hazard", AbilityHazard::new); registerType("cnpc:trap", AbilityTrap::new); + + registerType("cnpc:energy_dome", AbilityEnergyDome::new); + registerType("cnpc:energy_wall", AbilityEnergyWall::new); + registerType("cnpc:energy_shield", AbilityEnergyShield::new); + registerType("cnpc:energy_slicer", AbilityEnergySlicer::new); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java new file mode 100644 index 000000000..be1c3254a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -0,0 +1,115 @@ +package kamkeel.npcs.controllers.data.ability.data; + +import net.minecraft.nbt.NBTTagCompound; + +import java.util.HashMap; +import java.util.Map; + +/** + * Groups barrier properties shared by energy barrier abilities (Dome, Wall, Shield). + * Handles health/durability, duration, and per-projectile-type damage multipliers. + */ +public class EnergyBarrierData { + public float maxHealth = 100.0f; + public boolean useHealth = true; + public int durationTicks = 200; + public boolean useDuration = true; + public float defaultMultiplier = 1.0f; + + /** + * Per-projectile-type damage multipliers. + * Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam"). + * Value is the multiplier applied to damage dealt to this barrier. + */ + private Map damageMultipliers = new HashMap<>(); + + public EnergyBarrierData() {} + + public EnergyBarrierData(float maxHealth, boolean useHealth, int durationTicks, boolean useDuration) { + this.maxHealth = maxHealth; + this.useHealth = useHealth; + this.durationTicks = durationTicks; + this.useDuration = useDuration; + } + + // ==================== MULTIPLIER METHODS ==================== + + public float getMultiplier(String abilityTypeId) { + Float mult = damageMultipliers.get(abilityTypeId); + return mult != null ? mult : defaultMultiplier; + } + + public void setMultiplier(String abilityTypeId, float multiplier) { + damageMultipliers.put(abilityTypeId, multiplier); + } + + public void removeMultiplier(String abilityTypeId) { + damageMultipliers.remove(abilityTypeId); + } + + public Map getDamageMultipliers() { + return damageMultipliers; + } + + public void setDamageMultipliers(Map multipliers) { + this.damageMultipliers = multipliers != null ? multipliers : new HashMap<>(); + } + + // ==================== GETTERS & SETTERS ==================== + + public float getMaxHealth() { return maxHealth; } + public void setMaxHealth(float maxHealth) { this.maxHealth = Math.max(0, maxHealth); } + + public boolean isUseHealth() { return useHealth; } + public void setUseHealth(boolean useHealth) { this.useHealth = useHealth; } + + public int getDurationTicks() { return durationTicks; } + public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); } + + public boolean isUseDuration() { return useDuration; } + public void setUseDuration(boolean useDuration) { this.useDuration = useDuration; } + + public float getDefaultMultiplier() { return defaultMultiplier; } + public void setDefaultMultiplier(float defaultMultiplier) { this.defaultMultiplier = defaultMultiplier; } + + // ==================== NBT ==================== + + public void writeNBT(NBTTagCompound nbt) { + nbt.setFloat("barrierMaxHealth", maxHealth); + nbt.setBoolean("barrierUseHealth", useHealth); + nbt.setInteger("barrierDuration", durationTicks); + nbt.setBoolean("barrierUseDuration", useDuration); + nbt.setFloat("barrierDefaultMult", defaultMultiplier); + + NBTTagCompound multNbt = new NBTTagCompound(); + for (Map.Entry entry : damageMultipliers.entrySet()) { + multNbt.setFloat(entry.getKey(), entry.getValue()); + } + nbt.setTag("barrierMultipliers", multNbt); + } + + public void readNBT(NBTTagCompound nbt) { + maxHealth = nbt.getFloat("barrierMaxHealth"); + useHealth = nbt.getBoolean("barrierUseHealth"); + durationTicks = nbt.getInteger("barrierDuration"); + useDuration = nbt.getBoolean("barrierUseDuration"); + defaultMultiplier = nbt.hasKey("barrierDefaultMult") ? nbt.getFloat("barrierDefaultMult") : 1.0f; + + damageMultipliers.clear(); + if (nbt.hasKey("barrierMultipliers")) { + NBTTagCompound multNbt = nbt.getCompoundTag("barrierMultipliers"); + @SuppressWarnings("unchecked") + java.util.Set keys = multNbt.func_150296_c(); + for (String key : keys) { + damageMultipliers.put(key, multNbt.getFloat(key)); + } + } + } + + public EnergyBarrierData copy() { + EnergyBarrierData copy = new EnergyBarrierData(maxHealth, useHealth, durationTicks, useDuration); + copy.defaultMultiplier = defaultMultiplier; + copy.damageMultipliers = new HashMap<>(damageMultipliers); + return copy; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java new file mode 100644 index 000000000..87f68de1d --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java @@ -0,0 +1,81 @@ +package kamkeel.npcs.controllers.data.ability.data; + +import net.minecraft.nbt.NBTTagCompound; + +/** + * Groups panel-specific properties for energy wall/shield abilities. + * Handles dimensions, positioning, and launch behavior. + */ +public class EnergyPanelData { + public float panelWidth = 3.0f; + public float panelHeight = 3.0f; + public float heightOffset = 0.0f; + + // Launch properties (Wall only) + public boolean launching = false; + public float launchSpeed = 0.5f; + public float launchDamage = 8.0f; + public float launchKnockback = 2.0f; + + public EnergyPanelData() {} + + public EnergyPanelData(float panelWidth, float panelHeight, float heightOffset) { + this.panelWidth = panelWidth; + this.panelHeight = panelHeight; + this.heightOffset = heightOffset; + } + + // ==================== GETTERS & SETTERS ==================== + + public float getPanelWidth() { return panelWidth; } + public void setPanelWidth(float panelWidth) { this.panelWidth = Math.max(0.5f, panelWidth); } + + public float getPanelHeight() { return panelHeight; } + public void setPanelHeight(float panelHeight) { this.panelHeight = Math.max(0.5f, panelHeight); } + + public float getHeightOffset() { return heightOffset; } + public void setHeightOffset(float heightOffset) { this.heightOffset = heightOffset; } + + public boolean isLaunching() { return launching; } + public void setLaunching(boolean launching) { this.launching = launching; } + + public float getLaunchSpeed() { return launchSpeed; } + public void setLaunchSpeed(float launchSpeed) { this.launchSpeed = Math.max(0.1f, launchSpeed); } + + public float getLaunchDamage() { return launchDamage; } + public void setLaunchDamage(float launchDamage) { this.launchDamage = Math.max(0, launchDamage); } + + public float getLaunchKnockback() { return launchKnockback; } + public void setLaunchKnockback(float launchKnockback) { this.launchKnockback = Math.max(0, launchKnockback); } + + // ==================== NBT ==================== + + public void writeNBT(NBTTagCompound nbt) { + nbt.setFloat("panelWidth", panelWidth); + nbt.setFloat("panelHeight", panelHeight); + nbt.setFloat("panelHeightOffset", heightOffset); + nbt.setBoolean("panelLaunching", launching); + nbt.setFloat("panelLaunchSpeed", launchSpeed); + nbt.setFloat("panelLaunchDamage", launchDamage); + nbt.setFloat("panelLaunchKnockback", launchKnockback); + } + + public void readNBT(NBTTagCompound nbt) { + panelWidth = nbt.hasKey("panelWidth") ? nbt.getFloat("panelWidth") : 3.0f; + panelHeight = nbt.hasKey("panelHeight") ? nbt.getFloat("panelHeight") : 3.0f; + heightOffset = nbt.getFloat("panelHeightOffset"); + launching = nbt.getBoolean("panelLaunching"); + launchSpeed = nbt.hasKey("panelLaunchSpeed") ? nbt.getFloat("panelLaunchSpeed") : 0.5f; + launchDamage = nbt.hasKey("panelLaunchDamage") ? nbt.getFloat("panelLaunchDamage") : 8.0f; + launchKnockback = nbt.hasKey("panelLaunchKnockback") ? nbt.getFloat("panelLaunchKnockback") : 2.0f; + } + + public EnergyPanelData copy() { + EnergyPanelData copy = new EnergyPanelData(panelWidth, panelHeight, heightOffset); + copy.launching = launching; + copy.launchSpeed = launchSpeed; + copy.launchDamage = launchDamage; + copy.launchKnockback = launchKnockback; + return copy; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java new file mode 100644 index 000000000..0fdac9328 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java @@ -0,0 +1,122 @@ +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.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyDome; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.Arrays; +import java.util.List; + +/** + * Energy Dome ability: Spawns a spherical barrier centered on the caster. + * Blocks incoming energy projectiles with configurable damage multipliers. + * Duration and/or HP based. + */ +public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { + + private float domeRadius = 5.0f; + + public AbilityEnergyDome() { + super( + new EnergyDisplayData(0x44CCFF, 0x2288FF, true, 0.3f, 0.35f, 0.0f), + new EnergyBarrierData(100.0f, true, 200, true) + ); + this.typeId = "ability.cnpc.energy_dome"; + this.name = "Energy Dome"; + this.targetingMode = TargetingMode.SELF; + this.maxRange = 0; + this.minRange = 0; + this.cooldownTicks = 100; + this.windUpTicks = 30; + this.lockMovement = LockMovementType.WINDUP; + this.telegraphType = TelegraphType.RING; + this.showTelegraph = true; + this.windUpAnimationName = "Ability_EnergyDome_Windup"; + this.activeAnimationName = "Ability_EnergyDome_Active"; + } + + // ==================== ABSTRACT IMPLEMENTATIONS ==================== + + @Override + protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + EntityEnergyDome dome = new EntityEnergyDome( + caster.worldObj, caster, + caster.posX, caster.posY, caster.posZ, + domeRadius, displayData.copy(), lightningData.copy(), barrierData.copy() + ); + dome.setSourceAbility(this); + return dome; + } + + @Override + public boolean isTargetingModeLocked() { + return true; + } + + @Override + public TargetingMode[] getAllowedTargetingModes() { + return new TargetingMode[]{TargetingMode.SELF}; + } + + @Override + public float getTelegraphRadius() { + return domeRadius; + } + + // ==================== VARIANTS ==================== + + @Override + public List getVariants() { + return Arrays.asList( + new AbilityVariant("ability.variant.small", a -> { + AbilityEnergyDome dome = (AbilityEnergyDome) a; + a.setName("Small Energy Dome"); + dome.setDomeRadius(3.0f); + dome.setBarrierMaxHealth(60.0f); + }), + new AbilityVariant("ability.variant.large", a -> { + AbilityEnergyDome dome = (AbilityEnergyDome) a; + a.setName("Large Energy Dome"); + dome.setDomeRadius(8.0f); + dome.setBarrierMaxHealth(200.0f); + dome.setBarrierDuration(300); + }) + ); + } + + // ==================== TYPE-SPECIFIC NBT ==================== + + @Override + protected void writeBarrierTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("domeRadius", domeRadius); + } + + @Override + protected void readBarrierTypeNBT(NBTTagCompound nbt) { + this.domeRadius = nbt.hasKey("domeRadius") ? nbt.getFloat("domeRadius") : 5.0f; + } + + // ==================== GETTERS & SETTERS ==================== + + public float getDomeRadius() { return domeRadius; } + public void setDomeRadius(float radius) { this.domeRadius = Math.max(1.0f, radius); } + + // ==================== TYPE-SPECIFIC GUI ==================== + + @SideOnly(Side.CLIENT) + @Override + protected void addBarrierTypeDefinitions(List defs) { + defs.add(FieldDef.floatField("ability.domeRadius", this::getDomeRadius, this::setDomeRadius) + .range(1.0f, 30.0f)); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java new file mode 100644 index 000000000..9bcff686e --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java @@ -0,0 +1,144 @@ +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.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyPanel; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.Arrays; +import java.util.List; + +/** + * Energy Shield ability: Spawns a flat energy panel held in front of the caster. + * The shield follows the caster's position and rotates with their look direction. + * Cannot be placed — it's always held. + */ +public class AbilityEnergyShield extends AbstractEnergyBarrierAbility { + + private float shieldWidth = 2.5f; + private float shieldHeight = 2.5f; + + public AbilityEnergyShield() { + super( + new EnergyDisplayData(0xFFDD44, 0xFFAA00, true, 0.3f, 0.4f, 0.0f), + new EnergyBarrierData(60.0f, true, 120, true) + ); + this.typeId = "ability.cnpc.energy_shield"; + this.name = "Energy Shield"; + this.targetingMode = TargetingMode.SELF; + this.maxRange = 0; + this.minRange = 0; + this.cooldownTicks = 60; + this.windUpTicks = 10; + this.lockMovement = LockMovementType.NO; + this.telegraphType = TelegraphType.NONE; + this.showTelegraph = false; + this.windUpAnimationName = "Ability_EnergyShield_Windup"; + this.activeAnimationName = "Ability_EnergyShield_Active"; + this.allowedBy = UserType.BOTH; + } + + // ==================== ABSTRACT IMPLEMENTATIONS ==================== + + @Override + protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + EnergyPanelData panelData = new EnergyPanelData(shieldWidth, shieldHeight, 0.0f); + + float frontDist = 1.5f; + float yawRad = (float) Math.toRadians(caster.rotationYaw); + double spawnX = caster.posX + (-Math.sin(yawRad) * frontDist); + double spawnY = caster.posY + (caster.height * 0.5f); + double spawnZ = caster.posZ + (Math.cos(yawRad) * frontDist); + + EntityEnergyPanel panel = new EntityEnergyPanel( + caster.worldObj, caster, + spawnX, spawnY, spawnZ, caster.rotationYaw, + EntityEnergyPanel.PanelMode.HELD, + displayData.copy(), lightningData.copy(), barrierData.copy(), panelData + ); + panel.setSourceAbility(this); + return panel; + } + + @Override + public boolean isTargetingModeLocked() { + return true; + } + + @Override + public TargetingMode[] getAllowedTargetingModes() { + return new TargetingMode[]{TargetingMode.SELF}; + } + + @Override + public float getTelegraphRadius() { + return 0; // No telegraph for shield + } + + // ==================== VARIANTS ==================== + + @Override + public List getVariants() { + return Arrays.asList( + new AbilityVariant("ability.variant.small", a -> { + AbilityEnergyShield shield = (AbilityEnergyShield) a; + a.setName("Small Energy Shield"); + shield.setShieldWidth(1.5f); + shield.setShieldHeight(1.5f); + shield.setBarrierMaxHealth(40.0f); + }), + new AbilityVariant("ability.variant.large", a -> { + AbilityEnergyShield shield = (AbilityEnergyShield) a; + a.setName("Large Energy Shield"); + shield.setShieldWidth(4.0f); + shield.setShieldHeight(4.0f); + shield.setBarrierMaxHealth(120.0f); + shield.setBarrierDuration(200); + }) + ); + } + + // ==================== TYPE-SPECIFIC NBT ==================== + + @Override + protected void writeBarrierTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("shieldWidth", shieldWidth); + nbt.setFloat("shieldHeight", shieldHeight); + } + + @Override + protected void readBarrierTypeNBT(NBTTagCompound nbt) { + this.shieldWidth = nbt.hasKey("shieldWidth") ? nbt.getFloat("shieldWidth") : 2.5f; + this.shieldHeight = nbt.hasKey("shieldHeight") ? nbt.getFloat("shieldHeight") : 2.5f; + } + + // ==================== GETTERS & SETTERS ==================== + + public float getShieldWidth() { return shieldWidth; } + public void setShieldWidth(float width) { this.shieldWidth = Math.max(0.5f, width); } + + public float getShieldHeight() { return shieldHeight; } + public void setShieldHeight(float height) { this.shieldHeight = Math.max(0.5f, height); } + + // ==================== TYPE-SPECIFIC GUI ==================== + + @SideOnly(Side.CLIENT) + @Override + protected void addBarrierTypeDefinitions(List defs) { + defs.add(FieldDef.row( + FieldDef.floatField("gui.width", this::getShieldWidth, this::setShieldWidth).range(0.5f, 10.0f), + FieldDef.floatField("gui.height", this::getShieldHeight, this::setShieldHeight).range(0.5f, 10.0f) + )); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java new file mode 100644 index 000000000..195311049 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java @@ -0,0 +1,176 @@ +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.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergySlicer; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.Vec3; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.Arrays; +import java.util.List; + +/** + * Energy Slicer ability: Fires a thin, wide blade projectile in a straight line. + * JJK Cleave/Dismantle inspired. Piercing by default. + * Extends AbstractEnergyProjectileAbility to leverage the existing projectile infrastructure. + */ +public class AbilityEnergySlicer extends AbstractEnergyProjectileAbility { + + private float sliceWidth = 3.0f; + private float sliceThickness = 0.15f; + private boolean piercing = true; + + public AbilityEnergySlicer() { + super( + new EnergyDisplayData(0xFF4444, 0xFF0000, true, 0.2f, 0.6f, 0.0f), + new EnergyCombatData(12.0f, 0.5f, 0.0f, false, 3.0f, 0.5f), + new EnergyHomingData(0.8f, false, 0.0f, 0.0f), + new EnergyLifespanData(40.0f, 100) + ); + this.typeId = "ability.cnpc.energy_slicer"; + this.name = "Energy Slicer"; + this.targetingMode = TargetingMode.AGGRO_TARGET; + this.maxRange = 30.0f; + this.minRange = 3.0f; + this.cooldownTicks = 40; + this.windUpTicks = 15; + this.lockMovement = LockMovementType.WINDUP; + this.telegraphType = TelegraphType.LINE; + this.showTelegraph = true; + this.windUpAnimationName = "Ability_EnergySlicer_Windup"; + this.activeAnimationName = "Ability_EnergySlicer_Active"; + } + + // ==================== ABSTRACT IMPLEMENTATIONS ==================== + + @Override + protected EntityEnergySlicer createEntity(EntityLivingBase caster, EntityLivingBase target, + Vec3 spawnPos, EnergyDisplayData resolved, int index) { + return new EntityEnergySlicer( + caster.worldObj, caster, target, + spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, + sliceWidth, sliceThickness, + resolved, combatData, homingData, lightningData, lifespanData, trajectoryData, + piercing + ); + } + + @Override + protected void fireEntity(EntityEnergySlicer slicer, EntityLivingBase target) { + if (isPreview()) { + slicer.startPreviewFiring(); + } else { + slicer.startMoving(target); + } + } + + @Override + protected void setupEntityCharging(EntityEnergySlicer slicer, ProjectileData projData, int index) { + slicer.setupSlicerCharging(projData.anchor, windUpTicks, sliceWidth, sliceThickness); + } + + @Override + protected void setupEntityPreview(EntityEnergySlicer slicer, EntityLivingBase caster, + EnergyDisplayData resolved, ProjectileData projData, int index) { + slicer.setupPreview(caster, sliceWidth, sliceThickness, resolved, lightningData, projData.anchor, windUpTicks); + } + + @Override + protected EntityEnergySlicer[] createEntityArray(int size) { + return new EntityEnergySlicer[size]; + } + + @Override + protected float getProjectileTelegraphRadius() { + return sliceWidth * 0.75f; + } + + // ==================== VARIANTS ==================== + + @Override + public List getVariants() { + return Arrays.asList( + new AbilityVariant("ability.variant.single", a -> { + a.setName("Energy Slicer"); + }), + new AbilityVariant("ability.variant.dual", a -> { + AbilityEnergySlicer slicer = (AbilityEnergySlicer) a; + a.setName("Double Slicer"); + slicer.setProjectileCount(2); + slicer.setFireDelay(5); + a.setWindUpAnimationName("Ability_EnergySlicerDual_Windup"); + a.setActiveAnimationName("Ability_EnergySlicerDual_Active"); + }) + ); + } + + // ==================== TYPE-SPECIFIC NBT ==================== + + @Override + protected void writeTypeSpecificNBT(NBTTagCompound nbt) { + nbt.setFloat("sliceWidth", sliceWidth); + nbt.setFloat("sliceThickness", sliceThickness); + nbt.setBoolean("piercing", piercing); + } + + @Override + protected void readTypeSpecificNBT(NBTTagCompound nbt) { + this.sliceWidth = nbt.hasKey("sliceWidth") ? nbt.getFloat("sliceWidth") : 3.0f; + this.sliceThickness = nbt.hasKey("sliceThickness") ? nbt.getFloat("sliceThickness") : 0.15f; + this.piercing = !nbt.hasKey("piercing") || nbt.getBoolean("piercing"); + } + + // ==================== TYPE-SPECIFIC GETTERS ==================== + + public float getSlicerSpeed() { return homingData.speed; } + public void setSlicerSpeed(float speed) { homingData.speed = speed; } + + public float getSliceWidth() { return sliceWidth; } + public void setSliceWidth(float width) { this.sliceWidth = Math.max(0.5f, width); } + + public float getSliceThickness() { return sliceThickness; } + public void setSliceThickness(float thickness) { this.sliceThickness = Math.max(0.05f, thickness); } + + public boolean isPiercing() { return piercing; } + public void setPiercing(boolean piercing) { this.piercing = piercing; } + + // ==================== TYPE-SPECIFIC GUI ==================== + + @SideOnly(Side.CLIENT) + @Override + protected void addTypeDefinitions(List defs) { + defs.add(FieldDef.row( + FieldDef.intField("ability.projectileCount", this::getProjectileCount, this::setProjectileCount).range(1, MAX_PROJECTILES), + FieldDef.intField("ability.fireDelay", this::getFireDelay, this::setFireDelay) + .range(0, 200).visibleWhen(() -> projectileCount > 1) + )); + defs.add(FieldDef.row( + FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage), + FieldDef.floatField("stats.speed", this::getSlicerSpeed, this::setSlicerSpeed) + )); + defs.add(FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)); + defs.add(FieldDef.section("ability.section.slicer")); + defs.add(FieldDef.row( + FieldDef.floatField("gui.width", this::getSliceWidth, this::setSliceWidth).range(0.5f, 20.0f), + FieldDef.floatField("gui.thickness", this::getSliceThickness, this::setSliceThickness).range(0.05f, 2.0f) + )); + defs.add(FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing) + .hover("ability.hover.piercing")); + defs.add(FieldDef.row( + FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance), + FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime) + )); + 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).visibleWhen(this::isExplosive)); + defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java new file mode 100644 index 000000000..9007c1dba --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java @@ -0,0 +1,179 @@ +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.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyPanel; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.Arrays; +import java.util.List; + +/** + * Energy Wall ability: Spawns a placeable flat energy barrier. + * Can float above ground (configurable height offset). + * Optional launching mode fires the wall forward with damage/knockback. + */ +public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { + + private final EnergyPanelData panelData; + + public AbilityEnergyWall() { + super( + new EnergyDisplayData(0x44FFCC, 0x22CCAA, true, 0.3f, 0.4f, 0.0f), + new EnergyBarrierData(80.0f, true, 160, true) + ); + this.panelData = new EnergyPanelData(4.0f, 3.0f, 0.0f); + this.typeId = "ability.cnpc.energy_wall"; + this.name = "Energy Wall"; + this.targetingMode = TargetingMode.AGGRO_TARGET; + this.maxRange = 15.0f; + this.minRange = 2.0f; + this.cooldownTicks = 80; + this.windUpTicks = 20; + this.lockMovement = LockMovementType.WINDUP; + this.telegraphType = TelegraphType.LINE; + this.showTelegraph = true; + this.windUpAnimationName = "Ability_EnergyWall_Windup"; + this.activeAnimationName = "Ability_EnergyWall_Active"; + } + + @Override + public boolean hasDamage() { + return panelData.launching; // Only has damage when launching + } + + // ==================== ABSTRACT IMPLEMENTATIONS ==================== + + @Override + protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + // Place wall between caster and target + double placeX, placeY, placeZ; + float yaw; + + if (target != null) { + double dx = target.posX - caster.posX; + double dz = target.posZ - caster.posZ; + double dist = Math.sqrt(dx * dx + dz * dz); + double placeDist = Math.min(dist * 0.5, 5.0); + + placeX = caster.posX + (dx / dist) * placeDist; + placeY = caster.posY; + placeZ = caster.posZ + (dz / dist) * placeDist; + yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI); + } else { + float yawRad = (float) Math.toRadians(caster.rotationYaw); + placeX = caster.posX + (-Math.sin(yawRad) * 3.0); + placeY = caster.posY; + placeZ = caster.posZ + (Math.cos(yawRad) * 3.0); + yaw = caster.rotationYaw; + } + + EntityEnergyPanel.PanelMode mode = panelData.launching + ? EntityEnergyPanel.PanelMode.LAUNCHED + : EntityEnergyPanel.PanelMode.PLACED; + + EntityEnergyPanel panel = new EntityEnergyPanel( + caster.worldObj, caster, + placeX, placeY, placeZ, yaw, mode, + displayData.copy(), lightningData.copy(), barrierData.copy(), panelData.copy() + ); + panel.setSourceAbility(this); + return panel; + } + + @Override + public float getTelegraphRadius() { + return Math.max(panelData.panelWidth, panelData.panelHeight) * 0.5f; + } + + // ==================== VARIANTS ==================== + + @Override + public List getVariants() { + return Arrays.asList( + new AbilityVariant("ability.variant.static", a -> { + a.setName("Static Wall"); + }), + new AbilityVariant("ability.variant.launched", a -> { + AbilityEnergyWall wall = (AbilityEnergyWall) a; + a.setName("Launched Wall"); + wall.panelData.launching = true; + wall.panelData.launchSpeed = 0.6f; + wall.panelData.launchDamage = 10.0f; + wall.panelData.launchKnockback = 3.0f; + }) + ); + } + + // ==================== TYPE-SPECIFIC NBT ==================== + + @Override + protected void writeBarrierTypeNBT(NBTTagCompound nbt) { + panelData.writeNBT(nbt); + } + + @Override + protected void readBarrierTypeNBT(NBTTagCompound nbt) { + panelData.readNBT(nbt); + } + + // ==================== GETTERS & SETTERS ==================== + + public EnergyPanelData getPanelData() { return panelData; } + + public float getPanelWidth() { return panelData.panelWidth; } + public void setPanelWidth(float width) { panelData.setPanelWidth(width); } + + public float getPanelHeight() { return panelData.panelHeight; } + public void setPanelHeight(float height) { panelData.setPanelHeight(height); } + + public float getHeightOffset() { return panelData.heightOffset; } + public void setHeightOffset(float offset) { panelData.heightOffset = offset; } + + public boolean isLaunching() { return panelData.launching; } + public void setLaunching(boolean launching) { panelData.launching = launching; } + + public float getLaunchSpeed() { return panelData.launchSpeed; } + public void setLaunchSpeed(float speed) { panelData.setLaunchSpeed(speed); } + + public float getLaunchDamage() { return panelData.launchDamage; } + public void setLaunchDamage(float damage) { panelData.setLaunchDamage(damage); } + + public float getLaunchKnockback() { return panelData.launchKnockback; } + public void setLaunchKnockback(float kb) { panelData.setLaunchKnockback(kb); } + + // ==================== TYPE-SPECIFIC GUI ==================== + + @SideOnly(Side.CLIENT) + @Override + protected void addBarrierTypeDefinitions(List defs) { + defs.add(FieldDef.section("ability.section.panel")); + defs.add(FieldDef.row( + FieldDef.floatField("gui.width", this::getPanelWidth, this::setPanelWidth).range(0.5f, 20.0f), + FieldDef.floatField("gui.height", this::getPanelHeight, this::setPanelHeight).range(0.5f, 20.0f) + )); + defs.add(FieldDef.floatField("ability.heightOffset", this::getHeightOffset, this::setHeightOffset) + .min(Float.NEGATIVE_INFINITY)); + defs.add(FieldDef.section("ability.section.launch")); + defs.add(FieldDef.boolField("gui.enabled", this::isLaunching, this::setLaunching) + .hover("ability.hover.launching")); + defs.add(FieldDef.row( + FieldDef.floatField("stats.speed", this::getLaunchSpeed, this::setLaunchSpeed) + .visibleWhen(this::isLaunching), + FieldDef.floatField("enchantment.damage", this::getLaunchDamage, this::setLaunchDamage) + .visibleWhen(this::isLaunching) + )); + defs.add(FieldDef.floatField("ability.knockback", this::getLaunchKnockback, this::setLaunchKnockback) + .visibleWhen(this::isLaunching)); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java new file mode 100644 index 000000000..f95e76d47 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java @@ -0,0 +1,224 @@ +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.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.DamageSource; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; +import java.util.Map; + +/** + * Abstract base for energy barrier abilities (Dome, Wall, Shield). + * Handles shared visual data, barrier configuration, and lifecycle management. + */ +public abstract class AbstractEnergyBarrierAbility extends Ability { + + protected EnergyDisplayData displayData; + protected EnergyLightningData lightningData; + protected final EnergyBarrierData barrierData; + + // Runtime entity tracking + protected transient Entity barrierEntity; + + protected AbstractEnergyBarrierAbility(EnergyDisplayData displayData, EnergyBarrierData barrierData) { + this.displayData = displayData; + this.barrierData = barrierData; + this.lightningData = new EnergyLightningData(); + } + + // ==================== ABSTRACT METHODS ==================== + + /** + * Create and spawn the barrier entity during execution. + */ + protected abstract Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target); + + /** + * Add type-specific GUI field definitions. + */ + @SideOnly(Side.CLIENT) + protected abstract void addBarrierTypeDefinitions(List defs); + + /** + * Write type-specific fields to NBT. + */ + protected abstract void writeBarrierTypeNBT(NBTTagCompound nbt); + + /** + * Read type-specific fields from NBT. + */ + protected abstract void readBarrierTypeNBT(NBTTagCompound nbt); + + // ==================== LIFECYCLE ==================== + + @Override + public boolean allowBurst() { + return false; + } + + @Override + public boolean hasDamage() { + return false; // Barriers are defensive + } + + @Override + public void onExecute(EntityLivingBase caster, EntityLivingBase target) { + barrierEntity = createBarrierEntity(caster, target); + if (barrierEntity != null) { + spawnAbilityEntity(barrierEntity); + } + } + + @Override + public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { + if (barrierEntity == null || barrierEntity.isDead) { + signalCompletion(); + return; + } + + // Duration tracked by entity itself, but check here too as a failsafe + if (barrierData.useDuration && tick >= barrierData.durationTicks) { + cleanup(); + signalCompletion(); + } + } + + @Override + public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + } + + @Override + public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { + cleanup(); + } + + @Override + public void cleanup() { + if (barrierEntity != null && !barrierEntity.isDead) { + barrierEntity.setDead(); + } + barrierEntity = null; + } + + // ==================== NBT ==================== + + @Override + public final void writeTypeNBT(NBTTagCompound nbt) { + writeBarrierTypeNBT(nbt); + displayData.writeNBT(nbt); + lightningData.writeNBT(nbt); + barrierData.writeNBT(nbt); + } + + @Override + public final void readTypeNBT(NBTTagCompound nbt) { + readBarrierTypeNBT(nbt); + displayData.readNBT(nbt); + lightningData.readNBT(nbt); + barrierData.readNBT(nbt); + } + + // ==================== API GETTERS & SETTERS ==================== + + // Display data + public int getInnerColor() { return displayData.innerColor; } + public void setInnerColor(int color) { displayData.innerColor = color; } + + public int getOuterColor() { return displayData.outerColor; } + public void setOuterColor(int color) { displayData.outerColor = color; } + + public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } + public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; } + + public float getOuterColorWidth() { return displayData.outerColorWidth; } + public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; } + + public float getOuterColorAlpha() { return displayData.outerColorAlpha; } + public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; } + + // Lightning data + public boolean hasLightningEffect() { return lightningData.lightningEffect; } + public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; } + + public float getLightningDensity() { return lightningData.lightningDensity; } + public void setLightningDensity(float density) { lightningData.lightningDensity = density; } + + public float getLightningRadius() { return lightningData.lightningRadius; } + public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; } + + public int getLightningFadeTime() { return lightningData.lightningFadeTime; } + public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; } + + // Barrier data + public float getBarrierMaxHealth() { return barrierData.maxHealth; } + public void setBarrierMaxHealth(float maxHealth) { barrierData.setMaxHealth(maxHealth); } + + public boolean isUseHealth() { return barrierData.useHealth; } + public void setUseHealth(boolean useHealth) { barrierData.useHealth = useHealth; } + + public int getBarrierDuration() { return barrierData.durationTicks; } + public void setBarrierDuration(int ticks) { barrierData.setDurationTicks(ticks); } + + public boolean isUseDuration() { return barrierData.useDuration; } + public void setUseDuration(boolean useDuration) { barrierData.useDuration = useDuration; } + + public float getDefaultMultiplier() { return barrierData.defaultMultiplier; } + public void setDefaultMultiplier(float mult) { barrierData.defaultMultiplier = mult; } + + public void setDamageMultiplier(String typeId, float mult) { barrierData.setMultiplier(typeId, mult); } + public float getDamageMultiplier(String typeId) { return barrierData.getMultiplier(typeId); } + + // ==================== GUI ==================== + + @SideOnly(Side.CLIENT) + @Override + public final void getAbilityDefinitions(List defs) { + // Type-specific fields first + addBarrierTypeDefinitions(defs); + + // Barrier section + defs.add(FieldDef.section("ability.section.barrier")); + defs.add(FieldDef.boolField("ability.useHealth", this::isUseHealth, this::setUseHealth)); + defs.add(FieldDef.floatField("ability.maxHealth", this::getBarrierMaxHealth, this::setBarrierMaxHealth) + .visibleWhen(this::isUseHealth)); + defs.add(FieldDef.boolField("ability.useDuration", this::isUseDuration, this::setUseDuration)); + defs.add(FieldDef.intField("ability.duration", this::getBarrierDuration, this::setBarrierDuration) + .range(1, 6000).visibleWhen(this::isUseDuration)); + defs.add(FieldDef.floatField("ability.defaultMultiplier", this::getDefaultMultiplier, this::setDefaultMultiplier)); + + // Visual tab + defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual")); + defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor) + .tab("ability.tab.visual")); + defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled) + .tab("ability.tab.visual")); + defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor) + .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled)); + defs.add(FieldDef.row( + FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth) + .visibleWhen(this::isOuterColorEnabled), + FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha) + .range(0, 1).visibleWhen(this::isOuterColorEnabled) + ).tab("ability.tab.visual")); + + defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual")); + defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect) + .tab("ability.tab.visual")); + defs.add(FieldDef.row( + FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity) + .visibleWhen(this::hasLightningEffect).range(0.01f, 100f), + FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius) + .range(0.1f, 100f).visibleWhen(this::hasLightningEffect) + ).tab("ability.tab.visual")); + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java index 99c66edaa..630bf9c84 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java @@ -270,6 +270,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA } } + // Check barrier collisions (server-side, non-preview, non-charging) + if (!previewMode && !worldObj.isRemote && !isCharging()) { + if (checkBarrierCollision()) { + return; // Projectile was absorbed by a barrier + } + } + // Subclass-specific update updateProjectile(); @@ -310,6 +317,52 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA */ protected abstract void updateProjectile(); + // ==================== BARRIER COLLISION ==================== + + /** + * Check for collision with energy barrier entities (Dome, Panel). + * Barriers only block incoming projectiles from enemies. + * @return true if projectile was absorbed (caller should stop processing) + */ + protected boolean checkBarrierCollision() { + // Search area for barriers - check a generous range + double searchRange = 10.0; + AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( + posX - searchRange, posY - searchRange, posZ - searchRange, + posX + searchRange, posY + searchRange, posZ + searchRange + ); + + // Check domes + @SuppressWarnings("unchecked") + List domes = worldObj.getEntitiesWithinAABB(EntityEnergyDome.class, searchBox); + for (EntityEnergyDome dome : domes) { + if (dome.isDead) continue; + if (dome.isIncomingProjectile(this)) { + if (dome.onProjectileHit(this, getDamage())) { + hasHit = true; + this.setDead(); + return true; + } + } + } + + // Check panels + @SuppressWarnings("unchecked") + List panels = worldObj.getEntitiesWithinAABB(EntityEnergyPanel.class, searchBox); + for (EntityEnergyPanel panel : panels) { + if (panel.isDead) continue; + if (panel.isIncomingProjectile(this)) { + if (panel.onProjectileHit(this, getDamage())) { + hasHit = true; + this.setDead(); + return true; + } + } + } + + return false; + } + // ==================== POSITION INTERPOLATION ==================== @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java new file mode 100644 index 000000000..851de3687 --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -0,0 +1,314 @@ +package kamkeel.npcs.entity; + +import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.world.World; +import noppes.npcs.entity.EntityNPCInterface; + +import java.util.List; + +/** + * Energy Dome entity - a spherical barrier that blocks incoming energy projectiles. + * Centered on the caster's position at time of casting. + * Only blocks incoming attacks (not outgoing from allies inside). + */ +public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnData { + + // ==================== VISUAL PROPERTIES ==================== + protected EnergyDisplayData displayData = new EnergyDisplayData(); + protected EnergyLightningData lightningData = new EnergyLightningData(); + + // ==================== BARRIER PROPERTIES ==================== + protected EnergyBarrierData barrierData = new EnergyBarrierData(); + protected float currentHealth; + protected float domeRadius = 5.0f; + + // ==================== TRACKING ==================== + protected int ownerEntityId = -1; + protected int ticksAlive = 0; + + // ==================== STATE ==================== + protected transient Ability sourceAbility = null; + + // ==================== DATA WATCHER INDICES ==================== + private static final int DW_HEALTH_PERCENT = 20; + private static final int DW_HIT_FLASH = 21; + + // ==================== CLIENT STATE ==================== + @SideOnly(Side.CLIENT) + public transient Object lightningState; + + public EntityEnergyDome(World world) { + super(world); + this.setSize(1.0f, 1.0f); // Hitbox doesn't really matter, we use radius checks + this.noClip = true; + this.isImmuneToFire = true; + this.ignoreFrustumCheck = true; + } + + public EntityEnergyDome(World world, EntityLivingBase owner, double x, double y, double z, + float domeRadius, EnergyDisplayData display, EnergyLightningData lightning, + EnergyBarrierData barrier) { + this(world); + this.setPosition(x, y, z); + this.ownerEntityId = owner.getEntityId(); + this.domeRadius = domeRadius; + this.displayData = display; + this.lightningData = lightning; + this.barrierData = barrier; + this.currentHealth = barrier.maxHealth; + } + + @Override + protected void entityInit() { + this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f); + this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0); + } + + @Override + public void onUpdate() { + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + + super.onUpdate(); + ticksAlive++; + + if (!worldObj.isRemote) { + // Check owner death + if (ownerEntityId >= 0 && ticksAlive > 5) { + Entity owner = worldObj.getEntityByID(ownerEntityId); + if (owner != null) { + if (owner.isDead) { this.setDead(); return; } + if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) { + this.setDead(); return; + } + } + } + + // Duration check + if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) { + this.setDead(); + return; + } + + // Reset hit flash + if (getHitFlash() > 0) { + setHitFlash((byte) (getHitFlash() - 1)); + } + } + } + + /** + * Apply damage to this dome from a projectile. + * Returns true if the dome absorbed the hit (projectile should be destroyed). + */ + public boolean onProjectileHit(EntityAbilityProjectile projectile, float baseDamage) { + if (!barrierData.useHealth) { + // Duration-only mode: block but don't take damage + triggerHitFlash(); + return true; + } + + // Get damage multiplier for this projectile type + String typeId = ""; + if (projectile.getSourceAbility() != null) { + typeId = projectile.getSourceAbility().getTypeId(); + } + float multiplier = barrierData.getMultiplier(typeId); + float damage = baseDamage * multiplier; + + currentHealth -= damage; + syncHealthPercent(); + triggerHitFlash(); + + if (currentHealth <= 0) { + this.setDead(); + } + + return true; + } + + /** + * Check if a projectile at the given position is entering this dome from outside. + * Only blocks incoming projectiles (dot product check). + */ + public boolean isIncomingProjectile(EntityAbilityProjectile projectile) { + // Don't block projectiles from the dome's owner + if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; + + // Don't block projectiles from same-faction NPCs + Entity owner = getOwnerEntity(); + Entity projOwner = projectile.getOwnerEntity(); + if (owner instanceof EntityNPCInterface && projOwner instanceof EntityNPCInterface) { + if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) projOwner).faction.id) { + return false; + } + } + + double dx = projectile.posX - this.posX; + double dy = projectile.posY - this.posY; + double dz = projectile.posZ - this.posZ; + double distSq = dx * dx + dy * dy + dz * dz; + + // Check if projectile is within dome radius + if (distSq > domeRadius * domeRadius) return false; + + // Check if projectile is moving inward (dot product of velocity and position relative to center) + double dot = dx * projectile.motionX + dy * projectile.motionY + dz * projectile.motionZ; + // Negative dot = moving toward center = incoming + return dot < 0; + } + + // ==================== HELPERS ==================== + + private void syncHealthPercent() { + float percent = barrierData.useHealth && barrierData.maxHealth > 0 + ? Math.max(0, currentHealth / barrierData.maxHealth) + : 1.0f; + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent); + } + } + + private void triggerHitFlash() { + if (!worldObj.isRemote) { + setHitFlash((byte) 4); + } + } + + private void setHitFlash(byte value) { + this.dataWatcher.updateObject(DW_HIT_FLASH, value); + } + + public byte getHitFlash() { + return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH); + } + + public float getHealthPercent() { + return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); + } + + public Entity getOwnerEntity() { + if (ownerEntityId == -1) return null; + return worldObj.getEntityByID(ownerEntityId); + } + + public int getOwnerEntityId() { return ownerEntityId; } + public float getDomeRadius() { return domeRadius; } + public EnergyDisplayData getDisplayData() { return displayData; } + public EnergyLightningData getLightningData() { return lightningData; } + public EnergyBarrierData getBarrierData() { return barrierData; } + public float getCurrentHealth() { return currentHealth; } + + public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } + public Ability getSourceAbility() { return sourceAbility; } + + // ==================== VISUAL GETTERS ==================== + + public int getInnerColor() { return displayData.innerColor; } + public int getOuterColor() { return displayData.outerColor; } + public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } + public float getOuterColorWidth() { return displayData.outerColorWidth; } + public float getOuterColorAlpha() { return displayData.outerColorAlpha; } + public boolean hasLightningEffect() { return lightningData.lightningEffect; } + public float getLightningDensity() { return lightningData.lightningDensity; } + public float getLightningRadius() { return lightningData.lightningRadius; } + public int getLightningFadeTime() { return lightningData.lightningFadeTime; } + + // ==================== BRIGHTNESS ==================== + + @Override + public float getBrightness(float partialTicks) { return 1.0f; } + + @Override + @SideOnly(Side.CLIENT) + public int getBrightnessForRender(float partialTicks) { return 0xF000F0; } + + @Override + public boolean shouldRenderInPass(int pass) { return pass == 1; } + + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + double d = domeRadius * 8.0D; + d *= 64.0D; + return distance < d * d; + } + + // ==================== COLLISION SETTINGS ==================== + + @Override + public boolean canBeCollidedWith() { return false; } + + @Override + public boolean canBePushed() { return false; } + + @Override + protected boolean canTriggerWalking() { return false; } + + @Override + public boolean isBurning() { return false; } + + @SideOnly(Side.CLIENT) + public float getShadowSize() { return 0.0f; } + + // ==================== NBT ==================== + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt) { + this.domeRadius = nbt.getFloat("DomeRadius"); + this.ownerEntityId = nbt.getInteger("OwnerId"); + this.ticksAlive = nbt.getInteger("TicksAlive"); + this.currentHealth = nbt.getFloat("CurrentHealth"); + displayData.readNBT(nbt); + lightningData.readNBT(nbt); + barrierData.readNBT(nbt); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt) { + nbt.setFloat("DomeRadius", domeRadius); + nbt.setInteger("OwnerId", ownerEntityId); + nbt.setInteger("TicksAlive", ticksAlive); + nbt.setFloat("CurrentHealth", currentHealth); + displayData.writeNBT(nbt); + lightningData.writeNBT(nbt); + barrierData.writeNBT(nbt); + } + + // ==================== SPAWN DATA ==================== + + @Override + public void writeSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = new NBTTagCompound(); + this.writeEntityToNBT(compound); + cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error writing energy dome spawn data", e); + } + } + + @Override + public void readSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); + if (compound != null) { + this.readEntityFromNBT(compound); + } + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error reading energy dome spawn data", e); + } + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java new file mode 100644 index 000000000..ebc75bb0c --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -0,0 +1,447 @@ +package kamkeel.npcs.entity; + +import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.DamageSource; +import net.minecraft.world.World; +import noppes.npcs.NpcDamageSource; +import noppes.npcs.entity.EntityNPCInterface; +import net.minecraft.entity.player.EntityPlayer; + +import java.util.List; + +/** + * Energy Panel entity - a flat rectangular barrier used by Wall and Shield abilities. + * Supports three modes: PLACED (stationary), HELD (tracks caster), LAUNCHED (moves forward). + */ +public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnData { + + public enum PanelMode { + PLACED, // Stationary wall + HELD, // Follows caster, rotates with caster look + LAUNCHED // Moves forward, deals damage/knockback on contact + } + + // ==================== VISUAL PROPERTIES ==================== + protected EnergyDisplayData displayData = new EnergyDisplayData(); + protected EnergyLightningData lightningData = new EnergyLightningData(); + + // ==================== BARRIER PROPERTIES ==================== + protected EnergyBarrierData barrierData = new EnergyBarrierData(); + protected EnergyPanelData panelData = new EnergyPanelData(); + protected float currentHealth; + + // ==================== PANEL STATE ==================== + protected PanelMode mode = PanelMode.PLACED; + protected float panelYaw = 0.0f; // Rotation of the panel face (degrees) + + // ==================== TRACKING ==================== + protected int ownerEntityId = -1; + protected int ticksAlive = 0; + + // ==================== STATE ==================== + protected transient Ability sourceAbility = null; + + // ==================== DATA WATCHER INDICES ==================== + private static final int DW_HEALTH_PERCENT = 20; + private static final int DW_HIT_FLASH = 21; + + // ==================== CLIENT STATE ==================== + @SideOnly(Side.CLIENT) + public transient Object lightningState; + + public EntityEnergyPanel(World world) { + super(world); + this.setSize(0.5f, 0.5f); + this.noClip = true; + this.isImmuneToFire = true; + this.ignoreFrustumCheck = true; + } + + public EntityEnergyPanel(World world, EntityLivingBase owner, double x, double y, double z, + float yaw, PanelMode mode, + EnergyDisplayData display, EnergyLightningData lightning, + EnergyBarrierData barrier, EnergyPanelData panel) { + this(world); + this.setPosition(x, y + panel.heightOffset, z); + this.ownerEntityId = owner.getEntityId(); + this.panelYaw = yaw; + this.mode = mode; + this.displayData = display; + this.lightningData = lightning; + this.barrierData = barrier; + this.panelData = panel; + this.currentHealth = barrier.maxHealth; + + // For launched panels, set initial velocity + if (mode == PanelMode.LAUNCHED) { + float yawRad = (float) Math.toRadians(yaw); + this.motionX = -Math.sin(yawRad) * panel.launchSpeed; + this.motionY = 0; + this.motionZ = Math.cos(yawRad) * panel.launchSpeed; + } + } + + @Override + protected void entityInit() { + this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f); + this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0); + } + + @Override + public void onUpdate() { + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + + super.onUpdate(); + ticksAlive++; + + if (!worldObj.isRemote) { + // Check owner death + if (ownerEntityId >= 0 && ticksAlive > 5) { + Entity owner = worldObj.getEntityByID(ownerEntityId); + if (owner != null) { + if (owner.isDead) { this.setDead(); return; } + if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) { + this.setDead(); return; + } + } + } + + // Duration check + if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) { + this.setDead(); + return; + } + + // Reset hit flash + if (getHitFlash() > 0) { + setHitFlash((byte) (getHitFlash() - 1)); + } + } + + // Mode-specific updates + switch (mode) { + case HELD: + updateHeld(); + break; + case LAUNCHED: + updateLaunched(); + break; + case PLACED: + default: + // Stationary, no update needed + break; + } + } + + private void updateHeld() { + Entity owner = getOwnerEntity(); + if (owner == null) return; + + // Follow owner position + float frontDist = 1.5f; + float yawRad = (float) Math.toRadians(owner.rotationYaw); + double newX = owner.posX + (-Math.sin(yawRad) * frontDist); + double newY = owner.posY + panelData.heightOffset + (owner.height * 0.5f); + double newZ = owner.posZ + (Math.cos(yawRad) * frontDist); + + this.setPosition(newX, newY, newZ); + this.panelYaw = owner.rotationYaw; + } + + private void updateLaunched() { + if (worldObj.isRemote) return; + + // Move forward + this.posX += motionX; + this.posY += motionY; + this.posZ += motionZ; + this.setPosition(posX, posY, posZ); + + // Check entity collision for damage + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( + posX - halfW, posY - halfH, posZ - halfW, + posX + halfW, posY + halfH, posZ + halfW + ); + + @SuppressWarnings("unchecked") + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); + Entity owner = getOwnerEntity(); + + for (EntityLivingBase target : entities) { + if (target == owner) continue; + if (owner instanceof EntityNPCInterface && target instanceof EntityNPCInterface) { + if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) target).faction.id) continue; + } + + // Apply damage + if (panelData.launchDamage > 0) { + if (owner instanceof EntityNPCInterface) { + target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); + } else if (owner instanceof EntityPlayer) { + target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); + } + } + + // Apply knockback + if (panelData.launchKnockback > 0) { + double dx = target.posX - posX; + double dz = target.posZ - posZ; + double len = Math.sqrt(dx * dx + dz * dz); + if (len > 0) { + target.addVelocity( + (dx / len) * panelData.launchKnockback * 0.5, + 0.1, + (dz / len) * panelData.launchKnockback * 0.5 + ); + target.velocityChanged = true; + } + } + } + + // Max distance check (30 blocks) + double distSq = (posX - prevPosX) * (posX - prevPosX) + (posZ - prevPosZ) * (posZ - prevPosZ); + if (ticksAlive > 200) { + this.setDead(); + } + } + + /** + * Apply damage to this panel from a projectile. + */ + public boolean onProjectileHit(EntityAbilityProjectile projectile, float baseDamage) { + if (!barrierData.useHealth) { + triggerHitFlash(); + return true; + } + + String typeId = ""; + if (projectile.getSourceAbility() != null) { + typeId = projectile.getSourceAbility().getTypeId(); + } + float multiplier = barrierData.getMultiplier(typeId); + float damage = baseDamage * multiplier; + + currentHealth -= damage; + syncHealthPercent(); + triggerHitFlash(); + + if (currentHealth <= 0) { + this.setDead(); + } + + return true; + } + + /** + * Check if a projectile hits this panel surface. + * Only blocks incoming projectiles (not from the panel's owner). + */ + public boolean isIncomingProjectile(EntityAbilityProjectile projectile) { + if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; + + // Faction check + Entity owner = getOwnerEntity(); + Entity projOwner = projectile.getOwnerEntity(); + if (owner instanceof EntityNPCInterface && projOwner instanceof EntityNPCInterface) { + if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) projOwner).faction.id) { + return false; + } + } + + // Check if projectile is within panel bounds + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float panelThickness = 0.5f; // Collision thickness + + // Transform projectile position to panel-local space + float yawRad = (float) Math.toRadians(panelYaw); + double relX = projectile.posX - this.posX; + double relY = projectile.posY - this.posY; + double relZ = projectile.posZ - this.posZ; + + // Rotate into panel space (panel faces along -sin(yaw), cos(yaw)) + double cos = Math.cos(yawRad); + double sin = Math.sin(yawRad); + double localForward = relX * (-sin) + relZ * cos; // Distance along panel normal + double localRight = relX * cos + relZ * sin; // Distance along panel width + + // Check bounds + if (Math.abs(localForward) > panelThickness) return false; + if (Math.abs(localRight) > halfW) return false; + if (Math.abs(relY) > halfH) return false; + + // Check incoming direction (dot product with panel normal) + double normalX = -Math.sin(yawRad); + double normalZ = Math.cos(yawRad); + double dot = projectile.motionX * normalX + projectile.motionZ * normalZ; + + // If the projectile is on the owner's side, it must be moving toward the panel + // (negative dot means moving into the panel from the front side) + return true; // If within bounds, it's a hit regardless of direction + } + + // ==================== HELPERS ==================== + + private void syncHealthPercent() { + float percent = barrierData.useHealth && barrierData.maxHealth > 0 + ? Math.max(0, currentHealth / barrierData.maxHealth) + : 1.0f; + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent); + } + } + + private void triggerHitFlash() { + if (!worldObj.isRemote) { + setHitFlash((byte) 4); + } + } + + private void setHitFlash(byte value) { + this.dataWatcher.updateObject(DW_HIT_FLASH, value); + } + + public byte getHitFlash() { + return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH); + } + + public float getHealthPercent() { + return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); + } + + public Entity getOwnerEntity() { + if (ownerEntityId == -1) return null; + return worldObj.getEntityByID(ownerEntityId); + } + + // ==================== GETTERS ==================== + + public int getOwnerEntityId() { return ownerEntityId; } + public PanelMode getMode() { return mode; } + public float getPanelYaw() { return panelYaw; } + public EnergyDisplayData getDisplayData() { return displayData; } + public EnergyLightningData getLightningData() { return lightningData; } + public EnergyBarrierData getBarrierData() { return barrierData; } + public EnergyPanelData getPanelData() { return panelData; } + public float getCurrentHealth() { return currentHealth; } + + public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } + public Ability getSourceAbility() { return sourceAbility; } + + // Visual getters + public int getInnerColor() { return displayData.innerColor; } + public int getOuterColor() { return displayData.outerColor; } + public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } + public float getOuterColorWidth() { return displayData.outerColorWidth; } + public float getOuterColorAlpha() { return displayData.outerColorAlpha; } + public boolean hasLightningEffect() { return lightningData.lightningEffect; } + public float getLightningDensity() { return lightningData.lightningDensity; } + public float getLightningRadius() { return lightningData.lightningRadius; } + public int getLightningFadeTime() { return lightningData.lightningFadeTime; } + + // ==================== BRIGHTNESS ==================== + + @Override + public float getBrightness(float partialTicks) { return 1.0f; } + + @Override + @SideOnly(Side.CLIENT) + public int getBrightnessForRender(float partialTicks) { return 0xF000F0; } + + @Override + public boolean shouldRenderInPass(int pass) { return pass == 1; } + + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + double d = Math.max(panelData.panelWidth, panelData.panelHeight) * 8.0D; + d *= 64.0D; + return distance < d * d; + } + + // ==================== COLLISION SETTINGS ==================== + + @Override + public boolean canBeCollidedWith() { return false; } + + @Override + public boolean canBePushed() { return false; } + + @Override + protected boolean canTriggerWalking() { return false; } + + @Override + public boolean isBurning() { return false; } + + @SideOnly(Side.CLIENT) + public float getShadowSize() { return 0.0f; } + + // ==================== NBT ==================== + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt) { + this.ownerEntityId = nbt.getInteger("OwnerId"); + this.ticksAlive = nbt.getInteger("TicksAlive"); + this.currentHealth = nbt.getFloat("CurrentHealth"); + this.panelYaw = nbt.getFloat("PanelYaw"); + this.mode = PanelMode.values()[nbt.getInteger("PanelMode")]; + displayData.readNBT(nbt); + lightningData.readNBT(nbt); + barrierData.readNBT(nbt); + panelData.readNBT(nbt); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt) { + nbt.setInteger("OwnerId", ownerEntityId); + nbt.setInteger("TicksAlive", ticksAlive); + nbt.setFloat("CurrentHealth", currentHealth); + nbt.setFloat("PanelYaw", panelYaw); + nbt.setInteger("PanelMode", mode.ordinal()); + displayData.writeNBT(nbt); + lightningData.writeNBT(nbt); + barrierData.writeNBT(nbt); + panelData.writeNBT(nbt); + } + + // ==================== SPAWN DATA ==================== + + @Override + public void writeSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = new NBTTagCompound(); + this.writeEntityToNBT(compound); + cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error writing energy panel spawn data", e); + } + } + + @Override + public void readSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); + if (compound != null) { + this.readEntityFromNBT(compound); + } + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error reading energy panel spawn data", e); + } + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java new file mode 100644 index 000000000..5fccfe2e4 --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -0,0 +1,337 @@ +package kamkeel.npcs.entity; + +import kamkeel.npcs.controllers.data.ability.data.*; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; +import noppes.npcs.EventHooks; + +import java.util.ArrayList; +import java.util.List; + +/** + * Energy Slicer entity - a thin, wide blade projectile that flies in a straight line. + * JJK Cleave/Dismantle inspired. Piercing by default (passes through entities). + * Extends EntityAbilityProjectile to leverage the existing projectile infrastructure. + */ +public class EntityEnergySlicer extends EntityAbilityProjectile { + + private float sliceWidth = 3.0f; + private float sliceThickness = 0.15f; + private boolean piercing = true; + private List hitEntities = new ArrayList<>(); + + public EntityEnergySlicer(World world) { + super(world); + } + + /** + * Full constructor with all parameters. + */ + public EntityEnergySlicer(World world, EntityLivingBase owner, EntityLivingBase target, + double x, double y, double z, + float sliceWidth, float sliceThickness, + EnergyDisplayData display, EnergyCombatData combat, + EnergyHomingData homing, EnergyLightningData lightning, + EnergyLifespanData lifespan, EnergyTrajectoryData trajectory, + boolean piercing) { + super(world); + + initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan, trajectory); + this.homingData = homing; + this.sliceWidth = sliceWidth; + this.sliceThickness = sliceThickness; + this.piercing = piercing; + + calculateInitialVelocity(owner, target, x, y, z); + } + + /** + * Setup in charging mode (windup phase). + */ + public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float width, float thickness) { + this.sliceWidth = 0.01f; + this.sliceThickness = 0.01f; + super.setupCharging(anchor, chargeDuration); + // Store target dimensions + this.targetSize = width; // Re-use base field for width + } + + // Targets stored during charging for grow animation + private float targetSliceWidth = 3.0f; + private float targetSliceThickness = 0.15f; + + /** + * Setup charging with full target dimensions. + */ + public void setupSlicerCharging(EnergyAnchorData anchor, int chargeDuration, float width, float thickness) { + this.targetSliceWidth = width; + this.targetSliceThickness = thickness; + this.sliceWidth = 0.01f; + this.sliceThickness = 0.01f; + super.setupCharging(anchor, chargeDuration); + } + + /** + * Setup preview mode. + */ + public void setupPreview(EntityLivingBase owner, float width, float thickness, + EnergyDisplayData display, EnergyLightningData lightning, + EnergyAnchorData anchor, int chargeDuration) { + this.setPreviewMode(true); + this.setPreviewOwner(owner); + this.displayData = display; + this.lightningData = lightning; + this.setCharging(true); + this.chargeDuration = chargeDuration; + this.chargeTick = 0; + this.anchorData = anchor; + + this.targetSliceWidth = width; + this.targetSliceThickness = thickness; + this.sliceWidth = 0.01f; + this.sliceThickness = 0.01f; + this.size = 0.01f; + this.renderCurrentSize = 0.01f; + this.prevRenderSize = 0.01f; + + Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition(owner, anchorData); + this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + this.prevPosX = pos.xCoord; + this.prevPosY = pos.yCoord; + this.prevPosZ = pos.zCoord; + this.startX = pos.xCoord; + this.startY = pos.yCoord; + this.startZ = pos.zCoord; + this.motionX = 0; + this.motionY = 0; + this.motionZ = 0; + } + + /** + * Start preview firing. + */ + public void startPreviewFiring() { + setCharging(false); + startX = posX; + startY = posY; + startZ = posZ; + prevPosX = posX; + prevPosY = posY; + prevPosZ = posZ; + + Entity owner = getOwnerEntity(); + if (owner != null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); + motionX = -Math.sin(yaw) * getSpeed(); + motionY = 0; + motionZ = Math.cos(yaw) * getSpeed(); + } else { + motionX = getSpeed(); + motionY = 0; + motionZ = 0; + } + } + + /** + * Start the slicer moving (exit charging mode). + */ + public void startMoving(EntityLivingBase target) { + setCharging(false); + snapToPlayerLookVector(); + startX = posX; + startY = posY; + startZ = posZ; + + Entity owner = getOwnerEntity(); + if (target != null) { + double dx = target.posX - posX; + double dy = (target.posY + target.getEyeHeight()) - posY; + double dz = target.posZ - posZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 0) { + motionX = (dx / len) * getSpeed(); + motionY = (dy / len) * getSpeed(); + motionZ = (dz / len) * getSpeed(); + } + } else if (owner != null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); + motionY = -Math.sin(pitch) * getSpeed(); + motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); + } + } + + @Override + protected void updateRotation() { + // Slicer doesn't spin - it stays oriented perpendicular to travel direction + // No rotation update needed + } + + @Override + protected void updateProjectile() { + if (isCharging()) { + updateCharging(); + return; + } + + if (previewMode) { + posX += motionX; + posY += motionY; + posZ += motionZ; + return; + } + + if (worldObj.isRemote) { + handleClientInterpolation(); + } else { + // No homing by default for slicer (straight line flight) + if (homingData.isHoming()) { + updateHoming(); + } + + checkBlockCollision(); + checkEntityCollision(); + this.moveEntity(motionX, motionY, motionZ); + } + + // Wall collision + if (this.isCollidedHorizontally || this.isCollidedVertically) { + if (!worldObj.isRemote) { + hasHit = true; + if (isExplosive()) { + doExplosion(); + } + } + this.setDead(); + } + } + + @Override + protected void updateCharging() { + chargeTick++; + float progress = getChargeProgress(); + this.sliceWidth = targetSliceWidth * progress; + this.sliceThickness = targetSliceThickness * progress; + this.size = this.sliceWidth; + + Entity owner = getOwnerEntity(); + if (owner instanceof EntityLivingBase) { + Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData); + setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + } + } + + private void checkBlockCollision() { + if (piercing) return; // Piercing slicers pass through blocks + + Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); + Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); + MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); + + if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { + if (!worldObj.isRemote) { + EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); + } + hasHit = true; + this.setDead(); + } + } + + private void checkEntityCollision() { + float halfW = sliceWidth * 0.5f; + float halfH = sliceThickness * 0.5f; + AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( + posX - halfW, posY - halfH, posZ - halfW, + posX + halfW, posY + halfH, posZ + halfW + ); + + @SuppressWarnings("unchecked") + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); + + for (EntityLivingBase entity : entities) { + if (shouldIgnoreEntity(entity)) continue; + if (hitEntities.contains(entity.getEntityId())) continue; // Already hit this entity + + if (piercing) { + // Pierce: damage but keep flying + applyDamage(entity); + hitEntities.add(entity.getEntityId()); + } else { + // Non-piercing: damage and die + hasHit = true; + if (isExplosive()) { + doExplosion(); + } else { + applyDamage(entity); + } + this.setDead(); + return; + } + } + } + + /** + * Get the yaw angle from motion vector for orientation. + */ + public float getTravelYaw() { + if (isCharging()) { + Entity owner = previewMode ? previewOwner : getOwnerEntity(); + if (owner != null) return owner.rotationYaw; + } + double speed = Math.sqrt(motionX * motionX + motionZ * motionZ); + if (speed > 0.001) { + return (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI); + } + return 0.0f; + } + + /** + * Get the pitch angle from motion vector. + */ + public float getTravelPitch() { + double horizSpeed = Math.sqrt(motionX * motionX + motionZ * motionZ); + if (horizSpeed > 0.001 || Math.abs(motionY) > 0.001) { + return (float) (-Math.atan2(motionY, horizSpeed) * 180.0 / Math.PI); + } + return 0.0f; + } + + // ==================== GETTERS ==================== + + public float getSliceWidth() { return sliceWidth; } + public void setSliceWidth(float width) { this.sliceWidth = width; } + + public float getSliceThickness() { return sliceThickness; } + public void setSliceThickness(float thickness) { this.sliceThickness = thickness; } + + public boolean isPiercing() { return piercing; } + public void setPiercing(boolean piercing) { this.piercing = piercing; } + + // ==================== NBT ==================== + + @Override + protected void readProjectileNBT(NBTTagCompound nbt) { + readChargingNBT(nbt); + this.sliceWidth = nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f; + this.sliceThickness = nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f; + this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); + this.targetSliceWidth = nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth; + this.targetSliceThickness = nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness; + } + + @Override + protected void writeProjectileNBT(NBTTagCompound nbt) { + writeChargingNBT(nbt); + nbt.setFloat("SliceWidth", sliceWidth); + nbt.setFloat("SliceThickness", sliceThickness); + nbt.setBoolean("Piercing", piercing); + nbt.setFloat("TargetSliceWidth", targetSliceWidth); + nbt.setFloat("TargetSliceThickness", targetSliceThickness); + } +} diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index caf86c7b5..ed51c9d6c 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -92,6 +92,9 @@ import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; +import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityEnergySlicer; import noppes.npcs.entity.old.EntityNPCDwarfFemale; import noppes.npcs.entity.old.EntityNPCDwarfMale; import noppes.npcs.entity.old.EntityNPCElfFemale; @@ -294,6 +297,9 @@ public class CustomNpcs { registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 3, true); registerNewEntity(EntityAbilitySweeper.class, "abilitysweeper", 64, 3, true); registerNewEntity(EntityAbilityZone.class, "abilityzone", 64, 3, true); + registerNewEntity(EntityEnergyDome.class, "energydome", 64, 3, true); + registerNewEntity(EntityEnergyPanel.class, "energypanel", 64, 3, true); + registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); new RecipeController(); diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 2970d835a..28e028afe 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -152,6 +152,9 @@ import kamkeel.npcs.client.renderer.RenderAbilityLaser; import kamkeel.npcs.client.renderer.RenderAbilityBeam; import kamkeel.npcs.client.renderer.RenderAbilitySweeper; import kamkeel.npcs.client.renderer.RenderAbilityZone; +import kamkeel.npcs.client.renderer.RenderEnergyDome; +import kamkeel.npcs.client.renderer.RenderEnergyPanel; +import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.lightning.LightningHandler; import kamkeel.npcs.entity.EntityAbilityOrb; import kamkeel.npcs.entity.EntityAbilityDisc; @@ -159,6 +162,9 @@ import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; +import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityEnergySlicer; import noppes.npcs.client.renderer.blocks.BlockBannerRenderer; import noppes.npcs.client.renderer.blocks.BlockBarrelRenderer; import noppes.npcs.client.renderer.blocks.BlockBeamRenderer; @@ -273,6 +279,9 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityAbilityBeam.class, new RenderAbilityBeam()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilitySweeper.class, new RenderAbilitySweeper()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilityZone.class, new RenderAbilityZone()); + RenderingRegistry.registerEntityRenderingHandler(EntityEnergyDome.class, new RenderEnergyDome()); + RenderingRegistry.registerEntityRenderingHandler(EntityEnergyPanel.class, new RenderEnergyPanel()); + RenderingRegistry.registerEntityRenderingHandler(EntityEnergySlicer.class, new RenderEnergySlicer()); RenderingRegistry.registerEntityRenderingHandler(EntityCustomNpc.class, new RenderCustomNpc()); From d1f2c9695c99542a1cd7aaf8a8877ceaeab92abf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 13:27:29 +0000 Subject: [PATCH 2/3] Fix panel directional check to only block incoming projectiles The dot product was being computed but not used - panels were blocking projectiles from both sides. Now correctly checks which side the projectile approaches from and only blocks if it's moving toward the panel surface. https://claude.ai/code/session_01EV9KpiykvW4rASz7ju5iTG --- .../java/kamkeel/npcs/entity/EntityEnergyPanel.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index ebc75bb0c..e28e5d53d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -286,14 +286,18 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD if (Math.abs(localRight) > halfW) return false; if (Math.abs(relY) > halfH) return false; - // Check incoming direction (dot product with panel normal) + // Check incoming direction via dot product with panel normal double normalX = -Math.sin(yawRad); double normalZ = Math.cos(yawRad); double dot = projectile.motionX * normalX + projectile.motionZ * normalZ; - // If the projectile is on the owner's side, it must be moving toward the panel - // (negative dot means moving into the panel from the front side) - return true; // If within bounds, it's a hit regardless of direction + // Determine which side of the panel the projectile is on + // If on the front (localForward > 0), it must be moving backward (dot < 0) to be incoming + // If on the back (localForward < 0), it must be moving forward (dot > 0) to be incoming + if (localForward > 0 && dot >= 0) return false; // Moving away from front side + if (localForward < 0 && dot <= 0) return false; // Moving away from back side + + return true; } // ==================== HELPERS ==================== From c07e9e70060093abe44092b7bdaacb4ea51cd0fc Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 17 Feb 2026 14:32:13 -0500 Subject: [PATCH 3/3] Create Barrier Abilities + Heavy Refactoring --- src/api | 2 +- .../client/renderer/RenderAbilityBeam.java | 34 +- .../client/renderer/RenderAbilityDisc.java | 33 +- .../client/renderer/RenderAbilityLaser.java | 32 +- .../client/renderer/RenderAbilityOrb.java | 39 +- ...ojectile.java => RenderEnergyAbility.java} | 40 +- .../client/renderer/RenderEnergyBarrier.java | 23 + .../client/renderer/RenderEnergyDome.java | 91 ++-- .../client/renderer/RenderEnergyPanel.java | 65 +-- .../client/renderer/RenderEnergySlicer.java | 58 +-- .../data/ability/AbilityController.java | 17 +- .../data/ability/data/EnergyBarrierData.java | 52 ++- .../data/ability/data/EnergyDisplayData.java | 35 +- .../data/ability/data/EnergyPanelData.java | 4 +- .../type/{ => energy}/AbilityBeam.java | 5 +- .../type/{ => energy}/AbilityDisc.java | 4 +- .../AbilityDome.java} | 38 +- .../ability/type/energy/AbilityEnergy.java | 134 ++++++ .../AbilityEnergyBarrier.java} | 135 +++--- .../AbilityEnergyProjectile.java} | 84 +--- .../type/{ => energy}/AbilityLaserShot.java | 6 +- .../ability/type/{ => energy}/AbilityOrb.java | 4 +- .../AbilityShield.java} | 31 +- .../AbilitySlicer.java} | 24 +- .../type/{ => energy}/AbilitySweeper.java | 48 +- .../AbilityWall.java} | 40 +- .../npcs/entity/EntityAbilityBeam.java | 2 +- .../npcs/entity/EntityAbilityDisc.java | 2 +- .../npcs/entity/EntityAbilityLaser.java | 2 +- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 4 +- .../npcs/entity/EntityAbilitySweeper.java | 126 +----- .../npcs/entity/EntityEnergyAbility.java | 241 ++++++++++ .../npcs/entity/EntityEnergyBarrier.java | 269 +++++++++++ .../kamkeel/npcs/entity/EntityEnergyDome.java | 310 +++++-------- .../npcs/entity/EntityEnergyPanel.java | 420 +++++++----------- ...ctile.java => EntityEnergyProjectile.java} | 288 ++---------- .../npcs/entity/EntityEnergySlicer.java | 15 +- src/main/java/noppes/npcs/EventHooks.java | 60 ++- .../client/gui/builder/GuiFieldBuilder.java | 6 +- .../noppes/npcs/constants/EnumScriptType.java | 8 +- .../java/noppes/npcs/scripted/NpcAPI.java | 10 + .../npcs/scripted/constants/EntityType.java | 4 + .../scripted/entity/ScriptEnergyAbility.java | 68 +++ .../scripted/entity/ScriptEnergyBarrier.java | 99 +++++ .../scripted/entity/ScriptEnergyDome.java | 32 ++ .../scripted/entity/ScriptEnergyPanel.java | 41 ++ .../entity/ScriptEnergyProjectile.java | 48 +- .../scripted/entity/ScriptEnergySlicer.java | 67 +++ .../npcs/scripted/entity/ScriptEntity.java | 4 +- .../npcs/scripted/entity/ScriptNpc.java | 4 +- .../npcs/scripted/entity/ScriptPlayer.java | 4 +- .../scripted/event/EnergyBarrierEvent.java | 98 ++++ .../assets/customnpcs/lang/en_US.lang | 40 ++ 53 files changed, 1890 insertions(+), 1460 deletions(-) rename src/main/java/kamkeel/npcs/client/renderer/{RenderAbilityProjectile.java => RenderEnergyAbility.java} (84%) create mode 100644 src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{ => energy}/AbilityBeam.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{ => energy}/AbilityDisc.java (98%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbilityEnergyDome.java => energy/AbilityDome.java} (74%) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbstractEnergyBarrierAbility.java => energy/AbilityEnergyBarrier.java} (53%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbstractEnergyProjectileAbility.java => energy/AbilityEnergyProjectile.java} (83%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{ => energy}/AbilityLaserShot.java (96%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{ => energy}/AbilityOrb.java (97%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbilityEnergyShield.java => energy/AbilityShield.java} (84%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbilityEnergySlicer.java => energy/AbilitySlicer.java} (90%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{ => energy}/AbilitySweeper.java (81%) rename src/main/java/kamkeel/npcs/controllers/data/ability/type/{AbilityEnergyWall.java => energy/AbilityWall.java} (83%) create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java create mode 100644 src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java rename src/main/java/kamkeel/npcs/entity/{EntityAbilityProjectile.java => EntityEnergyProjectile.java} (76%) create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java create mode 100644 src/main/java/noppes/npcs/scripted/event/EnergyBarrierEvent.java diff --git a/src/api b/src/api index efa8721d3..3d648425f 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit efa8721d3658b45146d58d6731a0b83c4860f02e +Subproject commit 3d648425fabfd28ba74fc9743e6c3c19609d90bc diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 4912e4b72..725e440e5 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -2,7 +2,6 @@ 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 net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; @@ -24,7 +23,7 @@ import java.util.List; * Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) -public class RenderAbilityBeam extends RenderAbilityProjectile { +public class RenderAbilityBeam extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { @@ -117,7 +116,7 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { // Render lightning during charging if enabled if (beam.hasLightningEffect()) { - renderAttachedLightning(beam, scale); + renderAttachedLightning(beam, 0.6f * 0.5f, scale); } GL11.glPushMatrix(); @@ -545,7 +544,7 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { // Render lightning BEFORE rotation so it crackles in all directions (only on head) if (beam.hasLightningEffect()) { - renderAttachedLightning(beam, scale); + renderAttachedLightning(beam, 0.6f * 0.5f, scale); } // Rotation @@ -579,31 +578,4 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { GL11.glPopMatrix(); } - /** - * Render fading lightning arcs attached to the beam head (in local space). - */ - private void renderAttachedLightning(EntityAbilityBeam beam, float headScale) { - AttachedLightningRenderer.LightningState state = getLightningState(beam); - - float density = beam.getLightningDensity(); - // Lightning radius extends outward from inner surface (innerScale = 0.6) - float innerRadius = 0.6f * headScale * 0.5f; - float radius = innerRadius + beam.getLightningRadius() * headScale; - int outerColor = beam.getOuterColor(); - int innerColor = beam.getInnerColor(); - int fadeTime = beam.getLightningFadeTime(); - - state.update(density, radius, outerColor, innerColor, fadeTime); - state.render(); - } - - /** - * Get or create the lightning state for an entity. - */ - private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityBeam beam) { - if (beam.lightningState == null) { - beam.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) beam.lightningState; - } } diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index 8cb8cdaad..76ffc6cfb 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -2,7 +2,6 @@ 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.EntityAbilityDisc; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; @@ -14,7 +13,7 @@ import org.lwjgl.opengl.GL11; * Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) -public class RenderAbilityDisc extends RenderAbilityProjectile { +public class RenderAbilityDisc extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { @@ -37,7 +36,7 @@ public class RenderAbilityDisc extends RenderAbilityProjectile { // Render lightning BEFORE rotation so it crackles in all directions if (disc.hasLightningEffect()) { - renderAttachedLightning(disc, radius * scale); + renderAttachedLightning(disc, 0.6f * 0.5f, radius * scale); } GL11.glPushMatrix(); @@ -76,34 +75,6 @@ public class RenderAbilityDisc extends RenderAbilityProjectile { restoreRenderState(); } - /** - * Render fading lightning arcs attached to the disc (in local space). - */ - private void renderAttachedLightning(EntityAbilityDisc disc, float discSize) { - AttachedLightningRenderer.LightningState state = getLightningState(disc); - - float density = disc.getLightningDensity(); - // Lightning radius extends outward from inner surface (innerScale = 0.6) - float innerRadius = 0.6f * discSize * 0.5f; - float radius = innerRadius + disc.getLightningRadius() * discSize; - int outerColor = disc.getOuterColor(); - int innerColor = disc.getInnerColor(); - int fadeTime = disc.getLightningFadeTime(); - - state.update(density, radius, outerColor, innerColor, fadeTime); - state.render(); - } - - /** - * Get or create the lightning state for an entity. - */ - private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityDisc disc) { - if (disc.lightningState == null) { - disc.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) disc.lightningState; - } - /** * Render a flat square (thin cube) centered at origin. * The square lies flat on the XZ plane with thin Y height. diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index e70e5c6ba..e26f33489 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -3,7 +3,6 @@ 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; @@ -15,7 +14,7 @@ import org.lwjgl.opengl.GL11; * Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) -public class RenderAbilityLaser extends RenderAbilityProjectile { +public class RenderAbilityLaser extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { @@ -147,7 +146,7 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { // Render lightning during charging if enabled if (laser.hasLightningEffect()) { - renderAttachedLightning(laser, scale); + renderAttachedLightning(laser, 0.6f * 0.5f, scale); } GL11.glPushMatrix(); @@ -170,7 +169,7 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { // Render inner core GL11.glScalef(innerScale, innerScale, innerScale); - renderCube(innerColor, 1.0f, 0.5f); + renderCube(innerColor, laser.getInnerAlpha(), 0.5f); GL11.glPopMatrix(); GL11.glPopMatrix(); @@ -215,31 +214,6 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { } - 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. - */ - private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityLaser laser) { - if (laser.lightningState == null) { - laser.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) laser.lightningState; - } - /** * Get or create the tip lightning state for the laser. * Stored as a secondary object in lightningState2. diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java index 048b3642f..a3adf71a0 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java @@ -2,7 +2,6 @@ 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.EntityAbilityOrb; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; @@ -12,7 +11,7 @@ import org.lwjgl.opengl.GL11; * Minecraft-style blocky appearance using Tessellator. */ @SideOnly(Side.CLIENT) -public class RenderAbilityOrb extends RenderAbilityProjectile { +public class RenderAbilityOrb extends RenderEnergyAbility { private static final int LIGHTNING_FADE_TICKS = 6; @@ -35,7 +34,7 @@ public class RenderAbilityOrb extends RenderAbilityProjectile { // Render lightning BEFORE rotation so it crackles in all directions if (orb.hasLightningEffect()) { - renderAttachedLightning(orb, scale); + renderAttachedLightning(orb, 0.6f * 0.5f, scale); } // Use entity's interpolated rotation for smooth spinning @@ -63,7 +62,7 @@ public class RenderAbilityOrb extends RenderAbilityProjectile { // Render inner cube (solid) GL11.glScalef(innerScale, innerScale, innerScale); - renderCube(orb.getInnerColor(), 1.0f, 0.5f); + renderCube(orb.getInnerColor(), orb.getInnerAlpha(), 0.5f); GL11.glPopMatrix(); GL11.glPopMatrix(); @@ -71,36 +70,4 @@ public class RenderAbilityOrb extends RenderAbilityProjectile { restoreRenderState(); } - /** - * Render fading lightning arcs attached to the orb (in local space). - * Arcs persist for a few ticks and fade out, while staying attached to the orb. - */ - private void renderAttachedLightning(EntityAbilityOrb orb, float orbScale) { - // Get or create the lightning state for this entity - AttachedLightningRenderer.LightningState state = getLightningState(orb); - - float density = orb.getLightningDensity(); - // Lightning radius extends outward from inner surface (innerScale = 0.6) - float innerRadius = 0.6f * orbScale * 0.5f; // Half size of inner cube - float radius = innerRadius + orb.getLightningRadius() * orbScale; - int outerColor = orb.getOuterColor(); - int innerColor = orb.getInnerColor(); - int fadeTime = orb.getLightningFadeTime(); - - // Update: age existing arcs, spawn new ones based on density - state.update(density, radius, outerColor, innerColor, fadeTime); - - // Render all active arcs in local space (they move with the orb) - state.render(); - } - - /** - * Get or create the lightning state for an entity. - */ - private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityOrb orb) { - if (orb.lightningState == null) { - orb.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) orb.lightningState; - } } diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityProjectile.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java similarity index 84% rename from src/main/java/kamkeel/npcs/client/renderer/RenderAbilityProjectile.java rename to src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java index 96e8e7b08..f4aa9c318 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java @@ -2,6 +2,8 @@ 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.EntityEnergyAbility; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.Render; @@ -17,11 +19,11 @@ import org.lwjgl.opengl.GL12; * Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) -public abstract class RenderAbilityProjectile extends Render { +public abstract class RenderEnergyAbility extends Render { protected static final ResourceLocation WHITE_TEXTURE = new ResourceLocation("customnpcs", "textures/entity/white.png"); - public RenderAbilityProjectile() { + public RenderEnergyAbility() { this.shadowSize = 0.0f; } @@ -252,6 +254,40 @@ public abstract class RenderAbilityProjectile extends Render { tess.draw(); } + // ==================== SHARED LIGHTNING ==================== + + /** + * Get or create the lightning state for an energy entity. + * Uses the lightningState field on EntityEnergyAbility (stored as Object + * to avoid client class loading on server). + */ + protected AttachedLightningRenderer.LightningState getLightningState(EntityEnergyAbility entity) { + if (entity.lightningState == null) { + entity.lightningState = new AttachedLightningRenderer.LightningState(); + } + return (AttachedLightningRenderer.LightningState) entity.lightningState; + } + + /** + * Render attached lightning arcs around an energy entity. + * @param entity the energy entity + * @param innerScale multiplier for inner radius (e.g. 0.3f for projectiles, 0.95f for dome) + * @param baseSize the entity's current render size/scale + */ + protected void renderAttachedLightning(EntityEnergyAbility entity, float innerScale, float baseSize) { + AttachedLightningRenderer.LightningState state = getLightningState(entity); + + float density = entity.getLightningDensity(); + float innerRadius = innerScale * baseSize; + float radius = innerRadius + entity.getLightningRadius() * baseSize; + int outerColor = entity.getOuterColor(); + int innerColor = entity.getInnerColor(); + int fadeTime = entity.getLightningFadeTime(); + + state.update(density, radius, outerColor, innerColor, fadeTime); + state.render(); + } + @Override protected ResourceLocation getEntityTexture(Entity entity) { return WHITE_TEXTURE; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java new file mode 100644 index 000000000..6f4e1e89f --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBarrier.java @@ -0,0 +1,23 @@ +package kamkeel.npcs.client.renderer; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.entity.EntityEnergyBarrier; + +/** + * Intermediate renderer base for energy barrier entities (Dome, Panel). + * Provides shared hit flash alpha computation. + */ +@SideOnly(Side.CLIENT) +public abstract class RenderEnergyBarrier extends RenderEnergyAbility { + + /** + * Compute the alpha boost from a hit flash effect. + * Flash value decays from 4 to 0, producing alpha from 0.3 to 0. + */ + protected float computeFlashAlpha(EntityEnergyBarrier barrier) { + byte flash = barrier.getHitFlash(); + if (flash <= 0) return 0.0f; + return flash / 4.0f * 0.3f; + } +} diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java index f6a22e6ed..ecdba16eb 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java @@ -2,19 +2,18 @@ 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.EntityEnergyDome; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; /** - * Renders the Energy Dome entity as a translucent blocky hemisphere. - * Built from cube panels forming a dome shape - voxel-blocky aesthetic. - * Inner layer more opaque, outer layer translucent glow. + * Renders the Dome entity as a translucent sphere. + * Inner layer solid, outer layer translucent glow. + * Optional skybox texture rendered on the interior surface. */ @SideOnly(Side.CLIENT) -public class RenderEnergyDome extends RenderAbilityProjectile { +public class RenderEnergyDome extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { @@ -27,31 +26,32 @@ public class RenderEnergyDome extends RenderAbilityProjectile { GL11.glPushMatrix(); GL11.glTranslated(x, y, z); - // Lightning + // Lightning BEFORE any rotation (uniform spread) if (dome.hasLightningEffect()) { - renderAttachedLightning(dome, radius); + renderAttachedLightning(dome, 0.95f, radius); } // Hit flash effect - float flashAlpha = 0.0f; - if (dome.getHitFlash() > 0) { - flashAlpha = dome.getHitFlash() / 4.0f * 0.3f; - } + float flashAlpha = computeFlashAlpha(dome); float innerScale = 0.95f; - // Render outer dome (translucent) + // Render outer dome (translucent glow) if (dome.isOuterColorEnabled()) { float outerScale = 1.0f + dome.getOuterColorWidth() * 0.1f; float outerAlpha = dome.getOuterColorAlpha() * healthPercent; GL11.glDepthMask(false); - renderBlockyDome(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8); + renderSphere(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8); GL11.glDepthMask(true); } - // Render inner dome (more opaque) - float innerAlpha = (0.5f + 0.3f * healthPercent); - renderBlockyDome(dome.getInnerColor(), innerAlpha + flashAlpha, radius * innerScale, 8); + // TODO: Skybox Feature + // if (dome.isSkyboxEnabled() && !dome.getSkyboxTexture().isEmpty()) { + // renderSkyboxSphere(dome.getSkyboxTexture(), radius, radius * 0.94f, 32, x, y, z); + // } + + // Render inner dome + renderSphere(dome.getInnerColor(), dome.getInnerAlpha(), radius * innerScale, 8); GL11.glPopMatrix(); @@ -59,19 +59,19 @@ public class RenderEnergyDome extends RenderAbilityProjectile { } /** - * Render a dome made of blocky quad panels - voxel style. - * Uses latitude/longitude segments to form a hemisphere of flat panels. + * Render a sphere made of quad panels. + * Uses latitude/longitude segments to form a full sphere. */ - private void renderBlockyDome(int color, float alpha, float radius, int segments) { + private void renderSphere(int color, float alpha, float radius, int segments) { float[] rgb = extractRGB(color); - float r = rgb[0], g = rgb[1], b = rgb[2]; Tessellator tess = Tessellator.instance; - int latSegments = segments / 2; // Half sphere + tess.startDrawingQuads(); + tess.setColorRGBA_F(rgb[0], rgb[1], rgb[2], alpha); - for (int lat = 0; lat < latSegments; lat++) { - float theta1 = (float) (Math.PI * 0.5 * lat / latSegments); - float theta2 = (float) (Math.PI * 0.5 * (lat + 1) / latSegments); + for (int lat = 0; lat < segments; lat++) { + float theta1 = (float) (Math.PI * lat / segments - Math.PI * 0.5); + float theta2 = (float) (Math.PI * (lat + 1) / segments - Math.PI * 0.5); float y1 = (float) Math.sin(theta1) * radius; float y2 = (float) Math.sin(theta2) * radius; @@ -91,63 +91,22 @@ public class RenderEnergyDome extends RenderAbilityProjectile { float x4 = (float) Math.cos(phi1) * r2; float z4 = (float) Math.sin(phi1) * r2; - // Normal pointing outward float nx = (x1 + x2 + x3 + x4) * 0.25f; float ny = (y1 + y1 + y2 + y2) * 0.25f; float nz = (z1 + z2 + z3 + z4) * 0.25f; float nLen = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); if (nLen > 0) { nx /= nLen; ny /= nLen; nz /= nLen; } - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(nx, ny, nz); tess.addVertex(x1, y1, z1); tess.addVertex(x2, y1, z2); tess.addVertex(x3, y2, z3); tess.addVertex(x4, y2, z4); - tess.draw(); } } - // Bottom ring (ground level circle) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha * 0.5f); - tess.setNormal(0, -1, 0); - for (int lon = 0; lon < segments; lon++) { - float phi1 = (float) (2 * Math.PI * lon / segments); - float phi2 = (float) (2 * Math.PI * (lon + 1) / segments); - float x1 = (float) Math.cos(phi1) * radius; - float z1 = (float) Math.sin(phi1) * radius; - float x2 = (float) Math.cos(phi2) * radius; - float z2 = (float) Math.sin(phi2) * radius; - - tess.addVertex(0, 0, 0); - tess.addVertex(x1, 0, z1); - tess.addVertex(x2, 0, z2); - tess.addVertex(0, 0, 0); - } tess.draw(); } - /** - * Render lightning arcs on the dome surface. - */ - private void renderAttachedLightning(EntityEnergyDome dome, float radius) { - AttachedLightningRenderer.LightningState state = getLightningState(dome); - float density = dome.getLightningDensity(); - float lightningRadius = radius + dome.getLightningRadius(); - int outerColor = dome.getOuterColor(); - int innerColor = dome.getInnerColor(); - int fadeTime = dome.getLightningFadeTime(); - - state.update(density, lightningRadius, outerColor, innerColor, fadeTime); - state.render(); - } - - private AttachedLightningRenderer.LightningState getLightningState(EntityEnergyDome dome) { - if (dome.lightningState == null) { - dome.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) dome.lightningState; - } + // TODO: Skybox Feature — renderSkyboxSphere method removed } diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java index 25352ec54..279c6902d 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java @@ -2,7 +2,6 @@ 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.EntityEnergyPanel; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; @@ -14,7 +13,7 @@ import org.lwjgl.opengl.GL11; * Voxel-blocky style with inner/outer layers. */ @SideOnly(Side.CLIENT) -public class RenderEnergyPanel extends RenderAbilityProjectile { +public class RenderEnergyPanel extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { @@ -30,19 +29,16 @@ public class RenderEnergyPanel extends RenderAbilityProjectile { GL11.glPushMatrix(); GL11.glTranslated(x, y, z); + // Lightning BEFORE rotation for uniform spread (like Orb pattern) + if (panel.hasLightningEffect()) { + renderAttachedLightning(panel, 0.9f, Math.max(width, height) * 0.5f); + } + // Rotate panel to face the correct direction GL11.glRotatef(-panelYaw, 0.0f, 1.0f, 0.0f); - // Lightning (before rotation for uniform spread) - if (panel.hasLightningEffect()) { - renderAttachedLightning(panel, Math.max(width, height) * 0.5f); - } - // Hit flash - float flashAlpha = 0.0f; - if (panel.getHitFlash() > 0) { - flashAlpha = panel.getHitFlash() / 4.0f * 0.3f; - } + float flashAlpha = computeFlashAlpha(panel); // Subtle pulsing float pulseTime = entity.ticksExisted + partialTicks; @@ -65,9 +61,8 @@ public class RenderEnergyPanel extends RenderAbilityProjectile { GL11.glDepthMask(true); } - // Render inner panel (more opaque core) - float innerAlpha = (0.6f + 0.3f * healthPercent); - renderPanel(panel.getInnerColor(), innerAlpha + flashAlpha, + // Render inner panel (uses configurable innerAlpha for semi-transparency) + renderPanel(panel.getInnerColor(), panel.getInnerAlpha(), width * 0.5f * innerScale, height * 0.5f * innerScale, panelThickness); GL11.glPopMatrix(); @@ -79,93 +74,59 @@ public class RenderEnergyPanel extends RenderAbilityProjectile { /** * Render a flat panel (thin box) centered at origin. * Panel faces along the Z axis (width on X, height on Y). + * All 6 faces batched into a single draw call. */ private void renderPanel(int color, float alpha, float halfWidth, float halfHeight, float halfThickness) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; Tessellator tess = Tessellator.instance; - - // Front face (z+) tess.startDrawingQuads(); tess.setColorRGBA_F(r, g, b, alpha); + + // Front face (z+) tess.setNormal(0, 0, 1); tess.addVertex(-halfWidth, -halfHeight, halfThickness); tess.addVertex(halfWidth, -halfHeight, halfThickness); tess.addVertex(halfWidth, halfHeight, halfThickness); tess.addVertex(-halfWidth, halfHeight, halfThickness); - tess.draw(); // Back face (z-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(0, 0, -1); tess.addVertex(halfWidth, -halfHeight, -halfThickness); tess.addVertex(-halfWidth, -halfHeight, -halfThickness); tess.addVertex(-halfWidth, halfHeight, -halfThickness); tess.addVertex(halfWidth, halfHeight, -halfThickness); - tess.draw(); // Top edge (y+) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(0, 1, 0); tess.addVertex(-halfWidth, halfHeight, halfThickness); tess.addVertex(halfWidth, halfHeight, halfThickness); tess.addVertex(halfWidth, halfHeight, -halfThickness); tess.addVertex(-halfWidth, halfHeight, -halfThickness); - tess.draw(); // Bottom edge (y-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(0, -1, 0); tess.addVertex(-halfWidth, -halfHeight, -halfThickness); tess.addVertex(halfWidth, -halfHeight, -halfThickness); tess.addVertex(halfWidth, -halfHeight, halfThickness); tess.addVertex(-halfWidth, -halfHeight, halfThickness); - tess.draw(); // Right edge (x+) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(1, 0, 0); tess.addVertex(halfWidth, -halfHeight, halfThickness); tess.addVertex(halfWidth, -halfHeight, -halfThickness); tess.addVertex(halfWidth, halfHeight, -halfThickness); tess.addVertex(halfWidth, halfHeight, halfThickness); - tess.draw(); // Left edge (x-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(-1, 0, 0); tess.addVertex(-halfWidth, -halfHeight, -halfThickness); tess.addVertex(-halfWidth, -halfHeight, halfThickness); tess.addVertex(-halfWidth, halfHeight, halfThickness); tess.addVertex(-halfWidth, halfHeight, -halfThickness); + tess.draw(); } - /** - * Render lightning arcs on the panel surface. - */ - private void renderAttachedLightning(EntityEnergyPanel panel, float size) { - AttachedLightningRenderer.LightningState state = getLightningState(panel); - float density = panel.getLightningDensity(); - float radius = size + panel.getLightningRadius(); - int outerColor = panel.getOuterColor(); - int innerColor = panel.getInnerColor(); - int fadeTime = panel.getLightningFadeTime(); - - state.update(density, radius, outerColor, innerColor, fadeTime); - state.render(); - } - - private AttachedLightningRenderer.LightningState getLightningState(EntityEnergyPanel panel) { - if (panel.lightningState == null) { - panel.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) panel.lightningState; - } } diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java index ad48254ab..b5ee31568 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java @@ -2,7 +2,6 @@ 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.EntityEnergySlicer; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; @@ -14,28 +13,27 @@ import org.lwjgl.opengl.GL11; * Inner solid core with outer translucent glow. */ @SideOnly(Side.CLIENT) -public class RenderEnergySlicer extends RenderAbilityProjectile { +public class RenderEnergySlicer extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { EntityEnergySlicer slicer = (EntityEnergySlicer) entity; - float size = slicer.getInterpolatedSize(partialTicks); float width = slicer.getSliceWidth(); float thickness = slicer.getSliceThickness(); - // Scale with interpolated size for charging grow effect - float sizeRatio = size > 0.01f ? size / Math.max(0.01f, slicer.getSliceWidth()) : 0.01f; - if (sizeRatio > 1.0f) sizeRatio = 1.0f; + // During charging, width/thickness grow from 0.01 to target via updateCharging(). + // Use current dimensions directly - no separate sizeRatio needed. + if (width <= 0.01f) return; setupRenderState(); GL11.glPushMatrix(); GL11.glTranslated(x, y, z); - // Lightning before rotation + // Lightning BEFORE rotation for uniform spread (like Orb pattern) if (slicer.hasLightningEffect()) { - renderAttachedLightning(slicer, width * 0.5f * sizeRatio); + renderAttachedLightning(slicer, 0.7f, width * 0.5f); } // Orient perpendicular to travel direction @@ -44,9 +42,6 @@ public class RenderEnergySlicer extends RenderAbilityProjectile { GL11.glRotatef(travelYaw, 0.0f, 1.0f, 0.0f); GL11.glRotatef(travelPitch, 1.0f, 0.0f, 0.0f); - GL11.glPushMatrix(); - GL11.glScalef(sizeRatio, sizeRatio, sizeRatio); - float innerScale = 0.7f; // Render outer blade (translucent glow) @@ -58,12 +53,11 @@ public class RenderEnergySlicer extends RenderAbilityProjectile { GL11.glDepthMask(true); } - // Render inner blade (solid core) + // Render inner blade (solid core, alpha 1.0 like Orb pattern) renderBlade(slicer.getInnerColor(), 1.0f, width * 0.5f * innerScale, thickness, 0.03f); GL11.glPopMatrix(); - GL11.glPopMatrix(); restoreRenderState(); } @@ -71,93 +65,61 @@ public class RenderEnergySlicer extends RenderAbilityProjectile { /** * Render a thin wide blade shape. * The blade extends along X (width), thin on Y (thickness), and very thin on Z (depth). + * All 6 faces batched into a single draw call. */ private void renderBlade(int color, float alpha, float halfWidth, float halfHeight, float halfDepth) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; Tessellator tess = Tessellator.instance; + tess.startDrawingQuads(); // Front face (z+) - tess.startDrawingQuads(); tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(0, 0, 1); tess.addVertex(-halfWidth, -halfHeight, halfDepth); tess.addVertex(halfWidth, -halfHeight, halfDepth); tess.addVertex(halfWidth, halfHeight, halfDepth); tess.addVertex(-halfWidth, halfHeight, halfDepth); - tess.draw(); // Back face (z-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha); tess.setNormal(0, 0, -1); tess.addVertex(halfWidth, -halfHeight, -halfDepth); tess.addVertex(-halfWidth, -halfHeight, -halfDepth); tess.addVertex(-halfWidth, halfHeight, -halfDepth); tess.addVertex(halfWidth, halfHeight, -halfDepth); - tess.draw(); // Top edge (y+) - tess.startDrawingQuads(); tess.setColorRGBA_F(r, g, b, alpha * 0.8f); tess.setNormal(0, 1, 0); tess.addVertex(-halfWidth, halfHeight, halfDepth); tess.addVertex(halfWidth, halfHeight, halfDepth); tess.addVertex(halfWidth, halfHeight, -halfDepth); tess.addVertex(-halfWidth, halfHeight, -halfDepth); - tess.draw(); // Bottom edge (y-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha * 0.8f); tess.setNormal(0, -1, 0); tess.addVertex(-halfWidth, -halfHeight, -halfDepth); tess.addVertex(halfWidth, -halfHeight, -halfDepth); tess.addVertex(halfWidth, -halfHeight, halfDepth); tess.addVertex(-halfWidth, -halfHeight, halfDepth); - tess.draw(); // Right edge (x+) - tess.startDrawingQuads(); tess.setColorRGBA_F(r, g, b, alpha * 0.6f); tess.setNormal(1, 0, 0); tess.addVertex(halfWidth, -halfHeight, halfDepth); tess.addVertex(halfWidth, -halfHeight, -halfDepth); tess.addVertex(halfWidth, halfHeight, -halfDepth); tess.addVertex(halfWidth, halfHeight, halfDepth); - tess.draw(); // Left edge (x-) - tess.startDrawingQuads(); - tess.setColorRGBA_F(r, g, b, alpha * 0.6f); tess.setNormal(-1, 0, 0); tess.addVertex(-halfWidth, -halfHeight, -halfDepth); tess.addVertex(-halfWidth, -halfHeight, halfDepth); tess.addVertex(-halfWidth, halfHeight, halfDepth); tess.addVertex(-halfWidth, halfHeight, -halfDepth); + tess.draw(); } - /** - * Render lightning arcs on the slicer. - */ - private void renderAttachedLightning(EntityEnergySlicer slicer, float size) { - AttachedLightningRenderer.LightningState state = getLightningState(slicer); - float density = slicer.getLightningDensity(); - float radius = size + slicer.getLightningRadius(); - int outerColor = slicer.getOuterColor(); - int innerColor = slicer.getInnerColor(); - int fadeTime = slicer.getLightningFadeTime(); - - state.update(density, radius, outerColor, innerColor, fadeTime); - state.render(); - } - - private AttachedLightningRenderer.LightningState getLightningState(EntityEnergySlicer slicer) { - if (slicer.lightningState == null) { - slicer.lightningState = new AttachedLightningRenderer.LightningState(); - } - return (AttachedLightningRenderer.LightningState) slicer.lightningState; - } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 7d6cca477..341079af3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -2,6 +2,15 @@ package kamkeel.npcs.controllers.data.ability; import kamkeel.npcs.controllers.data.ability.type.*; import kamkeel.npcs.controllers.SyncController; +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.AbilityOrb; +import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield; +import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySlicer; +import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper; +import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.CustomNpcs; @@ -100,10 +109,10 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:hazard", AbilityHazard::new); registerType("cnpc:trap", AbilityTrap::new); - registerType("cnpc:energy_dome", AbilityEnergyDome::new); - registerType("cnpc:energy_wall", AbilityEnergyWall::new); - registerType("cnpc:energy_shield", AbilityEnergyShield::new); - registerType("cnpc:energy_slicer", AbilityEnergySlicer::new); + registerType("cnpc:dome", AbilityDome::new); + registerType("cnpc:wall", AbilityWall::new); + registerType("cnpc:shield", AbilityShield::new); + registerType("cnpc:slicer", AbilitySlicer::new); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java index be1c3254a..2d65b6228 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -10,12 +10,25 @@ import java.util.Map; * Handles health/durability, duration, and per-projectile-type damage multipliers. */ public class EnergyBarrierData { + // Knockback target display keys + private static final String[] KNOCKBACK_TARGET_KEYS = {"gui.both", "gui.players", "gui.npcs"}; + public float maxHealth = 100.0f; public boolean useHealth = true; public int durationTicks = 200; public boolean useDuration = true; public float defaultMultiplier = 1.0f; + // Knockback settings + public boolean knockbackEnabled = false; + public float knockbackStrength = 1.0f; + /** 0=Both, 1=Player only, 2=NPC only */ + public int knockbackTarget = 0; + + // Melee settings + public boolean meleeEnabled = false; + public float meleeDamageMultiplier = 1.0f; + /** * Per-projectile-type damage multipliers. * Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam"). @@ -72,6 +85,22 @@ public class EnergyBarrierData { public float getDefaultMultiplier() { return defaultMultiplier; } public void setDefaultMultiplier(float defaultMultiplier) { this.defaultMultiplier = defaultMultiplier; } + // Knockback target key helpers + public static String[] getKnockbackTargetKeys() { return KNOCKBACK_TARGET_KEYS; } + + public String getKnockbackTargetKey() { + return KNOCKBACK_TARGET_KEYS[Math.max(0, Math.min(knockbackTarget, 2))]; + } + + public void setKnockbackTargetFromKey(String key) { + for (int i = 0; i < KNOCKBACK_TARGET_KEYS.length; i++) { + if (KNOCKBACK_TARGET_KEYS[i].equals(key)) { + knockbackTarget = i; + return; + } + } + } + // ==================== NBT ==================== public void writeNBT(NBTTagCompound nbt) { @@ -80,6 +109,11 @@ public class EnergyBarrierData { nbt.setInteger("barrierDuration", durationTicks); nbt.setBoolean("barrierUseDuration", useDuration); nbt.setFloat("barrierDefaultMult", defaultMultiplier); + nbt.setBoolean("barrierKnockback", knockbackEnabled); + nbt.setFloat("barrierKnockbackStr", knockbackStrength); + nbt.setInteger("barrierKnockbackTarget", knockbackTarget); + nbt.setBoolean("barrierMelee", meleeEnabled); + nbt.setFloat("barrierMeleeMult", meleeDamageMultiplier); NBTTagCompound multNbt = new NBTTagCompound(); for (Map.Entry entry : damageMultipliers.entrySet()) { @@ -89,11 +123,16 @@ public class EnergyBarrierData { } public void readNBT(NBTTagCompound nbt) { - maxHealth = nbt.getFloat("barrierMaxHealth"); - useHealth = nbt.getBoolean("barrierUseHealth"); - durationTicks = nbt.getInteger("barrierDuration"); - useDuration = nbt.getBoolean("barrierUseDuration"); + maxHealth = nbt.hasKey("barrierMaxHealth") ? nbt.getFloat("barrierMaxHealth") : 100.0f; + useHealth = !nbt.hasKey("barrierUseHealth") || nbt.getBoolean("barrierUseHealth"); + durationTicks = nbt.hasKey("barrierDuration") ? nbt.getInteger("barrierDuration") : 200; + useDuration = !nbt.hasKey("barrierUseDuration") || nbt.getBoolean("barrierUseDuration"); defaultMultiplier = nbt.hasKey("barrierDefaultMult") ? nbt.getFloat("barrierDefaultMult") : 1.0f; + knockbackEnabled = nbt.hasKey("barrierKnockback") && nbt.getBoolean("barrierKnockback"); + knockbackStrength = nbt.hasKey("barrierKnockbackStr") ? nbt.getFloat("barrierKnockbackStr") : 1.0f; + knockbackTarget = nbt.hasKey("barrierKnockbackTarget") ? nbt.getInteger("barrierKnockbackTarget") : 0; + meleeEnabled = nbt.hasKey("barrierMelee") && nbt.getBoolean("barrierMelee"); + meleeDamageMultiplier = nbt.hasKey("barrierMeleeMult") ? nbt.getFloat("barrierMeleeMult") : 1.0f; damageMultipliers.clear(); if (nbt.hasKey("barrierMultipliers")) { @@ -109,6 +148,11 @@ public class EnergyBarrierData { public EnergyBarrierData copy() { EnergyBarrierData copy = new EnergyBarrierData(maxHealth, useHealth, durationTicks, useDuration); copy.defaultMultiplier = defaultMultiplier; + copy.knockbackEnabled = knockbackEnabled; + copy.knockbackStrength = knockbackStrength; + copy.knockbackTarget = knockbackTarget; + copy.meleeEnabled = meleeEnabled; + copy.meleeDamageMultiplier = meleeDamageMultiplier; copy.damageMultipliers = new HashMap<>(damageMultipliers); return copy; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java index 559be77f4..415c5796c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java @@ -13,8 +13,13 @@ public class EnergyDisplayData implements IEnergyDisplayData { public boolean outerColorEnabled = true; public float outerColorWidth = 0.4f; public float outerColorAlpha = 0.5f; + public float innerAlpha = 1.0f; public float rotationSpeed = 4.0f; + // TODO: Skybox Feature + // public boolean skyboxEnabled = false; + // public String skyboxTexture = ""; + public EnergyDisplayData() {} public EnergyDisplayData(int innerColor, int outerColor) { @@ -32,6 +37,12 @@ public class EnergyDisplayData implements IEnergyDisplayData { this.rotationSpeed = rotationSpeed; } + public EnergyDisplayData(int innerColor, int outerColor, boolean outerColorEnabled, + float outerColorWidth, float outerColorAlpha, float innerAlpha, float rotationSpeed) { + this(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, rotationSpeed); + this.innerAlpha = innerAlpha; + } + @Override public int getInnerColor() { return innerColor; @@ -82,6 +93,16 @@ public class EnergyDisplayData implements IEnergyDisplayData { this.outerColorAlpha = outerColorAlpha; } + @Override + public float getInnerAlpha() { + return innerAlpha; + } + + @Override + public void setInnerAlpha(float innerAlpha) { + this.innerAlpha = innerAlpha; + } + @Override public float getRotationSpeed() { return rotationSpeed; @@ -98,7 +119,11 @@ public class EnergyDisplayData implements IEnergyDisplayData { nbt.setBoolean("outerColorEnabled", outerColorEnabled); nbt.setFloat("outerColorWidth", outerColorWidth); nbt.setFloat("outerColorAlpha", outerColorAlpha); + nbt.setFloat("innerAlpha", innerAlpha); nbt.setFloat("rotationSpeed", rotationSpeed); + // TODO: Skybox Feature + // nbt.setBoolean("skyboxEnabled", skyboxEnabled); + // nbt.setString("skyboxTexture", skyboxTexture); } public void readNBT(NBTTagCompound nbt) { @@ -107,10 +132,18 @@ public class EnergyDisplayData implements IEnergyDisplayData { outerColorEnabled = nbt.getBoolean("outerColorEnabled"); outerColorWidth = nbt.getFloat("outerColorWidth"); outerColorAlpha = nbt.getFloat("outerColorAlpha"); + innerAlpha = nbt.hasKey("innerAlpha") ? nbt.getFloat("innerAlpha") : 1.0f; rotationSpeed = nbt.getFloat("rotationSpeed"); + // TODO: Skybox Feature + // skyboxEnabled = nbt.hasKey("skyboxEnabled") && nbt.getBoolean("skyboxEnabled"); + // skyboxTexture = nbt.hasKey("skyboxTexture") ? nbt.getString("skyboxTexture") : ""; } public EnergyDisplayData copy() { - return new EnergyDisplayData(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, rotationSpeed); + EnergyDisplayData copy = new EnergyDisplayData(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, innerAlpha, rotationSpeed); + // TODO: Skybox Feature + // copy.skyboxEnabled = skyboxEnabled; + // copy.skyboxTexture = skyboxTexture; + return copy; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java index 87f68de1d..1fa8586fe 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java @@ -63,8 +63,8 @@ public class EnergyPanelData { public void readNBT(NBTTagCompound nbt) { panelWidth = nbt.hasKey("panelWidth") ? nbt.getFloat("panelWidth") : 3.0f; panelHeight = nbt.hasKey("panelHeight") ? nbt.getFloat("panelHeight") : 3.0f; - heightOffset = nbt.getFloat("panelHeightOffset"); - launching = nbt.getBoolean("panelLaunching"); + heightOffset = nbt.hasKey("panelHeightOffset") ? nbt.getFloat("panelHeightOffset") : 0.0f; + launching = nbt.hasKey("panelLaunching") && nbt.getBoolean("panelLaunching"); launchSpeed = nbt.hasKey("panelLaunchSpeed") ? nbt.getFloat("panelLaunchSpeed") : 0.5f; launchDamage = nbt.hasKey("panelLaunchDamage") ? nbt.getFloat("panelLaunchDamage") : 8.0f; launchKnockback = nbt.hasKey("panelLaunchKnockback") ? nbt.getFloat("panelLaunchKnockback") : 2.0f; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java index 45772eedb..66e5ef4ac 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java @@ -1,9 +1,8 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.RotationMode; import kamkeel.npcs.controllers.data.ability.TargetingMode; @@ -25,7 +24,7 @@ import java.util.List; * Energy Beam ability: A homing head with a trailing path. * Supports 1-8 projectiles with shared visuals and per-projectile anchor points. */ -public class AbilityBeam extends AbstractEnergyProjectileAbility implements IAbilityEnergyBeam { +public class AbilityBeam extends AbilityEnergyProjectile implements IAbilityEnergyBeam { private float beamWidth = 0.4f; private float headSize = 0.6f; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index 5a542ce7d..cd36eeab3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -23,7 +23,7 @@ import java.util.List; * Supports 1-8 projectiles with shared visuals and per-projectile anchor points. * Has optional boomerang behavior to return to owner. */ -public class AbilityDisc extends AbstractEnergyProjectileAbility implements IAbilityDisc { +public class AbilityDisc extends AbilityEnergyProjectile implements IAbilityDisc { private float discRadius = 1.0f; private float discThickness = 0.2f; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java similarity index 74% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index 0fdac9328..b6bb57e96 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -8,6 +8,7 @@ import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyDome; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -22,38 +23,38 @@ import java.util.List; * Blocks incoming energy projectiles with configurable damage multipliers. * Duration and/or HP based. */ -public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { +public class AbilityDome extends AbilityEnergyBarrier { private float domeRadius = 5.0f; + private boolean followCaster = false; - public AbilityEnergyDome() { + public AbilityDome() { super( - new EnergyDisplayData(0x44CCFF, 0x2288FF, true, 0.3f, 0.35f, 0.0f), + new EnergyDisplayData(0x44CCFF, 0x2288FF, true, 0.3f, 0.35f, 0.5f, 0.0f), new EnergyBarrierData(100.0f, true, 200, true) ); - this.typeId = "ability.cnpc.energy_dome"; - this.name = "Energy Dome"; + this.typeId = "ability.cnpc.dome"; + this.name = "Dome"; this.targetingMode = TargetingMode.SELF; - this.maxRange = 0; - this.minRange = 0; this.cooldownTicks = 100; this.windUpTicks = 30; this.lockMovement = LockMovementType.WINDUP; this.telegraphType = TelegraphType.RING; this.showTelegraph = true; - this.windUpAnimationName = "Ability_EnergyDome_Windup"; - this.activeAnimationName = "Ability_EnergyDome_Active"; + this.windUpAnimationName = ""; + this.activeAnimationName = ""; } // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { EntityEnergyDome dome = new EntityEnergyDome( caster.worldObj, caster, caster.posX, caster.posY, caster.posZ, domeRadius, displayData.copy(), lightningData.copy(), barrierData.copy() ); + dome.setFollowCaster(followCaster); dome.setSourceAbility(this); return dome; } @@ -79,14 +80,14 @@ public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { public List getVariants() { return Arrays.asList( new AbilityVariant("ability.variant.small", a -> { - AbilityEnergyDome dome = (AbilityEnergyDome) a; - a.setName("Small Energy Dome"); + AbilityDome dome = (AbilityDome) a; + a.setName("Small Dome"); dome.setDomeRadius(3.0f); dome.setBarrierMaxHealth(60.0f); }), new AbilityVariant("ability.variant.large", a -> { - AbilityEnergyDome dome = (AbilityEnergyDome) a; - a.setName("Large Energy Dome"); + AbilityDome dome = (AbilityDome) a; + a.setName("Large Dome"); dome.setDomeRadius(8.0f); dome.setBarrierMaxHealth(200.0f); dome.setBarrierDuration(300); @@ -99,11 +100,13 @@ public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { @Override protected void writeBarrierTypeNBT(NBTTagCompound nbt) { nbt.setFloat("domeRadius", domeRadius); + nbt.setBoolean("followCaster", followCaster); } @Override protected void readBarrierTypeNBT(NBTTagCompound nbt) { this.domeRadius = nbt.hasKey("domeRadius") ? nbt.getFloat("domeRadius") : 5.0f; + this.followCaster = nbt.hasKey("followCaster") && nbt.getBoolean("followCaster"); } // ==================== GETTERS & SETTERS ==================== @@ -111,6 +114,9 @@ public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { public float getDomeRadius() { return domeRadius; } public void setDomeRadius(float radius) { this.domeRadius = Math.max(1.0f, radius); } + public boolean isFollowCaster() { return followCaster; } + public void setFollowCaster(boolean follow) { this.followCaster = follow; } + // ==================== TYPE-SPECIFIC GUI ==================== @SideOnly(Side.CLIENT) @@ -118,5 +124,7 @@ public class AbilityEnergyDome extends AbstractEnergyBarrierAbility { protected void addBarrierTypeDefinitions(List defs) { defs.add(FieldDef.floatField("ability.domeRadius", this::getDomeRadius, this::setDomeRadius) .range(1.0f, 30.0f)); + defs.add(FieldDef.boolField("ability.followCaster", this::isFollowCaster, this::setFollowCaster) + .hover("ability.hover.followCaster")); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java new file mode 100644 index 000000000..3e0747160 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java @@ -0,0 +1,134 @@ +package kamkeel.npcs.controllers.data.ability.type.energy; + +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.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; + +/** + * Abstract base for all energy-based abilities (Projectile, Barrier, Sweeper, etc.). + * Provides shared visual data (colors, lightning) and corresponding API/GUI/NBT. + */ +public abstract class AbilityEnergy extends Ability { + + protected EnergyDisplayData displayData; + protected EnergyLightningData lightningData; + + protected AbilityEnergy(EnergyDisplayData displayData) { + this.displayData = displayData; + this.lightningData = new EnergyLightningData(); + } + + // ==================== DISPLAY DATA ==================== + + public int getInnerColor() { return displayData.innerColor; } + public void setInnerColor(int color) { displayData.innerColor = color; } + + public float getInnerAlpha() { return displayData.innerAlpha; } + public void setInnerAlpha(float alpha) { displayData.innerAlpha = alpha; } + + public int getOuterColor() { return displayData.outerColor; } + public void setOuterColor(int color) { displayData.outerColor = color; } + + public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } + public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; } + + public float getOuterColorWidth() { return displayData.outerColorWidth; } + public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; } + + public float getOuterColorAlpha() { return displayData.outerColorAlpha; } + public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; } + + public float getRotationSpeed() { return displayData.rotationSpeed; } + public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; } + + // ==================== LIGHTNING DATA ==================== + + public boolean hasLightningEffect() { return lightningData.lightningEffect; } + public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; } + + public float getLightningDensity() { return lightningData.lightningDensity; } + public void setLightningDensity(float density) { lightningData.lightningDensity = density; } + + public float getLightningRadius() { return lightningData.lightningRadius; } + public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; } + + public int getLightningFadeTime() { return lightningData.lightningFadeTime; } + public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; } + + // ==================== NBT HELPERS ==================== + + protected void writeEnergyNBT(NBTTagCompound nbt) { + displayData.writeNBT(nbt); + lightningData.writeNBT(nbt); + } + + protected void readEnergyNBT(NBTTagCompound nbt) { + displayData.readNBT(nbt); + lightningData.readNBT(nbt); + } + + // ==================== GUI HELPERS ==================== + + /** + * Add complete visual tab definitions (colors section + effects/lightning section). + * Subclasses can call this directly or use the individual methods for more control. + */ + @SideOnly(Side.CLIENT) + protected void addEnergyVisualDefinitions(List defs) { + addEnergyColorDefinitions(defs); + addEnergyEffectDefinitions(defs); + } + + /** + * Add the colors section to the visual tab. + */ + @SideOnly(Side.CLIENT) + protected void addEnergyColorDefinitions(List defs) { + defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual")); + defs.add(FieldDef.row( + FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor), + FieldDef.floatField("ability.innerAlpha", this::getInnerAlpha, this::setInnerAlpha).range(0, 1) + ).tab("ability.tab.visual")); + defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled) + .tab("ability.tab.visual")); + defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor) + .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled)); + defs.add(FieldDef.row( + FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth) + .visibleWhen(this::isOuterColorEnabled), + FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha) + .range(0, 1).visibleWhen(this::isOuterColorEnabled) + ).tab("ability.tab.visual")); + } + + /** + * Add the effects section (lightning) to the visual tab. + * Override to insert additional fields before lightning (e.g., rotationSpeed). + */ + @SideOnly(Side.CLIENT) + protected void addEnergyEffectDefinitions(List defs) { + defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual")); + addEnergyLightningDefinitions(defs); + } + + /** + * Add just the lightning fields (no section header) to the visual tab. + */ + @SideOnly(Side.CLIENT) + protected void addEnergyLightningDefinitions(List defs) { + defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect) + .tab("ability.tab.visual")); + defs.add(FieldDef.row( + FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity) + .visibleWhen(this::hasLightningEffect).range(0.01f, 100f), + FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius) + .range(0.1f, 100f).visibleWhen(this::hasLightningEffect) + ).tab("ability.tab.visual")); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java similarity index 53% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java index f95e76d47..7909f5a56 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyBarrierAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java @@ -1,39 +1,31 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; 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.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.telegraph.TelegraphType; -import net.minecraft.entity.Entity; +import kamkeel.npcs.entity.EntityEnergyBarrier; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; -import java.util.Map; /** * Abstract base for energy barrier abilities (Dome, Wall, Shield). * Handles shared visual data, barrier configuration, and lifecycle management. */ -public abstract class AbstractEnergyBarrierAbility extends Ability { +public abstract class AbilityEnergyBarrier extends AbilityEnergy { - protected EnergyDisplayData displayData; - protected EnergyLightningData lightningData; protected final EnergyBarrierData barrierData; // Runtime entity tracking - protected transient Entity barrierEntity; + protected transient EntityEnergyBarrier barrierEntity; - protected AbstractEnergyBarrierAbility(EnergyDisplayData displayData, EnergyBarrierData barrierData) { - this.displayData = displayData; + protected AbilityEnergyBarrier(EnergyDisplayData displayData, EnergyBarrierData barrierData) { + super(displayData); this.barrierData = barrierData; - this.lightningData = new EnergyLightningData(); } // ==================== ABSTRACT METHODS ==================== @@ -41,7 +33,7 @@ public abstract class AbstractEnergyBarrierAbility extends Ability { /** * Create and spawn the barrier entity during execution. */ - protected abstract Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target); + protected abstract EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target); /** * Add type-specific GUI field definitions. @@ -71,11 +63,30 @@ public abstract class AbstractEnergyBarrierAbility extends Ability { return false; // Barriers are defensive } + @Override + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) { + if (caster.worldObj.isRemote) return; + + if (tick == 1) { + barrierEntity = createBarrierEntity(caster, target); + if (barrierEntity != null) { + barrierEntity.setupCharging(getWindUpTicks()); + spawnAbilityEntity(barrierEntity); + } + } + } + @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { - barrierEntity = createBarrierEntity(caster, target); - if (barrierEntity != null) { - spawnAbilityEntity(barrierEntity); + if (barrierEntity != null && !barrierEntity.isDead) { + // Barrier was spawned during windup — transition to active + barrierEntity.finishCharging(); + } else { + // No windup or entity died — create fresh + barrierEntity = createBarrierEntity(caster, target); + if (barrierEntity != null) { + spawnAbilityEntity(barrierEntity); + } } } @@ -115,49 +126,20 @@ public abstract class AbstractEnergyBarrierAbility extends Ability { @Override public final void writeTypeNBT(NBTTagCompound nbt) { writeBarrierTypeNBT(nbt); - displayData.writeNBT(nbt); - lightningData.writeNBT(nbt); + writeEnergyNBT(nbt); barrierData.writeNBT(nbt); } @Override public final void readTypeNBT(NBTTagCompound nbt) { readBarrierTypeNBT(nbt); - displayData.readNBT(nbt); - lightningData.readNBT(nbt); + readEnergyNBT(nbt); barrierData.readNBT(nbt); } // ==================== API GETTERS & SETTERS ==================== - // Display data - public int getInnerColor() { return displayData.innerColor; } - public void setInnerColor(int color) { displayData.innerColor = color; } - - public int getOuterColor() { return displayData.outerColor; } - public void setOuterColor(int color) { displayData.outerColor = color; } - - public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } - public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; } - - public float getOuterColorWidth() { return displayData.outerColorWidth; } - public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; } - - public float getOuterColorAlpha() { return displayData.outerColorAlpha; } - public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; } - - // Lightning data - public boolean hasLightningEffect() { return lightningData.lightningEffect; } - public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; } - - public float getLightningDensity() { return lightningData.lightningDensity; } - public void setLightningDensity(float density) { lightningData.lightningDensity = density; } - - public float getLightningRadius() { return lightningData.lightningRadius; } - public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; } - - public int getLightningFadeTime() { return lightningData.lightningFadeTime; } - public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; } + // Display and lightning data inherited from AbilityEnergy // Barrier data public float getBarrierMaxHealth() { return barrierData.maxHealth; } @@ -178,6 +160,20 @@ public abstract class AbstractEnergyBarrierAbility extends Ability { public void setDamageMultiplier(String typeId, float mult) { barrierData.setMultiplier(typeId, mult); } public float getDamageMultiplier(String typeId) { return barrierData.getMultiplier(typeId); } + // Knockback data + public boolean isKnockbackEnabled() { return barrierData.knockbackEnabled; } + public void setKnockbackEnabled(boolean enabled) { barrierData.knockbackEnabled = enabled; } + public float getKnockbackStrength() { return barrierData.knockbackStrength; } + public void setKnockbackStrength(float strength) { barrierData.knockbackStrength = strength; } + public String getKnockbackTargetKey() { return barrierData.getKnockbackTargetKey(); } + public void setKnockbackTargetKey(String key) { barrierData.setKnockbackTargetFromKey(key); } + + // Melee data + public boolean isMeleeEnabled() { return barrierData.meleeEnabled; } + public void setMeleeEnabled(boolean enabled) { barrierData.meleeEnabled = enabled; } + public float getMeleeDamageMultiplier() { return barrierData.meleeDamageMultiplier; } + public void setMeleeDamageMultiplier(float mult) { barrierData.meleeDamageMultiplier = mult; } + // ==================== GUI ==================== @SideOnly(Side.CLIENT) @@ -196,29 +192,24 @@ public abstract class AbstractEnergyBarrierAbility extends Ability { .range(1, 6000).visibleWhen(this::isUseDuration)); defs.add(FieldDef.floatField("ability.defaultMultiplier", this::getDefaultMultiplier, this::setDefaultMultiplier)); - // Visual tab - defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual")); - defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor) - .tab("ability.tab.visual")); - defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled) - .tab("ability.tab.visual")); - defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor) - .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled)); + // Knockback section + defs.add(FieldDef.section("ability.section.knockback")); + defs.add(FieldDef.boolField("ability.knockbackEnabled", this::isKnockbackEnabled, this::setKnockbackEnabled)); defs.add(FieldDef.row( - FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth) - .visibleWhen(this::isOuterColorEnabled), - FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha) - .range(0, 1).visibleWhen(this::isOuterColorEnabled) - ).tab("ability.tab.visual")); + FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength) + .range(0, 10).visibleWhen(this::isKnockbackEnabled), + FieldDef.stringEnumField("ability.knockbackTarget", EnergyBarrierData.getKnockbackTargetKeys(), + this::getKnockbackTargetKey, this::setKnockbackTargetKey) + .visibleWhen(this::isKnockbackEnabled) + )); - defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual")); - defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect) - .tab("ability.tab.visual")); - defs.add(FieldDef.row( - FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity) - .visibleWhen(this::hasLightningEffect).range(0.01f, 100f), - FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius) - .range(0.1f, 100f).visibleWhen(this::hasLightningEffect) - ).tab("ability.tab.visual")); + // Melee section + defs.add(FieldDef.section("ability.section.melee")); + defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled)); + defs.add(FieldDef.floatField("ability.meleeDamageMultiplier", this::getMeleeDamageMultiplier, this::setMeleeDamageMultiplier) + .range(0, 10).visibleWhen(this::isMeleeEnabled)); + + // Visual tab - colors + effects (from AbilityEnergy) + addEnergyVisualDefinitions(defs); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyProjectileAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java similarity index 83% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyProjectileAbility.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index f3ee572c2..2a1957f3c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbstractEnergyProjectileAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -1,16 +1,14 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; 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.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.*; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -30,8 +28,8 @@ import java.util.List; * * @param The entity type this ability spawns */ -public abstract class AbstractEnergyProjectileAbility - extends Ability implements IAbilityEnergyProjectile { +public abstract class AbilityEnergyProjectile + extends AbilityEnergy implements IAbilityEnergyProjectile { protected static final int MAX_PROJECTILES = 8; @@ -39,9 +37,7 @@ public abstract class AbstractEnergyProjectileAbility implements IAbilityLaserShot { +public class AbilityLaserShot extends AbilityEnergyProjectile implements IAbilityLaserShot { private float laserWidth = 0.3f; private float expansionSpeed = 3.0f; @@ -73,7 +73,7 @@ public class AbilityLaserShot extends AbstractEnergyProjectileAbility implements IAbilityOrb { +public class AbilityOrb extends AbilityEnergyProjectile implements IAbilityOrb { private float orbSize = 1.0f; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java similarity index 84% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java index 9bcff686e..3cb407ef3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyShield.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -10,6 +10,7 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyPanel; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -24,35 +25,33 @@ import java.util.List; * The shield follows the caster's position and rotates with their look direction. * Cannot be placed — it's always held. */ -public class AbilityEnergyShield extends AbstractEnergyBarrierAbility { +public class AbilityShield extends AbilityEnergyBarrier { private float shieldWidth = 2.5f; private float shieldHeight = 2.5f; - public AbilityEnergyShield() { + public AbilityShield() { super( - new EnergyDisplayData(0xFFDD44, 0xFFAA00, true, 0.3f, 0.4f, 0.0f), + new EnergyDisplayData(0xFFDD44, 0xFFAA00, true, 0.3f, 0.4f, 0.5f, 0.0f), new EnergyBarrierData(60.0f, true, 120, true) ); - this.typeId = "ability.cnpc.energy_shield"; - this.name = "Energy Shield"; + this.typeId = "ability.cnpc.shield"; + this.name = "Shield"; this.targetingMode = TargetingMode.SELF; - this.maxRange = 0; - this.minRange = 0; this.cooldownTicks = 60; this.windUpTicks = 10; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; - this.windUpAnimationName = "Ability_EnergyShield_Windup"; - this.activeAnimationName = "Ability_EnergyShield_Active"; + this.windUpAnimationName = ""; + this.activeAnimationName = ""; this.allowedBy = UserType.BOTH; } // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { EnergyPanelData panelData = new EnergyPanelData(shieldWidth, shieldHeight, 0.0f); float frontDist = 1.5f; @@ -92,15 +91,15 @@ public class AbilityEnergyShield extends AbstractEnergyBarrierAbility { public List getVariants() { return Arrays.asList( new AbilityVariant("ability.variant.small", a -> { - AbilityEnergyShield shield = (AbilityEnergyShield) a; - a.setName("Small Energy Shield"); + AbilityShield shield = (AbilityShield) a; + a.setName("Small Shield"); shield.setShieldWidth(1.5f); shield.setShieldHeight(1.5f); shield.setBarrierMaxHealth(40.0f); }), new AbilityVariant("ability.variant.large", a -> { - AbilityEnergyShield shield = (AbilityEnergyShield) a; - a.setName("Large Energy Shield"); + AbilityShield shield = (AbilityShield) a; + a.setName("Large Shield"); shield.setShieldWidth(4.0f); shield.setShieldHeight(4.0f); shield.setBarrierMaxHealth(120.0f); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java similarity index 90% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java index 195311049..c79822f52 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -20,23 +20,23 @@ import java.util.List; /** * Energy Slicer ability: Fires a thin, wide blade projectile in a straight line. * JJK Cleave/Dismantle inspired. Piercing by default. - * Extends AbstractEnergyProjectileAbility to leverage the existing projectile infrastructure. + * Extends AbilityEnergyProjectile to leverage the existing projectile infrastructure. */ -public class AbilityEnergySlicer extends AbstractEnergyProjectileAbility { +public class AbilitySlicer extends AbilityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; private boolean piercing = true; - public AbilityEnergySlicer() { + public AbilitySlicer() { super( new EnergyDisplayData(0xFF4444, 0xFF0000, true, 0.2f, 0.6f, 0.0f), new EnergyCombatData(12.0f, 0.5f, 0.0f, false, 3.0f, 0.5f), new EnergyHomingData(0.8f, false, 0.0f, 0.0f), new EnergyLifespanData(40.0f, 100) ); - this.typeId = "ability.cnpc.energy_slicer"; - this.name = "Energy Slicer"; + this.typeId = "ability.cnpc.slicer"; + this.name = "Slicer"; this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 30.0f; this.minRange = 3.0f; @@ -45,8 +45,8 @@ public class AbilityEnergySlicer extends AbstractEnergyProjectileAbility getVariants() { return Arrays.asList( new AbilityVariant("ability.variant.single", a -> { - a.setName("Energy Slicer"); + a.setName("Slicer"); }), new AbilityVariant("ability.variant.dual", a -> { - AbilityEnergySlicer slicer = (AbilityEnergySlicer) a; + AbilitySlicer slicer = (AbilitySlicer) a; a.setName("Double Slicer"); slicer.setProjectileCount(2); slicer.setFireDelay(5); - a.setWindUpAnimationName("Ability_EnergySlicerDual_Windup"); - a.setActiveAnimationName("Ability_EnergySlicerDual_Active"); + a.setWindUpAnimationName(""); + a.setActiveAnimationName(""); }) ); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java similarity index 81% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index c7cc9a1fa..67f9643d3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -1,12 +1,10 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; 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.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import noppes.npcs.client.gui.builder.FieldDef; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -14,8 +12,8 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilitySweeper; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; - import noppes.npcs.api.ability.type.IAbilitySweeper; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.List; @@ -28,7 +26,7 @@ import java.util.List; * All damage logic is handled by the EntityAbilitySweeper entity to ensure * visual and damage hitbox alignment. */ -public class AbilitySweeper extends Ability implements IAbilitySweeper { +public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { // Type-specific parameters private float beamLength = 10.0f; @@ -41,13 +39,11 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { private int numberOfRotations = 2; // How many full rotations before ability ends private boolean lockOnTarget = false; - // Visual properties - private EnergyDisplayData colorData = new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.8f, 0.5f, 0.0f); - // Runtime state (transient) private transient EntityAbilitySweeper activeEntity = null; public AbilitySweeper() { + super(new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.8f, 0.5f, 0.0f)); this.typeId = "ability.cnpc.sweeper"; this.name = "Sweeper"; this.targetingMode = TargetingMode.AGGRO_TARGET; @@ -83,7 +79,7 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { // Player: target is null — sweep rotates around caster's facing direction, lockOnTarget has no effect. activeEntity = new EntityAbilitySweeper(caster.worldObj, caster, target, beamLength, beamWidth, beamHeight, - colorData, + displayData, sweepSpeed, numberOfRotations, damage, damageInterval, piercing, lockOnTarget); @@ -93,9 +89,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { } spawnAbilityEntity(activeEntity); - - // Ability stays active until entity dies (prevents firing another while projectile is alive) - // Movement locking is handled separately by the base class } @Override @@ -162,7 +155,7 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { nbt.setFloat("sweepSpeed", sweepSpeed); nbt.setInteger("numberOfRotations", numberOfRotations); nbt.setBoolean("lockOnTarget", lockOnTarget); - colorData.writeNBT(nbt); + writeEnergyNBT(nbt); } @Override @@ -176,10 +169,10 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { this.sweepSpeed = nbt.getFloat("sweepSpeed"); this.numberOfRotations = nbt.getInteger("numberOfRotations"); this.lockOnTarget = nbt.getBoolean("lockOnTarget"); - colorData.readNBT(nbt); + readEnergyNBT(nbt); } - // Getters & Setters + // Getters & Setters (type-specific only; color/lightning inherited from AbilityEnergy) public float getBeamLength() { return beamLength; } public void setBeamLength(float beamLength) { this.beamLength = beamLength; } public float getBeamWidth() { return beamWidth; } @@ -198,14 +191,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { public void setNumberOfRotations(int numberOfRotations) { this.numberOfRotations = numberOfRotations; } public boolean isLockOnTarget() { return lockOnTarget; } public void setLockOnTarget(boolean lockOnTarget) { this.lockOnTarget = lockOnTarget; } - public int getInnerColor() { return colorData.innerColor; } - public void setInnerColor(int innerColor) { colorData.innerColor = innerColor; } - public int getOuterColor() { return colorData.outerColor; } - public void setOuterColor(int outerColor) { colorData.outerColor = outerColor; } - public float getOuterColorWidth() { return colorData.outerColorWidth; } - public void setOuterColorWidth(float outerColorWidth) { colorData.outerColorWidth = outerColorWidth; } - public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; } - public void setOuterColorEnabled(boolean outerColorEnabled) { colorData.outerColorEnabled = outerColorEnabled; } @Override public int getMaxPreviewDuration() { @@ -216,7 +201,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { @Override public void getAbilityDefinitions(List defs) { defs.addAll(Arrays.asList( - FieldDef.row( FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage), FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval) @@ -237,18 +221,10 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper { FieldDef.boolField("ability.lockTarget", this::isLockOnTarget, this::setLockOnTarget) .hover("ability.hover.lockTarget") ), - AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects), - - // Visual tab - FieldDef.section("ability.section.colors").tab("ability.tab.visual"), - FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor) - .tab("ability.tab.visual"), - FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled) - .tab("ability.tab.visual"), - FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor) - .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled), - FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth) - .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled) + AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects) )); + + // Visual tab - colors + effects (from AbilityEnergy) + addEnergyVisualDefinitions(defs); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java similarity index 83% rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index 9007c1dba..f6bc037a4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.type; +package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -9,6 +9,7 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyPanel; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -23,18 +24,18 @@ import java.util.List; * Can float above ground (configurable height offset). * Optional launching mode fires the wall forward with damage/knockback. */ -public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { +public class AbilityWall extends AbilityEnergyBarrier { private final EnergyPanelData panelData; - public AbilityEnergyWall() { + public AbilityWall() { super( - new EnergyDisplayData(0x44FFCC, 0x22CCAA, true, 0.3f, 0.4f, 0.0f), + new EnergyDisplayData(0x44FFCC, 0x22CCAA, true, 0.3f, 0.4f, 0.5f, 0.0f), new EnergyBarrierData(80.0f, true, 160, true) ); this.panelData = new EnergyPanelData(4.0f, 3.0f, 0.0f); - this.typeId = "ability.cnpc.energy_wall"; - this.name = "Energy Wall"; + this.typeId = "ability.cnpc.wall"; + this.name = "Wall"; this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 15.0f; this.minRange = 2.0f; @@ -43,8 +44,8 @@ public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { this.lockMovement = LockMovementType.WINDUP; this.telegraphType = TelegraphType.LINE; this.showTelegraph = true; - this.windUpAnimationName = "Ability_EnergyWall_Windup"; - this.activeAnimationName = "Ability_EnergyWall_Active"; + this.windUpAnimationName = ""; + this.activeAnimationName = ""; } @Override @@ -55,7 +56,7 @@ public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { // ==================== ABSTRACT IMPLEMENTATIONS ==================== @Override - protected Entity createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { + protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) { // Place wall between caster and target double placeX, placeY, placeZ; float yaw; @@ -64,12 +65,21 @@ public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { double dx = target.posX - caster.posX; double dz = target.posZ - caster.posZ; double dist = Math.sqrt(dx * dx + dz * dz); - double placeDist = Math.min(dist * 0.5, 5.0); - placeX = caster.posX + (dx / dist) * placeDist; - placeY = caster.posY; - placeZ = caster.posZ + (dz / dist) * placeDist; - yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI); + if (dist < 0.01) { + // Target at same position — fall through to look-direction placement + float yawRad = (float) Math.toRadians(caster.rotationYaw); + placeX = caster.posX + (-Math.sin(yawRad) * 3.0); + placeY = caster.posY; + placeZ = caster.posZ + (Math.cos(yawRad) * 3.0); + yaw = caster.rotationYaw; + } else { + double placeDist = Math.min(dist * 0.5, 5.0); + placeX = caster.posX + (dx / dist) * placeDist; + placeY = caster.posY; + placeZ = caster.posZ + (dz / dist) * placeDist; + yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI); + } } else { float yawRad = (float) Math.toRadians(caster.rotationYaw); placeX = caster.posX + (-Math.sin(yawRad) * 3.0); @@ -105,7 +115,7 @@ public class AbilityEnergyWall extends AbstractEnergyBarrierAbility { a.setName("Static Wall"); }), new AbilityVariant("ability.variant.launched", a -> { - AbilityEnergyWall wall = (AbilityEnergyWall) a; + AbilityWall wall = (AbilityWall) a; a.setName("Launched Wall"); wall.panelData.launching = true; wall.panelData.launchSpeed = 0.6f; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index db57660e4..4b6b6e18d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -25,7 +25,7 @@ import java.util.List; * * Design inspired by LouisXIV's energy attack system. */ -public class EntityAbilityBeam extends EntityAbilityProjectile { +public class EntityAbilityBeam extends EntityEnergyProjectile { // Beam shape properties private float beamWidth = 0.3f; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index dd499f122..72ebe29ae 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -19,7 +19,7 @@ import java.util.List; * * Design inspired by LouisXIV's energy attack system. */ -public class EntityAbilityDisc extends EntityAbilityProjectile { +public class EntityAbilityDisc extends EntityEnergyProjectile { // Disc-specific movement propertie diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 5adabc4b6..391d9466c 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -21,7 +21,7 @@ import java.util.Set; * * Design inspired by LouisXIV's energy attack system. */ -public class EntityAbilityLaser extends EntityAbilityProjectile { +public class EntityAbilityLaser extends EntityEnergyProjectile { // Laser-specific properties private float laserWidth = 0.2f; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 2eb5c0084..61cbe7a0b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -15,11 +15,11 @@ import java.util.List; /** * Orb projectile - spherical homing energy ball. - * Extends EntityAbilityProjectile for shared functionality. + * Extends EntityEnergyAbility for shared functionality. * * Design inspired by LouisXIV's energy attack system. */ -public class EntityAbilityOrb extends EntityAbilityProjectile { +public class EntityAbilityOrb extends EntityEnergyProjectile { public EntityAbilityOrb(World world) { super(world); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index 6f0068d26..f8b0aaac1 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -1,9 +1,7 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -24,19 +22,16 @@ import java.util.Set; * Sweeper ability entity - a rotating beam attached to the NPC. * Unlike other projectiles, this handles its own damage logic since it's * attached to and rotates around the caster. + * Extends EntityEnergyAbility for shared visual/owner/charging state. * * Design: Similar to Beam trail visuals but rotating around origin. */ -public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpawnData { +public class EntityAbilitySweeper extends EntityEnergyAbility { // Beam properties private float beamLength = 10.0f; private float beamWidth = 0.3f; // Thin like beam trail private float beamHeight = 0.8f; - private int innerColor = 0xFF6600; - private int outerColor = 0xFF0000; - private boolean outerColorEnabled = true; - private float outerColorWidth = 1.8f; // Combat properties private float damage = 5.0f; @@ -52,18 +47,13 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa private float baseYaw = 0; // Starting yaw direction private boolean lockOnTarget = false; - // Owner tracking - private int ownerEntityId = -1; + // Target tracking private int targetEntityId = -1; // Lifetime private int maxTicks = 400; private long deathWorldTime = -1; - // Preview mode - private boolean previewMode = false; - private EntityLivingBase previewOwner = null; - // Damage state private transient int ticksSinceDamage = 0; private transient Set hitThisTick = new HashSet<>(); @@ -72,8 +62,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa super(world); this.setSize(0.1f, 0.1f); this.noClip = true; - this.isImmuneToFire = true; - this.ignoreFrustumCheck = true; } public EntityAbilitySweeper(World world, EntityLivingBase owner, EntityLivingBase target, @@ -89,10 +77,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa this.beamLength = beamLength; this.beamWidth = beamWidth; this.beamHeight = beamHeight; - this.innerColor = displayData.innerColor; - this.outerColor = displayData.outerColor; - this.outerColorEnabled = displayData.outerColorEnabled; - this.outerColorWidth = displayData.outerColorWidth; + this.displayData = displayData; this.sweepSpeed = sweepSpeed; this.numberOfRotations = numberOfRotations; this.damage = damage; @@ -121,7 +106,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa @Override protected void entityInit() { - // No DataWatcher needed + super.entityInit(); } @Override @@ -144,7 +129,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa return; } - Entity owner = getOwner(); + Entity owner = getOwnerEntity(); if (owner != null && owner.isDead) { this.setDead(); return; @@ -297,12 +282,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa this.previewOwner = owner; } - private Entity getOwner() { - if (previewMode && previewOwner != null) return previewOwner; - if (ownerEntityId == -1) return null; - return worldObj.getEntityByID(ownerEntityId); - } - // ==================== RENDERING ==================== @Override @@ -311,22 +290,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa return distance < 16384.0D; // 128 blocks squared } - @Override - public boolean shouldRenderInPass(int pass) { - return pass == 1; // Translucent pass - } - - @Override - public float getBrightness(float partialTicks) { - return 1.0f; - } - - @Override - @SideOnly(Side.CLIENT) - public int getBrightnessForRender(float partialTicks) { - return 0xF000F0; - } - // ==================== GETTERS FOR RENDERER ==================== public float getBeamLength() { @@ -341,22 +304,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa return beamHeight; } - public int getInnerColor() { - return innerColor; - } - - public int getOuterColor() { - return outerColor; - } - - public boolean isOuterColorEnabled() { - return outerColorEnabled; - } - - public float getOuterColorWidth() { - return outerColorWidth; - } - /** * Get the full interpolated angle INCLUDING base yaw for rendering. */ @@ -382,18 +329,14 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa @Override protected void readEntityFromNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); this.beamLength = nbt.getFloat("BeamLength"); this.beamWidth = nbt.getFloat("BeamWidth"); this.beamHeight = nbt.getFloat("BeamHeight"); - this.innerColor = nbt.getInteger("InnerColor"); - this.outerColor = nbt.getInteger("OuterColor"); - this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled"); - this.outerColorWidth = nbt.hasKey("OuterColorWidth") ? nbt.getFloat("OuterColorWidth") : 1.8f; this.sweepSpeed = nbt.getFloat("SweepSpeed"); this.numberOfRotations = nbt.getInteger("NumRotations"); this.completedRotations = nbt.getInteger("CompletedRotations"); this.maxTicks = nbt.getInteger("MaxTicks"); - this.ownerEntityId = nbt.getInteger("OwnerId"); this.targetEntityId = nbt.getInteger("TargetId"); this.currentAngle = nbt.getFloat("CurrentAngle"); this.baseYaw = nbt.getFloat("BaseYaw"); @@ -406,18 +349,14 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa @Override protected void writeEntityToNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); nbt.setFloat("BeamLength", beamLength); nbt.setFloat("BeamWidth", beamWidth); nbt.setFloat("BeamHeight", beamHeight); - nbt.setInteger("InnerColor", innerColor); - nbt.setInteger("OuterColor", outerColor); - nbt.setBoolean("OuterColorEnabled", outerColorEnabled); - nbt.setFloat("OuterColorWidth", outerColorWidth); nbt.setFloat("SweepSpeed", sweepSpeed); nbt.setInteger("NumRotations", numberOfRotations); nbt.setInteger("CompletedRotations", completedRotations); nbt.setInteger("MaxTicks", maxTicks); - nbt.setInteger("OwnerId", ownerEntityId); nbt.setInteger("TargetId", targetEntityId); nbt.setFloat("CurrentAngle", currentAngle); nbt.setFloat("BaseYaw", baseYaw); @@ -427,53 +366,4 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa nbt.setBoolean("Piercing", piercing); nbt.setBoolean("LockOnTarget", lockOnTarget); } - - // ==================== SPAWN DATA ==================== - - @Override - public void writeSpawnData(ByteBuf buffer) { - buffer.writeFloat(beamLength); - buffer.writeFloat(beamWidth); - buffer.writeFloat(beamHeight); - buffer.writeInt(innerColor); - buffer.writeInt(outerColor); - buffer.writeBoolean(outerColorEnabled); - buffer.writeFloat(outerColorWidth); - buffer.writeFloat(sweepSpeed); - buffer.writeInt(numberOfRotations); - buffer.writeInt(completedRotations); - buffer.writeInt(maxTicks); - buffer.writeInt(ownerEntityId); - buffer.writeInt(targetEntityId); - buffer.writeFloat(currentAngle); - buffer.writeFloat(baseYaw); - buffer.writeFloat(damage); - buffer.writeInt(damageInterval); - buffer.writeBoolean(piercing); - buffer.writeBoolean(lockOnTarget); - } - - @Override - public void readSpawnData(ByteBuf buffer) { - this.beamLength = buffer.readFloat(); - this.beamWidth = buffer.readFloat(); - this.beamHeight = buffer.readFloat(); - this.innerColor = buffer.readInt(); - this.outerColor = buffer.readInt(); - this.outerColorEnabled = buffer.readBoolean(); - this.outerColorWidth = buffer.readFloat(); - this.sweepSpeed = buffer.readFloat(); - this.numberOfRotations = buffer.readInt(); - this.completedRotations = buffer.readInt(); - this.maxTicks = buffer.readInt(); - this.ownerEntityId = buffer.readInt(); - this.targetEntityId = buffer.readInt(); - this.currentAngle = buffer.readFloat(); - this.baseYaw = buffer.readFloat(); - this.damage = buffer.readFloat(); - this.damageInterval = buffer.readInt(); - this.piercing = buffer.readBoolean(); - this.lockOnTarget = buffer.readBoolean(); - this.prevAngle = this.currentAngle; - } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java new file mode 100644 index 000000000..d7322e90e --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -0,0 +1,241 @@ +package kamkeel.npcs.entity; + +import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +/** + * Universal base class for all energy ability entities (Projectiles, Barriers, Sweeper). + * Provides shared visual data, owner tracking, charging state, and common entity properties. + */ +public abstract class EntityEnergyAbility extends Entity implements IEntityAdditionalSpawnData { + + // ==================== VISUAL PROPERTIES ==================== + protected EnergyDisplayData displayData = new EnergyDisplayData(); + protected EnergyLightningData lightningData = new EnergyLightningData(); + + // Client-side lightning state (not saved to NBT) + @SideOnly(Side.CLIENT) + public transient Object lightningState; // Actually AttachedLightningRenderer.LightningState + + // ==================== TRACKING ==================== + protected int ownerEntityId = -1; + + // ==================== STATE ==================== + /** The ability that spawned this entity. Transient, not saved to NBT. */ + protected transient Ability sourceAbility = null; + protected boolean previewMode = false; + protected EntityLivingBase previewOwner = null; + + // ==================== CHARGING STATE ==================== + protected boolean charging = false; + protected int chargeDuration = 0; + protected int chargeTick = 0; + protected static final int DW_CHARGING = 20; + + public EntityEnergyAbility(World world) { + super(world); + this.isImmuneToFire = true; + this.ignoreFrustumCheck = true; + } + + @Override + protected void entityInit() { + this.dataWatcher.addObject(DW_CHARGING, (byte) 0); + } + + // ==================== CHARGING ==================== + + /** + * Check if entity 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). + */ + protected void setCharging(boolean value) { + this.charging = value; + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0)); + } + } + + 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; + } + + // ==================== OWNER ==================== + + /** + * Get the owner entity. In preview mode, returns the preview owner directly. + */ + public Entity getOwnerEntity() { + if (previewMode && previewOwner != null) { + return previewOwner; + } + if (ownerEntityId == -1) return null; + return worldObj.getEntityByID(ownerEntityId); + } + + public int getOwnerEntityId() { return ownerEntityId; } + public void setOwnerEntityId(int id) { this.ownerEntityId = id; } + + // ==================== SOURCE ABILITY ==================== + + public Ability getSourceAbility() { return sourceAbility; } + public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } + + // ==================== PREVIEW MODE ==================== + + public boolean isPreviewMode() { return previewMode; } + public void setPreviewMode(boolean preview) { this.previewMode = preview; } + public void setPreviewOwner(EntityLivingBase owner) { this.previewOwner = owner; } + + // ==================== DISPLAY GETTERS & SETTERS ==================== + + public EnergyDisplayData getDisplayData() { return displayData; } + public EnergyLightningData getLightningData() { return lightningData; } + + public int getInnerColor() { return displayData.getInnerColor(); } + public void setInnerColor(int color) { displayData.setInnerColor(color); } + + public float getInnerAlpha() { return displayData.getInnerAlpha(); } + public void setInnerAlpha(float alpha) { displayData.setInnerAlpha(alpha); } + + public int getOuterColor() { return displayData.getOuterColor(); } + public void setOuterColor(int color) { displayData.setOuterColor(color); } + + public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); } + public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); } + + public float getOuterColorWidth() { return displayData.getOuterColorWidth(); } + public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); } + + public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); } + public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); } + + public float getRotationSpeed() { return displayData.getRotationSpeed(); } + public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); } + + // ==================== LIGHTNING GETTERS & SETTERS ==================== + + public boolean hasLightningEffect() { return lightningData.isLightningEffect(); } + public void setLightningEffect(boolean enabled) { lightningData.setLightningEffect(enabled); } + + public float getLightningDensity() { return lightningData.getLightningDensity(); } + public void setLightningDensity(float density) { lightningData.setLightningDensity(density); } + + public float getLightningRadius() { return lightningData.getLightningRadius(); } + public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); } + + public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); } + public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); } + + // ==================== BRIGHTNESS ==================== + + @Override + public float getBrightness(float partialTicks) { + return 1.0f; + } + + @Override + @SideOnly(Side.CLIENT) + public int getBrightnessForRender(float partialTicks) { + return 0xF000F0; + } + + @Override + public boolean shouldRenderInPass(int pass) { + return pass == 1; // Translucent pass + } + + // ==================== COLLISION SETTINGS ==================== + + @Override + public boolean canBePushed() { + return false; + } + + @Override + protected boolean canTriggerWalking() { + return false; + } + + @Override + public boolean isBurning() { + return false; + } + + @SideOnly(Side.CLIENT) + public float getShadowSize() { + return 0.0f; + } + + // ==================== NBT HELPERS ==================== + + /** + * Write shared energy base fields to NBT (owner, display, lightning). + * Call from subclass NBT writers before writing type-specific fields. + */ + protected void writeEnergyBaseNBT(NBTTagCompound nbt) { + nbt.setInteger("OwnerId", ownerEntityId); + displayData.writeNBT(nbt); + lightningData.writeNBT(nbt); + } + + /** + * Read shared energy base fields from NBT (owner, display, lightning). + * Call from subclass NBT readers before reading type-specific fields. + */ + protected void readEnergyBaseNBT(NBTTagCompound nbt) { + this.ownerEntityId = nbt.getInteger("OwnerId"); + displayData.readNBT(nbt); + lightningData.readNBT(nbt); + } + + // ==================== SPAWN DATA ==================== + + @Override + public void writeSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = new NBTTagCompound(); + this.writeEntityToNBT(compound); + cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error writing energy ability spawn data", e); + } + } + + @Override + public void readSpawnData(ByteBuf buffer) { + try { + NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); + if (compound != null) { + this.readEntityFromNBT(compound); + } + } catch (Exception e) { + noppes.npcs.LogWriter.error("Error reading energy ability spawn data", e); + } + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java new file mode 100644 index 000000000..c295f586d --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -0,0 +1,269 @@ +package kamkeel.npcs.entity; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +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.DamageSource; +import net.minecraft.world.World; +import noppes.npcs.EventHooks; +import noppes.npcs.entity.EntityNPCInterface; + +/** + * Base class for energy barrier entities (Dome, Panel). + * Provides shared health, damage, hit flash, knockback target, + * and barrier lifecycle management. + * Extends EntityEnergyAbility for shared visual/owner/charging state. + */ +public abstract class EntityEnergyBarrier extends EntityEnergyAbility { + + // ==================== BARRIER PROPERTIES ==================== + protected EnergyBarrierData barrierData = new EnergyBarrierData(); + protected float currentHealth; + protected int ticksAlive = 0; + + // ==================== DATA WATCHER INDICES ==================== + protected static final int DW_HEALTH_PERCENT = 21; + protected static final int DW_HIT_FLASH = 22; + + public EntityEnergyBarrier(World world) { + super(world); + this.noClip = true; + } + + @Override + protected void entityInit() { + super.entityInit(); // DW_CHARGING = 20 + this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f); + this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0); + } + + // ==================== ABSTRACT METHODS ==================== + + /** + * Check if a projectile is incoming toward this barrier. + * Dome uses spherical check, Panel uses planar check. + */ + public abstract boolean isIncomingProjectile(EntityEnergyProjectile projectile); + + /** + * Setup charging state. Called during windup phase. + */ + public abstract void setupCharging(int duration); + + /** + * Finish charging — snap to full size and become active. + */ + public abstract void finishCharging(); + + /** + * Push entities away from the barrier surface. + */ + protected abstract void knockbackEntities(); + + // ==================== PROJECTILE HIT ==================== + + /** + * Apply damage to this barrier from a projectile. + * Returns true if the barrier absorbed the hit (projectile should be destroyed). + */ + public boolean onProjectileHit(EntityEnergyProjectile projectile, float baseDamage) { + // Get damage multiplier for this projectile type + String typeId = ""; + if (projectile.getSourceAbility() != null) { + typeId = projectile.getSourceAbility().getTypeId(); + } + float multiplier = barrierData.getMultiplier(typeId); + float damage = baseDamage * multiplier; + + // Fire hit event (may cancel or modify damage) + if (!worldObj.isRemote) { + float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage); + if (eventDamage < 0) return false; // Event cancelled — don't block + damage = eventDamage; + } + + triggerHitFlash(); + + if (!barrierData.useHealth) { + return true; // Duration-only mode: block but don't take damage + } + + currentHealth -= damage; + syncHealthPercent(); + + if (currentHealth <= 0) { + onBarrierDestroyed(); + this.setDead(); + } + + return true; + } + + // ==================== MELEE DAMAGE ==================== + + @Override + public boolean canBeCollidedWith() { + return barrierData.meleeEnabled; + } + + @Override + public boolean attackEntityFrom(DamageSource source, float amount) { + if (worldObj.isRemote || !barrierData.meleeEnabled) return false; + if (isCharging()) return false; + + float damage = amount * barrierData.meleeDamageMultiplier; + + // Fire hit event with null projectile (melee hit) + float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, damage); + if (eventDamage < 0) return false; + damage = eventDamage; + + triggerHitFlash(); + + if (!barrierData.useHealth) return true; + + currentHealth -= damage; + syncHealthPercent(); + + if (currentHealth <= 0) { + onBarrierDestroyed(); + this.setDead(); + } + return true; + } + + // ==================== SHARED UPDATE LOGIC ==================== + + /** + * Shared barrier tick logic for onUpdate(). Handles spawned event, owner death, + * duration check, hit flash decay, and tick event. + * Call this after incrementing ticksAlive in subclass onUpdate(). + * @return true if barrier died this tick (caller should return early) + */ + protected boolean updateBarrierTick() { + if (!worldObj.isRemote) { + // Fire spawned event on first tick + if (ticksAlive == 1) { + EventHooks.onEnergyBarrierSpawned(this, ownerEntityId); + } + + // Check owner death + if (ownerEntityId >= 0 && ticksAlive > 5) { + Entity owner = worldObj.getEntityByID(ownerEntityId); + if (owner != null) { + if (owner.isDead) { + onBarrierDestroyed(); + this.setDead(); + return true; + } + if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) { + onBarrierDestroyed(); + this.setDead(); + return true; + } + } + } + + // Duration check + if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) { + onBarrierDestroyed(); + this.setDead(); + return true; + } + + // Reset hit flash + if (getHitFlash() > 0) { + setHitFlash((byte) (getHitFlash() - 1)); + } + + // Fire tick event + EventHooks.onEnergyBarrierTick(this, ownerEntityId); + } + return false; + } + + // ==================== HELPERS ==================== + + protected void onBarrierDestroyed() { + EventHooks.onEnergyBarrierDestroyed(this, ownerEntityId); + } + + protected void syncHealthPercent() { + float percent = barrierData.useHealth && barrierData.maxHealth > 0 + ? Math.max(0, currentHealth / barrierData.maxHealth) + : 1.0f; + if (!worldObj.isRemote) { + this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent); + } + } + + protected void triggerHitFlash() { + if (!worldObj.isRemote) { + setHitFlash((byte) 4); + } + } + + protected void setHitFlash(byte value) { + this.dataWatcher.updateObject(DW_HIT_FLASH, value); + } + + public byte getHitFlash() { + return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH); + } + + public float getHealthPercent() { + return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); + } + + protected boolean isKnockbackTarget(EntityLivingBase entity) { + switch (barrierData.knockbackTarget) { + case 1: return entity instanceof EntityPlayer; + case 2: return entity instanceof EntityNPCInterface; + default: return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface; + } + } + + // ==================== GETTERS ==================== + + public EnergyBarrierData getBarrierData() { return barrierData; } + public float getCurrentHealth() { return currentHealth; } + public void setCurrentHealth(float health) { this.currentHealth = health; syncHealthPercent(); } + public int getTicksAlive() { return ticksAlive; } + + // ==================== NBT HELPERS ==================== + + /** + * Write shared barrier base fields to NBT. + * Call from subclass writeEntityToNBT() before writing type-specific fields. + */ + protected void writeBarrierBaseNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + nbt.setInteger("TicksAlive", ticksAlive); + nbt.setFloat("CurrentHealth", currentHealth); + nbt.setBoolean("Charging", charging); + nbt.setInteger("ChargeTick", chargeTick); + nbt.setInteger("ChargeDuration", chargeDuration); + barrierData.writeNBT(nbt); + } + + /** + * Read shared barrier base fields from NBT. + * Call from subclass readEntityFromNBT() before reading type-specific fields. + */ + protected void readBarrierBaseNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + this.ticksAlive = nbt.getInteger("TicksAlive"); + this.currentHealth = nbt.getFloat("CurrentHealth"); + this.charging = nbt.hasKey("Charging") && nbt.getBoolean("Charging"); + this.chargeTick = nbt.getInteger("ChargeTick"); + this.chargeDuration = nbt.getInteger("ChargeDuration"); + this.dataWatcher.updateObject(DW_CHARGING, (byte) (charging ? 1 : 0)); + barrierData.readNBT(nbt); + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 851de3687..d0a2a8a96 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -1,10 +1,7 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; @@ -13,7 +10,6 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; import java.util.List; @@ -21,39 +17,18 @@ import java.util.List; * Energy Dome entity - a spherical barrier that blocks incoming energy projectiles. * Centered on the caster's position at time of casting. * Only blocks incoming attacks (not outgoing from allies inside). + * Extends EntityEnergyBarrier for shared barrier logic. */ -public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnData { +public class EntityEnergyDome extends EntityEnergyBarrier { - // ==================== VISUAL PROPERTIES ==================== - protected EnergyDisplayData displayData = new EnergyDisplayData(); - protected EnergyLightningData lightningData = new EnergyLightningData(); - - // ==================== BARRIER PROPERTIES ==================== - protected EnergyBarrierData barrierData = new EnergyBarrierData(); - protected float currentHealth; + // ==================== DOME-SPECIFIC PROPERTIES ==================== protected float domeRadius = 5.0f; - - // ==================== TRACKING ==================== - protected int ownerEntityId = -1; - protected int ticksAlive = 0; - - // ==================== STATE ==================== - protected transient Ability sourceAbility = null; - - // ==================== DATA WATCHER INDICES ==================== - private static final int DW_HEALTH_PERCENT = 20; - private static final int DW_HIT_FLASH = 21; - - // ==================== CLIENT STATE ==================== - @SideOnly(Side.CLIENT) - public transient Object lightningState; + protected float targetDomeRadius = 5.0f; + protected boolean followCaster = false; public EntityEnergyDome(World world) { super(world); this.setSize(1.0f, 1.0f); // Hitbox doesn't really matter, we use radius checks - this.noClip = true; - this.isImmuneToFire = true; - this.ignoreFrustumCheck = true; } public EntityEnergyDome(World world, EntityLivingBase owner, double x, double y, double z, @@ -69,12 +44,6 @@ public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnDa this.currentHealth = barrier.maxHealth; } - @Override - protected void entityInit() { - this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f); - this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0); - } - @Override public void onUpdate() { this.prevPosX = this.posX; @@ -82,76 +51,51 @@ public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnDa this.prevPosZ = this.posZ; super.onUpdate(); + + // Follow caster: both sides for smooth interpolated rendering + if (followCaster) { + Entity owner = ownerEntityId >= 0 ? worldObj.getEntityByID(ownerEntityId) : null; + if (owner != null) { + this.setPosition(owner.posX, owner.posY, owner.posZ); + } + } + + // Handle charging animation (both sides for smooth rendering) + if (isCharging()) { + chargeTick++; + float progress = getChargeProgress(); + this.domeRadius = targetDomeRadius * progress; + return; // Don't tick duration/death during charging + } + ticksAlive++; - if (!worldObj.isRemote) { - // Check owner death - if (ownerEntityId >= 0 && ticksAlive > 5) { - Entity owner = worldObj.getEntityByID(ownerEntityId); - if (owner != null) { - if (owner.isDead) { this.setDead(); return; } - if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) { - this.setDead(); return; - } - } - } + if (updateBarrierTick()) return; - // Duration check - if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) { - this.setDead(); - return; - } - - // Reset hit flash - if (getHitFlash() > 0) { - setHitFlash((byte) (getHitFlash() - 1)); - } + // Knockback entities inside the dome + if (!worldObj.isRemote && barrierData.knockbackEnabled) { + knockbackEntities(); } } - /** - * Apply damage to this dome from a projectile. - * Returns true if the dome absorbed the hit (projectile should be destroyed). - */ - public boolean onProjectileHit(EntityAbilityProjectile projectile, float baseDamage) { - if (!barrierData.useHealth) { - // Duration-only mode: block but don't take damage - triggerHitFlash(); - return true; - } - - // Get damage multiplier for this projectile type - String typeId = ""; - if (projectile.getSourceAbility() != null) { - typeId = projectile.getSourceAbility().getTypeId(); - } - float multiplier = barrierData.getMultiplier(typeId); - float damage = baseDamage * multiplier; - - currentHealth -= damage; - syncHealthPercent(); - triggerHitFlash(); - - if (currentHealth <= 0) { - this.setDead(); - } - - return true; - } + // ==================== INCOMING CHECK ==================== /** * Check if a projectile at the given position is entering this dome from outside. * Only blocks incoming projectiles (dot product check). */ - public boolean isIncomingProjectile(EntityAbilityProjectile projectile) { + @Override + public boolean isIncomingProjectile(EntityEnergyProjectile projectile) { + // Don't block during charging phase + if (isCharging()) return false; // Don't block projectiles from the dome's owner if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; // Don't block projectiles from same-faction NPCs Entity owner = getOwnerEntity(); Entity projOwner = projectile.getOwnerEntity(); - if (owner instanceof EntityNPCInterface && projOwner instanceof EntityNPCInterface) { - if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) projOwner).faction.id) { + if (owner instanceof noppes.npcs.entity.EntityNPCInterface && projOwner instanceof noppes.npcs.entity.EntityNPCInterface) { + if (((noppes.npcs.entity.EntityNPCInterface) owner).faction.id == ((noppes.npcs.entity.EntityNPCInterface) projOwner).faction.id) { return false; } } @@ -170,74 +114,77 @@ public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnDa return dot < 0; } - // ==================== HELPERS ==================== + // ==================== CHARGING ==================== - private void syncHealthPercent() { - float percent = barrierData.useHealth && barrierData.maxHealth > 0 - ? Math.max(0, currentHealth / barrierData.maxHealth) - : 1.0f; - if (!worldObj.isRemote) { - this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent); + @Override + public void setupCharging(int duration) { + this.targetDomeRadius = this.domeRadius; + this.domeRadius = 0.01f; + this.chargeDuration = duration; + this.chargeTick = 0; + setCharging(true); + } + + @Override + public void finishCharging() { + this.domeRadius = targetDomeRadius; + setCharging(false); + } + + // ==================== KNOCKBACK ==================== + + /** + * Push entities away from the dome surface. + * Entities outside are pushed outward, entities inside are pushed inward (containment). + */ + @Override + @SuppressWarnings("unchecked") + protected void knockbackEntities() { + float margin = 1.0f; + AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( + posX - domeRadius - margin, posY - domeRadius - margin, posZ - domeRadius - margin, + posX + domeRadius + margin, posY + domeRadius + margin, posZ + domeRadius + margin + ); + + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); + for (EntityLivingBase ent : entities) { + if (ent.getEntityId() == ownerEntityId) continue; + if (!isKnockbackTarget(ent)) continue; + + double dx = ent.posX - posX; + double dy = (ent.posY + ent.height * 0.5) - posY; + double dz = ent.posZ - posZ; + double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + + if (dist < 0.01) continue; + + // Only affect entities near the dome surface + if (dist < domeRadius + margin && dist > domeRadius - margin) { + double pushStrength = barrierData.knockbackStrength * 0.5; + double pushDir = dist >= domeRadius ? 1.0 : -1.0; // Outside = outward, Inside = inward + ent.addVelocity( + (dx / dist) * pushStrength * pushDir, + 0.05 * pushDir, + (dz / dist) * pushStrength * pushDir + ); + ent.velocityChanged = true; + } } } - private void triggerHitFlash() { - if (!worldObj.isRemote) { - setHitFlash((byte) 4); + // ==================== BOUNDING BOX ==================== + + @Override + public AxisAlignedBB getBoundingBox() { + if (barrierData.meleeEnabled) { + return AxisAlignedBB.getBoundingBox( + posX - domeRadius, posY - domeRadius, posZ - domeRadius, + posX + domeRadius, posY + domeRadius, posZ + domeRadius + ); } + return null; } - private void setHitFlash(byte value) { - this.dataWatcher.updateObject(DW_HIT_FLASH, value); - } - - public byte getHitFlash() { - return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH); - } - - public float getHealthPercent() { - return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); - } - - public Entity getOwnerEntity() { - if (ownerEntityId == -1) return null; - return worldObj.getEntityByID(ownerEntityId); - } - - public int getOwnerEntityId() { return ownerEntityId; } - public float getDomeRadius() { return domeRadius; } - public EnergyDisplayData getDisplayData() { return displayData; } - public EnergyLightningData getLightningData() { return lightningData; } - public EnergyBarrierData getBarrierData() { return barrierData; } - public float getCurrentHealth() { return currentHealth; } - - public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } - public Ability getSourceAbility() { return sourceAbility; } - - // ==================== VISUAL GETTERS ==================== - - public int getInnerColor() { return displayData.innerColor; } - public int getOuterColor() { return displayData.outerColor; } - public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } - public float getOuterColorWidth() { return displayData.outerColorWidth; } - public float getOuterColorAlpha() { return displayData.outerColorAlpha; } - public boolean hasLightningEffect() { return lightningData.lightningEffect; } - public float getLightningDensity() { return lightningData.lightningDensity; } - public float getLightningRadius() { return lightningData.lightningRadius; } - public int getLightningFadeTime() { return lightningData.lightningFadeTime; } - - // ==================== BRIGHTNESS ==================== - - @Override - public float getBrightness(float partialTicks) { return 1.0f; } - - @Override - @SideOnly(Side.CLIENT) - public int getBrightnessForRender(float partialTicks) { return 0xF000F0; } - - @Override - public boolean shouldRenderInPass(int pass) { return pass == 1; } - @Override @SideOnly(Side.CLIENT) public boolean isInRangeToRenderDist(double distance) { @@ -246,69 +193,28 @@ public class EntityEnergyDome extends Entity implements IEntityAdditionalSpawnDa return distance < d * d; } - // ==================== COLLISION SETTINGS ==================== + // ==================== GETTERS ==================== - @Override - public boolean canBeCollidedWith() { return false; } - - @Override - public boolean canBePushed() { return false; } - - @Override - protected boolean canTriggerWalking() { return false; } - - @Override - public boolean isBurning() { return false; } - - @SideOnly(Side.CLIENT) - public float getShadowSize() { return 0.0f; } + public float getDomeRadius() { return domeRadius; } + public void setDomeRadius(float radius) { this.domeRadius = Math.max(0.1f, radius); } + public boolean isFollowCaster() { return followCaster; } + public void setFollowCaster(boolean follow) { this.followCaster = follow; } // ==================== NBT ==================== @Override protected void readEntityFromNBT(NBTTagCompound nbt) { + readBarrierBaseNBT(nbt); this.domeRadius = nbt.getFloat("DomeRadius"); - this.ownerEntityId = nbt.getInteger("OwnerId"); - this.ticksAlive = nbt.getInteger("TicksAlive"); - this.currentHealth = nbt.getFloat("CurrentHealth"); - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - barrierData.readNBT(nbt); + this.targetDomeRadius = nbt.hasKey("TargetDomeRadius") ? nbt.getFloat("TargetDomeRadius") : domeRadius; + this.followCaster = nbt.hasKey("FollowCaster") && nbt.getBoolean("FollowCaster"); } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { + writeBarrierBaseNBT(nbt); nbt.setFloat("DomeRadius", domeRadius); - nbt.setInteger("OwnerId", ownerEntityId); - nbt.setInteger("TicksAlive", ticksAlive); - nbt.setFloat("CurrentHealth", currentHealth); - displayData.writeNBT(nbt); - lightningData.writeNBT(nbt); - barrierData.writeNBT(nbt); - } - - // ==================== SPAWN DATA ==================== - - @Override - public void writeSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = new NBTTagCompound(); - this.writeEntityToNBT(compound); - cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error writing energy dome spawn data", e); - } - } - - @Override - public void readSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); - if (compound != null) { - this.readEntityFromNBT(compound); - } - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error reading energy dome spawn data", e); - } + nbt.setFloat("TargetDomeRadius", targetDomeRadius); + nbt.setBoolean("FollowCaster", followCaster); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index e28e5d53d..909e0dda8 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -1,31 +1,29 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; 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.world.World; import noppes.npcs.NpcDamageSource; import noppes.npcs.entity.EntityNPCInterface; -import net.minecraft.entity.player.EntityPlayer; import java.util.List; /** * Energy Panel entity - a flat rectangular barrier used by Wall and Shield abilities. * Supports three modes: PLACED (stationary), HELD (tracks caster), LAUNCHED (moves forward). + * Extends EntityEnergyBarrier for shared barrier logic. */ -public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnData { +public class EntityEnergyPanel extends EntityEnergyBarrier { public enum PanelMode { PLACED, // Stationary wall @@ -33,40 +31,18 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD LAUNCHED // Moves forward, deals damage/knockback on contact } - // ==================== VISUAL PROPERTIES ==================== - protected EnergyDisplayData displayData = new EnergyDisplayData(); - protected EnergyLightningData lightningData = new EnergyLightningData(); - - // ==================== BARRIER PROPERTIES ==================== - protected EnergyBarrierData barrierData = new EnergyBarrierData(); + // ==================== PANEL-SPECIFIC PROPERTIES ==================== protected EnergyPanelData panelData = new EnergyPanelData(); - protected float currentHealth; - - // ==================== PANEL STATE ==================== protected PanelMode mode = PanelMode.PLACED; protected float panelYaw = 0.0f; // Rotation of the panel face (degrees) - // ==================== TRACKING ==================== - protected int ownerEntityId = -1; - protected int ticksAlive = 0; - - // ==================== STATE ==================== - protected transient Ability sourceAbility = null; - - // ==================== DATA WATCHER INDICES ==================== - private static final int DW_HEALTH_PERCENT = 20; - private static final int DW_HIT_FLASH = 21; - - // ==================== CLIENT STATE ==================== - @SideOnly(Side.CLIENT) - public transient Object lightningState; + // ==================== CHARGING (panel-specific targets) ==================== + protected float targetPanelWidth; + protected float targetPanelHeight; public EntityEnergyPanel(World world) { super(world); this.setSize(0.5f, 0.5f); - this.noClip = true; - this.isImmuneToFire = true; - this.ignoreFrustumCheck = true; } public EntityEnergyPanel(World world, EntityLivingBase owner, double x, double y, double z, @@ -93,12 +69,6 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD } } - @Override - protected void entityInit() { - this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f); - this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0); - } - @Override public void onUpdate() { this.prevPosX = this.posX; @@ -106,30 +76,28 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD this.prevPosZ = this.posZ; super.onUpdate(); + + // Handle charging animation (both sides for smooth rendering) + if (isCharging()) { + chargeTick++; + float progress = getChargeProgress(); + panelData.panelWidth = targetPanelWidth * progress; + panelData.panelHeight = targetPanelHeight * progress; + + // During charging, held panels still track owner + if (mode == PanelMode.HELD) { + updateHeld(); + } + return; // Don't tick duration/death during charging + } + ticksAlive++; - if (!worldObj.isRemote) { - // Check owner death - if (ownerEntityId >= 0 && ticksAlive > 5) { - Entity owner = worldObj.getEntityByID(ownerEntityId); - if (owner != null) { - if (owner.isDead) { this.setDead(); return; } - if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) { - this.setDead(); return; - } - } - } + if (updateBarrierTick()) return; - // Duration check - if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) { - this.setDead(); - return; - } - - // Reset hit flash - if (getHitFlash() > 0) { - setHitFlash((byte) (getHitFlash() - 1)); - } + // Knockback (not during launched mode) + if (!worldObj.isRemote && barrierData.knockbackEnabled && mode != PanelMode.LAUNCHED) { + knockbackEntities(); } // Mode-specific updates @@ -151,108 +119,90 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD Entity owner = getOwnerEntity(); if (owner == null) return; + // Update yaw BEFORE position calc to avoid 1-tick lag + this.panelYaw = owner.rotationYaw; + // Follow owner position float frontDist = 1.5f; - float yawRad = (float) Math.toRadians(owner.rotationYaw); + float yawRad = (float) Math.toRadians(panelYaw); double newX = owner.posX + (-Math.sin(yawRad) * frontDist); double newY = owner.posY + panelData.heightOffset + (owner.height * 0.5f); double newZ = owner.posZ + (Math.cos(yawRad) * frontDist); this.setPosition(newX, newY, newZ); - this.panelYaw = owner.rotationYaw; } private void updateLaunched() { - if (worldObj.isRemote) return; - - // Move forward + // Move forward on both sides for smooth client rendering this.posX += motionX; this.posY += motionY; this.posZ += motionZ; this.setPosition(posX, posY, posZ); - // Check entity collision for damage - float halfW = panelData.panelWidth * 0.5f; - float halfH = panelData.panelHeight * 0.5f; - AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( - posX - halfW, posY - halfH, posZ - halfW, - posX + halfW, posY + halfH, posZ + halfW - ); + // Damage and knockback are server-only + if (!worldObj.isRemote) { + // Check entity collision for damage + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float halfD = 0.5f; // Panel collision depth + float searchRadius = Math.max(halfW, halfD); + AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( + posX - searchRadius, posY - halfH, posZ - searchRadius, + posX + searchRadius, posY + halfH, posZ + searchRadius + ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - Entity owner = getOwnerEntity(); + @SuppressWarnings("unchecked") + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); + Entity owner = getOwnerEntity(); - for (EntityLivingBase target : entities) { - if (target == owner) continue; - if (owner instanceof EntityNPCInterface && target instanceof EntityNPCInterface) { - if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) target).faction.id) continue; - } + for (EntityLivingBase target : entities) { + if (target == owner) continue; + if (owner instanceof EntityNPCInterface && target instanceof EntityNPCInterface) { + if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) target).faction.id) continue; + } - // Apply damage - if (panelData.launchDamage > 0) { - if (owner instanceof EntityNPCInterface) { - target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); - } else if (owner instanceof EntityPlayer) { - target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); + // Apply damage + if (panelData.launchDamage > 0) { + if (owner instanceof EntityNPCInterface) { + target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); + } else if (owner instanceof EntityPlayer) { + target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); + } + } + + // Apply knockback + if (panelData.launchKnockback > 0) { + double dx = target.posX - posX; + double dz = target.posZ - posZ; + double len = Math.sqrt(dx * dx + dz * dz); + if (len > 0) { + target.addVelocity( + (dx / len) * panelData.launchKnockback * 0.5, + 0.1, + (dz / len) * panelData.launchKnockback * 0.5 + ); + target.velocityChanged = true; + } } } - // Apply knockback - if (panelData.launchKnockback > 0) { - double dx = target.posX - posX; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dz * dz); - if (len > 0) { - target.addVelocity( - (dx / len) * panelData.launchKnockback * 0.5, - 0.1, - (dz / len) * panelData.launchKnockback * 0.5 - ); - target.velocityChanged = true; - } + // Max lifetime check + if (ticksAlive > 200) { + this.setDead(); } } - - // Max distance check (30 blocks) - double distSq = (posX - prevPosX) * (posX - prevPosX) + (posZ - prevPosZ) * (posZ - prevPosZ); - if (ticksAlive > 200) { - this.setDead(); - } } - /** - * Apply damage to this panel from a projectile. - */ - public boolean onProjectileHit(EntityAbilityProjectile projectile, float baseDamage) { - if (!barrierData.useHealth) { - triggerHitFlash(); - return true; - } - - String typeId = ""; - if (projectile.getSourceAbility() != null) { - typeId = projectile.getSourceAbility().getTypeId(); - } - float multiplier = barrierData.getMultiplier(typeId); - float damage = baseDamage * multiplier; - - currentHealth -= damage; - syncHealthPercent(); - triggerHitFlash(); - - if (currentHealth <= 0) { - this.setDead(); - } - - return true; - } + // ==================== INCOMING CHECK ==================== /** * Check if a projectile hits this panel surface. * Only blocks incoming projectiles (not from the panel's owner). */ - public boolean isIncomingProjectile(EntityAbilityProjectile projectile) { + @Override + public boolean isIncomingProjectile(EntityEnergyProjectile projectile) { + // Don't block during charging phase + if (isCharging()) return false; if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; // Faction check @@ -292,85 +242,97 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD double dot = projectile.motionX * normalX + projectile.motionZ * normalZ; // Determine which side of the panel the projectile is on - // If on the front (localForward > 0), it must be moving backward (dot < 0) to be incoming - // If on the back (localForward < 0), it must be moving forward (dot > 0) to be incoming if (localForward > 0 && dot >= 0) return false; // Moving away from front side if (localForward < 0 && dot <= 0) return false; // Moving away from back side return true; } - // ==================== HELPERS ==================== + // ==================== CHARGING ==================== - private void syncHealthPercent() { - float percent = barrierData.useHealth && barrierData.maxHealth > 0 - ? Math.max(0, currentHealth / barrierData.maxHealth) - : 1.0f; - if (!worldObj.isRemote) { - this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent); + @Override + public void setupCharging(int duration) { + this.targetPanelWidth = panelData.panelWidth; + this.targetPanelHeight = panelData.panelHeight; + panelData.panelWidth = 0.01f; + panelData.panelHeight = 0.01f; + this.chargeDuration = duration; + this.chargeTick = 0; + setCharging(true); + } + + @Override + public void finishCharging() { + panelData.panelWidth = targetPanelWidth; + panelData.panelHeight = targetPanelHeight; + setCharging(false); + } + + // ==================== KNOCKBACK ==================== + + /** + * Push entities away from the panel surface. + */ + @Override + @SuppressWarnings("unchecked") + protected void knockbackEntities() { + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float margin = 1.0f; + + AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( + posX - halfW - margin, posY - halfH - margin, posZ - halfW - margin, + posX + halfW + margin, posY + halfH + margin, posZ + halfW + margin + ); + + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); + float yawRad = (float) Math.toRadians(panelYaw); + float normalX = -(float) Math.sin(yawRad); + float normalZ = (float) Math.cos(yawRad); + + for (EntityLivingBase ent : entities) { + if (ent.getEntityId() == ownerEntityId) continue; + if (!isKnockbackTarget(ent)) continue; + + // Transform entity position to panel-local space + double dx = ent.posX - posX; + double dy = (ent.posY + ent.height * 0.5) - posY; + double dz = ent.posZ - posZ; + + // Check proximity to panel surface + float localForward = (float) (dx * normalX + dz * normalZ); + float localRight = (float) (dx * (-normalZ) + dz * normalX); + + if (Math.abs(localForward) < margin && Math.abs(localRight) < halfW + margin && Math.abs(dy) < halfH + margin) { + // Push entity away from the panel face + float pushDir = localForward >= 0 ? 1.0f : -1.0f; + double pushStrength = barrierData.knockbackStrength * 0.5; + ent.addVelocity( + normalX * pushDir * pushStrength, + 0.1, + normalZ * pushDir * pushStrength + ); + ent.velocityChanged = true; + } } } - private void triggerHitFlash() { - if (!worldObj.isRemote) { - setHitFlash((byte) 4); + // ==================== BOUNDING BOX ==================== + + @Override + public AxisAlignedBB getBoundingBox() { + if (barrierData.meleeEnabled) { + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float extent = Math.max(halfW, 0.5f); + return AxisAlignedBB.getBoundingBox( + posX - extent, posY - halfH, posZ - extent, + posX + extent, posY + halfH, posZ + extent + ); } + return null; } - private void setHitFlash(byte value) { - this.dataWatcher.updateObject(DW_HIT_FLASH, value); - } - - public byte getHitFlash() { - return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH); - } - - public float getHealthPercent() { - return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT); - } - - public Entity getOwnerEntity() { - if (ownerEntityId == -1) return null; - return worldObj.getEntityByID(ownerEntityId); - } - - // ==================== GETTERS ==================== - - public int getOwnerEntityId() { return ownerEntityId; } - public PanelMode getMode() { return mode; } - public float getPanelYaw() { return panelYaw; } - public EnergyDisplayData getDisplayData() { return displayData; } - public EnergyLightningData getLightningData() { return lightningData; } - public EnergyBarrierData getBarrierData() { return barrierData; } - public EnergyPanelData getPanelData() { return panelData; } - public float getCurrentHealth() { return currentHealth; } - - public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } - public Ability getSourceAbility() { return sourceAbility; } - - // Visual getters - public int getInnerColor() { return displayData.innerColor; } - public int getOuterColor() { return displayData.outerColor; } - public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } - public float getOuterColorWidth() { return displayData.outerColorWidth; } - public float getOuterColorAlpha() { return displayData.outerColorAlpha; } - public boolean hasLightningEffect() { return lightningData.lightningEffect; } - public float getLightningDensity() { return lightningData.lightningDensity; } - public float getLightningRadius() { return lightningData.lightningRadius; } - public int getLightningFadeTime() { return lightningData.lightningFadeTime; } - - // ==================== BRIGHTNESS ==================== - - @Override - public float getBrightness(float partialTicks) { return 1.0f; } - - @Override - @SideOnly(Side.CLIENT) - public int getBrightnessForRender(float partialTicks) { return 0xF000F0; } - - @Override - public boolean shouldRenderInPass(int pass) { return pass == 1; } - @Override @SideOnly(Side.CLIENT) public boolean isInRangeToRenderDist(double distance) { @@ -379,73 +341,33 @@ public class EntityEnergyPanel extends Entity implements IEntityAdditionalSpawnD return distance < d * d; } - // ==================== COLLISION SETTINGS ==================== + // ==================== GETTERS ==================== - @Override - public boolean canBeCollidedWith() { return false; } - - @Override - public boolean canBePushed() { return false; } - - @Override - protected boolean canTriggerWalking() { return false; } - - @Override - public boolean isBurning() { return false; } - - @SideOnly(Side.CLIENT) - public float getShadowSize() { return 0.0f; } + public PanelMode getMode() { return mode; } + public float getPanelYaw() { return panelYaw; } + public EnergyPanelData getPanelData() { return panelData; } // ==================== NBT ==================== @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - this.ownerEntityId = nbt.getInteger("OwnerId"); - this.ticksAlive = nbt.getInteger("TicksAlive"); - this.currentHealth = nbt.getFloat("CurrentHealth"); + readBarrierBaseNBT(nbt); this.panelYaw = nbt.getFloat("PanelYaw"); - this.mode = PanelMode.values()[nbt.getInteger("PanelMode")]; - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - barrierData.readNBT(nbt); + int modeOrdinal = nbt.getInteger("PanelMode"); + this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length) + ? PanelMode.values()[modeOrdinal] : PanelMode.PLACED; panelData.readNBT(nbt); + this.targetPanelWidth = nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth; + this.targetPanelHeight = nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight; } @Override protected void writeEntityToNBT(NBTTagCompound nbt) { - nbt.setInteger("OwnerId", ownerEntityId); - nbt.setInteger("TicksAlive", ticksAlive); - nbt.setFloat("CurrentHealth", currentHealth); + writeBarrierBaseNBT(nbt); nbt.setFloat("PanelYaw", panelYaw); nbt.setInteger("PanelMode", mode.ordinal()); - displayData.writeNBT(nbt); - lightningData.writeNBT(nbt); - barrierData.writeNBT(nbt); + nbt.setFloat("TargetPanelWidth", targetPanelWidth); + nbt.setFloat("TargetPanelHeight", targetPanelHeight); panelData.writeNBT(nbt); } - - // ==================== SPAWN DATA ==================== - - @Override - public void writeSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = new NBTTagCompound(); - this.writeEntityToNBT(compound); - cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error writing energy panel spawn data", e); - } - } - - @Override - public void readSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); - if (compound != null) { - this.readEntityFromNBT(compound); - } - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error reading energy panel spawn data", e); - } - } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java similarity index 76% rename from src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java rename to src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 630bf9c84..32ffcec5a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -1,14 +1,10 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityEffect; import kamkeel.npcs.controllers.data.ability.AnchorPoint; - import kamkeel.npcs.controllers.data.ability.data.*; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; @@ -35,20 +31,21 @@ import java.util.List; import java.util.Map; /** - * Base class for all ability projectiles (Orb, Disc, Beam, Laser). - * Provides common functionality for visuals, combat, effects, and interpolation. + * Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer). + * Provides common functionality for combat, effects, homing, and interpolation. + * Extends EntityEnergyAbility for shared visual/owner/charging state. * * Design inspired by LouisXIV's energy attack system. */ -public abstract class EntityAbilityProjectile extends Entity implements IEntityAdditionalSpawnData { +public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== ACTIVE PROJECTILE TRACKING ==================== - private static final Map>> activeProjectiles = new HashMap<>(); + private static final Map>> activeProjectiles = new HashMap<>(); - public static void trackProjectile(EntityAbilityProjectile projectile) { + public static void trackProjectile(EntityEnergyProjectile projectile) { int ownerId = projectile.getOwnerEntityId(); if (ownerId < 0) return; - List> refs = activeProjectiles.get(ownerId); + List> refs = activeProjectiles.get(ownerId); if (refs == null) { refs = new ArrayList<>(); activeProjectiles.put(ownerId, refs); @@ -56,13 +53,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA refs.add(new WeakReference<>(projectile)); } - public static List getActiveProjectiles(int ownerEntityId) { - List> refs = activeProjectiles.get(ownerEntityId); + public static List getActiveProjectiles(int ownerEntityId) { + List> refs = activeProjectiles.get(ownerEntityId); if (refs == null) return Collections.emptyList(); - List result = new ArrayList<>(); - Iterator> it = refs.iterator(); + List result = new ArrayList<>(); + Iterator> it = refs.iterator(); while (it.hasNext()) { - EntityAbilityProjectile p = it.next().get(); + EntityEnergyProjectile p = it.next().get(); if (p == null || p.isDead) { it.remove(); } else { @@ -82,16 +79,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA // ==================== EFFECT PROPERTIES ==================== protected List effects = new ArrayList<>(); - // ==================== LIGHTNING EFFECT PROPERTIES ==================== - protected EnergyLightningData lightningData = new EnergyLightningData(); - - // Client-side lightning state (not saved to NBT) - @cpw.mods.fml.relauncher.SideOnly(cpw.mods.fml.relauncher.Side.CLIENT) - public transient Object lightningState; // Actually AttachedLightningRenderer.LightningState - - // ==================== DISPLAY PROPERTIES ==================== - protected EnergyDisplayData displayData = new EnergyDisplayData(); - // ==================== ANCHOR PROPERTIES ==================== protected EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.FRONT); @@ -108,23 +95,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA // ==================== TRACKING ==================== protected double startX, startY, startZ; - protected int ownerEntityId = -1; protected int targetEntityId = -1; // ==================== STATE ==================== protected boolean hasHit = false; - protected boolean previewMode = false; // Client-side preview mode (no damage/effects) - protected EntityLivingBase previewOwner = null; // Direct reference for GUI preview (no world lookup) - /** The ability that spawned this projectile. Transient, not saved to NBT. */ - protected transient Ability sourceAbility = null; - - // ==================== CHARGING STATE ==================== - protected boolean charging = false; - protected int chargeDuration = 40; - protected int chargeTick = 0; + // ==================== CHARGING STATE (projectile-specific) ==================== protected float targetSize = 1.0f; - protected static final int DW_CHARGING = 20; // ==================== ROTATION INTERPOLATION ==================== public float prevRotationValX, prevRotationValY, prevRotationValZ; @@ -139,12 +116,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA protected double interpTargetMotionX, interpTargetMotionY, interpTargetMotionZ; protected int interpSteps; - public EntityAbilityProjectile(World world) { + public EntityEnergyProjectile(World world) { super(world); this.setSize(0.5f, 0.5f); this.noClip = false; - this.isImmuneToFire = true; - this.ignoreFrustumCheck = true; } /** @@ -177,11 +152,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.prevRenderSize = size; } - @Override - protected void entityInit() { - this.dataWatcher.addObject(DW_CHARGING, (byte) 0); - } - @Override @SideOnly(Side.CLIENT) public boolean isInRangeToRenderDist(double distance) { @@ -190,11 +160,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA return distance < d1 * d1; } - @Override - public boolean shouldRenderInPass(int pass) { - return pass == 1; // Translucent pass - } - @Override public void onUpdate() { // Store previous values for interpolation @@ -320,39 +285,24 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA // ==================== BARRIER COLLISION ==================== /** - * Check for collision with energy barrier entities (Dome, Panel). + * Check for collision with energy barrier entities. * Barriers only block incoming projectiles from enemies. * @return true if projectile was absorbed (caller should stop processing) */ protected boolean checkBarrierCollision() { - // Search area for barriers - check a generous range - double searchRange = 10.0; + // Search area for barriers - must cover large domes (up to 30 radius) + double searchRange = 32.0; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( posX - searchRange, posY - searchRange, posZ - searchRange, posX + searchRange, posY + searchRange, posZ + searchRange ); - // Check domes @SuppressWarnings("unchecked") - List domes = worldObj.getEntitiesWithinAABB(EntityEnergyDome.class, searchBox); - for (EntityEnergyDome dome : domes) { - if (dome.isDead) continue; - if (dome.isIncomingProjectile(this)) { - if (dome.onProjectileHit(this, getDamage())) { - hasHit = true; - this.setDead(); - return true; - } - } - } - - // Check panels - @SuppressWarnings("unchecked") - List panels = worldObj.getEntitiesWithinAABB(EntityEnergyPanel.class, searchBox); - for (EntityEnergyPanel panel : panels) { - if (panel.isDead) continue; - if (panel.isIncomingProjectile(this)) { - if (panel.onProjectileHit(this, getDamage())) { + List barriers = worldObj.getEntitiesWithinAABB(EntityEnergyBarrier.class, searchBox); + for (EntityEnergyBarrier barrier : barriers) { + if (barrier.isDead) continue; + if (barrier.isIncomingProjectile(this)) { + if (barrier.onProjectileHit(this, getDamage())) { hasHit = true; this.setDead(); return true; @@ -406,23 +356,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA } } - // ==================== PREVIEW MODE ==================== - - /** - * Set preview mode for GUI display. - * In preview mode, no damage or world effects are applied. - */ - public void setPreviewMode(boolean preview) { - this.previewMode = preview; - } - - /** - * Check if entity is in preview mode. - */ - public boolean isPreviewMode() { - return previewMode; - } - // ==================== DAMAGE & EFFECTS ==================== protected void applyDamage(EntityLivingBase target) { @@ -495,21 +428,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.effects = effects != null ? effects : new ArrayList<>(); } - /** - * Set the source ability that spawned this projectile. - * Used by external damage handlers to access ability-specific data. - */ - public void setSourceAbility(Ability ability) { - this.sourceAbility = ability; - } - - /** - * Get the source ability that spawned this projectile. - */ - public Ability getSourceAbility() { - return sourceAbility; - } - protected void doExplosion() { if (previewMode) return; // Skip explosion in preview mode worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); @@ -566,38 +484,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA prevPosZ = newZ; } - // ==================== CHARGING METHODS ==================== - - /** - * Check if entity 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). - */ - protected void setCharging(boolean value) { - this.charging = value; - if (!worldObj.isRemote) { - this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0)); - } - } - - 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; - } + // ==================== CHARGING METHODS (projectile-specific) ==================== /** * Setup this entity in charging mode (for windup phase). @@ -730,37 +617,12 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA // ==================== ENTITY HELPERS ==================== - public Entity getOwnerEntity() { - // In preview mode, use direct reference (no world lookup) - if (previewMode && previewOwner != null) { - return previewOwner; - } - if (ownerEntityId == -1) return null; - return worldObj.getEntityByID(ownerEntityId); - } - - public int getOwnerEntityId() { - return ownerEntityId; - } - - public void setOwnerEntityId(int id) { - this.ownerEntityId = id; - } - public IEntity getOwner() { if (previewMode) return null; if (getOwnerEntity() == null) return null; return NpcAPI.Instance().getIEntity(getOwnerEntity()); } - /** - * Set the preview owner for GUI preview mode. - * This allows anchor point calculations without world entity lookup. - */ - public void setPreviewOwner(EntityLivingBase owner) { - this.previewOwner = owner; - } - public int getTargetEntityId() { return targetEntityId; } @@ -794,8 +656,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA } // Ignore other projectiles from the same caster (e.g. multi-projectile abilities) - if (entity instanceof EntityAbilityProjectile) { - EntityAbilityProjectile other = (EntityAbilityProjectile) entity; + if (entity instanceof EntityEnergyProjectile) { + EntityEnergyProjectile other = (EntityEnergyProjectile) entity; if (other.ownerEntityId == this.ownerEntityId) return true; } @@ -821,24 +683,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.startZ = z; } - public int getInnerColor() { return displayData.getInnerColor(); } - public void setInnerColor(int color) { displayData.setInnerColor(color); } - - public int getOuterColor() { return displayData.getOuterColor(); } - public void setOuterColor(int color) { displayData.setOuterColor(color); } - - public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); } - public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); } - - public float getOuterColorWidth() { return displayData.getOuterColorWidth(); } - public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); } - - public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); } - public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); } - - public float getRotationSpeed() { return displayData.getRotationSpeed(); } - public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); } - // ==================== ROTATION GETTERS ==================== public float getInterpolatedRotationX(float partialTicks) { @@ -857,20 +701,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA return this.prevRenderSize + (this.renderCurrentSize - this.prevRenderSize) * partialTicks; } - // ==================== LIGHTNING GETTERS & SETTERS ==================== - - public boolean hasLightningEffect() { return lightningData.isLightningEffect(); } - public void setLightningEffect(boolean enabled) { lightningData.setLightningEffect(enabled); } - - public float getLightningDensity() { return lightningData.getLightningDensity(); } - public void setLightningDensity(float density) { lightningData.setLightningDensity(density); } - - public float getLightningRadius() { return lightningData.getLightningRadius(); } - public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); } - - public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); } - public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); } - // ==================== LIFESPAN GETTERS & SETTERS ==================== public float getMaxDistance() { return lifespanData.getMaxDistance(); } @@ -932,19 +762,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA public boolean getHasHit() { return hasHit; } - // ==================== BRIGHTNESS ==================== - - @Override - public float getBrightness(float partialTicks) { - return 1.0f; - } - - @Override - @SideOnly(Side.CLIENT) - public int getBrightnessForRender(float partialTicks) { - return 0xF000F0; - } - // ==================== NBT ==================== @Override @@ -963,6 +780,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA * Read base projectile properties from NBT. */ protected void readBaseNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + this.size = nbt.getFloat("Size"); // Read effects list @@ -980,7 +799,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.startY = nbt.getDouble("StartY"); this.startZ = nbt.getDouble("StartZ"); - this.ownerEntityId = nbt.getInteger("OwnerId"); this.targetEntityId = nbt.getInteger("TargetId"); this.motionX = nbt.getDouble("MotionX"); @@ -994,10 +812,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA anchorData.readNBT(nbt); combatData.readNBT(nbt); - displayData.readNBT(nbt); homingData.readNBT(nbt); lifespanData.readNBT(nbt); - lightningData.readNBT(nbt); trajectoryData.readNBT(nbt); } @@ -1005,6 +821,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA * Write base projectile properties to NBT. */ protected void writeBaseNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + nbt.setFloat("Size", size); // Write effects list @@ -1022,7 +840,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA nbt.setDouble("StartY", startY); nbt.setDouble("StartZ", startZ); - nbt.setInteger("OwnerId", ownerEntityId); nbt.setInteger("TargetId", targetEntityId); nbt.setDouble("MotionX", motionX); @@ -1031,10 +848,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA anchorData.writeNBT(nbt); combatData.writeNBT(nbt); - displayData.writeNBT(nbt); homingData.writeNBT(nbt); lifespanData.writeNBT(nbt); - lightningData.writeNBT(nbt); trajectoryData.writeNBT(nbt); } @@ -1048,55 +863,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA */ protected abstract void writeProjectileNBT(NBTTagCompound nbt); - // ==================== SPAWN DATA ==================== - - @Override - public void writeSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = new NBTTagCompound(); - this.writeEntityToNBT(compound); - cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound); - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error writing ability projectile spawn data", e); - } - } - - @Override - public void readSpawnData(ByteBuf buffer) { - try { - NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer); - if (compound != null) { - this.readEntityFromNBT(compound); - } - } catch (Exception e) { - noppes.npcs.LogWriter.error("Error reading ability projectile spawn data", e); - } - } - // ==================== COLLISION SETTINGS ==================== @Override public boolean canBeCollidedWith() { return false; } - - @Override - public boolean canBePushed() { - return false; - } - - @Override - protected boolean canTriggerWalking() { - return false; - } - - @Override - public boolean isBurning() { - return false; - } - - @SideOnly(Side.CLIENT) - public float getShadowSize() { - return 0.0f; - } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 5fccfe2e4..c76fc7bad 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -16,9 +16,9 @@ import java.util.List; /** * Energy Slicer entity - a thin, wide blade projectile that flies in a straight line. * JJK Cleave/Dismantle inspired. Piercing by default (passes through entities). - * Extends EntityAbilityProjectile to leverage the existing projectile infrastructure. + * Extends EntityEnergyAbility to leverage the existing projectile infrastructure. */ -public class EntityEnergySlicer extends EntityAbilityProjectile { +public class EntityEnergySlicer extends EntityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; @@ -50,17 +50,6 @@ public class EntityEnergySlicer extends EntityAbilityProjectile { calculateInitialVelocity(owner, target, x, y, z); } - /** - * Setup in charging mode (windup phase). - */ - public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float width, float thickness) { - this.sliceWidth = 0.01f; - this.sliceThickness = 0.01f; - super.setupCharging(anchor, chargeDuration); - // Store target dimensions - this.targetSize = width; // Re-use base field for width - } - // Targets stored during charging for grow animation private float targetSliceWidth = 3.0f; private float targetSliceThickness = 0.15f; diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index 75b615671..2fefb23ac 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -1,7 +1,7 @@ package noppes.npcs; import cpw.mods.fml.common.eventhandler.Event; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import kamkeel.npcs.network.packets.data.AchievementPacket; import net.minecraft.command.ICommandSender; import net.minecraft.entity.Entity; @@ -455,7 +455,7 @@ public class EventHooks { // ==================== ENERGY PROJECTILE EVENTS ==================== - public static void onEnergyProjectileFired(EntityAbilityProjectile projectile) { + public static void onEnergyProjectileFired(EntityEnergyProjectile projectile) { IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile); EnergyProjectileEvent.FiredEvent event = new EnergyProjectileEvent.FiredEvent(wrapped); ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_FIRED, event); @@ -473,7 +473,7 @@ public class EventHooks { NpcAPI.EVENT_BUS.post(event); } - public static void onEnergyProjectileTick(EntityAbilityProjectile projectile) { + public static void onEnergyProjectileTick(EntityEnergyProjectile projectile) { IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile); EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped); ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_TICK, event); @@ -494,7 +494,7 @@ public class EventHooks { /** * Fires the entity impact event. Returns the (possibly modified) damage, or -1 if cancelled. */ - public static float onEnergyProjectileEntityImpact(EntityAbilityProjectile projectile, EntityLivingBase target, float damage) { + public static float onEnergyProjectileEntityImpact(EntityEnergyProjectile projectile, EntityLivingBase target, float damage) { IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile); IEntity wrappedTarget = NpcAPI.Instance().getIEntity(target); EnergyProjectileEvent.EntityImpactEvent event = new EnergyProjectileEvent.EntityImpactEvent(wrapped, wrappedTarget, damage); @@ -518,7 +518,7 @@ public class EventHooks { return event.getDamage(); } - public static void onEnergyProjectileBlockImpact(EntityAbilityProjectile projectile, int blockX, int blockY, int blockZ) { + public static void onEnergyProjectileBlockImpact(EntityEnergyProjectile projectile, int blockX, int blockY, int blockZ) { IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile); IPos pos = NpcAPI.Instance().getIPos(blockX, blockY, blockZ); IWorld world = NpcAPI.Instance().getIWorld(projectile.worldObj); @@ -539,7 +539,7 @@ public class EventHooks { NpcAPI.EVENT_BUS.post(event); } - public static void onEnergyProjectileExpired(EntityAbilityProjectile projectile) { + public static void onEnergyProjectileExpired(EntityEnergyProjectile projectile) { IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile); EnergyProjectileEvent.ExpiredEvent event = new EnergyProjectileEvent.ExpiredEvent(wrapped); ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_EXPIRED, event); @@ -557,6 +557,54 @@ public class EventHooks { NpcAPI.EVENT_BUS.post(event); } + // ==================== ENERGY BARRIER EVENTS ==================== + + private static void dispatchBarrierEvent(net.minecraft.entity.Entity barrierEntity, int ownerEntityId, + noppes.npcs.scripted.event.EnergyBarrierEvent event, EnumScriptType type) { + ScriptController.Instance.globalNpcScripts.callScript(type, event); + + net.minecraft.entity.Entity owner = barrierEntity.worldObj.getEntityByID(ownerEntityId); + if (owner instanceof EntityNPCInterface) { + ((EntityNPCInterface) owner).script.callScript(type, event); + } else if (owner instanceof EntityPlayer) { + PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) owner); + if (handler != null) { + handler.callScript(type, event); + } + } + + NpcAPI.EVENT_BUS.post(event); + } + + public static void onEnergyBarrierSpawned(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) { + noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity); + noppes.npcs.scripted.event.EnergyBarrierEvent.SpawnedEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.SpawnedEvent(wrapped); + dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_SPAWNED); + } + + public static void onEnergyBarrierTick(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) { + noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity); + noppes.npcs.scripted.event.EnergyBarrierEvent.UpdateEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.UpdateEvent(wrapped); + dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_TICK); + } + + public static float onEnergyBarrierHit(net.minecraft.entity.Entity barrierEntity, int ownerEntityId, + EntityEnergyProjectile projectile, float damage) { + noppes.npcs.api.entity.IEnergyBarrier wrappedBarrier = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity); + noppes.npcs.api.entity.IEnergyProjectile wrappedProjectile = projectile != null + ? (noppes.npcs.api.entity.IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile) : null; + noppes.npcs.scripted.event.EnergyBarrierEvent.HitEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.HitEvent(wrappedBarrier, wrappedProjectile, damage); + dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_HIT); + if (event.isCanceled()) return -1; + return event.getDamage(); + } + + public static void onEnergyBarrierDestroyed(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) { + noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity); + noppes.npcs.scripted.event.EnergyBarrierEvent.DestroyedEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.DestroyedEvent(wrapped); + dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_DESTROYED); + } + public static void onPlayerInit(PlayerDataScript handler, IPlayer player) { PlayerEvent.InitEvent event = new PlayerEvent.InitEvent(player); handler.callScript(EnumScriptType.INIT, event); diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index b1f123956..1ef3d52e5 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -284,12 +284,12 @@ public class GuiFieldBuilder { String btnText = def.getButtonLabel(); int btnW; if (def.hasClearAction()) { - btnW = contentRight - 22 - fieldX; - GuiNpcButton clearBtn = new GuiNpcButton(clearId, contentRight - 20, y, 20, 20, "X"); + btnW = fieldW - 22; + GuiNpcButton clearBtn = new GuiNpcButton(clearId, fieldX + fieldW - 20, y, 20, 20, "X"); sw.addButton(clearBtn); clearFieldMap.put(clearId, def); } else { - btnW = contentRight - fieldX; + btnW = fieldW; } GuiNpcButton btn = new GuiNpcButton(widgetId, fieldX, y, btnW, 20, btnText); Integer textColor = def.getButtonTextColor(); diff --git a/src/main/java/noppes/npcs/constants/EnumScriptType.java b/src/main/java/noppes/npcs/constants/EnumScriptType.java index da4e8f136..9e2588103 100644 --- a/src/main/java/noppes/npcs/constants/EnumScriptType.java +++ b/src/main/java/noppes/npcs/constants/EnumScriptType.java @@ -131,7 +131,13 @@ public enum EnumScriptType { ENERGY_PROJECTILE_TICK("energyProjectileTick"), ENERGY_PROJECTILE_ENTITY_IMPACT("energyProjectileEntityImpact"), ENERGY_PROJECTILE_BLOCK_IMPACT("energyProjectileBlockImpact"), - ENERGY_PROJECTILE_EXPIRED("energyProjectileExpired"); + ENERGY_PROJECTILE_EXPIRED("energyProjectileExpired"), + + // Energy Barrier Events + ENERGY_BARRIER_SPAWNED("energyBarrierSpawned"), + ENERGY_BARRIER_TICK("energyBarrierTick"), + ENERGY_BARRIER_HIT("energyBarrierHit"), + ENERGY_BARRIER_DESTROYED("energyBarrierDestroyed"); public String function; diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index 24f271f33..02ec0f4b3 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -11,6 +11,7 @@ import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.block.Block; import net.minecraft.command.CommandHandler; import net.minecraft.entity.Entity; @@ -127,6 +128,7 @@ import noppes.npcs.scripted.entity.ScriptEnergyBeam; import noppes.npcs.scripted.entity.ScriptEnergyDisc; import noppes.npcs.scripted.entity.ScriptEnergyLaser; import noppes.npcs.scripted.entity.ScriptEnergyOrb; +import noppes.npcs.scripted.entity.ScriptEnergyProjectile; import noppes.npcs.scripted.entity.ScriptEntity; import noppes.npcs.scripted.entity.ScriptEntityItem; import noppes.npcs.scripted.entity.ScriptFishHook; @@ -470,6 +472,14 @@ public class NpcAPI extends AbstractNpcAPI { data = new ScriptEntityData(new ScriptEnergyDisc<>((EntityAbilityDisc) entity)); else if (entity instanceof EntityAbilityLaser) data = new ScriptEntityData(new ScriptEnergyLaser<>((EntityAbilityLaser) entity)); + else if (entity instanceof kamkeel.npcs.entity.EntityEnergySlicer) + data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergySlicer<>((kamkeel.npcs.entity.EntityEnergySlicer) entity)); + else if (entity instanceof EntityEnergyProjectile) + data = new ScriptEntityData(new ScriptEnergyProjectile((EntityEnergyProjectile) entity)); + else if (entity instanceof kamkeel.npcs.entity.EntityEnergyDome) + data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyDome<>((kamkeel.npcs.entity.EntityEnergyDome) entity)); + else if (entity instanceof kamkeel.npcs.entity.EntityEnergyPanel) + data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyPanel<>((kamkeel.npcs.entity.EntityEnergyPanel) entity)); else data = new ScriptEntityData(new ScriptEntity<>(entity)); entity.registerExtendedProperties("ScriptedObject", data); diff --git a/src/main/java/noppes/npcs/scripted/constants/EntityType.java b/src/main/java/noppes/npcs/scripted/constants/EntityType.java index 355173646..62cd482fa 100644 --- a/src/main/java/noppes/npcs/scripted/constants/EntityType.java +++ b/src/main/java/noppes/npcs/scripted/constants/EntityType.java @@ -19,4 +19,8 @@ public class EntityType { public static final int ENERGY_BEAM = 15; public static final int ENERGY_DISC = 16; public static final int ENERGY_LASER = 17; + public static final int ENERGY_SLICER = 18; + public static final int ENERGY_BARRIER = 19; + public static final int ENERGY_DOME = 20; + public static final int ENERGY_PANEL = 21; } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java new file mode 100644 index 000000000..b84c2bf4b --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java @@ -0,0 +1,68 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergyAbility; +import net.minecraft.entity.Entity; +import noppes.npcs.api.entity.IEnergyAbility; +import noppes.npcs.api.entity.IEntity; +import noppes.npcs.scripted.NpcAPI; + +/** + * Base script wrapper for all energy ability entities. + * Implements shared display, lightning, owner, and charging methods. + */ +public abstract class ScriptEnergyAbility extends ScriptEntity implements IEnergyAbility { + + public ScriptEnergyAbility(T entity) { + super(entity); + } + + // ==================== OWNER ==================== + + public int getOwnerEntityId() { + return entity.getOwnerEntityId(); + } + + public IEntity getOwner() { + Entity owner = entity.getOwnerEntity(); + return owner != null ? NpcAPI.Instance().getIEntity(owner) : null; + } + + // ==================== DISPLAY ==================== + + public int getInnerColor() { return entity.getInnerColor(); } + public void setInnerColor(int color) { entity.setInnerColor(color); } + + public float getInnerAlpha() { return entity.getInnerAlpha(); } + public void setInnerAlpha(float alpha) { entity.setInnerAlpha(alpha); } + + public int getOuterColor() { return entity.getOuterColor(); } + public void setOuterColor(int color) { entity.setOuterColor(color); } + + public boolean isOuterColorEnabled() { return entity.isOuterColorEnabled(); } + public void setOuterColorEnabled(boolean enabled) { entity.setOuterColorEnabled(enabled); } + + public float getOuterColorWidth() { return entity.getOuterColorWidth(); } + public void setOuterColorWidth(float width) { entity.setOuterColorWidth(width); } + + public float getOuterColorAlpha() { return entity.getOuterColorAlpha(); } + public void setOuterColorAlpha(float alpha) { entity.setOuterColorAlpha(alpha); } + + // ==================== LIGHTNING ==================== + + public boolean hasLightningEffect() { return entity.hasLightningEffect(); } + public void setLightningEffect(boolean enabled) { entity.setLightningEffect(enabled); } + + public float getLightningDensity() { return entity.getLightningDensity(); } + public void setLightningDensity(float density) { entity.setLightningDensity(density); } + + public float getLightningRadius() { return entity.getLightningRadius(); } + public void setLightningRadius(float radius) { entity.setLightningRadius(radius); } + + public int getLightningFadeTime() { return entity.getLightningFadeTime(); } + public void setLightningFadeTime(int ticks) { entity.setLightningFadeTime(ticks); } + + // ==================== STATE ==================== + + public boolean isCharging() { return entity.isCharging(); } + public float getChargeProgress() { return entity.getChargeProgress(); } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java new file mode 100644 index 000000000..9455b28cd --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java @@ -0,0 +1,99 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergyBarrier; +import kamkeel.npcs.entity.EntityEnergyDome; +import noppes.npcs.api.entity.IEnergyBarrier; +import noppes.npcs.scripted.constants.EntityType; + +/** + * Base script wrapper for energy barrier entities (Dome, Panel). + * All shared methods delegate directly to EntityEnergyBarrier base. + */ +public class ScriptEnergyBarrier extends ScriptEnergyAbility implements IEnergyBarrier { + + public ScriptEnergyBarrier(T entity) { + super(entity); + } + + @Override + public int getType() { + return EntityType.ENERGY_BARRIER; + } + + @Override + public boolean typeOf(int type) { + return type == EntityType.ENERGY_BARRIER || super.typeOf(type); + } + + // ==================== HEALTH ==================== + + public float getCurrentHealth() { + return entity.getCurrentHealth(); + } + + public void setCurrentHealth(float health) { + entity.setCurrentHealth(health); + } + + public float getHealthPercent() { + return entity.getHealthPercent(); + } + + public float getMaxHealth() { + return entity.getBarrierData().maxHealth; + } + + public void setMaxHealth(float maxHealth) { + entity.getBarrierData().setMaxHealth(maxHealth); + } + + public boolean isUseHealth() { + return entity.getBarrierData().useHealth; + } + + public void setUseHealth(boolean useHealth) { + entity.getBarrierData().useHealth = useHealth; + } + + // ==================== DURATION ==================== + + public int getDuration() { + return entity.getBarrierData().durationTicks; + } + + public void setDuration(int ticks) { + entity.getBarrierData().setDurationTicks(ticks); + } + + public boolean isUseDuration() { + return entity.getBarrierData().useDuration; + } + + public void setUseDuration(boolean useDuration) { + entity.getBarrierData().useDuration = useDuration; + } + + // ==================== STATE (BARRIER-SPECIFIC) ==================== + + public int getTicksAlive() { + return entity.getTicksAlive(); + } + + // ==================== BARRIER ==================== + + public float getDefaultMultiplier() { return entity.getBarrierData().defaultMultiplier; } + public void setDefaultMultiplier(float multiplier) { entity.getBarrierData().defaultMultiplier = multiplier; } + + public boolean isKnockbackEnabled() { return entity.getBarrierData().knockbackEnabled; } + public void setKnockbackEnabled(boolean enabled) { entity.getBarrierData().knockbackEnabled = enabled; } + + public float getKnockbackStrength() { return entity.getBarrierData().knockbackStrength; } + public void setKnockbackStrength(float strength) { entity.getBarrierData().knockbackStrength = strength; } + + public int getKnockbackTarget() { return entity.getBarrierData().knockbackTarget; } + public void setKnockbackTarget(int target) { entity.getBarrierData().knockbackTarget = target; } + + public int getBarrierType() { + return entity instanceof EntityEnergyDome ? 0 : 1; + } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java new file mode 100644 index 000000000..8d331c170 --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java @@ -0,0 +1,32 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergyDome; +import noppes.npcs.api.entity.IEnergyDome; +import noppes.npcs.scripted.constants.EntityType; + +public class ScriptEnergyDome extends ScriptEnergyBarrier implements IEnergyDome { + + public ScriptEnergyDome(T entity) { + super(entity); + } + + @Override + public int getType() { + return EntityType.ENERGY_DOME; + } + + @Override + public boolean typeOf(int type) { + return type == EntityType.ENERGY_DOME || super.typeOf(type); + } + + @Override + public int getBarrierType() { + return 0; + } + + // ==================== DOME-SPECIFIC ==================== + + public float getDomeRadius() { return entity.getDomeRadius(); } + public void setDomeRadius(float radius) { entity.setDomeRadius(radius); } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java new file mode 100644 index 000000000..5da93dc4a --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java @@ -0,0 +1,41 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergyPanel; +import noppes.npcs.api.entity.IEnergyPanel; +import noppes.npcs.scripted.constants.EntityType; + +public class ScriptEnergyPanel extends ScriptEnergyBarrier implements IEnergyPanel { + + public ScriptEnergyPanel(T entity) { + super(entity); + } + + @Override + public int getType() { + return EntityType.ENERGY_PANEL; + } + + @Override + public boolean typeOf(int type) { + return type == EntityType.ENERGY_PANEL || super.typeOf(type); + } + + @Override + public int getBarrierType() { + return 1; + } + + // ==================== PANEL-SPECIFIC ==================== + + public float getPanelWidth() { return entity.getPanelData().panelWidth; } + public void setPanelWidth(float width) { entity.getPanelData().setPanelWidth(width); } + + public float getPanelHeight() { return entity.getPanelData().panelHeight; } + public void setPanelHeight(float height) { entity.getPanelData().setPanelHeight(height); } + + public float getPanelYaw() { return entity.getPanelYaw(); } + + public int getPanelMode() { return entity.getMode().ordinal(); } + + public boolean isLaunched() { return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED; } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index f614b8122..9da13936f 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -1,21 +1,18 @@ package noppes.npcs.scripted.entity; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.api.entity.IEnergyProjectile; import noppes.npcs.api.entity.IEntity; import noppes.npcs.api.entity.IEntityLivingBase; import noppes.npcs.EventHooks; -import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.constants.EntityType; -public class ScriptEnergyProjectile extends ScriptEntity implements IEnergyProjectile { - protected T entity; +public class ScriptEnergyProjectile extends ScriptEnergyAbility implements IEnergyProjectile { public ScriptEnergyProjectile(T entity) { super(entity); - this.entity = entity; } // ==================== TYPE ==================== @@ -36,14 +33,6 @@ public class ScriptEnergyProjectile extends S // ==================== OWNER & TARGET ==================== - public int getOwnerEntityId() { - return entity.getOwnerEntityId(); - } - - public IEntity getOwner() { - return entity.getOwner(); - } - public void setOwner(IEntity owner) { if (owner != null) { entity.setOwnerEntityId(owner.getMCEntity().getEntityId()); @@ -76,22 +65,7 @@ public class ScriptEnergyProjectile extends S entity.setProjectileSize(size); } - // ==================== DISPLAY ==================== - - public int getInnerColor() { return entity.getInnerColor(); } - public void setInnerColor(int color) { entity.setInnerColor(color); } - - public int getOuterColor() { return entity.getOuterColor(); } - public void setOuterColor(int color) { entity.setOuterColor(color); } - - public boolean isOuterColorEnabled() { return entity.isOuterColorEnabled(); } - public void setOuterColorEnabled(boolean enabled) { entity.setOuterColorEnabled(enabled); } - - public float getOuterColorWidth() { return entity.getOuterColorWidth(); } - public void setOuterColorWidth(float width) { entity.setOuterColorWidth(width); } - - public float getOuterColorAlpha() { return entity.getOuterColorAlpha(); } - public void setOuterColorAlpha(float alpha) { entity.setOuterColorAlpha(alpha); } + // ==================== DISPLAY (PROJECTILE-SPECIFIC) ==================== public float getRotationSpeed() { return entity.getRotationSpeed(); } public void setRotationSpeed(float speed) { entity.setRotationSpeed(speed); } @@ -103,20 +77,6 @@ public class ScriptEnergyProjectile extends S public float getInterpolatedRotationZ(float partialTicks) { return entity.getInterpolatedRotationZ(partialTicks); } public float getInterpolatedSize(float partialTicks) { return entity.getInterpolatedSize(partialTicks); } - // ==================== LIGHTNING ==================== - - public boolean hasLightningEffect() { return entity.hasLightningEffect(); } - public void setLightningEffect(boolean enabled) { entity.setLightningEffect(enabled); } - - public float getLightningDensity() { return entity.getLightningDensity(); } - public void setLightningDensity(float density) { entity.setLightningDensity(density); } - - public float getLightningRadius() { return entity.getLightningRadius(); } - public void setLightningRadius(float radius) { entity.setLightningRadius(radius); } - - public int getLightningFadeTime() { return entity.getLightningFadeTime(); } - public void setLightningFadeTime(int ticks) { entity.setLightningFadeTime(ticks); } - // ==================== LIFESPAN ==================== public float getMaxDistance() { return entity.getMaxDistance(); } @@ -175,8 +135,6 @@ public class ScriptEnergyProjectile extends S // ==================== STATE ==================== public boolean hasHit() { return entity.getHasHit(); } - public boolean isCharging() { return entity.isCharging(); } - public float getChargeProgress() { return entity.getChargeProgress(); } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java new file mode 100644 index 000000000..270266123 --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java @@ -0,0 +1,67 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityEnergySlicer; +import net.minecraft.entity.EntityLivingBase; +import noppes.npcs.api.entity.IEnergySlicer; +import noppes.npcs.api.entity.IEntity; +import noppes.npcs.scripted.constants.EntityType; + +public class ScriptEnergySlicer extends ScriptEnergyProjectile implements IEnergySlicer { + + public ScriptEnergySlicer(T entity) { + super(entity); + } + + @Override + public int getType() { + return EntityType.ENERGY_SLICER; + } + + @Override + public boolean typeOf(int type) { + return type == EntityType.ENERGY_SLICER || super.typeOf(type); + } + + @Override + public int getEnergyType() { + return 4; + } + + // ==================== SLICER-SPECIFIC ==================== + + public float getSliceWidth() { return entity.getSliceWidth(); } + public void setSliceWidth(float width) { entity.setSliceWidth(width); } + + public float getSliceThickness() { return entity.getSliceThickness(); } + public void setSliceThickness(float thickness) { entity.setSliceThickness(thickness); } + + public boolean isPiercing() { return entity.isPiercing(); } + public void setPiercing(boolean piercing) { entity.setPiercing(piercing); } + + // ==================== FIRE ==================== + + @Override + public void fire(IEntity target) { + ensureSpawned(); + EntityLivingBase mcTarget = null; + if (target != null && target.getMCEntity() instanceof EntityLivingBase) { + mcTarget = (EntityLivingBase) target.getMCEntity(); + } + entity.startMoving(mcTarget); + } + + @Override + public void fire(double x, double y, double z) { + ensureSpawned(); + double dx = x - entity.posX; + double dy = y - entity.posY; + double dz = z - entity.posZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 0) { + entity.motionX = (dx / len) * entity.getSpeed(); + entity.motionY = (dy / len) * entity.getSpeed(); + entity.motionZ = (dz / len) * entity.getSpeed(); + } + entity.startMoving(null); + } +} diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEntity.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEntity.java index 65b2866af..c5b2c6a6e 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEntity.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEntity.java @@ -4,7 +4,7 @@ import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; import kamkeel.npcs.entity.EntityAbilityLaser; import kamkeel.npcs.entity.EntityAbilityOrb; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; @@ -532,7 +532,7 @@ public class ScriptEntity implements IEntity { else if (type == EntityType.NPC) cls = EntityNPCInterface.class; else if (type == EntityType.ENERGY_PROJECTILE) - cls = EntityAbilityProjectile.class; + cls = EntityEnergyProjectile.class; else if (type == EntityType.ENERGY_ORB) cls = EntityAbilityOrb.class; else if (type == EntityType.ENERGY_BEAM) diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java index 2379c2035..78134a3fc 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java @@ -29,7 +29,7 @@ import noppes.npcs.constants.EnumMovingType; import noppes.npcs.constants.EnumNavType; import noppes.npcs.constants.EnumRoleType; import noppes.npcs.constants.EnumStandingType; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import noppes.npcs.controllers.FactionController; import noppes.npcs.controllers.data.DialogOption; import noppes.npcs.controllers.data.Line; @@ -1467,7 +1467,7 @@ public class ScriptNpc extends ScriptLiving imp } public IEnergyProjectile[] getActiveEnergyProjectiles() { - java.util.List entities = EntityAbilityProjectile.getActiveProjectiles(npc.getEntityId()); + java.util.List entities = EntityEnergyProjectile.getActiveProjectiles(npc.getEntityId()); IEnergyProjectile[] result = new IEnergyProjectile[entities.size()]; for (int i = 0; i < entities.size(); i++) { result[i] = (IEnergyProjectile) NpcAPI.Instance().getIEntity(entities.get(i)); diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java index 3608b9d1e..6caca7419 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java @@ -1,7 +1,7 @@ package noppes.npcs.scripted.entity; import kamkeel.npcs.controllers.AttributeController; -import kamkeel.npcs.entity.EntityAbilityProjectile; +import kamkeel.npcs.entity.EntityEnergyProjectile; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.data.AchievementPacket; import kamkeel.npcs.network.packets.data.ChatAlertPacket; @@ -937,7 +937,7 @@ public class ScriptPlayer extends ScriptLivingBase } public IEnergyProjectile[] getActiveEnergyProjectiles() { - List entities = EntityAbilityProjectile.getActiveProjectiles(player.getEntityId()); + List entities = EntityEnergyProjectile.getActiveProjectiles(player.getEntityId()); IEnergyProjectile[] result = new IEnergyProjectile[entities.size()]; for (int i = 0; i < entities.size(); i++) { result[i] = (IEnergyProjectile) NpcAPI.Instance().getIEntity(entities.get(i)); diff --git a/src/main/java/noppes/npcs/scripted/event/EnergyBarrierEvent.java b/src/main/java/noppes/npcs/scripted/event/EnergyBarrierEvent.java new file mode 100644 index 000000000..a64948a6c --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/event/EnergyBarrierEvent.java @@ -0,0 +1,98 @@ +package noppes.npcs.scripted.event; + +import cpw.mods.fml.common.eventhandler.Cancelable; +import noppes.npcs.api.entity.IEnergyBarrier; +import noppes.npcs.api.entity.IEnergyProjectile; +import noppes.npcs.api.entity.IEntity; +import noppes.npcs.api.event.IEnergyBarrierEvent; +import noppes.npcs.constants.EnumScriptType; + +public class EnergyBarrierEvent extends CustomNPCsEvent implements IEnergyBarrierEvent { + public final IEnergyBarrier barrier; + public final IEntity owner; + + public EnergyBarrierEvent(IEnergyBarrier barrier) { + this.barrier = barrier; + this.owner = barrier.getOwner(); + } + + @Override + public IEnergyBarrier getBarrier() { + return barrier; + } + + @Override + public IEntity getOwner() { + return owner; + } + + @Override + public String getHookName() { + return "energyBarrierEvent"; + } + + public static class SpawnedEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.SpawnedEvent { + public SpawnedEvent(IEnergyBarrier barrier) { + super(barrier); + } + + @Override + public String getHookName() { + return EnumScriptType.ENERGY_BARRIER_SPAWNED.function; + } + } + + public static class UpdateEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.UpdateEvent { + public UpdateEvent(IEnergyBarrier barrier) { + super(barrier); + } + + @Override + public String getHookName() { + return EnumScriptType.ENERGY_BARRIER_TICK.function; + } + } + + @Cancelable + public static class HitEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.HitEvent { + private final IEnergyProjectile projectile; + private float damage; + + public HitEvent(IEnergyBarrier barrier, IEnergyProjectile projectile, float damage) { + super(barrier); + this.projectile = projectile; + this.damage = damage; + } + + @Override + public IEnergyProjectile getProjectile() { + return projectile; + } + + @Override + public float getDamage() { + return damage; + } + + @Override + public void setDamage(float damage) { + this.damage = damage; + } + + @Override + public String getHookName() { + return EnumScriptType.ENERGY_BARRIER_HIT.function; + } + } + + public static class DestroyedEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.DestroyedEvent { + public DestroyedEvent(IEnergyBarrier barrier) { + super(barrier); + } + + @Override + public String getHookName() { + return EnumScriptType.ENERGY_BARRIER_DESTROYED.function; + } + } +} diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index eaaf48a45..83cc1c513 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1987,6 +1987,46 @@ ability.hover.canCounter=Enable counterattack when blocking damage ability.hover.counterType=Flat = fixed damage, Percent = % of blocked damage ability.hover.affectsCaster=Hazard can damage the NPC that created it ability.hover.sweepMode=Swipe = single arc attack, Spin = continuous rotation +ability.hover.launching=When enabled, wall launches forward dealing damage and knockback + +# Energy Barrier Abilities +ability.cnpc.dome=Dome +ability.cnpc.wall=Wall +ability.cnpc.shield=Shield +ability.cnpc.slicer=Slicer + +ability.section.barrier=Barrier +ability.section.knockback=Knockback +ability.section.panel=Panel +ability.section.launch=Launch +ability.section.slicer=Slicer + +ability.domeRadius=Dome Radius +ability.useHealth=Use Health +ability.maxHealth=Max Health +ability.useDuration=Use Duration +ability.defaultMultiplier=Default Multiplier +ability.innerAlpha=Inner Alpha +ability.heightOffset=Height Offset + +ability.knockbackEnabled=Knockback +ability.knockbackStrength=Knockback Strength +ability.knockbackTarget=Target + +ability.section.melee=Melee +ability.meleeEnabled=Melee Attackable +ability.meleeDamageMultiplier=Melee Multiplier + +ability.followCaster=Follow Caster +ability.hover.followCaster=Dome stays centered on the caster as they move + +ability.variant.small=Small +ability.variant.large=Large +ability.variant.static=Static +ability.variant.launched=Launched + +gui.both=Both +gui.players=Players # Conditions condition.edit=Edit Condition