mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 20:44:08 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
commit
375c41daba
79 files changed
+5137
-1857
No files matched your search
+1
-1
Submodule src/api updated: efa8721d36...22bce4b2ac.
@@ -2,7 +2,6 @@ package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -24,7 +23,7 @@ import java.util.List;
|
||||
* Design inspired by LouisXIV's energy rendering system.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderAbilityBeam extends RenderAbilityProjectile {
|
||||
public class RenderAbilityBeam extends RenderEnergyAbility {
|
||||
|
||||
@Override
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
@@ -117,7 +116,7 @@ public class RenderAbilityBeam extends RenderAbilityProjectile {
|
||||
|
||||
// Render lightning during charging if enabled
|
||||
if (beam.hasLightningEffect()) {
|
||||
renderAttachedLightning(beam, scale);
|
||||
renderAttachedLightning(beam, 0.6f * 0.5f, scale);
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
@@ -545,7 +544,7 @@ public class RenderAbilityBeam extends RenderAbilityProjectile {
|
||||
|
||||
// Render lightning BEFORE rotation so it crackles in all directions (only on head)
|
||||
if (beam.hasLightningEffect()) {
|
||||
renderAttachedLightning(beam, scale);
|
||||
renderAttachedLightning(beam, 0.6f * 0.5f, scale);
|
||||
}
|
||||
|
||||
// Rotation
|
||||
@@ -579,31 +578,4 @@ public class RenderAbilityBeam extends RenderAbilityProjectile {
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fading lightning arcs attached to the beam head (in local space).
|
||||
*/
|
||||
private void renderAttachedLightning(EntityAbilityBeam beam, float headScale) {
|
||||
AttachedLightningRenderer.LightningState state = getLightningState(beam);
|
||||
|
||||
float density = beam.getLightningDensity();
|
||||
// Lightning radius extends outward from inner surface (innerScale = 0.6)
|
||||
float innerRadius = 0.6f * headScale * 0.5f;
|
||||
float radius = innerRadius + beam.getLightningRadius() * headScale;
|
||||
int outerColor = beam.getOuterColor();
|
||||
int innerColor = beam.getInnerColor();
|
||||
int fadeTime = beam.getLightningFadeTime();
|
||||
|
||||
state.update(density, radius, outerColor, innerColor, fadeTime);
|
||||
state.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the lightning state for an entity.
|
||||
*/
|
||||
private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityBeam beam) {
|
||||
if (beam.lightningState == null) {
|
||||
beam.lightningState = new AttachedLightningRenderer.LightningState();
|
||||
}
|
||||
return (AttachedLightningRenderer.LightningState) beam.lightningState;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -14,7 +13,7 @@ import org.lwjgl.opengl.GL11;
|
||||
* Design inspired by LouisXIV's energy rendering system.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderAbilityDisc extends RenderAbilityProjectile {
|
||||
public class RenderAbilityDisc extends RenderEnergyAbility {
|
||||
|
||||
@Override
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
@@ -37,7 +36,7 @@ public class RenderAbilityDisc extends RenderAbilityProjectile {
|
||||
|
||||
// Render lightning BEFORE rotation so it crackles in all directions
|
||||
if (disc.hasLightningEffect()) {
|
||||
renderAttachedLightning(disc, radius * scale);
|
||||
renderAttachedLightning(disc, 0.6f * 0.5f, radius * scale);
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
@@ -76,34 +75,6 @@ public class RenderAbilityDisc extends RenderAbilityProjectile {
|
||||
restoreRenderState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fading lightning arcs attached to the disc (in local space).
|
||||
*/
|
||||
private void renderAttachedLightning(EntityAbilityDisc disc, float discSize) {
|
||||
AttachedLightningRenderer.LightningState state = getLightningState(disc);
|
||||
|
||||
float density = disc.getLightningDensity();
|
||||
// Lightning radius extends outward from inner surface (innerScale = 0.6)
|
||||
float innerRadius = 0.6f * discSize * 0.5f;
|
||||
float radius = innerRadius + disc.getLightningRadius() * discSize;
|
||||
int outerColor = disc.getOuterColor();
|
||||
int innerColor = disc.getInnerColor();
|
||||
int fadeTime = disc.getLightningFadeTime();
|
||||
|
||||
state.update(density, radius, outerColor, innerColor, fadeTime);
|
||||
state.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the lightning state for an entity.
|
||||
*/
|
||||
private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityDisc disc) {
|
||||
if (disc.lightningState == null) {
|
||||
disc.lightningState = new AttachedLightningRenderer.LightningState();
|
||||
}
|
||||
return (AttachedLightningRenderer.LightningState) disc.lightningState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a flat square (thin cube) centered at origin.
|
||||
* The square lies flat on the XZ plane with thin Y height.
|
||||
|
||||
@@ -3,7 +3,6 @@ package kamkeel.npcs.client.renderer;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -15,7 +14,7 @@ import org.lwjgl.opengl.GL11;
|
||||
* Design inspired by LouisXIV's energy rendering system.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderAbilityLaser extends RenderAbilityProjectile {
|
||||
public class RenderAbilityLaser extends RenderEnergyAbility {
|
||||
|
||||
@Override
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
@@ -147,7 +146,7 @@ public class RenderAbilityLaser extends RenderAbilityProjectile {
|
||||
|
||||
// Render lightning during charging if enabled
|
||||
if (laser.hasLightningEffect()) {
|
||||
renderAttachedLightning(laser, scale);
|
||||
renderAttachedLightning(laser, 0.6f * 0.5f, scale);
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
@@ -170,7 +169,7 @@ public class RenderAbilityLaser extends RenderAbilityProjectile {
|
||||
|
||||
// Render inner core
|
||||
GL11.glScalef(innerScale, innerScale, innerScale);
|
||||
renderCube(innerColor, 1.0f, 0.5f);
|
||||
renderCube(innerColor, laser.getInnerAlpha(), 0.5f);
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GL11.glPopMatrix();
|
||||
@@ -215,31 +214,6 @@ public class RenderAbilityLaser extends RenderAbilityProjectile {
|
||||
}
|
||||
|
||||
|
||||
private void renderAttachedLightning(EntityAbilityLaser laser, float headScale) {
|
||||
AttachedLightningRenderer.LightningState state = getLightningState(laser);
|
||||
|
||||
float density = laser.getLightningDensity();
|
||||
// Lightning radius extends outward from inner surface (innerScale = 0.6)
|
||||
float innerRadius = 0.6f * headScale * 0.5f;
|
||||
float radius = innerRadius + laser.getLightningRadius() * headScale;
|
||||
int outerColor = laser.getOuterColor();
|
||||
int innerColor = laser.getInnerColor();
|
||||
int fadeTime = laser.getLightningFadeTime();
|
||||
|
||||
state.update(density, radius, outerColor, innerColor, fadeTime);
|
||||
state.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the main lightning state for the laser.
|
||||
*/
|
||||
private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityLaser laser) {
|
||||
if (laser.lightningState == null) {
|
||||
laser.lightningState = new AttachedLightningRenderer.LightningState();
|
||||
}
|
||||
return (AttachedLightningRenderer.LightningState) laser.lightningState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the tip lightning state for the laser.
|
||||
* Stored as a secondary object in lightningState2.
|
||||
|
||||
@@ -2,7 +2,6 @@ package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -12,7 +11,7 @@ import org.lwjgl.opengl.GL11;
|
||||
* Minecraft-style blocky appearance using Tessellator.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderAbilityOrb extends RenderAbilityProjectile {
|
||||
public class RenderAbilityOrb extends RenderEnergyAbility {
|
||||
|
||||
private static final int LIGHTNING_FADE_TICKS = 6;
|
||||
|
||||
@@ -35,7 +34,7 @@ public class RenderAbilityOrb extends RenderAbilityProjectile {
|
||||
|
||||
// Render lightning BEFORE rotation so it crackles in all directions
|
||||
if (orb.hasLightningEffect()) {
|
||||
renderAttachedLightning(orb, scale);
|
||||
renderAttachedLightning(orb, 0.6f * 0.5f, scale);
|
||||
}
|
||||
|
||||
// Use entity's interpolated rotation for smooth spinning
|
||||
@@ -63,7 +62,7 @@ public class RenderAbilityOrb extends RenderAbilityProjectile {
|
||||
|
||||
// Render inner cube (solid)
|
||||
GL11.glScalef(innerScale, innerScale, innerScale);
|
||||
renderCube(orb.getInnerColor(), 1.0f, 0.5f);
|
||||
renderCube(orb.getInnerColor(), orb.getInnerAlpha(), 0.5f);
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GL11.glPopMatrix();
|
||||
@@ -71,36 +70,4 @@ public class RenderAbilityOrb extends RenderAbilityProjectile {
|
||||
restoreRenderState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fading lightning arcs attached to the orb (in local space).
|
||||
* Arcs persist for a few ticks and fade out, while staying attached to the orb.
|
||||
*/
|
||||
private void renderAttachedLightning(EntityAbilityOrb orb, float orbScale) {
|
||||
// Get or create the lightning state for this entity
|
||||
AttachedLightningRenderer.LightningState state = getLightningState(orb);
|
||||
|
||||
float density = orb.getLightningDensity();
|
||||
// Lightning radius extends outward from inner surface (innerScale = 0.6)
|
||||
float innerRadius = 0.6f * orbScale * 0.5f; // Half size of inner cube
|
||||
float radius = innerRadius + orb.getLightningRadius() * orbScale;
|
||||
int outerColor = orb.getOuterColor();
|
||||
int innerColor = orb.getInnerColor();
|
||||
int fadeTime = orb.getLightningFadeTime();
|
||||
|
||||
// Update: age existing arcs, spawn new ones based on density
|
||||
state.update(density, radius, outerColor, innerColor, fadeTime);
|
||||
|
||||
// Render all active arcs in local space (they move with the orb)
|
||||
state.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the lightning state for an entity.
|
||||
*/
|
||||
private AttachedLightningRenderer.LightningState getLightningState(EntityAbilityOrb orb) {
|
||||
if (orb.lightningState == null) {
|
||||
orb.lightningState = new AttachedLightningRenderer.LightningState();
|
||||
}
|
||||
return (AttachedLightningRenderer.LightningState) orb.lightningState;
|
||||
}
|
||||
}
|
||||
+38
-2
@@ -2,6 +2,8 @@ package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
|
||||
import kamkeel.npcs.entity.EntityEnergyAbility;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
@@ -17,11 +19,11 @@ import org.lwjgl.opengl.GL12;
|
||||
* Design inspired by LouisXIV's energy rendering system.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public abstract class RenderAbilityProjectile extends Render {
|
||||
public abstract class RenderEnergyAbility extends Render {
|
||||
|
||||
protected static final ResourceLocation WHITE_TEXTURE = new ResourceLocation("customnpcs", "textures/entity/white.png");
|
||||
|
||||
public RenderAbilityProjectile() {
|
||||
public RenderEnergyAbility() {
|
||||
this.shadowSize = 0.0f;
|
||||
}
|
||||
|
||||
@@ -252,6 +254,40 @@ public abstract class RenderAbilityProjectile extends Render {
|
||||
tess.draw();
|
||||
}
|
||||
|
||||
// ==================== SHARED LIGHTNING ====================
|
||||
|
||||
/**
|
||||
* Get or create the lightning state for an energy entity.
|
||||
* Uses the lightningState field on EntityEnergyAbility (stored as Object
|
||||
* to avoid client class loading on server).
|
||||
*/
|
||||
protected AttachedLightningRenderer.LightningState getLightningState(EntityEnergyAbility entity) {
|
||||
if (entity.lightningState == null) {
|
||||
entity.lightningState = new AttachedLightningRenderer.LightningState();
|
||||
}
|
||||
return (AttachedLightningRenderer.LightningState) entity.lightningState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render attached lightning arcs around an energy entity.
|
||||
* @param entity the energy entity
|
||||
* @param innerScale multiplier for inner radius (e.g. 0.3f for projectiles, 0.95f for dome)
|
||||
* @param baseSize the entity's current render size/scale
|
||||
*/
|
||||
protected void renderAttachedLightning(EntityEnergyAbility entity, float innerScale, float baseSize) {
|
||||
AttachedLightningRenderer.LightningState state = getLightningState(entity);
|
||||
|
||||
float density = entity.getLightningDensity();
|
||||
float innerRadius = innerScale * baseSize;
|
||||
float radius = innerRadius + entity.getLightningRadius() * baseSize;
|
||||
int outerColor = entity.getOuterColor();
|
||||
int innerColor = entity.getInnerColor();
|
||||
int fadeTime = entity.getLightningFadeTime();
|
||||
|
||||
state.update(density, radius, outerColor, innerColor, fadeTime);
|
||||
state.render();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceLocation getEntityTexture(Entity entity) {
|
||||
return WHITE_TEXTURE;
|
||||
@@ -0,0 +1,23 @@
|
||||
package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
|
||||
/**
|
||||
* Intermediate renderer base for energy barrier entities (Dome, Panel).
|
||||
* Provides shared hit flash alpha computation.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public abstract class RenderEnergyBarrier extends RenderEnergyAbility {
|
||||
|
||||
/**
|
||||
* Compute the alpha boost from a hit flash effect.
|
||||
* Flash value decays from 4 to 0, producing alpha from 0.3 to 0.
|
||||
*/
|
||||
protected float computeFlashAlpha(EntityEnergyBarrier barrier) {
|
||||
byte flash = barrier.getHitFlash();
|
||||
if (flash <= 0) return 0.0f;
|
||||
return flash / 4.0f * 0.3f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Renders the Dome entity as a translucent sphere.
|
||||
* Inner layer solid, outer layer translucent glow.
|
||||
* Optional skybox texture rendered on the interior surface.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderEnergyDome extends RenderEnergyBarrier {
|
||||
|
||||
@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 BEFORE any rotation (uniform spread)
|
||||
if (dome.hasLightningEffect()) {
|
||||
renderAttachedLightning(dome, 0.95f, radius);
|
||||
}
|
||||
|
||||
// Hit flash effect
|
||||
float flashAlpha = computeFlashAlpha(dome);
|
||||
|
||||
float innerScale = 0.95f;
|
||||
|
||||
// Render outer dome (translucent glow)
|
||||
if (dome.isOuterColorEnabled()) {
|
||||
float outerScale = 1.0f + dome.getOuterColorWidth() * 0.1f;
|
||||
float outerAlpha = dome.getOuterColorAlpha() * healthPercent;
|
||||
GL11.glDepthMask(false);
|
||||
renderSphere(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8);
|
||||
GL11.glDepthMask(true);
|
||||
}
|
||||
|
||||
// TODO: Skybox Feature
|
||||
// if (dome.isSkyboxEnabled() && !dome.getSkyboxTexture().isEmpty()) {
|
||||
// renderSkyboxSphere(dome.getSkyboxTexture(), radius, radius * 0.94f, 32, x, y, z);
|
||||
// }
|
||||
|
||||
// Render inner dome
|
||||
renderSphere(dome.getInnerColor(), dome.getInnerAlpha(), radius * innerScale, 8);
|
||||
|
||||
GL11.glPopMatrix();
|
||||
|
||||
restoreRenderState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a sphere made of quad panels.
|
||||
* Uses latitude/longitude segments to form a full sphere.
|
||||
*/
|
||||
private void renderSphere(int color, float alpha, float radius, int segments) {
|
||||
float[] rgb = extractRGB(color);
|
||||
|
||||
Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(rgb[0], rgb[1], rgb[2], alpha);
|
||||
|
||||
for (int lat = 0; lat < segments; lat++) {
|
||||
float theta1 = (float) (Math.PI * lat / segments - Math.PI * 0.5);
|
||||
float theta2 = (float) (Math.PI * (lat + 1) / segments - Math.PI * 0.5);
|
||||
|
||||
float y1 = (float) Math.sin(theta1) * radius;
|
||||
float y2 = (float) Math.sin(theta2) * radius;
|
||||
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;
|
||||
|
||||
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.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();
|
||||
}
|
||||
|
||||
// TODO: Skybox Feature — renderSkyboxSphere method removed
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
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 RenderEnergyBarrier {
|
||||
|
||||
@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);
|
||||
|
||||
// Lightning BEFORE rotation for uniform spread (like Orb pattern)
|
||||
if (panel.hasLightningEffect()) {
|
||||
renderAttachedLightning(panel, 0.9f, Math.max(width, height) * 0.5f);
|
||||
}
|
||||
|
||||
// Rotate panel to face the correct direction
|
||||
GL11.glRotatef(-panelYaw, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
// Hit flash
|
||||
float flashAlpha = computeFlashAlpha(panel);
|
||||
|
||||
// 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 (uses configurable innerAlpha for semi-transparency)
|
||||
renderPanel(panel.getInnerColor(), panel.getInnerAlpha(),
|
||||
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).
|
||||
* All 6 faces batched into a single draw call.
|
||||
*/
|
||||
private void renderPanel(int color, float alpha, float halfWidth, float halfHeight, float halfThickness) {
|
||||
float[] rgb = extractRGB(color);
|
||||
float r = rgb[0], g = rgb[1], b = rgb[2];
|
||||
|
||||
Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
|
||||
// Front face (z+)
|
||||
tess.setNormal(0, 0, 1);
|
||||
tess.addVertex(-halfWidth, -halfHeight, halfThickness);
|
||||
tess.addVertex(halfWidth, -halfHeight, halfThickness);
|
||||
tess.addVertex(halfWidth, halfHeight, halfThickness);
|
||||
tess.addVertex(-halfWidth, halfHeight, halfThickness);
|
||||
|
||||
// Back face (z-)
|
||||
tess.setNormal(0, 0, -1);
|
||||
tess.addVertex(halfWidth, -halfHeight, -halfThickness);
|
||||
tess.addVertex(-halfWidth, -halfHeight, -halfThickness);
|
||||
tess.addVertex(-halfWidth, halfHeight, -halfThickness);
|
||||
tess.addVertex(halfWidth, halfHeight, -halfThickness);
|
||||
|
||||
// Top edge (y+)
|
||||
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);
|
||||
|
||||
// Bottom edge (y-)
|
||||
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);
|
||||
|
||||
// Right edge (x+)
|
||||
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);
|
||||
|
||||
// Left edge (x-)
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package kamkeel.npcs.client.renderer;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
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 RenderEnergyAbility {
|
||||
|
||||
@Override
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
EntityEnergySlicer slicer = (EntityEnergySlicer) entity;
|
||||
|
||||
float width = slicer.getSliceWidth();
|
||||
float thickness = slicer.getSliceThickness();
|
||||
|
||||
// During charging, width/thickness grow from 0.01 to target via updateCharging().
|
||||
// Use current dimensions directly - no separate sizeRatio needed.
|
||||
if (width <= 0.01f) return;
|
||||
|
||||
setupRenderState();
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslated(x, y, z);
|
||||
|
||||
// Lightning BEFORE rotation for uniform spread (like Orb pattern)
|
||||
if (slicer.hasLightningEffect()) {
|
||||
renderAttachedLightning(slicer, 0.7f, width * 0.5f);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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, alpha 1.0 like Orb pattern)
|
||||
renderBlade(slicer.getInnerColor(), 1.0f,
|
||||
width * 0.5f * innerScale, thickness, 0.03f);
|
||||
|
||||
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).
|
||||
* All 6 faces batched into a single draw call.
|
||||
*/
|
||||
private void renderBlade(int color, float alpha, float halfWidth, float halfHeight, float halfDepth) {
|
||||
float[] rgb = extractRGB(color);
|
||||
float r = rgb[0], g = rgb[1], b = rgb[2];
|
||||
|
||||
Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
// Front face (z+)
|
||||
tess.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);
|
||||
|
||||
// Back face (z-)
|
||||
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);
|
||||
|
||||
// Top edge (y+)
|
||||
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);
|
||||
|
||||
// Bottom edge (y-)
|
||||
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);
|
||||
|
||||
// Right edge (x+)
|
||||
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);
|
||||
|
||||
// Left edge (x-)
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,20 +33,19 @@ public class AbilityCommand extends CommandKamkeelBase {
|
||||
desc = "List all custom abilities"
|
||||
)
|
||||
public void list(ICommandSender sender, String[] args) {
|
||||
Set<String> uuids = AbilityController.Instance.getCustomAbilityIds();
|
||||
if (uuids.isEmpty()) {
|
||||
Set<String> names = AbilityController.Instance.getCustomAbilityNames();
|
||||
if (names.isEmpty()) {
|
||||
sendResult(sender, "No custom abilities found.");
|
||||
return;
|
||||
}
|
||||
|
||||
sendResult(sender, "Custom Abilities (" + uuids.size() + "):");
|
||||
for (String uuid : uuids) {
|
||||
Ability ability = AbilityController.Instance.getCustomAbility(uuid);
|
||||
sendResult(sender, "Custom Abilities (" + names.size() + "):");
|
||||
for (String name : names) {
|
||||
Ability ability = AbilityController.Instance.getCustomAbility(name);
|
||||
if (ability != null) {
|
||||
String name = ability.getName() != null ? ability.getName() : uuid;
|
||||
sendResult(sender, " - \u00A7b" + name + "\u00A77 (" + ability.getTypeId() + ") [" + uuid + "]");
|
||||
sendResult(sender, " - \u00A7b" + name + "\u00A77 (" + ability.getTypeId() + ")");
|
||||
} else {
|
||||
sendResult(sender, " - \u00A7b" + uuid);
|
||||
sendResult(sender, " - \u00A7b" + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,23 +81,23 @@ public class AbilityCommand extends CommandKamkeelBase {
|
||||
}
|
||||
|
||||
@SubCommand(
|
||||
desc = "Delete a custom ability by UUID",
|
||||
usage = "<uuid>"
|
||||
desc = "Delete a custom ability by name",
|
||||
usage = "<name>"
|
||||
)
|
||||
public void delete(ICommandSender sender, String[] args) {
|
||||
if (args.length < 1) {
|
||||
sendError(sender, "Usage: /kam ability delete <uuid>");
|
||||
sendError(sender, "Usage: /kam ability delete <name>");
|
||||
return;
|
||||
}
|
||||
|
||||
String uuid = args[0];
|
||||
if (!AbilityController.Instance.hasCustomAbility(uuid)) {
|
||||
sendError(sender, "No custom ability with UUID: " + uuid);
|
||||
String name = joinArgs(args, 0);
|
||||
if (!AbilityController.Instance.hasCustomAbilityName(name)) {
|
||||
sendError(sender, "No custom ability with name: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
AbilityController.Instance.deleteCustomAbility(uuid);
|
||||
sendResult(sender, "Deleted ability: \u00A7b" + uuid);
|
||||
AbilityController.Instance.deleteCustomAbility(name);
|
||||
sendResult(sender, "Deleted ability: \u00A7b" + name);
|
||||
}
|
||||
|
||||
@SubCommand(
|
||||
@@ -189,7 +188,7 @@ public class AbilityCommand extends CommandKamkeelBase {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the ability's canonical ID for storage (registry key for built-in, UUID for custom)
|
||||
// Use the ability's canonical ID for storage (registry key for built-in, name for custom)
|
||||
String canonicalKey = ability.getId();
|
||||
if (canonicalKey == null || canonicalKey.isEmpty()) {
|
||||
canonicalKey = abilityKey; // Fallback to user-provided key
|
||||
|
||||
@@ -81,10 +81,12 @@ public abstract class Ability implements IAbility {
|
||||
// Animations (global animation IDs, -1 = none)
|
||||
protected int windUpAnimationId = -1; // Animation to play during wind up (user animations)
|
||||
protected int activeAnimationId = -1; // Animation to play during active phase (user animations)
|
||||
protected int dazedAnimationId = -1; // Animation to play during dazed phase (user animations)
|
||||
|
||||
// Built-in animation names (empty = none, takes priority over IDs if set)
|
||||
protected String windUpAnimationName = ""; // Built-in animation name for wind up
|
||||
protected String activeAnimationName = ""; // Built-in animation name for active phase
|
||||
protected String dazedAnimationName = "Ability_Generic_Dazed"; // Built-in animation name for dazed phase
|
||||
|
||||
// Telegraph configuration
|
||||
protected boolean showTelegraph = true;
|
||||
@@ -101,7 +103,7 @@ public abstract class Ability implements IAbility {
|
||||
protected boolean ignoreCooldown = false;
|
||||
|
||||
// Configurable potion effects
|
||||
protected List<AbilityEffect> effects = new ArrayList<>();
|
||||
protected List<AbilityPotionEffect> effects = new ArrayList<>();
|
||||
|
||||
// Burst
|
||||
protected boolean burstEnabled = false;
|
||||
@@ -376,7 +378,7 @@ public abstract class Ability implements IAbility {
|
||||
if (entity == null || effects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effect.apply(entity);
|
||||
}
|
||||
}
|
||||
@@ -597,6 +599,10 @@ public abstract class Ability implements IAbility {
|
||||
this::getActiveAnimationId, this::setActiveAnimationId,
|
||||
this::getActiveAnimationName, this::setActiveAnimationName)
|
||||
.tab("Effects"));
|
||||
defs.add(FieldDef.animSubGui("ability.dazedAnimation",
|
||||
this::getDazedAnimationId, this::setDazedAnimationId,
|
||||
this::getDazedAnimationName, this::setDazedAnimationName)
|
||||
.tab("Effects"));
|
||||
|
||||
TelegraphType tType = getTelegraphType();
|
||||
if (tType != null && tType != TelegraphType.NONE) {
|
||||
@@ -1133,8 +1139,10 @@ public abstract class Ability implements IAbility {
|
||||
nbt.setString("activeSound", activeSound);
|
||||
nbt.setInteger("windUpAnimationId", windUpAnimationId);
|
||||
nbt.setInteger("activeAnimationId", activeAnimationId);
|
||||
nbt.setInteger("dazedAnimationId", dazedAnimationId);
|
||||
nbt.setString("windUpAnimationName", windUpAnimationName);
|
||||
nbt.setString("activeAnimationName", activeAnimationName);
|
||||
nbt.setString("dazedAnimationName", dazedAnimationName);
|
||||
nbt.setBoolean("showTelegraph", showTelegraph);
|
||||
nbt.setString("telegraphType", telegraphType.name());
|
||||
nbt.setFloat("telegraphHeightOffset", telegraphHeightOffset);
|
||||
@@ -1156,7 +1164,7 @@ public abstract class Ability implements IAbility {
|
||||
|
||||
// Effects
|
||||
NBTTagList effectList = new NBTTagList();
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effectList.appendTag(effect.writeNBT());
|
||||
}
|
||||
nbt.setTag("effects", effectList);
|
||||
@@ -1192,8 +1200,10 @@ public abstract class Ability implements IAbility {
|
||||
activeSound = nbt.getString("activeSound");
|
||||
windUpAnimationId = nbt.getInteger("windUpAnimationId");
|
||||
activeAnimationId = nbt.getInteger("activeAnimationId");
|
||||
dazedAnimationId = nbt.getInteger("dazedAnimationId");
|
||||
windUpAnimationName = nbt.getString("windUpAnimationName");
|
||||
activeAnimationName = nbt.getString("activeAnimationName");
|
||||
dazedAnimationName = nbt.getString("dazedAnimationName");
|
||||
showTelegraph = nbt.getBoolean("showTelegraph");
|
||||
try {
|
||||
telegraphType = TelegraphType.valueOf(nbt.getString("telegraphType"));
|
||||
@@ -1225,7 +1235,7 @@ public abstract class Ability implements IAbility {
|
||||
if (nbt.hasKey("effects")) {
|
||||
NBTTagList effectList = nbt.getTagList("effects", 10);
|
||||
for (int i = 0; i < effectList.tagCount(); i++) {
|
||||
AbilityEffect effect = AbilityEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
if (effect != null && effect.isValid()) {
|
||||
effects.add(effect);
|
||||
}
|
||||
@@ -1614,6 +1624,36 @@ public abstract class Ability implements IAbility {
|
||||
this.activeAnimationName = activeAnimationName != null ? activeAnimationName : "";
|
||||
}
|
||||
|
||||
public Animation getDazedAnimation() {
|
||||
if (AnimationController.Instance == null) return null;
|
||||
|
||||
// Built-in animation by name takes priority
|
||||
if (dazedAnimationName != null && !dazedAnimationName.isEmpty()) {
|
||||
return (Animation) AnimationController.Instance.get(dazedAnimationName, true);
|
||||
}
|
||||
// Fall back to user animation by ID
|
||||
if (dazedAnimationId >= 0) {
|
||||
return (Animation) AnimationController.Instance.get(dazedAnimationId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getDazedAnimationId() {
|
||||
return dazedAnimationId;
|
||||
}
|
||||
|
||||
public void setDazedAnimationId(int dazedAnimationId) {
|
||||
this.dazedAnimationId = dazedAnimationId;
|
||||
}
|
||||
|
||||
public String getDazedAnimationName() {
|
||||
return dazedAnimationName;
|
||||
}
|
||||
|
||||
public void setDazedAnimationName(String dazedAnimationName) {
|
||||
this.dazedAnimationName = dazedAnimationName != null ? dazedAnimationName : "";
|
||||
}
|
||||
|
||||
public boolean isShowTelegraph() {
|
||||
return showTelegraph;
|
||||
}
|
||||
@@ -1687,15 +1727,15 @@ public abstract class Ability implements IAbility {
|
||||
this.ignoreCooldown = ignoreCooldown;
|
||||
}
|
||||
|
||||
public List<AbilityEffect> getEffects() {
|
||||
public List<AbilityPotionEffect> getEffects() {
|
||||
return effects;
|
||||
}
|
||||
|
||||
public void setEffects(List<AbilityEffect> effects) {
|
||||
public void setEffects(List<AbilityPotionEffect> effects) {
|
||||
this.effects = effects != null ? effects : new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addEffect(AbilityEffect effect) {
|
||||
public void addEffect(AbilityPotionEffect effect) {
|
||||
if (effect != null && effect.isValid()) {
|
||||
effects.add(effect);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@ package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.type.*;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaserShot;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityOrb;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySlicer;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
@@ -15,6 +24,7 @@ import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AbilityController implements IAbilityHandler {
|
||||
|
||||
@@ -31,6 +41,11 @@ public class AbilityController implements IAbilityHandler {
|
||||
private final List<IAbilityExtender> extenders = new ArrayList<>();
|
||||
private final List<Predicate<EntityPlayer>> flightCheckers = new ArrayList<>();
|
||||
|
||||
// ── Legacy Migration ─────────────────────────────────────────────────────
|
||||
private static final Pattern UUID_PATTERN =
|
||||
Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// ── Derived State ────────────────────────────────────────────────────────
|
||||
private final Set<String> builtInTypeIds = new HashSet<>();
|
||||
|
||||
@@ -99,6 +114,11 @@ public class AbilityController implements IAbilityHandler {
|
||||
|
||||
registerType("cnpc:hazard", AbilityHazard::new);
|
||||
registerType("cnpc:trap", AbilityTrap::new);
|
||||
|
||||
registerType("cnpc:dome", AbilityDome::new);
|
||||
registerType("cnpc:wall", AbilityWall::new);
|
||||
registerType("cnpc:shield", AbilityShield::new);
|
||||
registerType("cnpc:slicer", AbilitySlicer::new);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -142,6 +162,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
|
||||
public void load() {
|
||||
customAbilities.clear();
|
||||
int migrated = 0;
|
||||
|
||||
File dir = getDir();
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
@@ -150,14 +171,39 @@ public class AbilityController implements IAbilityHandler {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
||||
try {
|
||||
String filename = file.getName();
|
||||
String uuid = filename.substring(0, filename.length() - 5);
|
||||
String key = filename.substring(0, filename.length() - 5);
|
||||
|
||||
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
||||
Ability ability = fromNBT(nbt);
|
||||
if (ability != null) {
|
||||
ability.setId(uuid);
|
||||
customAbilities.put(uuid, ability);
|
||||
if (ability == null) continue;
|
||||
|
||||
// Legacy migration: UUID-named files get renamed to name-based
|
||||
if (UUID_PATTERN.matcher(key).matches()) {
|
||||
String name = ability.getName();
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = key; // Fallback to UUID if no name stored
|
||||
}
|
||||
|
||||
// Ensure unique name
|
||||
while (hasCustomAbilityName(name) || nameFileExists(dir, name)) {
|
||||
name = name + "_";
|
||||
}
|
||||
ability.setName(name);
|
||||
|
||||
// Rename file from UUID.json to name.json
|
||||
File newFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(newFile)) {
|
||||
LogWriter.info("Migrated ability file: " + key + ".json -> " + name + ".json");
|
||||
} else {
|
||||
LogWriter.error("Failed to rename ability file: " + key + ".json -> " + name + ".json");
|
||||
}
|
||||
|
||||
key = name;
|
||||
migrated++;
|
||||
}
|
||||
|
||||
ability.setId(key);
|
||||
customAbilities.put(key, ability);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
@@ -165,21 +211,57 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
customAbilityRevision++;
|
||||
if (migrated > 0) {
|
||||
LogWriter.info("Migrated " + migrated + " UUID-named abilities to name-based files");
|
||||
}
|
||||
LogWriter.info("Loaded " + customAbilities.size() + " custom abilities");
|
||||
}
|
||||
|
||||
private boolean nameFileExists(File dir, String name) {
|
||||
return new File(dir, name + ".json").exists();
|
||||
}
|
||||
|
||||
public boolean saveCustomAbility(Ability ability) {
|
||||
if (ability == null) return false;
|
||||
|
||||
String uuid = ability.getId();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
ability.setId(uuid);
|
||||
String name = ability.getName();
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
|
||||
// Ensure unique name for new abilities (no existing id)
|
||||
String oldName = ability.getId();
|
||||
boolean isNew = oldName == null || oldName.isEmpty();
|
||||
if (isNew) {
|
||||
while (hasCustomAbilityName(name)) {
|
||||
name = name + "_";
|
||||
}
|
||||
ability.setName(name);
|
||||
}
|
||||
|
||||
// Handle rename: old id (previous name) differs from current name
|
||||
if (!isNew && !oldName.equals(name)) {
|
||||
// Check uniqueness of new name (excluding the entry being renamed)
|
||||
String testName = name;
|
||||
while (customAbilities.containsKey(testName) && !testName.equals(oldName)) {
|
||||
testName = testName + "_";
|
||||
}
|
||||
if (!testName.equals(name)) {
|
||||
name = testName;
|
||||
ability.setName(name);
|
||||
}
|
||||
|
||||
// Delete old file and remove old map entry
|
||||
File oldFile = new File(getDir(), oldName + ".json");
|
||||
if (oldFile.exists()) {
|
||||
oldFile.delete();
|
||||
}
|
||||
customAbilities.remove(oldName);
|
||||
}
|
||||
|
||||
ability.setId(name);
|
||||
|
||||
File dir = getDir();
|
||||
File fileNew = new File(dir, uuid + ".json_new");
|
||||
File fileCurrent = new File(dir, uuid + ".json");
|
||||
File fileNew = new File(dir, name + ".json_new");
|
||||
File fileCurrent = new File(dir, name + ".json");
|
||||
|
||||
try {
|
||||
NBTTagCompound nbt = ability.writeNBT();
|
||||
@@ -193,52 +275,43 @@ public class AbilityController implements IAbilityHandler {
|
||||
fileNew.delete();
|
||||
}
|
||||
|
||||
customAbilities.put(uuid, ability);
|
||||
customAbilities.put(name, ability);
|
||||
customAbilityRevision++;
|
||||
LogWriter.info("Saved custom ability: " + ability.getName() + " [" + uuid + "]");
|
||||
LogWriter.info("Saved custom ability: " + name);
|
||||
SyncController.syncAllCustomAbilities();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error saving custom ability: " + uuid, e);
|
||||
LogWriter.error("Error saving custom ability: " + name, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteCustomAbility(String uuid) {
|
||||
if (uuid == null || uuid.isEmpty()) return false;
|
||||
public boolean deleteCustomAbility(String name) {
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
|
||||
Ability removed = customAbilities.remove(uuid);
|
||||
Ability removed = customAbilities.remove(name);
|
||||
if (removed == null) return false;
|
||||
|
||||
File dir = getDir();
|
||||
File file = new File(dir, uuid + ".json");
|
||||
File file = new File(dir, name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
customAbilityRevision++;
|
||||
LogWriter.info("Deleted custom ability: " + uuid);
|
||||
LogWriter.info("Deleted custom ability: " + name);
|
||||
SyncController.syncAllCustomAbilities();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Ability getCustomAbility(String uuid) {
|
||||
return customAbilities.get(uuid);
|
||||
public Ability getCustomAbility(String name) {
|
||||
return customAbilities.get(name);
|
||||
}
|
||||
|
||||
public boolean hasCustomAbility(String uuid) {
|
||||
return customAbilities.containsKey(uuid);
|
||||
}
|
||||
|
||||
public Set<String> getCustomAbilityIds() {
|
||||
public Set<String> getCustomAbilityNames() {
|
||||
return new LinkedHashSet<>(customAbilities.keySet());
|
||||
}
|
||||
|
||||
public String getCustomAbilityName(String uuid) {
|
||||
Ability a = customAbilities.get(uuid);
|
||||
return a != null ? a.getName() : null;
|
||||
}
|
||||
|
||||
public Map<String, Ability> getCustomAbilities() {
|
||||
return customAbilities;
|
||||
}
|
||||
@@ -286,7 +359,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom: exact UUID
|
||||
// Custom: exact name
|
||||
Ability custom = customAbilities.get(key);
|
||||
if (custom != null) return custom.deepCopy();
|
||||
|
||||
@@ -308,7 +381,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
keys.add(id);
|
||||
}
|
||||
}
|
||||
// Use ability names for custom abilities (instead of UUIDs) for readable tab completion
|
||||
// Custom abilities keyed by name
|
||||
for (Ability ability : customAbilities.values()) {
|
||||
String name = ability.getName();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
@@ -328,7 +401,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use ability names for custom abilities (instead of UUIDs) for readable tab completion
|
||||
// Custom abilities keyed by name
|
||||
for (Ability ability : customAbilities.values()) {
|
||||
if (ability.getAllowedBy().allowsPlayer()) {
|
||||
String name = ability.getName();
|
||||
@@ -364,7 +437,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) return true;
|
||||
}
|
||||
|
||||
// Custom: exact UUID
|
||||
// Custom: exact name
|
||||
if (customAbilities.containsKey(key)) return true;
|
||||
|
||||
// Custom: case-insensitive name
|
||||
@@ -497,6 +570,33 @@ public class AbilityController implements IAbilityHandler {
|
||||
return abilityTypes.containsKey(typeId);
|
||||
}
|
||||
|
||||
public boolean isAllowedByPlayer(String typeId) {
|
||||
if (abilityTypes.containsKey(typeId)) {
|
||||
Ability temp = abilityTypes.get(typeId).get();
|
||||
return temp.getAllowedBy() == UserType.PLAYER_ONLY;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isAllowedByNPC(String typeId) {
|
||||
if (abilityTypes.containsKey(typeId)) {
|
||||
Ability temp = abilityTypes.get(typeId).get();
|
||||
return temp.getAllowedBy() == UserType.NPC_ONLY;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isAllowedByBoth(String typeId) {
|
||||
if (abilityTypes.containsKey(typeId)) {
|
||||
Ability temp = abilityTypes.get(typeId).get();
|
||||
return temp.getAllowedBy() == UserType.BOTH;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAbilityNameArray() {
|
||||
return builtAbilities.keySet().toArray(new String[0]);
|
||||
@@ -508,17 +608,17 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCustomAbilityIdArray() {
|
||||
public String[] getCustomAbilityNameArray() {
|
||||
return customAbilities.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomAbilityId(String uuid) {
|
||||
return customAbilities.containsKey(uuid);
|
||||
public boolean hasCustomAbilityName(String name) {
|
||||
return customAbilities.containsKey(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteCustomAbilityById(String uuid) {
|
||||
return deleteCustomAbility(uuid);
|
||||
public boolean deleteCustomAbilityByName(String name) {
|
||||
return deleteCustomAbility(name);
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -9,17 +9,17 @@ import noppes.npcs.constants.EnumPotionType;
|
||||
* Represents a configurable potion effect that can be applied by abilities.
|
||||
* Uses the unified EnumPotionType enum shared across melee, ranged, and ability systems.
|
||||
*/
|
||||
public class AbilityEffect {
|
||||
public class AbilityPotionEffect {
|
||||
|
||||
private EnumPotionType type = EnumPotionType.None;
|
||||
private int manualPotionId = 0;
|
||||
private int durationTicks = 60;
|
||||
private int amplifier = 0; // 0-10
|
||||
|
||||
public AbilityEffect() {
|
||||
public AbilityPotionEffect() {
|
||||
}
|
||||
|
||||
public AbilityEffect(EnumPotionType type, int durationTicks, int amplifier) {
|
||||
public AbilityPotionEffect(EnumPotionType type, int durationTicks, int amplifier) {
|
||||
this.type = type;
|
||||
this.durationTicks = Math.max(1, durationTicks);
|
||||
this.amplifier = Math.max(0, Math.min(10, amplifier));
|
||||
@@ -28,8 +28,8 @@ public class AbilityEffect {
|
||||
/**
|
||||
* Creates a copy of this effect.
|
||||
*/
|
||||
public AbilityEffect copy() {
|
||||
AbilityEffect copy = new AbilityEffect(type, durationTicks, amplifier);
|
||||
public AbilityPotionEffect copy() {
|
||||
AbilityPotionEffect copy = new AbilityPotionEffect(type, durationTicks, amplifier);
|
||||
copy.manualPotionId = this.manualPotionId;
|
||||
return copy;
|
||||
}
|
||||
@@ -81,10 +81,10 @@ public class AbilityEffect {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AbilityEffect from NBT.
|
||||
* Creates an AbilityPotionEffect from NBT.
|
||||
*/
|
||||
public static AbilityEffect fromNBT(NBTTagCompound nbt) {
|
||||
AbilityEffect effect = new AbilityEffect();
|
||||
public static AbilityPotionEffect fromNBT(NBTTagCompound nbt) {
|
||||
AbilityPotionEffect effect = new AbilityPotionEffect();
|
||||
effect.readNBT(nbt);
|
||||
return effect;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class AbilitySlot {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a slot that references a built-in ability (by name) or custom ability (by UUID).
|
||||
* Create a slot that references a built-in ability (by name) or custom ability (by name).
|
||||
*/
|
||||
public static AbilitySlot reference(String referenceId) {
|
||||
AbilitySlot slot = new AbilitySlot();
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
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 {
|
||||
// Knockback target display keys
|
||||
private static final String[] KNOCKBACK_TARGET_KEYS = {"gui.both", "gui.players", "gui.npcs"};
|
||||
|
||||
public float maxHealth = 100.0f;
|
||||
public boolean useHealth = true;
|
||||
public int durationTicks = 200;
|
||||
public boolean useDuration = true;
|
||||
public float defaultMultiplier = 1.0f;
|
||||
|
||||
// Knockback settings
|
||||
public boolean knockbackEnabled = false;
|
||||
public float knockbackStrength = 1.0f;
|
||||
/** 0=Both, 1=Player only, 2=NPC only */
|
||||
public int knockbackTarget = 0;
|
||||
|
||||
// Melee settings
|
||||
public boolean meleeEnabled = false;
|
||||
public float meleeDamageMultiplier = 1.0f;
|
||||
|
||||
/**
|
||||
* Per-projectile-type damage multipliers.
|
||||
* Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam").
|
||||
* 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; }
|
||||
|
||||
// Knockback target key helpers
|
||||
public static String[] getKnockbackTargetKeys() { return KNOCKBACK_TARGET_KEYS; }
|
||||
|
||||
public String getKnockbackTargetKey() {
|
||||
return KNOCKBACK_TARGET_KEYS[Math.max(0, Math.min(knockbackTarget, 2))];
|
||||
}
|
||||
|
||||
public void setKnockbackTargetFromKey(String key) {
|
||||
for (int i = 0; i < KNOCKBACK_TARGET_KEYS.length; i++) {
|
||||
if (KNOCKBACK_TARGET_KEYS[i].equals(key)) {
|
||||
knockbackTarget = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
public void writeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("barrierMaxHealth", maxHealth);
|
||||
nbt.setBoolean("barrierUseHealth", useHealth);
|
||||
nbt.setInteger("barrierDuration", durationTicks);
|
||||
nbt.setBoolean("barrierUseDuration", useDuration);
|
||||
nbt.setFloat("barrierDefaultMult", defaultMultiplier);
|
||||
nbt.setBoolean("barrierKnockback", knockbackEnabled);
|
||||
nbt.setFloat("barrierKnockbackStr", knockbackStrength);
|
||||
nbt.setInteger("barrierKnockbackTarget", knockbackTarget);
|
||||
nbt.setBoolean("barrierMelee", meleeEnabled);
|
||||
nbt.setFloat("barrierMeleeMult", meleeDamageMultiplier);
|
||||
|
||||
NBTTagCompound multNbt = new NBTTagCompound();
|
||||
for (Map.Entry<String, Float> entry : damageMultipliers.entrySet()) {
|
||||
multNbt.setFloat(entry.getKey(), entry.getValue());
|
||||
}
|
||||
nbt.setTag("barrierMultipliers", multNbt);
|
||||
}
|
||||
|
||||
public void readNBT(NBTTagCompound nbt) {
|
||||
maxHealth = nbt.hasKey("barrierMaxHealth") ? nbt.getFloat("barrierMaxHealth") : 100.0f;
|
||||
useHealth = !nbt.hasKey("barrierUseHealth") || nbt.getBoolean("barrierUseHealth");
|
||||
durationTicks = nbt.hasKey("barrierDuration") ? nbt.getInteger("barrierDuration") : 200;
|
||||
useDuration = !nbt.hasKey("barrierUseDuration") || nbt.getBoolean("barrierUseDuration");
|
||||
defaultMultiplier = nbt.hasKey("barrierDefaultMult") ? nbt.getFloat("barrierDefaultMult") : 1.0f;
|
||||
knockbackEnabled = nbt.hasKey("barrierKnockback") && nbt.getBoolean("barrierKnockback");
|
||||
knockbackStrength = nbt.hasKey("barrierKnockbackStr") ? nbt.getFloat("barrierKnockbackStr") : 1.0f;
|
||||
knockbackTarget = nbt.hasKey("barrierKnockbackTarget") ? nbt.getInteger("barrierKnockbackTarget") : 0;
|
||||
meleeEnabled = nbt.hasKey("barrierMelee") && nbt.getBoolean("barrierMelee");
|
||||
meleeDamageMultiplier = nbt.hasKey("barrierMeleeMult") ? nbt.getFloat("barrierMeleeMult") : 1.0f;
|
||||
|
||||
damageMultipliers.clear();
|
||||
if (nbt.hasKey("barrierMultipliers")) {
|
||||
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.knockbackEnabled = knockbackEnabled;
|
||||
copy.knockbackStrength = knockbackStrength;
|
||||
copy.knockbackTarget = knockbackTarget;
|
||||
copy.meleeEnabled = meleeEnabled;
|
||||
copy.meleeDamageMultiplier = meleeDamageMultiplier;
|
||||
copy.damageMultipliers = new HashMap<>(damageMultipliers);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,13 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
public boolean outerColorEnabled = true;
|
||||
public float outerColorWidth = 0.4f;
|
||||
public float outerColorAlpha = 0.5f;
|
||||
public float innerAlpha = 1.0f;
|
||||
public float rotationSpeed = 4.0f;
|
||||
|
||||
// TODO: Skybox Feature
|
||||
// public boolean skyboxEnabled = false;
|
||||
// public String skyboxTexture = "";
|
||||
|
||||
public EnergyDisplayData() {}
|
||||
|
||||
public EnergyDisplayData(int innerColor, int outerColor) {
|
||||
@@ -32,6 +37,12 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
this.rotationSpeed = rotationSpeed;
|
||||
}
|
||||
|
||||
public EnergyDisplayData(int innerColor, int outerColor, boolean outerColorEnabled,
|
||||
float outerColorWidth, float outerColorAlpha, float innerAlpha, float rotationSpeed) {
|
||||
this(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, rotationSpeed);
|
||||
this.innerAlpha = innerAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInnerColor() {
|
||||
return innerColor;
|
||||
@@ -82,6 +93,16 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
this.outerColorAlpha = outerColorAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getInnerAlpha() {
|
||||
return innerAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInnerAlpha(float innerAlpha) {
|
||||
this.innerAlpha = innerAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRotationSpeed() {
|
||||
return rotationSpeed;
|
||||
@@ -98,7 +119,11 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
nbt.setBoolean("outerColorEnabled", outerColorEnabled);
|
||||
nbt.setFloat("outerColorWidth", outerColorWidth);
|
||||
nbt.setFloat("outerColorAlpha", outerColorAlpha);
|
||||
nbt.setFloat("innerAlpha", innerAlpha);
|
||||
nbt.setFloat("rotationSpeed", rotationSpeed);
|
||||
// TODO: Skybox Feature
|
||||
// nbt.setBoolean("skyboxEnabled", skyboxEnabled);
|
||||
// nbt.setString("skyboxTexture", skyboxTexture);
|
||||
}
|
||||
|
||||
public void readNBT(NBTTagCompound nbt) {
|
||||
@@ -107,10 +132,18 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
outerColorEnabled = nbt.getBoolean("outerColorEnabled");
|
||||
outerColorWidth = nbt.getFloat("outerColorWidth");
|
||||
outerColorAlpha = nbt.getFloat("outerColorAlpha");
|
||||
innerAlpha = nbt.hasKey("innerAlpha") ? nbt.getFloat("innerAlpha") : 1.0f;
|
||||
rotationSpeed = nbt.getFloat("rotationSpeed");
|
||||
// TODO: Skybox Feature
|
||||
// skyboxEnabled = nbt.hasKey("skyboxEnabled") && nbt.getBoolean("skyboxEnabled");
|
||||
// skyboxTexture = nbt.hasKey("skyboxTexture") ? nbt.getString("skyboxTexture") : "";
|
||||
}
|
||||
|
||||
public EnergyDisplayData copy() {
|
||||
return new EnergyDisplayData(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, rotationSpeed);
|
||||
EnergyDisplayData copy = new EnergyDisplayData(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, innerAlpha, rotationSpeed);
|
||||
// TODO: Skybox Feature
|
||||
// copy.skyboxEnabled = skyboxEnabled;
|
||||
// copy.skyboxTexture = skyboxTexture;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -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.hasKey("panelHeightOffset") ? nbt.getFloat("panelHeightOffset") : 0.0f;
|
||||
launching = nbt.hasKey("panelLaunching") && nbt.getBoolean("panelLaunching");
|
||||
launchSpeed = nbt.hasKey("panelLaunchSpeed") ? nbt.getFloat("panelLaunchSpeed") : 0.5f;
|
||||
launchDamage = nbt.hasKey("panelLaunchDamage") ? nbt.getFloat("panelLaunchDamage") : 8.0f;
|
||||
launchKnockback = nbt.hasKey("panelLaunchKnockback") ? nbt.getFloat("panelLaunchKnockback") : 2.0f;
|
||||
}
|
||||
|
||||
public EnergyPanelData copy() {
|
||||
EnergyPanelData copy = new EnergyPanelData(panelWidth, panelHeight, heightOffset);
|
||||
copy.launching = launching;
|
||||
copy.launchSpeed = launchSpeed;
|
||||
copy.launchDamage = launchDamage;
|
||||
copy.launchKnockback = launchKnockback;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.gui;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
@@ -55,14 +55,14 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
|
||||
sw.addLabel(new GuiNpcLabel(labelId++, def.getLabel(), colLLabel, y + 2, 0xFFFF55));
|
||||
y += 15;
|
||||
|
||||
List<AbilityEffect> effects = (List<AbilityEffect>) def.getValue();
|
||||
List<AbilityPotionEffect> effects = (List<AbilityPotionEffect>) def.getValue();
|
||||
if (effects == null) effects = new ArrayList<>();
|
||||
|
||||
String[] typeNames = EnumPotionType.getLangKeysNoNone();
|
||||
String[] ampValues = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
|
||||
|
||||
for (int e = 0; e < effects.size() && e < 5; e++) {
|
||||
AbilityEffect effect = effects.get(e);
|
||||
AbilityPotionEffect effect = effects.get(e);
|
||||
|
||||
// Type selector button (index offset by -1 since None is excluded)
|
||||
int typeIdx = effect.getType().ordinal() - 1;
|
||||
@@ -144,7 +144,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
|
||||
FieldDef def = buttonFieldMap.get(buttonId);
|
||||
if (def == null) def = clearFieldMap.get(buttonId);
|
||||
if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
|
||||
List<AbilityEffect> effects = (List<AbilityEffect>) def.getValue();
|
||||
List<AbilityPotionEffect> effects = (List<AbilityPotionEffect>) def.getValue();
|
||||
if (effects == null) return false;
|
||||
int effectIdx = meta[0];
|
||||
int action = meta[1];
|
||||
@@ -167,7 +167,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
|
||||
return true;
|
||||
case 4: // Add
|
||||
if (effects.size() < 5) {
|
||||
effects.add(new AbilityEffect(EnumPotionType.Slowness, 60, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 60, 0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
|
||||
if (meta != null) {
|
||||
FieldDef def = textFieldMap.get(textFieldId);
|
||||
if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
|
||||
List<AbilityEffect> effects = (List<AbilityEffect>) def.getValue();
|
||||
List<AbilityPotionEffect> effects = (List<AbilityPotionEffect>) def.getValue();
|
||||
if (effects != null && meta[0] < effects.size()) {
|
||||
if (meta[1] == 1) { // duration field
|
||||
effects.get(meta[0]).setDurationTicks(field.getInteger());
|
||||
|
||||
@@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.gui;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import noppes.npcs.client.gui.builder.FieldType;
|
||||
|
||||
@@ -18,10 +18,10 @@ import java.util.function.Supplier;
|
||||
public class AbilityFieldDefs {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static FieldDef effectsListField(String label, Supplier<List<AbilityEffect>> getter, Consumer<List<AbilityEffect>> setter) {
|
||||
public static FieldDef effectsListField(String label, Supplier<List<AbilityPotionEffect>> getter, Consumer<List<AbilityPotionEffect>> setter) {
|
||||
return FieldDef.custom(label, FieldType.EFFECTS_LIST,
|
||||
() -> getter.get(),
|
||||
v -> setter.accept((List<AbilityEffect>) v));
|
||||
v -> setter.accept((List<AbilityPotionEffect>) v));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -373,7 +373,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
|
||||
FieldDef.soundSubGui("ability.counterSound", this::getCounterSound, this::setCounterSound)
|
||||
.tab("Effects").visibleWhen(this::canCounter));
|
||||
|
||||
FieldDef.insertAfter(defs, "ability.activeAnimation",
|
||||
FieldDef.insertAfter(defs, "ability.dazedAnimation",
|
||||
FieldDef.animSubGui("ability.counterAnimation",
|
||||
this::getCounterAnimationId, this::setCounterAnimationId,
|
||||
this::getCounterAnimationName, this::setCounterAnimationName)
|
||||
|
||||
@@ -27,6 +27,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
private float knockback = 2.0f;
|
||||
private float hitLength = 4.0f; // How far in front of the caster the hit reaches
|
||||
private float hitWidth = 3.0f; // How wide to each side (total width = hitWidth * 2)
|
||||
private int hitDelayTicks = 0;
|
||||
private int activeDisplayTicks = 10; // How long active animation plays before completing
|
||||
|
||||
public AbilityHeavyHit() {
|
||||
@@ -70,10 +71,24 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (isPreview()) {
|
||||
if (tick >= (activeDisplayTicks + hitDelayTicks))
|
||||
signalCompletion();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPreview()) {
|
||||
if (tick >= (activeDisplayTicks + hitDelayTicks)) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tick == hitDelayTicks) {
|
||||
// Calculate forward and right vectors from caster yaw
|
||||
float yawRad = (float) Math.toRadians(caster.rotationYaw);
|
||||
double forwardX = -Math.sin(yawRad);
|
||||
@@ -118,12 +133,6 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (tick >= activeDisplayTicks)
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("damage", damage);
|
||||
@@ -131,6 +140,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
nbt.setFloat("hitLength", hitLength);
|
||||
nbt.setFloat("hitWidth", hitWidth);
|
||||
nbt.setInteger("activeDisplayTicks", activeDisplayTicks);
|
||||
nbt.setInteger("hitDelayTicks", hitDelayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +149,8 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
this.knockback = nbt.getFloat("knockback");
|
||||
this.hitLength = nbt.getFloat("hitLength");
|
||||
this.hitWidth = nbt.getFloat("hitWidth");
|
||||
this.activeDisplayTicks = nbt.hasKey("activeDisplayTicks") ? nbt.getInteger("activeDisplayTicks") : 10;
|
||||
this.hitDelayTicks = nbt.getInteger("hitDelayTicks");
|
||||
this.activeDisplayTicks = nbt.getInteger("activeDisplayTicks");
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -183,6 +194,14 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
this.activeDisplayTicks = activeDisplayTicks;
|
||||
}
|
||||
|
||||
public int getHitDelayTicks() {
|
||||
return hitDelayTicks;
|
||||
}
|
||||
|
||||
public void setHitDelayTicks(int hitDelayTicks) {
|
||||
this.hitDelayTicks = hitDelayTicks;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
@@ -196,7 +215,11 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
FieldDef.floatField("ability.hitLength", this::getHitLength, this::setHitLength),
|
||||
FieldDef.floatField("ability.hitWidth", this::getHitWidth, this::setHitWidth)
|
||||
),
|
||||
FieldDef.intField("ability.activeDisplayTicks", this::getActiveDisplayTicks, this::setActiveDisplayTicks).range(1, 200),
|
||||
FieldDef.section("ability.section.timing"),
|
||||
FieldDef.row(
|
||||
FieldDef.intField("ability.hitDelayTicks", this::getHitDelayTicks, this::setHitDelayTicks).min(0),
|
||||
FieldDef.intField("ability.activeDisplayTicks", this::getActiveDisplayTicks, this::setActiveDisplayTicks).range(1, 200)
|
||||
),
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
@@ -119,7 +119,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleMotion = 1; particleDir = "mc:mobSpell";
|
||||
colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611;
|
||||
windUpColor = 0x6044DD44; activeColor = 0xC044FF44;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Poison, 100, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0));
|
||||
break;
|
||||
case "EXPLOSIVE":
|
||||
groundAlpha = 0.04f;
|
||||
@@ -127,7 +127,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleMotion = 0; particleDir = "mc:smoke";
|
||||
colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200;
|
||||
windUpColor = 0x60FF6611; activeColor = 0xC0FF4400;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Fire, 60, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0));
|
||||
break;
|
||||
case "CURSED":
|
||||
groundAlpha = 0.04f;
|
||||
@@ -135,7 +135,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleMotion = 1; particleDir = "mc:portal";
|
||||
colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB;
|
||||
windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Weakness, 100, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0));
|
||||
break;
|
||||
case "SHOCK":
|
||||
groundAlpha = 0.04f;
|
||||
@@ -143,7 +143,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleMotion = 2; particleDir = "mc:enchantmenttable";
|
||||
colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB;
|
||||
windUpColor = 0x604488FF; activeColor = 0xC066AAFF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.MiningFatigue, 80, 1));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1));
|
||||
break;
|
||||
case "SNARE":
|
||||
groundAlpha = 0.04f;
|
||||
@@ -151,7 +151,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleMotion = 1; particleDir = "mc:snowshovel";
|
||||
colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC;
|
||||
windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Slowness, 100, 1));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1));
|
||||
break;
|
||||
case "HIDDEN":
|
||||
default:
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
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.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone.AccentStyle;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone.ParticleMotion;
|
||||
@@ -220,7 +219,7 @@ public abstract class AbilityZone extends Ability {
|
||||
particleMotion = 1; particleGlow = true; particleDir = "mc:mobSpell";
|
||||
colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611;
|
||||
windUpColor = 0x6044DD44; activeColor = 0xC044FF44;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Poison, 100, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0));
|
||||
break;
|
||||
case "INFERNO":
|
||||
groundFill = true; groundAlpha = 0.35f; rings = true; ringCount = 3;
|
||||
@@ -229,7 +228,7 @@ public abstract class AbilityZone extends Ability {
|
||||
particleMotion = 0; particleGlow = true; particleDir = "mc:flame";
|
||||
colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200;
|
||||
windUpColor = 0x60FF6611; activeColor = 0xC0FF4400;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Fire, 60, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0));
|
||||
break;
|
||||
case "ARCANE":
|
||||
groundFill = true; groundAlpha = 0.20f; rings = true; ringCount = 1;
|
||||
@@ -238,7 +237,7 @@ public abstract class AbilityZone extends Ability {
|
||||
particleMotion = 1; particleGlow = true; particleDir = "mc:portal";
|
||||
colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB;
|
||||
windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Weakness, 100, 0));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0));
|
||||
break;
|
||||
case "ELECTRIC":
|
||||
groundFill = true; groundAlpha = 0.15f; rings = false; ringCount = 1;
|
||||
@@ -247,7 +246,7 @@ public abstract class AbilityZone extends Ability {
|
||||
particleMotion = 2; particleGlow = true; particleDir = "mc:enchantmenttable";
|
||||
colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB;
|
||||
windUpColor = 0x604488FF; activeColor = 0xC066AAFF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.MiningFatigue, 80, 1));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1));
|
||||
break;
|
||||
case "FROST":
|
||||
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
|
||||
@@ -256,7 +255,7 @@ public abstract class AbilityZone extends Ability {
|
||||
particleMotion = 1; particleGlow = true; particleDir = "mc:snowshovel";
|
||||
colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC;
|
||||
windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF;
|
||||
effects.add(new AbilityEffect(EnumPotionType.Slowness, 100, 1));
|
||||
effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1));
|
||||
break;
|
||||
case "DEFAULT":
|
||||
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
|
||||
|
||||
+2
-3
@@ -1,9 +1,8 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.RotationMode;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
@@ -25,7 +24,7 @@ import java.util.List;
|
||||
* Energy Beam ability: A homing head with a trailing path.
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
*/
|
||||
public class AbilityBeam extends AbstractEnergyProjectileAbility<EntityAbilityBeam> implements IAbilityEnergyBeam {
|
||||
public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> implements IAbilityEnergyBeam {
|
||||
|
||||
private float beamWidth = 0.4f;
|
||||
private float headSize = 0.6f;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
@@ -23,7 +23,7 @@ import java.util.List;
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
* Has optional boomerang behavior to return to owner.
|
||||
*/
|
||||
public class AbilityDisc extends AbstractEnergyProjectileAbility<EntityAbilityDisc> implements IAbilityDisc {
|
||||
public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> implements IAbilityDisc {
|
||||
|
||||
private float discRadius = 1.0f;
|
||||
private float discThickness = 0.2f;
|
||||
@@ -0,0 +1,130 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.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.EntityEnergyBarrier;
|
||||
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 AbilityDome extends AbilityEnergyBarrier {
|
||||
|
||||
private float domeRadius = 5.0f;
|
||||
private boolean followCaster = false;
|
||||
|
||||
public AbilityDome() {
|
||||
super(
|
||||
new EnergyDisplayData(0x44CCFF, 0x2288FF, true, 0.3f, 0.35f, 0.5f, 0.0f),
|
||||
new EnergyBarrierData(100.0f, true, 200, true)
|
||||
);
|
||||
this.typeId = "ability.cnpc.dome";
|
||||
this.name = "Dome";
|
||||
this.targetingMode = TargetingMode.SELF;
|
||||
this.cooldownTicks = 100;
|
||||
this.windUpTicks = 30;
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.RING;
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "";
|
||||
this.activeAnimationName = "";
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityEnergyBarrier createBarrierEntity(EntityLivingBase caster, EntityLivingBase target) {
|
||||
EntityEnergyDome dome = new EntityEnergyDome(
|
||||
caster.worldObj, caster,
|
||||
caster.posX, caster.posY, caster.posZ,
|
||||
domeRadius, displayData.copy(), lightningData.copy(), barrierData.copy()
|
||||
);
|
||||
dome.setFollowCaster(followCaster);
|
||||
dome.setSourceAbility(this);
|
||||
return dome;
|
||||
}
|
||||
|
||||
@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 -> {
|
||||
AbilityDome dome = (AbilityDome) a;
|
||||
a.setName("Small Dome");
|
||||
dome.setDomeRadius(3.0f);
|
||||
dome.setBarrierMaxHealth(60.0f);
|
||||
}),
|
||||
new AbilityVariant("ability.variant.large", a -> {
|
||||
AbilityDome dome = (AbilityDome) a;
|
||||
a.setName("Large 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);
|
||||
nbt.setBoolean("followCaster", followCaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readBarrierTypeNBT(NBTTagCompound nbt) {
|
||||
this.domeRadius = nbt.hasKey("domeRadius") ? nbt.getFloat("domeRadius") : 5.0f;
|
||||
this.followCaster = nbt.hasKey("followCaster") && nbt.getBoolean("followCaster");
|
||||
}
|
||||
|
||||
// ==================== GETTERS & SETTERS ====================
|
||||
|
||||
public float getDomeRadius() { return domeRadius; }
|
||||
public void setDomeRadius(float radius) { this.domeRadius = Math.max(1.0f, radius); }
|
||||
|
||||
public boolean isFollowCaster() { return followCaster; }
|
||||
public void setFollowCaster(boolean follow) { this.followCaster = follow; }
|
||||
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
protected void addBarrierTypeDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.floatField("ability.domeRadius", this::getDomeRadius, this::setDomeRadius)
|
||||
.range(1.0f, 30.0f));
|
||||
defs.add(FieldDef.boolField("ability.followCaster", this::isFollowCaster, this::setFollowCaster)
|
||||
.hover("ability.hover.followCaster"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base for all energy-based abilities (Projectile, Barrier, Sweeper, etc.).
|
||||
* Provides shared visual data (colors, lightning) and corresponding API/GUI/NBT.
|
||||
*/
|
||||
public abstract class AbilityEnergy extends Ability {
|
||||
|
||||
protected EnergyDisplayData displayData;
|
||||
protected EnergyLightningData lightningData;
|
||||
|
||||
protected AbilityEnergy(EnergyDisplayData displayData) {
|
||||
this.displayData = displayData;
|
||||
this.lightningData = new EnergyLightningData();
|
||||
}
|
||||
|
||||
// ==================== DISPLAY DATA ====================
|
||||
|
||||
public int getInnerColor() { return displayData.innerColor; }
|
||||
public void setInnerColor(int color) { displayData.innerColor = color; }
|
||||
|
||||
public float getInnerAlpha() { return displayData.innerAlpha; }
|
||||
public void setInnerAlpha(float alpha) { displayData.innerAlpha = alpha; }
|
||||
|
||||
public int getOuterColor() { return displayData.outerColor; }
|
||||
public void setOuterColor(int color) { displayData.outerColor = color; }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; }
|
||||
|
||||
// ==================== LIGHTNING DATA ====================
|
||||
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; }
|
||||
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float density) { lightningData.lightningDensity = density; }
|
||||
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; }
|
||||
|
||||
// ==================== NBT HELPERS ====================
|
||||
|
||||
protected void writeEnergyNBT(NBTTagCompound nbt) {
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
protected void readEnergyNBT(NBTTagCompound nbt) {
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== GUI HELPERS ====================
|
||||
|
||||
/**
|
||||
* Add complete visual tab definitions (colors section + effects/lightning section).
|
||||
* Subclasses can call this directly or use the individual methods for more control.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
protected void addEnergyVisualDefinitions(List<FieldDef> defs) {
|
||||
addEnergyColorDefinitions(defs);
|
||||
addEnergyEffectDefinitions(defs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the colors section to the visual tab.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
protected void addEnergyColorDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor),
|
||||
FieldDef.floatField("ability.innerAlpha", this::getInnerAlpha, this::setInnerAlpha).range(0, 1)
|
||||
).tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the effects section (lightning) to the visual tab.
|
||||
* Override to insert additional fields before lightning (e.g., rotationSpeed).
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
protected void addEnergyEffectDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
addEnergyLightningDefinitions(defs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add just the lightning fields (no section header) to the visual tab.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
protected void addEnergyLightningDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.visibleWhen(this::hasLightningEffect).range(0.01f, 100f),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual"));
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base for energy barrier abilities (Dome, Wall, Shield).
|
||||
* Handles shared visual data, barrier configuration, and lifecycle management.
|
||||
*/
|
||||
public abstract class AbilityEnergyBarrier extends AbilityEnergy {
|
||||
|
||||
protected final EnergyBarrierData barrierData;
|
||||
|
||||
// Runtime entity tracking
|
||||
protected transient EntityEnergyBarrier barrierEntity;
|
||||
|
||||
protected AbilityEnergyBarrier(EnergyDisplayData displayData, EnergyBarrierData barrierData) {
|
||||
super(displayData);
|
||||
this.barrierData = barrierData;
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT METHODS ====================
|
||||
|
||||
/**
|
||||
* Create and spawn the barrier entity during execution.
|
||||
*/
|
||||
protected abstract EntityEnergyBarrier 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 onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (caster.worldObj.isRemote) return;
|
||||
|
||||
if (tick == 1) {
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
barrierEntity.setupCharging(getWindUpTicks());
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (barrierEntity != null && !barrierEntity.isDead) {
|
||||
// Barrier was spawned during windup — transition to active
|
||||
barrierEntity.finishCharging();
|
||||
} else {
|
||||
// No windup or entity died — create fresh
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
writeEnergyNBT(nbt);
|
||||
barrierData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readTypeNBT(NBTTagCompound nbt) {
|
||||
readBarrierTypeNBT(nbt);
|
||||
readEnergyNBT(nbt);
|
||||
barrierData.readNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== API GETTERS & SETTERS ====================
|
||||
|
||||
// Display and lightning data inherited from AbilityEnergy
|
||||
|
||||
// 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); }
|
||||
|
||||
// Knockback data
|
||||
public boolean isKnockbackEnabled() { return barrierData.knockbackEnabled; }
|
||||
public void setKnockbackEnabled(boolean enabled) { barrierData.knockbackEnabled = enabled; }
|
||||
public float getKnockbackStrength() { return barrierData.knockbackStrength; }
|
||||
public void setKnockbackStrength(float strength) { barrierData.knockbackStrength = strength; }
|
||||
public String getKnockbackTargetKey() { return barrierData.getKnockbackTargetKey(); }
|
||||
public void setKnockbackTargetKey(String key) { barrierData.setKnockbackTargetFromKey(key); }
|
||||
|
||||
// Melee data
|
||||
public boolean isMeleeEnabled() { return barrierData.meleeEnabled; }
|
||||
public void setMeleeEnabled(boolean enabled) { barrierData.meleeEnabled = enabled; }
|
||||
public float getMeleeDamageMultiplier() { return barrierData.meleeDamageMultiplier; }
|
||||
public void setMeleeDamageMultiplier(float mult) { barrierData.meleeDamageMultiplier = mult; }
|
||||
|
||||
// ==================== GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@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));
|
||||
|
||||
// Knockback section
|
||||
defs.add(FieldDef.section("ability.section.knockback"));
|
||||
defs.add(FieldDef.boolField("ability.knockbackEnabled", this::isKnockbackEnabled, this::setKnockbackEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength)
|
||||
.range(0, 10).visibleWhen(this::isKnockbackEnabled),
|
||||
FieldDef.stringEnumField("ability.knockbackTarget", EnergyBarrierData.getKnockbackTargetKeys(),
|
||||
this::getKnockbackTargetKey, this::setKnockbackTargetKey)
|
||||
.visibleWhen(this::isKnockbackEnabled)
|
||||
));
|
||||
|
||||
// Melee section
|
||||
defs.add(FieldDef.section("ability.section.melee"));
|
||||
defs.add(FieldDef.boolField("ability.meleeEnabled", this::isMeleeEnabled, this::setMeleeEnabled));
|
||||
defs.add(FieldDef.floatField("ability.meleeDamageMultiplier", this::getMeleeDamageMultiplier, this::setMeleeDamageMultiplier)
|
||||
.range(0, 10).visibleWhen(this::isMeleeEnabled));
|
||||
|
||||
// Visual tab - colors + effects (from AbilityEnergy)
|
||||
addEnergyVisualDefinitions(defs);
|
||||
}
|
||||
}
|
||||
+31
-70
@@ -1,4 +1,4 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
@@ -6,11 +6,10 @@ import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
@@ -30,8 +29,7 @@ import java.util.List;
|
||||
*
|
||||
* @param <E> The entity type this ability spawns
|
||||
*/
|
||||
public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityProjectile>
|
||||
extends Ability implements IAbilityEnergyProjectile {
|
||||
public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile> extends AbilityEnergy implements IAbilityEnergyProjectile {
|
||||
|
||||
protected static final int MAX_PROJECTILES = 8;
|
||||
|
||||
@@ -39,9 +37,7 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
protected int projectileCount = 1;
|
||||
protected int fireDelay = 0;
|
||||
|
||||
// Shared visual data
|
||||
protected EnergyDisplayData displayData;
|
||||
protected EnergyLightningData lightningData;
|
||||
// Trajectory data
|
||||
protected EnergyTrajectoryData trajectoryData;
|
||||
|
||||
// Shared combat/movement data
|
||||
@@ -57,17 +53,16 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
protected AbstractEnergyProjectileAbility(
|
||||
protected AbilityEnergyProjectile(
|
||||
EnergyDisplayData displayData,
|
||||
EnergyCombatData combatData,
|
||||
EnergyHomingData homingData,
|
||||
EnergyLifespanData lifespanData
|
||||
) {
|
||||
this.displayData = displayData;
|
||||
super(displayData);
|
||||
this.combatData = combatData;
|
||||
this.homingData = homingData;
|
||||
this.lifespanData = lifespanData;
|
||||
this.lightningData = new EnergyLightningData();
|
||||
this.trajectoryData = new EnergyTrajectoryData();
|
||||
initProjectiles(1);
|
||||
}
|
||||
@@ -207,9 +202,13 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
|
||||
/**
|
||||
* Fire an entity if it's valid.
|
||||
* Clears charging state first so the entity can move and check collisions.
|
||||
*/
|
||||
protected void fireEntitySafe(E entity, EntityLivingBase target) {
|
||||
if (entity != null && !entity.isDead) {
|
||||
if (entity.isCharging()) {
|
||||
entity.setCharging(false);
|
||||
}
|
||||
fireEntity(entity, target);
|
||||
}
|
||||
}
|
||||
@@ -266,6 +265,18 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
cleanup();
|
||||
}
|
||||
entities = createAllEntities(caster, target);
|
||||
|
||||
// If there's a fire delay, put unfired entities in charging state
|
||||
// to prevent collision checks before they're actually fired.
|
||||
// Entity[0] will be fired immediately below, so skip it.
|
||||
if (fireDelay > 0 && projectileCount > 1) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (entities[i] != null) {
|
||||
entities[i].setCharging(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (E entity : entities) {
|
||||
spawnAbilityEntity(entity);
|
||||
}
|
||||
@@ -304,7 +315,8 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
if (entity == null || entity.isDead) continue;
|
||||
|
||||
// Check if entity was removed from world (chunk unload, etc.)
|
||||
if (tick > 5 && entity.worldObj != null
|
||||
// Skip this check for preview entities - they're not registered in the world
|
||||
if (!isPreview() && tick > 5 && entity.worldObj != null
|
||||
&& entity.worldObj.getEntityByID(entity.getEntityId()) != entity) {
|
||||
entity.setDead();
|
||||
continue;
|
||||
@@ -390,8 +402,7 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared data classes
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
writeEnergyNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
@@ -415,8 +426,7 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
|
||||
// Shared data classes
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
readEnergyNBT(nbt);
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
@@ -474,37 +484,7 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float range) { homingData.homingRange = range; }
|
||||
|
||||
// Display data - primary colors
|
||||
@Override public int getInnerColor() { return displayData.innerColor; }
|
||||
@Override public void setInnerColor(int color) { displayData.innerColor = color; }
|
||||
|
||||
@Override public int getOuterColor() { return displayData.outerColor; }
|
||||
@Override public void setOuterColor(int color) { displayData.outerColor = color; }
|
||||
|
||||
@Override public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
@Override public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; }
|
||||
|
||||
@Override public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
@Override public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; }
|
||||
|
||||
@Override public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
@Override public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; }
|
||||
|
||||
// Lightning data
|
||||
@Override public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
@Override public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; }
|
||||
|
||||
@Override public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
@Override public void setLightningDensity(float density) { lightningData.lightningDensity = density; }
|
||||
|
||||
@Override public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
@Override public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; }
|
||||
// Display and lightning data inherited from AbilityEnergy
|
||||
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
@@ -557,33 +537,14 @@ public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityPro
|
||||
// Type-specific fields first
|
||||
addTypeDefinitions(defs);
|
||||
|
||||
// Shared visual tab - primary colors
|
||||
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"));
|
||||
// Shared visual tab - colors + effects (from AbilityEnergy)
|
||||
addEnergyColorDefinitions(defs);
|
||||
|
||||
// Shared visual tab - effects
|
||||
// Effects section with projectile-specific rotationSpeed before lightning
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.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"));
|
||||
addEnergyLightningDefinitions(defs);
|
||||
|
||||
// Shared visual tab - per-projectile sections
|
||||
for (int i = 0; i < MAX_PROJECTILES; i++) {
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
@@ -25,7 +25,7 @@ import java.util.List;
|
||||
* Travels in a straight line from origin, damaging all entities it passes through.
|
||||
* Single-projectile only (always projectileCount=1).
|
||||
*/
|
||||
public class AbilityLaserShot extends AbstractEnergyProjectileAbility<EntityAbilityLaser> implements IAbilityLaserShot {
|
||||
public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser> implements IAbilityLaserShot {
|
||||
|
||||
private float laserWidth = 0.3f;
|
||||
private float expansionSpeed = 3.0f;
|
||||
@@ -73,7 +73,7 @@ public class AbilityLaserShot extends AbstractEnergyProjectileAbility<EntityAbil
|
||||
|
||||
/**
|
||||
* LINE telegraph: positioned at caster, aimed toward target.
|
||||
* Overrides the circle telegraph from AbstractEnergyProjectileAbility.
|
||||
* Overrides the circle telegraph from AbilityEnergyProjectile.
|
||||
*/
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
@@ -22,7 +22,7 @@ import java.util.List;
|
||||
* Orb ability: Spawns homing projectile sphere(s) that track target.
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
*/
|
||||
public class AbilityOrb extends AbstractEnergyProjectileAbility<EntityAbilityOrb> implements IAbilityOrb {
|
||||
public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implements IAbilityOrb {
|
||||
|
||||
private float orbSize = 1.0f;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.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.EntityEnergyBarrier;
|
||||
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 AbilityShield extends AbilityEnergyBarrier {
|
||||
|
||||
private float shieldWidth = 2.5f;
|
||||
private float shieldHeight = 2.5f;
|
||||
|
||||
public AbilityShield() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFDD44, 0xFFAA00, true, 0.3f, 0.4f, 0.5f, 0.0f),
|
||||
new EnergyBarrierData(60.0f, true, 120, true)
|
||||
);
|
||||
this.typeId = "ability.cnpc.shield";
|
||||
this.name = "Shield";
|
||||
this.targetingMode = TargetingMode.SELF;
|
||||
this.cooldownTicks = 60;
|
||||
this.windUpTicks = 10;
|
||||
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
|
||||
this.telegraphType = TelegraphType.NONE;
|
||||
this.showTelegraph = false;
|
||||
this.windUpAnimationName = "";
|
||||
this.activeAnimationName = "";
|
||||
this.allowedBy = UserType.BOTH;
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityEnergyBarrier 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 -> {
|
||||
AbilityShield shield = (AbilityShield) a;
|
||||
a.setName("Small Shield");
|
||||
shield.setShieldWidth(1.5f);
|
||||
shield.setShieldHeight(1.5f);
|
||||
shield.setBarrierMaxHealth(40.0f);
|
||||
}),
|
||||
new AbilityVariant("ability.variant.large", a -> {
|
||||
AbilityShield shield = (AbilityShield) a;
|
||||
a.setName("Large 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.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.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 AbilityEnergyProjectile to leverage the existing projectile infrastructure.
|
||||
*/
|
||||
public class AbilitySlicer extends AbilityEnergyProjectile<EntityEnergySlicer> {
|
||||
|
||||
private float sliceWidth = 3.0f;
|
||||
private float sliceThickness = 0.15f;
|
||||
private boolean piercing = true;
|
||||
|
||||
public AbilitySlicer() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFF4444, 0xFF0000, true, 0.2f, 0.6f, 0.0f),
|
||||
new EnergyCombatData(12.0f, 0.5f, 0.0f, false, 3.0f, 0.5f),
|
||||
new EnergyHomingData(0.8f, false, 0.0f, 0.0f),
|
||||
new EnergyLifespanData(40.0f, 100)
|
||||
);
|
||||
this.typeId = "ability.cnpc.slicer";
|
||||
this.name = "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 = "";
|
||||
this.activeAnimationName = "";
|
||||
}
|
||||
|
||||
// ==================== 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("Slicer");
|
||||
}),
|
||||
new AbilityVariant("ability.variant.dual", a -> {
|
||||
AbilitySlicer slicer = (AbilitySlicer) a;
|
||||
a.setName("Double Slicer");
|
||||
slicer.setProjectileCount(2);
|
||||
slicer.setFireDelay(5);
|
||||
a.setWindUpAnimationName("");
|
||||
a.setActiveAnimationName("");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 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));
|
||||
}
|
||||
}
|
||||
+12
-36
@@ -1,12 +1,10 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type;
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
@@ -14,8 +12,8 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilitySweeper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import noppes.npcs.api.ability.type.IAbilitySweeper;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -28,7 +26,7 @@ import java.util.List;
|
||||
* All damage logic is handled by the EntityAbilitySweeper entity to ensure
|
||||
* visual and damage hitbox alignment.
|
||||
*/
|
||||
public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper {
|
||||
|
||||
// Type-specific parameters
|
||||
private float beamLength = 10.0f;
|
||||
@@ -41,13 +39,11 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
private int numberOfRotations = 2; // How many full rotations before ability ends
|
||||
private boolean lockOnTarget = false;
|
||||
|
||||
// Visual properties
|
||||
private EnergyDisplayData colorData = new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.8f, 0.5f, 0.0f);
|
||||
|
||||
// Runtime state (transient)
|
||||
private transient EntityAbilitySweeper activeEntity = null;
|
||||
|
||||
public AbilitySweeper() {
|
||||
super(new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.8f, 0.5f, 0.0f));
|
||||
this.typeId = "ability.cnpc.sweeper";
|
||||
this.name = "Sweeper";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -83,7 +79,7 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
// Player: target is null — sweep rotates around caster's facing direction, lockOnTarget has no effect.
|
||||
activeEntity = new EntityAbilitySweeper(caster.worldObj, caster, target,
|
||||
beamLength, beamWidth, beamHeight,
|
||||
colorData,
|
||||
displayData,
|
||||
sweepSpeed, numberOfRotations,
|
||||
damage, damageInterval, piercing,
|
||||
lockOnTarget);
|
||||
@@ -93,9 +89,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
}
|
||||
|
||||
spawnAbilityEntity(activeEntity);
|
||||
|
||||
// Ability stays active until entity dies (prevents firing another while projectile is alive)
|
||||
// Movement locking is handled separately by the base class
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,7 +155,7 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
nbt.setFloat("sweepSpeed", sweepSpeed);
|
||||
nbt.setInteger("numberOfRotations", numberOfRotations);
|
||||
nbt.setBoolean("lockOnTarget", lockOnTarget);
|
||||
colorData.writeNBT(nbt);
|
||||
writeEnergyNBT(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -176,10 +169,10 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
this.sweepSpeed = nbt.getFloat("sweepSpeed");
|
||||
this.numberOfRotations = nbt.getInteger("numberOfRotations");
|
||||
this.lockOnTarget = nbt.getBoolean("lockOnTarget");
|
||||
colorData.readNBT(nbt);
|
||||
readEnergyNBT(nbt);
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
// Getters & Setters (type-specific only; color/lightning inherited from AbilityEnergy)
|
||||
public float getBeamLength() { return beamLength; }
|
||||
public void setBeamLength(float beamLength) { this.beamLength = beamLength; }
|
||||
public float getBeamWidth() { return beamWidth; }
|
||||
@@ -198,14 +191,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
public void setNumberOfRotations(int numberOfRotations) { this.numberOfRotations = numberOfRotations; }
|
||||
public boolean isLockOnTarget() { return lockOnTarget; }
|
||||
public void setLockOnTarget(boolean lockOnTarget) { this.lockOnTarget = lockOnTarget; }
|
||||
public int getInnerColor() { return colorData.innerColor; }
|
||||
public void setInnerColor(int innerColor) { colorData.innerColor = innerColor; }
|
||||
public int getOuterColor() { return colorData.outerColor; }
|
||||
public void setOuterColor(int outerColor) { colorData.outerColor = outerColor; }
|
||||
public float getOuterColorWidth() { return colorData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float outerColorWidth) { colorData.outerColorWidth = outerColorWidth; }
|
||||
public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) { colorData.outerColorEnabled = outerColorEnabled; }
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
@@ -216,7 +201,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval)
|
||||
@@ -237,18 +221,10 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
FieldDef.boolField("ability.lockTarget", this::isLockOnTarget, this::setLockOnTarget)
|
||||
.hover("ability.hover.lockTarget")
|
||||
),
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects),
|
||||
|
||||
// Visual tab
|
||||
FieldDef.section("ability.section.colors").tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled)
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
|
||||
));
|
||||
|
||||
// Visual tab - colors + effects (from AbilityEnergy)
|
||||
addEnergyVisualDefinitions(defs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.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.EntityEnergyBarrier;
|
||||
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 AbilityWall extends AbilityEnergyBarrier {
|
||||
|
||||
private final EnergyPanelData panelData;
|
||||
|
||||
public AbilityWall() {
|
||||
super(
|
||||
new EnergyDisplayData(0x44FFCC, 0x22CCAA, true, 0.3f, 0.4f, 0.5f, 0.0f),
|
||||
new EnergyBarrierData(80.0f, true, 160, true)
|
||||
);
|
||||
this.panelData = new EnergyPanelData(4.0f, 3.0f, 0.0f);
|
||||
this.typeId = "ability.cnpc.wall";
|
||||
this.name = "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 = "";
|
||||
this.activeAnimationName = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDamage() {
|
||||
return panelData.launching; // Only has damage when launching
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityEnergyBarrier 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);
|
||||
|
||||
if (dist < 0.01) {
|
||||
// Target at same position — fall through to look-direction placement
|
||||
float yawRad = (float) Math.toRadians(caster.rotationYaw);
|
||||
placeX = caster.posX + (-Math.sin(yawRad) * 3.0);
|
||||
placeY = caster.posY;
|
||||
placeZ = caster.posZ + (Math.cos(yawRad) * 3.0);
|
||||
yaw = caster.rotationYaw;
|
||||
} else {
|
||||
double placeDist = Math.min(dist * 0.5, 5.0);
|
||||
placeX = caster.posX + (dx / dist) * placeDist;
|
||||
placeY = caster.posY;
|
||||
placeZ = caster.posZ + (dz / dist) * placeDist;
|
||||
yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
||||
}
|
||||
} else {
|
||||
float yawRad = (float) Math.toRadians(caster.rotationYaw);
|
||||
placeX = caster.posX + (-Math.sin(yawRad) * 3.0);
|
||||
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 -> {
|
||||
AbilityWall wall = (AbilityWall) 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));
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.util.List;
|
||||
*
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
|
||||
// Beam shape properties
|
||||
private float beamWidth = 0.3f;
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
*
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
|
||||
// Disc-specific movement propertie
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Set;
|
||||
*
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
// Laser-specific properties
|
||||
private float laserWidth = 0.2f;
|
||||
|
||||
@@ -15,11 +15,11 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Orb projectile - spherical homing energy ball.
|
||||
* Extends EntityAbilityProjectile for shared functionality.
|
||||
* Extends EntityEnergyAbility for shared functionality.
|
||||
*
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
|
||||
public EntityAbilityOrb(World world) {
|
||||
super(world);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
@@ -24,19 +22,16 @@ import java.util.Set;
|
||||
* Sweeper ability entity - a rotating beam attached to the NPC.
|
||||
* Unlike other projectiles, this handles its own damage logic since it's
|
||||
* attached to and rotates around the caster.
|
||||
* Extends EntityEnergyAbility for shared visual/owner/charging state.
|
||||
*
|
||||
* Design: Similar to Beam trail visuals but rotating around origin.
|
||||
*/
|
||||
public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpawnData {
|
||||
public class EntityAbilitySweeper extends EntityEnergyAbility {
|
||||
|
||||
// Beam properties
|
||||
private float beamLength = 10.0f;
|
||||
private float beamWidth = 0.3f; // Thin like beam trail
|
||||
private float beamHeight = 0.8f;
|
||||
private int innerColor = 0xFF6600;
|
||||
private int outerColor = 0xFF0000;
|
||||
private boolean outerColorEnabled = true;
|
||||
private float outerColorWidth = 1.8f;
|
||||
|
||||
// Combat properties
|
||||
private float damage = 5.0f;
|
||||
@@ -52,18 +47,13 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
private float baseYaw = 0; // Starting yaw direction
|
||||
private boolean lockOnTarget = false;
|
||||
|
||||
// Owner tracking
|
||||
private int ownerEntityId = -1;
|
||||
// Target tracking
|
||||
private int targetEntityId = -1;
|
||||
|
||||
// Lifetime
|
||||
private int maxTicks = 400;
|
||||
private long deathWorldTime = -1;
|
||||
|
||||
// Preview mode
|
||||
private boolean previewMode = false;
|
||||
private EntityLivingBase previewOwner = null;
|
||||
|
||||
// Damage state
|
||||
private transient int ticksSinceDamage = 0;
|
||||
private transient Set<Integer> hitThisTick = new HashSet<>();
|
||||
@@ -72,8 +62,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
super(world);
|
||||
this.setSize(0.1f, 0.1f);
|
||||
this.noClip = true;
|
||||
this.isImmuneToFire = true;
|
||||
this.ignoreFrustumCheck = true;
|
||||
}
|
||||
|
||||
public EntityAbilitySweeper(World world, EntityLivingBase owner, EntityLivingBase target,
|
||||
@@ -89,10 +77,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
this.beamLength = beamLength;
|
||||
this.beamWidth = beamWidth;
|
||||
this.beamHeight = beamHeight;
|
||||
this.innerColor = displayData.innerColor;
|
||||
this.outerColor = displayData.outerColor;
|
||||
this.outerColorEnabled = displayData.outerColorEnabled;
|
||||
this.outerColorWidth = displayData.outerColorWidth;
|
||||
this.displayData = displayData;
|
||||
this.sweepSpeed = sweepSpeed;
|
||||
this.numberOfRotations = numberOfRotations;
|
||||
this.damage = damage;
|
||||
@@ -121,7 +106,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
// No DataWatcher needed
|
||||
super.entityInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +129,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
return;
|
||||
}
|
||||
|
||||
Entity owner = getOwner();
|
||||
Entity owner = getOwnerEntity();
|
||||
if (owner != null && owner.isDead) {
|
||||
this.setDead();
|
||||
return;
|
||||
@@ -297,12 +282,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
this.previewOwner = owner;
|
||||
}
|
||||
|
||||
private Entity getOwner() {
|
||||
if (previewMode && previewOwner != null) return previewOwner;
|
||||
if (ownerEntityId == -1) return null;
|
||||
return worldObj.getEntityByID(ownerEntityId);
|
||||
}
|
||||
|
||||
// ==================== RENDERING ====================
|
||||
|
||||
@Override
|
||||
@@ -311,22 +290,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
return distance < 16384.0D; // 128 blocks squared
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
return pass == 1; // Translucent pass
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks) {
|
||||
return 0xF000F0;
|
||||
}
|
||||
|
||||
// ==================== GETTERS FOR RENDERER ====================
|
||||
|
||||
public float getBeamLength() {
|
||||
@@ -341,22 +304,6 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
return beamHeight;
|
||||
}
|
||||
|
||||
public int getInnerColor() {
|
||||
return innerColor;
|
||||
}
|
||||
|
||||
public int getOuterColor() {
|
||||
return outerColor;
|
||||
}
|
||||
|
||||
public boolean isOuterColorEnabled() {
|
||||
return outerColorEnabled;
|
||||
}
|
||||
|
||||
public float getOuterColorWidth() {
|
||||
return outerColorWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full interpolated angle INCLUDING base yaw for rendering.
|
||||
*/
|
||||
@@ -382,18 +329,14 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
||||
readEnergyBaseNBT(nbt);
|
||||
this.beamLength = nbt.getFloat("BeamLength");
|
||||
this.beamWidth = nbt.getFloat("BeamWidth");
|
||||
this.beamHeight = nbt.getFloat("BeamHeight");
|
||||
this.innerColor = nbt.getInteger("InnerColor");
|
||||
this.outerColor = nbt.getInteger("OuterColor");
|
||||
this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled");
|
||||
this.outerColorWidth = nbt.hasKey("OuterColorWidth") ? nbt.getFloat("OuterColorWidth") : 1.8f;
|
||||
this.sweepSpeed = nbt.getFloat("SweepSpeed");
|
||||
this.numberOfRotations = nbt.getInteger("NumRotations");
|
||||
this.completedRotations = nbt.getInteger("CompletedRotations");
|
||||
this.maxTicks = nbt.getInteger("MaxTicks");
|
||||
this.ownerEntityId = nbt.getInteger("OwnerId");
|
||||
this.targetEntityId = nbt.getInteger("TargetId");
|
||||
this.currentAngle = nbt.getFloat("CurrentAngle");
|
||||
this.baseYaw = nbt.getFloat("BaseYaw");
|
||||
@@ -406,18 +349,14 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbt) {
|
||||
writeEnergyBaseNBT(nbt);
|
||||
nbt.setFloat("BeamLength", beamLength);
|
||||
nbt.setFloat("BeamWidth", beamWidth);
|
||||
nbt.setFloat("BeamHeight", beamHeight);
|
||||
nbt.setInteger("InnerColor", innerColor);
|
||||
nbt.setInteger("OuterColor", outerColor);
|
||||
nbt.setBoolean("OuterColorEnabled", outerColorEnabled);
|
||||
nbt.setFloat("OuterColorWidth", outerColorWidth);
|
||||
nbt.setFloat("SweepSpeed", sweepSpeed);
|
||||
nbt.setInteger("NumRotations", numberOfRotations);
|
||||
nbt.setInteger("CompletedRotations", completedRotations);
|
||||
nbt.setInteger("MaxTicks", maxTicks);
|
||||
nbt.setInteger("OwnerId", ownerEntityId);
|
||||
nbt.setInteger("TargetId", targetEntityId);
|
||||
nbt.setFloat("CurrentAngle", currentAngle);
|
||||
nbt.setFloat("BaseYaw", baseYaw);
|
||||
@@ -427,53 +366,4 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa
|
||||
nbt.setBoolean("Piercing", piercing);
|
||||
nbt.setBoolean("LockOnTarget", lockOnTarget);
|
||||
}
|
||||
|
||||
// ==================== SPAWN DATA ====================
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer) {
|
||||
buffer.writeFloat(beamLength);
|
||||
buffer.writeFloat(beamWidth);
|
||||
buffer.writeFloat(beamHeight);
|
||||
buffer.writeInt(innerColor);
|
||||
buffer.writeInt(outerColor);
|
||||
buffer.writeBoolean(outerColorEnabled);
|
||||
buffer.writeFloat(outerColorWidth);
|
||||
buffer.writeFloat(sweepSpeed);
|
||||
buffer.writeInt(numberOfRotations);
|
||||
buffer.writeInt(completedRotations);
|
||||
buffer.writeInt(maxTicks);
|
||||
buffer.writeInt(ownerEntityId);
|
||||
buffer.writeInt(targetEntityId);
|
||||
buffer.writeFloat(currentAngle);
|
||||
buffer.writeFloat(baseYaw);
|
||||
buffer.writeFloat(damage);
|
||||
buffer.writeInt(damageInterval);
|
||||
buffer.writeBoolean(piercing);
|
||||
buffer.writeBoolean(lockOnTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer) {
|
||||
this.beamLength = buffer.readFloat();
|
||||
this.beamWidth = buffer.readFloat();
|
||||
this.beamHeight = buffer.readFloat();
|
||||
this.innerColor = buffer.readInt();
|
||||
this.outerColor = buffer.readInt();
|
||||
this.outerColorEnabled = buffer.readBoolean();
|
||||
this.outerColorWidth = buffer.readFloat();
|
||||
this.sweepSpeed = buffer.readFloat();
|
||||
this.numberOfRotations = buffer.readInt();
|
||||
this.completedRotations = buffer.readInt();
|
||||
this.maxTicks = buffer.readInt();
|
||||
this.ownerEntityId = buffer.readInt();
|
||||
this.targetEntityId = buffer.readInt();
|
||||
this.currentAngle = buffer.readFloat();
|
||||
this.baseYaw = buffer.readFloat();
|
||||
this.damage = buffer.readFloat();
|
||||
this.damageInterval = buffer.readInt();
|
||||
this.piercing = buffer.readBoolean();
|
||||
this.lockOnTarget = buffer.readBoolean();
|
||||
this.prevAngle = this.currentAngle;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ 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.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -121,7 +121,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
private boolean particleGlow = true;
|
||||
|
||||
private long deathWorldTime = -1;
|
||||
private List<AbilityEffect> effects = new ArrayList<>();
|
||||
private List<AbilityPotionEffect> effects = new ArrayList<>();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TRAP-SPECIFIC PROPERTIES
|
||||
@@ -194,7 +194,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
float particleDensity, float particleScale,
|
||||
float animSpeed, float lightningDensity,
|
||||
boolean visible,
|
||||
List<AbilityEffect> effects) {
|
||||
List<AbilityPotionEffect> effects) {
|
||||
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.TRAP, owner, x, y, z);
|
||||
zone.shape = shape;
|
||||
zone.radius = triggerRadius;
|
||||
@@ -217,7 +217,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
zone.lightningDensity = lightningDensity;
|
||||
zone.visible = visible;
|
||||
if (effects != null) {
|
||||
for (AbilityEffect e : effects) {
|
||||
for (AbilityPotionEffect e : effects) {
|
||||
zone.effects.add(e.copy());
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
float zoneHeight,
|
||||
float particleDensity, float particleScale,
|
||||
float animSpeed, float lightningDensity,
|
||||
List<AbilityEffect> effects) {
|
||||
List<AbilityPotionEffect> effects) {
|
||||
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.HAZARD, owner, x, y, z);
|
||||
zone.shape = shape;
|
||||
zone.radius = radius;
|
||||
@@ -255,7 +255,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
zone.animSpeed = animSpeed;
|
||||
zone.lightningDensity = lightningDensity;
|
||||
if (effects != null) {
|
||||
for (AbilityEffect e : effects) {
|
||||
for (AbilityPotionEffect e : effects) {
|
||||
zone.effects.add(e.copy());
|
||||
}
|
||||
}
|
||||
@@ -499,7 +499,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
|
||||
private void applyEffects(EntityLivingBase entity) {
|
||||
if (entity == null || effects.isEmpty()) return;
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effect.apply(entity);
|
||||
}
|
||||
}
|
||||
@@ -668,7 +668,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
if (nbt.hasKey("Effects")) {
|
||||
NBTTagList effectList = nbt.getTagList("Effects", 10);
|
||||
for (int i = 0; i < effectList.tagCount(); i++) {
|
||||
AbilityEffect effect = AbilityEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
if (effect != null && effect.isValid()) {
|
||||
effects.add(effect);
|
||||
}
|
||||
@@ -728,7 +728,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
|
||||
// Effects
|
||||
NBTTagList effectList = new NBTTagList();
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effectList.appendTag(effect.writeNBT());
|
||||
}
|
||||
nbt.setTag("Effects", effectList);
|
||||
@@ -778,7 +778,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
// Effects
|
||||
NBTTagCompound effectsNbt = new NBTTagCompound();
|
||||
NBTTagList effectList = new NBTTagList();
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effectList.appendTag(effect.writeNBT());
|
||||
}
|
||||
effectsNbt.setTag("Effects", effectList);
|
||||
@@ -832,7 +832,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
if (effectsNbt != null && effectsNbt.hasKey("Effects")) {
|
||||
NBTTagList effectList = effectsNbt.getTagList("Effects", 10);
|
||||
for (int i = 0; i < effectList.tagCount(); i++) {
|
||||
AbilityEffect effect = AbilityEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i));
|
||||
if (effect != null && effect.isValid()) {
|
||||
effects.add(effect);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* Universal base class for all energy ability entities (Projectiles, Barriers, Sweeper).
|
||||
* Provides shared visual data, owner tracking, charging state, and common entity properties.
|
||||
*/
|
||||
public abstract class EntityEnergyAbility extends Entity implements IEntityAdditionalSpawnData {
|
||||
|
||||
// ==================== VISUAL PROPERTIES ====================
|
||||
protected EnergyDisplayData displayData = new EnergyDisplayData();
|
||||
protected EnergyLightningData lightningData = new EnergyLightningData();
|
||||
|
||||
// Client-side lightning state (not saved to NBT)
|
||||
@SideOnly(Side.CLIENT)
|
||||
public transient Object lightningState; // Actually AttachedLightningRenderer.LightningState
|
||||
|
||||
// ==================== TRACKING ====================
|
||||
protected int ownerEntityId = -1;
|
||||
|
||||
// ==================== STATE ====================
|
||||
/** The ability that spawned this entity. Transient, not saved to NBT. */
|
||||
protected transient Ability sourceAbility = null;
|
||||
protected boolean previewMode = false;
|
||||
protected EntityLivingBase previewOwner = null;
|
||||
|
||||
// ==================== CHARGING STATE ====================
|
||||
protected boolean charging = false;
|
||||
protected int chargeDuration = 0;
|
||||
protected int chargeTick = 0;
|
||||
protected static final int DW_CHARGING = 20;
|
||||
|
||||
public EntityEnergyAbility(World world) {
|
||||
super(world);
|
||||
this.isImmuneToFire = true;
|
||||
this.ignoreFrustumCheck = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
// ==================== CHARGING ====================
|
||||
|
||||
/**
|
||||
* Check if entity is in charging state (synced via data watcher).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) return this.charging;
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
public void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
}
|
||||
|
||||
public float getInterpolatedChargeProgress(float partialTicks) {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
|
||||
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
return prevProgress + (currProgress - prevProgress) * partialTicks;
|
||||
}
|
||||
|
||||
// ==================== OWNER ====================
|
||||
|
||||
/**
|
||||
* Get the owner entity. In preview mode, returns the preview owner directly.
|
||||
*/
|
||||
public Entity getOwnerEntity() {
|
||||
if (previewMode && previewOwner != null) {
|
||||
return previewOwner;
|
||||
}
|
||||
if (ownerEntityId == -1) return null;
|
||||
return worldObj.getEntityByID(ownerEntityId);
|
||||
}
|
||||
|
||||
public int getOwnerEntityId() { return ownerEntityId; }
|
||||
public void setOwnerEntityId(int id) { this.ownerEntityId = id; }
|
||||
|
||||
// ==================== SOURCE ABILITY ====================
|
||||
|
||||
public Ability getSourceAbility() { return sourceAbility; }
|
||||
public void setSourceAbility(Ability ability) { this.sourceAbility = ability; }
|
||||
|
||||
// ==================== PREVIEW MODE ====================
|
||||
|
||||
public boolean isPreviewMode() { return previewMode; }
|
||||
public void setPreviewMode(boolean preview) { this.previewMode = preview; }
|
||||
public void setPreviewOwner(EntityLivingBase owner) { this.previewOwner = owner; }
|
||||
|
||||
// ==================== DISPLAY GETTERS & SETTERS ====================
|
||||
|
||||
public EnergyDisplayData getDisplayData() { return displayData; }
|
||||
public EnergyLightningData getLightningData() { return lightningData; }
|
||||
|
||||
public int getInnerColor() { return displayData.getInnerColor(); }
|
||||
public void setInnerColor(int color) { displayData.setInnerColor(color); }
|
||||
|
||||
public float getInnerAlpha() { return displayData.getInnerAlpha(); }
|
||||
public void setInnerAlpha(float alpha) { displayData.setInnerAlpha(alpha); }
|
||||
|
||||
public int getOuterColor() { return displayData.getOuterColor(); }
|
||||
public void setOuterColor(int color) { displayData.setOuterColor(color); }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); }
|
||||
public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.getOuterColorWidth(); }
|
||||
public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); }
|
||||
public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); }
|
||||
|
||||
public float getRotationSpeed() { return displayData.getRotationSpeed(); }
|
||||
public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); }
|
||||
|
||||
// ==================== LIGHTNING GETTERS & SETTERS ====================
|
||||
|
||||
public boolean hasLightningEffect() { return lightningData.isLightningEffect(); }
|
||||
public void setLightningEffect(boolean enabled) { lightningData.setLightningEffect(enabled); }
|
||||
|
||||
public float getLightningDensity() { return lightningData.getLightningDensity(); }
|
||||
public void setLightningDensity(float density) { lightningData.setLightningDensity(density); }
|
||||
|
||||
public float getLightningRadius() { return lightningData.getLightningRadius(); }
|
||||
public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); }
|
||||
public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); }
|
||||
|
||||
// ==================== BRIGHTNESS ====================
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks) {
|
||||
return 0xF000F0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
return pass == 1; // Translucent pass
|
||||
}
|
||||
|
||||
// ==================== COLLISION SETTINGS ====================
|
||||
|
||||
@Override
|
||||
public boolean canBePushed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canTriggerWalking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public float getShadowSize() {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// ==================== NBT HELPERS ====================
|
||||
|
||||
/**
|
||||
* Write shared energy base fields to NBT (owner, display, lightning).
|
||||
* Call from subclass NBT writers before writing type-specific fields.
|
||||
*/
|
||||
protected void writeEnergyBaseNBT(NBTTagCompound nbt) {
|
||||
nbt.setInteger("OwnerId", ownerEntityId);
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read shared energy base fields from NBT (owner, display, lightning).
|
||||
* Call from subclass NBT readers before reading type-specific fields.
|
||||
*/
|
||||
protected void readEnergyBaseNBT(NBTTagCompound nbt) {
|
||||
this.ownerEntityId = nbt.getInteger("OwnerId");
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== SPAWN DATA ====================
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer) {
|
||||
try {
|
||||
NBTTagCompound compound = new NBTTagCompound();
|
||||
this.writeEntityToNBT(compound);
|
||||
cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound);
|
||||
} catch (Exception e) {
|
||||
noppes.npcs.LogWriter.error("Error writing energy ability spawn data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer) {
|
||||
try {
|
||||
NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer);
|
||||
if (compound != null) {
|
||||
this.readEntityFromNBT(compound);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
noppes.npcs.LogWriter.error("Error reading energy ability spawn data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
/**
|
||||
* Base class for energy barrier entities (Dome, Panel).
|
||||
* Provides shared health, damage, hit flash, knockback target,
|
||||
* and barrier lifecycle management.
|
||||
* Extends EntityEnergyAbility for shared visual/owner/charging state.
|
||||
*/
|
||||
public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
|
||||
// ==================== BARRIER PROPERTIES ====================
|
||||
protected EnergyBarrierData barrierData = new EnergyBarrierData();
|
||||
protected float currentHealth;
|
||||
protected int ticksAlive = 0;
|
||||
|
||||
// ==================== DATA WATCHER INDICES ====================
|
||||
protected static final int DW_HEALTH_PERCENT = 21;
|
||||
protected static final int DW_HIT_FLASH = 22;
|
||||
|
||||
public EntityEnergyBarrier(World world) {
|
||||
super(world);
|
||||
this.noClip = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
super.entityInit(); // DW_CHARGING = 20
|
||||
this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f);
|
||||
this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0);
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT METHODS ====================
|
||||
|
||||
/**
|
||||
* Check if a projectile is incoming toward this barrier.
|
||||
* Dome uses spherical check, Panel uses planar check.
|
||||
*/
|
||||
public abstract boolean isIncomingProjectile(EntityEnergyProjectile projectile);
|
||||
|
||||
/**
|
||||
* Setup charging state. Called during windup phase.
|
||||
*/
|
||||
public abstract void setupCharging(int duration);
|
||||
|
||||
/**
|
||||
* Finish charging — snap to full size and become active.
|
||||
*/
|
||||
public abstract void finishCharging();
|
||||
|
||||
/**
|
||||
* Push entities away from the barrier surface.
|
||||
*/
|
||||
protected abstract void knockbackEntities();
|
||||
|
||||
// ==================== PROJECTILE HIT ====================
|
||||
|
||||
/**
|
||||
* Apply damage to this barrier from a projectile.
|
||||
* Returns true if the barrier absorbed the hit (projectile should be destroyed).
|
||||
*/
|
||||
public boolean onProjectileHit(EntityEnergyProjectile projectile, float baseDamage) {
|
||||
// Get damage multiplier for this projectile type
|
||||
String typeId = "";
|
||||
if (projectile.getSourceAbility() != null) {
|
||||
typeId = projectile.getSourceAbility().getTypeId();
|
||||
}
|
||||
float multiplier = barrierData.getMultiplier(typeId);
|
||||
float damage = baseDamage * multiplier;
|
||||
|
||||
// Fire hit event (may cancel or modify damage)
|
||||
if (!worldObj.isRemote) {
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage);
|
||||
if (eventDamage < 0) return false; // Event cancelled — don't block
|
||||
damage = eventDamage;
|
||||
}
|
||||
|
||||
triggerHitFlash();
|
||||
|
||||
if (!barrierData.useHealth) {
|
||||
return true; // Duration-only mode: block but don't take damage
|
||||
}
|
||||
|
||||
currentHealth -= damage;
|
||||
syncHealthPercent();
|
||||
|
||||
if (currentHealth <= 0) {
|
||||
onBarrierDestroyed();
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== MELEE DAMAGE ====================
|
||||
|
||||
@Override
|
||||
public boolean canBeCollidedWith() {
|
||||
return barrierData.meleeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount) {
|
||||
if (worldObj.isRemote || !barrierData.meleeEnabled) return false;
|
||||
if (isCharging()) return false;
|
||||
|
||||
float damage = amount * barrierData.meleeDamageMultiplier;
|
||||
|
||||
// Fire hit event with null projectile (melee hit)
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, damage);
|
||||
if (eventDamage < 0) return false;
|
||||
damage = eventDamage;
|
||||
|
||||
triggerHitFlash();
|
||||
|
||||
if (!barrierData.useHealth) return true;
|
||||
|
||||
currentHealth -= damage;
|
||||
syncHealthPercent();
|
||||
|
||||
if (currentHealth <= 0) {
|
||||
onBarrierDestroyed();
|
||||
this.setDead();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== SHARED UPDATE LOGIC ====================
|
||||
|
||||
/**
|
||||
* Shared barrier tick logic for onUpdate(). Handles spawned event, owner death,
|
||||
* duration check, hit flash decay, and tick event.
|
||||
* Call this after incrementing ticksAlive in subclass onUpdate().
|
||||
* @return true if barrier died this tick (caller should return early)
|
||||
*/
|
||||
protected boolean updateBarrierTick() {
|
||||
if (!worldObj.isRemote) {
|
||||
// Fire spawned event on first tick
|
||||
if (ticksAlive == 1) {
|
||||
EventHooks.onEnergyBarrierSpawned(this, ownerEntityId);
|
||||
}
|
||||
|
||||
// Check owner death
|
||||
if (ownerEntityId >= 0 && ticksAlive > 5) {
|
||||
Entity owner = worldObj.getEntityByID(ownerEntityId);
|
||||
if (owner != null) {
|
||||
if (owner.isDead) {
|
||||
onBarrierDestroyed();
|
||||
this.setDead();
|
||||
return true;
|
||||
}
|
||||
if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
|
||||
onBarrierDestroyed();
|
||||
this.setDead();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Duration check
|
||||
if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) {
|
||||
onBarrierDestroyed();
|
||||
this.setDead();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reset hit flash
|
||||
if (getHitFlash() > 0) {
|
||||
setHitFlash((byte) (getHitFlash() - 1));
|
||||
}
|
||||
|
||||
// Fire tick event
|
||||
EventHooks.onEnergyBarrierTick(this, ownerEntityId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
protected void onBarrierDestroyed() {
|
||||
EventHooks.onEnergyBarrierDestroyed(this, ownerEntityId);
|
||||
}
|
||||
|
||||
protected void syncHealthPercent() {
|
||||
float percent = barrierData.useHealth && barrierData.maxHealth > 0
|
||||
? Math.max(0, currentHealth / barrierData.maxHealth)
|
||||
: 1.0f;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent);
|
||||
}
|
||||
}
|
||||
|
||||
protected void triggerHitFlash() {
|
||||
if (!worldObj.isRemote) {
|
||||
setHitFlash((byte) 4);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setHitFlash(byte value) {
|
||||
this.dataWatcher.updateObject(DW_HIT_FLASH, value);
|
||||
}
|
||||
|
||||
public byte getHitFlash() {
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH);
|
||||
}
|
||||
|
||||
public float getHealthPercent() {
|
||||
return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT);
|
||||
}
|
||||
|
||||
protected boolean isKnockbackTarget(EntityLivingBase entity) {
|
||||
switch (barrierData.knockbackTarget) {
|
||||
case 1: return entity instanceof EntityPlayer;
|
||||
case 2: return entity instanceof EntityNPCInterface;
|
||||
default: return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public EnergyBarrierData getBarrierData() { return barrierData; }
|
||||
public float getCurrentHealth() { return currentHealth; }
|
||||
public void setCurrentHealth(float health) { this.currentHealth = health; syncHealthPercent(); }
|
||||
public int getTicksAlive() { return ticksAlive; }
|
||||
|
||||
// ==================== NBT HELPERS ====================
|
||||
|
||||
/**
|
||||
* Write shared barrier base fields to NBT.
|
||||
* Call from subclass writeEntityToNBT() before writing type-specific fields.
|
||||
*/
|
||||
protected void writeBarrierBaseNBT(NBTTagCompound nbt) {
|
||||
writeEnergyBaseNBT(nbt);
|
||||
nbt.setInteger("TicksAlive", ticksAlive);
|
||||
nbt.setFloat("CurrentHealth", currentHealth);
|
||||
nbt.setBoolean("Charging", charging);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
barrierData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read shared barrier base fields from NBT.
|
||||
* Call from subclass readEntityFromNBT() before reading type-specific fields.
|
||||
*/
|
||||
protected void readBarrierBaseNBT(NBTTagCompound nbt) {
|
||||
readEnergyBaseNBT(nbt);
|
||||
this.ticksAlive = nbt.getInteger("TicksAlive");
|
||||
this.currentHealth = nbt.getFloat("CurrentHealth");
|
||||
this.charging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
|
||||
this.chargeTick = nbt.getInteger("ChargeTick");
|
||||
this.chargeDuration = nbt.getInteger("ChargeDuration");
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (charging ? 1 : 0));
|
||||
barrierData.readNBT(nbt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Energy Dome entity - a spherical barrier that blocks incoming energy projectiles.
|
||||
* Centered on the caster's position at time of casting.
|
||||
* Only blocks incoming attacks (not outgoing from allies inside).
|
||||
* Extends EntityEnergyBarrier for shared barrier logic.
|
||||
*/
|
||||
public class EntityEnergyDome extends EntityEnergyBarrier {
|
||||
|
||||
// ==================== DOME-SPECIFIC PROPERTIES ====================
|
||||
protected float domeRadius = 5.0f;
|
||||
protected float targetDomeRadius = 5.0f;
|
||||
protected boolean followCaster = false;
|
||||
|
||||
public EntityEnergyDome(World world) {
|
||||
super(world);
|
||||
this.setSize(1.0f, 1.0f); // Hitbox doesn't really matter, we use radius checks
|
||||
}
|
||||
|
||||
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
|
||||
public void onUpdate() {
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Follow caster: both sides for smooth interpolated rendering
|
||||
if (followCaster) {
|
||||
Entity owner = ownerEntityId >= 0 ? worldObj.getEntityByID(ownerEntityId) : null;
|
||||
if (owner != null) {
|
||||
this.setPosition(owner.posX, owner.posY, owner.posZ);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle charging animation (both sides for smooth rendering)
|
||||
if (isCharging()) {
|
||||
chargeTick++;
|
||||
float progress = getChargeProgress();
|
||||
this.domeRadius = targetDomeRadius * progress;
|
||||
return; // Don't tick duration/death during charging
|
||||
}
|
||||
|
||||
ticksAlive++;
|
||||
|
||||
if (updateBarrierTick()) return;
|
||||
|
||||
// Knockback entities inside the dome
|
||||
if (!worldObj.isRemote && barrierData.knockbackEnabled) {
|
||||
knockbackEntities();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== INCOMING CHECK ====================
|
||||
|
||||
/**
|
||||
* Check if a projectile at the given position is entering this dome from outside.
|
||||
* Only blocks incoming projectiles (dot product check).
|
||||
*/
|
||||
@Override
|
||||
public boolean isIncomingProjectile(EntityEnergyProjectile projectile) {
|
||||
// Don't block during charging phase
|
||||
if (isCharging()) return false;
|
||||
// Don't block projectiles from the dome's owner
|
||||
if (projectile.getOwnerEntityId() == this.ownerEntityId) return false;
|
||||
|
||||
// Don't block projectiles from same-faction NPCs
|
||||
Entity owner = getOwnerEntity();
|
||||
Entity projOwner = projectile.getOwnerEntity();
|
||||
if (owner instanceof noppes.npcs.entity.EntityNPCInterface && projOwner instanceof noppes.npcs.entity.EntityNPCInterface) {
|
||||
if (((noppes.npcs.entity.EntityNPCInterface) owner).faction.id == ((noppes.npcs.entity.EntityNPCInterface) projOwner).faction.id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ==================== CHARGING ====================
|
||||
|
||||
@Override
|
||||
public void setupCharging(int duration) {
|
||||
this.targetDomeRadius = this.domeRadius;
|
||||
this.domeRadius = 0.01f;
|
||||
this.chargeDuration = duration;
|
||||
this.chargeTick = 0;
|
||||
setCharging(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishCharging() {
|
||||
this.domeRadius = targetDomeRadius;
|
||||
setCharging(false);
|
||||
}
|
||||
|
||||
// ==================== KNOCKBACK ====================
|
||||
|
||||
/**
|
||||
* Push entities away from the dome surface.
|
||||
* Entities outside are pushed outward, entities inside are pushed inward (containment).
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void knockbackEntities() {
|
||||
float margin = 1.0f;
|
||||
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
|
||||
posX - domeRadius - margin, posY - domeRadius - margin, posZ - domeRadius - margin,
|
||||
posX + domeRadius + margin, posY + domeRadius + margin, posZ + domeRadius + margin
|
||||
);
|
||||
|
||||
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
|
||||
for (EntityLivingBase ent : entities) {
|
||||
if (ent.getEntityId() == ownerEntityId) continue;
|
||||
if (!isKnockbackTarget(ent)) continue;
|
||||
|
||||
double dx = ent.posX - posX;
|
||||
double dy = (ent.posY + ent.height * 0.5) - posY;
|
||||
double dz = ent.posZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist < 0.01) continue;
|
||||
|
||||
// Only affect entities near the dome surface
|
||||
if (dist < domeRadius + margin && dist > domeRadius - margin) {
|
||||
double pushStrength = barrierData.knockbackStrength * 0.5;
|
||||
double pushDir = dist >= domeRadius ? 1.0 : -1.0; // Outside = outward, Inside = inward
|
||||
ent.addVelocity(
|
||||
(dx / dist) * pushStrength * pushDir,
|
||||
0.05 * pushDir,
|
||||
(dz / dist) * pushStrength * pushDir
|
||||
);
|
||||
ent.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== BOUNDING BOX ====================
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox() {
|
||||
if (barrierData.meleeEnabled) {
|
||||
return AxisAlignedBB.getBoundingBox(
|
||||
posX - domeRadius, posY - domeRadius, posZ - domeRadius,
|
||||
posX + domeRadius, posY + domeRadius, posZ + domeRadius
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public boolean isInRangeToRenderDist(double distance) {
|
||||
double d = domeRadius * 8.0D;
|
||||
d *= 64.0D;
|
||||
return distance < d * d;
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getDomeRadius() { return domeRadius; }
|
||||
public void setDomeRadius(float radius) { this.domeRadius = Math.max(0.1f, radius); }
|
||||
public boolean isFollowCaster() { return followCaster; }
|
||||
public void setFollowCaster(boolean follow) { this.followCaster = follow; }
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
||||
readBarrierBaseNBT(nbt);
|
||||
this.domeRadius = nbt.getFloat("DomeRadius");
|
||||
this.targetDomeRadius = nbt.hasKey("TargetDomeRadius") ? nbt.getFloat("TargetDomeRadius") : domeRadius;
|
||||
this.followCaster = nbt.hasKey("FollowCaster") && nbt.getBoolean("FollowCaster");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbt) {
|
||||
writeBarrierBaseNBT(nbt);
|
||||
nbt.setFloat("DomeRadius", domeRadius);
|
||||
nbt.setFloat("TargetDomeRadius", targetDomeRadius);
|
||||
nbt.setBoolean("FollowCaster", followCaster);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Energy Panel entity - a flat rectangular barrier used by Wall and Shield abilities.
|
||||
* Supports three modes: PLACED (stationary), HELD (tracks caster), LAUNCHED (moves forward).
|
||||
* Extends EntityEnergyBarrier for shared barrier logic.
|
||||
*/
|
||||
public class EntityEnergyPanel extends EntityEnergyBarrier {
|
||||
|
||||
public enum PanelMode {
|
||||
PLACED, // Stationary wall
|
||||
HELD, // Follows caster, rotates with caster look
|
||||
LAUNCHED // Moves forward, deals damage/knockback on contact
|
||||
}
|
||||
|
||||
// ==================== PANEL-SPECIFIC PROPERTIES ====================
|
||||
protected EnergyPanelData panelData = new EnergyPanelData();
|
||||
protected PanelMode mode = PanelMode.PLACED;
|
||||
protected float panelYaw = 0.0f; // Rotation of the panel face (degrees)
|
||||
|
||||
// ==================== CHARGING (panel-specific targets) ====================
|
||||
protected float targetPanelWidth;
|
||||
protected float targetPanelHeight;
|
||||
|
||||
public EntityEnergyPanel(World world) {
|
||||
super(world);
|
||||
this.setSize(0.5f, 0.5f);
|
||||
}
|
||||
|
||||
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
|
||||
public void onUpdate() {
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Handle charging animation (both sides for smooth rendering)
|
||||
if (isCharging()) {
|
||||
chargeTick++;
|
||||
float progress = getChargeProgress();
|
||||
panelData.panelWidth = targetPanelWidth * progress;
|
||||
panelData.panelHeight = targetPanelHeight * progress;
|
||||
|
||||
// During charging, held panels still track owner
|
||||
if (mode == PanelMode.HELD) {
|
||||
updateHeld();
|
||||
}
|
||||
return; // Don't tick duration/death during charging
|
||||
}
|
||||
|
||||
ticksAlive++;
|
||||
|
||||
if (updateBarrierTick()) return;
|
||||
|
||||
// Knockback (not during launched mode)
|
||||
if (!worldObj.isRemote && barrierData.knockbackEnabled && mode != PanelMode.LAUNCHED) {
|
||||
knockbackEntities();
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Update yaw BEFORE position calc to avoid 1-tick lag
|
||||
this.panelYaw = owner.rotationYaw;
|
||||
|
||||
// Follow owner position
|
||||
float frontDist = 1.5f;
|
||||
float yawRad = (float) Math.toRadians(panelYaw);
|
||||
double newX = owner.posX + (-Math.sin(yawRad) * frontDist);
|
||||
double newY = owner.posY + panelData.heightOffset + (owner.height * 0.5f);
|
||||
double newZ = owner.posZ + (Math.cos(yawRad) * frontDist);
|
||||
|
||||
this.setPosition(newX, newY, newZ);
|
||||
}
|
||||
|
||||
private void updateLaunched() {
|
||||
// Move forward on both sides for smooth client rendering
|
||||
this.posX += motionX;
|
||||
this.posY += motionY;
|
||||
this.posZ += motionZ;
|
||||
this.setPosition(posX, posY, posZ);
|
||||
|
||||
// Damage and knockback are server-only
|
||||
if (!worldObj.isRemote) {
|
||||
// Check entity collision for damage
|
||||
float halfW = panelData.panelWidth * 0.5f;
|
||||
float halfH = panelData.panelHeight * 0.5f;
|
||||
float halfD = 0.5f; // Panel collision depth
|
||||
float searchRadius = Math.max(halfW, halfD);
|
||||
AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox(
|
||||
posX - searchRadius, posY - halfH, posZ - searchRadius,
|
||||
posX + searchRadius, posY + halfH, posZ + searchRadius
|
||||
);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<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 lifetime check
|
||||
if (ticksAlive > 200) {
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== INCOMING CHECK ====================
|
||||
|
||||
/**
|
||||
* Check if a projectile hits this panel surface.
|
||||
* Only blocks incoming projectiles (not from the panel's owner).
|
||||
*/
|
||||
@Override
|
||||
public boolean isIncomingProjectile(EntityEnergyProjectile projectile) {
|
||||
// Don't block during charging phase
|
||||
if (isCharging()) return false;
|
||||
if (projectile.getOwnerEntityId() == this.ownerEntityId) return false;
|
||||
|
||||
// Faction check
|
||||
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 via dot product with panel normal
|
||||
double normalX = -Math.sin(yawRad);
|
||||
double normalZ = Math.cos(yawRad);
|
||||
double dot = projectile.motionX * normalX + projectile.motionZ * normalZ;
|
||||
|
||||
// Determine which side of the panel the projectile is on
|
||||
if (localForward > 0 && dot >= 0) return false; // Moving away from front side
|
||||
if (localForward < 0 && dot <= 0) return false; // Moving away from back side
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== CHARGING ====================
|
||||
|
||||
@Override
|
||||
public void setupCharging(int duration) {
|
||||
this.targetPanelWidth = panelData.panelWidth;
|
||||
this.targetPanelHeight = panelData.panelHeight;
|
||||
panelData.panelWidth = 0.01f;
|
||||
panelData.panelHeight = 0.01f;
|
||||
this.chargeDuration = duration;
|
||||
this.chargeTick = 0;
|
||||
setCharging(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishCharging() {
|
||||
panelData.panelWidth = targetPanelWidth;
|
||||
panelData.panelHeight = targetPanelHeight;
|
||||
setCharging(false);
|
||||
}
|
||||
|
||||
// ==================== KNOCKBACK ====================
|
||||
|
||||
/**
|
||||
* Push entities away from the panel surface.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void knockbackEntities() {
|
||||
float halfW = panelData.panelWidth * 0.5f;
|
||||
float halfH = panelData.panelHeight * 0.5f;
|
||||
float margin = 1.0f;
|
||||
|
||||
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
|
||||
posX - halfW - margin, posY - halfH - margin, posZ - halfW - margin,
|
||||
posX + halfW + margin, posY + halfH + margin, posZ + halfW + margin
|
||||
);
|
||||
|
||||
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
|
||||
float yawRad = (float) Math.toRadians(panelYaw);
|
||||
float normalX = -(float) Math.sin(yawRad);
|
||||
float normalZ = (float) Math.cos(yawRad);
|
||||
|
||||
for (EntityLivingBase ent : entities) {
|
||||
if (ent.getEntityId() == ownerEntityId) continue;
|
||||
if (!isKnockbackTarget(ent)) continue;
|
||||
|
||||
// Transform entity position to panel-local space
|
||||
double dx = ent.posX - posX;
|
||||
double dy = (ent.posY + ent.height * 0.5) - posY;
|
||||
double dz = ent.posZ - posZ;
|
||||
|
||||
// Check proximity to panel surface
|
||||
float localForward = (float) (dx * normalX + dz * normalZ);
|
||||
float localRight = (float) (dx * (-normalZ) + dz * normalX);
|
||||
|
||||
if (Math.abs(localForward) < margin && Math.abs(localRight) < halfW + margin && Math.abs(dy) < halfH + margin) {
|
||||
// Push entity away from the panel face
|
||||
float pushDir = localForward >= 0 ? 1.0f : -1.0f;
|
||||
double pushStrength = barrierData.knockbackStrength * 0.5;
|
||||
ent.addVelocity(
|
||||
normalX * pushDir * pushStrength,
|
||||
0.1,
|
||||
normalZ * pushDir * pushStrength
|
||||
);
|
||||
ent.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== BOUNDING BOX ====================
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox() {
|
||||
if (barrierData.meleeEnabled) {
|
||||
float halfW = panelData.panelWidth * 0.5f;
|
||||
float halfH = panelData.panelHeight * 0.5f;
|
||||
float extent = Math.max(halfW, 0.5f);
|
||||
return AxisAlignedBB.getBoundingBox(
|
||||
posX - extent, posY - halfH, posZ - extent,
|
||||
posX + extent, posY + halfH, posZ + extent
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public PanelMode getMode() { return mode; }
|
||||
public float getPanelYaw() { return panelYaw; }
|
||||
public EnergyPanelData getPanelData() { return panelData; }
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
||||
readBarrierBaseNBT(nbt);
|
||||
this.panelYaw = nbt.getFloat("PanelYaw");
|
||||
int modeOrdinal = nbt.getInteger("PanelMode");
|
||||
this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length)
|
||||
? PanelMode.values()[modeOrdinal] : PanelMode.PLACED;
|
||||
panelData.readNBT(nbt);
|
||||
this.targetPanelWidth = nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth;
|
||||
this.targetPanelHeight = nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbt) {
|
||||
writeBarrierBaseNBT(nbt);
|
||||
nbt.setFloat("PanelYaw", panelYaw);
|
||||
nbt.setInteger("PanelMode", mode.ordinal());
|
||||
nbt.setFloat("TargetPanelWidth", targetPanelWidth);
|
||||
nbt.setFloat("TargetPanelHeight", targetPanelHeight);
|
||||
panelData.writeNBT(nbt);
|
||||
}
|
||||
}
|
||||
+103
-250
@@ -1,14 +1,10 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -23,6 +19,9 @@ import net.minecraft.world.World;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.controllers.PartyController;
|
||||
import noppes.npcs.controllers.data.Party;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
@@ -35,20 +34,21 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for all ability projectiles (Orb, Disc, Beam, Laser).
|
||||
* Provides common functionality for visuals, combat, effects, and interpolation.
|
||||
* Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer).
|
||||
* Provides common functionality for combat, effects, homing, and interpolation.
|
||||
* Extends EntityEnergyAbility for shared visual/owner/charging state.
|
||||
*
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
public abstract class EntityAbilityProjectile extends Entity implements IEntityAdditionalSpawnData {
|
||||
public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
|
||||
// ==================== ACTIVE PROJECTILE TRACKING ====================
|
||||
private static final Map<Integer, List<WeakReference<EntityAbilityProjectile>>> activeProjectiles = new HashMap<>();
|
||||
private static final Map<Integer, List<WeakReference<EntityEnergyProjectile>>> activeProjectiles = new HashMap<>();
|
||||
|
||||
public static void trackProjectile(EntityAbilityProjectile projectile) {
|
||||
public static void trackProjectile(EntityEnergyProjectile projectile) {
|
||||
int ownerId = projectile.getOwnerEntityId();
|
||||
if (ownerId < 0) return;
|
||||
List<WeakReference<EntityAbilityProjectile>> refs = activeProjectiles.get(ownerId);
|
||||
List<WeakReference<EntityEnergyProjectile>> refs = activeProjectiles.get(ownerId);
|
||||
if (refs == null) {
|
||||
refs = new ArrayList<>();
|
||||
activeProjectiles.put(ownerId, refs);
|
||||
@@ -56,13 +56,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
refs.add(new WeakReference<>(projectile));
|
||||
}
|
||||
|
||||
public static List<EntityAbilityProjectile> getActiveProjectiles(int ownerEntityId) {
|
||||
List<WeakReference<EntityAbilityProjectile>> refs = activeProjectiles.get(ownerEntityId);
|
||||
public static List<EntityEnergyProjectile> getActiveProjectiles(int ownerEntityId) {
|
||||
List<WeakReference<EntityEnergyProjectile>> refs = activeProjectiles.get(ownerEntityId);
|
||||
if (refs == null) return Collections.emptyList();
|
||||
List<EntityAbilityProjectile> result = new ArrayList<>();
|
||||
Iterator<WeakReference<EntityAbilityProjectile>> it = refs.iterator();
|
||||
List<EntityEnergyProjectile> result = new ArrayList<>();
|
||||
Iterator<WeakReference<EntityEnergyProjectile>> it = refs.iterator();
|
||||
while (it.hasNext()) {
|
||||
EntityAbilityProjectile p = it.next().get();
|
||||
EntityEnergyProjectile p = it.next().get();
|
||||
if (p == null || p.isDead) {
|
||||
it.remove();
|
||||
} else {
|
||||
@@ -80,17 +80,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
protected EnergyCombatData combatData = new EnergyCombatData();
|
||||
|
||||
// ==================== EFFECT PROPERTIES ====================
|
||||
protected List<AbilityEffect> effects = new ArrayList<>();
|
||||
|
||||
// ==================== LIGHTNING EFFECT PROPERTIES ====================
|
||||
protected EnergyLightningData lightningData = new EnergyLightningData();
|
||||
|
||||
// Client-side lightning state (not saved to NBT)
|
||||
@cpw.mods.fml.relauncher.SideOnly(cpw.mods.fml.relauncher.Side.CLIENT)
|
||||
public transient Object lightningState; // Actually AttachedLightningRenderer.LightningState
|
||||
|
||||
// ==================== DISPLAY PROPERTIES ====================
|
||||
protected EnergyDisplayData displayData = new EnergyDisplayData();
|
||||
protected List<AbilityPotionEffect> effects = new ArrayList<>();
|
||||
|
||||
// ==================== ANCHOR PROPERTIES ====================
|
||||
protected EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.FRONT);
|
||||
@@ -108,23 +98,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
// ==================== TRACKING ====================
|
||||
protected double startX, startY, startZ;
|
||||
protected int ownerEntityId = -1;
|
||||
protected int targetEntityId = -1;
|
||||
|
||||
// ==================== STATE ====================
|
||||
protected boolean hasHit = false;
|
||||
protected boolean previewMode = false; // Client-side preview mode (no damage/effects)
|
||||
protected EntityLivingBase previewOwner = null; // Direct reference for GUI preview (no world lookup)
|
||||
|
||||
/** The ability that spawned this projectile. Transient, not saved to NBT. */
|
||||
protected transient Ability sourceAbility = null;
|
||||
|
||||
// ==================== CHARGING STATE ====================
|
||||
protected boolean charging = false;
|
||||
protected int chargeDuration = 40;
|
||||
protected int chargeTick = 0;
|
||||
// ==================== CHARGING STATE (projectile-specific) ====================
|
||||
protected float targetSize = 1.0f;
|
||||
protected static final int DW_CHARGING = 20;
|
||||
|
||||
// ==================== ROTATION INTERPOLATION ====================
|
||||
public float prevRotationValX, prevRotationValY, prevRotationValZ;
|
||||
@@ -139,12 +119,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
protected double interpTargetMotionX, interpTargetMotionY, interpTargetMotionZ;
|
||||
protected int interpSteps;
|
||||
|
||||
public EntityAbilityProjectile(World world) {
|
||||
public EntityEnergyProjectile(World world) {
|
||||
super(world);
|
||||
this.setSize(0.5f, 0.5f);
|
||||
this.noClip = false;
|
||||
this.isImmuneToFire = true;
|
||||
this.ignoreFrustumCheck = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,11 +155,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
this.prevRenderSize = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public boolean isInRangeToRenderDist(double distance) {
|
||||
@@ -190,11 +163,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
return distance < d1 * d1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
return pass == 1; // Translucent pass
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
// Store previous values for interpolation
|
||||
@@ -242,7 +210,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
}
|
||||
|
||||
// Set death time on first tick if not already set (handles chunk load/unload)
|
||||
if (deathWorldTime < 0 && worldObj != null) {
|
||||
// Skip during charging phase - lifetime starts when the entity actually fires
|
||||
if (deathWorldTime < 0 && worldObj != null && !isCharging()) {
|
||||
deathWorldTime = worldObj.getTotalWorldTime() + getMaxLifetime();
|
||||
}
|
||||
|
||||
@@ -270,6 +239,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 +286,37 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
*/
|
||||
protected abstract void updateProjectile();
|
||||
|
||||
// ==================== BARRIER COLLISION ====================
|
||||
|
||||
/**
|
||||
* Check for collision with energy barrier entities.
|
||||
* Barriers only block incoming projectiles from enemies.
|
||||
* @return true if projectile was absorbed (caller should stop processing)
|
||||
*/
|
||||
protected boolean checkBarrierCollision() {
|
||||
// Search area for barriers - must cover large domes (up to 30 radius)
|
||||
double searchRange = 32.0;
|
||||
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
|
||||
posX - searchRange, posY - searchRange, posZ - searchRange,
|
||||
posX + searchRange, posY + searchRange, posZ + searchRange
|
||||
);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<EntityEnergyBarrier> barriers = worldObj.getEntitiesWithinAABB(EntityEnergyBarrier.class, searchBox);
|
||||
for (EntityEnergyBarrier barrier : barriers) {
|
||||
if (barrier.isDead) continue;
|
||||
if (barrier.isIncomingProjectile(this)) {
|
||||
if (barrier.onProjectileHit(this, getDamage())) {
|
||||
hasHit = true;
|
||||
this.setDead();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== POSITION INTERPOLATION ====================
|
||||
|
||||
@Override
|
||||
@@ -353,23 +360,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PREVIEW MODE ====================
|
||||
|
||||
/**
|
||||
* Set preview mode for GUI display.
|
||||
* In preview mode, no damage or world effects are applied.
|
||||
*/
|
||||
public void setPreviewMode(boolean preview) {
|
||||
this.previewMode = preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity is in preview mode.
|
||||
*/
|
||||
public boolean isPreviewMode() {
|
||||
return previewMode;
|
||||
}
|
||||
|
||||
// ==================== DAMAGE & EFFECTS ====================
|
||||
|
||||
protected void applyDamage(EntityLivingBase target) {
|
||||
@@ -430,7 +420,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
}
|
||||
|
||||
protected void applyEffects(EntityLivingBase target) {
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effect.apply(target);
|
||||
}
|
||||
}
|
||||
@@ -438,25 +428,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
/**
|
||||
* Set the effects list from the ability's configured effects.
|
||||
*/
|
||||
public void setEffects(List<AbilityEffect> effects) {
|
||||
public void setEffects(List<AbilityPotionEffect> effects) {
|
||||
this.effects = effects != null ? effects : new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source ability that spawned this projectile.
|
||||
* Used by external damage handlers to access ability-specific data.
|
||||
*/
|
||||
public void setSourceAbility(Ability ability) {
|
||||
this.sourceAbility = ability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source ability that spawned this projectile.
|
||||
*/
|
||||
public Ability getSourceAbility() {
|
||||
return sourceAbility;
|
||||
}
|
||||
|
||||
protected void doExplosion() {
|
||||
if (previewMode) return; // Skip explosion in preview mode
|
||||
worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f);
|
||||
@@ -513,38 +488,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
prevPosZ = newZ;
|
||||
}
|
||||
|
||||
// ==================== CHARGING METHODS ====================
|
||||
|
||||
/**
|
||||
* Check if entity is in charging state (synced via data watcher).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) return this.charging;
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
protected void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
}
|
||||
|
||||
public float getInterpolatedChargeProgress(float partialTicks) {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
|
||||
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
return prevProgress + (currProgress - prevProgress) * partialTicks;
|
||||
}
|
||||
// ==================== CHARGING METHODS (projectile-specific) ====================
|
||||
|
||||
/**
|
||||
* Setup this entity in charging mode (for windup phase).
|
||||
@@ -677,37 +621,12 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
// ==================== ENTITY HELPERS ====================
|
||||
|
||||
public Entity getOwnerEntity() {
|
||||
// In preview mode, use direct reference (no world lookup)
|
||||
if (previewMode && previewOwner != null) {
|
||||
return previewOwner;
|
||||
}
|
||||
if (ownerEntityId == -1) return null;
|
||||
return worldObj.getEntityByID(ownerEntityId);
|
||||
}
|
||||
|
||||
public int getOwnerEntityId() {
|
||||
return ownerEntityId;
|
||||
}
|
||||
|
||||
public void setOwnerEntityId(int id) {
|
||||
this.ownerEntityId = id;
|
||||
}
|
||||
|
||||
public IEntity getOwner() {
|
||||
if (previewMode) return null;
|
||||
if (getOwnerEntity() == null) return null;
|
||||
return NpcAPI.Instance().getIEntity(getOwnerEntity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preview owner for GUI preview mode.
|
||||
* This allows anchor point calculations without world entity lookup.
|
||||
*/
|
||||
public void setPreviewOwner(EntityLivingBase owner) {
|
||||
this.previewOwner = owner;
|
||||
}
|
||||
|
||||
public int getTargetEntityId() {
|
||||
return targetEntityId;
|
||||
}
|
||||
@@ -728,22 +647,48 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
/**
|
||||
* Check if an entity should be ignored for collision.
|
||||
* Ignores: the owner, same-faction NPCs, and other projectiles from the same caster.
|
||||
* Ignores: the owner, same-faction NPCs, passive NPCs, NPCs friendly to player owner,
|
||||
* party members (if friendly fire is off), and other projectiles from the same caster.
|
||||
*/
|
||||
protected boolean shouldIgnoreEntity(Entity entity) {
|
||||
Entity owner = getOwnerEntity();
|
||||
if (entity == owner) return true;
|
||||
|
||||
if (owner instanceof EntityNPCInterface && entity instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface ownerNpc = (EntityNPCInterface) owner;
|
||||
EntityNPCInterface targetNpc = (EntityNPCInterface) entity;
|
||||
if (ownerNpc.faction.id == targetNpc.faction.id) return true;
|
||||
// Ignore other projectiles from the same caster (e.g. multi-projectile abilities)
|
||||
if (entity instanceof EntityEnergyProjectile) {
|
||||
EntityEnergyProjectile other = (EntityEnergyProjectile) entity;
|
||||
if (other.ownerEntityId == this.ownerEntityId) return true;
|
||||
}
|
||||
|
||||
// Ignore other projectiles from the same caster (e.g. multi-projectile abilities)
|
||||
if (entity instanceof EntityAbilityProjectile) {
|
||||
EntityAbilityProjectile other = (EntityAbilityProjectile) entity;
|
||||
if (other.ownerEntityId == this.ownerEntityId) return true;
|
||||
// NPC target checks
|
||||
if (entity instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface targetNpc = (EntityNPCInterface) entity;
|
||||
|
||||
// Ignore passive NPCs
|
||||
if (targetNpc.faction.isPassive) return true;
|
||||
|
||||
// NPC owner: ignore same-faction NPCs
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface ownerNpc = (EntityNPCInterface) owner;
|
||||
if (ownerNpc.faction.id == targetNpc.faction.id) return true;
|
||||
}
|
||||
|
||||
// Player owner: ignore NPCs whose faction is friendly to the player
|
||||
if (owner instanceof EntityPlayer) {
|
||||
if (targetNpc.faction.isFriendlyToPlayer((EntityPlayer) owner)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Party friendly fire check: player owner hitting another player
|
||||
if (owner instanceof EntityPlayer && entity instanceof EntityPlayer) {
|
||||
EntityPlayer ownerPlayer = (EntityPlayer) owner;
|
||||
EntityPlayer targetPlayer = (EntityPlayer) entity;
|
||||
PlayerData ownerData = PlayerData.get(ownerPlayer);
|
||||
PlayerData targetData = PlayerData.get(targetPlayer);
|
||||
if (ownerData.partyUUID != null && ownerData.partyUUID.equals(targetData.partyUUID)) {
|
||||
Party party = PartyController.Instance().getParty(ownerData.partyUUID);
|
||||
if (party != null && !party.friendlyFire()) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -768,24 +713,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
this.startZ = z;
|
||||
}
|
||||
|
||||
public int getInnerColor() { return displayData.getInnerColor(); }
|
||||
public void setInnerColor(int color) { displayData.setInnerColor(color); }
|
||||
|
||||
public int getOuterColor() { return displayData.getOuterColor(); }
|
||||
public void setOuterColor(int color) { displayData.setOuterColor(color); }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); }
|
||||
public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.getOuterColorWidth(); }
|
||||
public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); }
|
||||
public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); }
|
||||
|
||||
public float getRotationSpeed() { return displayData.getRotationSpeed(); }
|
||||
public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); }
|
||||
|
||||
// ==================== ROTATION GETTERS ====================
|
||||
|
||||
public float getInterpolatedRotationX(float partialTicks) {
|
||||
@@ -804,20 +731,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
return this.prevRenderSize + (this.renderCurrentSize - this.prevRenderSize) * partialTicks;
|
||||
}
|
||||
|
||||
// ==================== LIGHTNING GETTERS & SETTERS ====================
|
||||
|
||||
public boolean hasLightningEffect() { return lightningData.isLightningEffect(); }
|
||||
public void setLightningEffect(boolean enabled) { lightningData.setLightningEffect(enabled); }
|
||||
|
||||
public float getLightningDensity() { return lightningData.getLightningDensity(); }
|
||||
public void setLightningDensity(float density) { lightningData.setLightningDensity(density); }
|
||||
|
||||
public float getLightningRadius() { return lightningData.getLightningRadius(); }
|
||||
public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); }
|
||||
public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); }
|
||||
|
||||
// ==================== LIFESPAN GETTERS & SETTERS ====================
|
||||
|
||||
public float getMaxDistance() { return lifespanData.getMaxDistance(); }
|
||||
@@ -879,19 +792,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
public boolean getHasHit() { return hasHit; }
|
||||
|
||||
// ==================== BRIGHTNESS ====================
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks) {
|
||||
return 0xF000F0;
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
@@ -910,6 +810,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
* Read base projectile properties from NBT.
|
||||
*/
|
||||
protected void readBaseNBT(NBTTagCompound nbt) {
|
||||
readEnergyBaseNBT(nbt);
|
||||
|
||||
this.size = nbt.getFloat("Size");
|
||||
|
||||
// Read effects list
|
||||
@@ -917,7 +819,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
if (nbt.hasKey("Effects")) {
|
||||
NBTTagList effectsList = nbt.getTagList("Effects", 10); // 10 = compound tag
|
||||
for (int i = 0; i < effectsList.tagCount(); i++) {
|
||||
effects.add(AbilityEffect.fromNBT(effectsList.getCompoundTagAt(i)));
|
||||
effects.add(AbilityPotionEffect.fromNBT(effectsList.getCompoundTagAt(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,7 +829,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
this.startY = nbt.getDouble("StartY");
|
||||
this.startZ = nbt.getDouble("StartZ");
|
||||
|
||||
this.ownerEntityId = nbt.getInteger("OwnerId");
|
||||
this.targetEntityId = nbt.getInteger("TargetId");
|
||||
|
||||
this.motionX = nbt.getDouble("MotionX");
|
||||
@@ -941,10 +842,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
anchorData.readNBT(nbt);
|
||||
combatData.readNBT(nbt);
|
||||
displayData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
trajectoryData.readNBT(nbt);
|
||||
}
|
||||
|
||||
@@ -952,12 +851,14 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
* Write base projectile properties to NBT.
|
||||
*/
|
||||
protected void writeBaseNBT(NBTTagCompound nbt) {
|
||||
writeEnergyBaseNBT(nbt);
|
||||
|
||||
nbt.setFloat("Size", size);
|
||||
|
||||
// Write effects list
|
||||
if (!effects.isEmpty()) {
|
||||
NBTTagList effectsList = new NBTTagList();
|
||||
for (AbilityEffect effect : effects) {
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
effectsList.appendTag(effect.writeNBT());
|
||||
}
|
||||
nbt.setTag("Effects", effectsList);
|
||||
@@ -969,7 +870,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
nbt.setDouble("StartY", startY);
|
||||
nbt.setDouble("StartZ", startZ);
|
||||
|
||||
nbt.setInteger("OwnerId", ownerEntityId);
|
||||
nbt.setInteger("TargetId", targetEntityId);
|
||||
|
||||
nbt.setDouble("MotionX", motionX);
|
||||
@@ -978,10 +878,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
|
||||
anchorData.writeNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
displayData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
trajectoryData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
@@ -995,55 +893,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
|
||||
*/
|
||||
protected abstract void writeProjectileNBT(NBTTagCompound nbt);
|
||||
|
||||
// ==================== SPAWN DATA ====================
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer) {
|
||||
try {
|
||||
NBTTagCompound compound = new NBTTagCompound();
|
||||
this.writeEntityToNBT(compound);
|
||||
cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound);
|
||||
} catch (Exception e) {
|
||||
noppes.npcs.LogWriter.error("Error writing ability projectile spawn data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer) {
|
||||
try {
|
||||
NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer);
|
||||
if (compound != null) {
|
||||
this.readEntityFromNBT(compound);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
noppes.npcs.LogWriter.error("Error reading ability projectile spawn data", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== COLLISION SETTINGS ====================
|
||||
|
||||
@Override
|
||||
public boolean canBeCollidedWith() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBePushed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canTriggerWalking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public float getShadowSize() {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
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 EntityEnergyAbility to leverage the existing projectile infrastructure.
|
||||
*/
|
||||
public class EntityEnergySlicer extends EntityEnergyProjectile {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
+7
-9
@@ -20,18 +20,18 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Request packet to get a specific custom ability by UUID.
|
||||
* Request packet to get a specific custom ability by name.
|
||||
*/
|
||||
public final class CustomAbilityGetPacket extends AbstractPacket {
|
||||
public static String packetName = "Request|CustomAbilityGet";
|
||||
|
||||
private String uuid;
|
||||
private String name;
|
||||
|
||||
public CustomAbilityGetPacket() {
|
||||
}
|
||||
|
||||
public CustomAbilityGetPacket(String uuid) {
|
||||
this.uuid = uuid;
|
||||
public CustomAbilityGetPacket(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,7 +47,7 @@ public final class CustomAbilityGetPacket extends AbstractPacket {
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
ByteBufUtils.writeString(out, uuid);
|
||||
ByteBufUtils.writeString(out, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,12 +58,10 @@ public final class CustomAbilityGetPacket extends AbstractPacket {
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
String id = ByteBufUtils.readString(in);
|
||||
Ability ability = AbilityController.Instance.getCustomAbility(id);
|
||||
String abilityName = ByteBufUtils.readString(in);
|
||||
Ability ability = AbilityController.Instance.getCustomAbility(abilityName);
|
||||
if (ability != null) {
|
||||
NBTTagCompound compound = ability.writeNBT();
|
||||
// Include the UUID so the client can track it
|
||||
compound.setString("_uuid", id);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -19,18 +19,18 @@ import noppes.npcs.NoppesUtilServer;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Request packet to remove a custom ability by UUID.
|
||||
* Request packet to remove a custom ability by name.
|
||||
*/
|
||||
public final class CustomAbilityRemovePacket extends AbstractPacket {
|
||||
public static String packetName = "Request|CustomAbilityRemove";
|
||||
|
||||
private String uuid;
|
||||
private String name;
|
||||
|
||||
public CustomAbilityRemovePacket() {
|
||||
}
|
||||
|
||||
public CustomAbilityRemovePacket(String uuid) {
|
||||
this.uuid = uuid;
|
||||
public CustomAbilityRemovePacket(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,7 +51,7 @@ public final class CustomAbilityRemovePacket extends AbstractPacket {
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
ByteBufUtils.writeString(out, uuid);
|
||||
ByteBufUtils.writeString(out, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-1
@@ -71,7 +71,6 @@ public final class CustomAbilitySavePacket extends AbstractPacket {
|
||||
AbilityController.Instance.saveCustomAbility(ability);
|
||||
NoppesUtilServer.sendCustomAbilitiesData((EntityPlayerMP) player);
|
||||
NBTTagCompound responseNbt = ability.writeNBT();
|
||||
responseNbt.setString("_uuid", ability.getId());
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, responseNbt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package kamkeel.npcs.util;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Register<T> {
|
||||
@@ -36,6 +38,7 @@ public class Register<T> {
|
||||
}
|
||||
|
||||
public static class Abilities extends Register<Ability> {
|
||||
protected final Map<String, Map<String, Supplier<AbilityVariant>>> variantEntries = new LinkedHashMap<>();
|
||||
protected final Map<String, String> uniqueNames = new LinkedHashMap<>();
|
||||
protected final Set<String> typeOnly = new HashSet<>();
|
||||
|
||||
@@ -64,6 +67,23 @@ public class Register<T> {
|
||||
return factory.get();
|
||||
}
|
||||
|
||||
public AbilityVariant registerVariant(String typeId, String variantName, String group, Consumer<Ability> configurator) {
|
||||
registerVariant(typeId, variantName, () -> new AbilityVariant(variantName, group, configurator));
|
||||
return new AbilityVariant(variantName, group, configurator);
|
||||
}
|
||||
|
||||
public AbilityVariant registerVariant(String typeId, String factoryName, Supplier<AbilityVariant> factory) {
|
||||
if (!variantEntries.containsKey(typeId)) {
|
||||
variantEntries.put(typeId, new HashMap<>());
|
||||
}
|
||||
|
||||
Map<String, Supplier<AbilityVariant>> type = variantEntries.get(typeId);
|
||||
|
||||
String name = registryKey + "." + namespace + "." + factoryName.trim().toLowerCase().replaceAll(" ", "_");
|
||||
type.put(name, factory);
|
||||
return factory.get();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
for (Map.Entry<String, Supplier<Ability>> entry : entries.entrySet()) {
|
||||
AbilityController.Instance.registerType(entry.getKey(), entry.getValue());
|
||||
@@ -71,6 +91,14 @@ public class Register<T> {
|
||||
AbilityController.Instance.registerAbility(uniqueNames.get(entry.getKey()), entry.getValue().get());
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Map<String, Supplier<AbilityVariant>>> entry : variantEntries.entrySet()) {
|
||||
String typeId = entry.getKey();
|
||||
Map<String, Supplier<AbilityVariant>> map = entry.getValue();
|
||||
for (Map.Entry<String, Supplier<AbilityVariant>> innerEntry : map.entrySet()) {
|
||||
AbilityController.Instance.registerVariant(typeId, innerEntry.getValue().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Register.Abilities create(String namespace, String displayName) {
|
||||
@@ -117,7 +145,9 @@ public class Register<T> {
|
||||
try {
|
||||
String path = "/assets/" + namespace + "/" + animationsPath;
|
||||
for (Map.Entry<String, Supplier<Animation>> entry : entries.entrySet()) {
|
||||
String name = entry.getKey().substring(entry.getKey().indexOf(namespace + ".") + 1);
|
||||
String prefix = registryKey + "." + namespace + ".";
|
||||
String key = entry.getKey();
|
||||
String name = key.substring(prefix.length());
|
||||
AnimationController.Instance.loadBuiltInAnimation(modClass, path, name);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
package noppes.npcs;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPhase;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Shared base class for NPC and Player ability data management.
|
||||
* Contains the common ability lifecycle logic: tick, phase transitions,
|
||||
* animation/telegraph/sound management, cooldown state, and position/rotation locking.
|
||||
* <p>
|
||||
* Subclasses must implement abstract methods for entity-specific behavior:
|
||||
* event firing, rotation/position application, telegraph packets, and cooldown rolling.
|
||||
*
|
||||
* @see DataAbilities - NPC implementation
|
||||
* @see noppes.npcs.controllers.data.PlayerAbilityData - Player implementation
|
||||
*/
|
||||
public abstract class AbstractDataAbilities {
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SHARED RUNTIME STATE (not saved to NBT)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Currently executing ability (null if none) */
|
||||
protected Ability currentAbility;
|
||||
|
||||
/** World time when cooldown ends */
|
||||
protected long cooldownEndTime = 0;
|
||||
|
||||
/** Rotation lock state */
|
||||
protected boolean rotationLocked = false;
|
||||
protected float lockedYaw = 0;
|
||||
protected float lockedPitch = 0;
|
||||
|
||||
/** Position lock state */
|
||||
protected boolean positionLocked = false;
|
||||
protected double lockedPosX = 0;
|
||||
protected double lockedPosY = 0;
|
||||
protected double lockedPosZ = 0;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABSTRACT METHODS - Subclasses must implement
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Get the entity this ability data belongs to (NPC or Player). */
|
||||
protected abstract EntityLivingBase getEntity();
|
||||
|
||||
/** Get the current target for ability execution. */
|
||||
protected abstract EntityLivingBase getTarget();
|
||||
|
||||
/** Get the current world time. */
|
||||
protected abstract long getWorldTime();
|
||||
|
||||
// -- Event Firing --
|
||||
|
||||
/** Fire tick event. Implementation differs between NPC (NpcAPI) and Player (ScriptController). */
|
||||
protected abstract void fireTickEvent(Ability ability, EntityLivingBase target);
|
||||
|
||||
/** Fire execute event. Returns true if cancelled. */
|
||||
protected abstract boolean fireExecuteEvent(Ability ability, EntityLivingBase target);
|
||||
|
||||
/** Fire complete event. */
|
||||
protected abstract void fireCompleteEvent(Ability ability, EntityLivingBase target);
|
||||
|
||||
/** Fire interrupt event. */
|
||||
protected abstract void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage);
|
||||
|
||||
// -- Telegraph --
|
||||
|
||||
/** Spawn telegraphs for an ability. */
|
||||
protected abstract void spawnTelegraph(Ability ability, EntityLivingBase target);
|
||||
|
||||
/** Remove telegraphs for an ability. */
|
||||
protected abstract void removeTelegraph(Ability ability);
|
||||
|
||||
// -- Animation/Sound Data Access --
|
||||
|
||||
/** Set animation data on the entity. */
|
||||
protected abstract void setAnimationData(Animation animation);
|
||||
|
||||
/** Clear animation data on the entity. */
|
||||
protected abstract void clearAnimationData();
|
||||
|
||||
/** Play a sound at the entity's location. */
|
||||
protected abstract void playAbilitySound(String sound);
|
||||
|
||||
// -- Rotation (subclass handles extra fields and application) --
|
||||
|
||||
/** Capture rotation values for locking. NPC captures 4 fields, Player captures 2. */
|
||||
protected abstract void captureLockedRotation();
|
||||
|
||||
// -- Completion --
|
||||
|
||||
/**
|
||||
* Roll cooldown after ability completes.
|
||||
* NPC: random(min, max) + ability offset. Player: ability's base cooldownTicks.
|
||||
*/
|
||||
protected abstract void rollCooldown(Ability ability);
|
||||
|
||||
/** Additional completion logic (NPC clears lastTarget, Player clears key/target and syncs). */
|
||||
protected abstract void onAbilityComplete();
|
||||
|
||||
|
||||
// -- Hooks (optional overrides) --
|
||||
|
||||
/** Called before onExecute in ACTIVE phase. NPC uses this for faceTarget/hitScan. */
|
||||
protected void onPreExecute(Ability ability, EntityLivingBase target) {}
|
||||
|
||||
/** Called after the phase switch. NPC uses this for hit scan updates and movement control. */
|
||||
protected void onPostPhaseTick(Ability ability, EntityLivingBase target) {}
|
||||
|
||||
/** Additional lock releases in BURST_DELAY. NPC releases hitScan. */
|
||||
protected void onBurstDelayReleaseLocks() {}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SHARED ANIMATION METHODS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Play an animation on the entity.
|
||||
*/
|
||||
public void playAbilityAnimation(Animation animation) {
|
||||
if (animation == null) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
setAnimationData(animation);
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(int animation) {
|
||||
if (animation < 0) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation) == null) return;
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation));
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(String animation) {
|
||||
if (animation.isEmpty()) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation, true) == null) return;
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any currently playing ability animation.
|
||||
*/
|
||||
protected void stopAbilityAnimation() {
|
||||
clearAnimationData();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// STATE QUERIES
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Check if an ability is currently executing. */
|
||||
public boolean isExecutingAbility() {
|
||||
return currentAbility != null && currentAbility.isExecuting();
|
||||
}
|
||||
|
||||
/** Get the currently executing ability. */
|
||||
public Ability getCurrentAbility() {
|
||||
return currentAbility;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// COOLDOWN MANAGEMENT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Check if on cooldown. */
|
||||
public boolean isOnCooldown() {
|
||||
return getWorldTime() < cooldownEndTime;
|
||||
}
|
||||
|
||||
/** Get remaining cooldown ticks. */
|
||||
public long getRemainingCooldown() {
|
||||
long remaining = cooldownEndTime - getWorldTime();
|
||||
return remaining > 0 ? remaining : 0;
|
||||
}
|
||||
|
||||
/** Reset cooldown (allow immediate ability use). */
|
||||
public void resetCooldown() {
|
||||
cooldownEndTime = 0;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// POSITION LOCKING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Capture current position to lock entity in place.
|
||||
*/
|
||||
protected void captureLockedPosition() {
|
||||
EntityLivingBase entity = getEntity();
|
||||
lockedPosX = entity.posX;
|
||||
lockedPosY = entity.posY;
|
||||
lockedPosZ = entity.posZ;
|
||||
positionLocked = true;
|
||||
onPositionLockChanged(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release position lock.
|
||||
*/
|
||||
protected void releaseLockedPosition() {
|
||||
positionLocked = false;
|
||||
onPositionLockChanged(false);
|
||||
}
|
||||
|
||||
/** Hook for subclass to react to position lock state changes (e.g., NPC sets data watcher flag). */
|
||||
protected void onPositionLockChanged(boolean locked) {}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ROTATION LOCKING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Release rotation control.
|
||||
*/
|
||||
protected void releaseRotationControl() {
|
||||
rotationLocked = false;
|
||||
onRotationLockChanged(false);
|
||||
}
|
||||
|
||||
/** Hook for subclass to react to rotation lock state changes (e.g., NPC sets flag, clears hitScan). */
|
||||
protected void onRotationLockChanged(boolean locked) {}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CORE TICK - Phase transition logic shared between NPC and Player
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Tick the currently executing ability. Handles all phase transitions,
|
||||
* telegraph/animation/sound lifecycle, and burst management.
|
||||
* <p>
|
||||
* This is the core shared logic between DataAbilities and PlayerAbilityData.
|
||||
* Subclass-specific behavior is injected via abstract methods and hooks.
|
||||
*/
|
||||
protected void tickCurrentAbility() {
|
||||
EntityLivingBase entity = getEntity();
|
||||
EntityLivingBase target = getTarget();
|
||||
AbilityPhase oldPhase = currentAbility.getPhase();
|
||||
|
||||
// Tick advances time and possibly changes phase
|
||||
boolean phaseChanged = currentAbility.tick();
|
||||
|
||||
// Fire tick event
|
||||
fireTickEvent(currentAbility, target);
|
||||
|
||||
// Fire extender tick hook (e.g., per-tick resource drain)
|
||||
if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, entity, target,
|
||||
currentAbility.getPhase(), currentAbility.getCurrentTick())) {
|
||||
interruptCurrentAbility(null, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle phase-specific logic
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
// Burst replay: re-enter windup - set up locks, telegraph, sound, animation
|
||||
if (currentAbility.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (currentAbility.isMovementLockedDuringWindup() && !currentAbility.hasAbilityMovement()) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
spawnTelegraph(currentAbility, target);
|
||||
playAbilitySound(currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(entity, target, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) {
|
||||
// Just entered ACTIVE phase - lock telegraph positions
|
||||
for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) {
|
||||
telegraph.lockPosition();
|
||||
}
|
||||
|
||||
// Remove telegraph unless the ability keeps it during active phase
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
removeTelegraph(currentAbility);
|
||||
}
|
||||
|
||||
// Handle rotation control transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isRotationLockedDuringActive()) {
|
||||
if (!rotationLocked) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
} else if (rotationLocked) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Handle position lock transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isMovementLockedDuringActive() && !currentAbility.hasAbilityMovement()) {
|
||||
if (!positionLocked) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
} else if (positionLocked) {
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
playAbilitySound(currentAbility.getActiveSound());
|
||||
}
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
|
||||
// Pre-execute hook (NPC: snap to face target for hit scan)
|
||||
onPreExecute(currentAbility, target);
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
if (fireExecuteEvent(currentAbility, target)) {
|
||||
return; // Cancelled
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
currentAbility.onExecute(entity, target);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentAbility.onActiveTick(entity, target, currentAbility.getCurrentTick());
|
||||
|
||||
// Check if ability completed during onActiveTick
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-complete for burst overlap mode
|
||||
if (currentAbility.isBurstEnabled()
|
||||
&& currentAbility.getBurstIndex() < currentAbility.getBurstAmount()
|
||||
&& currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) {
|
||||
currentAbility.signalCompletion();
|
||||
}
|
||||
|
||||
// Check if ability entered burst delay
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
onBurstDelayReleaseLocks();
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Free movement and rotation during burst delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
break;
|
||||
|
||||
case DAZED:
|
||||
// Dazed phase - just wait for phase transition
|
||||
break;
|
||||
|
||||
case IDLE:
|
||||
// Ability completed (reached via tick() from DAZED)
|
||||
handleAbilityCompletion(target);
|
||||
break;
|
||||
}
|
||||
|
||||
// Post-phase hook (NPC: hit scan + movement control)
|
||||
onPostPhaseTick(currentAbility, target);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABILITY COMPLETION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Handle ability completion. Called when ability phase becomes IDLE.
|
||||
*/
|
||||
protected void handleAbilityCompletion(EntityLivingBase target) {
|
||||
if (currentAbility == null) return;
|
||||
|
||||
// Remove any remaining telegraphs
|
||||
removeTelegraph(currentAbility);
|
||||
|
||||
// Fire extender complete hook
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, getEntity(), target, false);
|
||||
|
||||
// Call onComplete callback
|
||||
currentAbility.onComplete(getEntity(), target);
|
||||
|
||||
// Fire complete event
|
||||
fireCompleteEvent(currentAbility, target);
|
||||
|
||||
// Release locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Subclass-specific completion (cooldown, cleanup, sync)
|
||||
rollCooldown(currentAbility);
|
||||
stopAbilityAnimation();
|
||||
onAbilityComplete();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// EXECUTE IMMEDIATE
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Execute an ability immediately (no windup). Called when windUpTicks is 0.
|
||||
*/
|
||||
protected void executeImmediate(Ability ability, EntityLivingBase target) {
|
||||
// Play active sound and animation
|
||||
playAbilitySound(ability.getActiveSound());
|
||||
playAbilityAnimation(ability.getActiveAnimation());
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
if (fireExecuteEvent(ability, target)) {
|
||||
return; // Cancelled
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
ability.onExecute(getEntity(), target);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (ability.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// INTERRUPTION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Interrupt the currently executing ability.
|
||||
* Shared interrupt logic: remove telegraph, play dazed animation,
|
||||
* release locks, fire events, call ability.interrupt().
|
||||
*/
|
||||
public void interruptCurrentAbility(DamageSource source, float damage) {
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
// Remove telegraph
|
||||
removeTelegraph(currentAbility);
|
||||
|
||||
// Fire extender complete hook (interrupted)
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, getEntity(), getTarget(), true);
|
||||
|
||||
// Fire interrupt event
|
||||
fireInterruptEvent(currentAbility, getTarget(), source, damage);
|
||||
|
||||
// Call ability callbacks
|
||||
currentAbility.onInterrupt(getEntity(), source, damage);
|
||||
currentAbility.interrupt();
|
||||
|
||||
// Play dazed animation
|
||||
stopAbilityAnimation();
|
||||
playAbilityAnimation(currentAbility.getDazedAnimation());
|
||||
|
||||
// Release locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Let subclass handle cleanup
|
||||
onInterruptComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook called after interrupt completes. Subclass handles clearing state and syncing.
|
||||
* NPC: keeps currentAbility (ticks through DAZED). Player: clears immediately.
|
||||
*/
|
||||
protected void onInterruptComplete() {}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -338,6 +344,7 @@ public class CustomNpcs {
|
||||
clientJaninoCompiler = null;
|
||||
|
||||
Server = event.getServer();
|
||||
AnimationController.Instance.load();
|
||||
ChunkController.Instance.clear();
|
||||
FactionController.getInstance().load();
|
||||
MagicController.getInstance().load();
|
||||
@@ -347,7 +354,6 @@ public class CustomNpcs {
|
||||
new GlobalDataController();
|
||||
new SpawnController();
|
||||
new LinkedNpcController();
|
||||
new AnimationController();
|
||||
AbilityController.Instance.load();
|
||||
TelegraphController.init();
|
||||
|
||||
|
||||
@@ -12,22 +12,20 @@ import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Manages NPC abilities - storage, selection, execution, and cooldowns.
|
||||
* Follows the DataStats/DataAI pattern for NPC data management.
|
||||
* Extends AbstractDataAbilities for shared ability lifecycle logic.
|
||||
*/
|
||||
public class DataAbilities {
|
||||
public class DataAbilities extends AbstractDataAbilities {
|
||||
|
||||
private final EntityNPCInterface npc;
|
||||
private final Random random = new Random();
|
||||
@@ -57,19 +55,9 @@ public class DataAbilities {
|
||||
public int maxCooldown = 200;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// RUNTIME STATE (not saved)
|
||||
// NPC-SPECIFIC RUNTIME STATE (not saved)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Currently executing ability (null if none)
|
||||
*/
|
||||
private transient Ability currentAbility;
|
||||
|
||||
/**
|
||||
* World time when NPC cooldown ends (can select next ability)
|
||||
*/
|
||||
private transient long cooldownEndTime = 0;
|
||||
|
||||
/**
|
||||
* Last target used for ability execution
|
||||
*/
|
||||
@@ -81,33 +69,26 @@ public class DataAbilities {
|
||||
private transient List<Long> recentHitTimes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Locked rotation values for ACTIVE phase when movement is locked.
|
||||
* Stored when entering ACTIVE phase, applied every tick to prevent rotation changes.
|
||||
* NPC-specific locked rotation fields (yawHead, renderYawOffset).
|
||||
* Base class handles lockedYaw and lockedPitch.
|
||||
*/
|
||||
private transient boolean rotationLocked = false;
|
||||
private transient float lockedYaw = 0;
|
||||
private transient float lockedYawHead = 0;
|
||||
private transient float lockedRenderYawOffset = 0;
|
||||
private transient float lockedPitch = 0;
|
||||
|
||||
/**
|
||||
* Locked position values for movement lock.
|
||||
* Stored when entering a locked phase, applied after super.onLivingUpdate()
|
||||
* to override any position changes from AI/physics.
|
||||
*/
|
||||
private transient boolean positionLocked = false;
|
||||
private transient double lockedPosX = 0;
|
||||
private transient double lockedPosY = 0;
|
||||
private transient double lockedPosZ = 0;
|
||||
|
||||
/**
|
||||
* Hit scan state - forces NPC to face target.
|
||||
* Set during tickCurrentAbility(), applied after super.onLivingUpdate()
|
||||
* Set during onPostPhaseTick(), applied after super.onLivingUpdate()
|
||||
* to override AI look helper rotation.
|
||||
*/
|
||||
private transient boolean hitScanActive = false;
|
||||
private transient EntityLivingBase hitScanTarget = null;
|
||||
|
||||
/** Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15 */
|
||||
private static final int ROTATION_CONTROLLED_FLAG = 16;
|
||||
|
||||
/** Bit flag for position lock in data watcher slot 15 */
|
||||
private static final int POSITION_LOCKED_FLAG = 32;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CONSTRUCTOR
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -116,6 +97,170 @@ public class DataAbilities {
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABSTRACT METHOD IMPLEMENTATIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
protected EntityLivingBase getEntity() {
|
||||
return npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityLivingBase getTarget() {
|
||||
return lastTarget != null ? lastTarget : npc.getAttackTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getWorldTime() {
|
||||
return npc.worldObj.getTotalWorldTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.TickEvent event = new AbilityEvent.TickEvent(
|
||||
npc.wrappedNPC, ability, target,
|
||||
ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fireExecuteEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.ExecuteEvent executeEvent = new AbilityEvent.ExecuteEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
return NpcAPI.EVENT_BUS.post(executeEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireCompleteEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.CompleteEvent completeEvent = new AbilityEvent.CompleteEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
NpcAPI.EVENT_BUS.post(completeEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent(
|
||||
npc.wrappedNPC, ability, target, source, damage);
|
||||
NpcAPI.EVENT_BUS.post(interruptEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnTelegraph(Ability ability, EntityLivingBase target) {
|
||||
List<TelegraphInstance> telegraphs = ability.createTelegraphs(npc, target);
|
||||
if (!telegraphs.isEmpty()) {
|
||||
ability.setTelegraphInstances(telegraphs);
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphSpawnPacket.sendToTracking(telegraph, npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeTelegraph(Ability ability) {
|
||||
List<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), npc);
|
||||
}
|
||||
ability.setTelegraphInstances(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setAnimationData(Animation animation) {
|
||||
npc.display.animationData.setEnabled(true);
|
||||
npc.display.animationData.setAnimation(animation);
|
||||
npc.display.animationData.updateClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearAnimationData() {
|
||||
npc.display.animationData.setAnimation(null);
|
||||
npc.display.animationData.updateClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playAbilitySound(String sound) {
|
||||
if (sound != null && !sound.isEmpty()) {
|
||||
npc.worldObj.playSoundAtEntity(npc, sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void captureLockedRotation() {
|
||||
lockedYaw = npc.rotationYaw;
|
||||
lockedYawHead = npc.rotationYawHead;
|
||||
lockedRenderYawOffset = npc.renderYawOffset;
|
||||
lockedPitch = npc.rotationPitch;
|
||||
rotationLocked = true;
|
||||
npc.setBoolFlag(true, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollCooldown(Ability ability) {
|
||||
int baseCooldown = minCooldown;
|
||||
if (maxCooldown > minCooldown) {
|
||||
baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1);
|
||||
}
|
||||
cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown;
|
||||
// Add ability-specific cooldown offset
|
||||
cooldownEndTime += ability.getCooldownTicks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAbilityComplete() {
|
||||
currentAbility = null;
|
||||
lastTarget = null;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// HOOK OVERRIDES
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
protected void onPreExecute(Ability ability, EntityLivingBase target) {
|
||||
// Snap NPC to face target before execute so startMoving() reads correct rotation
|
||||
if (ability.isHitScanForCurrentPhase() && target != null) {
|
||||
faceTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostPhaseTick(Ability ability, EntityLivingBase target) {
|
||||
// Update hit scan state - actual facing is deferred to applyRotationControl()
|
||||
// which runs AFTER super.onLivingUpdate() to override AI look helper
|
||||
if (ability != null && ability.isExecuting()
|
||||
&& ability.isHitScanForCurrentPhase() && target != null) {
|
||||
enableHitScan(target);
|
||||
} else if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Apply movement control if ability is still executing
|
||||
if (ability != null && ability.isExecuting()) {
|
||||
applyMovementControl();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBurstDelayReleaseLocks() {
|
||||
if (hitScanActive) releaseRotationControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPositionLockChanged(boolean locked) {
|
||||
npc.setBoolFlag(locked, POSITION_LOCKED_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRotationLockChanged(boolean locked) {
|
||||
if (!locked) {
|
||||
hitScanActive = false;
|
||||
hitScanTarget = null;
|
||||
}
|
||||
npc.setBoolFlag(locked, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TICK - Called from EntityNPCInterface.onLivingUpdate()
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -152,173 +297,15 @@ public class DataAbilities {
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Tick current ability if executing
|
||||
// Tick current ability if executing (shared logic from base class)
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
tickCurrentAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the currently executing ability.
|
||||
*/
|
||||
private void tickCurrentAbility() {
|
||||
EntityLivingBase target = lastTarget != null ? lastTarget : npc.getAttackTarget();
|
||||
AbilityPhase oldPhase = currentAbility.getPhase();
|
||||
|
||||
// Tick advances time and possibly changes phase
|
||||
boolean phaseChanged = currentAbility.tick();
|
||||
|
||||
// Fire tick event for scripts
|
||||
fireTickEvent(currentAbility, target);
|
||||
|
||||
// Fire extender tick hook (e.g., per-tick resource drain)
|
||||
if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, npc, target,
|
||||
currentAbility.getPhase(), currentAbility.getCurrentTick())) {
|
||||
interruptCurrentAbility(null, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle phase-specific logic
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
// Burst replay: re-enter windup - set up telegraph, sound, animation, locks
|
||||
if (currentAbility.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (currentAbility.isMovementLockedDuringWindup() && !currentAbility.hasAbilityMovement()) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
spawnTelegraph(currentAbility, target);
|
||||
playAbilitySound(currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(npc, target, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) {
|
||||
// Just entered ACTIVE phase - lock telegraph positions if they were following
|
||||
// This commits the ability to its current target position
|
||||
for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) {
|
||||
telegraph.lockPosition();
|
||||
}
|
||||
|
||||
// Remove telegraph unless the ability keeps it during active phase
|
||||
// (e.g., sweeping attacks where the telegraph shows the ongoing danger zone)
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
removeTelegraph(currentAbility);
|
||||
}
|
||||
|
||||
// Handle rotation control transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isRotationLockedDuringActive()) {
|
||||
if (!rotationLocked) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
} else if (rotationLocked) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Handle position lock transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isMovementLockedDuringActive() && !currentAbility.hasAbilityMovement()) {
|
||||
if (!positionLocked) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
} else if (positionLocked) {
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
// Abilities that keep telegraph during active manage their own sound timing
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
playAbilitySound(currentAbility.getActiveSound());
|
||||
}
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
|
||||
// Snap NPC to face target before execute so startMoving() reads correct rotation
|
||||
if (currentAbility.isHitScanForCurrentPhase() && target != null) {
|
||||
faceTarget(target);
|
||||
}
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
AbilityEvent.ExecuteEvent executeEvent = new AbilityEvent.ExecuteEvent(
|
||||
npc.wrappedNPC, currentAbility, target);
|
||||
if (NpcAPI.EVENT_BUS.post(executeEvent)) {
|
||||
// Event was cancelled - skip execution but continue to recovery
|
||||
return;
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
currentAbility.onExecute(npc, target);
|
||||
|
||||
// Check if ability completed during onExecute (signalCompletion was called)
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
currentAbility.onActiveTick(npc, target, currentAbility.getCurrentTick());
|
||||
|
||||
// Check if ability completed during onActiveTick (signalCompletion was called)
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-complete for burst overlap mode (entities fly independently)
|
||||
// This allows entity-spawning abilities to immediately chain next burst
|
||||
// without each type needing to check burst state in their onActiveTick()
|
||||
// Wait until all staggered projectiles have been fired before auto-completing
|
||||
if (currentAbility.isBurstEnabled()
|
||||
&& currentAbility.getBurstIndex() < currentAbility.getBurstAmount()
|
||||
&& currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) {
|
||||
currentAbility.signalCompletion();
|
||||
}
|
||||
|
||||
// Check if ability entered burst delay during onActiveTick or overlap auto-complete
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
// Release all locks - free movement/rotation during delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
if (hitScanActive) releaseRotationControl();
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Free movement and rotation during burst delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
break;
|
||||
|
||||
case DAZED:
|
||||
// Dazed phase - NPC cannot attack, just wait for daze to end
|
||||
break;
|
||||
|
||||
case IDLE:
|
||||
// Ability completed (reached via tick() phase transition from DAZED)
|
||||
handleAbilityCompletion(target);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update hit scan state - actual facing is deferred to applyRotationControl()
|
||||
// which runs AFTER super.onLivingUpdate() to override AI look helper
|
||||
if (currentAbility != null && currentAbility.isExecuting()
|
||||
&& currentAbility.isHitScanForCurrentPhase() && target != null) {
|
||||
enableHitScan(target);
|
||||
} else if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Apply movement control if ability is still executing
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
applyMovementControl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply movement control based on lock movement settings.
|
||||
* Called every tick during ability execution.
|
||||
* Called every tick during ability execution via onPostPhaseTick().
|
||||
*/
|
||||
private void applyMovementControl() {
|
||||
if (currentAbility == null) return;
|
||||
@@ -337,61 +324,6 @@ public class DataAbilities {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ability completion - called when ability phase becomes IDLE.
|
||||
* This can happen from:
|
||||
* - signalCompletion() called in onExecute() or onActiveTick()
|
||||
* - tick() transitioning from DAZED to IDLE
|
||||
*/
|
||||
private void handleAbilityCompletion(EntityLivingBase target) {
|
||||
if (currentAbility == null) return;
|
||||
|
||||
// Remove any remaining telegraphs (for abilities that keep telegraph during active)
|
||||
removeTelegraph(currentAbility);
|
||||
|
||||
// Fire extender complete hook
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, npc, target, false);
|
||||
|
||||
// Call onComplete callback
|
||||
currentAbility.onComplete(npc, target);
|
||||
|
||||
// Fire complete event
|
||||
AbilityEvent.CompleteEvent completeEvent = new AbilityEvent.CompleteEvent(
|
||||
npc.wrappedNPC, currentAbility, target);
|
||||
NpcAPI.EVENT_BUS.post(completeEvent);
|
||||
|
||||
// Release locks before cleanup
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Clean up and roll cooldown (this sets currentAbility to null)
|
||||
onAbilityComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an ability completes.
|
||||
* Calculates cooldown as: random(minCooldown, maxCooldown) + ability's cooldown offset
|
||||
*/
|
||||
private void onAbilityComplete() {
|
||||
if (currentAbility != null) {
|
||||
// Stop any ability animation
|
||||
stopAbilityAnimation();
|
||||
|
||||
// Release locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Calculate cooldown: random(min, max) + ability offset
|
||||
int abilityCooldownOffset = currentAbility.getCooldownTicks();
|
||||
rollCooldown();
|
||||
// Add ability-specific cooldown offset
|
||||
cooldownEndTime += abilityCooldownOffset;
|
||||
|
||||
currentAbility = null;
|
||||
lastTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABILITY SELECTION - Called from CombatHandler
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -521,7 +453,13 @@ public class DataAbilities {
|
||||
ability.start(target);
|
||||
|
||||
if (ability.getPhase() == AbilityPhase.ACTIVE) {
|
||||
// Windup was 0 — skip telegraph/windup and go straight to active
|
||||
// Windup was 0 — capture locks for immediate active phase, then execute
|
||||
if (ability.isRotationLockedDuringActive()) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (ability.isMovementLockedDuringActive() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
executeImmediate(ability, target);
|
||||
} else {
|
||||
// Normal windup flow
|
||||
@@ -539,137 +477,6 @@ public class DataAbilities {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an ability immediately (no windup).
|
||||
* Called when windUpTicks is 0.
|
||||
*/
|
||||
private void executeImmediate(Ability ability, EntityLivingBase target) {
|
||||
// Lock rotation if rotation is locked during ACTIVE phase
|
||||
if (ability.isRotationLockedDuringActive()) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (ability.isMovementLockedDuringActive() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
playAbilitySound(ability.getActiveSound());
|
||||
playAbilityAnimation(ability.getActiveAnimation());
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
AbilityEvent.ExecuteEvent executeEvent = new AbilityEvent.ExecuteEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
if (NpcAPI.EVENT_BUS.post(executeEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
ability.onExecute(npc, target);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (ability.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a sound at the NPC's location.
|
||||
*/
|
||||
private void playAbilitySound(String sound) {
|
||||
if (sound != null && !sound.isEmpty()) {
|
||||
npc.worldObj.playSoundAtEntity(npc, sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation on the NPC by ID.
|
||||
* Public so abilities can trigger animations directly if needed.
|
||||
*
|
||||
* @param animation The animation to play, or null if none
|
||||
*/
|
||||
public void playAbilityAnimation(Animation animation) {
|
||||
if (animation == null) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
|
||||
npc.display.animationData.setEnabled(true);
|
||||
npc.display.animationData.setAnimation(animation);
|
||||
npc.display.animationData.updateClient();
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(int animation) {
|
||||
if (animation < 0) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation) == null) return;
|
||||
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation));
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(String animation) {
|
||||
if (animation.isEmpty()) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation, true) == null) return;
|
||||
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any currently playing ability animation.
|
||||
*/
|
||||
private void stopAbilityAnimation() {
|
||||
npc.display.animationData.setAnimation(null);
|
||||
npc.display.animationData.updateClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn and send telegraphs for an ability.
|
||||
*/
|
||||
private void spawnTelegraph(Ability ability, EntityLivingBase target) {
|
||||
List<TelegraphInstance> telegraphs = ability.createTelegraphs(npc, target);
|
||||
if (!telegraphs.isEmpty()) {
|
||||
ability.setTelegraphInstances(telegraphs);
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphSpawnPacket.sendToTracking(telegraph, npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove telegraphs when ability ends or is interrupted.
|
||||
*/
|
||||
private void removeTelegraph(Ability ability) {
|
||||
List<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), npc);
|
||||
}
|
||||
ability.setTelegraphInstances(null);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// COOLDOWN MANAGEMENT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if NPC is on cooldown (cannot use any ability).
|
||||
*/
|
||||
public boolean isOnCooldown() {
|
||||
return npc.worldObj.getTotalWorldTime() < cooldownEndTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining cooldown ticks.
|
||||
*/
|
||||
public long getRemainingCooldown() {
|
||||
long remaining = cooldownEndTime - npc.worldObj.getTotalWorldTime();
|
||||
return remaining > 0 ? remaining : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cooldown (allow immediate ability use).
|
||||
*/
|
||||
public void resetCooldown() {
|
||||
cooldownEndTime = 0;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// INTERRUPTION - Called from CombatHandler on damage
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -748,39 +555,6 @@ public class DataAbilities {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupt the currently executing ability.
|
||||
* If interrupted during WINDUP, the ability will transition to DAZED phase.
|
||||
* When DAZED completes, the ability will move to IDLE and onAbilityComplete() is called.
|
||||
*/
|
||||
public void interruptCurrentAbility(DamageSource source, float damage) {
|
||||
if (currentAbility != null) {
|
||||
// Remove telegraph early
|
||||
removeTelegraph(currentAbility);
|
||||
|
||||
// Stop any ability animation
|
||||
stopAbilityAnimation();
|
||||
|
||||
// Release locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Fire extender complete hook (interrupted)
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, npc, lastTarget, true);
|
||||
|
||||
// Fire interrupt event
|
||||
AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent(
|
||||
npc.wrappedNPC, currentAbility, lastTarget, source, damage);
|
||||
NpcAPI.EVENT_BUS.post(interruptEvent);
|
||||
|
||||
currentAbility.onInterrupt(npc, source, damage);
|
||||
currentAbility.interrupt(); // Transitions to DAZED if interrupted during WINDUP
|
||||
|
||||
// Don't clear currentAbility - let it tick through DAZED phase
|
||||
// When DAZED ends and phase becomes IDLE, onAbilityComplete() will be called
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force stop the current ability (no interrupt event).
|
||||
*/
|
||||
@@ -849,16 +623,6 @@ public class DataAbilities {
|
||||
// SCRIPT EVENTS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Fire the ability tick event for scripts.
|
||||
*/
|
||||
private void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.TickEvent event = new AbilityEvent.TickEvent(
|
||||
npc.wrappedNPC, ability, target,
|
||||
ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire an ability hit event. Called by abilities when they hit an entity.
|
||||
* Returns null if the event was cancelled, otherwise returns the (possibly modified) event.
|
||||
@@ -902,7 +666,7 @@ public class DataAbilities {
|
||||
stopCurrentAbility();
|
||||
|
||||
// Roll cooldown so NPC doesn't immediately attack after reset
|
||||
rollCooldown();
|
||||
rollCooldownOnReset();
|
||||
|
||||
// Reset execution state on all resolved abilities
|
||||
for (Ability ability : getAbilities()) {
|
||||
@@ -911,10 +675,10 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a new cooldown using the min/max range.
|
||||
* Called after ability completes and on reset.
|
||||
* Roll a new cooldown using the min/max range (without ability offset).
|
||||
* Called on reset when no ability is involved.
|
||||
*/
|
||||
private void rollCooldown() {
|
||||
private void rollCooldownOnReset() {
|
||||
int baseCooldown = minCooldown;
|
||||
if (maxCooldown > minCooldown) {
|
||||
baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1);
|
||||
@@ -950,7 +714,7 @@ public class DataAbilities {
|
||||
abilitySlots.add(AbilitySlot.inline(ability));
|
||||
}
|
||||
|
||||
/** Add a reference ability by key (built-in name or custom UUID). */
|
||||
/** Add a reference ability by key (built-in name or custom ability name). */
|
||||
public void addAbilityReference(String key) {
|
||||
abilitySlots.add(AbilitySlot.reference(key));
|
||||
}
|
||||
@@ -1002,23 +766,9 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// STATE QUERIES
|
||||
// STATE QUERIES (NPC-specific)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if NPC is currently executing an ability.
|
||||
*/
|
||||
public boolean isExecutingAbility() {
|
||||
return currentAbility != null && currentAbility.isExecuting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently executing ability.
|
||||
*/
|
||||
public Ability getCurrentAbility() {
|
||||
return currentAbility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ability is controlling NPC movement (AI pathfinding should be blocked).
|
||||
* Called by AI tasks to determine if they should try to path.
|
||||
@@ -1086,35 +836,9 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ROTATION CONTROL
|
||||
// ROTATION CONTROL (NPC-specific: hit scan + 4-field rotation)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15 */
|
||||
private static final int ROTATION_CONTROLLED_FLAG = 16;
|
||||
|
||||
/**
|
||||
* Capture current rotation values to lock NPC's look direction.
|
||||
* Called when entering a LOCKED rotation phase.
|
||||
*/
|
||||
private void captureLockedRotation() {
|
||||
lockedYaw = npc.rotationYaw;
|
||||
lockedYawHead = npc.rotationYawHead;
|
||||
lockedRenderYawOffset = npc.renderYawOffset;
|
||||
lockedPitch = npc.rotationPitch;
|
||||
rotationLocked = true;
|
||||
npc.setBoolFlag(true, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release rotation control (both locked and hit scan).
|
||||
*/
|
||||
private void releaseRotationControl() {
|
||||
rotationLocked = false;
|
||||
hitScanActive = false;
|
||||
hitScanTarget = null;
|
||||
npc.setBoolFlag(false, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable hit scan tracking for the given target.
|
||||
*/
|
||||
@@ -1195,33 +919,9 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// POSITION LOCKING
|
||||
// POSITION LOCKING (NPC-specific: data watcher + client-side logic)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Bit flag for position lock in data watcher slot 15 */
|
||||
private static final int POSITION_LOCKED_FLAG = 32;
|
||||
|
||||
/**
|
||||
* Capture current position to lock NPC in place.
|
||||
* Called when entering a phase with movement lock.
|
||||
*/
|
||||
private void captureLockedPosition() {
|
||||
lockedPosX = npc.posX;
|
||||
lockedPosY = npc.posY;
|
||||
lockedPosZ = npc.posZ;
|
||||
positionLocked = true;
|
||||
npc.setBoolFlag(true, POSITION_LOCKED_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the position lock.
|
||||
* Called when leaving the locked phase or ability completes.
|
||||
*/
|
||||
private void releaseLockedPosition() {
|
||||
positionLocked = false;
|
||||
npc.setBoolFlag(false, POSITION_LOCKED_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply locked position values to the NPC.
|
||||
* Called AFTER super.onLivingUpdate() in EntityNPCInterface to override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package noppes.npcs;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.Event;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.network.packets.data.AchievementPacket;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -455,7 +455,7 @@ public class EventHooks {
|
||||
|
||||
// ==================== ENERGY PROJECTILE EVENTS ====================
|
||||
|
||||
public static void onEnergyProjectileFired(EntityAbilityProjectile projectile) {
|
||||
public static void onEnergyProjectileFired(EntityEnergyProjectile projectile) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
EnergyProjectileEvent.FiredEvent event = new EnergyProjectileEvent.FiredEvent(wrapped);
|
||||
ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_FIRED, event);
|
||||
@@ -473,7 +473,7 @@ public class EventHooks {
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void onEnergyProjectileTick(EntityAbilityProjectile projectile) {
|
||||
public static void onEnergyProjectileTick(EntityEnergyProjectile projectile) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped);
|
||||
ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_TICK, event);
|
||||
@@ -494,7 +494,7 @@ public class EventHooks {
|
||||
/**
|
||||
* Fires the entity impact event. Returns the (possibly modified) damage, or -1 if cancelled.
|
||||
*/
|
||||
public static float onEnergyProjectileEntityImpact(EntityAbilityProjectile projectile, EntityLivingBase target, float damage) {
|
||||
public static float onEnergyProjectileEntityImpact(EntityEnergyProjectile projectile, EntityLivingBase target, float damage) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
IEntity wrappedTarget = NpcAPI.Instance().getIEntity(target);
|
||||
EnergyProjectileEvent.EntityImpactEvent event = new EnergyProjectileEvent.EntityImpactEvent(wrapped, wrappedTarget, damage);
|
||||
@@ -518,7 +518,7 @@ public class EventHooks {
|
||||
return event.getDamage();
|
||||
}
|
||||
|
||||
public static void onEnergyProjectileBlockImpact(EntityAbilityProjectile projectile, int blockX, int blockY, int blockZ) {
|
||||
public static void onEnergyProjectileBlockImpact(EntityEnergyProjectile projectile, int blockX, int blockY, int blockZ) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
IPos pos = NpcAPI.Instance().getIPos(blockX, blockY, blockZ);
|
||||
IWorld world = NpcAPI.Instance().getIWorld(projectile.worldObj);
|
||||
@@ -539,7 +539,7 @@ public class EventHooks {
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void onEnergyProjectileExpired(EntityAbilityProjectile projectile) {
|
||||
public static void onEnergyProjectileExpired(EntityEnergyProjectile projectile) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
EnergyProjectileEvent.ExpiredEvent event = new EnergyProjectileEvent.ExpiredEvent(wrapped);
|
||||
ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_EXPIRED, event);
|
||||
@@ -557,6 +557,54 @@ public class EventHooks {
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
// ==================== ENERGY BARRIER EVENTS ====================
|
||||
|
||||
private static void dispatchBarrierEvent(net.minecraft.entity.Entity barrierEntity, int ownerEntityId,
|
||||
noppes.npcs.scripted.event.EnergyBarrierEvent event, EnumScriptType type) {
|
||||
ScriptController.Instance.globalNpcScripts.callScript(type, event);
|
||||
|
||||
net.minecraft.entity.Entity owner = barrierEntity.worldObj.getEntityByID(ownerEntityId);
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
((EntityNPCInterface) owner).script.callScript(type, event);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) owner);
|
||||
if (handler != null) {
|
||||
handler.callScript(type, event);
|
||||
}
|
||||
}
|
||||
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void onEnergyBarrierSpawned(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) {
|
||||
noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity);
|
||||
noppes.npcs.scripted.event.EnergyBarrierEvent.SpawnedEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.SpawnedEvent(wrapped);
|
||||
dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_SPAWNED);
|
||||
}
|
||||
|
||||
public static void onEnergyBarrierTick(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) {
|
||||
noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity);
|
||||
noppes.npcs.scripted.event.EnergyBarrierEvent.UpdateEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.UpdateEvent(wrapped);
|
||||
dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_TICK);
|
||||
}
|
||||
|
||||
public static float onEnergyBarrierHit(net.minecraft.entity.Entity barrierEntity, int ownerEntityId,
|
||||
EntityEnergyProjectile projectile, float damage) {
|
||||
noppes.npcs.api.entity.IEnergyBarrier wrappedBarrier = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity);
|
||||
noppes.npcs.api.entity.IEnergyProjectile wrappedProjectile = projectile != null
|
||||
? (noppes.npcs.api.entity.IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile) : null;
|
||||
noppes.npcs.scripted.event.EnergyBarrierEvent.HitEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.HitEvent(wrappedBarrier, wrappedProjectile, damage);
|
||||
dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_HIT);
|
||||
if (event.isCanceled()) return -1;
|
||||
return event.getDamage();
|
||||
}
|
||||
|
||||
public static void onEnergyBarrierDestroyed(net.minecraft.entity.Entity barrierEntity, int ownerEntityId) {
|
||||
noppes.npcs.api.entity.IEnergyBarrier wrapped = (noppes.npcs.api.entity.IEnergyBarrier) NpcAPI.Instance().getIEntity(barrierEntity);
|
||||
noppes.npcs.scripted.event.EnergyBarrierEvent.DestroyedEvent event = new noppes.npcs.scripted.event.EnergyBarrierEvent.DestroyedEvent(wrapped);
|
||||
dispatchBarrierEvent(barrierEntity, ownerEntityId, event, EnumScriptType.ENERGY_BARRIER_DESTROYED);
|
||||
}
|
||||
|
||||
public static void onPlayerInit(PlayerDataScript handler, IPlayer player) {
|
||||
PlayerEvent.InitEvent event = new PlayerEvent.InitEvent(player);
|
||||
handler.callScript(EnumScriptType.INIT, event);
|
||||
|
||||
@@ -781,8 +781,9 @@ public class NoppesUtilServer {
|
||||
if (controller != null) {
|
||||
int index = 0;
|
||||
for (String typeId : controller.getTypes()) {
|
||||
// Exclude built-in types from the NPC type list
|
||||
if (controller.isBuiltInType(typeId)) continue;
|
||||
if (!controller.isAllowedByNPC(typeId) && !controller.isAllowedByBoth(typeId))
|
||||
continue;
|
||||
|
||||
map.put(typeId, index++);
|
||||
}
|
||||
}
|
||||
@@ -809,13 +810,10 @@ public class NoppesUtilServer {
|
||||
kamkeel.npcs.controllers.data.ability.AbilityController controller = kamkeel.npcs.controllers.data.ability.AbilityController.Instance;
|
||||
if (controller != null) {
|
||||
int index = 0;
|
||||
for (String uuid : controller.getCustomAbilityIds()) {
|
||||
Ability ability = controller.getCustomAbility(uuid);
|
||||
for (String name : controller.getCustomAbilityNames()) {
|
||||
Ability ability = controller.getCustomAbility(name);
|
||||
if (ability == null || !ability.getAllowedBy().allowsNpc()) continue;
|
||||
|
||||
String name = controller.getCustomAbilityName(uuid);
|
||||
if (name == null || name.isEmpty()) name = uuid;
|
||||
map.put(name + "\t" + uuid, index++);
|
||||
map.put(name, index++);
|
||||
}
|
||||
}
|
||||
sendScrollData(player, map, EnumScrollData.CUSTOM_ABILITIES);
|
||||
|
||||
@@ -154,6 +154,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;
|
||||
@@ -161,6 +164,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;
|
||||
@@ -275,6 +281,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());
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import noppes.npcs.constants.EnumScrollData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
@@ -33,10 +32,8 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
private final SubGuiAbilityConfig parentConfig;
|
||||
private final GuiNPCAbilities parentAbilities;
|
||||
private String selectedDisplayName = null;
|
||||
private String selectedUuid = null;
|
||||
private String selectedName = null;
|
||||
|
||||
/** Maps display name -> UUID for custom abilities. */
|
||||
private final HashMap<String, String> displayToUuid = new HashMap<>();
|
||||
private HashMap<String, Integer> rawData = new HashMap<>();
|
||||
private GuiCustomScroll scroll;
|
||||
|
||||
@@ -97,15 +94,15 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
addButton(new GuiNpcButton(0, guiLeft + 10, y, 95, 20, "gui.load"));
|
||||
addButton(new GuiNpcButton(2, guiLeft + 115, y, 95, 20, "gui.cancel"));
|
||||
|
||||
getButton(0).setEnabled(selectedUuid != null);
|
||||
getButton(0).setEnabled(selectedName != null);
|
||||
}
|
||||
|
||||
private List<String> getFilteredList() {
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(displayToUuid.keySet());
|
||||
return new ArrayList<>(rawData.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : displayToUuid.keySet()) {
|
||||
for (String name : rawData.keySet()) {
|
||||
if (name.toLowerCase().contains(search.toLowerCase())) {
|
||||
list.add(name);
|
||||
}
|
||||
@@ -117,7 +114,7 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
public void buttonEvent(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
|
||||
if (id == 0 && selectedUuid != null) {
|
||||
if (id == 0 && selectedName != null) {
|
||||
if (parentAbilities != null) {
|
||||
// From NPC abilities GUI - ask clone/reference
|
||||
setSubGui(new SubGuiAbilityLoadMode());
|
||||
@@ -125,7 +122,7 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
// From config GUI - always clone
|
||||
waitingForLoad = true;
|
||||
pendingLoadMode = SubGuiAbilityLoadMode.MODE_CLONE;
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedName));
|
||||
}
|
||||
} else if (id == 2) {
|
||||
close();
|
||||
@@ -152,16 +149,16 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
if (mode < 0) return; // cancelled
|
||||
|
||||
if (mode == SubGuiAbilityLoadMode.MODE_REFERENCE) {
|
||||
// Reference mode - just pass the reference ID
|
||||
if (parentAbilities != null && selectedUuid != null) {
|
||||
parentAbilities.loadAbilityReference(selectedUuid);
|
||||
// Reference mode - just pass the reference name
|
||||
if (parentAbilities != null && selectedName != null) {
|
||||
parentAbilities.loadAbilityReference(selectedName);
|
||||
}
|
||||
close();
|
||||
} else {
|
||||
// Clone mode - request full data
|
||||
waitingForLoad = true;
|
||||
pendingLoadMode = SubGuiAbilityLoadMode.MODE_CLONE;
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,9 +167,7 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) {
|
||||
if (scroll.id == 0) {
|
||||
selectedDisplayName = scroll.getSelected();
|
||||
if (selectedDisplayName != null) {
|
||||
selectedUuid = displayToUuid.get(selectedDisplayName);
|
||||
}
|
||||
selectedName = selectedDisplayName;
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
@@ -181,15 +176,13 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
|
||||
if (scroll.id == 0 && selection != null && !selection.isEmpty()) {
|
||||
selectedDisplayName = selection;
|
||||
selectedUuid = displayToUuid.get(selectedDisplayName);
|
||||
if (selectedUuid != null) {
|
||||
if (parentAbilities != null) {
|
||||
setSubGui(new SubGuiAbilityLoadMode());
|
||||
} else {
|
||||
waitingForLoad = true;
|
||||
pendingLoadMode = SubGuiAbilityLoadMode.MODE_CLONE;
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
|
||||
}
|
||||
selectedName = selection;
|
||||
if (parentAbilities != null) {
|
||||
setSubGui(new SubGuiAbilityLoadMode());
|
||||
} else {
|
||||
waitingForLoad = true;
|
||||
pendingLoadMode = SubGuiAbilityLoadMode.MODE_CLONE;
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,22 +190,10 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
@Override
|
||||
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
|
||||
if (type == EnumScrollData.CUSTOM_ABILITIES) {
|
||||
displayToUuid.clear();
|
||||
rawData = data;
|
||||
for (String key : data.keySet()) {
|
||||
// key format: "displayName\tUUID"
|
||||
int tabIndex = key.indexOf('\t');
|
||||
if (tabIndex > 0) {
|
||||
String displayName = key.substring(0, tabIndex);
|
||||
String uuid = key.substring(tabIndex + 1);
|
||||
displayToUuid.put(displayName, uuid);
|
||||
} else {
|
||||
displayToUuid.put(key, key);
|
||||
}
|
||||
}
|
||||
if (scroll != null) {
|
||||
scroll.setList(getFilteredList());
|
||||
if (selectedDisplayName != null && displayToUuid.containsKey(selectedDisplayName)) {
|
||||
if (selectedDisplayName != null && rawData.containsKey(selectedDisplayName)) {
|
||||
scroll.setSelected(selectedDisplayName);
|
||||
}
|
||||
}
|
||||
@@ -226,11 +207,6 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
|
||||
waitingForLoad = false;
|
||||
Ability loaded = AbilityController.Instance.fromNBT(compound);
|
||||
if (loaded != null) {
|
||||
if (pendingLoadMode == SubGuiAbilityLoadMode.MODE_CLONE) {
|
||||
// Give it a new UUID so it's a unique inline copy
|
||||
loaded.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
if (parentConfig != null) {
|
||||
parentConfig.loadAbility(loaded);
|
||||
} else if (parentAbilities != null) {
|
||||
|
||||
@@ -58,8 +58,6 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS
|
||||
allDisplayNameToTypeId.clear();
|
||||
String[] types = AbilityController.Instance.getTypes();
|
||||
for (String typeId : types) {
|
||||
// Exclude built-in types — they cannot be created as custom presets
|
||||
if (AbilityController.Instance.isBuiltInType(typeId)) continue;
|
||||
String displayName = I18n.format(typeId);
|
||||
allDisplayNameToTypeId.put(displayName, typeId);
|
||||
}
|
||||
|
||||
@@ -284,12 +284,12 @@ public class GuiFieldBuilder {
|
||||
String btnText = def.getButtonLabel();
|
||||
int btnW;
|
||||
if (def.hasClearAction()) {
|
||||
btnW = contentRight - 22 - fieldX;
|
||||
GuiNpcButton clearBtn = new GuiNpcButton(clearId, contentRight - 20, y, 20, 20, "X");
|
||||
btnW = fieldW - 22;
|
||||
GuiNpcButton clearBtn = new GuiNpcButton(clearId, fieldX + fieldW - 20, y, 20, 20, "X");
|
||||
sw.addButton(clearBtn);
|
||||
clearFieldMap.put(clearId, def);
|
||||
} else {
|
||||
btnW = contentRight - fieldX;
|
||||
btnW = fieldW;
|
||||
}
|
||||
GuiNpcButton btn = new GuiNpcButton(widgetId, fieldX, y, btnW, 20, btnText);
|
||||
Integer textColor = def.getButtonTextColor();
|
||||
|
||||
@@ -3,7 +3,6 @@ package noppes.npcs.client.gui.global;
|
||||
import kamkeel.npcs.client.renderer.TelegraphRenderer;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import java.util.UUID;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket;
|
||||
@@ -62,10 +61,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
private GuiCustomScroll scroll;
|
||||
private HashMap<String, Integer> customData = new HashMap<>();
|
||||
private HashMap<String, Integer> builtInData = new HashMap<>();
|
||||
/** Maps display name -> UUID for custom abilities */
|
||||
private final HashMap<String, String> displayToUuid = new HashMap<>();
|
||||
private String selected = null;
|
||||
private String selectedUuid = null;
|
||||
private String search = "";
|
||||
private Ability selectedAbility = null;
|
||||
|
||||
@@ -117,7 +113,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
|
||||
// Remove button — only for custom view
|
||||
addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove"));
|
||||
getButton(1).setEnabled(selected != null && !selected.isEmpty() && displayToUuid.containsKey(selected));
|
||||
getButton(1).setEnabled(selected != null && !selected.isEmpty() && customData.containsKey(selected));
|
||||
}
|
||||
|
||||
// Edit button — hidden when viewing built-in
|
||||
@@ -178,29 +174,17 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
}
|
||||
|
||||
private List<String> getSearchList() {
|
||||
if (showingBuiltIn) {
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(builtInData.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : builtInData.keySet()) {
|
||||
if (name.toLowerCase().contains(search.toLowerCase())) {
|
||||
list.add(name);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
} else {
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(displayToUuid.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : displayToUuid.keySet()) {
|
||||
if (name.toLowerCase().contains(search.toLowerCase())) {
|
||||
list.add(name);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
HashMap<String, Integer> data = getCurrentData();
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(data.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : data.keySet()) {
|
||||
if (name.toLowerCase().contains(search.toLowerCase())) {
|
||||
list.add(name);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,7 +244,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
if (id == 10) {
|
||||
showingBuiltIn = !showingBuiltIn;
|
||||
selected = null;
|
||||
selectedUuid = null;
|
||||
selectedAbility = null;
|
||||
currentIsBuiltIn = false;
|
||||
search = "";
|
||||
@@ -276,7 +259,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
if (id == 2 && !showingBuiltIn) {
|
||||
// Add — open type selection
|
||||
setSubGui(new SubGuiAbilityTypeSelect());
|
||||
} else if (id == 1 && !showingBuiltIn && selected != null && selectedUuid != null) {
|
||||
} else if (id == 1 && !showingBuiltIn && selected != null) {
|
||||
GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), 1);
|
||||
displayGuiScreen(guiyesno);
|
||||
} else if (id == 100 && !showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) {
|
||||
@@ -325,17 +308,11 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
selected = newSelection;
|
||||
|
||||
if (showingBuiltIn) {
|
||||
// Fetch built-in ability by display name
|
||||
selectedUuid = null;
|
||||
currentIsBuiltIn = true;
|
||||
PacketClient.sendClient(new BuiltInAbilityGetPacket(selected));
|
||||
} else {
|
||||
// Fetch custom ability by UUID
|
||||
selectedUuid = displayToUuid.get(selected);
|
||||
currentIsBuiltIn = false;
|
||||
if (selectedUuid != null) {
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
|
||||
}
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selected));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,21 +334,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
if (type == EnumScrollData.CUSTOM_ABILITIES) {
|
||||
String prevSelected = scroll != null ? scroll.getSelected() : null;
|
||||
this.customData = data;
|
||||
displayToUuid.clear();
|
||||
for (String key : data.keySet()) {
|
||||
// key format: "displayName\tUUID"
|
||||
int tabIndex = key.indexOf('\t');
|
||||
if (tabIndex > 0) {
|
||||
String displayName = key.substring(0, tabIndex);
|
||||
String uuid = key.substring(tabIndex + 1);
|
||||
displayToUuid.put(displayName, uuid);
|
||||
} else {
|
||||
displayToUuid.put(key, key);
|
||||
}
|
||||
}
|
||||
if (!showingBuiltIn && scroll != null) {
|
||||
scroll.setList(getSearchList());
|
||||
if (prevSelected != null && displayToUuid.containsKey(prevSelected)) {
|
||||
if (prevSelected != null && customData.containsKey(prevSelected)) {
|
||||
scroll.setSelected(prevSelected);
|
||||
}
|
||||
}
|
||||
@@ -422,7 +387,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
Ability newAbility = AbilityController.Instance.create(pendingTypeId);
|
||||
if (newAbility != null) {
|
||||
variantGui.getVariants().get(idx).apply(newAbility);
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
if (hasDuplicateName(newAbility)) {
|
||||
pendingSaveAbility = newAbility;
|
||||
pendingNewCreation = true;
|
||||
@@ -450,7 +414,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
if (variants.size() == 1) {
|
||||
variants.get(0).apply(newAbility);
|
||||
}
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
if (hasDuplicateName(newAbility)) {
|
||||
pendingSaveAbility = newAbility;
|
||||
pendingNewCreation = true;
|
||||
@@ -504,7 +467,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
private void openConfig(Ability ability) {
|
||||
selectedAbility = ability;
|
||||
selected = ability.getName();
|
||||
selectedUuid = ability.getId();
|
||||
previewExecutor.stop();
|
||||
setSubGui(ability.createConfigGui(this));
|
||||
}
|
||||
@@ -512,12 +474,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
private boolean hasDuplicateName(Ability ability) {
|
||||
String name = ability.getName();
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
for (java.util.Map.Entry<String, String> entry : displayToUuid.entrySet()) {
|
||||
if (entry.getKey().equals(name) && !entry.getValue().equals(ability.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
String oldName = ability.getId();
|
||||
// A duplicate exists if the name is already in use and it's not the same ability being edited
|
||||
return customData.containsKey(name) && !name.equals(oldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -525,11 +484,10 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
NoppesUtil.openGUI(player, this);
|
||||
if (!result) return;
|
||||
|
||||
if (id == 1 && selected != null && selectedUuid != null) {
|
||||
PacketClient.sendClient(new CustomAbilityRemovePacket(selectedUuid));
|
||||
if (id == 1 && selected != null) {
|
||||
PacketClient.sendClient(new CustomAbilityRemovePacket(selected));
|
||||
scroll.clear();
|
||||
selected = null;
|
||||
selectedUuid = null;
|
||||
selectedAbility = null;
|
||||
initGui();
|
||||
}
|
||||
|
||||
@@ -131,7 +131,13 @@ public enum EnumScriptType {
|
||||
ENERGY_PROJECTILE_TICK("energyProjectileTick"),
|
||||
ENERGY_PROJECTILE_ENTITY_IMPACT("energyProjectileEntityImpact"),
|
||||
ENERGY_PROJECTILE_BLOCK_IMPACT("energyProjectileBlockImpact"),
|
||||
ENERGY_PROJECTILE_EXPIRED("energyProjectileExpired");
|
||||
ENERGY_PROJECTILE_EXPIRED("energyProjectileExpired"),
|
||||
|
||||
// Energy Barrier Events
|
||||
ENERGY_BARRIER_SPAWNED("energyBarrierSpawned"),
|
||||
ENERGY_BARRIER_TICK("energyBarrierTick"),
|
||||
ENERGY_BARRIER_HIT("energyBarrierHit"),
|
||||
ENERGY_BARRIER_DESTROYED("energyBarrierDestroyed");
|
||||
|
||||
public String function;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package noppes.npcs.controllers;
|
||||
|
||||
import kamkeel.npcs.util.Register;
|
||||
import net.minecraft.nbt.CompressedStreamTools;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
@@ -28,11 +29,7 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@@ -45,14 +42,15 @@ public class AnimationController implements IAnimationHandler {
|
||||
private static final String BUILTIN_ANIMATIONS_PATH = "/assets/customnpcs/animations";
|
||||
private static final String BUILTIN_ANIMATIONS_RESOURCE = "assets/customnpcs/animations";
|
||||
|
||||
public static AnimationController Instance;
|
||||
public static AnimationController Instance = new AnimationController();
|
||||
private int lastUsedID = 0;
|
||||
|
||||
public LinkedHashMap<String, Register.Animations> registeredAnimations = new LinkedHashMap<>();
|
||||
|
||||
public AnimationController() {
|
||||
Instance = this;
|
||||
bootOrder = new HashMap<>();
|
||||
animations = new HashMap<>();
|
||||
load();
|
||||
}
|
||||
|
||||
public static AnimationController getInstance() {
|
||||
@@ -65,6 +63,7 @@ public class AnimationController implements IAnimationHandler {
|
||||
builtInAnimations = new HashMap<>();
|
||||
LogWriter.info("Loading animations...");
|
||||
loadBuiltInAnimations();
|
||||
loadRegisteredBuiltIns();
|
||||
readAnimationMap();
|
||||
loadAnimations();
|
||||
LogWriter.info("Done loading animations.");
|
||||
@@ -124,6 +123,7 @@ public class AnimationController implements IAnimationHandler {
|
||||
String resourcePath = path + "/" + animName + ".json";
|
||||
try (InputStream stream = modClass.getResourceAsStream(resourcePath)) {
|
||||
if (stream == null) {
|
||||
LogWriter.error("Animation not found: " + animName.toLowerCase());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ public class AnimationController implements IAnimationHandler {
|
||||
|
||||
// Store with lowercase key for case-insensitive lookup
|
||||
builtInAnimations.put(animName.toLowerCase(), animation);
|
||||
LogWriter.info("Registered animation: " + animName.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +200,16 @@ public class AnimationController implements IAnimationHandler {
|
||||
saveAnimationMap();
|
||||
}
|
||||
|
||||
public void addAnimationRegister(String namespace, Register.Animations register) {
|
||||
registeredAnimations.put(namespace, register);
|
||||
}
|
||||
|
||||
private void loadRegisteredBuiltIns() {
|
||||
for (Register.Animations register : registeredAnimations.values()) {
|
||||
register.register();
|
||||
}
|
||||
}
|
||||
|
||||
private File getDir() {
|
||||
return new File(CustomNpcs.getWorldSaveDirectory(), "animations");
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package noppes.npcs.controllers.data;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityPhase;
|
||||
import kamkeel.npcs.controllers.data.ability.UserType;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityGuard;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
|
||||
@@ -15,6 +14,7 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.AbstractDataAbilities;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.api.ability.IPlayerAbilityData;
|
||||
@@ -35,6 +35,7 @@ import java.util.List;
|
||||
/**
|
||||
* Manages player ability data - unlocked abilities, selection, cooldowns, and execution.
|
||||
* Players reference abilities by key (built-in keys or preset names), never customized copies.
|
||||
* Extends AbstractDataAbilities for shared ability lifecycle logic.
|
||||
* <p>
|
||||
* Key differences from NPC DataAbilities:
|
||||
* - Universal cooldown (shared across all abilities)
|
||||
@@ -42,7 +43,7 @@ import java.util.List;
|
||||
* - Conditions requiring a target are skipped
|
||||
* - Uses ability's base cooldownTicks only
|
||||
*/
|
||||
public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerAbilityData {
|
||||
|
||||
private final PlayerData playerData;
|
||||
|
||||
@@ -69,19 +70,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
private boolean playingAbilityAnimation = false;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// RUNTIME STATE (not saved)
|
||||
// PLAYER-SPECIFIC RUNTIME STATE (not saved)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Universal cooldown end time (shared across all abilities).
|
||||
*/
|
||||
private transient long cooldownEndTime = 0;
|
||||
|
||||
/**
|
||||
* Currently executing ability instance (null if none).
|
||||
*/
|
||||
private transient Ability currentAbility;
|
||||
|
||||
/**
|
||||
* Current ability's key (for cooldown tracking after completion).
|
||||
*/
|
||||
@@ -92,21 +83,6 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
*/
|
||||
private transient EntityLivingBase currentTarget;
|
||||
|
||||
/**
|
||||
* Rotation lock state - freezes player yaw/pitch during ability phases.
|
||||
*/
|
||||
private transient boolean rotationLocked = false;
|
||||
private transient float lockedYaw = 0;
|
||||
private transient float lockedPitch = 0;
|
||||
|
||||
/**
|
||||
* Position lock state - freezes player position during ability phases.
|
||||
*/
|
||||
private transient boolean positionLocked = false;
|
||||
private transient double lockedPosX = 0;
|
||||
private transient double lockedPosY = 0;
|
||||
private transient double lockedPosZ = 0;
|
||||
|
||||
/**
|
||||
* Last synced state flags byte for change detection (avoids spamming packets).
|
||||
*/
|
||||
@@ -120,6 +96,160 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
this.playerData = playerData;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABSTRACT METHOD IMPLEMENTATIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
protected EntityLivingBase getEntity() {
|
||||
return playerData.player;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityLivingBase getTarget() {
|
||||
return currentTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getWorldTime() {
|
||||
EntityPlayer player = playerData.player;
|
||||
return player != null ? player.worldObj.getTotalWorldTime() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.TickEvent event = new PlayerAbilityEvent.TickEvent(
|
||||
iPlayer, ability, target, ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
EventHooks.onPlayerAbilityTick(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fireExecuteEvent(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return false;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return false;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.ExecuteEvent event = new PlayerAbilityEvent.ExecuteEvent(iPlayer, ability, target);
|
||||
return EventHooks.onPlayerAbilityExecute(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireCompleteEvent(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.CompleteEvent event = new PlayerAbilityEvent.CompleteEvent(iPlayer, ability, target);
|
||||
EventHooks.onPlayerAbilityComplete(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.InterruptEvent event = new PlayerAbilityEvent.InterruptEvent(
|
||||
iPlayer, ability, target, source, damage);
|
||||
EventHooks.onPlayerAbilityInterrupt(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnTelegraph(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
List<TelegraphInstance> telegraphs = ability.createTelegraphs(player, target);
|
||||
if (!telegraphs.isEmpty()) {
|
||||
ability.setTelegraphInstances(telegraphs);
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphSpawnPacket.sendToTracking(telegraph, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeTelegraph(Ability ability) {
|
||||
EntityPlayer player = playerData.player;
|
||||
List<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), player);
|
||||
}
|
||||
}
|
||||
ability.setTelegraphInstances(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setAnimationData(Animation animation) {
|
||||
playerData.animationData.setEnabled(true);
|
||||
playerData.animationData.setAnimation(animation);
|
||||
playerData.animationData.updateClient();
|
||||
playingAbilityAnimation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearAnimationData() {
|
||||
playerData.animationData.setAnimation(null);
|
||||
playerData.animationData.updateClient();
|
||||
playingAbilityAnimation = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playAbilitySound(String sound) {
|
||||
if (sound != null && !sound.isEmpty()) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player != null) {
|
||||
player.worldObj.playSoundAtEntity(player, sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void captureLockedRotation() {
|
||||
EntityPlayer player = playerData.player;
|
||||
lockedYaw = player.rotationYaw;
|
||||
lockedPitch = player.rotationPitch;
|
||||
rotationLocked = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollCooldown(Ability ability) {
|
||||
if (!ability.isIgnoreCooldown()) {
|
||||
cooldownEndTime = getWorldTime() + ability.getCooldownTicks();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAbilityComplete() {
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
syncAbilityStateClear(playerData.player);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// HOOK OVERRIDES
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
protected void onInterruptComplete() {
|
||||
// Player clears state immediately (unlike NPC which ticks through DAZED)
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
syncAbilityStateClear(playerData.player);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TICK - Called from player tick handler
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -134,7 +264,7 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
}
|
||||
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
tickCurrentAbility(player);
|
||||
tickCurrentAbility();
|
||||
|
||||
// Apply rotation and position locks after ability tick
|
||||
applyRotationControl(player);
|
||||
@@ -152,160 +282,6 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the currently executing ability.
|
||||
*/
|
||||
private void tickCurrentAbility(EntityPlayer player) {
|
||||
AbilityPhase oldPhase = currentAbility.getPhase();
|
||||
|
||||
// Tick advances time and possibly changes phase
|
||||
boolean phaseChanged = currentAbility.tick();
|
||||
|
||||
// Fire tick event for scripts
|
||||
firePlayerTickEvent(player, currentAbility, currentTarget);
|
||||
|
||||
// Fire extender tick hook (e.g., per-tick resource drain)
|
||||
if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, player, currentTarget,
|
||||
currentAbility.getPhase(), currentAbility.getCurrentTick())) {
|
||||
interruptCurrentAbility();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
// Burst replay: re-enter windup - set up locks
|
||||
if (currentAbility.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
if (currentAbility.isMovementLockedDuringWindup() && !currentAbility.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
spawnTelegraph(currentAbility, player, null);
|
||||
playAbilitySound(player, currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(player, currentTarget, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) {
|
||||
// Just entered ACTIVE phase - lock all telegraph positions
|
||||
for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) {
|
||||
telegraph.lockPosition();
|
||||
}
|
||||
// Remove telegraph unless the ability keeps it during active phase
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
removeTelegraph(currentAbility, player);
|
||||
}
|
||||
|
||||
// Handle rotation control transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isRotationLockedDuringActive()) {
|
||||
if (!rotationLocked) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
} else if (rotationLocked) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Handle position lock transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isMovementLockedDuringActive() && !currentAbility.hasAbilityMovement()) {
|
||||
if (!positionLocked) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
} else if (positionLocked) {
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
playAbilitySound(player, currentAbility.getActiveSound());
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
if (firePlayerExecuteEvent(player, currentAbility, currentTarget)) {
|
||||
return; // Cancelled
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
currentAbility.onExecute(player, currentTarget);
|
||||
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(player);
|
||||
return;
|
||||
}
|
||||
}
|
||||
currentAbility.onActiveTick(player, currentTarget, currentAbility.getCurrentTick());
|
||||
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-complete for burst overlap mode (entities fly independently)
|
||||
if (currentAbility.isBurstEnabled()
|
||||
&& currentAbility.getBurstIndex() < currentAbility.getBurstAmount()
|
||||
&& currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) {
|
||||
currentAbility.signalCompletion();
|
||||
}
|
||||
|
||||
// Release locks during burst delay
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Free movement and rotation during burst delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
break;
|
||||
|
||||
case DAZED:
|
||||
// Player is dazed - just wait
|
||||
break;
|
||||
|
||||
case IDLE:
|
||||
handleAbilityCompletion(player);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ability completion.
|
||||
*/
|
||||
private void handleAbilityCompletion(EntityPlayer player) {
|
||||
if (currentAbility == null) return;
|
||||
|
||||
// Fire extender complete hook
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, player, currentTarget, false);
|
||||
|
||||
currentAbility.onComplete(player, currentTarget);
|
||||
|
||||
// Fire complete event
|
||||
firePlayerCompleteEvent(player, currentAbility, currentTarget);
|
||||
|
||||
// Release all locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Apply universal cooldown (using base cooldownTicks only, not min/max random)
|
||||
if (!currentAbility.isIgnoreCooldown()) {
|
||||
cooldownEndTime = player.worldObj.getTotalWorldTime() + currentAbility.getCooldownTicks();
|
||||
}
|
||||
|
||||
// Stop animation
|
||||
stopAbilityAnimation();
|
||||
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
|
||||
// Send cleared state to client
|
||||
syncAbilityStateClear(player);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABILITY ACTIVATION - Called on Special Key press
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -348,7 +324,7 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
if (!ability.getAllowedBy().allowsPlayer()) return false;
|
||||
|
||||
// Check universal cooldown
|
||||
if (!ability.isIgnoreCooldown() && isOnCooldown(player)) return false;
|
||||
if (!ability.isIgnoreCooldown() && isOnCooldown()) return false;
|
||||
|
||||
// Check conditions (skip target-requiring ones)
|
||||
if (!ability.checkConditionsForPlayer(player)) return false;
|
||||
@@ -370,53 +346,30 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
ability.start(null);
|
||||
|
||||
if (ability.getPhase() == AbilityPhase.ACTIVE) {
|
||||
// Windup was 0 — skip telegraph/windup and go straight to active
|
||||
// Windup was 0 — capture locks for immediate active phase, then execute
|
||||
if (ability.isRotationLockedDuringActive()) {
|
||||
captureLockedRotation(player);
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (ability.isMovementLockedDuringActive() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
captureLockedPosition();
|
||||
}
|
||||
executeImmediate(ability, player);
|
||||
executeImmediate(ability, currentTarget);
|
||||
} else {
|
||||
// Normal windup flow
|
||||
if (ability.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation(player);
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (ability.isMovementLockedDuringWindup() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
captureLockedPosition();
|
||||
}
|
||||
spawnTelegraph(ability, player, null);
|
||||
playAbilitySound(player, ability.getWindUpSound());
|
||||
spawnTelegraph(ability, null);
|
||||
playAbilitySound(ability.getWindUpSound());
|
||||
playAbilityAnimation(ability.getWindUpAnimation());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an ability immediately (no windup).
|
||||
* Called when windUpTicks is 0.
|
||||
*/
|
||||
private void executeImmediate(Ability ability, EntityPlayer player) {
|
||||
// Play active sound and animation
|
||||
playAbilitySound(player, ability.getActiveSound());
|
||||
playAbilityAnimation(ability.getActiveAnimation());
|
||||
|
||||
// Fire execute event (cancelable)
|
||||
if (firePlayerExecuteEvent(player, ability, currentTarget)) {
|
||||
return; // Cancelled
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
ability.onExecute(player, currentTarget);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (ability.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(player);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CLIENT SYNC
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -508,9 +461,12 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// COOLDOWNS
|
||||
// COOLDOWNS (Player-specific overloads)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if on cooldown using player reference.
|
||||
*/
|
||||
public boolean isOnCooldown(EntityPlayer player) {
|
||||
return player.worldObj.getTotalWorldTime() < cooldownEndTime;
|
||||
}
|
||||
@@ -543,6 +499,10 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
cooldownEndTime = 0;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// IPlayerAbilityData INTERFACE
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
public boolean activateAbility() {
|
||||
return activateAbility(playerData.player);
|
||||
@@ -553,40 +513,36 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
return activateAbility(playerData.player, key);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// STATE QUERIES
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public boolean isExecutingAbility() {
|
||||
return currentAbility != null && currentAbility.isExecuting();
|
||||
@Override
|
||||
public boolean isOnCooldown() {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player == null) return false;
|
||||
return isOnCooldown(player);
|
||||
}
|
||||
|
||||
public Ability getCurrentAbility() {
|
||||
return currentAbility;
|
||||
@Override
|
||||
public boolean isOnCooldown(String key) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player == null) return false;
|
||||
return isOnCooldown(key, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCooldown() {
|
||||
cooldownEndTime = 0;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// INTERRUPTION (convenience overload)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public void interruptCurrentAbility() {
|
||||
interruptCurrentAbility(null, 0);
|
||||
}
|
||||
|
||||
public void interruptCurrentAbility(DamageSource source, float damage) {
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
// Fire extender complete hook (interrupted)
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, playerData.player, currentTarget, true);
|
||||
|
||||
// Fire interrupt event
|
||||
firePlayerInterruptEvent(playerData.player, currentAbility, currentTarget, source, damage);
|
||||
|
||||
currentAbility.interrupt();
|
||||
stopAbilityAnimation();
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
syncAbilityStateClear(playerData.player);
|
||||
}
|
||||
}
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// DAMAGE HANDLING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Handle damage taken while an ability is executing.
|
||||
@@ -635,24 +591,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnCooldown() {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player == null) return false;
|
||||
return isOnCooldown(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnCooldown(String key) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player == null) return false;
|
||||
return isOnCooldown(key, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCooldown() {
|
||||
cooldownEndTime = 0;
|
||||
}
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// STATE QUERIES (Player-specific)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if the player's movement should be locked due to an executing ability.
|
||||
@@ -663,26 +604,26 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
&& currentAbility.isMovementLockedForCurrentPhase();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ROTATION CONTROL
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Capture current rotation values to lock player's look direction.
|
||||
* Returns true if an ability-driven animation is currently playing.
|
||||
* Used by PlayerData.onLogin() to detect orphaned ability animations.
|
||||
*/
|
||||
private void captureLockedRotation(EntityPlayer player) {
|
||||
lockedYaw = player.rotationYaw;
|
||||
lockedPitch = player.rotationPitch;
|
||||
rotationLocked = true;
|
||||
public boolean isPlayingAbilityAnimation() {
|
||||
return playingAbilityAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release rotation lock.
|
||||
* Clears an orphaned ability animation (e.g. after relog when the ability is gone).
|
||||
* Stops the animation and resets the tracking flag.
|
||||
*/
|
||||
private void releaseRotationControl() {
|
||||
rotationLocked = false;
|
||||
public void clearOrphanedAbilityAnimation() {
|
||||
stopAbilityAnimation();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ROTATION CONTROL (Player-specific application)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Apply rotation control on the server side.
|
||||
* Enforces locked rotation values on the player entity.
|
||||
@@ -702,26 +643,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// POSITION LOCKING
|
||||
// POSITION LOCKING (Player-specific application)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Capture current position to lock player in place.
|
||||
*/
|
||||
private void captureLockedPosition(EntityPlayer player) {
|
||||
lockedPosX = player.posX;
|
||||
lockedPosY = player.posY;
|
||||
lockedPosZ = player.posZ;
|
||||
positionLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release position lock.
|
||||
*/
|
||||
private void releaseLockedPosition() {
|
||||
positionLocked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply position lock on the server side.
|
||||
* Snaps player back to locked position and zeroes motion.
|
||||
@@ -729,12 +653,23 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
private void applyPositionLock(EntityPlayer player) {
|
||||
if (!positionLocked) return;
|
||||
|
||||
player.setPosition(lockedPosX, lockedPosY, lockedPosZ);
|
||||
player.prevPosX = lockedPosX;
|
||||
player.prevPosY = lockedPosY;
|
||||
player.prevPosZ = lockedPosZ;
|
||||
boolean flying = AbilityController.Instance != null
|
||||
&& AbilityController.Instance.isPlayerFlying(player);
|
||||
|
||||
if (flying) {
|
||||
// Flying players: lock X/Z only, preserve Y freedom
|
||||
player.setPosition(lockedPosX, player.posY, lockedPosZ);
|
||||
player.prevPosX = lockedPosX;
|
||||
player.prevPosZ = lockedPosZ;
|
||||
} else {
|
||||
// Grounded players: full position lock including Y
|
||||
player.setPosition(lockedPosX, lockedPosY, lockedPosZ);
|
||||
player.prevPosX = lockedPosX;
|
||||
player.prevPosY = lockedPosY;
|
||||
player.prevPosZ = lockedPosZ;
|
||||
player.motionY = 0;
|
||||
}
|
||||
player.motionX = 0;
|
||||
player.motionY = 0; // Full vertical freeze for position lock
|
||||
player.motionZ = 0;
|
||||
}
|
||||
|
||||
@@ -788,89 +723,7 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TELEGRAPH
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
private void spawnTelegraph(Ability ability, EntityPlayer player, EntityLivingBase target) {
|
||||
List<TelegraphInstance> telegraphs = ability.createTelegraphs(player, target);
|
||||
if (!telegraphs.isEmpty()) {
|
||||
ability.setTelegraphInstances(telegraphs);
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
TelegraphSpawnPacket.sendToTracking(telegraph, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTelegraph(Ability ability, EntityPlayer player) {
|
||||
List<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
|
||||
for (TelegraphInstance telegraph : telegraphs) {
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), player);
|
||||
}
|
||||
}
|
||||
ability.setTelegraphInstances(null);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ANIMATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public void playAbilityAnimation(Animation animation) {
|
||||
if (animation == null || AnimationController.Instance == null) return;
|
||||
playerData.animationData.setEnabled(true);
|
||||
playerData.animationData.setAnimation(animation);
|
||||
playerData.animationData.updateClient();
|
||||
playingAbilityAnimation = true;
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(int animation) {
|
||||
if (animation < 0) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation) == null) return;
|
||||
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation));
|
||||
}
|
||||
|
||||
public void playAbilityAnimation(String animation) {
|
||||
if (animation.isEmpty()) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
if (AnimationController.Instance.get(animation, true) == null) return;
|
||||
|
||||
playAbilityAnimation((Animation) AnimationController.Instance.get(animation, true));
|
||||
}
|
||||
|
||||
private void stopAbilityAnimation() {
|
||||
playerData.animationData.setAnimation(null);
|
||||
playerData.animationData.updateClient();
|
||||
playingAbilityAnimation = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an ability-driven animation is currently playing.
|
||||
* Used by PlayerData.onLogin() to detect orphaned ability animations.
|
||||
*/
|
||||
public boolean isPlayingAbilityAnimation() {
|
||||
return playingAbilityAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an orphaned ability animation (e.g. after relog when the ability is gone).
|
||||
* Stops the animation and resets the tracking flag.
|
||||
*/
|
||||
public void clearOrphanedAbilityAnimation() {
|
||||
stopAbilityAnimation();
|
||||
}
|
||||
|
||||
private void playAbilitySound(EntityPlayer player, String sound) {
|
||||
if (sound != null && !sound.isEmpty()) {
|
||||
player.worldObj.playSoundAtEntity(player, sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SCRIPT EVENTS
|
||||
// SCRIPT EVENTS (Player-specific: start event only, others via abstract)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
@@ -885,57 +738,6 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
return EventHooks.onPlayerAbilityStart(handler, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the ability execute event. Returns true if cancelled.
|
||||
*/
|
||||
private boolean firePlayerExecuteEvent(EntityPlayer player, Ability ability, EntityLivingBase target) {
|
||||
if (ScriptController.Instance == null) return false;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return false;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.ExecuteEvent event = new PlayerAbilityEvent.ExecuteEvent(iPlayer, ability, target);
|
||||
return EventHooks.onPlayerAbilityExecute(handler, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the ability tick event.
|
||||
*/
|
||||
private void firePlayerTickEvent(EntityPlayer player, Ability ability, EntityLivingBase target) {
|
||||
if (ScriptController.Instance == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.TickEvent event = new PlayerAbilityEvent.TickEvent(
|
||||
iPlayer, ability, target, ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
EventHooks.onPlayerAbilityTick(handler, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the ability interrupt event.
|
||||
*/
|
||||
private void firePlayerInterruptEvent(EntityPlayer player, Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
if (ScriptController.Instance == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.InterruptEvent event = new PlayerAbilityEvent.InterruptEvent(
|
||||
iPlayer, ability, target, source, damage);
|
||||
EventHooks.onPlayerAbilityInterrupt(handler, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the ability complete event.
|
||||
*/
|
||||
private void firePlayerCompleteEvent(EntityPlayer player, Ability ability, EntityLivingBase target) {
|
||||
if (ScriptController.Instance == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.CompleteEvent event = new PlayerAbilityEvent.CompleteEvent(iPlayer, ability, target);
|
||||
EventHooks.onPlayerAbilityComplete(handler, event);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// NBT SERIALIZATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -11,6 +11,7 @@ import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.command.CommandHandler;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -127,6 +128,7 @@ import noppes.npcs.scripted.entity.ScriptEnergyBeam;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyDisc;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyLaser;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyOrb;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyProjectile;
|
||||
import noppes.npcs.scripted.entity.ScriptEntity;
|
||||
import noppes.npcs.scripted.entity.ScriptEntityItem;
|
||||
import noppes.npcs.scripted.entity.ScriptFishHook;
|
||||
@@ -470,6 +472,14 @@ public class NpcAPI extends AbstractNpcAPI {
|
||||
data = new ScriptEntityData(new ScriptEnergyDisc<>((EntityAbilityDisc) entity));
|
||||
else if (entity instanceof EntityAbilityLaser)
|
||||
data = new ScriptEntityData(new ScriptEnergyLaser<>((EntityAbilityLaser) entity));
|
||||
else if (entity instanceof kamkeel.npcs.entity.EntityEnergySlicer)
|
||||
data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergySlicer<>((kamkeel.npcs.entity.EntityEnergySlicer) entity));
|
||||
else if (entity instanceof EntityEnergyProjectile)
|
||||
data = new ScriptEntityData(new ScriptEnergyProjectile((EntityEnergyProjectile) entity));
|
||||
else if (entity instanceof kamkeel.npcs.entity.EntityEnergyDome)
|
||||
data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyDome<>((kamkeel.npcs.entity.EntityEnergyDome) entity));
|
||||
else if (entity instanceof kamkeel.npcs.entity.EntityEnergyPanel)
|
||||
data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyPanel<>((kamkeel.npcs.entity.EntityEnergyPanel) entity));
|
||||
else
|
||||
data = new ScriptEntityData(new ScriptEntity<>(entity));
|
||||
entity.registerExtendedProperties("ScriptedObject", data);
|
||||
|
||||
@@ -19,4 +19,8 @@ public class EntityType {
|
||||
public static final int ENERGY_BEAM = 15;
|
||||
public static final int ENERGY_DISC = 16;
|
||||
public static final int ENERGY_LASER = 17;
|
||||
public static final int ENERGY_SLICER = 18;
|
||||
public static final int ENERGY_BARRIER = 19;
|
||||
public static final int ENERGY_DOME = 20;
|
||||
public static final int ENERGY_PANEL = 21;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyAbility;
|
||||
import net.minecraft.entity.Entity;
|
||||
import noppes.npcs.api.entity.IEnergyAbility;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
* Base script wrapper for all energy ability entities.
|
||||
* Implements shared display, lightning, owner, and charging methods.
|
||||
*/
|
||||
public abstract class ScriptEnergyAbility<T extends EntityEnergyAbility> extends ScriptEntity<T> implements IEnergyAbility {
|
||||
|
||||
public ScriptEnergyAbility(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
// ==================== OWNER ====================
|
||||
|
||||
public int getOwnerEntityId() {
|
||||
return entity.getOwnerEntityId();
|
||||
}
|
||||
|
||||
public IEntity getOwner() {
|
||||
Entity owner = entity.getOwnerEntity();
|
||||
return owner != null ? NpcAPI.Instance().getIEntity(owner) : null;
|
||||
}
|
||||
|
||||
// ==================== DISPLAY ====================
|
||||
|
||||
public int getInnerColor() { return entity.getInnerColor(); }
|
||||
public void setInnerColor(int color) { entity.setInnerColor(color); }
|
||||
|
||||
public float getInnerAlpha() { return entity.getInnerAlpha(); }
|
||||
public void setInnerAlpha(float alpha) { entity.setInnerAlpha(alpha); }
|
||||
|
||||
public int getOuterColor() { return entity.getOuterColor(); }
|
||||
public void setOuterColor(int color) { entity.setOuterColor(color); }
|
||||
|
||||
public boolean isOuterColorEnabled() { return entity.isOuterColorEnabled(); }
|
||||
public void setOuterColorEnabled(boolean enabled) { entity.setOuterColorEnabled(enabled); }
|
||||
|
||||
public float getOuterColorWidth() { return entity.getOuterColorWidth(); }
|
||||
public void setOuterColorWidth(float width) { entity.setOuterColorWidth(width); }
|
||||
|
||||
public float getOuterColorAlpha() { return entity.getOuterColorAlpha(); }
|
||||
public void setOuterColorAlpha(float alpha) { entity.setOuterColorAlpha(alpha); }
|
||||
|
||||
// ==================== LIGHTNING ====================
|
||||
|
||||
public boolean hasLightningEffect() { return entity.hasLightningEffect(); }
|
||||
public void setLightningEffect(boolean enabled) { entity.setLightningEffect(enabled); }
|
||||
|
||||
public float getLightningDensity() { return entity.getLightningDensity(); }
|
||||
public void setLightningDensity(float density) { entity.setLightningDensity(density); }
|
||||
|
||||
public float getLightningRadius() { return entity.getLightningRadius(); }
|
||||
public void setLightningRadius(float radius) { entity.setLightningRadius(radius); }
|
||||
|
||||
public int getLightningFadeTime() { return entity.getLightningFadeTime(); }
|
||||
public void setLightningFadeTime(int ticks) { entity.setLightningFadeTime(ticks); }
|
||||
|
||||
// ==================== STATE ====================
|
||||
|
||||
public boolean isCharging() { return entity.isCharging(); }
|
||||
public float getChargeProgress() { return entity.getChargeProgress(); }
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import noppes.npcs.api.entity.IEnergyBarrier;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
/**
|
||||
* Base script wrapper for energy barrier entities (Dome, Panel).
|
||||
* All shared methods delegate directly to EntityEnergyBarrier base.
|
||||
*/
|
||||
public class ScriptEnergyBarrier<T extends EntityEnergyBarrier> extends ScriptEnergyAbility<T> implements IEnergyBarrier {
|
||||
|
||||
public ScriptEnergyBarrier(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return EntityType.ENERGY_BARRIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean typeOf(int type) {
|
||||
return type == EntityType.ENERGY_BARRIER || super.typeOf(type);
|
||||
}
|
||||
|
||||
// ==================== HEALTH ====================
|
||||
|
||||
public float getCurrentHealth() {
|
||||
return entity.getCurrentHealth();
|
||||
}
|
||||
|
||||
public void setCurrentHealth(float health) {
|
||||
entity.setCurrentHealth(health);
|
||||
}
|
||||
|
||||
public float getHealthPercent() {
|
||||
return entity.getHealthPercent();
|
||||
}
|
||||
|
||||
public float getMaxHealth() {
|
||||
return entity.getBarrierData().maxHealth;
|
||||
}
|
||||
|
||||
public void setMaxHealth(float maxHealth) {
|
||||
entity.getBarrierData().setMaxHealth(maxHealth);
|
||||
}
|
||||
|
||||
public boolean isUseHealth() {
|
||||
return entity.getBarrierData().useHealth;
|
||||
}
|
||||
|
||||
public void setUseHealth(boolean useHealth) {
|
||||
entity.getBarrierData().useHealth = useHealth;
|
||||
}
|
||||
|
||||
// ==================== DURATION ====================
|
||||
|
||||
public int getDuration() {
|
||||
return entity.getBarrierData().durationTicks;
|
||||
}
|
||||
|
||||
public void setDuration(int ticks) {
|
||||
entity.getBarrierData().setDurationTicks(ticks);
|
||||
}
|
||||
|
||||
public boolean isUseDuration() {
|
||||
return entity.getBarrierData().useDuration;
|
||||
}
|
||||
|
||||
public void setUseDuration(boolean useDuration) {
|
||||
entity.getBarrierData().useDuration = useDuration;
|
||||
}
|
||||
|
||||
// ==================== STATE (BARRIER-SPECIFIC) ====================
|
||||
|
||||
public int getTicksAlive() {
|
||||
return entity.getTicksAlive();
|
||||
}
|
||||
|
||||
// ==================== BARRIER ====================
|
||||
|
||||
public float getDefaultMultiplier() { return entity.getBarrierData().defaultMultiplier; }
|
||||
public void setDefaultMultiplier(float multiplier) { entity.getBarrierData().defaultMultiplier = multiplier; }
|
||||
|
||||
public boolean isKnockbackEnabled() { return entity.getBarrierData().knockbackEnabled; }
|
||||
public void setKnockbackEnabled(boolean enabled) { entity.getBarrierData().knockbackEnabled = enabled; }
|
||||
|
||||
public float getKnockbackStrength() { return entity.getBarrierData().knockbackStrength; }
|
||||
public void setKnockbackStrength(float strength) { entity.getBarrierData().knockbackStrength = strength; }
|
||||
|
||||
public int getKnockbackTarget() { return entity.getBarrierData().knockbackTarget; }
|
||||
public void setKnockbackTarget(int target) { entity.getBarrierData().knockbackTarget = target; }
|
||||
|
||||
public int getBarrierType() {
|
||||
return entity instanceof EntityEnergyDome ? 0 : 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import noppes.npcs.api.entity.IEnergyDome;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
public class ScriptEnergyDome<T extends EntityEnergyDome> extends ScriptEnergyBarrier<T> implements IEnergyDome {
|
||||
|
||||
public ScriptEnergyDome(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return EntityType.ENERGY_DOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean typeOf(int type) {
|
||||
return type == EntityType.ENERGY_DOME || super.typeOf(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBarrierType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== DOME-SPECIFIC ====================
|
||||
|
||||
public float getDomeRadius() { return entity.getDomeRadius(); }
|
||||
public void setDomeRadius(float radius) { entity.setDomeRadius(radius); }
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyPanel;
|
||||
import noppes.npcs.api.entity.IEnergyPanel;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
public class ScriptEnergyPanel<T extends EntityEnergyPanel> extends ScriptEnergyBarrier<T> implements IEnergyPanel {
|
||||
|
||||
public ScriptEnergyPanel(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return EntityType.ENERGY_PANEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean typeOf(int type) {
|
||||
return type == EntityType.ENERGY_PANEL || super.typeOf(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBarrierType() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ==================== PANEL-SPECIFIC ====================
|
||||
|
||||
public float getPanelWidth() { return entity.getPanelData().panelWidth; }
|
||||
public void setPanelWidth(float width) { entity.getPanelData().setPanelWidth(width); }
|
||||
|
||||
public float getPanelHeight() { return entity.getPanelData().panelHeight; }
|
||||
public void setPanelHeight(float height) { entity.getPanelData().setPanelHeight(height); }
|
||||
|
||||
public float getPanelYaw() { return entity.getPanelYaw(); }
|
||||
|
||||
public int getPanelMode() { return entity.getMode().ordinal(); }
|
||||
|
||||
public boolean isLaunched() { return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED; }
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import noppes.npcs.api.entity.IEnergyProjectile;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.api.entity.IEntityLivingBase;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
public class ScriptEnergyProjectile<T extends EntityAbilityProjectile> extends ScriptEntity<T> implements IEnergyProjectile {
|
||||
protected T entity;
|
||||
public class ScriptEnergyProjectile<T extends EntityEnergyProjectile> extends ScriptEnergyAbility<T> implements IEnergyProjectile {
|
||||
|
||||
public ScriptEnergyProjectile(T entity) {
|
||||
super(entity);
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
// ==================== TYPE ====================
|
||||
@@ -36,14 +33,6 @@ public class ScriptEnergyProjectile<T extends EntityAbilityProjectile> extends S
|
||||
|
||||
// ==================== OWNER & TARGET ====================
|
||||
|
||||
public int getOwnerEntityId() {
|
||||
return entity.getOwnerEntityId();
|
||||
}
|
||||
|
||||
public IEntity getOwner() {
|
||||
return entity.getOwner();
|
||||
}
|
||||
|
||||
public void setOwner(IEntity owner) {
|
||||
if (owner != null) {
|
||||
entity.setOwnerEntityId(owner.getMCEntity().getEntityId());
|
||||
@@ -76,22 +65,7 @@ public class ScriptEnergyProjectile<T extends EntityAbilityProjectile> extends S
|
||||
entity.setProjectileSize(size);
|
||||
}
|
||||
|
||||
// ==================== DISPLAY ====================
|
||||
|
||||
public int getInnerColor() { return entity.getInnerColor(); }
|
||||
public void setInnerColor(int color) { entity.setInnerColor(color); }
|
||||
|
||||
public int getOuterColor() { return entity.getOuterColor(); }
|
||||
public void setOuterColor(int color) { entity.setOuterColor(color); }
|
||||
|
||||
public boolean isOuterColorEnabled() { return entity.isOuterColorEnabled(); }
|
||||
public void setOuterColorEnabled(boolean enabled) { entity.setOuterColorEnabled(enabled); }
|
||||
|
||||
public float getOuterColorWidth() { return entity.getOuterColorWidth(); }
|
||||
public void setOuterColorWidth(float width) { entity.setOuterColorWidth(width); }
|
||||
|
||||
public float getOuterColorAlpha() { return entity.getOuterColorAlpha(); }
|
||||
public void setOuterColorAlpha(float alpha) { entity.setOuterColorAlpha(alpha); }
|
||||
// ==================== DISPLAY (PROJECTILE-SPECIFIC) ====================
|
||||
|
||||
public float getRotationSpeed() { return entity.getRotationSpeed(); }
|
||||
public void setRotationSpeed(float speed) { entity.setRotationSpeed(speed); }
|
||||
@@ -103,20 +77,6 @@ public class ScriptEnergyProjectile<T extends EntityAbilityProjectile> extends S
|
||||
public float getInterpolatedRotationZ(float partialTicks) { return entity.getInterpolatedRotationZ(partialTicks); }
|
||||
public float getInterpolatedSize(float partialTicks) { return entity.getInterpolatedSize(partialTicks); }
|
||||
|
||||
// ==================== LIGHTNING ====================
|
||||
|
||||
public boolean hasLightningEffect() { return entity.hasLightningEffect(); }
|
||||
public void setLightningEffect(boolean enabled) { entity.setLightningEffect(enabled); }
|
||||
|
||||
public float getLightningDensity() { return entity.getLightningDensity(); }
|
||||
public void setLightningDensity(float density) { entity.setLightningDensity(density); }
|
||||
|
||||
public float getLightningRadius() { return entity.getLightningRadius(); }
|
||||
public void setLightningRadius(float radius) { entity.setLightningRadius(radius); }
|
||||
|
||||
public int getLightningFadeTime() { return entity.getLightningFadeTime(); }
|
||||
public void setLightningFadeTime(int ticks) { entity.setLightningFadeTime(ticks); }
|
||||
|
||||
// ==================== LIFESPAN ====================
|
||||
|
||||
public float getMaxDistance() { return entity.getMaxDistance(); }
|
||||
@@ -175,8 +135,6 @@ public class ScriptEnergyProjectile<T extends EntityAbilityProjectile> extends S
|
||||
// ==================== STATE ====================
|
||||
|
||||
public boolean hasHit() { return entity.getHasHit(); }
|
||||
public boolean isCharging() { return entity.isCharging(); }
|
||||
public float getChargeProgress() { return entity.getChargeProgress(); }
|
||||
|
||||
// ==================== FIRE ====================
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergySlicer;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import noppes.npcs.api.entity.IEnergySlicer;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
public class ScriptEnergySlicer<T extends EntityEnergySlicer> extends ScriptEnergyProjectile<T> implements IEnergySlicer {
|
||||
|
||||
public ScriptEnergySlicer(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return EntityType.ENERGY_SLICER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean typeOf(int type) {
|
||||
return type == EntityType.ENERGY_SLICER || super.typeOf(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnergyType() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// ==================== SLICER-SPECIFIC ====================
|
||||
|
||||
public float getSliceWidth() { return entity.getSliceWidth(); }
|
||||
public void setSliceWidth(float width) { entity.setSliceWidth(width); }
|
||||
|
||||
public float getSliceThickness() { return entity.getSliceThickness(); }
|
||||
public void setSliceThickness(float thickness) { entity.setSliceThickness(thickness); }
|
||||
|
||||
public boolean isPiercing() { return entity.isPiercing(); }
|
||||
public void setPiercing(boolean piercing) { entity.setPiercing(piercing); }
|
||||
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
mcTarget = (EntityLivingBase) target.getMCEntity();
|
||||
}
|
||||
entity.startMoving(mcTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
double dz = z - entity.posZ;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
entity.motionX = (dx / len) * entity.getSpeed();
|
||||
entity.motionY = (dy / len) * entity.getSpeed();
|
||||
entity.motionZ = (dz / len) * entity.getSpeed();
|
||||
}
|
||||
entity.startMoving(null);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
@@ -532,7 +532,7 @@ public class ScriptEntity<T extends Entity> implements IEntity {
|
||||
else if (type == EntityType.NPC)
|
||||
cls = EntityNPCInterface.class;
|
||||
else if (type == EntityType.ENERGY_PROJECTILE)
|
||||
cls = EntityAbilityProjectile.class;
|
||||
cls = EntityEnergyProjectile.class;
|
||||
else if (type == EntityType.ENERGY_ORB)
|
||||
cls = EntityAbilityOrb.class;
|
||||
else if (type == EntityType.ENERGY_BEAM)
|
||||
|
||||
@@ -29,7 +29,7 @@ import noppes.npcs.constants.EnumMovingType;
|
||||
import noppes.npcs.constants.EnumNavType;
|
||||
import noppes.npcs.constants.EnumRoleType;
|
||||
import noppes.npcs.constants.EnumStandingType;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import noppes.npcs.controllers.FactionController;
|
||||
import noppes.npcs.controllers.data.DialogOption;
|
||||
import noppes.npcs.controllers.data.Line;
|
||||
@@ -1467,7 +1467,7 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
|
||||
}
|
||||
|
||||
public IEnergyProjectile[] getActiveEnergyProjectiles() {
|
||||
java.util.List<EntityAbilityProjectile> entities = EntityAbilityProjectile.getActiveProjectiles(npc.getEntityId());
|
||||
java.util.List<EntityEnergyProjectile> entities = EntityEnergyProjectile.getActiveProjectiles(npc.getEntityId());
|
||||
IEnergyProjectile[] result = new IEnergyProjectile[entities.size()];
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
result[i] = (IEnergyProjectile) NpcAPI.Instance().getIEntity(entities.get(i));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.AttributeController;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.packets.data.AchievementPacket;
|
||||
import kamkeel.npcs.network.packets.data.ChatAlertPacket;
|
||||
@@ -937,7 +937,7 @@ public class ScriptPlayer<T extends EntityPlayerMP> extends ScriptLivingBase<T>
|
||||
}
|
||||
|
||||
public IEnergyProjectile[] getActiveEnergyProjectiles() {
|
||||
List<EntityAbilityProjectile> entities = EntityAbilityProjectile.getActiveProjectiles(player.getEntityId());
|
||||
List<EntityEnergyProjectile> entities = EntityEnergyProjectile.getActiveProjectiles(player.getEntityId());
|
||||
IEnergyProjectile[] result = new IEnergyProjectile[entities.size()];
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
result[i] = (IEnergyProjectile) NpcAPI.Instance().getIEntity(entities.get(i));
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package noppes.npcs.scripted.event;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.Cancelable;
|
||||
import noppes.npcs.api.entity.IEnergyBarrier;
|
||||
import noppes.npcs.api.entity.IEnergyProjectile;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.api.event.IEnergyBarrierEvent;
|
||||
import noppes.npcs.constants.EnumScriptType;
|
||||
|
||||
public class EnergyBarrierEvent extends CustomNPCsEvent implements IEnergyBarrierEvent {
|
||||
public final IEnergyBarrier barrier;
|
||||
public final IEntity owner;
|
||||
|
||||
public EnergyBarrierEvent(IEnergyBarrier barrier) {
|
||||
this.barrier = barrier;
|
||||
this.owner = barrier.getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyBarrier getBarrier() {
|
||||
return barrier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntity getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return "energyBarrierEvent";
|
||||
}
|
||||
|
||||
public static class SpawnedEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.SpawnedEvent {
|
||||
public SpawnedEvent(IEnergyBarrier barrier) {
|
||||
super(barrier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ENERGY_BARRIER_SPAWNED.function;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.UpdateEvent {
|
||||
public UpdateEvent(IEnergyBarrier barrier) {
|
||||
super(barrier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ENERGY_BARRIER_TICK.function;
|
||||
}
|
||||
}
|
||||
|
||||
@Cancelable
|
||||
public static class HitEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.HitEvent {
|
||||
private final IEnergyProjectile projectile;
|
||||
private float damage;
|
||||
|
||||
public HitEvent(IEnergyBarrier barrier, IEnergyProjectile projectile, float damage) {
|
||||
super(barrier);
|
||||
this.projectile = projectile;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyProjectile getProjectile() {
|
||||
return projectile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamage(float damage) {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ENERGY_BARRIER_HIT.function;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DestroyedEvent extends EnergyBarrierEvent implements IEnergyBarrierEvent.DestroyedEvent {
|
||||
public DestroyedEvent(IEnergyBarrier barrier) {
|
||||
super(barrier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ENERGY_BARRIER_DESTROYED.function;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
|
||||
if (!ability.getAllowedBy().allowsPlayer()) {
|
||||
return; // Ability is NPC-only
|
||||
}
|
||||
// Use canonical key (registry key for built-in, UUID for custom)
|
||||
// Use canonical key (registry key for built-in, name for custom)
|
||||
String canonicalKey = ability.getId() != null ? ability.getId() : key;
|
||||
data.unlockAbility(canonicalKey);
|
||||
}
|
||||
|
||||
@@ -1694,6 +1694,7 @@ ability.activeSound=Active Sound
|
||||
ability.counterSound=Counter Sound
|
||||
ability.windUpAnimation=Wind Up Animation
|
||||
ability.activeAnimation=Active Animation
|
||||
ability.dazedAnimation=Dazed Animation
|
||||
ability.counterAnimation=Counter Animation
|
||||
ability.showTelegraph=Show Telegraph
|
||||
ability.windUpColor=Wind Up Color
|
||||
@@ -1804,6 +1805,10 @@ ability.innerRadius=Inner Radius
|
||||
# Projectile
|
||||
ability.projectileType=Projectile Type
|
||||
|
||||
# Heavy Hit
|
||||
ability.hitDelayTicks=Hit Delay
|
||||
ability.activeDisplayTicks=End Delay
|
||||
|
||||
# Orb Settings
|
||||
ability.homing=Homing
|
||||
ability.explosive=Explosive
|
||||
@@ -1987,6 +1992,46 @@ ability.hover.canCounter=Enable counterattack when blocking damage
|
||||
ability.hover.counterType=Flat = fixed damage, Percent = % of blocked damage
|
||||
ability.hover.affectsCaster=Hazard can damage the NPC that created it
|
||||
ability.hover.sweepMode=Swipe = single arc attack, Spin = continuous rotation
|
||||
ability.hover.launching=When enabled, wall launches forward dealing damage and knockback
|
||||
|
||||
# Energy Barrier Abilities
|
||||
ability.cnpc.dome=Dome
|
||||
ability.cnpc.wall=Wall
|
||||
ability.cnpc.shield=Shield
|
||||
ability.cnpc.slicer=Slicer
|
||||
|
||||
ability.section.barrier=Barrier
|
||||
ability.section.knockback=Knockback
|
||||
ability.section.panel=Panel
|
||||
ability.section.launch=Launch
|
||||
ability.section.slicer=Slicer
|
||||
|
||||
ability.domeRadius=Dome Radius
|
||||
ability.useHealth=Use Health
|
||||
ability.maxHealth=Max Health
|
||||
ability.useDuration=Use Duration
|
||||
ability.defaultMultiplier=Default Multiplier
|
||||
ability.innerAlpha=Inner Alpha
|
||||
ability.heightOffset=Height Offset
|
||||
|
||||
ability.knockbackEnabled=Knockback
|
||||
ability.knockbackStrength=Knockback Strength
|
||||
ability.knockbackTarget=Target
|
||||
|
||||
ability.section.melee=Melee
|
||||
ability.meleeEnabled=Melee Attackable
|
||||
ability.meleeDamageMultiplier=Melee Multiplier
|
||||
|
||||
ability.followCaster=Follow Caster
|
||||
ability.hover.followCaster=Dome stays centered on the caster as they move
|
||||
|
||||
ability.variant.small=Small
|
||||
ability.variant.large=Large
|
||||
ability.variant.static=Static
|
||||
ability.variant.launched=Launched
|
||||
|
||||
gui.both=Both
|
||||
gui.players=Players
|
||||
|
||||
# Conditions
|
||||
condition.edit=Edit Condition
|
||||
|
||||
Reference in new issue
Block a user