Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
Kam committed 2026-02-18 22:41:27 -05:00
commit df6beb4f15
207 files changed
+10330 -2705

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
id "dts.typescript-generator"
}
version = "1.11-beta3"
version = "1.11-beta4"
group= "kamkeel.customnpc-plus"
archivesBaseName = "CustomNPC-Plus"
+1 -1
Submodule src/api updated: 22bce4b2ac...2c2f0c0d8f.
@@ -14,12 +14,12 @@ import java.util.List;
/**
* Renders the AbilityBeam entity with a head, tail orb, and curving trail.
* The trail shows the path the head has traveled using laser-style rectangular segments.
*
* <p>
* IMPORTANT: Trail points are stored RELATIVE to the origin position.
* We render by translating to the origin first, then drawing all points relative to that.
*
* <p>
* Trail segments now share vertices at connection points to prevent gaps during curves.
*
* <p>
* Design inspired by LouisXIV's energy rendering system.
*/
@SideOnly(Side.CLIENT)
@@ -182,7 +182,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
* Uses averaged perpendicular vectors at connection points to prevent gaps.
*/
private void renderSmoothTrail(List<Vec3> trail, float width, int innerColor, int outerColor,
boolean outerColorEnabled, float outerColorWidth) {
boolean outerColorEnabled, float outerColorWidth) {
if (trail.size() < 2) return;
int trailSize = trail.size();
@@ -213,7 +213,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
* Older trail points fade out based on their age.
*/
private void renderFadingTrail(EntityAbilityBeam beam, List<Vec3> trail, float width, int innerColor, int outerColor,
boolean outerColorEnabled, float outerColorWidth) {
boolean outerColorEnabled, float outerColorWidth) {
if (trail.size() < 2) return;
List<Integer> ages = beam.getTrailPointAges();
@@ -242,7 +242,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
* Render trail tube with age-based fading.
*/
private void renderFadingTrailTube(List<Vec3> trail, List<double[]> perpFrames, List<Integer> ages,
int fadeTime, float width, int color, float baseAlpha) {
int fadeTime, float width, int color, float baseAlpha) {
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
float halfWidth = width * 0.5f;
@@ -392,13 +392,17 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
dy /= len;
dz /= len;
} else {
dx = 0; dy = 0; dz = 1;
dx = 0;
dy = 0;
dz = 1;
}
// Calculate perpendicular vectors using consistent up reference
double upX = 0, upY = 1, upZ = 0;
if (Math.abs(dy) > 0.9) {
upX = 1; upY = 0; upZ = 0;
upX = 1;
upY = 0;
upZ = 0;
}
// Cross product for horizontal perpendicular
@@ -417,8 +421,12 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
double vertY = dz * horzX - dx * horzZ;
double vertZ = dx * horzY - dy * horzX;
frame[0] = horzX; frame[1] = horzY; frame[2] = horzZ;
frame[3] = vertX; frame[4] = vertY; frame[5] = vertZ;
frame[0] = horzX;
frame[1] = horzY;
frame[2] = horzZ;
frame[3] = vertX;
frame[4] = vertY;
frame[5] = vertZ;
frames.add(frame);
}
@@ -429,7 +437,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
* Render the trail as a continuous tube using pre-computed perpendicular frames.
*/
private void renderTrailTube(List<Vec3> trail, List<double[]> perpFrames,
float width, int color, float baseAlpha, boolean fadeFromStart) {
float width, int color, float baseAlpha, boolean fadeFromStart) {
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
float halfWidth = width * 0.5f;
@@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL11;
/**
* Renders the AbilityDisc entity as a flat spinning square.
* Rotates only on Y axis (flat spin like a saw blade).
*
* <p>
* Design inspired by LouisXIV's energy rendering system.
*/
@SideOnly(Side.CLIENT)
@@ -10,7 +10,7 @@ import org.lwjgl.opengl.GL11;
/**
* Renders the AbilityLaser entity as a rectangular beam with clear inner/outer colors.
*
* <p>
* Design inspired by LouisXIV's energy rendering system.
*/
@SideOnly(Side.CLIENT)
@@ -69,7 +69,9 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
// Calculate perpendicular vectors (one horizontal, one vertical relative to beam)
double upX = 0, upY = 1, upZ = 0;
if (Math.abs(dy) > 0.9) {
upX = 1; upY = 0; upZ = 0;
upX = 1;
upY = 0;
upZ = 0;
}
// Cross product for horizontal perpendicular
@@ -98,29 +100,29 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
float outerWidth = innerWidth + laser.getOuterColorWidth() * width;
GL11.glDepthMask(false);
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
outerWidth, laser.getOuterColor(), alpha * 0.4f);
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
outerWidth, laser.getOuterColor(), alpha * 0.4f);
// Render middle layer (halfway between inner and outer)
float midWidth = innerWidth + (outerWidth - innerWidth) * 0.5f;
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
midWidth, laser.getOuterColor(), alpha * 0.7f);
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
midWidth, laser.getOuterColor(), alpha * 0.7f);
GL11.glDepthMask(true);
}
// Render inner core (solid)
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
innerWidth, laser.getInnerColor(), alpha);
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
innerWidth, laser.getInnerColor(), alpha);
// Render lightning along the laser beam
if (laser.hasLightningEffect() && laser.getCurrentLength() > 0.1f) {
renderLaserLightning(laser, renderStartX, renderStartY, renderStartZ,
renderEndX, renderEndY, renderEndZ, width);
renderEndX, renderEndY, renderEndZ, width);
}
restoreRenderState();
@@ -179,8 +181,8 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
* Render lightning effects along the laser beam at multiple points.
*/
private void renderLaserLightning(EntityAbilityLaser laser,
double startX, double startY, double startZ,
double endX, double endY, double endZ, float width) {
double startX, double startY, double startZ,
double endX, double endY, double endZ, float width) {
AttachedLightningRenderer.LightningState state = getLightningState(laser);
float density = laser.getLightningDensity();
@@ -229,10 +231,10 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
* Render a beam as a 3D rectangular prism (box along a line).
*/
private void renderBeamRectangle(double x1, double y1, double z1,
double x2, double y2, double z2,
double horzX, double horzY, double horzZ,
double vertX, double vertY, double vertZ,
float width, int color, float alpha) {
double x2, double y2, double z2,
double horzX, double horzY, double horzZ,
double vertX, double vertY, double vertZ,
float width, int color, float alpha) {
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
@@ -104,8 +104,8 @@ public class RenderAbilitySweeper extends Render {
* Uses the same approach as RenderAbilityBeam.renderBeamRectangle.
*/
private void renderBeamSegment(double x1, double y1, double z1,
double x2, double y2, double z2,
float width, float r, float g, float b, float a) {
double x2, double y2, double z2,
float width, float r, float g, float b, float a) {
// Calculate direction
double dx = x2 - x1;
double dy = y2 - y1;
@@ -121,7 +121,9 @@ public class RenderAbilitySweeper extends Render {
// Calculate perpendicular vectors
double upX = 0, upY = 1, upZ = 0;
if (Math.abs(dy) > 0.9) {
upX = 1; upY = 0; upZ = 0;
upX = 1;
upY = 0;
upZ = 0;
}
// Cross product for horizontal perpendicular
@@ -8,13 +8,13 @@ import kamkeel.npcs.entity.EntityAbilityZone.AccentStyle;
import kamkeel.npcs.entity.EntityAbilityZone.ParticleMotion;
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
import kamkeel.npcs.entity.EntityAbilityZone.ZoneType;
import noppes.npcs.client.fx.CustomFX;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import noppes.npcs.client.fx.CustomFX;
import noppes.npcs.client.fx.ZoneParticleFX;
import org.lwjgl.opengl.GL11;
@@ -373,11 +373,21 @@ public class RenderAbilityZone extends Render {
}
}
private float ringR(Ctx c) { return c.outerEnabled ? c.outerR : c.ir; }
private float ringG(Ctx c) { return c.outerEnabled ? c.outerG : c.ig; }
private float ringB(Ctx c) { return c.outerEnabled ? c.outerB : c.ib; }
private float ringR(Ctx c) {
return c.outerEnabled ? c.outerR : c.ir;
}
private static float sin(double v) { return (float) Math.sin(v); }
private float ringG(Ctx c) {
return c.outerEnabled ? c.outerG : c.ig;
}
private float ringB(Ctx c) {
return c.outerEnabled ? c.outerB : c.ib;
}
private static float sin(double v) {
return (float) Math.sin(v);
}
private void renderSwayingAccents(Ctx c, float r, float g, float b, float a, float height) {
if (height <= 0.1f) return;
@@ -413,10 +423,22 @@ public class RenderAbilityZone extends Render {
float frac = (j + 0.5f) / perSide;
double lx, lz;
switch (side) {
case 0: lx = -c.radius + frac * 2 * c.radius; lz = -c.radius; break;
case 1: lx = c.radius; lz = -c.radius + frac * 2 * c.radius; break;
case 2: lx = c.radius - frac * 2 * c.radius; lz = c.radius; break;
default: lx = -c.radius; lz = c.radius - frac * 2 * c.radius; break;
case 0:
lx = -c.radius + frac * 2 * c.radius;
lz = -c.radius;
break;
case 1:
lx = c.radius;
lz = -c.radius + frac * 2 * c.radius;
break;
case 2:
lx = c.radius - frac * 2 * c.radius;
lz = c.radius;
break;
default:
lx = -c.radius;
lz = c.radius - frac * 2 * c.radius;
break;
}
float swayX = 0.15f * sin(c.time * 0.08f + idx * 1.7f);
float swayZ = 0.15f * sin(c.time * 0.06f + idx * 2.3f);
@@ -467,10 +489,22 @@ public class RenderAbilityZone extends Render {
float frac = (j + 0.5f) / perSide;
double lx, lz;
switch (side) {
case 0: lx = -c.radius + frac * 2 * c.radius; lz = -c.radius; break;
case 1: lx = c.radius; lz = -c.radius + frac * 2 * c.radius; break;
case 2: lx = c.radius - frac * 2 * c.radius; lz = c.radius; break;
default: lx = -c.radius; lz = c.radius - frac * 2 * c.radius; break;
case 0:
lx = -c.radius + frac * 2 * c.radius;
lz = -c.radius;
break;
case 1:
lx = c.radius;
lz = -c.radius + frac * 2 * c.radius;
break;
case 2:
lx = c.radius - frac * 2 * c.radius;
lz = c.radius;
break;
default:
lx = -c.radius;
lz = c.radius - frac * 2 * c.radius;
break;
}
float flicker = 0.3f + 0.7f * Math.abs(sin(c.time * 0.7f + idx * 3.14f));
float lineAlpha = baseAlpha * flicker;
@@ -506,10 +540,14 @@ public class RenderAbilityZone extends Render {
int side = RANDOM.nextInt(4);
double frac = RANDOM.nextDouble() * 2 - 1;
switch (side) {
case 0: return new double[]{frac * c.radius, -c.radius};
case 1: return new double[]{frac * c.radius, c.radius};
case 2: return new double[]{-c.radius, frac * c.radius};
default: return new double[]{c.radius, frac * c.radius};
case 0:
return new double[]{frac * c.radius, -c.radius};
case 1:
return new double[]{frac * c.radius, c.radius};
case 2:
return new double[]{-c.radius, frac * c.radius};
default:
return new double[]{c.radius, frac * c.radius};
}
} else {
double angle = RANDOM.nextDouble() * Math.PI * 2;
@@ -519,9 +557,9 @@ public class RenderAbilityZone extends Render {
}
private void spawnParticle(EntityAbilityZone zone, double offsetX, double offsetY, double offsetZ,
double mx, double my, double mz,
float r, float g, float b, float alpha,
float scale, int maxAge, float gravity, boolean glow) {
double mx, double my, double mz,
float r, float g, float b, float alpha,
float scale, int maxAge, float gravity, boolean glow) {
ZoneParticleFX particle = new ZoneParticleFX(
zone.worldObj,
zone.posX + offsetX, zone.posY + offsetY, zone.posZ + offsetZ,
@@ -587,7 +625,7 @@ public class RenderAbilityZone extends Render {
}
private void renderRingLine(ZoneShape shape, float radius,
float r, float g, float b, float a, float yOffset) {
float r, float g, float b, float a, float yOffset) {
switch (shape) {
case CIRCLE:
renderCircleRingLine(radius, r, g, b, a, yOffset);
@@ -621,8 +659,8 @@ public class RenderAbilityZone extends Render {
}
private void renderRotatingBorder(ZoneShape shape, float radius,
float r, float g, float b, float a,
float rotationDeg, float yOffset) {
float r, float g, float b, float a,
float rotationDeg, float yOffset) {
int dashCount = 16;
if (shape == ZoneShape.SQUARE) {
@@ -688,8 +726,8 @@ public class RenderAbilityZone extends Render {
}
private void renderVerticalAccents(ZoneShape shape, float radius,
float r, float g, float b, float a,
float height, float rotationDeg) {
float r, float g, float b, float a,
float height, float rotationDeg) {
if (height <= 0.1f) return;
Tessellator tess = Tessellator.instance;
@@ -701,10 +739,22 @@ public class RenderAbilityZone extends Render {
float frac = (j + 0.5f) / perSide;
double lx, lz;
switch (side) {
case 0: lx = -radius + frac * 2 * radius; lz = -radius; break;
case 1: lx = radius; lz = -radius + frac * 2 * radius; break;
case 2: lx = radius - frac * 2 * radius; lz = radius; break;
default: lx = -radius; lz = radius - frac * 2 * radius; break;
case 0:
lx = -radius + frac * 2 * radius;
lz = -radius;
break;
case 1:
lx = radius;
lz = -radius + frac * 2 * radius;
break;
case 2:
lx = radius - frac * 2 * radius;
lz = radius;
break;
default:
lx = -radius;
lz = radius - frac * 2 * radius;
break;
}
tess.startDrawing(GL11.GL_LINES);
tess.setColorRGBA_F(r, g, b, a);
@@ -15,7 +15,7 @@ import org.lwjgl.opengl.GL12;
/**
* Base renderer for ability projectiles.
* Provides common GL state setup and rendering utilities.
*
* <p>
* Design inspired by LouisXIV's energy rendering system.
*/
@SideOnly(Side.CLIENT)
@@ -212,8 +212,8 @@ public abstract class RenderEnergyAbility extends Render {
* Render a line segment as a quad billboard facing the camera.
*/
protected void renderLineSegment(double x1, double y1, double z1,
double x2, double y2, double z2,
float width, int color, float alpha) {
double x2, double y2, double z2,
float width, int color, float alpha) {
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
@@ -270,9 +270,10 @@ public abstract class RenderEnergyAbility extends Render {
/**
* Render attached lightning arcs around an energy entity.
* @param entity the 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
* @param baseSize the entity's current render size/scale
*/
protected void renderAttachedLightning(EntityEnergyAbility entity, float innerScale, float baseSize) {
AttachedLightningRenderer.LightningState state = getLightningState(entity);
@@ -95,7 +95,11 @@ public class RenderEnergyDome extends RenderEnergyBarrier {
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; }
if (nLen > 0) {
nx /= nLen;
ny /= nLen;
nz /= nLen;
}
tess.setNormal(nx, ny, nz);
tess.addVertex(x1, y1, z1);
@@ -299,8 +299,8 @@ public class TelegraphRenderer {
* @param partialTicks Partial ticks for interpolation
*/
public void renderTelegraphInGUI(TelegraphInstance instance,
double centerX, double centerY, double centerZ,
float scale, float partialTicks) {
double centerX, double centerY, double centerZ,
float scale, float partialTicks) {
if (instance == null) return;
Telegraph telegraph = instance.getTelegraph();
@@ -165,8 +165,8 @@ public class AttachedLightningRenderer {
* Generate a jagged lightning path between two points.
*/
private static List<double[]> generateLightningPath(double x1, double y1, double z1,
double x2, double y2, double z2,
int segments, float displacement) {
double x2, double y2, double z2,
int segments, float displacement) {
List<double[]> points = new ArrayList<>();
points.add(new double[]{x1, y1, z1});
@@ -257,8 +257,8 @@ public class AttachedLightningRenderer {
* Create a lightning arc between two arbitrary points.
*/
public static LightningArc createArcBetween(double x1, double y1, double z1,
double x2, double y2, double z2,
float displacement, int outerColor, int innerColor, int maxAge) {
double x2, double y2, double z2,
float displacement, int outerColor, int innerColor, int maxAge) {
int segments = 4 + rand.nextInt(3);
List<double[]> points = generateLightningPath(x1, y1, z1, x2, y2, z2, segments, displacement);
return new LightningArc(points, maxAge, outerColor, innerColor);
@@ -243,7 +243,7 @@ public class LightningBolt {
public Segment(Vec3d start, Vec3d end) {
this(new BoltPoint(start, new Vec3d(0, 0, 0)),
new BoltPoint(end, new Vec3d(0, 0, 0)), 1, 0, 0);
new BoltPoint(end, new Vec3d(0, 0, 0)), 1, 0, 0);
}
public void calcDiff() {
@@ -5,7 +5,6 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.client.renderer.lightning.LightningBolt.Segment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.entity.Entity;
@@ -19,7 +18,7 @@ import java.awt.Color;
/**
* Handles rendering of lightning bolts in the world.
* Adapted from Botania's LightningHandler by Vazkii/ChickenBones.
*
* <p>
* Register this handler with: MinecraftForge.EVENT_BUS.register(new LightningHandler());
*/
@SideOnly(Side.CLIENT)
@@ -168,13 +167,13 @@ public class LightningHandler {
/**
* Spawn a lightning bolt between two points.
*
* @param world The world
* @param start Start position
* @param end End position
* @param world The world
* @param start Start position
* @param end End position
* @param ticksPerMeter Speed of bolt expansion
* @param seed Random seed for consistent appearance
* @param colorOuter Outer glow color (ARGB)
* @param colorInner Inner core color (ARGB)
* @param seed Random seed for consistent appearance
* @param colorOuter Outer glow color (ARGB)
* @param colorInner Inner core color (ARGB)
*/
public static void spawnLightningBolt(World world, Vec3d start, Vec3d end, float ticksPerMeter, long seed, int colorOuter, int colorInner) {
LightningBolt bolt = new LightningBolt(world, start, end, ticksPerMeter, seed, colorOuter, colorInner);
@@ -1,12 +1,14 @@
package kamkeel.npcs.command;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.controllers.data.ability.UserType;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import noppes.npcs.NoppesUtilServer;
import noppes.npcs.controllers.PlayerDataController;
import noppes.npcs.controllers.data.PlayerAbilityData;
import noppes.npcs.controllers.data.PlayerData;
import java.util.ArrayList;
@@ -77,7 +79,7 @@ public class AbilityCommand extends CommandKamkeelBase {
)
public void reload(ICommandSender sender, String[] args) {
AbilityController.Instance.load();
sendResult(sender, "Abilities reloaded from disk.");
sendResult(sender, "Abilities and chained abilities reloaded from disk.");
}
@SubCommand(
@@ -110,14 +112,15 @@ public class AbilityCommand extends CommandKamkeelBase {
return;
}
String key = args[0];
String key = joinArgs(args, 0);
Ability ability = AbilityController.Instance.resolveAbility(key);
if (ability == null) {
sendError(sender, "No ability found for: " + key);
return;
}
sendResult(sender, "Ability: \u00A7b" + ability.getName());
sendResult(sender, "Ability: \u00A7b" + ability.getDisplayName());
sendResult(sender, " Name: \u00A77" + ability.getName());
sendResult(sender, " Type: \u00A7d" + ability.getTypeId());
sendResult(sender, " Cooldown: \u00A7e" + ability.getCooldownTicks() + " ticks");
sendResult(sender, " Wind Up: \u00A7e" + ability.getWindUpTicks() + " ticks");
@@ -162,46 +165,42 @@ public class AbilityCommand extends CommandKamkeelBase {
}
@SubCommand(
desc = "Unlock an ability for a player",
desc = "Give an ability to a player",
usage = "<player> <ability>"
)
public void unlock(ICommandSender sender, String[] args) {
public void give(ICommandSender sender, String[] args) {
if (args.length < 2) {
sendError(sender, "Usage: /kam ability unlock <player> <ability>");
sendError(sender, "Usage: /kam ability give <player> <ability>");
return;
}
String playerName = args[0];
String abilityKey = joinArgs(args, 1);
// Find player
EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName);
if (player == null) {
sendError(sender, "Player not found: " + playerName);
return;
}
// Resolve ability
Ability ability = AbilityController.Instance.resolveAbility(abilityKey);
if (ability == null) {
sendError(sender, "Ability not found: " + abilityKey);
return;
}
// Use the ability's canonical ID for storage (registry key for built-in, name for custom)
// Use the ability's canonical ID for storage (registry key for built-in, UUID for custom)
String canonicalKey = ability.getId();
if (canonicalKey == null || canonicalKey.isEmpty()) {
canonicalKey = abilityKey; // Fallback to user-provided key
canonicalKey = abilityKey;
}
String displayName = ability.getName() != null ? ability.getName() : canonicalKey;
String displayName = ability.getDisplayName();
// Check if ability allows players
if (!ability.getAllowedBy().allowsPlayer()) {
sendError(sender, "Ability '\u00A7b" + displayName + "\u00A7c' is NPC-only and cannot be given to players.");
return;
}
// Get player data and unlock
PlayerData data = PlayerDataController.Instance.getPlayerData(player);
if (data.abilityData.hasUnlockedAbility(canonicalKey)) {
sendError(sender, "Player already has ability: " + displayName);
@@ -209,39 +208,36 @@ public class AbilityCommand extends CommandKamkeelBase {
}
data.abilityData.unlockAbility(canonicalKey);
sendResult(sender, "Unlocked ability '\u00A7b" + displayName + "\u00A77' for player \u00A7a" + playerName);
sendResult(sender, "Gave ability '\u00A7b" + displayName + "\u00A77' to player \u00A7a" + playerName);
}
@SubCommand(
desc = "Lock (revoke) an ability from a player",
desc = "Remove an ability from a player",
usage = "<player> <ability>"
)
public void lock(ICommandSender sender, String[] args) {
public void remove(ICommandSender sender, String[] args) {
if (args.length < 2) {
sendError(sender, "Usage: /kam ability lock <player> <ability>");
sendError(sender, "Usage: /kam ability remove <player> <ability>");
return;
}
String playerName = args[0];
String abilityKey = joinArgs(args, 1);
// Find player
EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName);
if (player == null) {
sendError(sender, "Player not found: " + playerName);
return;
}
// Resolve ability to get canonical key
Ability ability = AbilityController.Instance.resolveAbility(abilityKey);
String canonicalKey = abilityKey;
String displayName = abilityKey;
if (ability != null) {
canonicalKey = ability.getId() != null ? ability.getId() : abilityKey;
displayName = ability.getName() != null ? ability.getName() : canonicalKey;
displayName = ability.getDisplayName();
}
// Get player data and lock
PlayerData data = PlayerDataController.Instance.getPlayerData(player);
if (!data.abilityData.hasUnlockedAbility(canonicalKey)) {
sendError(sender, "Player doesn't have ability: " + displayName);
@@ -249,7 +245,98 @@ public class AbilityCommand extends CommandKamkeelBase {
}
data.abilityData.lockAbility(canonicalKey);
sendResult(sender, "Locked ability '\u00A7b" + displayName + "\u00A77' from player \u00A7a" + playerName);
sendResult(sender, "Removed ability '\u00A7b" + displayName + "\u00A77' from player \u00A7a" + playerName);
}
@SubCommand(
desc = "Give a chained ability to a player",
usage = "<player> <chain>"
)
public void giveChain(ICommandSender sender, String[] args) {
if (args.length < 2) {
sendError(sender, "Usage: /kam ability giveChain <player> <chain>");
return;
}
String playerName = args[0];
String chainKey = joinArgs(args, 1);
EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName);
if (player == null) {
sendError(sender, "Player not found: " + playerName);
return;
}
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainKey);
if (chain == null) {
sendError(sender, "Chained ability not found: " + chainKey);
return;
}
if (!chain.getAllowedBy().allowsPlayer()) {
sendError(sender, "Chained ability '\u00A7b" + chain.getDisplayName() + "\u00A7c' is NPC-only and cannot be given to players.");
return;
}
String chainId = chain.getId();
if (chainId == null || chainId.isEmpty()) {
sendError(sender, "Chained ability has no ID: " + chain.getDisplayName());
return;
}
// Store by UUID: chain:<uuid>
String storageKey = PlayerAbilityData.CHAIN_PREFIX + chainId;
PlayerData data = PlayerDataController.Instance.getPlayerData(player);
if (data.abilityData.hasUnlockedAbility(storageKey)) {
sendError(sender, "Player already has chained ability: " + chain.getDisplayName());
return;
}
data.abilityData.unlockAbility(storageKey);
sendResult(sender, "Gave chained ability '\u00A7b" + chain.getDisplayName() + "\u00A77' to player \u00A7a" + playerName);
}
@SubCommand(
desc = "Remove a chained ability from a player",
usage = "<player> <chain>"
)
public void removeChain(ICommandSender sender, String[] args) {
if (args.length < 2) {
sendError(sender, "Usage: /kam ability removeChain <player> <chain>");
return;
}
String playerName = args[0];
String chainKey = joinArgs(args, 1);
EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName);
if (player == null) {
sendError(sender, "Player not found: " + playerName);
return;
}
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainKey);
if (chain == null) {
sendError(sender, "Chained ability not found: " + chainKey);
return;
}
String chainId = chain.getId();
if (chainId == null || chainId.isEmpty()) {
sendError(sender, "Chained ability has no ID: " + chain.getDisplayName());
return;
}
// Storage uses UUID: chain:<uuid>
String storageKey = PlayerAbilityData.CHAIN_PREFIX + chainId;
PlayerData data = PlayerDataController.Instance.getPlayerData(player);
if (!data.abilityData.hasUnlockedAbility(storageKey)) {
sendError(sender, "Player doesn't have chained ability: " + chain.getDisplayName());
return;
}
data.abilityData.lockAbility(storageKey);
sendResult(sender, "Removed chained ability '\u00A7b" + chain.getDisplayName() + "\u00A77' from player \u00A7a" + playerName);
}
@SubCommand(
@@ -283,14 +370,23 @@ public class AbilityCommand extends CommandKamkeelBase {
int selected = data.abilityData.getSelectedIndex();
for (int i = 0; i < abilityKeys.length; i++) {
String key = abilityKeys[i];
Ability ability = AbilityController.Instance.resolveAbility(key);
String displayName = ability != null && ability.getName() != null ? ability.getName() : key;
String prefix = (i == selected) ? "\u00A7e> " : " ";
// Show display name and key if different
if (!displayName.equals(key)) {
sendResult(sender, prefix + "\u00A7b" + displayName + " \u00A78[" + key + "]");
if (key.startsWith(PlayerAbilityData.CHAIN_PREFIX)) {
// Chained ability
String chainName = key.substring(PlayerAbilityData.CHAIN_PREFIX.length());
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainName);
String displayName = chain != null ? chain.getDisplayName() : chainName;
sendResult(sender, prefix + "\u00A7d[Chain] \u00A7b" + displayName);
} else {
sendResult(sender, prefix + "\u00A7b" + displayName);
// Regular ability
Ability ability = AbilityController.Instance.resolveAbility(key);
String displayName = ability != null ? ability.getDisplayName() : key;
if (!displayName.equals(key)) {
sendResult(sender, prefix + "\u00A7b" + displayName + " \u00A78[" + key + "]");
} else {
sendResult(sender, prefix + "\u00A7b" + displayName);
}
}
}
}
@@ -306,12 +402,33 @@ public class AbilityCommand extends CommandKamkeelBase {
/**
* Get list of player-usable ability names (for tab completion).
* Uses registry keys for built-in abilities and display names for custom abilities.
* Used by CommandKamkeel for <ability> usage token.
* Resolves UUIDs to names so tab completion shows human-readable names.
* Used by CommandKamkeel for {@code <ability>} usage token.
*/
public static List<String> getPlayerAbilityNames() {
List<String> keys = new ArrayList<>(AbilityController.Instance.getPlayerAbilityKeys());
Collections.sort(keys);
return keys;
Set<String> keys = AbilityController.Instance.getPlayerAbilityKeys();
List<String> names = new ArrayList<>();
for (String key : keys) {
Ability a = AbilityController.Instance.resolveAbility(key);
names.add(a != null ? a.getName() : key);
}
Collections.sort(names);
return names;
}
/**
* Get list of player-usable chained ability names (for tab completion).
* Used by CommandKamkeel for {@code <chain>} usage token.
*/
public static List<String> getPlayerChainNames() {
List<String> names = new ArrayList<>();
for (String chainName : AbilityController.Instance.getChainedAbilityNamesSet()) {
ChainedAbility chain = AbilityController.Instance.getChainedAbility(chainName);
if (chain != null && chain.getAllowedBy().allowsPlayer()) {
names.add(chainName);
}
}
Collections.sort(names);
return names;
}
}
@@ -20,6 +20,7 @@ import static kamkeel.npcs.util.ColorUtil.sendMessage;
import static kamkeel.npcs.util.ColorUtil.sendResult;
public class AuctionCommand extends CommandKamkeelBase {
private static final int COMMAND_PAGE_SIZE = 10;
public AuctionCommand() {
super();
@@ -62,9 +63,10 @@ public class AuctionCommand extends CommandKamkeelBase {
}
}
List<AuctionListing> listings = controller.getActiveListings(null, page - 1, 10);
page = Math.max(1, page);
List<AuctionListing> listings = controller.getActiveListings(null, page - 1, COMMAND_PAGE_SIZE);
int total = controller.getTotalActiveListings(null);
int totalPages = Math.max(1, (int) Math.ceil((double) total / 10));
int totalPages = Math.max(1, (int) Math.ceil((double) total / COMMAND_PAGE_SIZE));
if (listings.isEmpty()) {
sendMessage(sender, "\u00A77No active auctions found");
@@ -98,6 +100,10 @@ public class AuctionCommand extends CommandKamkeelBase {
return;
}
if (args.length < 1) {
throw new CommandException("Usage: /auction view <player>");
}
String playername = args[0];
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import static kamkeel.npcs.command.AbilityCommand.getPlayerAbilityNames;
import static kamkeel.npcs.command.AbilityCommand.getPlayerChainNames;
import static kamkeel.npcs.command.EffectCommand.getSortedEffectNames;
public class CommandKamkeel extends CommandBase {
@@ -139,6 +140,9 @@ public class CommandKamkeel extends CommandBase {
} else if (usage.equals("<ability>")) {
List<String> keys = getPlayerAbilityNames();
return CommandBase.getListOfStringsMatchingLastWord(args, keys.toArray(new String[keys.size()]));
} else if (usage.equals("<chain>")) {
List<String> keys = getPlayerChainNames();
return CommandBase.getListOfStringsMatchingLastWord(args, keys.toArray(new String[keys.size()]));
}
}
}
@@ -11,7 +11,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class CommandKamkeelBase extends CommandBase {
public Map<String, Method> subcommands = new HashMap<String, Method>();
@@ -1,7 +1,27 @@
package kamkeel.npcs.controllers.data.ability;
package kamkeel.npcs.controllers;
import kamkeel.npcs.controllers.data.ability.type.*;
import kamkeel.npcs.controllers.SyncController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityPhase;
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.controllers.data.ability.IAbilityAction;
import kamkeel.npcs.controllers.data.ability.IAbilityExtender;
import kamkeel.npcs.controllers.data.ability.IAbilityFieldProvider;
import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider;
import kamkeel.npcs.controllers.data.ability.UserType;
import kamkeel.npcs.controllers.data.ability.type.AbilityCharge;
import kamkeel.npcs.controllers.data.ability.type.AbilityCutter;
import kamkeel.npcs.controllers.data.ability.type.AbilityDash;
import kamkeel.npcs.controllers.data.ability.type.AbilityGuard;
import kamkeel.npcs.controllers.data.ability.type.AbilityHazard;
import kamkeel.npcs.controllers.data.ability.type.AbilityHeal;
import kamkeel.npcs.controllers.data.ability.type.AbilityHeavyHit;
import kamkeel.npcs.controllers.data.ability.type.AbilityProjectile;
import kamkeel.npcs.controllers.data.ability.type.AbilityShockwave;
import kamkeel.npcs.controllers.data.ability.type.AbilitySlam;
import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport;
import kamkeel.npcs.controllers.data.ability.type.AbilityTrap;
import kamkeel.npcs.controllers.data.ability.type.AbilityVortex;
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;
@@ -12,16 +32,22 @@ 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.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcs;
import noppes.npcs.LogWriter;
import noppes.npcs.api.handler.IAbilityHandler;
import noppes.npcs.util.NBTJsonUtil;
import net.minecraft.entity.player.EntityPlayer;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@@ -33,11 +59,13 @@ public class AbilityController implements IAbilityHandler {
// ── Core Registries ──────────────────────────────────────────────────────
private final Map<String, Supplier<Ability>> abilityTypes = new LinkedHashMap<>();
private final Map<String, Ability> builtAbilities = new LinkedHashMap<>();
private final Map<String, Ability> customAbilities = new LinkedHashMap<>();
private final Map<String, Ability> customAbilities = new LinkedHashMap<>(); // name → Ability
private final Map<String, Ability> customAbilitiesById = new LinkedHashMap<>(); // UUID → Ability
// ── Extension Points ─────────────────────────────────────────────────────
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
private final List<IChainedAbilityFieldProvider> chainedFieldProviders = new ArrayList<>();
private final List<IAbilityExtender> extenders = new ArrayList<>();
private final List<Predicate<EntityPlayer>> flightCheckers = new ArrayList<>();
@@ -46,6 +74,11 @@ public class AbilityController implements IAbilityHandler {
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);
// ── Chained Abilities ──────────────────────────────────────────────────
private final Map<String, ChainedAbility> chainedAbilities = new LinkedHashMap<>(); // name → ChainedAbility
private final Map<String, ChainedAbility> chainedAbilitiesById = new LinkedHashMap<>(); // UUID → ChainedAbility
private int chainedAbilityRevision = 0;
// ── Derived State ────────────────────────────────────────────────────────
private final Set<String> builtInTypeIds = new HashSet<>();
@@ -162,6 +195,7 @@ public class AbilityController implements IAbilityHandler {
public void load() {
customAbilities.clear();
customAbilitiesById.clear();
int migrated = 0;
File dir = getDir();
@@ -177,6 +211,13 @@ public class AbilityController implements IAbilityHandler {
Ability ability = fromNBT(nbt);
if (ability == null) continue;
// Skip built-in abilities that were erroneously saved as custom files
if (ability.isBuiltIn()) {
file.delete();
LogWriter.info("Removed stale built-in ability file: " + filename);
continue;
}
// Legacy migration: UUID-named files get renamed to name-based
if (UUID_PATTERN.matcher(key).matches()) {
String name = ability.getName();
@@ -202,8 +243,24 @@ public class AbilityController implements IAbilityHandler {
migrated++;
}
ability.setId(key);
// Ensure ability has a UUID; generate one for legacy abilities
String uuid = ability.getId();
if (uuid == null || uuid.isEmpty() || uuid.equals(key)) {
// Legacy: id was the name or empty — generate a real UUID
uuid = UUID.randomUUID().toString();
ability.setId(uuid);
// Re-save file with the new UUID
try {
NBTJsonUtil.SaveFile(file, ability.writeNBT());
} catch (Exception ex) {
LogWriter.error("Failed to save UUID for ability: " + key, ex);
}
migrated++;
}
ability.setName(key);
customAbilities.put(key, ability);
customAbilitiesById.put(uuid, ability);
} catch (Exception e) {
LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e);
}
@@ -212,9 +269,11 @@ public class AbilityController implements IAbilityHandler {
customAbilityRevision++;
if (migrated > 0) {
LogWriter.info("Migrated " + migrated + " UUID-named abilities to name-based files");
LogWriter.info("Migrated " + migrated + " abilities (UUID-named files or missing UUIDs)");
}
LogWriter.info("Loaded " + customAbilities.size() + " custom abilities");
loadChainedAbilities();
}
private boolean nameFileExists(File dir, String name) {
@@ -223,42 +282,50 @@ public class AbilityController implements IAbilityHandler {
public boolean saveCustomAbility(Ability ability) {
if (ability == null) return false;
if (ability.isBuiltIn()) return false;
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();
String uuid = ability.getId();
boolean isNew = (uuid == null || uuid.isEmpty());
if (isNew) {
// Generate UUID for new ability
uuid = UUID.randomUUID().toString();
ability.setId(uuid);
// Ensure unique name
while (hasCustomAbilityName(name)) {
name = name + "_";
}
ability.setName(name);
} else {
// Existing ability — check for rename via UUID lookup
Ability existing = customAbilitiesById.get(uuid);
if (existing != null) {
String oldName = existing.getName();
if (!oldName.equals(name)) {
// Name changed! Ensure new name is unique (excluding self)
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);
}
}
}
// 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, name + ".json_new");
File fileCurrent = new File(dir, name + ".json");
@@ -276,8 +343,9 @@ public class AbilityController implements IAbilityHandler {
}
customAbilities.put(name, ability);
customAbilitiesById.put(uuid, ability);
customAbilityRevision++;
LogWriter.info("Saved custom ability: " + name);
LogWriter.info("Saved custom ability: " + name + " [" + uuid + "]");
SyncController.syncAllCustomAbilities();
return true;
} catch (Exception e) {
@@ -292,6 +360,12 @@ public class AbilityController implements IAbilityHandler {
Ability removed = customAbilities.remove(name);
if (removed == null) return false;
// Also remove from UUID index
String uuid = removed.getId();
if (uuid != null && !uuid.isEmpty()) {
customAbilitiesById.remove(uuid);
}
File dir = getDir();
File file = new File(dir, name + ".json");
if (file.exists()) {
@@ -318,7 +392,14 @@ public class AbilityController implements IAbilityHandler {
public void setCustomAbilities(Map<String, Ability> synced) {
customAbilities.clear();
customAbilitiesById.clear();
customAbilities.putAll(synced);
for (Ability ability : synced.values()) {
String uuid = ability.getId();
if (uuid != null && !uuid.isEmpty()) {
customAbilitiesById.put(uuid, ability);
}
}
customAbilityRevision++;
}
@@ -334,6 +415,229 @@ public class AbilityController implements IAbilityHandler {
return dir;
}
// ═══════════════════════════════════════════════════════════════════════════
// CHAINED ABILITIES
// ═══════════════════════════════════════════════════════════════════════════
private void loadChainedAbilities() {
chainedAbilities.clear();
chainedAbilitiesById.clear();
File dir = getChainedDir();
File[] files = dir.exists() ? dir.listFiles() : null;
if (files != null) {
for (File file : files) {
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
try {
String filename = file.getName();
String key = filename.substring(0, filename.length() - 5);
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
ChainedAbility chain = new ChainedAbility();
chain.readNBT(nbt);
chain.setName(key);
chainedAbilities.put(key, chain);
String uuid = chain.getId();
if (uuid != null && !uuid.isEmpty()) {
chainedAbilitiesById.put(uuid, chain);
}
} catch (Exception e) {
LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e);
}
}
}
chainedAbilityRevision++;
LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities");
}
public boolean saveChainedAbility(ChainedAbility chain) {
if (chain == null) return false;
String name = chain.getName();
if (name == null || name.isEmpty()) return false;
String uuid = chain.getId();
boolean isNew = (uuid == null || uuid.isEmpty());
if (isNew) {
uuid = UUID.randomUUID().toString();
chain.setId(uuid);
while (hasChainedAbilityName(name)) {
name = name + "_";
}
chain.setName(name);
} else {
ChainedAbility existing = chainedAbilitiesById.get(uuid);
if (existing != null) {
String oldName = existing.getName();
if (!oldName.equals(name)) {
String testName = name;
while (chainedAbilities.containsKey(testName) && !testName.equals(oldName)) {
testName = testName + "_";
}
if (!testName.equals(name)) {
name = testName;
chain.setName(name);
}
File oldFile = new File(getChainedDir(), oldName + ".json");
if (oldFile.exists()) {
oldFile.delete();
}
chainedAbilities.remove(oldName);
}
}
}
File dir = getChainedDir();
File fileNew = new File(dir, name + ".json_new");
File fileCurrent = new File(dir, name + ".json");
try {
NBTTagCompound nbt = chain.writeNBT();
NBTJsonUtil.SaveFile(fileNew, nbt);
if (fileCurrent.exists()) {
fileCurrent.delete();
}
fileNew.renameTo(fileCurrent);
if (fileNew.exists()) {
fileNew.delete();
}
chainedAbilities.put(name, chain);
chainedAbilitiesById.put(uuid, chain);
chainedAbilityRevision++;
LogWriter.info("Saved chained ability: " + name + " [" + uuid + "]");
SyncController.syncAllChainedAbilities();
return true;
} catch (Exception e) {
LogWriter.error("Error saving chained ability: " + name, e);
return false;
}
}
public boolean deleteChainedAbility(String name) {
if (name == null || name.isEmpty()) return false;
ChainedAbility removed = chainedAbilities.remove(name);
if (removed == null) return false;
String uuid = removed.getId();
if (uuid != null && !uuid.isEmpty()) {
chainedAbilitiesById.remove(uuid);
}
File dir = getChainedDir();
File file = new File(dir, name + ".json");
if (file.exists()) {
file.delete();
}
chainedAbilityRevision++;
LogWriter.info("Deleted chained ability: " + name);
SyncController.syncAllChainedAbilities();
return true;
}
public ChainedAbility resolveChainedAbility(String key) {
if (key == null || key.isEmpty()) return null;
ChainedAbility byId = chainedAbilitiesById.get(key);
if (byId != null) return byId.deepCopy();
ChainedAbility chain = chainedAbilities.get(key);
if (chain != null) return chain.deepCopy();
for (Map.Entry<String, ChainedAbility> entry : chainedAbilities.entrySet()) {
if (entry.getKey().equalsIgnoreCase(key)) {
return entry.getValue().deepCopy();
}
}
return null;
}
public boolean canResolveChainedAbility(String key) {
if (key == null || key.isEmpty()) return false;
if (chainedAbilitiesById.containsKey(key)) return true;
if (chainedAbilities.containsKey(key)) return true;
for (String name : chainedAbilities.keySet()) {
if (name.equalsIgnoreCase(key)) return true;
}
return false;
}
@Override
public ChainedAbility getChainedAbility(String name) {
ChainedAbility chain = chainedAbilities.get(name);
return chain != null ? chain.deepCopy() : null;
}
public ChainedAbility getChainedAbilityByUUID(String uuid) {
return chainedAbilitiesById.get(uuid);
}
public Set<String> getChainedAbilityNamesSet() {
return new LinkedHashSet<>(chainedAbilities.keySet());
}
public Map<String, ChainedAbility> getChainedAbilities() {
return chainedAbilities;
}
@Override
public String[] getChainedAbilityNames() {
return chainedAbilities.keySet().toArray(new String[0]);
}
@Override
public boolean hasChainedAbilityName(String name) {
return chainedAbilities.containsKey(name);
}
@Override
public boolean deleteChainedAbilityByName(String name) {
return deleteChainedAbility(name);
}
@Override
public boolean saveChainedAbility(noppes.npcs.api.ability.IChainedAbility chain) {
if (chain instanceof ChainedAbility) {
return saveChainedAbility((ChainedAbility) chain);
}
return false;
}
public int getChainedAbilityRevision() {
return chainedAbilityRevision;
}
public void setChainedAbilities(Map<String, ChainedAbility> synced) {
chainedAbilities.clear();
chainedAbilitiesById.clear();
chainedAbilities.putAll(synced);
for (ChainedAbility chain : synced.values()) {
String uuid = chain.getId();
if (uuid != null && !uuid.isEmpty()) {
chainedAbilitiesById.put(uuid, chain);
}
}
chainedAbilityRevision++;
}
private File getChainedDir() {
File dir = new File(CustomNpcs.getWorldSaveDirectory(), "abilities" + File.separator + "chained");
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
// ═══════════════════════════════════════════════════════════════════════════
// RESOLUTION
// ═══════════════════════════════════════════════════════════════════════════
@@ -341,17 +645,28 @@ public class AbilityController implements IAbilityHandler {
public Ability resolveAbility(String key) {
if (key == null || key.isEmpty()) return null;
// Custom: UUID lookup (most common for persistent references)
Ability byUuid = customAbilitiesById.get(key);
if (byUuid != null) return byUuid.deepCopy();
// Built-in: exact name
Ability builtIn = builtAbilities.get(key);
if (builtIn != null) return builtIn.deepCopy();
// Built-in: case-insensitive name
// Built-in: case-insensitive map key
for (Map.Entry<String, Ability> entry : builtAbilities.entrySet()) {
if (entry.getKey().equalsIgnoreCase(key)) {
return entry.getValue().deepCopy();
}
}
// Built-in: by ability name (name may differ from map key, e.g. "Ki Fist" vs "ki_fist")
for (Ability ability : builtAbilities.values()) {
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) {
return ability.deepCopy();
}
}
// Built-in: registry key / ID
for (Ability ability : builtAbilities.values()) {
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) {
@@ -359,7 +674,7 @@ public class AbilityController implements IAbilityHandler {
}
}
// Custom: exact name
// Custom: exact name (fallback for legacy references)
Ability custom = customAbilities.get(key);
if (custom != null) return custom.deepCopy();
@@ -381,11 +696,11 @@ public class AbilityController implements IAbilityHandler {
keys.add(id);
}
}
// Custom abilities keyed by name
// Custom abilities keyed by UUID (stable references)
for (Ability ability : customAbilities.values()) {
String name = ability.getName();
if (name != null && !name.isEmpty()) {
keys.add(name);
String id = ability.getId();
if (id != null && !id.isEmpty()) {
keys.add(id);
}
}
return keys;
@@ -401,12 +716,12 @@ public class AbilityController implements IAbilityHandler {
}
}
}
// Custom abilities keyed by name
// Custom abilities keyed by UUID (stable references)
for (Ability ability : customAbilities.values()) {
if (ability.getAllowedBy().allowsPlayer()) {
String name = ability.getName();
if (name != null && !name.isEmpty()) {
keys.add(name);
String id = ability.getId();
if (id != null && !id.isEmpty()) {
keys.add(id);
}
}
}
@@ -414,7 +729,16 @@ public class AbilityController implements IAbilityHandler {
}
public boolean hasAbility(String key) {
return builtAbilities.containsKey(key) || customAbilities.containsKey(key);
return builtAbilities.containsKey(key) || customAbilities.containsKey(key)
|| customAbilitiesById.containsKey(key);
}
public Ability getCustomAbilityByUUID(String uuid) {
return customAbilitiesById.get(uuid);
}
public Ability getCustomAbilityByName(String name) {
return customAbilities.get(name);
}
/**
@@ -424,6 +748,9 @@ public class AbilityController implements IAbilityHandler {
public boolean canResolveAbility(String key) {
if (key == null || key.isEmpty()) return false;
// Custom: UUID lookup (most common for persistent references)
if (customAbilitiesById.containsKey(key)) return true;
// Built-in: exact name
if (builtAbilities.containsKey(key)) return true;
@@ -437,7 +764,7 @@ public class AbilityController implements IAbilityHandler {
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) return true;
}
// Custom: exact name
// Custom: exact name (fallback for legacy references)
if (customAbilities.containsKey(key)) return true;
// Custom: case-insensitive name
@@ -448,6 +775,31 @@ public class AbilityController implements IAbilityHandler {
return false;
}
// ═══════════════════════════════════════════════════════════════════════════
// UNIFIED ACTION RESOLUTION
// ═══════════════════════════════════════════════════════════════════════════
/**
* Resolve any action key to an IAbilityAction.
* Checks abilities first, then chained abilities.
*/
public IAbilityAction resolveAction(String key) {
if (key == null || key.isEmpty()) return null;
Ability ability = resolveAbility(key);
if (ability != null) return ability;
return resolveChainedAbility(key);
}
/**
* Check if a key can be resolved as any action (ability or chain).
*/
public boolean canResolveAction(String key) {
if (canResolveAbility(key)) return true;
return canResolveChainedAbility(key);
}
// ═══════════════════════════════════════════════════════════════════════════
// EXTENSION POINTS
// ═══════════════════════════════════════════════════════════════════════════
@@ -467,7 +819,8 @@ public class AbilityController implements IAbilityHandler {
// If no built-in variants exist but external ones do,
// inject a "Base" variant so the user always gets a choice
if (result.isEmpty()) {
result.add(new AbilityVariant("ability.variant.base", a -> {}));
result.add(new AbilityVariant("ability.variant.base", a -> {
}));
}
result.addAll(ext);
}
@@ -482,6 +835,14 @@ public class AbilityController implements IAbilityHandler {
return fieldProviders;
}
public void registerChainedFieldProvider(IChainedAbilityFieldProvider provider) {
chainedFieldProviders.add(provider);
}
public List<IChainedAbilityFieldProvider> getChainedFieldProviders() {
return chainedFieldProviders;
}
public void registerExtender(IAbilityExtender extender) {
extenders.add(extender);
}
@@ -545,7 +906,7 @@ public class AbilityController implements IAbilityHandler {
double knockbackDirX, double knockbackDirZ) {
for (IAbilityExtender ext : extenders) {
if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp,
knockbackDirX, knockbackDirZ)) {
knockbackDirX, knockbackDirZ)) {
return true;
}
}
@@ -621,4 +982,5 @@ public class AbilityController implements IAbilityHandler {
public boolean deleteCustomAbilityByName(String name) {
return deleteCustomAbility(name);
}
}
@@ -5,6 +5,8 @@ import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import kamkeel.npcs.addon.DBCAddon;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.network.LargeAbstractPacket;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.enums.EnumSyncAction;
@@ -39,8 +41,6 @@ import noppes.npcs.controllers.data.Quest;
import noppes.npcs.controllers.data.QuestCategory;
import noppes.npcs.controllers.data.RecipeAnvil;
import noppes.npcs.controllers.data.RecipeCarpentry;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import java.io.IOException;
import java.util.EnumMap;
@@ -76,7 +76,8 @@ public class SyncController {
EnumSyncType.CUSTOM_EFFECTS,
EnumSyncType.MAGIC,
EnumSyncType.MAGIC_CYCLE,
EnumSyncType.CUSTOM_ABILITY
EnumSyncType.CUSTOM_ABILITY,
EnumSyncType.CHAINED_ABILITY
};
public static void load() {
@@ -93,6 +94,7 @@ public class SyncController {
registerCache(EnumSyncType.MAGIC, SyncController::magicsNBT);
registerCache(EnumSyncType.MAGIC_CYCLE, SyncController::magicCyclesNBT);
registerCache(EnumSyncType.CUSTOM_ABILITY, SyncController::customAbilitiesNBT);
registerCache(EnumSyncType.CHAINED_ABILITY, SyncController::chainedAbilitiesNBT);
}
public static void syncPlayer(EntityPlayerMP player) {
@@ -349,6 +351,27 @@ public class SyncController {
updateAllPlayerRevisions(EnumSyncType.CUSTOM_ABILITY, payload.getRevision());
}
public static NBTTagCompound chainedAbilitiesNBT() {
NBTTagList list = new NBTTagList();
NBTTagCompound compound = new NBTTagCompound();
for (Map.Entry<String, ChainedAbility> entry : AbilityController.Instance.getChainedAbilities().entrySet()) {
NBTTagCompound chainNBT = entry.getValue().writeNBT();
chainNBT.setString("ChainedAbilityId", entry.getKey());
list.appendTag(chainNBT);
}
compound.setTag("Data", list);
return compound;
}
public static void syncAllChainedAbilities() {
CachedSyncPayload payload = rebuildNow(EnumSyncType.CHAINED_ABILITY);
if (payload == null) {
return;
}
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.CHAINED_ABILITY, payload));
updateAllPlayerRevisions(EnumSyncType.CHAINED_ABILITY, payload.getRevision());
}
public static void syncPlayerData(EntityPlayerMP player, boolean update) {
PlayerData data = PlayerData.get(player);
if (data != null) {
@@ -626,16 +649,30 @@ public class SyncController {
LinkedHashMap<String, Ability> sync = new LinkedHashMap<>();
for (int i = 0; i < list.tagCount(); i++) {
NBTTagCompound nbt = list.getCompoundTagAt(i);
String uuid = nbt.getString("CustomAbilityId");
String name = nbt.getString("CustomAbilityId");
Ability ability = AbilityController.Instance.fromNBT(nbt);
if (ability != null) {
ability.setId(uuid);
sync.put(uuid, ability);
ability.setName(name);
sync.put(name, ability);
}
}
AbilityController.Instance.setCustomAbilities(sync);
break;
}
case CHAINED_ABILITY: {
NBTTagList list = fullCompound.getTagList("Data", 10);
LinkedHashMap<String, ChainedAbility> sync = new LinkedHashMap<>();
for (int i = 0; i < list.tagCount(); i++) {
NBTTagCompound nbt = list.getCompoundTagAt(i);
String id = nbt.getString("ChainedAbilityId");
ChainedAbility chain = new ChainedAbility();
chain.readNBT(nbt);
chain.setName(id);
sync.put(id, chain);
}
AbilityController.Instance.setChainedAbilities(sync);
break;
}
}
ClientCacheHandler.updateClientRevision(enumSyncType, revision);
@@ -886,6 +923,8 @@ public class SyncController {
return EnumSet.of(EnumSyncType.CUSTOM_EFFECTS);
case CUSTOM_ABILITY:
return EnumSet.of(EnumSyncType.CUSTOM_ABILITY);
case CHAINED_ABILITY:
return EnumSet.of(EnumSyncType.CHAINED_ABILITY);
default:
return EnumSet.noneOf(EnumSyncType.class);
}
@@ -2,51 +2,53 @@ package kamkeel.npcs.controllers.data.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import noppes.npcs.DataAbilities;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.api.INbt;
import noppes.npcs.api.ability.IAbility;
import noppes.npcs.api.entity.IPlayer;
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.IAbilityConfigCallback;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.data.Animation;
import net.minecraft.entity.player.EntityPlayer;
import noppes.npcs.controllers.data.Frame;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.entity.IPlayer;
import noppes.npcs.controllers.ScriptController;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.Frame;
import noppes.npcs.controllers.data.PlayerDataScript;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.scripted.NpcAPI;
import noppes.npcs.scripted.event.AbilityEvent;
import noppes.npcs.scripted.event.player.PlayerAbilityEvent;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.minecraft.entity.Entity;
public abstract class Ability implements IAbility {
public abstract class Ability implements IAbility, IAbilityAction {
// ═══════════════════════════════════════════════════════════════════
// CONFIGURATION (saved to NBT)
// ═══════════════════════════════════════════════════════════════════
protected String id;
protected String name;
protected String typeId; // e.g., "ability.cnpc.slam" (also used as lang key)
protected String id = ""; // UUID for custom, registry key for built-in
protected String name = ""; // Unique file key / identifier
protected String displayName = ""; // Cosmetic name (falls back to name when empty)
protected String typeId = ""; // e.g., "ability.cnpc.slam" (also used as lang key)
// Selection
protected int weight = 10;
@@ -102,6 +104,35 @@ public abstract class Ability implements IAbility {
// Cooldown override
protected boolean ignoreCooldown = false;
// ═══════════════════════════════════════════════════════════════════
// BUILT-IN (set in constructor via configureAsBuiltIn, NOT persisted)
// ═══════════════════════════════════════════════════════════════════
protected transient boolean builtIn = false;
protected transient String registryKey;
// ═══════════════════════════════════════════════════════════════════
// TOGGLE CONFIGURATION (saved to NBT for custom abilities)
// ═══════════════════════════════════════════════════════════════════
/**
* Number of toggle states. 0 = not toggleable, 1 = binary on/off, 2+ = multi-state cycling.
* Cycling order: Off(0) -> State 1 -> State 2 -> ... -> State N -> Off(0)
*/
protected int toggleStates = 0;
/**
* Whether the toggled-ON state ticks each game tick (e.g., Kaioken drains ki per tick)
*/
protected boolean hasActiveToggle = false;
/**
* Optional display labels for each toggle state (1-indexed).
* Used in ability wheel/hotbar to show current state name.
* Null means no labels. Array length should match toggleStates.
*/
protected String[] toggleStateLabels;
// Configurable potion effects
protected List<AbilityPotionEffect> effects = new ArrayList<>();
@@ -205,9 +236,50 @@ public abstract class Ability implements IAbility {
return false;
}
// ═══════════════════════════════════════════════════════════════════
// TOGGLE CALLBACKS (optional overrides for toggleable abilities)
// ═══════════════════════════════════════════════════════════════════
/**
* Called when this toggle ability is first activated (state goes from 0 to any active state).
* Override to apply effects (e.g., set a DBC flag, apply a buff).
*/
public void onToggleOn(EntityLivingBase caster) {
}
/**
* Called when this toggle ability is fully deactivated (state goes to 0).
* Override to remove effects (e.g., clear a DBC flag, remove a buff).
*/
public void onToggleOff(EntityLivingBase caster) {
}
/**
* Called whenever the toggle state changes (including on/off transitions and mid-cycling).
* For multi-state toggles, this fires on every state transition.
*
* @param caster The entity
* @param oldState Previous state (0 = was off)
* @param newState New state (0 = now off, 1+ = active state)
*/
public void onToggleStateChanged(EntityLivingBase caster, int oldState, int newState) {
}
/**
* Called every tick while this toggle is active (only if hasActiveToggle is true).
* Override for per-tick effects (e.g., ki drain for Kaioken).
*
* @param state Current toggle state (1-based)
* @return true to keep the toggle active, false to auto-deactivate
*/
public boolean onToggleTick(EntityLivingBase caster, int tickCount, int state) {
return true;
}
/**
* Whether this ability is ready for auto-completion during burst overlap mode.
* Override to delay auto-completion until all staggered projectiles have been fired.
*
* @param activeTick the current tick within the ACTIVE phase
*/
public boolean isReadyForBurstCompletion(int activeTick) {
@@ -231,6 +303,17 @@ public abstract class Ability implements IAbility {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
/**
* Called when the ability enters BURST_DELAY between burst iterations.
* Override in subclasses to reset transient state that should not carry
* across burst repetitions (e.g., movement direction, hit entity lists).
* <p>
* Unlike {@link #cleanup()}, this does NOT kill spawned entities (they may
* still be in flight for overlap mode).
*/
public void resetForBurst() {
}
public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) {
}
@@ -483,6 +566,12 @@ public abstract class Ability implements IAbility {
// ── General tab ──────────────────────────────────────────────
defs.add(FieldDef.stringField("gui.name", this::getName, this::setName)
.tab("General"));
defs.add(FieldDef.stringField("gui.displayName", this::getRawDisplayName, this::setDisplayName)
.tab("General"));
defs.add(FieldDef.labelField("ability.validFor", () ->
StatCollector.translateToLocal("ability.validFor") + ": \u00A7e"
+ StatCollector.translateToLocal("ability.userType." + getAllowedBy().name()))
.tab("General"));
defs.add(FieldDef.row(
FieldDef.intField("ability.weight", this::getWeight, this::setWeight).range(1, 1000),
FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled)
@@ -508,27 +597,36 @@ public abstract class Ability implements IAbility {
() -> this.getLockMovement().getDisplayKey(),
v -> {
for (LockMovementType t : LockMovementType.values()) {
if (t.getDisplayKey().equals(v)) { this.setLockMovement(t); break; }
if (t.getDisplayKey().equals(v)) {
this.setLockMovement(t);
break;
}
}
})
.hover("ability.hover.lockMovement")
.tab("General"));
.hover("ability.hover.lockMovement")
.tab("General"));
defs.add(FieldDef.row(
FieldDef.stringEnumField("ability.rotationMode", RotationMode.getDisplayKeys(),
() -> this.getRotationMode().getDisplayKey(),
v -> {
for (RotationMode m : RotationMode.values()) {
if (m.getDisplayKey().equals(v)) { this.setRotationMode(m); break; }
}
})
() -> this.getRotationMode().getDisplayKey(),
v -> {
for (RotationMode m : RotationMode.values()) {
if (m.getDisplayKey().equals(v)) {
this.setRotationMode(m);
break;
}
}
})
.hover("ability.hover.rotationMode"),
FieldDef.stringEnumField("ability.rotationPhase", getRotationPhaseKeys(),
() -> this.getRotationPhase().getDisplayKey(),
v -> {
for (LockMovementType t : LockMovementType.values()) {
if (t.getDisplayKey().equals(v)) { this.setRotationPhase(t); break; }
}
})
() -> this.getRotationPhase().getDisplayKey(),
v -> {
for (LockMovementType t : LockMovementType.values()) {
if (t.getDisplayKey().equals(v)) {
this.setRotationPhase(t);
break;
}
}
})
.hover("ability.hover.rotationPhase")
.visibleWhen(() -> this.rotationMode != RotationMode.FREE)
).tab("General"));
@@ -571,15 +669,17 @@ public abstract class Ability implements IAbility {
allowedKeys[i] = allowed[i].name();
}
defs.add(FieldDef.stringEnumField("ability.targetingMode", allowedKeys,
() -> this.getTargetingMode().name(),
v -> {
try { this.setTargetingMode(TargetingMode.valueOf(v)); }
catch (Exception ignored) {}
})
() -> this.getTargetingMode().name(),
v -> {
try {
this.setTargetingMode(TargetingMode.valueOf(v));
} catch (Exception ignored) {
}
})
.tab("Target").hover("ability.hover.targeting"));
} else {
defs.add(FieldDef.enumField("ability.targetingMode", TargetingMode.class,
this::getTargetingMode, this::setTargetingMode)
this::getTargetingMode, this::setTargetingMode)
.tab("Target").hover("ability.hover.targeting"));
}
}
@@ -592,12 +692,12 @@ public abstract class Ability implements IAbility {
.tab("Effects"));
defs.add(FieldDef.section("ability.section.animations").tab("Effects"));
defs.add(FieldDef.animSubGui("ability.windUpAnimation",
this::getWindUpAnimationId, this::setWindUpAnimationId,
this::getWindUpAnimationName, this::setWindUpAnimationName)
this::getWindUpAnimationId, this::setWindUpAnimationId,
this::getWindUpAnimationName, this::setWindUpAnimationName)
.tab("Effects"));
defs.add(FieldDef.animSubGui("ability.activeAnimation",
this::getActiveAnimationId, this::setActiveAnimationId,
this::getActiveAnimationName, this::setActiveAnimationName)
this::getActiveAnimationId, this::setActiveAnimationId,
this::getActiveAnimationName, this::setActiveAnimationName)
.tab("Effects"));
defs.add(FieldDef.animSubGui("ability.dazedAnimation",
this::getDazedAnimationId, this::setDazedAnimationId,
@@ -653,7 +753,7 @@ public abstract class Ability implements IAbility {
* Create a telegraph instance for this ability.
* Override for custom telegraph shapes.
*
* @param caster The caster
* @param caster The caster
* @param target The target (for position calculation)
* @return The telegraph instance, or null if no telegraph
*/
@@ -916,9 +1016,9 @@ public abstract class Ability implements IAbility {
if (phase == AbilityPhase.ACTIVE) {
// Check if more burst iterations remain
if (burstEnabled && burstAmount > 0 && burstIndex < burstAmount) {
// Don't cleanup here - entities may still be in flight
// For non-overlap: cleaned up when next burst starts (in onExecute)
// For overlap: entities continue flying indefinitely
// Reset transient state for next burst iteration (e.g., movement direction, hit lists)
// Does NOT kill entities - they may still be in flight (overlap mode)
resetForBurst();
burstIndex++;
phase = AbilityPhase.BURST_DELAY;
currentTick = 0;
@@ -1119,6 +1219,7 @@ public abstract class Ability implements IAbility {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("id", id);
nbt.setString("name", name);
nbt.setString("displayName", displayName);
nbt.setString("typeId", typeId);
nbt.setInteger("weight", weight);
nbt.setBoolean("enabled", enabled);
@@ -1150,6 +1251,17 @@ public abstract class Ability implements IAbility {
nbt.setInteger("allowedBy", allowedBy.ordinal());
nbt.setBoolean("ignoreCooldown", ignoreCooldown);
// Toggle
nbt.setInteger("toggleStates", toggleStates);
nbt.setBoolean("hasActiveToggle", hasActiveToggle);
if (toggleStateLabels != null && toggleStateLabels.length > 0) {
NBTTagList labelList = new NBTTagList();
for (String label : toggleStateLabels) {
labelList.appendTag(new NBTTagString(label != null ? label : ""));
}
nbt.setTag("toggleStateLabels", labelList);
}
// Burst
nbt.setBoolean("burstEnabled", burstEnabled);
nbt.setInteger("burstAmount", burstAmount);
@@ -1180,17 +1292,22 @@ public abstract class Ability implements IAbility {
public void readNBT(NBTTagCompound nbt) {
id = nbt.getString("id");
name = nbt.getString("name");
displayName = nbt.getString("displayName");
typeId = nbt.getString("typeId");
weight = nbt.getInteger("weight");
enabled = nbt.getBoolean("enabled");
targetingMode = TargetingMode.valueOf(nbt.getString("targetingMode"));
weight = nbt.hasKey("weight") ? nbt.getInteger("weight") : 10;
enabled = nbt.hasKey("enabled") ? nbt.getBoolean("enabled") : true;
try {
targetingMode = TargetingMode.valueOf(nbt.getString("targetingMode"));
} catch (Exception e) {
targetingMode = TargetingMode.AGGRO_TARGET;
}
minRange = nbt.getFloat("minRange");
maxRange = nbt.getFloat("maxRange");
cooldownTicks = nbt.getInteger("cooldown");
windUpTicks = nbt.getInteger("windUp");
syncWindupWithAnimation = nbt.getBoolean("syncWindup");
dazedTicks = nbt.getInteger("recovery");
interruptible = nbt.getBoolean("interruptible");
syncWindupWithAnimation = nbt.hasKey("syncWindup") ? nbt.getBoolean("syncWindup") : true;
dazedTicks = nbt.hasKey("recovery") ? nbt.getInteger("recovery") : 80;
interruptible = nbt.hasKey("interruptible") ? nbt.getBoolean("interruptible") : true;
lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement"));
rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode"));
rotationPhase = LockMovementType.fromOrdinal(nbt.getInteger("rotationPhase"));
@@ -1204,7 +1321,7 @@ public abstract class Ability implements IAbility {
windUpAnimationName = nbt.getString("windUpAnimationName");
activeAnimationName = nbt.getString("activeAnimationName");
dazedAnimationName = nbt.getString("dazedAnimationName");
showTelegraph = nbt.getBoolean("showTelegraph");
showTelegraph = nbt.hasKey("showTelegraph") ? nbt.getBoolean("showTelegraph") : true;
try {
telegraphType = TelegraphType.valueOf(nbt.getString("telegraphType"));
} catch (Exception e) {
@@ -1215,6 +1332,20 @@ public abstract class Ability implements IAbility {
allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy"));
ignoreCooldown = nbt.getBoolean("ignoreCooldown");
// Toggle
toggleStates = nbt.getInteger("toggleStates");
hasActiveToggle = nbt.getBoolean("hasActiveToggle");
if (nbt.hasKey("toggleStateLabels")) {
NBTTagList labelList = nbt.getTagList("toggleStateLabels", 8);
toggleStateLabels = new String[labelList.tagCount()];
for (int i = 0; i < labelList.tagCount(); i++) {
String label = labelList.getStringTagAt(i);
toggleStateLabels[i] = (label != null && !label.isEmpty()) ? label : null;
}
} else {
toggleStateLabels = null;
}
// Burst
burstEnabled = nbt.getBoolean("burstEnabled");
burstAmount = nbt.getInteger("burstAmount");
@@ -1242,7 +1373,10 @@ public abstract class Ability implements IAbility {
}
}
readTypeNBT(nbt.getCompoundTag("typeData"));
// Type-specific
if (nbt.hasKey("typeData")) {
readTypeNBT(nbt.getCompoundTag("typeData"));
}
}
// ═══════════════════════════════════════════════════════════════════
@@ -1265,18 +1399,70 @@ public abstract class Ability implements IAbility {
this.name = name;
}
/**
* Get the display name for this ability.
* Returns displayName if set, otherwise falls back to name.
* Converts &amp; color codes to § for rendering.
*/
public String getDisplayName() {
String result = (displayName != null && !displayName.isEmpty()) ? displayName : name;
return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : "";
}
/**
* Get the raw display name (may be empty).
*/
public String getRawDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName != null ? displayName : "";
}
public String getTypeId() {
return typeId;
}
public boolean isBuiltIn() {
return false;
return builtIn;
}
/**
* Get the registry key for built-in abilities.
* Returns null for custom abilities.
*/
public String getRegistryKey() {
return registryKey;
}
/**
* Configure this ability as a built-in ability with fixed config.
* Sets builtIn flag, assigns registry key, id, name, and derives typeId.
* Call this in the constructor of any ability that should be non-customizable.
*/
protected void configureAsBuiltIn(String registryKey) {
this.builtIn = true;
this.registryKey = registryKey;
this.id = registryKey;
this.name = registryKey;
this.typeId = "ability." + registryKey.replace(':', '.');
}
public Ability deepCopy() {
return AbilityController.Instance.fromNBT(this.writeNBT());
}
@Override
public boolean isChain() {
return false;
}
@Override
public IAbilityAction deepCopyAction() {
return deepCopy();
}
public boolean isNpcInlineEdit() {
return npcInlineEdit;
}
@@ -1399,6 +1585,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Get lock movement type as integer.
*
* @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
@@ -1408,6 +1595,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Set lock movement type from integer.
*
* @param type 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
@@ -1417,6 +1605,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Get rotation mode as integer.
*
* @return 0=FREE, 1=LOCKED, 2=TRACK
*/
@Override
@@ -1426,6 +1615,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Set rotation mode from integer.
*
* @param type 0=FREE, 1=LOCKED, 2=TRACK
*/
@Override
@@ -1435,6 +1625,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Get rotation phase as integer.
*
* @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
@@ -1444,6 +1635,7 @@ public abstract class Ability implements IAbility {
/**
* API method: Set rotation phase from integer.
*
* @param type 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
@@ -1727,6 +1919,46 @@ public abstract class Ability implements IAbility {
this.ignoreCooldown = ignoreCooldown;
}
public boolean isToggleable() {
return toggleStates > 0;
}
public int getToggleStates() {
return toggleStates;
}
public void setToggleStates(int toggleStates) {
this.toggleStates = Math.max(0, toggleStates);
}
public boolean hasActiveToggle() {
return hasActiveToggle;
}
public void setHasActiveToggle(boolean hasActiveToggle) {
this.hasActiveToggle = hasActiveToggle;
}
/**
* Get the display label for a specific toggle state.
* @param state 1-based state index
* @return the label, or null if none set
*/
public String getToggleStateLabel(int state) {
if (toggleStateLabels != null && state >= 1 && state <= toggleStateLabels.length) {
return toggleStateLabels[state - 1];
}
return null;
}
/**
* Set display labels for toggle states (1-indexed).
* @param labels one label per state, null entries allowed
*/
public void setToggleStateLabels(String... labels) {
this.toggleStateLabels = (labels != null && labels.length > 0) ? labels : null;
}
public List<AbilityPotionEffect> getEffects() {
return effects;
}
@@ -0,0 +1,309 @@
package kamkeel.npcs.controllers.data.ability;
import kamkeel.npcs.controllers.AbilityController;
import net.minecraft.nbt.NBTTagCompound;
/**
* Unified slot for storing any combat action: inline ability, ability reference, or chain reference.
* Replaces {@link AbilitySlot} with support for all {@link IAbilityAction} types.
* <p>
* References are lazy-resolved via their respective controllers and cached until
* the controller revision changes.
*/
public class AbilityAction {
public enum SlotType {
INLINE_ABILITY,
ABILITY_REFERENCE,
CHAIN_REFERENCE,
INLINE_CHAIN
}
private SlotType slotType;
private Ability inlineAbility;
private ChainedAbility inlineChain;
private String referenceId;
/**
* Per-slot enabled override for reference slots (null = use master's default).
*/
private Boolean enabledOverride;
// Cache
private transient IAbilityAction cachedAction;
private transient int cachedRevision = -1;
private AbilityAction() {
}
// ═══════════════════════════════════════════════════════════════════
// FACTORY METHODS
// ═══════════════════════════════════════════════════════════════════
public static AbilityAction inline(Ability ability) {
AbilityAction slot = new AbilityAction();
slot.slotType = SlotType.INLINE_ABILITY;
slot.inlineAbility = ability;
return slot;
}
public static AbilityAction abilityReference(String referenceId) {
AbilityAction slot = new AbilityAction();
slot.slotType = SlotType.ABILITY_REFERENCE;
slot.referenceId = referenceId;
return slot;
}
public static AbilityAction chainReference(String referenceId) {
AbilityAction slot = new AbilityAction();
slot.slotType = SlotType.CHAIN_REFERENCE;
slot.referenceId = referenceId;
return slot;
}
public static AbilityAction inlineChain(ChainedAbility chain) {
AbilityAction slot = new AbilityAction();
slot.slotType = SlotType.INLINE_CHAIN;
slot.inlineChain = chain;
return slot;
}
// ═══════════════════════════════════════════════════════════════════
// QUERIES
// ═══════════════════════════════════════════════════════════════════
public SlotType getSlotType() {
return slotType;
}
public boolean isReference() {
return slotType == SlotType.ABILITY_REFERENCE || slotType == SlotType.CHAIN_REFERENCE;
}
public boolean isChainReference() {
return slotType == SlotType.CHAIN_REFERENCE;
}
public boolean isInlineChain() {
return slotType == SlotType.INLINE_CHAIN;
}
public boolean isChain() {
return slotType == SlotType.CHAIN_REFERENCE || slotType == SlotType.INLINE_CHAIN;
}
public boolean isAbilityReference() {
return slotType == SlotType.ABILITY_REFERENCE;
}
public String getReferenceId() {
return referenceId;
}
// ═══════════════════════════════════════════════════════════════════
// RESOLUTION
// ═══════════════════════════════════════════════════════════════════
/**
* Resolve the action. Inline returns directly; references resolve
* from the appropriate controller with version-based caching.
*/
public IAbilityAction getAction() {
switch (slotType) {
case INLINE_ABILITY:
return inlineAbility;
case INLINE_CHAIN:
return inlineChain;
case ABILITY_REFERENCE: {
AbilityController ctrl = AbilityController.Instance;
if (ctrl == null) return null;
int rev = ctrl.getCustomAbilityRevision();
if (cachedAction != null && cachedRevision == rev) {
return cachedAction;
}
Ability resolved = ctrl.resolveAbility(referenceId);
if (resolved != null && enabledOverride != null) {
resolved.setEnabled(enabledOverride);
}
cachedAction = resolved;
cachedRevision = rev;
return cachedAction;
}
case CHAIN_REFERENCE: {
AbilityController ctrl = AbilityController.Instance;
if (ctrl == null) return null;
int rev = ctrl.getChainedAbilityRevision();
if (cachedAction != null && cachedRevision == rev) {
return cachedAction;
}
ChainedAbility resolved = ctrl.resolveChainedAbility(referenceId);
if (resolved != null && enabledOverride != null) {
resolved.setEnabled(enabledOverride);
}
cachedAction = resolved;
cachedRevision = rev;
return cachedAction;
}
default:
return null;
}
}
/**
* Get as Ability (returns null if this is a chain slot or resolution fails).
*/
public Ability getAbility() {
IAbilityAction action = getAction();
return (action instanceof Ability) ? (Ability) action : null;
}
/**
* Get as ChainedAbility (returns null if this is an ability slot or resolution fails).
*/
public ChainedAbility getChainedAbility() {
IAbilityAction action = getAction();
return (action instanceof ChainedAbility) ? (ChainedAbility) action : null;
}
// ═══════════════════════════════════════════════════════════════════
// ENABLED OVERRIDE
// ═══════════════════════════════════════════════════════════════════
public void setEnabled(boolean enabled) {
if (slotType == SlotType.INLINE_ABILITY) {
if (inlineAbility != null) {
inlineAbility.setEnabled(enabled);
}
} else if (slotType == SlotType.INLINE_CHAIN) {
if (inlineChain != null) {
inlineChain.setEnabled(enabled);
}
} else {
this.enabledOverride = enabled;
if (cachedAction != null) {
cachedAction.setEnabled(enabled);
}
}
}
// ═══════════════════════════════════════════════════════════════════
// INLINE CONVERSION (ability references only)
// ═══════════════════════════════════════════════════════════════════
public Ability getInlineAbility() {
return inlineAbility;
}
public ChainedAbility getInlineChain() {
return inlineChain;
}
/**
* Convert this reference slot to an inline slot by copying the resolved data.
* Works for ability references (→ INLINE_ABILITY) and chain references (→ INLINE_CHAIN).
* Chain entries are kept as-is (not auto-converted to inline).
*/
public boolean convertToInline() {
if (slotType == SlotType.ABILITY_REFERENCE) {
Ability resolved = getAbility();
if (resolved == null) return false;
inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT());
slotType = SlotType.INLINE_ABILITY;
referenceId = null;
cachedAction = null;
cachedRevision = -1;
return true;
}
if (slotType == SlotType.CHAIN_REFERENCE) {
ChainedAbility resolved = getChainedAbility();
if (resolved == null) return false;
inlineChain = resolved.deepCopy();
slotType = SlotType.INLINE_CHAIN;
referenceId = null;
cachedAction = null;
cachedRevision = -1;
return true;
}
return false;
}
// ═══════════════════════════════════════════════════════════════════
// NBT
// ═══════════════════════════════════════════════════════════════════
public NBTTagCompound writeNBT() {
switch (slotType) {
case INLINE_CHAIN: {
NBTTagCompound nbt = new NBTTagCompound();
if (inlineChain != null) {
nbt.setTag("InlineChain", inlineChain.writeNBT());
}
return nbt;
}
case CHAIN_REFERENCE: {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("ChainReference", referenceId);
if (enabledOverride != null) {
nbt.setBoolean("RefEnabled", enabledOverride);
}
return nbt;
}
case ABILITY_REFERENCE: {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("Reference", referenceId);
if (enabledOverride != null) {
nbt.setBoolean("RefEnabled", enabledOverride);
}
return nbt;
}
case INLINE_ABILITY:
default:
return inlineAbility != null ? inlineAbility.writeNBT() : new NBTTagCompound();
}
}
public static AbilityAction fromNBT(NBTTagCompound nbt) {
if (nbt == null) return null;
// Inline chain (new format)
if (nbt.hasKey("InlineChain")) {
ChainedAbility chain = new ChainedAbility();
chain.readNBT(nbt.getCompoundTag("InlineChain"));
return inlineChain(chain);
}
// Chain reference
if (nbt.hasKey("ChainReference")) {
AbilityAction slot = chainReference(nbt.getString("ChainReference"));
if (nbt.hasKey("RefEnabled")) {
slot.enabledOverride = nbt.getBoolean("RefEnabled");
}
return slot;
}
// Ability reference
if (nbt.hasKey("Reference")) {
AbilityAction slot = abilityReference(nbt.getString("Reference"));
if (nbt.hasKey("RefEnabled")) {
slot.enabledOverride = nbt.getBoolean("RefEnabled");
}
return slot;
}
// Inline ability
Ability ability = AbilityController.Instance.fromNBT(nbt);
if (ability == null) return null;
return inline(ability);
}
}
@@ -1,5 +1,6 @@
package kamkeel.npcs.controllers.data.ability;
import kamkeel.npcs.controllers.AbilityController;
import net.minecraft.nbt.NBTTagCompound;
/**
@@ -8,19 +9,25 @@ import net.minecraft.nbt.NBTTagCompound;
* <p>
* References are lazy-resolved via {@link AbilityController#resolveAbility(String)}
* and cached until the controller version changes.
*
* @deprecated Use {@link AbilityAction} instead, which supports abilities and chained abilities.
*/
@Deprecated
public class AbilitySlot {
private Ability inlineAbility;
private String referenceId;
/** Per-slot enabled override for reference slots (null = use master's default). */
/**
* Per-slot enabled override for reference slots (null = use master's default).
*/
private Boolean enabledOverride;
private transient Ability cachedAbility;
private transient int cachedVersion = -1;
private AbilitySlot() {}
private AbilitySlot() {
}
/**
* Create a slot with an inline (embedded) ability.
@@ -6,7 +6,7 @@ import java.util.function.Consumer;
* Represents a variant/template for an ability type.
* When a user creates an ability that has registered variants,
* they are presented with a selection dialog before the ability is created.
*
* <p>
* Each variant has a display name (lang key) and a configurator that
* applies preset values to the newly created ability.
*/
@@ -50,14 +50,22 @@ public enum AnchorPoint {
@Override
public String toString() {
switch (this) {
case FRONT: return "ability.anchor.front";
case CENTER: return "ability.anchor.center";
case RIGHT_HAND: return "ability.anchor.rightHand";
case LEFT_HAND: return "ability.anchor.leftHand";
case ABOVE_HEAD: return "ability.anchor.aboveHead";
case CHEST: return "ability.anchor.chest";
case EYE: return "ability.anchor.eye";
default: return name();
case FRONT:
return "ability.anchor.front";
case CENTER:
return "ability.anchor.center";
case RIGHT_HAND:
return "ability.anchor.rightHand";
case LEFT_HAND:
return "ability.anchor.leftHand";
case ABOVE_HEAD:
return "ability.anchor.aboveHead";
case CHEST:
return "ability.anchor.chest";
case EYE:
return "ability.anchor.eye";
default:
return name();
}
}
}
@@ -1,61 +0,0 @@
package kamkeel.npcs.controllers.data.ability;
import net.minecraft.nbt.NBTTagCompound;
/**
* Abstract base class for built-in (non-customizable) abilities.
* Built-in abilities are registered by key, have fixed configurations,
* and cannot be modified through the preset GUI system.
* <p>
* Examples: simple utility abilities like Jump, Sprint, etc.
* Third-party mods can extend this class to create their own built-in abilities.
*/
public abstract class BuiltInAbility extends Ability {
/**
* The registry key used to look up this ability (e.g., "cnpc:jump", "dbc:ki_blast").
*/
private final String registryKey;
public BuiltInAbility(String registryKey) {
this.registryKey = registryKey;
this.id = registryKey;
this.name = registryKey;
// Convert "namespace:name" to "ability.namespace.name" for GUI filter compatibility
this.typeId = "ability." + registryKey.replace(':', '.');
}
@Override
public boolean isBuiltIn() {
return true;
}
/**
* Get the registry key for this built-in ability.
*/
public String getRegistryKey() {
return registryKey;
}
/**
* Built-in abilities do not write type-specific NBT (they have fixed configs).
*/
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
// No-op: built-in abilities have fixed configurations
}
/**
* Built-in abilities do not read type-specific NBT.
*/
@Override
public void readTypeNBT(NBTTagCompound nbt) {
// No-op: built-in abilities have fixed configurations
}
/**
* Get the duration of the active phase in ticks.
* Built-in abilities must define their own active duration.
*/
public abstract int getActiveDurationTicks();
}
@@ -0,0 +1,438 @@
package kamkeel.npcs.controllers.data.ability;
import kamkeel.npcs.controllers.AbilityController;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.api.ability.IChainedAbility;
import java.util.ArrayList;
import java.util.List;
/**
* A chained ability is an ordered sequence of ability references that execute
* one after another. It has its own conditions, cooldown, weight, and per-entry
* delay configuration. Individual ability conditions are skipped during chain execution.
* <p>
* All entries must be ability references (by name/key). Inline abilities are not supported.
* When saving, all references are validated against {@link AbilityController}.
*/
public class ChainedAbility implements IChainedAbility, IAbilityAction {
// ═══════════════════════════════════════════════════════════════════
// CONFIGURATION (saved to NBT)
// ═══════════════════════════════════════════════════════════════════
private String id = ""; // UUID (stable persistent reference)
private String name = ""; // Unique file key / identifier
private String displayName = ""; // Cosmetic name (falls back to name when empty)
private boolean enabled = true;
/**
* Weight for NPC weighted random selection.
*/
private int weight = 10;
/**
* Whether each ability in the chain uses its own windup phase.
*/
private boolean windUpAll = true;
/**
* Chain-level cooldown in ticks (applied after entire chain completes).
*/
private int cooldownTicks = 100;
/**
* Minimum range for NPC eligibility check.
*/
private float minRange = 0;
/**
* Maximum range for NPC eligibility check.
*/
private float maxRange = 20;
/**
* Chain-level conditions (individual ability conditions are ignored).
*/
private List<Condition> conditions = new ArrayList<>();
/**
* Ordered list of ability entries to execute.
*/
private List<ChainedAbilityEntry> entries = new ArrayList<>();
/**
* Extension data for external mods (e.g., icons, DBC stats).
*/
private NBTTagCompound customData = new NBTTagCompound();
public ChainedAbility() {
}
public ChainedAbility(String name) {
this.name = name != null ? name : "";
}
// ═══════════════════════════════════════════════════════════════════
// GETTERS / SETTERS
// ═══════════════════════════════════════════════════════════════════
public String getId() {
return id;
}
public void setId(String id) {
this.id = id != null ? id : "";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name != null ? name : "";
}
/**
* Get the display name for this chained ability.
* Returns displayName if set, otherwise falls back to name.
* Converts &amp; color codes to § for rendering.
*/
public String getDisplayName() {
String result = (displayName != null && !displayName.isEmpty()) ? displayName : name;
return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : "";
}
public String getRawDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName != null ? displayName : "";
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = Math.max(1, weight);
}
public boolean isWindUpAll() {
return windUpAll;
}
public void setWindUpAll(boolean windUpAll) {
this.windUpAll = windUpAll;
}
public int getCooldownTicks() {
return cooldownTicks;
}
public void setCooldownTicks(int cooldownTicks) {
this.cooldownTicks = Math.max(0, cooldownTicks);
}
public float getMinRange() {
return minRange;
}
public void setMinRange(float minRange) {
this.minRange = Math.max(0, minRange);
}
public float getMaxRange() {
return maxRange;
}
public void setMaxRange(float maxRange) {
this.maxRange = Math.max(0, maxRange);
}
/**
* Compute the allowed user type from child abilities.
* A chain allows players if ALL resolved entries allow players.
* A chain allows NPCs if ALL resolved entries allow NPCs.
* Unresolvable entries are skipped (don't restrict).
*/
public UserType getAllowedBy() {
if (entries.isEmpty()) return UserType.BOTH;
boolean allAllowPlayer = true;
boolean allAllowNpc = true;
for (ChainedAbilityEntry entry : entries) {
Ability a = entry.resolve();
if (a == null) continue;
UserType ut = a.getAllowedBy();
if (!ut.allowsPlayer()) allAllowPlayer = false;
if (!ut.allowsNpc()) allAllowNpc = false;
}
if (allAllowPlayer && allAllowNpc) return UserType.BOTH;
if (allAllowPlayer) return UserType.PLAYER_ONLY;
if (allAllowNpc) return UserType.NPC_ONLY;
return UserType.NPC_ONLY;
}
public List<Condition> getConditions() {
return conditions;
}
public void setConditions(List<Condition> conditions) {
this.conditions = conditions != null ? conditions : new ArrayList<>();
}
public List<ChainedAbilityEntry> getEntries() {
return entries;
}
public void setEntries(List<ChainedAbilityEntry> entries) {
this.entries = entries != null ? entries : new ArrayList<>();
}
public NBTTagCompound getCustomData() {
return customData;
}
// ═══════════════════════════════════════════════════════════════════
// IChainedAbility INTERFACE
// ═══════════════════════════════════════════════════════════════════
@Override
public int getEntryCount() {
return entries.size();
}
@Override
public String getEntryReference(int index) {
if (index < 0 || index >= entries.size()) return null;
ChainedAbilityEntry entry = entries.get(index);
if (entry.isInline()) {
Ability a = entry.getInlineAbility();
return a != null ? a.getName() : "";
}
return entry.getAbilityReference();
}
@Override
public boolean isEntryInline(int index) {
if (index < 0 || index >= entries.size()) return false;
return entries.get(index).isInline();
}
@Override
public int getEntryDelay(int index) {
if (index < 0 || index >= entries.size()) return 0;
return entries.get(index).getDelayTicks();
}
// ═══════════════════════════════════════════════════════════════════
// ENTRY MANAGEMENT
// ═══════════════════════════════════════════════════════════════════
public void addEntry(ChainedAbilityEntry entry) {
if (entry != null) {
entries.add(entry);
}
}
public void addEntry(String abilityReference, int delayTicks) {
entries.add(new ChainedAbilityEntry(abilityReference, delayTicks));
}
public void removeEntry(int index) {
if (index >= 0 && index < entries.size()) {
entries.remove(index);
}
}
public void moveEntryUp(int index) {
if (index > 0 && index < entries.size()) {
ChainedAbilityEntry entry = entries.remove(index);
entries.add(index - 1, entry);
}
}
public void moveEntryDown(int index) {
if (index >= 0 && index < entries.size() - 1) {
ChainedAbilityEntry entry = entries.remove(index);
entries.add(index + 1, entry);
}
}
// ═══════════════════════════════════════════════════════════════════
// CONDITIONS
// ═══════════════════════════════════════════════════════════════════
/**
* Check all chain-level conditions against the caster and target.
*/
public boolean checkConditions(EntityLivingBase caster, EntityLivingBase target) {
for (Condition c : conditions) {
if (!c.check(caster, target)) {
return false;
}
}
return true;
}
/**
* Check conditions for player use (skip target-requiring conditions).
*/
public boolean checkConditionsForPlayer(EntityLivingBase caster) {
for (Condition c : conditions) {
if (c.requiresTarget()) continue;
if (!c.check(caster, null)) {
return false;
}
}
return true;
}
// ═══════════════════════════════════════════════════════════════════
// VALIDATION
// ═══════════════════════════════════════════════════════════════════
/**
* Validate this chained ability. Returns a list of error messages.
* An empty list means valid.
*/
public List<String> validate() {
List<String> errors = new ArrayList<>();
if (name == null || name.isEmpty()) {
errors.add("Name is required");
}
if (entries.isEmpty()) {
errors.add("At least one ability entry is required");
}
AbilityController controller = AbilityController.Instance;
if (controller != null) {
for (int i = 0; i < entries.size(); i++) {
ChainedAbilityEntry entry = entries.get(i);
if (entry.isInline()) {
if (entry.getInlineAbility() == null) {
errors.add("Entry " + (i + 1) + ": inline ability is null");
}
} else {
String ref = entry.getAbilityReference();
if (ref == null || ref.isEmpty()) {
errors.add("Entry " + (i + 1) + ": ability reference is empty");
} else if (!controller.canResolveAbility(ref)) {
errors.add("Entry " + (i + 1) + ": cannot resolve ability '" + ref + "'");
}
}
}
}
return errors;
}
// ═══════════════════════════════════════════════════════════════════
// DEEP COPY
// ═══════════════════════════════════════════════════════════════════
/**
* Create a deep copy via NBT round-trip.
*/
public ChainedAbility deepCopy() {
ChainedAbility copy = new ChainedAbility();
copy.readNBT(this.writeNBT());
return copy;
}
@Override
public boolean isChain() {
return true;
}
@Override
public IAbilityAction deepCopyAction() {
return deepCopy();
}
// ═══════════════════════════════════════════════════════════════════
// NBT SERIALIZATION
// ═══════════════════════════════════════════════════════════════════
public NBTTagCompound writeNBT() {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("Id", id);
nbt.setString("Name", name);
nbt.setString("DisplayName", displayName);
nbt.setBoolean("Enabled", enabled);
nbt.setInteger("Weight", weight);
nbt.setBoolean("WindUpAll", windUpAll);
nbt.setInteger("CooldownTicks", cooldownTicks);
nbt.setFloat("MinRange", minRange);
nbt.setFloat("MaxRange", maxRange);
// Conditions
NBTTagList condList = new NBTTagList();
for (Condition c : conditions) {
condList.appendTag(c.writeNBT());
}
nbt.setTag("Conditions", condList);
// Entries
NBTTagList entryList = new NBTTagList();
for (ChainedAbilityEntry entry : entries) {
entryList.appendTag(entry.writeNBT());
}
nbt.setTag("Entries", entryList);
// Custom data (for external mods)
nbt.setTag("customData", (NBTTagCompound) customData.copy());
return nbt;
}
public void readNBT(NBTTagCompound nbt) {
id = nbt.getString("Id");
name = nbt.getString("Name");
displayName = nbt.getString("DisplayName");
enabled = !nbt.hasKey("Enabled") || nbt.getBoolean("Enabled");
weight = nbt.hasKey("Weight") ? nbt.getInteger("Weight") : 10;
windUpAll = !nbt.hasKey("WindUpAll") || nbt.getBoolean("WindUpAll");
cooldownTicks = nbt.hasKey("CooldownTicks") ? nbt.getInteger("CooldownTicks") : 100;
minRange = nbt.getFloat("MinRange");
maxRange = nbt.hasKey("MaxRange") ? nbt.getFloat("MaxRange") : 20;
// AllowedBy is computed from child abilities — old NBT data silently ignored
// Conditions
conditions.clear();
NBTTagList condList = nbt.getTagList("Conditions", 10);
for (int i = 0; i < condList.tagCount(); i++) {
Condition c = Condition.fromNBT(condList.getCompoundTagAt(i));
if (c != null) {
conditions.add(c);
}
}
// Entries
entries.clear();
NBTTagList entryList = nbt.getTagList("Entries", 10);
for (int i = 0; i < entryList.tagCount(); i++) {
ChainedAbilityEntry entry = ChainedAbilityEntry.fromNBT(entryList.getCompoundTagAt(i));
if (entry != null) {
entries.add(entry);
}
}
// Custom data (for external mods)
customData = nbt.getCompoundTag("customData");
}
}
@@ -0,0 +1,202 @@
package kamkeel.npcs.controllers.data.ability;
import kamkeel.npcs.controllers.AbilityController;
import net.minecraft.nbt.NBTTagCompound;
/**
* A single entry in a {@link ChainedAbility}, representing one ability
* to execute in sequence along with its delay configuration.
* <p>
* Entries can be either a <b>reference</b> to a registered ability (by name/key)
* or an <b>inline</b> ability that is owned directly by this entry.
*/
public class ChainedAbilityEntry {
public enum EntryType {REFERENCE, INLINE}
private EntryType entryType = EntryType.REFERENCE;
/**
* Reference key (built-in name or custom ability name). Used when entryType == REFERENCE.
*/
private String abilityReference = "";
/**
* Inline ability data. Used when entryType == INLINE.
*/
private Ability inlineAbility = null;
/**
* Delay in ticks BEFORE this ability starts (after previous entry completes).
*/
private int delayTicks = 0;
public ChainedAbilityEntry() {
}
public ChainedAbilityEntry(String abilityReference, int delayTicks) {
this.entryType = EntryType.REFERENCE;
this.abilityReference = abilityReference != null ? abilityReference : "";
this.delayTicks = Math.max(0, delayTicks);
}
// ═══════════════════════════════════════════════════════════════════
// FACTORY METHODS
// ═══════════════════════════════════════════════════════════════════
public static ChainedAbilityEntry reference(String ref, int delay) {
ChainedAbilityEntry entry = new ChainedAbilityEntry();
entry.entryType = EntryType.REFERENCE;
entry.abilityReference = ref != null ? ref : "";
entry.delayTicks = Math.max(0, delay);
return entry;
}
public static ChainedAbilityEntry inline(Ability ability, int delay) {
ChainedAbilityEntry entry = new ChainedAbilityEntry();
entry.entryType = EntryType.INLINE;
entry.inlineAbility = ability;
entry.delayTicks = Math.max(0, delay);
return entry;
}
// ═══════════════════════════════════════════════════════════════════
// TYPE QUERIES
// ═══════════════════════════════════════════════════════════════════
public EntryType getEntryType() {
return entryType;
}
public boolean isInline() {
return entryType == EntryType.INLINE;
}
public boolean isReference() {
return entryType == EntryType.REFERENCE;
}
// ═══════════════════════════════════════════════════════════════════
// GETTERS / SETTERS
// ═══════════════════════════════════════════════════════════════════
public String getAbilityReference() {
return abilityReference;
}
public void setAbilityReference(String abilityReference) {
this.abilityReference = abilityReference != null ? abilityReference : "";
}
public Ability getInlineAbility() {
return inlineAbility;
}
public void setInlineAbility(Ability ability) {
this.inlineAbility = ability;
}
public int getDelayTicks() {
return delayTicks;
}
public void setDelayTicks(int delayTicks) {
this.delayTicks = Math.max(0, delayTicks);
}
// ═══════════════════════════════════════════════════════════════════
// RESOLUTION
// ═══════════════════════════════════════════════════════════════════
/**
* Resolve this entry to an Ability instance.
* Inline entries return their ability directly.
* Reference entries resolve via AbilityController.
*/
public Ability resolve() {
if (entryType == EntryType.INLINE) {
return inlineAbility;
}
AbilityController ctrl = AbilityController.Instance;
if (ctrl == null) return null;
return ctrl.resolveAbility(abilityReference);
}
// ═══════════════════════════════════════════════════════════════════
// CONVERSION
// ═══════════════════════════════════════════════════════════════════
/**
* Convert a reference entry to inline by cloning the resolved ability.
* Returns false if already inline or the reference cannot be resolved.
*/
public boolean convertToInline() {
if (entryType != EntryType.REFERENCE) return false;
AbilityController ctrl = AbilityController.Instance;
if (ctrl == null) return false;
Ability resolved = ctrl.resolveAbility(abilityReference);
if (resolved == null) return false;
inlineAbility = ctrl.fromNBT(resolved.writeNBT());
entryType = EntryType.INLINE;
abilityReference = "";
return true;
}
// ═══════════════════════════════════════════════════════════════════
// DEEP COPY
// ═══════════════════════════════════════════════════════════════════
/**
* Create a deep copy of this entry. Inline abilities are copied via NBT round-trip.
*/
public ChainedAbilityEntry deepCopy() {
ChainedAbilityEntry copy = new ChainedAbilityEntry();
copy.entryType = this.entryType;
copy.abilityReference = this.abilityReference;
copy.delayTicks = this.delayTicks;
if (this.inlineAbility != null) {
copy.inlineAbility = AbilityController.Instance.fromNBT(this.inlineAbility.writeNBT());
}
return copy;
}
// ═══════════════════════════════════════════════════════════════════
// NBT (backwards compatible)
// ═══════════════════════════════════════════════════════════════════
public NBTTagCompound writeNBT() {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setInteger("Delay", delayTicks);
if (entryType == EntryType.INLINE && inlineAbility != null) {
nbt.setTag("InlineAbility", inlineAbility.writeNBT());
} else {
nbt.setString("Reference", abilityReference);
}
return nbt;
}
public static ChainedAbilityEntry fromNBT(NBTTagCompound nbt) {
if (nbt == null) return null;
ChainedAbilityEntry entry = new ChainedAbilityEntry();
entry.delayTicks = Math.max(0, nbt.getInteger("Delay"));
// Inline entry (new format)
if (nbt.hasKey("InlineAbility")) {
NBTTagCompound abilityNBT = nbt.getCompoundTag("InlineAbility");
entry.entryType = EntryType.INLINE;
entry.inlineAbility = AbilityController.Instance != null
? AbilityController.Instance.fromNBT(abilityNBT) : null;
} else {
// Reference entry (old + new format)
entry.entryType = EntryType.REFERENCE;
entry.abilityReference = nbt.getString("Reference");
}
return entry;
}
}
@@ -0,0 +1,50 @@
package kamkeel.npcs.controllers.data.ability;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import java.util.List;
/**
* Common interface for all combat actions that can be assigned to NPCs or players.
* Implemented by {@link Ability} (individual ability) and {@link ChainedAbility} (sequence).
* <p>
* This enables unified storage via {@link AbilityAction}, unified weighted random selection,
* and unified eligibility checking without instanceof or separate codepaths.
*/
public interface IAbilityAction {
String getName();
boolean isEnabled();
void setEnabled(boolean enabled);
int getWeight();
int getCooldownTicks();
float getMinRange();
float getMaxRange();
UserType getAllowedBy();
List<Condition> getConditions();
boolean checkConditions(EntityLivingBase caster, EntityLivingBase target);
boolean checkConditionsForPlayer(EntityLivingBase caster);
/**
* Whether this action is a chained ability (sequence of abilities) vs an individual ability.
*/
boolean isChain();
/**
* Create a deep copy of this action.
*/
IAbilityAction deepCopyAction();
NBTTagCompound writeNBT();
}
@@ -0,0 +1,25 @@
package kamkeel.npcs.controllers.data.ability;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.List;
/**
* External field definition provider for chained abilities.
* Called during GUI initialization to allow mods to inject custom tabs/fields
* into the chained ability configuration GUI.
* <p>
* Note: Although this interface references FieldDef (client-only), it is safe to
* register on both sides because the generic type is erased at bytecode level.
* The addFieldDefinitions method is only ever called from client-side GUI code.
*/
public interface IChainedAbilityFieldProvider {
/**
* Add field definitions to the chained ability's GUI.
* Fields added here should use .tab() to assign them to a custom tab.
*
* @param chain The chained ability being configured
* @param defs The mutable list of field definitions to add to
*/
void addFieldDefinitions(ChainedAbility chain, List<FieldDef> defs);
}
@@ -9,5 +9,6 @@ import net.minecraft.entity.Entity;
*/
public interface PreviewEntityHandler {
void onEntitySpawned(Entity entity);
void onEntityRemoved(Entity entity);
}
@@ -27,11 +27,16 @@ public enum TargetingMode {
@Override
public String toString() {
switch (this) {
case AGGRO_TARGET: return "ability.target.aggro_target";
case SELF: return "ability.target.self";
case AOE_SELF: return "ability.target.aoe_self";
case AOE_TARGET: return "ability.target.aoe_target";
default: return name();
case AGGRO_TARGET:
return "ability.target.aggro_target";
case SELF:
return "ability.target.self";
case AOE_SELF:
return "ability.target.aoe_self";
case AOE_TARGET:
return "ability.target.aoe_target";
default:
return name();
}
}
}
@@ -0,0 +1,40 @@
package kamkeel.npcs.controllers.data.ability;
/**
* Tracks the state of a single active toggle for an entity.
* Stored in AbstractDataAbilities.activeToggles map.
* <p>
* State is 1-based: state 1 = first ON state, state 2 = second, etc.
* State 0 means OFF (entry should not exist in the map).
*/
public class ToggleEntry {
private final Ability ability;
private int tickCount;
private int state;
public ToggleEntry(Ability ability, int state) {
this.ability = ability;
this.tickCount = 0;
this.state = Math.max(1, state);
}
public Ability getAbility() {
return ability;
}
public int getTickCount() {
return tickCount;
}
public void incrementTick() {
tickCount++;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = Math.max(1, state);
}
}
@@ -22,7 +22,9 @@ public class EnergyBarrierData {
// Knockback settings
public boolean knockbackEnabled = false;
public float knockbackStrength = 1.0f;
/** 0=Both, 1=Player only, 2=NPC only */
/**
* 0=Both, 1=Player only, 2=NPC only
*/
public int knockbackTarget = 0;
// Melee settings
@@ -36,7 +38,8 @@ public class EnergyBarrierData {
*/
private Map<String, Float> damageMultipliers = new HashMap<>();
public EnergyBarrierData() {}
public EnergyBarrierData() {
}
public EnergyBarrierData(float maxHealth, boolean useHealth, int durationTicks, boolean useDuration) {
this.maxHealth = maxHealth;
@@ -70,23 +73,50 @@ public class EnergyBarrierData {
// ==================== GETTERS & SETTERS ====================
public float getMaxHealth() { return maxHealth; }
public void setMaxHealth(float maxHealth) { this.maxHealth = Math.max(0, maxHealth); }
public float getMaxHealth() {
return maxHealth;
}
public boolean isUseHealth() { return useHealth; }
public void setUseHealth(boolean useHealth) { this.useHealth = useHealth; }
public void setMaxHealth(float maxHealth) {
this.maxHealth = Math.max(0, maxHealth);
}
public int getDurationTicks() { return durationTicks; }
public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); }
public boolean isUseHealth() {
return useHealth;
}
public boolean isUseDuration() { return useDuration; }
public void setUseDuration(boolean useDuration) { this.useDuration = useDuration; }
public void setUseHealth(boolean useHealth) {
this.useHealth = useHealth;
}
public float getDefaultMultiplier() { return defaultMultiplier; }
public void setDefaultMultiplier(float defaultMultiplier) { this.defaultMultiplier = defaultMultiplier; }
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 static String[] getKnockbackTargetKeys() {
return KNOCKBACK_TARGET_KEYS;
}
public String getKnockbackTargetKey() {
return KNOCKBACK_TARGET_KEYS[Math.max(0, Math.min(knockbackTarget, 2))];
@@ -15,7 +15,8 @@ public class EnergyCombatData implements IEnergyCombatData {
public float explosionRadius = 3.0f;
public float explosionDamageFalloff = 0.5f;
public EnergyCombatData() {}
public EnergyCombatData() {
}
public EnergyCombatData(float damage, float knockback, float knockbackUp,
boolean explosive, float explosionRadius, float explosionDamageFalloff) {
@@ -20,7 +20,8 @@ public class EnergyDisplayData implements IEnergyDisplayData {
// public boolean skyboxEnabled = false;
// public String skyboxTexture = "";
public EnergyDisplayData() {}
public EnergyDisplayData() {
}
public EnergyDisplayData(int innerColor, int outerColor) {
this.innerColor = innerColor;
@@ -14,7 +14,8 @@ public class EnergyHomingData implements IEnergyHomingData {
public float homingStrength = 0.15f;
public float homingRange = 20.0f;
public EnergyHomingData() {}
public EnergyHomingData() {
}
public EnergyHomingData(float speed, boolean homing, float homingStrength, float homingRange) {
this.speed = speed;
@@ -8,10 +8,11 @@ import noppes.npcs.api.ability.data.IEnergyLifespanData;
* Used as a parameter object for entity constructors and ability configuration.
*/
public class EnergyLifespanData implements IEnergyLifespanData {
public float maxDistance = 30.0f;
public float maxDistance = 150.0f;
public int maxLifetime = 200;
public EnergyLifespanData() {}
public EnergyLifespanData() {
}
public EnergyLifespanData(float maxDistance, int maxLifetime) {
this.maxDistance = maxDistance;
@@ -13,7 +13,8 @@ public class EnergyLightningData implements IEnergyLightningData {
public float lightningRadius = 0.5f;
public int lightningFadeTime = 6;
public EnergyLightningData() {}
public EnergyLightningData() {
}
public EnergyLightningData(boolean lightningEffect, float lightningDensity,
float lightningRadius, int lightningFadeTime) {
@@ -17,7 +17,8 @@ public class EnergyPanelData {
public float launchDamage = 8.0f;
public float launchKnockback = 2.0f;
public EnergyPanelData() {}
public EnergyPanelData() {
}
public EnergyPanelData(float panelWidth, float panelHeight, float heightOffset) {
this.panelWidth = panelWidth;
@@ -27,26 +28,61 @@ public class EnergyPanelData {
// ==================== GETTERS & SETTERS ====================
public float getPanelWidth() { return panelWidth; }
public void setPanelWidth(float panelWidth) { this.panelWidth = Math.max(0.5f, panelWidth); }
public float getPanelWidth() {
return panelWidth;
}
public float getPanelHeight() { return panelHeight; }
public void setPanelHeight(float panelHeight) { this.panelHeight = Math.max(0.5f, panelHeight); }
public void setPanelWidth(float panelWidth) {
this.panelWidth = Math.max(0.5f, panelWidth);
}
public float getHeightOffset() { return heightOffset; }
public void setHeightOffset(float heightOffset) { this.heightOffset = heightOffset; }
public float getPanelHeight() {
return panelHeight;
}
public boolean isLaunching() { return launching; }
public void setLaunching(boolean launching) { this.launching = launching; }
public void setPanelHeight(float panelHeight) {
this.panelHeight = Math.max(0.5f, panelHeight);
}
public float getLaunchSpeed() { return launchSpeed; }
public void setLaunchSpeed(float launchSpeed) { this.launchSpeed = Math.max(0.1f, launchSpeed); }
public float getHeightOffset() {
return heightOffset;
}
public float getLaunchDamage() { return launchDamage; }
public void setLaunchDamage(float launchDamage) { this.launchDamage = Math.max(0, launchDamage); }
public void setHeightOffset(float heightOffset) {
this.heightOffset = heightOffset;
}
public float getLaunchKnockback() { return launchKnockback; }
public void setLaunchKnockback(float launchKnockback) { this.launchKnockback = Math.max(0, launchKnockback); }
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 ====================
@@ -15,7 +15,8 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData {
public int currentPath = 0;
private final List<Path> trajectory = new ArrayList<>();
public EnergyTrajectoryData() {}
public EnergyTrajectoryData() {
}
public EnergyTrajectoryData(int startingPath, Path... paths) {
trajectory.addAll(Arrays.asList(paths));
@@ -85,6 +86,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData {
IPath p = getPath(path);
return p == null ? 0 : p.getZ();
}
@Override
public void setX(int path, double x) {
IPath p = getPath(path);
@@ -126,8 +128,8 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData {
@Override
public void setPath(int path, double x, double y, double z, int delay) {
Path newPath = new Path(
NpcAPI.Instance().getIPos(x, y, z),
delay
NpcAPI.Instance().getIPos(x, y, z),
delay
);
while (trajectory.size() <= path) {
@@ -196,7 +198,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData {
}
}
public static class Path implements IEnergyTrajectoryData.IPath{
public static class Path implements IEnergyTrajectoryData.IPath {
public IPos pos;
public int delayTicks;
public boolean concluded;
@@ -199,5 +199,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
return super.handleTextFieldEvent(textFieldId, field);
}
public Map<Integer, int[]> getEffectWidgetMeta() { return effectWidgetMeta; }
public Map<Integer, int[]> getEffectWidgetMeta() {
return effectWidgetMeta;
}
}
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
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;
@@ -11,10 +12,8 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityCharge;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.Arrays;
import java.util.HashSet;
@@ -172,6 +171,12 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge {
hitEntities.clear();
}
@Override
public void resetForBurst() {
super.resetForBurst();
hitEntities.clear();
}
@Override
public float getTelegraphLength() {
return maxRange;
@@ -1,8 +1,11 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
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;
@@ -11,15 +14,8 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityCutter;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.Arrays;
import java.util.HashSet;
@@ -40,9 +36,12 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
@Override
public String toString() {
switch (this) {
case SWIPE: return "ability.sweep.swipe";
case SPIN: return "ability.sweep.spin";
default: return name();
case SWIPE:
return "ability.sweep.swipe";
case SPIN:
return "ability.sweep.spin";
default:
return name();
}
}
}
@@ -232,7 +231,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
* For SWIPE mode, this covers the continuous arc from startAngle to endAngle.
*/
private void performSweepDamageRange(EntityLivingBase caster, World world, float minDist, float maxDist,
float startAngle, float endAngle) {
float startAngle, float endAngle) {
// Calculate the angular range covered this tick
float minAngle = Math.min(startAngle, endAngle);
float maxAngle = Math.max(startAngle, endAngle);
@@ -8,9 +8,8 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.api.ability.type.IAbilityDash;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.Arrays;
import java.util.List;
@@ -32,9 +31,12 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash {
@Override
public String toString() {
switch (this) {
case AGGRESSIVE: return "ability.dash.aggressive";
case DEFENSIVE: return "ability.dash.defensive";
default: return name();
case AGGRESSIVE:
return "ability.dash.aggressive";
case DEFENSIVE:
return "ability.dash.defensive";
default:
return name();
}
}
}
@@ -194,6 +196,12 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash {
chosenDirection = null;
}
@Override
public void resetForBurst() {
super.resetForBurst();
chosenDirection = null;
}
@Override
public int getMaxPreviewDuration() {
return (int) Math.ceil(dashDistance / dashSpeed) + 5;
@@ -1,25 +1,26 @@
package kamkeel.npcs.controllers.data.ability.type;
import kamkeel.npcs.controllers.data.ability.*;
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.AbilityPhase;
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.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
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.api.ability.type.IAbilityGuard;
import noppes.npcs.api.handler.data.IAnimation;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.PlayerData;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityGuard;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import noppes.npcs.util.ValueUtil;
import java.util.Arrays;
@@ -77,9 +78,12 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
@Override
public String toString() {
switch (this) {
case FLAT: return "ability.counter.flat";
case PERCENT: return "ability.counter.percent";
default: return name();
case FLAT:
return "ability.counter.flat";
case PERCENT:
return "ability.counter.percent";
default:
return name();
}
}
}
@@ -1,19 +1,15 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.entity.EntityAbilityZone;
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.ability.type.IAbilityHazard;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.ArrayList;
import java.util.Arrays;
@@ -106,20 +102,45 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
this.affectsCaster = nbt.getBoolean("affectsCaster");
}
public float getRadius() { return radius; }
public void setRadius(float radius) { this.radius = radius; }
public float getRadius() {
return radius;
}
public float getDamagePerSecond() { return damagePerSecond; }
public void setDamagePerSecond(float damagePerSecond) { this.damagePerSecond = damagePerSecond; }
public void setRadius(float radius) {
this.radius = radius;
}
public int getDamageInterval() { return damageInterval; }
public void setDamageInterval(int damageInterval) { this.damageInterval = damageInterval; }
public float getDamagePerSecond() {
return damagePerSecond;
}
public boolean isIgnoreInvulnFrames() { return ignoreInvulnFrames; }
public void setIgnoreInvulnFrames(boolean ignoreInvulnFrames) { this.ignoreInvulnFrames = ignoreInvulnFrames; }
public void setDamagePerSecond(float damagePerSecond) {
this.damagePerSecond = damagePerSecond;
}
public boolean isAffectsCaster() { return affectsCaster; }
public void setAffectsCaster(boolean affectsCaster) { this.affectsCaster = affectsCaster; }
public int getDamageInterval() {
return damageInterval;
}
public void setDamageInterval(int damageInterval) {
this.damageInterval = damageInterval;
}
public boolean isIgnoreInvulnFrames() {
return ignoreInvulnFrames;
}
public void setIgnoreInvulnFrames(boolean ignoreInvulnFrames) {
this.ignoreInvulnFrames = ignoreInvulnFrames;
}
public boolean isAffectsCaster() {
return affectsCaster;
}
public void setAffectsCaster(boolean affectsCaster) {
this.affectsCaster = affectsCaster;
}
@SideOnly(Side.CLIENT)
@Override
@@ -1,5 +1,7 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
@@ -9,13 +11,9 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.api.ability.type.IAbilityHeal;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.Arrays;
@@ -5,13 +5,13 @@ 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.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityHeavyHit;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.Arrays;
import java.util.List;
@@ -53,6 +53,12 @@ public abstract class AbilityMovement extends Ability {
maxActiveTicks = 0;
}
@Override
public void resetForBurst() {
movementDirection = null;
maxActiveTicks = 0;
}
// ═══════════════════════════════════════════════════════════════════
// DIRECTION HELPERS
// ═══════════════════════════════════════════════════════════════════
@@ -5,18 +5,15 @@ 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.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityProjectile;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import java.util.Arrays;
import java.util.List;
@@ -94,7 +91,11 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
double dy = (target.posY + target.height / 2) - (caster.posY + caster.getEyeHeight());
double dz = target.posZ - caster.posZ;
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len > 0) { dx /= len; dy /= len; dz /= len; }
if (len > 0) {
dx /= len;
dy /= len;
dz /= len;
}
applyAbilityDamageWithDirection(caster, target, damage, knockback, dx, dz);
world.playSoundAtEntity(caster, "random.bow", 1.0f, 0.8f);
@@ -167,7 +168,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
* Apply explosion splash damage around a point.
*/
private void applyExplosionDamage(EntityLivingBase caster, EntityLivingBase primaryTarget, World world,
double x, double y, double z) {
double x, double y, double z) {
@SuppressWarnings("unchecked")
List<Entity> entities = world.getEntitiesWithinAABBExcludingEntity(primaryTarget,
primaryTarget.boundingBox.expand(explosionRadius, explosionRadius, explosionRadius));
@@ -5,16 +5,13 @@ 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.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityShockwave;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.Arrays;
import java.util.List;
@@ -5,6 +5,7 @@ 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.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -13,13 +14,10 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.api.ability.type.IAbilitySlam;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilitySlam;
import somehussar.gui.annotationHandling.GuiEditable;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import java.util.Arrays;
import java.util.List;
@@ -1,9 +1,12 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.UserType;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
@@ -13,15 +16,8 @@ import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityTeleport;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.Arrays;
import java.util.List;
@@ -43,10 +39,14 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
@Override
public String toString() {
switch (this) {
case BLINK: return "ability.teleport.blink";
case BEHIND: return "ability.teleport.behind";
case SINGLE: return "ability.teleport.single";
default: return name();
case BLINK:
return "ability.teleport.blink";
case BEHIND:
return "ability.teleport.behind";
case SINGLE:
return "ability.teleport.single";
default:
return name();
}
}
}
@@ -1,5 +1,7 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -12,9 +14,6 @@ import noppes.npcs.client.gui.SubGuiTrapPresetSelector;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.constants.EnumPotionType;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -115,51 +114,80 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
switch (preset) {
case "VENOM":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:mobSpell";
colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611;
windUpColor = 0x6044DD44; activeColor = 0xC044FF44;
particles = true;
particleDensity = 0.3f;
particleScale = 0.5f;
particleMotion = 1;
particleDir = "mc:mobSpell";
colorData.innerColor = 0x44DD44;
colorData.outerColor = 0x116611;
windUpColor = 0x6044DD44;
activeColor = 0xC044FF44;
effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0));
break;
case "EXPLOSIVE":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.6f;
particleMotion = 0; particleDir = "mc:smoke";
colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200;
windUpColor = 0x60FF6611; activeColor = 0xC0FF4400;
particles = true;
particleDensity = 0.3f;
particleScale = 0.6f;
particleMotion = 0;
particleDir = "mc:smoke";
colorData.innerColor = 0xFF6611;
colorData.outerColor = 0xCC2200;
windUpColor = 0x60FF6611;
activeColor = 0xC0FF4400;
effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0));
break;
case "CURSED":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:portal";
colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB;
windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF;
particles = true;
particleDensity = 0.3f;
particleScale = 0.5f;
particleMotion = 1;
particleDir = "mc:portal";
colorData.innerColor = 0xAA44FF;
colorData.outerColor = 0x6622BB;
windUpColor = 0x60AA44FF;
activeColor = 0xC0CC66FF;
effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0));
break;
case "SHOCK":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.4f;
particleMotion = 2; particleDir = "mc:enchantmenttable";
colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB;
windUpColor = 0x604488FF; activeColor = 0xC066AAFF;
particles = true;
particleDensity = 0.3f;
particleScale = 0.4f;
particleMotion = 2;
particleDir = "mc:enchantmenttable";
colorData.innerColor = 0x4488FF;
colorData.outerColor = 0x2244BB;
windUpColor = 0x604488FF;
activeColor = 0xC066AAFF;
effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1));
break;
case "SNARE":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:snowshovel";
colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC;
windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF;
particles = true;
particleDensity = 0.3f;
particleScale = 0.5f;
particleMotion = 1;
particleDir = "mc:snowshovel";
colorData.innerColor = 0x88CCFF;
colorData.outerColor = 0x4488CC;
windUpColor = 0x6088CCFF;
activeColor = 0xC0AADDFF;
effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1));
break;
case "HIDDEN":
default:
groundAlpha = 0.03f;
particles = false; particleDensity = 0.0f; particleScale = 1.0f;
particles = false;
particleDensity = 0.0f;
particleScale = 1.0f;
particleMotion = 0;
colorData.innerColor = 0x888888; colorData.outerColor = 0x444444;
windUpColor = 0x60888888; activeColor = 0xC0AAAAAA;
colorData.innerColor = 0x888888;
colorData.outerColor = 0x444444;
windUpColor = 0x60888888;
activeColor = 0xC0AAAAAA;
break;
}
}
@@ -190,22 +218,69 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
this.visible = nbt.getBoolean("visible");
}
public float getTriggerRadius() { return triggerRadius; }
public void setTriggerRadius(float triggerRadius) { this.triggerRadius = triggerRadius; }
public int getArmTime() { return armTime; }
public void setArmTime(int armTime) { this.armTime = armTime; }
public int getMaxTriggers() { return maxTriggers; }
public void setMaxTriggers(int maxTriggers) { this.maxTriggers = maxTriggers; }
public int getTriggerCooldown() { return triggerCooldown; }
public void setTriggerCooldown(int triggerCooldown) { this.triggerCooldown = triggerCooldown; }
public float getDamage() { return damage; }
public void setDamage(float damage) { this.damage = damage; }
public float getDamageRadius() { return damageRadius; }
public void setDamageRadius(float damageRadius) { this.damageRadius = damageRadius; }
public float getKnockback() { return knockback; }
public void setKnockback(float knockback) { this.knockback = knockback; }
public boolean isVisible() { return visible; }
public void setVisible(boolean visible) { this.visible = visible; }
public float getTriggerRadius() {
return triggerRadius;
}
public void setTriggerRadius(float triggerRadius) {
this.triggerRadius = triggerRadius;
}
public int getArmTime() {
return armTime;
}
public void setArmTime(int armTime) {
this.armTime = armTime;
}
public int getMaxTriggers() {
return maxTriggers;
}
public void setMaxTriggers(int maxTriggers) {
this.maxTriggers = maxTriggers;
}
public int getTriggerCooldown() {
return triggerCooldown;
}
public void setTriggerCooldown(int triggerCooldown) {
this.triggerCooldown = triggerCooldown;
}
public float getDamage() {
return damage;
}
public void setDamage(float damage) {
this.damage = damage;
}
public float getDamageRadius() {
return damageRadius;
}
public void setDamageRadius(float damageRadius) {
this.damageRadius = damageRadius;
}
public float getKnockback() {
return knockback;
}
public void setKnockback(float knockback) {
this.knockback = knockback;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
@SideOnly(Side.CLIENT)
@Override
@@ -1,21 +1,18 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityVortex;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.Arrays;
import java.util.HashSet;
@@ -1,5 +1,7 @@
package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
@@ -14,15 +16,10 @@ import kamkeel.npcs.entity.EntityAbilityZone.ParticleMotion;
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.constants.EnumPotionType;
import net.minecraft.util.DamageSource;
import noppes.npcs.client.gui.SubGuiZonePresetSelector;
import noppes.npcs.client.gui.builder.FieldDef;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import noppes.npcs.constants.EnumPotionType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -213,57 +210,134 @@ public abstract class AbilityZone extends Ability {
particleDir = "";
switch (styleName) {
case "TOXIC":
groundFill = true; groundAlpha = 0.30f; rings = true; ringCount = 3;
border = false; borderSpeed = 1.0f; accents = true; accentStyle = 1;
lightning = false; particles = true; particleDensity = 1.5f; particleScale = 0.8f;
particleMotion = 1; particleGlow = true; particleDir = "mc:mobSpell";
colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611;
windUpColor = 0x6044DD44; activeColor = 0xC044FF44;
groundFill = true;
groundAlpha = 0.30f;
rings = true;
ringCount = 3;
border = false;
borderSpeed = 1.0f;
accents = true;
accentStyle = 1;
lightning = false;
particles = true;
particleDensity = 1.5f;
particleScale = 0.8f;
particleMotion = 1;
particleGlow = true;
particleDir = "mc:mobSpell";
colorData.innerColor = 0x44DD44;
colorData.outerColor = 0x116611;
windUpColor = 0x6044DD44;
activeColor = 0xC044FF44;
effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0));
break;
case "INFERNO":
groundFill = true; groundAlpha = 0.35f; rings = true; ringCount = 3;
border = true; borderSpeed = 2.0f; accents = true; accentStyle = 2;
lightning = false; particles = true; particleDensity = 2.0f; particleScale = 1.2f;
particleMotion = 0; particleGlow = true; particleDir = "mc:flame";
colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200;
windUpColor = 0x60FF6611; activeColor = 0xC0FF4400;
groundFill = true;
groundAlpha = 0.35f;
rings = true;
ringCount = 3;
border = true;
borderSpeed = 2.0f;
accents = true;
accentStyle = 2;
lightning = false;
particles = true;
particleDensity = 2.0f;
particleScale = 1.2f;
particleMotion = 0;
particleGlow = true;
particleDir = "mc:flame";
colorData.innerColor = 0xFF6611;
colorData.outerColor = 0xCC2200;
windUpColor = 0x60FF6611;
activeColor = 0xC0FF4400;
effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0));
break;
case "ARCANE":
groundFill = true; groundAlpha = 0.20f; rings = true; ringCount = 1;
border = true; borderSpeed = 0.8f; accents = true; accentStyle = 0;
lightning = false; particles = true; particleDensity = 1.0f; particleScale = 1.0f;
particleMotion = 1; particleGlow = true; particleDir = "mc:portal";
colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB;
windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF;
groundFill = true;
groundAlpha = 0.20f;
rings = true;
ringCount = 1;
border = true;
borderSpeed = 0.8f;
accents = true;
accentStyle = 0;
lightning = false;
particles = true;
particleDensity = 1.0f;
particleScale = 1.0f;
particleMotion = 1;
particleGlow = true;
particleDir = "mc:portal";
colorData.innerColor = 0xAA44FF;
colorData.outerColor = 0x6622BB;
windUpColor = 0x60AA44FF;
activeColor = 0xC0CC66FF;
effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0));
break;
case "ELECTRIC":
groundFill = true; groundAlpha = 0.15f; rings = false; ringCount = 1;
border = false; borderSpeed = 1.0f; accents = false; accentStyle = 0;
lightning = true; particles = true; particleDensity = 0.8f; particleScale = 0.6f;
particleMotion = 2; particleGlow = true; particleDir = "mc:enchantmenttable";
colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB;
windUpColor = 0x604488FF; activeColor = 0xC066AAFF;
groundFill = true;
groundAlpha = 0.15f;
rings = false;
ringCount = 1;
border = false;
borderSpeed = 1.0f;
accents = false;
accentStyle = 0;
lightning = true;
particles = true;
particleDensity = 0.8f;
particleScale = 0.6f;
particleMotion = 2;
particleGlow = true;
particleDir = "mc:enchantmenttable";
colorData.innerColor = 0x4488FF;
colorData.outerColor = 0x2244BB;
windUpColor = 0x604488FF;
activeColor = 0xC066AAFF;
effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1));
break;
case "FROST":
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
border = true; borderSpeed = 0.3f; accents = true; accentStyle = 0;
lightning = false; particles = true; particleDensity = 1.5f; particleScale = 1.0f;
particleMotion = 1; particleGlow = true; particleDir = "mc:snowshovel";
colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC;
windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF;
groundFill = true;
groundAlpha = 0.25f;
rings = true;
ringCount = 3;
border = true;
borderSpeed = 0.3f;
accents = true;
accentStyle = 0;
lightning = false;
particles = true;
particleDensity = 1.5f;
particleScale = 1.0f;
particleMotion = 1;
particleGlow = true;
particleDir = "mc:snowshovel";
colorData.innerColor = 0x88CCFF;
colorData.outerColor = 0x4488CC;
windUpColor = 0x6088CCFF;
activeColor = 0xC0AADDFF;
effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1));
break;
case "DEFAULT":
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
border = true; borderSpeed = 1.0f; accents = true; accentStyle = 0;
lightning = false; particles = false; particleDensity = 1.0f; particleScale = 1.0f;
particleMotion = 0; particleGlow = true;
colorData.innerColor = 0xCCCCCC; colorData.outerColor = 0x666666;
windUpColor = 0x60CCCCCC; activeColor = 0xC0FFFFFF;
groundFill = true;
groundAlpha = 0.25f;
rings = true;
ringCount = 3;
border = true;
borderSpeed = 1.0f;
accents = true;
accentStyle = 0;
lightning = false;
particles = false;
particleDensity = 1.0f;
particleScale = 1.0f;
particleMotion = 0;
particleGlow = true;
colorData.innerColor = 0xCCCCCC;
colorData.outerColor = 0x666666;
windUpColor = 0x60CCCCCC;
activeColor = 0xC0FFFFFF;
break;
default:
break;
@@ -310,8 +384,11 @@ public abstract class AbilityZone extends Ability {
protected void readZoneNBT(NBTTagCompound nbt, int defaultDuration) {
this.durationTicks = nbt.getInteger("durationTicks");
try { this.zoneShape = ZoneShape.valueOf(nbt.getString("zoneShape")); }
catch (Exception e) { this.zoneShape = ZoneShape.CIRCLE; }
try {
this.zoneShape = ZoneShape.valueOf(nbt.getString("zoneShape"));
} catch (Exception e) {
this.zoneShape = ZoneShape.CIRCLE;
}
this.spawnRadius = nbt.getFloat("spawnRadius");
this.zoneCount = nbt.getInteger("zoneCount");
this.zoneHeight = nbt.getFloat("zoneHeight");
@@ -337,73 +414,238 @@ public abstract class AbilityZone extends Ability {
this.particleGlow = nbt.getBoolean("particleGlow");
}
public int getDurationTicks() { return durationTicks; }
public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); }
public int getDurationTicks() {
return durationTicks;
}
public ZoneShape getZoneShapeEnum() { return zoneShape; }
public void setZoneShapeEnum(ZoneShape shape) { this.zoneShape = shape; }
public void setDurationTicks(int durationTicks) {
this.durationTicks = Math.max(1, durationTicks);
}
public ZoneShape getZoneShapeEnum() {
return zoneShape;
}
public void setZoneShapeEnum(ZoneShape shape) {
this.zoneShape = shape;
}
public int getZoneShapeOrdinal() {
return zoneShape.ordinal();
}
public int getZoneShapeOrdinal() { return zoneShape.ordinal(); }
public void setZoneShapeOrdinal(int shape) {
ZoneShape[] values = ZoneShape.values();
this.zoneShape = shape >= 0 && shape < values.length ? values[shape] : ZoneShape.CIRCLE;
}
public float getSpawnRadius() { return spawnRadius; }
public void setSpawnRadius(float spawnRadius) { this.spawnRadius = Math.max(0, spawnRadius); }
public int getZoneCount() { return zoneCount; }
public void setZoneCount(int zoneCount) { this.zoneCount = Math.max(1, zoneCount); }
public float getZoneHeight() { return zoneHeight; }
public void setZoneHeight(float zoneHeight) { this.zoneHeight = Math.max(0.5f, zoneHeight); }
public float getSpawnRadius() {
return spawnRadius;
}
public float getParticleDensity() { return particleDensity; }
public void setParticleDensity(float v) { this.particleDensity = Math.max(0, Math.min(5, v)); }
public float getParticleScale() { return particleScale; }
public void setParticleScale(float v) { this.particleScale = Math.max(0.1f, Math.min(5, v)); }
public float getAnimSpeed() { return animSpeed; }
public void setAnimSpeed(float v) { this.animSpeed = Math.max(0.1f, Math.min(5, v)); }
public float getLightningDensity() { return lightningDensity; }
public void setLightningDensity(float v) { this.lightningDensity = Math.max(0, Math.min(3, v)); }
public void setSpawnRadius(float spawnRadius) {
this.spawnRadius = Math.max(0, spawnRadius);
}
public int getInnerColor() { return colorData.innerColor; }
public void setInnerColor(int color) { colorData.innerColor = color; }
public int getOuterColor() { return colorData.outerColor; }
public void setOuterColor(int color) { colorData.outerColor = color; }
public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
public void setOuterColorEnabled(boolean enabled) { colorData.outerColorEnabled = enabled; }
public int getZoneCount() {
return zoneCount;
}
public boolean isGroundFill() { return groundFill; }
public void setGroundFill(boolean v) { this.groundFill = v; }
public float getGroundAlpha() { return groundAlpha; }
public void setGroundAlpha(float v) { this.groundAlpha = Math.max(0, Math.min(1, v)); }
public boolean isRings() { return rings; }
public void setRings(boolean v) { this.rings = v; }
public int getRingCount() { return ringCount; }
public void setRingCount(int v) { this.ringCount = Math.max(1, Math.min(5, v)); }
public boolean isBorder() { return border; }
public void setBorder(boolean v) { this.border = v; }
public float getBorderSpeed() { return borderSpeed; }
public void setBorderSpeed(float v) { this.borderSpeed = Math.max(0.1f, Math.min(5, v)); }
public boolean isAccents() { return accents; }
public void setAccents(boolean v) { this.accents = v; }
public int getAccentStyle() { return accentStyle; }
public void setAccentStyle(int v) { this.accentStyle = Math.max(0, Math.min(AccentStyle.values().length - 1, v)); }
public AccentStyle getAccentStyleEnum() { return AccentStyle.values()[Math.max(0, Math.min(accentStyle, AccentStyle.values().length - 1))]; }
public void setAccentStyleEnum(AccentStyle s) { this.accentStyle = s.ordinal(); }
public boolean isLightning() { return lightning; }
public void setLightning(boolean v) { this.lightning = v; }
public boolean isParticles() { return particles; }
public void setParticles(boolean v) { this.particles = v; }
public int getParticleMotion() { return particleMotion; }
public void setParticleMotion(int v) { this.particleMotion = Math.max(0, Math.min(ParticleMotion.values().length - 1, v)); }
public ParticleMotion getParticleMotionEnum() { return ParticleMotion.values()[Math.max(0, Math.min(particleMotion, ParticleMotion.values().length - 1))]; }
public void setParticleMotionEnum(ParticleMotion m) { this.particleMotion = m.ordinal(); }
public String getParticleDir() { return particleDir; }
public void setParticleDir(String v) { this.particleDir = v != null ? v : ""; }
public int getParticleSize() { return particleSize; }
public void setParticleSize(int v) { this.particleSize = Math.max(1, Math.min(256, v)); }
public boolean isParticleGlow() { return particleGlow; }
public void setParticleGlow(boolean v) { this.particleGlow = v; }
public void setZoneCount(int zoneCount) {
this.zoneCount = Math.max(1, zoneCount);
}
public float getZoneHeight() {
return zoneHeight;
}
public void setZoneHeight(float zoneHeight) {
this.zoneHeight = Math.max(0.5f, zoneHeight);
}
public float getParticleDensity() {
return particleDensity;
}
public void setParticleDensity(float v) {
this.particleDensity = Math.max(0, Math.min(5, v));
}
public float getParticleScale() {
return particleScale;
}
public void setParticleScale(float v) {
this.particleScale = Math.max(0.1f, Math.min(5, v));
}
public float getAnimSpeed() {
return animSpeed;
}
public void setAnimSpeed(float v) {
this.animSpeed = Math.max(0.1f, Math.min(5, v));
}
public float getLightningDensity() {
return lightningDensity;
}
public void setLightningDensity(float v) {
this.lightningDensity = Math.max(0, Math.min(3, v));
}
public int getInnerColor() {
return colorData.innerColor;
}
public void setInnerColor(int color) {
colorData.innerColor = color;
}
public int getOuterColor() {
return colorData.outerColor;
}
public void setOuterColor(int color) {
colorData.outerColor = color;
}
public boolean isOuterColorEnabled() {
return colorData.outerColorEnabled;
}
public void setOuterColorEnabled(boolean enabled) {
colorData.outerColorEnabled = enabled;
}
public boolean isGroundFill() {
return groundFill;
}
public void setGroundFill(boolean v) {
this.groundFill = v;
}
public float getGroundAlpha() {
return groundAlpha;
}
public void setGroundAlpha(float v) {
this.groundAlpha = Math.max(0, Math.min(1, v));
}
public boolean isRings() {
return rings;
}
public void setRings(boolean v) {
this.rings = v;
}
public int getRingCount() {
return ringCount;
}
public void setRingCount(int v) {
this.ringCount = Math.max(1, Math.min(5, v));
}
public boolean isBorder() {
return border;
}
public void setBorder(boolean v) {
this.border = v;
}
public float getBorderSpeed() {
return borderSpeed;
}
public void setBorderSpeed(float v) {
this.borderSpeed = Math.max(0.1f, Math.min(5, v));
}
public boolean isAccents() {
return accents;
}
public void setAccents(boolean v) {
this.accents = v;
}
public int getAccentStyle() {
return accentStyle;
}
public void setAccentStyle(int v) {
this.accentStyle = Math.max(0, Math.min(AccentStyle.values().length - 1, v));
}
public AccentStyle getAccentStyleEnum() {
return AccentStyle.values()[Math.max(0, Math.min(accentStyle, AccentStyle.values().length - 1))];
}
public void setAccentStyleEnum(AccentStyle s) {
this.accentStyle = s.ordinal();
}
public boolean isLightning() {
return lightning;
}
public void setLightning(boolean v) {
this.lightning = v;
}
public boolean isParticles() {
return particles;
}
public void setParticles(boolean v) {
this.particles = v;
}
public int getParticleMotion() {
return particleMotion;
}
public void setParticleMotion(int v) {
this.particleMotion = Math.max(0, Math.min(ParticleMotion.values().length - 1, v));
}
public ParticleMotion getParticleMotionEnum() {
return ParticleMotion.values()[Math.max(0, Math.min(particleMotion, ParticleMotion.values().length - 1))];
}
public void setParticleMotionEnum(ParticleMotion m) {
this.particleMotion = m.ordinal();
}
public String getParticleDir() {
return particleDir;
}
public void setParticleDir(String v) {
this.particleDir = v != null ? v : "";
}
public int getParticleSize() {
return particleSize;
}
public void setParticleSize(int v) {
this.particleSize = Math.max(1, Math.min(256, v));
}
public boolean isParticleGlow() {
return particleGlow;
}
public void setParticleGlow(boolean v) {
this.particleGlow = v;
}
@SideOnly(Side.CLIENT)
protected void addPresetFieldDef(List<FieldDef> defs) {
@@ -6,7 +6,11 @@ import kamkeel.npcs.controllers.data.ability.AbilityVariant;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.RotationMode;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.entity.EntityAbilityBeam;
@@ -34,7 +38,7 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f),
new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f),
new EnergyHomingData(0.4f, true, 0.1f, 15.0f),
new EnergyLifespanData(25.0f, 200)
new EnergyLifespanData(150.0f, 200)
);
this.typeId = "ability.cnpc.beam";
this.name = "Beam";
@@ -56,7 +60,7 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
@Override
protected EntityAbilityBeam createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityAbilityBeam(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
@@ -81,7 +85,7 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
@Override
protected void setupEntityPreview(EntityAbilityBeam beam, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
EnergyDisplayData resolved, ProjectileData projData, int index) {
beam.setupPreview(caster, beamWidth, headSize, resolved, lightningData, projData.anchor, windUpTicks, 1.0f);
}
@@ -139,14 +143,29 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
// ==================== TYPE-SPECIFIC GETTERS ====================
public float getSpeed() { return homingData.speed; }
public void setSpeed(float speed) { homingData.speed = speed; }
public float getSpeed() {
return homingData.speed;
}
public float getBeamWidth() { return beamWidth; }
public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; }
public void setSpeed(float speed) {
homingData.speed = speed;
}
public float getHeadSize() { return headSize; }
public void setHeadSize(float headSize) { this.headSize = headSize; }
public float getBeamWidth() {
return beamWidth;
}
public void setBeamWidth(float beamWidth) {
this.beamWidth = beamWidth;
}
public float getHeadSize() {
return headSize;
}
public void setHeadSize(float headSize) {
this.headSize = headSize;
}
// ==================== TYPE-SPECIFIC GUI ====================
@@ -5,7 +5,11 @@ 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.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.entity.EntityAbilityDisc;
@@ -36,7 +40,7 @@ public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> impl
new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f),
new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f),
new EnergyHomingData(0.6f, true, 0.12f, 18.0f),
new EnergyLifespanData(35.0f, 200)
new EnergyLifespanData(150.0f, 200)
);
this.typeId = "ability.cnpc.disc";
this.name = "Disc";
@@ -56,7 +60,7 @@ public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> impl
@Override
protected EntityAbilityDisc createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityAbilityDisc(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
@@ -81,7 +85,7 @@ public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> impl
@Override
protected void setupEntityPreview(EntityAbilityDisc disc, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
EnergyDisplayData resolved, ProjectileData projData, int index) {
disc.setupPreview(caster, discRadius, discThickness, resolved, lightningData, projData.anchor, windUpTicks, vertical);
}
@@ -139,23 +143,53 @@ public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> impl
// ==================== TYPE-SPECIFIC GETTERS ====================
public float getSpeed() { return homingData.speed; }
public void setSpeed(float speed) { homingData.speed = speed; }
public float getSpeed() {
return homingData.speed;
}
public float getDiscRadius() { return discRadius; }
public void setDiscRadius(float discRadius) { this.discRadius = discRadius; }
public void setSpeed(float speed) {
homingData.speed = speed;
}
public float getDiscThickness() { return discThickness; }
public void setDiscThickness(float discThickness) { this.discThickness = discThickness; }
public float getDiscRadius() {
return discRadius;
}
public boolean isVertical() { return vertical; }
public void setVertical(boolean vertical) { this.vertical = vertical; }
public void setDiscRadius(float discRadius) {
this.discRadius = discRadius;
}
public boolean isBoomerang() { return boomerang; }
public void setBoomerang(boolean boomerang) { this.boomerang = boomerang; }
public float getDiscThickness() {
return discThickness;
}
public int getBoomerangDelay() { return boomerangDelay; }
public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; }
public void setDiscThickness(float discThickness) {
this.discThickness = discThickness;
}
public boolean isVertical() {
return vertical;
}
public void setVertical(boolean vertical) {
this.vertical = vertical;
}
public boolean isBoomerang() {
return boomerang;
}
public void setBoomerang(boolean boomerang) {
this.boomerang = boomerang;
}
public int getBoomerangDelay() {
return boomerangDelay;
}
public void setBoomerangDelay(int boomerangDelay) {
this.boomerangDelay = boomerangDelay;
}
// ==================== TYPE-SPECIFIC GUI ====================
@@ -10,7 +10,6 @@ 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;
@@ -111,11 +110,21 @@ public class AbilityDome extends AbilityEnergyBarrier {
// ==================== GETTERS & SETTERS ====================
public float getDomeRadius() { return domeRadius; }
public void setDomeRadius(float radius) { this.domeRadius = Math.max(1.0f, radius); }
public float getDomeRadius() {
return domeRadius;
}
public boolean isFollowCaster() { return followCaster; }
public void setFollowCaster(boolean follow) { this.followCaster = follow; }
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 ====================
@@ -26,40 +26,95 @@ public abstract class AbilityEnergy extends Ability {
// ==================== DISPLAY DATA ====================
public int getInnerColor() { return displayData.innerColor; }
public void setInnerColor(int color) { displayData.innerColor = color; }
public int getInnerColor() {
return displayData.innerColor;
}
public float getInnerAlpha() { return displayData.innerAlpha; }
public void setInnerAlpha(float alpha) { displayData.innerAlpha = alpha; }
public void setInnerColor(int color) {
displayData.innerColor = color;
}
public int getOuterColor() { return displayData.outerColor; }
public void setOuterColor(int color) { displayData.outerColor = color; }
public float getInnerAlpha() {
return displayData.innerAlpha;
}
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; }
public void setInnerAlpha(float alpha) {
displayData.innerAlpha = alpha;
}
public float getOuterColorWidth() { return displayData.outerColorWidth; }
public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; }
public int getOuterColor() {
return displayData.outerColor;
}
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; }
public void setOuterColor(int color) {
displayData.outerColor = color;
}
public float getRotationSpeed() { return displayData.rotationSpeed; }
public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; }
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 boolean hasLightningEffect() {
return lightningData.lightningEffect;
}
public float getLightningDensity() { return lightningData.lightningDensity; }
public void setLightningDensity(float density) { lightningData.lightningDensity = density; }
public void setLightningEffect(boolean enabled) {
lightningData.lightningEffect = enabled;
}
public float getLightningRadius() { return lightningData.lightningRadius; }
public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; }
public float getLightningDensity() {
return lightningData.lightningDensity;
}
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; }
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 ====================
@@ -142,37 +142,95 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy {
// Display and lightning data inherited from AbilityEnergy
// Barrier data
public float getBarrierMaxHealth() { return barrierData.maxHealth; }
public void setBarrierMaxHealth(float maxHealth) { barrierData.setMaxHealth(maxHealth); }
public float getBarrierMaxHealth() {
return barrierData.maxHealth;
}
public boolean isUseHealth() { return barrierData.useHealth; }
public void setUseHealth(boolean useHealth) { barrierData.useHealth = useHealth; }
public void setBarrierMaxHealth(float maxHealth) {
barrierData.setMaxHealth(maxHealth);
}
public int getBarrierDuration() { return barrierData.durationTicks; }
public void setBarrierDuration(int ticks) { barrierData.setDurationTicks(ticks); }
public boolean isUseHealth() {
return barrierData.useHealth;
}
public boolean isUseDuration() { return barrierData.useDuration; }
public void setUseDuration(boolean useDuration) { barrierData.useDuration = useDuration; }
public void setUseHealth(boolean useHealth) {
barrierData.useHealth = useHealth;
}
public float getDefaultMultiplier() { return barrierData.defaultMultiplier; }
public void setDefaultMultiplier(float mult) { barrierData.defaultMultiplier = mult; }
public int getBarrierDuration() {
return barrierData.durationTicks;
}
public void setDamageMultiplier(String typeId, float mult) { barrierData.setMultiplier(typeId, mult); }
public float getDamageMultiplier(String typeId) { return barrierData.getMultiplier(typeId); }
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); }
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; }
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 ====================
@@ -199,7 +257,7 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy {
FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength)
.range(0, 10).visibleWhen(this::isKnockbackEnabled),
FieldDef.stringEnumField("ability.knockbackTarget", EnergyBarrierData.getKnockbackTargetKeys(),
this::getKnockbackTargetKey, this::setKnockbackTargetKey)
this::getKnockbackTargetKey, this::setKnockbackTargetKey)
.visibleWhen(this::isKnockbackEnabled)
));
@@ -2,10 +2,14 @@ package kamkeel.npcs.controllers.data.ability.type.energy;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -24,7 +28,7 @@ import java.util.List;
* Abstract base for energy projectile abilities (Orb, Disc, Beam, LaserShot).
* Handles shared lifecycle (windup → execute → active → completion), multi-projectile
* support with staggered firing, telegraph creation, NBT persistence, and GUI definitions.
*
* <p>
* Subclasses provide entity creation/firing/charging logic and type-specific fields.
*
* @param <E> The entity type this ability spawns
@@ -64,6 +68,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
this.homingData = homingData;
this.lifespanData = lifespanData;
this.trajectoryData = new EnergyTrajectoryData();
this.burstOverlap = true;
initProjectiles(1);
}
@@ -74,7 +79,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
* Called during windup (tick 1) and burst refire (onExecute when entities are null).
*/
protected abstract E createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index);
Vec3 spawnPos, EnergyDisplayData resolved, int index);
/**
* Fire an entity (start moving toward target). Entity is guaranteed non-null and alive.
@@ -90,7 +95,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
* Setup entity for preview mode during windup.
*/
protected abstract void setupEntityPreview(E entity, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index);
EnergyDisplayData resolved, ProjectileData projData, int index);
/**
* Create typed array (Java generics can't create generic arrays).
@@ -129,9 +134,12 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
*/
protected AnchorPoint getDefaultAnchor(int index) {
switch (index) {
case 0: return AnchorPoint.RIGHT_HAND;
case 1: return AnchorPoint.LEFT_HAND;
default: return AnchorPoint.FRONT;
case 0:
return AnchorPoint.RIGHT_HAND;
case 1:
return AnchorPoint.LEFT_HAND;
default:
return AnchorPoint.FRONT;
}
}
@@ -168,15 +176,31 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
}
}
public int getProjectileCount() { return projectileCount; }
public int getProjectileCount() {
return projectileCount;
}
public int getFireDelay() { return fireDelay; }
public void setFireDelay(int delay) { this.fireDelay = Math.max(0, delay); }
public int getFireDelay() {
return fireDelay;
}
/** @deprecated Use {@link #getFireDelay()} */
public int getDualFireDelay() { return fireDelay; }
/** @deprecated Use {@link #setFireDelay(int)} */
public void setDualFireDelay(int delay) { setFireDelay(delay); }
public void setFireDelay(int delay) {
this.fireDelay = Math.max(0, delay);
}
/**
* @deprecated Use {@link #getFireDelay()}
*/
public int getDualFireDelay() {
return fireDelay;
}
/**
* @deprecated Use {@link #setFireDelay(int)}
*/
public void setDualFireDelay(int delay) {
setFireDelay(delay);
}
protected int clampIndex(int index) {
return Math.max(0, Math.min(index, projectileCount - 1));
@@ -317,7 +341,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
// Check if entity was removed from world (chunk unload, etc.)
// 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.worldObj.getEntityByID(entity.getEntityId()) != entity) {
entity.setDead();
continue;
}
@@ -449,64 +473,163 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
// ==================== API GETTERS & SETTERS ====================
// Combat data
@Override public float getDamage() { return combatData.damage; }
@Override public void setDamage(float damage) { combatData.damage = damage; }
@Override
public float getDamage() {
return combatData.damage;
}
@Override public float getKnockback() { return combatData.knockback; }
@Override public void setKnockback(float knockback) { combatData.knockback = knockback; }
@Override
public void setDamage(float damage) {
combatData.damage = damage;
}
@Override public float getKnockbackUp() { return combatData.knockbackUp; }
@Override public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; }
@Override
public float getKnockback() {
return combatData.knockback;
}
@Override public boolean isExplosive() { return combatData.explosive; }
@Override public void setExplosive(boolean explosive) { combatData.explosive = explosive; }
@Override
public void setKnockback(float knockback) {
combatData.knockback = knockback;
}
@Override public float getExplosionRadius() { return combatData.explosionRadius; }
@Override public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; }
@Override
public float getKnockbackUp() {
return combatData.knockbackUp;
}
@Override public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
@Override public void setExplosionDamageFalloff(float falloff) { combatData.explosionDamageFalloff = falloff; }
@Override
public void setKnockbackUp(float knockbackUp) {
combatData.knockbackUp = knockbackUp;
}
@Override
public boolean isExplosive() {
return combatData.explosive;
}
@Override
public void setExplosive(boolean explosive) {
combatData.explosive = explosive;
}
@Override
public float getExplosionRadius() {
return combatData.explosionRadius;
}
@Override
public void setExplosionRadius(float explosionRadius) {
combatData.explosionRadius = explosionRadius;
}
@Override
public float getExplosionDamageFalloff() {
return combatData.explosionDamageFalloff;
}
@Override
public void setExplosionDamageFalloff(float falloff) {
combatData.explosionDamageFalloff = falloff;
}
// Lifespan data
@Override public float getMaxDistance() { return lifespanData.maxDistance; }
@Override public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; }
@Override
public float getMaxDistance() {
return lifespanData.maxDistance;
}
@Override public int getMaxLifetime() { return lifespanData.maxLifetime; }
@Override public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; }
@Override
public void setMaxDistance(float maxDistance) {
lifespanData.maxDistance = maxDistance;
}
@Override
public int getMaxLifetime() {
return lifespanData.maxLifetime;
}
@Override
public void setMaxLifetime(int maxLifetime) {
lifespanData.maxLifetime = maxLifetime;
}
// Homing data
public boolean isHoming() { return homingData.homing; }
public void setHoming(boolean homing) { homingData.homing = homing; }
public boolean isHoming() {
return homingData.homing;
}
public float getHomingStrength() { return homingData.homingStrength; }
public void setHomingStrength(float strength) { homingData.homingStrength = strength; }
public void setHoming(boolean homing) {
homingData.homing = homing;
}
public float getHomingRange() { return homingData.homingRange; }
public void setHomingRange(float range) { homingData.homingRange = range; }
public float getHomingStrength() {
return homingData.homingStrength;
}
public void setHomingStrength(float strength) {
homingData.homingStrength = strength;
}
public float getHomingRange() {
return homingData.homingRange;
}
public void setHomingRange(float range) {
homingData.homingRange = range;
}
// Display and lightning data inherited from AbilityEnergy
// Anchor - default to projectile 0
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
public void setAnchorPointEnum(AnchorPoint point) { projectiles[0].anchor.anchorPoint = point; }
public AnchorPoint getAnchorPointEnum() {
return projectiles[0].anchor.anchorPoint;
}
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
public void setAnchorPointEnum(AnchorPoint point) {
projectiles[0].anchor.anchorPoint = point;
}
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
public float getAnchorOffsetX() {
return projectiles[0].anchor.anchorOffsetX;
}
public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; }
public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; }
public void setAnchorOffsetX(float x) {
projectiles[0].anchor.anchorOffsetX = x;
}
@Override public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
@Override public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
public float getAnchorOffsetY() {
return projectiles[0].anchor.anchorOffsetY;
}
public void setAnchorOffsetY(float y) {
projectiles[0].anchor.anchorOffsetY = y;
}
public float getAnchorOffsetZ() {
return projectiles[0].anchor.anchorOffsetZ;
}
public void setAnchorOffsetZ(float z) {
projectiles[0].anchor.anchorOffsetZ = z;
}
@Override
public int getAnchorPoint() {
return projectiles[0].anchor.anchorPoint.ordinal();
}
@Override
public void setAnchorPoint(int point) {
projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point);
}
// Indexed API methods
public int getInnerColor(int index) {
ProjectileData p = projectiles[clampIndex(index)];
return p.colorOverride ? p.innerColor : displayData.innerColor;
}
public void setInnerColor(int index, int color) {
ProjectileData p = projectiles[clampIndex(index)];
p.colorOverride = true;
@@ -517,17 +640,32 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
ProjectileData p = projectiles[clampIndex(index)];
return p.colorOverride ? p.outerColor : displayData.outerColor;
}
public void setOuterColor(int index, int color) {
ProjectileData p = projectiles[clampIndex(index)];
p.colorOverride = true;
p.outerColor = color;
}
public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); }
public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; }
public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; }
public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; }
public int getAnchorPoint(int index) {
return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal();
}
public void setAnchorPoint(int index, int point) {
projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point);
}
public void setAnchorOffsetX(int index, float offset) {
projectiles[clampIndex(index)].anchor.anchorOffsetX = offset;
}
public void setAnchorOffsetY(int index, float offset) {
projectiles[clampIndex(index)].anchor.anchorOffsetY = offset;
}
public void setAnchorOffsetZ(int index, float offset) {
projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset;
}
// ==================== GUI FIELD DEFINITIONS ====================
@@ -555,7 +693,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
.visibleWhen(() -> idx < projectileCount));
defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
defs.add(FieldDef.row(
FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX, v -> projectiles[idx].anchor.anchorOffsetX = v)
@@ -6,7 +6,11 @@ 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;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -30,13 +34,14 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
private float laserWidth = 0.3f;
private float expansionSpeed = 3.0f;
private int lingerTicks = 8;
private boolean dieOnImpact = false;
public AbilityLaserShot() {
super(
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f),
new EnergyHomingData(),
new EnergyLifespanData(40.0f, 100)
new EnergyLifespanData(150.0f, 100)
);
this.typeId = "ability.cnpc.laser_shot";
this.name = "Laser Shot";
@@ -111,13 +116,15 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
protected EntityAbilityLaser createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityAbilityLaser(
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
EntityAbilityLaser laser = new EntityAbilityLaser(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
laserWidth,
resolved, combatData, lightningData, lifespanData, trajectoryData,
expansionSpeed, lingerTicks);
laser.setDieOnImpact(dieOnImpact);
return laser;
}
@Override
@@ -133,7 +140,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
protected void setupEntityPreview(EntityAbilityLaser laser, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
EnergyDisplayData resolved, ProjectileData projData, int index) {
laser.setupPreview(caster, laserWidth, resolved, lightningData, expansionSpeed, lifespanData.maxDistance);
}
@@ -154,6 +161,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
nbt.setFloat("laserWidth", laserWidth);
nbt.setFloat("expansionSpeed", expansionSpeed);
nbt.setInteger("lingerTicks", lingerTicks);
nbt.setBoolean("dieOnImpact", dieOnImpact);
}
@Override
@@ -161,18 +169,42 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
this.laserWidth = nbt.getFloat("laserWidth");
this.expansionSpeed = nbt.getFloat("expansionSpeed");
this.lingerTicks = nbt.getInteger("lingerTicks");
this.dieOnImpact = nbt.getBoolean("dieOnImpact");
}
// ==================== TYPE-SPECIFIC GETTERS ====================
public float getLaserWidth() { return laserWidth; }
public void setLaserWidth(float laserWidth) { this.laserWidth = laserWidth; }
public float getLaserWidth() {
return laserWidth;
}
public float getExpansionSpeed() { return expansionSpeed; }
public void setExpansionSpeed(float expansionSpeed) { this.expansionSpeed = expansionSpeed; }
public void setLaserWidth(float laserWidth) {
this.laserWidth = laserWidth;
}
public int getLingerTicks() { return lingerTicks; }
public void setLingerTicks(int lingerTicks) { this.lingerTicks = lingerTicks; }
public float getExpansionSpeed() {
return expansionSpeed;
}
public void setExpansionSpeed(float expansionSpeed) {
this.expansionSpeed = expansionSpeed;
}
public int getLingerTicks() {
return lingerTicks;
}
public void setLingerTicks(int lingerTicks) {
this.lingerTicks = lingerTicks;
}
public boolean isDieOnImpact() {
return dieOnImpact;
}
public void setDieOnImpact(boolean dieOnImpact) {
this.dieOnImpact = dieOnImpact;
}
// ==================== TYPE-SPECIFIC GUI ====================
@@ -194,6 +226,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
));
defs.add(FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance));
defs.add(FieldDef.boolField("ability.dieOnImpact", this::isDieOnImpact, this::setDieOnImpact));
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));
@@ -5,7 +5,11 @@ 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.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.entity.EntityAbilityOrb;
@@ -51,7 +55,7 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
@Override
protected EntityAbilityOrb createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityAbilityOrb(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize,
@@ -74,7 +78,7 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
@Override
protected void setupEntityPreview(EntityAbilityOrb orb, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
EnergyDisplayData resolved, ProjectileData projData, int index) {
orb.setupPreview(caster, orbSize, resolved, lightningData, projData.anchor, windUpTicks);
}
@@ -137,11 +141,21 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
// ==================== TYPE-SPECIFIC GETTERS ====================
public float getOrbSpeed() { return homingData.speed; }
public void setOrbSpeed(float speed) { homingData.speed = speed; }
public float getOrbSpeed() {
return homingData.speed;
}
public float getOrbSize() { return orbSize; }
public void setOrbSize(float size) { this.orbSize = size; }
public void setOrbSpeed(float speed) {
homingData.speed = speed;
}
public float getOrbSize() {
return orbSize;
}
public void setOrbSize(float size) {
this.orbSize = size;
}
// ==================== TYPE-SPECIFIC GUI ====================
@@ -12,7 +12,6 @@ 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;
@@ -124,11 +123,21 @@ public class AbilityShield extends AbilityEnergyBarrier {
// ==================== GETTERS & SETTERS ====================
public float getShieldWidth() { return shieldWidth; }
public void setShieldWidth(float width) { this.shieldWidth = Math.max(0.5f, width); }
public float getShieldWidth() {
return shieldWidth;
}
public float getShieldHeight() { return shieldHeight; }
public void setShieldHeight(float height) { this.shieldHeight = Math.max(0.5f, height); }
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 ====================
@@ -5,7 +5,11 @@ 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.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.entity.EntityEnergySlicer;
@@ -33,7 +37,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile<EntityEnergySlicer> {
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)
new EnergyLifespanData(150.0f, 100)
);
this.typeId = "ability.cnpc.slicer";
this.name = "Slicer";
@@ -53,7 +57,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile<EntityEnergySlicer> {
@Override
protected EntityEnergySlicer createEntity(EntityLivingBase caster, EntityLivingBase target,
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
return new EntityEnergySlicer(
caster.worldObj, caster, target,
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
@@ -79,7 +83,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile<EntityEnergySlicer> {
@Override
protected void setupEntityPreview(EntityEnergySlicer slicer, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
EnergyDisplayData resolved, ProjectileData projData, int index) {
slicer.setupPreview(caster, sliceWidth, sliceThickness, resolved, lightningData, projData.anchor, windUpTicks);
}
@@ -130,17 +134,37 @@ public class AbilitySlicer extends AbilityEnergyProjectile<EntityEnergySlicer> {
// ==================== TYPE-SPECIFIC GETTERS ====================
public float getSlicerSpeed() { return homingData.speed; }
public void setSlicerSpeed(float speed) { homingData.speed = speed; }
public float getSlicerSpeed() {
return homingData.speed;
}
public float getSliceWidth() { return sliceWidth; }
public void setSliceWidth(float width) { this.sliceWidth = Math.max(0.5f, width); }
public void setSlicerSpeed(float speed) {
homingData.speed = speed;
}
public float getSliceThickness() { return sliceThickness; }
public void setSliceThickness(float thickness) { this.sliceThickness = Math.max(0.05f, thickness); }
public float getSliceWidth() {
return sliceWidth;
}
public boolean isPiercing() { return piercing; }
public void setPiercing(boolean piercing) { this.piercing = piercing; }
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 ====================
@@ -22,7 +22,7 @@ import java.util.List;
* Sweeper ability: A low sweeping beam that rotates around the NPC.
* Players can jump over it. The beam completes a set number of rotations before ending.
* Telegraph shows a large circle on the ground indicating range.
*
* <p>
* All damage logic is handled by the EntityAbilitySweeper entity to ensure
* visual and damage hitbox alignment.
*/
@@ -173,24 +173,77 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper {
}
// 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; }
public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; }
public float getBeamHeight() { return beamHeight; }
public void setBeamHeight(float beamHeight) { this.beamHeight = beamHeight; }
public float getDamage() { return damage; }
public void setDamage(float damage) { this.damage = damage; }
public int getDamageInterval() { return damageInterval; }
public void setDamageInterval(int damageInterval) { this.damageInterval = damageInterval; }
public boolean isPiercing() { return piercing; }
public void setPiercing(boolean piercing) { this.piercing = piercing; }
public float getSweepSpeed() { return sweepSpeed; }
public void setSweepSpeed(float sweepSpeed) { this.sweepSpeed = sweepSpeed; }
public int getNumberOfRotations() { return numberOfRotations; }
public void setNumberOfRotations(int numberOfRotations) { this.numberOfRotations = numberOfRotations; }
public boolean isLockOnTarget() { return lockOnTarget; }
public void setLockOnTarget(boolean lockOnTarget) { this.lockOnTarget = lockOnTarget; }
public float getBeamLength() {
return beamLength;
}
public void setBeamLength(float beamLength) {
this.beamLength = beamLength;
}
public float getBeamWidth() {
return beamWidth;
}
public void setBeamWidth(float beamWidth) {
this.beamWidth = beamWidth;
}
public float getBeamHeight() {
return beamHeight;
}
public void setBeamHeight(float beamHeight) {
this.beamHeight = beamHeight;
}
public float getDamage() {
return damage;
}
public void setDamage(float damage) {
this.damage = damage;
}
public int getDamageInterval() {
return damageInterval;
}
public void setDamageInterval(int damageInterval) {
this.damageInterval = damageInterval;
}
public boolean isPiercing() {
return piercing;
}
public void setPiercing(boolean piercing) {
this.piercing = piercing;
}
public float getSweepSpeed() {
return sweepSpeed;
}
public void setSweepSpeed(float sweepSpeed) {
this.sweepSpeed = sweepSpeed;
}
public int getNumberOfRotations() {
return numberOfRotations;
}
public void setNumberOfRotations(int numberOfRotations) {
this.numberOfRotations = numberOfRotations;
}
public boolean isLockOnTarget() {
return lockOnTarget;
}
public void setLockOnTarget(boolean lockOnTarget) {
this.lockOnTarget = lockOnTarget;
}
@Override
public int getMaxPreviewDuration() {
@@ -11,7 +11,6 @@ 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;
@@ -139,28 +138,65 @@ public class AbilityWall extends AbilityEnergyBarrier {
// ==================== GETTERS & SETTERS ====================
public EnergyPanelData getPanelData() { return panelData; }
public EnergyPanelData getPanelData() {
return panelData;
}
public float getPanelWidth() { return panelData.panelWidth; }
public void setPanelWidth(float width) { panelData.setPanelWidth(width); }
public float getPanelWidth() {
return panelData.panelWidth;
}
public float getPanelHeight() { return panelData.panelHeight; }
public void setPanelHeight(float height) { panelData.setPanelHeight(height); }
public void setPanelWidth(float width) {
panelData.setPanelWidth(width);
}
public float getHeightOffset() { return panelData.heightOffset; }
public void setHeightOffset(float offset) { panelData.heightOffset = offset; }
public float getPanelHeight() {
return panelData.panelHeight;
}
public boolean isLaunching() { return panelData.launching; }
public void setLaunching(boolean launching) { panelData.launching = launching; }
public void setPanelHeight(float height) {
panelData.setPanelHeight(height);
}
public float getLaunchSpeed() { return panelData.launchSpeed; }
public void setLaunchSpeed(float speed) { panelData.setLaunchSpeed(speed); }
public float getHeightOffset() {
return panelData.heightOffset;
}
public float getLaunchDamage() { return panelData.launchDamage; }
public void setLaunchDamage(float damage) { panelData.setLaunchDamage(damage); }
public void setHeightOffset(float offset) {
panelData.heightOffset = offset;
}
public float getLaunchKnockback() { return panelData.launchKnockback; }
public void setLaunchKnockback(float kb) { panelData.setLaunchKnockback(kb); }
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 ====================
@@ -1,9 +1,14 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import noppes.npcs.EventHooks;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
@@ -11,6 +16,7 @@ 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 noppes.npcs.LogWriter;
import java.util.ArrayList;
@@ -19,10 +25,10 @@ import java.util.List;
/**
* Beam projectile - head with trailing path that curves with homing.
* Stays attached to origin point, trail shows path of head.
*
* <p>
* IMPORTANT: Trail points are stored RELATIVE to startX/Y/Z (origin).
* This ensures rendering is stable regardless of entity position interpolation.
*
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
public class EntityAbilityBeam extends EntityEnergyProjectile {
@@ -63,6 +69,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
/**
* Full constructor with all parameters using data classes.
*
* @param anchoredMode If true, origin follows owner and tail orb is rendered.
* If false, beam is free-moving with trailing length (no tail orb).
*/
@@ -214,7 +221,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
/**
* Start the beam firing (exit charging mode).
* Called by ability when windup ends.
*
* <p>
* For anchored beams: origin follows owner, head starts at charged position
* For non-anchored beams: origin fixed at charged position, head starts there
*/
@@ -281,8 +288,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Check distance using head offset (distance from origin)
double distFromOrigin = Math.sqrt(
headOffsetX * headOffsetX +
headOffsetY * headOffsetY +
headOffsetZ * headOffsetZ
headOffsetY * headOffsetY +
headOffsetZ * headOffsetZ
);
boolean exceeded = distFromOrigin >= getMaxDistance();
if (exceeded && !worldObj.isRemote && DEBUG_LOGGING) {
@@ -440,8 +447,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
Vec3 lastPoint = trailPoints.get(trailPoints.size() - 1);
double dist = Math.sqrt(
(headOffsetX - lastPoint.xCoord) * (headOffsetX - lastPoint.xCoord) +
(headOffsetY - lastPoint.yCoord) * (headOffsetY - lastPoint.yCoord) +
(headOffsetZ - lastPoint.zCoord) * (headOffsetZ - lastPoint.zCoord)
(headOffsetY - lastPoint.yCoord) * (headOffsetY - lastPoint.yCoord) +
(headOffsetZ - lastPoint.zCoord) * (headOffsetZ - lastPoint.zCoord)
);
if (dist >= MIN_POINT_DISTANCE) {
@@ -1,22 +1,28 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import noppes.npcs.EventHooks;
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.List;
/**
* Disc projectile - flat spinning disc with optional boomerang behavior.
* Has a wider, thinner hitbox compared to Orb.
*
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
public class EntityAbilityDisc extends EntityEnergyProjectile {
@@ -252,8 +258,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
protected boolean checkMaxDistance() {
double distTraveled = Math.sqrt(
(posX - startX) * (posX - startX) +
(posY - startY) * (posY - startY) +
(posZ - startZ) * (posZ - startZ)
(posY - startY) * (posY - startY) +
(posZ - startZ) * (posZ - startZ)
);
if (boomerang) {
@@ -270,8 +276,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
if (owner != null) {
double distToOwner = Math.sqrt(
(posX - owner.posX) * (posX - owner.posX) +
(posY - owner.posY) * (posY - owner.posY) +
(posZ - owner.posZ) * (posZ - owner.posZ)
(posY - owner.posY) * (posY - owner.posY) +
(posZ - owner.posZ) * (posZ - owner.posZ)
);
return distToOwner < 1.5;
}
@@ -1,15 +1,19 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import noppes.npcs.EventHooks;
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.HashSet;
import java.util.List;
@@ -18,7 +22,7 @@ import java.util.Set;
/**
* Laser projectile - fast expanding thin line that pierces through multiple targets.
* No homing, travels in a straight line from origin to max distance.
*
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
public class EntityAbilityLaser extends EntityEnergyProjectile {
@@ -39,6 +43,9 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// Lock vertical direction after firing (only update yaw, keep pitch fixed)
private boolean lockVerticalDirection = false;
// Whether to die on first entity impact (hit-scan mode)
private boolean dieOnImpact = false;
// Track hit entities to avoid double-damage
private Set<Integer> hitEntities = new HashSet<>();
@@ -246,8 +253,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// Laser stops at block
currentLength = (float) Math.sqrt(
(blockHit.hitVec.xCoord - startX) * (blockHit.hitVec.xCoord - startX) +
(blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) +
(blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ)
(blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) +
(blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ)
);
endX = blockHit.hitVec.xCoord;
endY = blockHit.hitVec.yCoord;
@@ -294,6 +301,20 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
hitEntities.add(entity.getEntityId());
applyDamage(entity);
if (dieOnImpact) {
// Stop at impact point and begin linger/fade
double dx = entity.posX - startX;
double dy = (entity.posY + entity.height * 0.5) - startY;
double dz = entity.posZ - startZ;
float impactDist = (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
currentLength = Math.min(impactDist, currentLength);
endX = startX + dirX * currentLength;
endY = startY + dirY * currentLength;
endZ = startZ + dirZ * currentLength;
fullyExtended = true;
return; // Stop checking further entities
}
// Piercing - don't stop, continue to next entity
}
}
@@ -382,6 +403,14 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
this.lingerTicks = ticks;
}
public boolean isDieOnImpact() {
return dieOnImpact;
}
public void setDieOnImpact(boolean dieOnImpact) {
this.dieOnImpact = dieOnImpact;
}
public void setDirection(double x, double y, double z) {
this.dirX = x;
this.dirY = y;
@@ -447,6 +476,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
this.laserWidth = nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f;
this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f;
this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10;
this.dieOnImpact = nbt.getBoolean("DieOnImpact");
this.dirX = nbt.getDouble("DirX");
this.dirY = nbt.getDouble("DirY");
this.dirZ = nbt.getDouble("DirZ");
@@ -464,6 +494,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
nbt.setFloat("LaserWidth", laserWidth);
nbt.setFloat("ExpansionSpeed", expansionSpeed);
nbt.setInteger("LingerTicks", lingerTicks);
nbt.setBoolean("DieOnImpact", dieOnImpact);
nbt.setDouble("DirX", dirX);
nbt.setDouble("DirY", dirY);
nbt.setDouble("DirZ", dirZ);
@@ -1,22 +1,28 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import noppes.npcs.EventHooks;
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.List;
/**
* Orb projectile - spherical homing energy ball.
* Extends EntityEnergyAbility for shared functionality.
*
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
public class EntityAbilityOrb extends EntityEnergyProjectile {
@@ -23,7 +23,7 @@ import java.util.Set;
* 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.
*
* <p>
* Design: Similar to Beam trail visuals but rotating around origin.
*/
public class EntityAbilitySweeper extends EntityEnergyAbility {
@@ -65,11 +65,11 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
}
public EntityAbilitySweeper(World world, EntityLivingBase owner, EntityLivingBase target,
float beamLength, float beamWidth, float beamHeight,
EnergyDisplayData displayData,
float sweepSpeed, int numberOfRotations,
float damage, int damageInterval, boolean piercing,
boolean lockOnTarget) {
float beamLength, float beamWidth, float beamHeight,
EnergyDisplayData displayData,
float sweepSpeed, int numberOfRotations,
float damage, int damageInterval, boolean piercing,
boolean lockOnTarget) {
this(world);
this.ownerEntityId = owner.getEntityId();
@@ -34,16 +34,20 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
// ENUMS
// ═══════════════════════════════════════════════════════════════════
public enum ZoneType { TRAP, HAZARD }
public enum ZoneType {TRAP, HAZARD}
public enum ZoneShape {
CIRCLE, SQUARE;
@Override
public String toString() {
switch (this) {
case CIRCLE: return "ability.shape.circle";
case SQUARE: return "ability.shape.square";
default: return name();
case CIRCLE:
return "ability.shape.circle";
case SQUARE:
return "ability.shape.square";
default:
return name();
}
}
}
@@ -54,10 +58,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
public String toString() {
switch (this) {
case STATIC: return "ability.accentStyle.static";
case SWAYING: return "ability.accentStyle.swaying";
case FLICKERING: return "ability.accentStyle.flickering";
default: return name();
case STATIC:
return "ability.accentStyle.static";
case SWAYING:
return "ability.accentStyle.swaying";
case FLICKERING:
return "ability.accentStyle.flickering";
default:
return name();
}
}
}
@@ -68,10 +76,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
public String toString() {
switch (this) {
case RISING: return "ability.particleMotion.rising";
case DRIFTING: return "ability.particleMotion.drifting";
case SPARKS: return "ability.particleMotion.sparks";
default: return name();
case RISING:
return "ability.particleMotion.rising";
case DRIFTING:
return "ability.particleMotion.drifting";
case SPARKS:
return "ability.particleMotion.sparks";
default:
return name();
}
}
}
@@ -174,7 +186,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
private EntityAbilityZone(World world, ZoneType type, EntityLivingBase owner,
double x, double y, double z) {
double x, double y, double z) {
this(world);
this.zoneType = type;
this.ownerEntityId = owner.getEntityId();
@@ -184,17 +196,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
public static EntityAbilityZone createTrap(World world, EntityLivingBase owner,
double x, double y, double z,
ZoneShape shape,
float triggerRadius, int armTime, int maxTriggers,
int triggerCooldown, float damage, float damageRadius,
float knockback, int durationTicks,
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
boolean visible,
List<AbilityPotionEffect> effects) {
double x, double y, double z,
ZoneShape shape,
float triggerRadius, int armTime, int maxTriggers,
int triggerCooldown, float damage, float damageRadius,
float knockback, int durationTicks,
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
boolean visible,
List<AbilityPotionEffect> effects) {
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.TRAP, owner, x, y, z);
zone.shape = shape;
zone.radius = triggerRadius;
@@ -226,17 +238,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
public static EntityAbilityZone createHazard(World world, EntityLivingBase owner,
double x, double y, double z,
ZoneShape shape,
float radius,
float damagePerSecond, int damageInterval,
boolean ignoreInvulnFrames, boolean affectsCaster,
int durationTicks,
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
List<AbilityPotionEffect> effects) {
double x, double y, double z,
ZoneShape shape,
float radius,
float damagePerSecond, int damageInterval,
boolean ignoreInvulnFrames, boolean affectsCaster,
int durationTicks,
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
List<AbilityPotionEffect> effects) {
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.HAZARD, owner, x, y, z);
zone.shape = shape;
zone.radius = radius;
@@ -268,7 +280,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
// ═══════════════════════════════════════════════════════════════════
@Override
protected void entityInit() {}
protected void entityInit() {
}
@Override
public void handleHealthUpdate(byte id) {
@@ -549,45 +562,133 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
// GETTERS FOR RENDERER
// ═══════════════════════════════════════════════════════════════════
public ZoneType getZoneType() { return zoneType; }
public ZoneShape getShape() { return shape; }
public float getRadius() { return radius; }
public int getInnerColor() { return innerColor; }
public int getOuterColor() { return outerColor; }
public boolean isOuterColorEnabled() { return outerColorEnabled; }
public float getZoneHeight() { return zoneHeight; }
public boolean isArmed() { return armed || ticksExisted >= armTime; }
public boolean isVisible() { return visible; }
public int getTriggerFlashTick() { return triggerFlashTick; }
public int getDurationTicks() { return durationTicks; }
public int getMaxTicks() { return maxTicks; }
public float getParticleDensity() { return particleDensity; }
public float getParticleScale() { return particleScale; }
public float getAnimSpeed() { return animSpeed; }
public float getLightningDensity() { return lightningDensity; }
public ZoneType getZoneType() {
return zoneType;
}
public boolean isGroundFill() { return groundFill; }
public float getGroundAlpha() { return groundAlpha; }
public boolean isRings() { return rings; }
public int getRingCount() { return ringCount; }
public boolean isBorder() { return border; }
public float getBorderSpeed() { return borderSpeed; }
public boolean isAccents() { return accents; }
public int getAccentStyle() { return accentStyle; }
public boolean isLightning() { return lightning; }
public boolean isParticles() { return particles; }
public int getParticleMotion() { return particleMotion; }
public String getParticleDir() { return particleDir; }
public int getParticleSize() { return particleSize; }
public boolean isParticleGlow() { return particleGlow; }
public ZoneShape getShape() {
return shape;
}
public float getRadius() {
return radius;
}
public int getInnerColor() {
return innerColor;
}
public int getOuterColor() {
return outerColor;
}
public boolean isOuterColorEnabled() {
return outerColorEnabled;
}
public float getZoneHeight() {
return zoneHeight;
}
public boolean isArmed() {
return armed || ticksExisted >= armTime;
}
public boolean isVisible() {
return visible;
}
public int getTriggerFlashTick() {
return triggerFlashTick;
}
public int getDurationTicks() {
return durationTicks;
}
public int getMaxTicks() {
return maxTicks;
}
public float getParticleDensity() {
return particleDensity;
}
public float getParticleScale() {
return particleScale;
}
public float getAnimSpeed() {
return animSpeed;
}
public float getLightningDensity() {
return lightningDensity;
}
public boolean isGroundFill() {
return groundFill;
}
public float getGroundAlpha() {
return groundAlpha;
}
public boolean isRings() {
return rings;
}
public int getRingCount() {
return ringCount;
}
public boolean isBorder() {
return border;
}
public float getBorderSpeed() {
return borderSpeed;
}
public boolean isAccents() {
return accents;
}
public int getAccentStyle() {
return accentStyle;
}
public boolean isLightning() {
return lightning;
}
public boolean isParticles() {
return particles;
}
public int getParticleMotion() {
return particleMotion;
}
public String getParticleDir() {
return particleDir;
}
public int getParticleSize() {
return particleSize;
}
public boolean isParticleGlow() {
return particleGlow;
}
public void applyVisual(boolean groundFill, float groundAlpha,
boolean rings, int ringCount,
boolean border, float borderSpeed,
boolean accents, int accentStyle,
boolean lightning,
boolean particles, int particleMotion,
String particleDir, int particleSize, boolean particleGlow) {
boolean rings, int ringCount,
boolean border, float borderSpeed,
boolean accents, int accentStyle,
boolean lightning,
boolean particles, int particleMotion,
String particleDir, int particleSize, boolean particleGlow) {
this.groundFill = groundFill;
this.groundAlpha = groundAlpha;
this.rings = rings;
@@ -30,7 +30,9 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
protected int ownerEntityId = -1;
// ==================== STATE ====================
/** The ability that spawned this entity. Transient, not saved to NBT. */
/**
* The ability that spawned this entity. Transient, not saved to NBT.
*/
protected transient Ability sourceAbility = null;
protected boolean previewMode = false;
protected EntityLivingBase previewOwner = null;
@@ -98,59 +100,137 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
return worldObj.getEntityByID(ownerEntityId);
}
public int getOwnerEntityId() { return ownerEntityId; }
public void setOwnerEntityId(int id) { this.ownerEntityId = id; }
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; }
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; }
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 EnergyDisplayData getDisplayData() {
return displayData;
}
public int getInnerColor() { return displayData.getInnerColor(); }
public void setInnerColor(int color) { displayData.setInnerColor(color); }
public EnergyLightningData getLightningData() {
return lightningData;
}
public float getInnerAlpha() { return displayData.getInnerAlpha(); }
public void setInnerAlpha(float alpha) { displayData.setInnerAlpha(alpha); }
public int getInnerColor() {
return displayData.getInnerColor();
}
public int getOuterColor() { return displayData.getOuterColor(); }
public void setOuterColor(int color) { displayData.setOuterColor(color); }
public void setInnerColor(int color) {
displayData.setInnerColor(color);
}
public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); }
public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); }
public float getInnerAlpha() {
return displayData.getInnerAlpha();
}
public float getOuterColorWidth() { return displayData.getOuterColorWidth(); }
public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); }
public void setInnerAlpha(float alpha) {
displayData.setInnerAlpha(alpha);
}
public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); }
public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); }
public int getOuterColor() {
return displayData.getOuterColor();
}
public float getRotationSpeed() { return displayData.getRotationSpeed(); }
public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); }
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 boolean hasLightningEffect() {
return lightningData.isLightningEffect();
}
public float getLightningDensity() { return lightningData.getLightningDensity(); }
public void setLightningDensity(float density) { lightningData.setLightningDensity(density); }
public void setLightningEffect(boolean enabled) {
lightningData.setLightningEffect(enabled);
}
public float getLightningRadius() { return lightningData.getLightningRadius(); }
public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); }
public float getLightningDensity() {
return lightningData.getLightningDensity();
}
public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); }
public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); }
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 ====================
@@ -1,10 +1,6 @@
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;
@@ -144,6 +140,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
* 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() {
@@ -223,18 +220,33 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
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;
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; }
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 ====================
@@ -195,10 +195,21 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
// ==================== 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; }
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 ====================
@@ -343,9 +343,17 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
// ==================== GETTERS ====================
public PanelMode getMode() { return mode; }
public float getPanelYaw() { return panelYaw; }
public EnergyPanelData getPanelData() { return panelData; }
public PanelMode getMode() {
return mode;
}
public float getPanelYaw() {
return panelYaw;
}
public EnergyPanelData getPanelData() {
return panelData;
}
// ==================== NBT ====================
@@ -2,10 +2,16 @@ package kamkeel.npcs.entity;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
@@ -37,7 +43,7 @@ import java.util.Map;
* 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.
*
* <p>
* Design inspired by LouisXIV's energy attack system.
*/
public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
@@ -56,6 +62,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
refs.add(new WeakReference<>(projectile));
}
public static void clearAllProjectiles() {
activeProjectiles.clear();
}
public static List<EntityEnergyProjectile> getActiveProjectiles(int ownerEntityId) {
List<WeakReference<EntityEnergyProjectile>> refs = activeProjectiles.get(ownerEntityId);
if (refs == null) return Collections.emptyList();
@@ -270,13 +280,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
/**
* Check if projectile has exceeded max distance.
*
* @return true if should die
*/
protected boolean checkMaxDistance() {
double distTraveled = Math.sqrt(
(posX - startX) * (posX - startX) +
(posY - startY) * (posY - startY) +
(posZ - startZ) * (posZ - startZ)
(posY - startY) * (posY - startY) +
(posZ - startZ) * (posZ - startZ)
);
return distTraveled >= getMaxDistance();
}
@@ -291,6 +302,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
/**
* 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() {
@@ -451,12 +463,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
double dist = Math.sqrt(
Math.pow(target.posX - posX, 2) +
Math.pow(target.posY - posY, 2) +
Math.pow(target.posZ - posZ, 2)
Math.pow(target.posY - posY, 2) +
Math.pow(target.posZ - posZ, 2)
);
if (dist <= getExplosionRadius()) {
float falloff = 1.0f - (float) (dist / getExplosionRadius()) * getExplosionDamageFalloff();
float radius = getExplosionRadius();
if (radius > 0 && dist <= radius) {
float falloff = 1.0f - (float) (dist / radius) * getExplosionDamageFalloff();
applyDamage(target, getDamage() * falloff, getKnockback() * falloff);
}
}
@@ -513,7 +526,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
* Update during charging state - grow size and follow anchor.
* Default implementation grows {@code size} from 0 to {@code targetSize}.
* Override in subclasses for type-specific charging (Disc, Beam).
*
* <p>
* Note: Owner death checks are handled by onUpdate() before this is called.
*/
protected void updateCharging() {
@@ -575,7 +588,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
* Call from subclass constructors after initProjectile().
*/
protected void calculateInitialVelocity(EntityLivingBase owner, EntityLivingBase target,
double x, double y, double z) {
double x, double y, double z) {
if (target != null) {
double dx = target.posX - x;
double dy = (target.posY + target.getEyeHeight()) - y;
@@ -696,7 +709,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// ==================== VISUAL GETTERS & SETTERS ====================
public float getSize() { return size; }
public float getSize() {
return size;
}
public void setProjectileSize(float size) {
this.size = size;
this.renderCurrentSize = size;
@@ -733,64 +749,147 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// ==================== LIFESPAN GETTERS & SETTERS ====================
public float getMaxDistance() { return lifespanData.getMaxDistance(); }
public void setMaxDistance(float distance) { lifespanData.setMaxDistance(distance); }
public float getMaxDistance() {
return lifespanData.getMaxDistance();
}
public int getMaxLifetime() { return lifespanData.getMaxLifetime(); }
public void setMaxLifetime(int ticks) { lifespanData.setMaxLifetime(ticks); }
public void setMaxDistance(float distance) {
lifespanData.setMaxDistance(distance);
}
public int getMaxLifetime() {
return lifespanData.getMaxLifetime();
}
public void setMaxLifetime(int ticks) {
lifespanData.setMaxLifetime(ticks);
}
// ==================== COMBAT GETTERS & SETTERS ====================
public float getDamage() { return combatData.getDamage(); }
public void setCombatDamage(float damage) { combatData.setDamage(damage); }
public float getDamage() {
return combatData.getDamage();
}
public float getKnockback() { return combatData.knockback; }
public void setCombatKnockback(float knockback) { combatData.setKnockback(knockback); }
public void setCombatDamage(float damage) {
combatData.setDamage(damage);
}
public float getKnockbackUp() { return combatData.knockbackUp; }
public void setCombatKnockbackUp(float knockbackUp) { combatData.setKnockbackUp(knockbackUp); }
public float getKnockback() {
return combatData.knockback;
}
public boolean isExplosive() { return combatData.isExplosive(); }
public void setExplosive(boolean explosive) { combatData.setExplosive(explosive); }
public void setCombatKnockback(float knockback) {
combatData.setKnockback(knockback);
}
public float getExplosionRadius() { return combatData.explosionRadius; }
public void setExplosionRadius(float radius) { combatData.setExplosionRadius(radius); }
public float getKnockbackUp() {
return combatData.knockbackUp;
}
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
public void setExplosionDamageFalloff(float falloff) { combatData.setExplosionDamageFalloff(falloff); }
public void setCombatKnockbackUp(float knockbackUp) {
combatData.setKnockbackUp(knockbackUp);
}
public boolean isExplosive() {
return combatData.isExplosive();
}
public void setExplosive(boolean explosive) {
combatData.setExplosive(explosive);
}
public float getExplosionRadius() {
return combatData.explosionRadius;
}
public void setExplosionRadius(float radius) {
combatData.setExplosionRadius(radius);
}
public float getExplosionDamageFalloff() {
return combatData.explosionDamageFalloff;
}
public void setExplosionDamageFalloff(float falloff) {
combatData.setExplosionDamageFalloff(falloff);
}
// ==================== HOMING GETTERS & SETTERS ====================
public boolean isHoming() { return homingData.isHoming(); }
public void setHomingEnabled(boolean homing) { homingData.setHoming(homing); }
public boolean isHoming() {
return homingData.isHoming();
}
public float getHomingStrength() { return homingData.getHomingStrength(); }
public void setHomingStrength(float strength) { homingData.setHomingStrength(strength); }
public void setHomingEnabled(boolean homing) {
homingData.setHoming(homing);
}
public float getHomingRange() { return homingData.getHomingRange(); }
public void setHomingRange(float range) { homingData.setHomingRange(range); }
public float getHomingStrength() {
return homingData.getHomingStrength();
}
public void setHomingStrength(float strength) {
homingData.setHomingStrength(strength);
}
public float getHomingRange() {
return homingData.getHomingRange();
}
public void setHomingRange(float range) {
homingData.setHomingRange(range);
}
// ==================== SPEED & ANCHOR GETTERS & SETTERS ====================
public float getSpeed() { return homingData.getSpeed(); }
public void setSpeed(float speed) { homingData.setSpeed(speed); }
public float getSpeed() {
return homingData.getSpeed();
}
public AnchorPoint getAnchorPoint() { return anchorData.getAnchorPoint(); }
public void setSpeed(float speed) {
homingData.setSpeed(speed);
}
public int getAnchor() { return anchorData.getAnchor(); }
public float getAnchorOffsetX() { return anchorData.getAnchorOffsetX(); }
public float getAnchorOffsetY() { return anchorData.getAnchorOffsetY(); }
public float getAnchorOffsetZ() { return anchorData.getAnchorOffsetZ(); }
public AnchorPoint getAnchorPoint() {
return anchorData.getAnchorPoint();
}
public int getAnchor() {
return anchorData.getAnchor();
}
public float getAnchorOffsetX() {
return anchorData.getAnchorOffsetX();
}
public float getAnchorOffsetY() {
return anchorData.getAnchorOffsetY();
}
public float getAnchorOffsetZ() {
return anchorData.getAnchorOffsetZ();
}
// ==================== MOVEMENT GETTERS ====================
public double getStartX() { return startX; }
public double getStartY() { return startY; }
public double getStartZ() { return startZ; }
public double getStartX() {
return startX;
}
public double getStartY() {
return startY;
}
public double getStartZ() {
return startZ;
}
// ==================== STATE GETTERS ====================
public boolean getHasHit() { return hasHit; }
public boolean getHasHit() {
return hasHit;
}
// ==================== NBT ====================
@@ -1,6 +1,12 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -293,14 +299,29 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
// ==================== GETTERS ====================
public float getSliceWidth() { return sliceWidth; }
public void setSliceWidth(float width) { this.sliceWidth = width; }
public float getSliceWidth() {
return sliceWidth;
}
public float getSliceThickness() { return sliceThickness; }
public void setSliceThickness(float thickness) { this.sliceThickness = thickness; }
public void setSliceWidth(float width) {
this.sliceWidth = width;
}
public boolean isPiercing() { return piercing; }
public void setPiercing(boolean piercing) { this.piercing = piercing; }
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 ====================
@@ -28,8 +28,6 @@ import kamkeel.npcs.network.packets.data.UpdateAnimationsPacket;
import kamkeel.npcs.network.packets.data.VillagerListPacket;
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
import kamkeel.npcs.network.packets.data.gui.GuiClosePacket;
import kamkeel.npcs.network.packets.data.gui.GuiErrorPacket;
import kamkeel.npcs.network.packets.data.gui.GuiOpenBookPacket;
@@ -53,6 +51,9 @@ import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket;
import kamkeel.npcs.network.packets.data.script.ScriptOverlayClosePacket;
import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket;
import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
import kamkeel.npcs.network.packets.player.AuctionActionPacket;
import kamkeel.npcs.network.packets.player.BankActionPacket;
import kamkeel.npcs.network.packets.player.CheckPlayerValue;
import kamkeel.npcs.network.packets.player.CompanionActionPacket;
@@ -61,7 +62,6 @@ import kamkeel.npcs.network.packets.player.FollowerPacket;
import kamkeel.npcs.network.packets.player.GetNPCRole;
import kamkeel.npcs.network.packets.player.GetTraderData;
import kamkeel.npcs.network.packets.player.InputDevicePacket;
import kamkeel.npcs.network.packets.player.AuctionActionPacket;
import kamkeel.npcs.network.packets.player.MailActionPacket;
import kamkeel.npcs.network.packets.player.SaveBookPacket;
import kamkeel.npcs.network.packets.player.SaveSignPacket;
@@ -94,10 +94,14 @@ import kamkeel.npcs.network.packets.request.TraderMarketSavePacket;
import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket;
import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket;
import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcSavePacket;
import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket;
import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket;
import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket;
import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket;
import kamkeel.npcs.network.packets.request.animation.AnimationGetPacket;
import kamkeel.npcs.network.packets.request.animation.AnimationRemovePacket;
@@ -112,8 +116,8 @@ import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket;
import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket;
import kamkeel.npcs.network.packets.request.clone.CloneMovePacket;
import kamkeel.npcs.network.packets.request.clone.CloneListPacket;
import kamkeel.npcs.network.packets.request.clone.CloneMovePacket;
import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket;
import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket;
import kamkeel.npcs.network.packets.request.clone.CloneSavePacket;
@@ -519,6 +523,10 @@ public class PacketHandler {
REQUEST_PACKET.registerPacket(new CustomAbilityRemovePacket());
REQUEST_PACKET.registerPacket(new CustomAbilitySavePacket());
REQUEST_PACKET.registerPacket(new BuiltInAbilityGetPacket());
REQUEST_PACKET.registerPacket(new ChainedAbilitiesGetPacket());
REQUEST_PACKET.registerPacket(new ChainedAbilityGetPacket());
REQUEST_PACKET.registerPacket(new ChainedAbilitySavePacket());
REQUEST_PACKET.registerPacket(new ChainedAbilityRemovePacket());
// Other Packets
REQUEST_PACKET.registerPacket(new IsGuiOpenInform());
@@ -206,6 +206,10 @@ public enum EnumRequestPacket {
CustomAbilityRemove,
CustomAbilitySave,
BuiltInAbilityGet,
ChainedAbilitiesGet,
ChainedAbilityGet,
ChainedAbilitySave,
ChainedAbilityRemove,
Hammer,
RecipeScript,
@@ -13,5 +13,6 @@ public enum EnumSyncType {
CARPENTRY_RECIPES,
ANVIL_RECIPES,
CUSTOM_EFFECTS,
CUSTOM_ABILITY
CUSTOM_ABILITY,
CHAINED_ABILITY
}
@@ -15,8 +15,8 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.client.AuctionClientConfig;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.config.ConfigMain;
import noppes.npcs.client.ScriptClientConfig;
import noppes.npcs.config.ConfigMain;
import noppes.npcs.controllers.AuctionConfigSync;
import noppes.npcs.controllers.ScriptConfigSync;
@@ -16,7 +16,7 @@ import java.io.IOException;
/**
* Lightweight packet syncing ability lock state from server to client.
* Sent every tick during ability execution when flags change, and once when ability ends.
*
* <p>
* Flags byte layout:
* bit 0: movement locked (suppress WASD, zero motion)
* bit 1: rotation locked (freeze yaw/pitch)
@@ -17,6 +17,7 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.NoppesUtilServer;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.constants.EnumAuctionPage;
import noppes.npcs.constants.EnumGuiType;
import noppes.npcs.constants.EnumRoleType;
import noppes.npcs.controllers.AuctionController;
@@ -39,7 +40,8 @@ public class AuctionActionPacket extends AbstractPacket {
private long amount;
private NBTTagCompound data;
public AuctionActionPacket() {}
public AuctionActionPacket() {
}
private AuctionActionPacket(Action action) {
this.action = action;
@@ -57,7 +59,9 @@ public class AuctionActionPacket extends AbstractPacket {
}
@Override
public boolean needsNPC() { return false; } // Allow opening via command without NPC
public boolean needsNPC() {
return false;
} // Allow opening via command without NPC
// =========================================
// Client-side static methods
@@ -116,7 +120,9 @@ public class AuctionActionPacket extends AbstractPacket {
PacketClient.sendClient(packet);
}
/** Request listings from server with filter and page */
/**
* Request listings from server with filter and page
*/
@SideOnly(Side.CLIENT)
public static void requestListings(AuctionFilter filter, int page) {
AuctionActionPacket packet = new AuctionActionPacket(Action.RequestListings);
@@ -130,10 +136,14 @@ public class AuctionActionPacket extends AbstractPacket {
// =========================================
@Override
public Enum getType() { return EnumPlayerPacket.AuctionAction; }
public Enum getType() {
return EnumPlayerPacket.AuctionAction;
}
@Override
public PacketChannel getChannel() { return PacketHandler.PLAYER_PACKET; }
public PacketChannel getChannel() {
return PacketHandler.PLAYER_PACKET;
}
@Override
@SideOnly(Side.CLIENT)
@@ -290,7 +300,10 @@ public class AuctionActionPacket extends AbstractPacket {
long startingPrice = in.readLong();
String buyoutStr = ByteBufUtils.readString(in);
long buyoutPrice = 0;
try { buyoutPrice = Long.parseLong(buyoutStr); } catch (NumberFormatException ignored) {}
try {
buyoutPrice = Long.parseLong(buyoutStr);
} catch (NumberFormatException ignored) {
}
NBTTagCompound itemNBT = ByteBufUtils.readNBT(in);
if (itemNBT == null) return "No item data received.";
@@ -315,13 +328,8 @@ public class AuctionActionPacket extends AbstractPacket {
private void handleOpenPage(ByteBuf in, EntityPlayerMP player) {
int page = in.readInt();
EnumGuiType guiType;
switch (page) {
case 0: guiType = EnumGuiType.PlayerAuction; break;
case 1: guiType = EnumGuiType.PlayerAuctionSell; break;
case 2: guiType = EnumGuiType.PlayerAuctionTrades; break;
default: guiType = EnumGuiType.PlayerAuction; break;
}
EnumAuctionPage auctionPage = EnumAuctionPage.fromOrdinal(page);
EnumGuiType guiType = auctionPage.getGuiType();
NoppesUtilServer.sendOpenGui(player, guiType, npc);
// Send trades data when opening the trades page
@@ -333,7 +341,9 @@ public class AuctionActionPacket extends AbstractPacket {
}
}
/** Send full trades data to player (listings, bids, claims) */
/**
* Send full trades data to player (listings, bids, claims)
*/
private void sendTradesData(EntityPlayerMP player, AuctionController controller) {
NBTTagCompound response = new NBTTagCompound();
response.setBoolean("TradesData", true);
@@ -481,6 +491,9 @@ public class AuctionActionPacket extends AbstractPacket {
} else {
// Need only part of stack
stack.splitStack(needed);
if (stack.stackSize <= 0) {
player.inventory.setInventorySlotContents(i, null);
}
needed = 0;
}
}
@@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -0,0 +1,51 @@
package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import noppes.npcs.NoppesUtilServer;
import java.io.IOException;
/**
* Request packet to get list of all chained ability names.
*/
public final class ChainedAbilitiesGetPacket extends AbstractPacket {
public static String packetName = "Request|ChainedAbilitiesGet";
public ChainedAbilitiesGetPacket() {
}
@Override
public Enum getType() {
return EnumRequestPacket.ChainedAbilitiesGet;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
return;
NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player);
}
}
@@ -0,0 +1,68 @@
package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import java.io.IOException;
/**
* Request packet to get a specific chained ability by name.
*/
public final class ChainedAbilityGetPacket extends AbstractPacket {
public static String packetName = "Request|ChainedAbilityGet";
private String name;
public ChainedAbilityGetPacket() {
}
public ChainedAbilityGetPacket(String name) {
this.name = name;
}
@Override
public Enum getType() {
return EnumRequestPacket.ChainedAbilityGet;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeString(out, name);
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
return;
String chainName = ByteBufUtils.readString(in);
ChainedAbility chain = AbilityController.Instance.getChainedAbility(chainName);
if (chain != null) {
NBTTagCompound compound = chain.writeNBT();
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
}
}
}
@@ -0,0 +1,69 @@
package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.NoppesUtilServer;
import java.io.IOException;
/**
* Request packet to remove a chained ability by name.
*/
public final class ChainedAbilityRemovePacket extends AbstractPacket {
public static String packetName = "Request|ChainedAbilityRemove";
private String name;
public ChainedAbilityRemovePacket() {
}
public ChainedAbilityRemovePacket(String name) {
this.name = name;
}
@Override
public Enum getType() {
return EnumRequestPacket.ChainedAbilityRemove;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@Override
public CustomNpcsPermissions.Permission getPermission() {
return CustomNpcsPermissions.GLOBAL_ABILITY;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeString(out, name);
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
return;
String chainName = ByteBufUtils.readString(in);
AbilityController.Instance.deleteChainedAbility(chainName);
NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player);
}
}
@@ -0,0 +1,79 @@
package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.NoppesUtilServer;
import java.io.IOException;
/**
* Request packet to save a chained ability.
*/
public final class ChainedAbilitySavePacket extends AbstractPacket {
public static String packetName = "Request|ChainedAbilitySave";
private NBTTagCompound chainNBT;
public ChainedAbilitySavePacket() {
}
public ChainedAbilitySavePacket(NBTTagCompound chainNBT) {
this.chainNBT = chainNBT;
}
@Override
public Enum getType() {
return EnumRequestPacket.ChainedAbilitySave;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@Override
public CustomNpcsPermissions.Permission getPermission() {
return CustomNpcsPermissions.GLOBAL_ABILITY;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeNBT(out, chainNBT);
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
return;
NBTTagCompound compound = ByteBufUtils.readNBT(in);
if (compound == null) return;
ChainedAbility chain = new ChainedAbility();
chain.readNBT(compound);
if (chain.getName() != null && !chain.getName().isEmpty()) {
AbilityController.Instance.saveChainedAbility(chain);
NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, chain.writeNBT());
}
}
}
@@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -3,7 +3,7 @@ package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -15,7 +15,6 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.BuiltInAnimation;
import java.io.IOException;
@@ -10,10 +10,10 @@ import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import kamkeel.npcs.util.ByteBufUtils;
import noppes.npcs.controllers.ServerTagMapController;
import noppes.npcs.controllers.data.TagMap;
@@ -1,7 +1,5 @@
package kamkeel.npcs.network.packets.request.script;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
@@ -411,6 +411,7 @@ public class AnchorPointHelper {
/**
* Rotate an offset vector by the entity's body yaw so offsets are entity-relative.
* +X = entity's right, +Y = up, +Z = entity's forward.
*
* @param bodyYawRad body yaw in radians
*/
private static Vec3 rotateOffsetByYaw(double offsetX, double offsetY, double offsetZ, float bodyYawRad) {
@@ -444,19 +445,19 @@ public class AnchorPointHelper {
* Apply FULL_MODEL rotation to a world-space offset from entity position.
* Converts the offset to model space, applies FULL_MODEL rotation and pivots,
* then converts back to world space.
*
* <p>
* The rendering pipeline is: entity_pos * body_yaw * scale(-1,-1,1) * FULL_MODEL * parts
* Combined body_yaw + scale maps model space to world space:
* worldX = mx*cos(yaw) + mz*sin(yaw)
* worldY = -my (model Y-down → world Y-up)
* worldZ = mx*sin(yaw) - mz*cos(yaw)
* worldX = mx*cos(yaw) + mz*sin(yaw)
* worldY = -my (model Y-down → world Y-up)
* worldZ = mx*sin(yaw) - mz*cos(yaw)
* This transform is its own inverse for X,Z.
*
* <p>
* FULL_MODEL rotation order matches renderer: vertex sees Z, then Y, then X.
*/
private static Vec3 applyFullModelToWorldOffset(FramePart fullModel, EntityLivingBase entity,
double worldDX, double worldDY, double worldDZ,
float bodyYaw, float scale) {
double worldDX, double worldDY, double worldDZ,
float bodyYaw, float scale) {
float[] rotations = getRotations(fullModel, entity);
float[] pivots = getPivots(fullModel, entity);
@@ -98,6 +98,7 @@ public class BukkitUtil {
/**
* Checks if a specific Bukkit plugin is enabled.
*
* @param pluginName the plugin name to check
* @return true if the plugin is enabled
*/
@@ -115,6 +116,7 @@ public class BukkitUtil {
/**
* Gets a Bukkit OfflinePlayer by name.
*
* @param playerName the player name
* @return the OfflinePlayer object, or null if not available
*/
@@ -131,6 +133,7 @@ public class BukkitUtil {
/**
* Gets a Bukkit Player by name (online players only).
*
* @param playerName the player name
* @return the Player object, or null if not online or not available
*/
@@ -147,6 +150,7 @@ public class BukkitUtil {
/**
* Gets a service provider from Bukkit's ServicesManager.
*
* @param serviceClass the service class to get
* @return the service provider, or null if not available
*/
@@ -1,13 +1,19 @@
package kamkeel.npcs.util;
import kamkeel.npcs.controllers.AbilityController;
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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -68,8 +74,7 @@ public class Register<T> {
}
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);
return registerVariant(typeId, variantName, () -> new AbilityVariant(variantName, group, configurator));
}
public AbilityVariant registerVariant(String typeId, String factoryName, Supplier<AbilityVariant> factory) {
Loaded 100 of 207 files, more files were not shown because too many files have changed in this diff. Show more