mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 09:37:29 +00:00
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
This commit is contained in:
17 files changed
+2699
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -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<String, Float> 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<String, Float> getDamageMultipliers() {
|
||||
return damageMultipliers;
|
||||
}
|
||||
|
||||
public void setDamageMultipliers(Map<String, Float> 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<String, Float> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<AbilityVariant> 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<FieldDef> defs) {
|
||||
defs.add(FieldDef.floatField("ability.domeRadius", this::getDomeRadius, this::setDomeRadius)
|
||||
.range(1.0f, 30.0f));
|
||||
}
|
||||
}
|
||||
@@ -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<AbilityVariant> 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<FieldDef> 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)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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<EntityEnergySlicer> {
|
||||
|
||||
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<AbilityVariant> 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<FieldDef> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<AbilityVariant> 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<FieldDef> 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));
|
||||
}
|
||||
}
|
||||
+224
@@ -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<FieldDef> 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<FieldDef> 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"));
|
||||
}
|
||||
}
|
||||
@@ -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<EntityEnergyDome> 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<EntityEnergyPanel> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<EntityLivingBase> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> 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<EntityLivingBase> 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Reference in new issue
Block a user