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:
Claude committed 2026-02-16 08:54:55 +00:00
1 parent 01e18e0221
commit 0ebdfafccd
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));
}
}
@@ -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());