mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 05:27:37 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
commit
513956062d
68 files changed
+2634
-246
No files matched your search
+1
-1
Submodule src/api updated: 9fbd1eceff...4e96425314.
@@ -81,70 +81,95 @@ public abstract class RenderEnergy extends Render {
|
||||
* Render a cube centered at origin with given color and alpha.
|
||||
*/
|
||||
protected void renderCube(int color, float alpha, float halfSize) {
|
||||
float[] rgb = extractRGB(color);
|
||||
float r = rgb[0], g = rgb[1], b = rgb[2];
|
||||
|
||||
Tessellator tess = Tessellator.instance;
|
||||
float r = ((color >> 16) & 0xFF) / 255.0f;
|
||||
float g = ((color >> 8) & 0xFF) / 255.0f;
|
||||
float b = (color & 0xFF) / 255.0f;
|
||||
|
||||
// Front face (z+)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
addCubeVertices(tess, 0, 0, 0, halfSize);
|
||||
tess.draw();
|
||||
}
|
||||
|
||||
// ==================== BATCHED CUBE RENDERING ====================
|
||||
|
||||
/**
|
||||
* Begin a batched cube draw call. All cubes added via {@link #addBatchedCube}
|
||||
* will be drawn in a single tessellator pass when {@link #endCubeBatch} is called.
|
||||
*/
|
||||
protected void beginCubeBatch() {
|
||||
Tessellator.instance.startDrawingQuads();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a cube to the current batch at the given position with per-cube color/alpha.
|
||||
* Must be called between {@link #beginCubeBatch} and {@link #endCubeBatch}.
|
||||
*/
|
||||
protected void addBatchedCube(float px, float py, float pz, float halfSize,
|
||||
float r, float g, float b, float alpha) {
|
||||
Tessellator tess = Tessellator.instance;
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
addCubeVertices(tess, px, py, pz, halfSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* End the batched cube draw call, flushing all queued cubes in one draw.
|
||||
*/
|
||||
protected void endCubeBatch() {
|
||||
Tessellator.instance.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the 24 vertices (6 faces) of a cube centered at (px,py,pz) to the tessellator.
|
||||
* Color must be set before calling. Used by both single and batched cube rendering.
|
||||
*/
|
||||
private static void addCubeVertices(Tessellator tess, float px, float py, float pz, float h) {
|
||||
float x0 = px - h, x1 = px + h;
|
||||
float y0 = py - h, y1 = py + h;
|
||||
float z0 = pz - h, z1 = pz + h;
|
||||
|
||||
// Front (z+)
|
||||
tess.setNormal(0, 0, 1);
|
||||
tess.addVertex(-halfSize, -halfSize, halfSize);
|
||||
tess.addVertex(halfSize, -halfSize, halfSize);
|
||||
tess.addVertex(halfSize, halfSize, halfSize);
|
||||
tess.addVertex(-halfSize, halfSize, halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x0, y0, z1);
|
||||
tess.addVertex(x1, y0, z1);
|
||||
tess.addVertex(x1, y1, z1);
|
||||
tess.addVertex(x0, y1, z1);
|
||||
|
||||
// Back face (z-)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
// Back (z-)
|
||||
tess.setNormal(0, 0, -1);
|
||||
tess.addVertex(halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(-halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(-halfSize, halfSize, -halfSize);
|
||||
tess.addVertex(halfSize, halfSize, -halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x1, y0, z0);
|
||||
tess.addVertex(x0, y0, z0);
|
||||
tess.addVertex(x0, y1, z0);
|
||||
tess.addVertex(x1, y1, z0);
|
||||
|
||||
// Top face (y+)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
// Top (y+)
|
||||
tess.setNormal(0, 1, 0);
|
||||
tess.addVertex(-halfSize, halfSize, halfSize);
|
||||
tess.addVertex(halfSize, halfSize, halfSize);
|
||||
tess.addVertex(halfSize, halfSize, -halfSize);
|
||||
tess.addVertex(-halfSize, halfSize, -halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x0, y1, z1);
|
||||
tess.addVertex(x1, y1, z1);
|
||||
tess.addVertex(x1, y1, z0);
|
||||
tess.addVertex(x0, y1, z0);
|
||||
|
||||
// Bottom face (y-)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
// Bottom (y-)
|
||||
tess.setNormal(0, -1, 0);
|
||||
tess.addVertex(-halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(halfSize, -halfSize, halfSize);
|
||||
tess.addVertex(-halfSize, -halfSize, halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x0, y0, z0);
|
||||
tess.addVertex(x1, y0, z0);
|
||||
tess.addVertex(x1, y0, z1);
|
||||
tess.addVertex(x0, y0, z1);
|
||||
|
||||
// Right face (x+)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
// Right (x+)
|
||||
tess.setNormal(1, 0, 0);
|
||||
tess.addVertex(halfSize, -halfSize, halfSize);
|
||||
tess.addVertex(halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(halfSize, halfSize, -halfSize);
|
||||
tess.addVertex(halfSize, halfSize, halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x1, y0, z1);
|
||||
tess.addVertex(x1, y0, z0);
|
||||
tess.addVertex(x1, y1, z0);
|
||||
tess.addVertex(x1, y1, z1);
|
||||
|
||||
// Left face (x-)
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
// Left (x-)
|
||||
tess.setNormal(-1, 0, 0);
|
||||
tess.addVertex(-halfSize, -halfSize, -halfSize);
|
||||
tess.addVertex(-halfSize, -halfSize, halfSize);
|
||||
tess.addVertex(-halfSize, halfSize, halfSize);
|
||||
tess.addVertex(-halfSize, halfSize, -halfSize);
|
||||
tess.draw();
|
||||
tess.addVertex(x0, y0, z0);
|
||||
tess.addVertex(x0, y0, z1);
|
||||
tess.addVertex(x0, y1, z1);
|
||||
tess.addVertex(x0, y1, z0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.lwjgl.opengl.GL11;
|
||||
/**
|
||||
* Voxel-style renderer for {@link EntityEnergyExplosion}.
|
||||
* Draws expanding cubic shells with a short fade-out.
|
||||
* Uses batched cube rendering for performance — all cubes of each layer
|
||||
* are drawn in a single tessellator pass instead of per-cube draw calls.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderEnergyExplosion extends RenderEnergy {
|
||||
@@ -55,44 +57,73 @@ public class RenderEnergyExplosion extends RenderEnergy {
|
||||
int steps = Math.max(2, Math.min(6, (int) Math.ceil(radius * 0.65f) + 1));
|
||||
float cell = radius / steps;
|
||||
float cubeHalf = Math.max(0.04f, cell * 0.22f * (0.90f - life * 0.25f));
|
||||
float outerScale = hasOuter ? 1.0f + explosion.getOuterColorWidth() * 0.35f : 1.0f;
|
||||
long seed = explosion.getRenderSeed();
|
||||
|
||||
// Pre-extract RGB for batched rendering
|
||||
float outerR = ((outerColor >> 16) & 0xFF) / 255.0f;
|
||||
float outerG = ((outerColor >> 8) & 0xFF) / 255.0f;
|
||||
float outerB = (outerColor & 0xFF) / 255.0f;
|
||||
float innerR = ((innerColor >> 16) & 0xFF) / 255.0f;
|
||||
float innerG = ((innerColor >> 8) & 0xFF) / 255.0f;
|
||||
float innerB = (innerColor & 0xFF) / 255.0f;
|
||||
|
||||
GL11.glDepthMask(false);
|
||||
|
||||
// Smoke layer — single batched pass
|
||||
renderSmokeVoxelShell(radius, life, seed);
|
||||
|
||||
for (int ix = -steps; ix <= steps; ix++) {
|
||||
for (int iy = -steps; iy <= steps; iy++) {
|
||||
for (int iz = -steps; iz <= steps; iz++) {
|
||||
int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz)));
|
||||
if (edge != steps) continue;
|
||||
if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue;
|
||||
// Outer color layer — single batched pass
|
||||
if (hasOuter) {
|
||||
float outerHalf = cubeHalf * outerScale;
|
||||
float oa = outerAlpha * fade;
|
||||
beginCubeBatch();
|
||||
for (int ix = -steps; ix <= steps; ix++) {
|
||||
for (int iy = -steps; iy <= steps; iy++) {
|
||||
for (int iz = -steps; iz <= steps; iz++) {
|
||||
int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz)));
|
||||
if (edge != steps) continue;
|
||||
if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue;
|
||||
|
||||
float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f;
|
||||
float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f;
|
||||
float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f;
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(px, py, pz);
|
||||
|
||||
if (hasOuter) {
|
||||
GL11.glPushMatrix();
|
||||
float outerScale = 1.0f + explosion.getOuterColorWidth() * 0.35f;
|
||||
GL11.glScalef(outerScale, outerScale, outerScale);
|
||||
renderCube(outerColor, outerAlpha * fade, cubeHalf);
|
||||
GL11.glPopMatrix();
|
||||
float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f;
|
||||
float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f;
|
||||
float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f;
|
||||
addBatchedCube(px, py, pz, outerHalf, outerR, outerG, outerB, oa);
|
||||
}
|
||||
|
||||
renderCube(innerColor, innerAlpha * fade, cubeHalf * 0.9f);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
}
|
||||
endCubeBatch();
|
||||
}
|
||||
|
||||
// Inner color layer — single batched pass
|
||||
{
|
||||
float innerHalf = cubeHalf * 0.9f;
|
||||
float ia = innerAlpha * fade;
|
||||
beginCubeBatch();
|
||||
for (int ix = -steps; ix <= steps; ix++) {
|
||||
for (int iy = -steps; iy <= steps; iy++) {
|
||||
for (int iz = -steps; iz <= steps; iz++) {
|
||||
int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz)));
|
||||
if (edge != steps) continue;
|
||||
if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue;
|
||||
|
||||
float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f;
|
||||
float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f;
|
||||
float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f;
|
||||
addBatchedCube(px, py, pz, innerHalf, innerR, innerG, innerB, ia);
|
||||
}
|
||||
}
|
||||
}
|
||||
endCubeBatch();
|
||||
}
|
||||
|
||||
renderCoreFlash(innerColor, innerAlpha, radius, cubeHalf, life);
|
||||
GL11.glDepthMask(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds large translucent white/gray cubes to fake a smoky volumetric expansion.
|
||||
* All smoke cubes are batched into a single draw call.
|
||||
*/
|
||||
private void renderSmokeVoxelShell(float radius, float life, long seed) {
|
||||
int smokeSteps = Math.max(2, Math.min(5, (int) Math.ceil(radius * 0.5f) + 1));
|
||||
@@ -101,6 +132,8 @@ public class RenderEnergyExplosion extends RenderEnergy {
|
||||
float smokeCubeHalf = Math.max(0.07f, smokeCell * 0.24f * (0.95f - life * 0.25f));
|
||||
float smokeAlpha = Math.min(MAX_SMOKE_CUBE_ALPHA, (0.15f + (1.0f - life) * 0.55f));
|
||||
|
||||
// White = 1,1,1 Gray (0xDCDCDC) = 0.863,0.863,0.863
|
||||
beginCubeBatch();
|
||||
for (int ix = -smokeSteps; ix <= smokeSteps; ix++) {
|
||||
for (int iy = -smokeSteps; iy <= smokeSteps; iy++) {
|
||||
for (int iz = -smokeSteps; iz <= smokeSteps; iz++) {
|
||||
@@ -115,16 +148,14 @@ public class RenderEnergyExplosion extends RenderEnergy {
|
||||
float py = iy * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 2) * smokeJitter;
|
||||
float pz = iz * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 3) * smokeJitter;
|
||||
|
||||
int smokeColor = ((h >>> 9) & 1) == 0 ? 0xFFFFFF : 0xDCDCDC;
|
||||
boolean isWhite = ((h >>> 9) & 1) == 0;
|
||||
float gray = isWhite ? 1.0f : 0.863f;
|
||||
float alpha = smokeAlpha * (0.78f + (((h >>> 10) & 15) / 15.0f) * 0.22f);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(px, py, pz);
|
||||
renderCube(smokeColor, alpha, smokeCubeHalf);
|
||||
GL11.glPopMatrix();
|
||||
addBatchedCube(px, py, pz, smokeCubeHalf, gray, gray, gray, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
endCubeBatch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,11 +169,8 @@ public class RenderEnergyExplosion extends RenderEnergy {
|
||||
float whiteAlpha = Math.min(1.0f, 0.25f + flash * 0.95f);
|
||||
float innerFlashAlpha = Math.min(1.0f, innerAlpha * (0.45f + flash * 0.95f));
|
||||
|
||||
GL11.glPushMatrix();
|
||||
renderCube(0xFFFFFF, whiteAlpha, coreHalf * 1.45f);
|
||||
GL11.glScalef(0.82f, 0.82f, 0.82f);
|
||||
renderCube(innerColor, innerFlashAlpha, coreHalf);
|
||||
GL11.glPopMatrix();
|
||||
renderCube(innerColor, innerFlashAlpha, coreHalf * 0.82f);
|
||||
}
|
||||
|
||||
private float saturate(float value) {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package kamkeel.npcs.controllers;
|
||||
|
||||
import kamkeel.npcs.controllers.data.energy.IEnergyHandler;
|
||||
import kamkeel.npcs.entity.*;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.api.IEnergyController;
|
||||
import noppes.npcs.api.IWorld;
|
||||
import noppes.npcs.api.entity.*;
|
||||
import noppes.npcs.api.entity.IEnergyExplosion;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyExplosion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Central controller for script-created energy entities and handler registration.
|
||||
* Provides factory methods for creating all energy entity types and routes
|
||||
* damage through registered handlers when entities have customDamageData
|
||||
* but no sourceAbility.
|
||||
*/
|
||||
public class EnergyController implements IEnergyController {
|
||||
|
||||
public static EnergyController Instance = new EnergyController();
|
||||
|
||||
private final List<IEnergyHandler> handlers = new ArrayList<>();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// HANDLER REGISTRATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public void registerHandler(IEnergyHandler handler) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
|
||||
public List<IEnergyHandler> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// DAMAGE ROUTING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Fire onEnergyDamage on all handlers. Chain of responsibility — first true wins.
|
||||
*/
|
||||
public boolean fireOnEnergyDamage(Entity energyEntity, EntityLivingBase owner,
|
||||
EntityLivingBase target, float damage,
|
||||
float knockback, float knockbackUp,
|
||||
double kbDirX, double kbDirZ,
|
||||
float damageMultiplier,
|
||||
NBTTagCompound damageData) {
|
||||
for (IEnergyHandler handler : handlers) {
|
||||
if (handler.onEnergyDamage(energyEntity, owner, target, damage,
|
||||
knockback, knockbackUp, kbDirX, kbDirZ, damageMultiplier, damageData)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire modifyEnergyDamage on all handlers. Cumulative — each handler's output feeds the next.
|
||||
*/
|
||||
public float fireModifyEnergyDamage(Entity energyEntity, EntityLivingBase owner,
|
||||
float baseDamage, NBTTagCompound damageData) {
|
||||
float damage = baseDamage;
|
||||
for (IEnergyHandler handler : handlers) {
|
||||
damage = handler.modifyEnergyDamage(energyEntity, owner, damage, damageData);
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// INTERNAL FACTORY METHODS (MC types)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public EntityAbilityOrb createOrbInternal(World world, Entity owner, double x, double y, double z, float size) {
|
||||
EntityAbilityOrb entity = new EntityAbilityOrb(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setStartPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
entity.setProjectileSize(size);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityAbilityBeam createBeamInternal(World world, Entity owner, double x, double y, double z,
|
||||
float beamWidth, float headSize) {
|
||||
EntityAbilityBeam entity = new EntityAbilityBeam(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setStartPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
entity.setBeamWidth(beamWidth);
|
||||
entity.setHeadSize(headSize);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityAbilityDisc createDiscInternal(World world, Entity owner, double x, double y, double z,
|
||||
float radius, float thickness) {
|
||||
EntityAbilityDisc entity = new EntityAbilityDisc(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setStartPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
entity.setDiscRadius(radius);
|
||||
entity.setDiscThickness(thickness);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityAbilityLaser createLaserInternal(World world, Entity owner, double x, double y, double z,
|
||||
float laserWidth) {
|
||||
EntityAbilityLaser entity = new EntityAbilityLaser(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setStartPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
entity.setLaserWidth(laserWidth);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityAbilityZone createHazardInternal(World world, Entity owner, double x, double y, double z) {
|
||||
EntityAbilityZone entity = new EntityAbilityZone(world);
|
||||
entity.initAsHazard(owner, x, y, z);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityAbilityZone createTrapInternal(World world, Entity owner, double x, double y, double z) {
|
||||
EntityAbilityZone entity = new EntityAbilityZone(world);
|
||||
entity.initAsTrap(owner, x, y, z);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityEnergySweeper createSweeperInternal(World world, Entity owner, double x, double y, double z) {
|
||||
EntityEnergySweeper entity = new EntityEnergySweeper(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityEnergyPanel createPanelInternal(World world, Entity owner, double x, double y, double z) {
|
||||
EntityEnergyPanel entity = new EntityEnergyPanel(world);
|
||||
entity.setPosition(x, y, z);
|
||||
entity.setOwnerEntityId(owner.getEntityId());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public EntityEnergyExplosion createExplosionInternal(World world, Entity owner, double x, double y, double z, float radius) {
|
||||
return new EntityEnergyExplosion(world, owner, x, y, z, radius);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// IEnergyController (Script API methods)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
public IEnergyOrb createOrb(IWorld world, IEntity owner, double x, double y, double z, float size) {
|
||||
EntityAbilityOrb entity = createOrbInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, size);
|
||||
return (IEnergyOrb) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyBeam createBeam(IWorld world, IEntity owner, double x, double y, double z, float beamWidth, float headSize) {
|
||||
EntityAbilityBeam entity = createBeamInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, beamWidth, headSize);
|
||||
return (IEnergyBeam) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyDisc createDisc(IWorld world, IEntity owner, double x, double y, double z, float radius, float thickness) {
|
||||
EntityAbilityDisc entity = createDiscInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, radius, thickness);
|
||||
return (IEnergyDisc) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyLaser createLaser(IWorld world, IEntity owner, double x, double y, double z, float laserWidth) {
|
||||
EntityAbilityLaser entity = createLaserInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, laserWidth);
|
||||
return (IEnergyLaser) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyZone createHazard(IWorld world, IEntity owner, double x, double y, double z) {
|
||||
EntityAbilityZone entity = createHazardInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z);
|
||||
return (IEnergyZone) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyZone createTrap(IWorld world, IEntity owner, double x, double y, double z) {
|
||||
EntityAbilityZone entity = createTrapInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z);
|
||||
return (IEnergyZone) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergySweeper createSweeper(IWorld world, IEntity owner, double x, double y, double z) {
|
||||
EntityEnergySweeper entity = createSweeperInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z);
|
||||
return (IEnergySweeper) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyPanel createPanel(IWorld world, IEntity owner, double x, double y, double z) {
|
||||
EntityEnergyPanel entity = createPanelInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z);
|
||||
return (IEnergyPanel) NpcAPI.Instance().getIEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyExplosion createExplosion(IWorld world, IEntity owner, double x, double y, double z, float radius) {
|
||||
EntityEnergyExplosion entity = createExplosionInternal((World) world.getMCWorld(), owner.getMCEntity(), x, y, z, radius);
|
||||
return new ScriptEnergyExplosion<>(entity);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import kamkeel.npcs.entity.EntityEnergyPanel;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.FileNameHelper;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -37,6 +38,7 @@ import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.ability.IAbility;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
@@ -49,11 +51,18 @@ import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.MagicEntry;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityMagic;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Ability implements IAbility, IAbilityAction {
|
||||
|
||||
@@ -91,6 +100,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
protected LockMode lockMovement = LockMode.WINDUP;
|
||||
protected RotationMode rotationMode = RotationMode.FREE;
|
||||
protected LockMode rotationPhase = LockMode.WINDUP_AND_ACTIVE;
|
||||
protected float trackDelay = 0; // 0 = instant tracking (legacy). Higher = slower lerp toward target.
|
||||
protected int windUpColor = 0x80FF4400; // Telegraph color during wind up
|
||||
protected int activeColor = 0xC0FF0000; // Telegraph warning/active color
|
||||
|
||||
@@ -116,6 +126,9 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// Custom data for external mods
|
||||
protected NBTTagCompound customData = new NBTTagCompound();
|
||||
|
||||
// Magic data — defines magic types and damage splits for this ability
|
||||
protected MagicData magicData = new MagicData();
|
||||
|
||||
// User type restriction
|
||||
protected UserType allowedBy = UserType.BOTH;
|
||||
|
||||
@@ -280,6 +293,35 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this ability type uses magic data.
|
||||
* By default, any ability that deals damage also has magic.
|
||||
* Override in subclasses like Barrier that need magic but don't deal direct damage.
|
||||
*/
|
||||
public boolean hasMagic() {
|
||||
return hasDamage();
|
||||
}
|
||||
|
||||
// ── Display methods for inventory preview ──────────────────────
|
||||
|
||||
/** Base damage for preview display. Override in damaging types. */
|
||||
public float getDisplayDamage() { return 0; }
|
||||
|
||||
/** True if getDisplayDamage() is damage-per-second (e.g., Hazard). */
|
||||
public boolean isDisplayDamageDPS() { return false; }
|
||||
|
||||
/** Base barrier health for preview display. Override in barrier types. */
|
||||
public float getDisplayBarrierHealth() { return 0; }
|
||||
|
||||
/** True if this ability reflects projectiles. */
|
||||
public boolean isDisplayReflect() { return false; }
|
||||
|
||||
/** Reflection strength percentage (0-100). */
|
||||
public float getDisplayReflectStrength() { return 0; }
|
||||
|
||||
/** True if this ability absorbs projectiles. */
|
||||
public boolean isDisplayAbsorbing() { return false; }
|
||||
|
||||
/**
|
||||
* Override to provide variant templates for this ability type.
|
||||
* When a user creates this ability type, they will be shown a selection dialog
|
||||
@@ -471,13 +513,22 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this, caster, hitEntity, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ, 1.0f);
|
||||
if (!handled) {
|
||||
float finalDamage = damage;
|
||||
|
||||
// Apply magic pipeline for player and NPC casters
|
||||
MagicData resolved = resolveMagicData(caster);
|
||||
if (resolved != null && !resolved.isEmpty()) {
|
||||
finalDamage = AttributeAttackUtil.calculateAbilityDamage(
|
||||
caster, hitEntity, damage, resolved);
|
||||
}
|
||||
|
||||
// Default damage path
|
||||
if (caster instanceof EntityNPCInterface) {
|
||||
hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), damage);
|
||||
hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), finalDamage);
|
||||
} else if (caster instanceof EntityPlayer) {
|
||||
hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), damage);
|
||||
hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), finalDamage);
|
||||
} else {
|
||||
hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), damage);
|
||||
hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), finalDamage);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -687,6 +738,11 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
.hover("ability.hover.rotationPhase")
|
||||
.visibleWhen(() -> this.rotationMode != RotationMode.FREE)
|
||||
).tab("General"));
|
||||
defs.add(FieldDef.floatField("ability.trackDelay", this::getTrackDelay, this::setTrackDelay)
|
||||
.range(0.0f, 20.0f)
|
||||
.hover("ability.hover.trackDelay")
|
||||
.visibleWhen(() -> this.rotationMode == RotationMode.TRACK)
|
||||
.tab("General"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible)
|
||||
.hover("ability.hover.interruptible")
|
||||
@@ -731,6 +787,41 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Magic section ────────────────────────────────────────────
|
||||
if (hasMagic()) {
|
||||
defs.add(FieldDef.section("ability.section.magic").tab("General"));
|
||||
|
||||
defs.add(FieldDef.labelField("ability.magic.note", () -> {
|
||||
if (magicData.isEmpty()) {
|
||||
return StatCollector.translateToLocal("ability.magic.usesCaster");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<Integer, MagicEntry> entry : magicData.getMagics().entrySet()) {
|
||||
Magic magic = MagicController.getInstance().getMagic(entry.getKey());
|
||||
if (magic != null) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(magic.getDisplayName()).append(": ")
|
||||
.append(Math.round(entry.getValue().split * 100)).append("%");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}).tab("General"));
|
||||
|
||||
defs.add(FieldDef.subGuiField("ability.magic.editor",
|
||||
() -> new SubGuiAbilityMagic(magicData.copy()),
|
||||
gui -> {
|
||||
MagicData result = ((SubGuiAbilityMagic) gui).magicData;
|
||||
magicData.getMagics().clear();
|
||||
for (Map.Entry<Integer, MagicEntry> e : result.getMagics().entrySet()) {
|
||||
magicData.getMagics().put(e.getKey(), e.getValue());
|
||||
}
|
||||
})
|
||||
.buttonLabel(() -> magicData.isEmpty()
|
||||
? StatCollector.translateToLocal("ability.magic.usesCaster")
|
||||
: magicData.getMagics().size() + " Magic(s)")
|
||||
.tab("General"));
|
||||
}
|
||||
|
||||
// ── Target tab ───────────────────────────────────────────────
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.minRange", () -> (int) getMinRange(), v -> setMinRange(v)).range(0, 100),
|
||||
@@ -1388,6 +1479,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
nbt.setInteger("lockMovement", lockMovement.ordinal());
|
||||
nbt.setInteger("rotationMode", rotationMode.ordinal());
|
||||
nbt.setInteger("rotationPhase", rotationPhase.ordinal());
|
||||
nbt.setFloat("trackDelay", trackDelay);
|
||||
nbt.setInteger("windUpColor", windUpColor);
|
||||
nbt.setInteger("activeColor", activeColor);
|
||||
nbt.setString("windUpSound", windUpSound);
|
||||
@@ -1403,6 +1495,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
nbt.setString("telegraphType", telegraphType.name());
|
||||
nbt.setFloat("telegraphHeightOffset", telegraphHeightOffset);
|
||||
nbt.setTag("customData", (NBTTagCompound) customData.copy());
|
||||
magicData.writeToNBT(nbt);
|
||||
nbt.setInteger("allowedBy", allowedBy.ordinal());
|
||||
nbt.setBoolean("ignoreCooldown", ignoreCooldown);
|
||||
nbt.setBoolean("perAbilityCooldown", perAbilityCooldown);
|
||||
@@ -1482,6 +1575,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement"));
|
||||
rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode"));
|
||||
rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase"));
|
||||
trackDelay = nbt.hasKey("trackDelay") ? nbt.getFloat("trackDelay") : 0;
|
||||
windUpColor = nbt.hasKey("windUpColor") ? nbt.getInteger("windUpColor") : 0x80FF4400;
|
||||
activeColor = nbt.hasKey("activeColor") ? nbt.getInteger("activeColor") : 0xC0FF0000;
|
||||
windUpSound = nbt.getString("windUpSound");
|
||||
@@ -1500,6 +1594,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
}
|
||||
telegraphHeightOffset = nbt.getFloat("telegraphHeightOffset");
|
||||
customData = (NBTTagCompound) nbt.getCompoundTag("customData").copy();
|
||||
magicData.readToNBT(nbt);
|
||||
allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy"));
|
||||
ignoreCooldown = nbt.getBoolean("ignoreCooldown");
|
||||
perAbilityCooldown = nbt.getBoolean("perAbilityCooldown");
|
||||
@@ -1817,6 +1912,14 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.rotationPhase = rotationPhase;
|
||||
}
|
||||
|
||||
public float getTrackDelay() {
|
||||
return trackDelay;
|
||||
}
|
||||
|
||||
public void setTrackDelay(float trackDelay) {
|
||||
this.trackDelay = trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Get lock movement type as integer.
|
||||
*
|
||||
@@ -1877,6 +1980,25 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.rotationPhase = LockMode.fromOrdinal(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Get track delay for TRACK rotation mode.
|
||||
* 0 = instant tracking (legacy). Higher = slower lerp toward target.
|
||||
*/
|
||||
@Override
|
||||
public float getTrackDelayValue() {
|
||||
return trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Set track delay for TRACK rotation mode.
|
||||
*
|
||||
* @param delay 0 = instant tracking. Higher = slower lerp toward target.
|
||||
*/
|
||||
@Override
|
||||
public void setTrackDelayValue(float delay) {
|
||||
this.trackDelay = Math.max(0, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if movement (pathfinding/motion) should be locked during WINDUP phase.
|
||||
*/
|
||||
@@ -2170,6 +2292,35 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
return customData;
|
||||
}
|
||||
|
||||
public MagicData getMagicData() {
|
||||
return magicData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve magic data for this ability + caster combination.
|
||||
* If the ability has its own magic data, use that (override).
|
||||
* Otherwise, fall back to the caster's magic data (player or NPC).
|
||||
*
|
||||
* @return Resolved MagicData, or null if neither has magic
|
||||
*/
|
||||
public MagicData resolveMagicData(EntityLivingBase caster) {
|
||||
if (!this.magicData.isEmpty()) {
|
||||
return this.magicData;
|
||||
}
|
||||
if (caster instanceof EntityPlayer) {
|
||||
PlayerData data = PlayerData.get((EntityPlayer) caster);
|
||||
if (data != null && data.magicData != null && !data.magicData.isEmpty()) {
|
||||
return data.magicData;
|
||||
}
|
||||
} else if (caster instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) caster;
|
||||
if (npc.stats != null && npc.stats.magicData != null && !npc.stats.magicData.isEmpty()) {
|
||||
return npc.stats.magicData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AbilityCondition> getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
@@ -276,6 +276,9 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
@@ -375,6 +375,9 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
|
||||
zoneHeight,
|
||||
particleDensity, particleScale, animSpeed, lightningDensity,
|
||||
getEffects());
|
||||
entity.setSourceAbility(this);
|
||||
|
||||
applyVisualToEntity(entity);
|
||||
|
||||
@@ -126,6 +127,12 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
|
||||
this.damagePerSecond = damagePerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damagePerSecond; }
|
||||
|
||||
@Override
|
||||
public boolean isDisplayDamageDPS() { return true; }
|
||||
|
||||
public int getDamageInterval() {
|
||||
return damageInterval;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,9 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
@@ -293,6 +293,9 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
@@ -197,6 +197,9 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public boolean isAoe() {
|
||||
return aoe;
|
||||
}
|
||||
|
||||
@@ -751,6 +751,9 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
@@ -536,6 +536,9 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getDamageRadius() {
|
||||
return damageRadius;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
particleDensity, particleScale, animSpeed, lightningDensity,
|
||||
visible,
|
||||
getEffects());
|
||||
entity.setSourceAbility(this);
|
||||
|
||||
applyVisualToEntity(entity);
|
||||
|
||||
@@ -269,6 +270,9 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getDamageRadius() {
|
||||
return damageRadius;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import kamkeel.npcs.controllers.data.ability.enums.TargetFilter;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.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.AxisAlignedBB;
|
||||
@@ -23,7 +24,6 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Vortex ability: Pulls targets toward the caster.
|
||||
@@ -39,8 +39,8 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
private boolean damageOnPull = false;
|
||||
private float pullDamage = 0.0f;
|
||||
|
||||
// Runtime state
|
||||
private transient Map<UUID, PullState> pulledEntities;
|
||||
// Runtime state — keyed by entity ID (not UUID, since cloned NPCs share UUIDs)
|
||||
private transient Map<Integer, PullState> pulledEntities;
|
||||
private transient boolean pullComplete = false;
|
||||
private transient int ticksSincePullDamage = 0;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<UUID, PullState> getPulledEntities() {
|
||||
private Map<Integer, PullState> getPulledEntities() {
|
||||
if (pulledEntities == null) {
|
||||
pulledEntities = new HashMap<>();
|
||||
}
|
||||
@@ -115,7 +115,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
|
||||
double dist = caster.getDistanceToEntity(entity);
|
||||
if (dist <= pullRadius) {
|
||||
getPulledEntities().put(entity.getUniqueID(), new PullState(entity));
|
||||
getPulledEntities().put(entity.getEntityId(), new PullState(entity));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
if (!isPlayerCaster(caster) && target != null && !target.isDead) {
|
||||
double dist = caster.getDistanceToEntity(target);
|
||||
if (dist <= pullRadius) {
|
||||
getPulledEntities().put(target.getUniqueID(), new PullState(target));
|
||||
getPulledEntities().put(target.getEntityId(), new PullState(target));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
}
|
||||
if (!validTargets.isEmpty()) {
|
||||
EntityLivingBase chosen = validTargets.get(caster.worldObj.rand.nextInt(validTargets.size()));
|
||||
getPulledEntities().put(chosen.getUniqueID(), new PullState(chosen));
|
||||
getPulledEntities().put(chosen.getEntityId(), new PullState(chosen));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,8 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
// Safety cap: force-complete if active too long
|
||||
int maxActiveTicks = Math.max(40, (int)(pullRadius / pullStrength * 3));
|
||||
if (tick >= maxActiveTicks) {
|
||||
for (Map.Entry<UUID, PullState> entry : new HashMap<>(getPulledEntities()).entrySet()) {
|
||||
EntityLivingBase entity = findEntity(caster, caster.worldObj, entry.getKey());
|
||||
for (Map.Entry<Integer, PullState> entry : new HashMap<>(getPulledEntities()).entrySet()) {
|
||||
EntityLivingBase entity = findEntity(caster.worldObj, entry.getKey());
|
||||
if (entity != null && !entity.isDead) {
|
||||
onTargetArrived(caster, entity, caster.worldObj);
|
||||
}
|
||||
@@ -191,13 +191,13 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
ticksSincePullDamage = 0;
|
||||
}
|
||||
|
||||
for (Map.Entry<UUID, PullState> entry : new HashMap<>(getPulledEntities()).entrySet()) {
|
||||
UUID uuid = entry.getKey();
|
||||
for (Map.Entry<Integer, PullState> entry : new HashMap<>(getPulledEntities()).entrySet()) {
|
||||
int entityId = entry.getKey();
|
||||
PullState state = entry.getValue();
|
||||
|
||||
EntityLivingBase entity = findEntity(caster, caster.worldObj, uuid);
|
||||
EntityLivingBase entity = findEntity(caster.worldObj, entityId);
|
||||
if (entity == null || entity.isDead) {
|
||||
getPulledEntities().remove(uuid);
|
||||
getPulledEntities().remove(entityId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= 1.5) {
|
||||
getPulledEntities().remove(uuid);
|
||||
getPulledEntities().remove(entityId);
|
||||
onTargetArrived(caster, entity, caster.worldObj);
|
||||
continue;
|
||||
}
|
||||
@@ -228,13 +228,19 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
|
||||
if (state.stuckTicks >= 5) {
|
||||
// Entity is stuck (wall, block, partial obstruction) - treat as arrived
|
||||
getPulledEntities().remove(uuid);
|
||||
getPulledEntities().remove(entityId);
|
||||
onTargetArrived(caster, entity, caster.worldObj);
|
||||
continue;
|
||||
}
|
||||
|
||||
anyStillPulling = true;
|
||||
|
||||
// Record position BEFORE movement so next tick's stuck detection
|
||||
// can measure the actual progress (pull + AI movement combined)
|
||||
state.lastX = entity.posX;
|
||||
state.lastY = entity.posY;
|
||||
state.lastZ = entity.posZ;
|
||||
|
||||
// Clamp speed to never exceed half the remaining distance, preventing overshoot/slingshot
|
||||
double maxSpeed = dist * 0.5;
|
||||
double effectiveSpeed = Math.min(pullStrength, maxSpeed);
|
||||
@@ -257,11 +263,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
entity.moveEntity(motionX, motionY, motionZ);
|
||||
}
|
||||
|
||||
// Update tracked position for next tick's stuck detection
|
||||
state.lastX = entity.posX;
|
||||
state.lastY = entity.posY;
|
||||
state.lastZ = entity.posZ;
|
||||
|
||||
if (shouldDealPullDamage) {
|
||||
applyAbilityDamage(caster, entity, pullDamage * 0.5f, 0);
|
||||
}
|
||||
@@ -284,20 +285,12 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entity by UUID within the vortex pull area.
|
||||
* Uses AABB search instead of iterating all loaded entities.
|
||||
* Search area is expanded beyond pullRadius to account for entity drift.
|
||||
* Find an entity by entity ID.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntityLivingBase findEntity(EntityLivingBase caster, World world, UUID uuid) {
|
||||
double searchRadius = pullRadius + 5;
|
||||
AxisAlignedBB searchBox = caster.boundingBox.expand(searchRadius, searchRadius / 2, searchRadius);
|
||||
List<EntityLivingBase> nearbyEntities = world.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
|
||||
|
||||
for (EntityLivingBase entity : nearbyEntities) {
|
||||
if (entity.getUniqueID().equals(uuid)) {
|
||||
return entity;
|
||||
}
|
||||
private EntityLivingBase findEntity(World world, int entityId) {
|
||||
Entity entity = world.getEntityByID(entityId);
|
||||
if (entity instanceof EntityLivingBase) {
|
||||
return (EntityLivingBase) entity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -356,6 +349,9 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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.controllers.data.MagicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -69,6 +70,11 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
return false; // Barriers are defensive
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMagic() {
|
||||
return true; // Barriers use magic for defense interactions
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowFreeOnCast() {
|
||||
return true;
|
||||
@@ -86,6 +92,7 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
if (tick == 1) {
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
inheritMagicData(barrierEntity, caster);
|
||||
applyBarrierHealthModifiers(caster);
|
||||
barrierEntity.setupCharging(getWindUpTicks());
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
@@ -102,12 +109,20 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
// No windup or entity died — create fresh
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
inheritMagicData(barrierEntity, caster);
|
||||
applyBarrierHealthModifiers(caster);
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void inheritMagicData(EntityEnergyBarrier entity, EntityLivingBase caster) {
|
||||
MagicData resolved = resolveMagicData(caster);
|
||||
if (resolved != null) {
|
||||
entity.setMagicData(resolved.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (barrierEntity == null || barrierEntity.isDead) {
|
||||
@@ -198,6 +213,18 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
barrierData.useHealth = useHealth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayBarrierHealth() { return barrierData.useHealth ? barrierData.maxHealth : 0; }
|
||||
|
||||
@Override
|
||||
public boolean isDisplayReflect() { return barrierData.reflect; }
|
||||
|
||||
@Override
|
||||
public float getDisplayReflectStrength() { return barrierData.reflectStrengthPct; }
|
||||
|
||||
@Override
|
||||
public boolean isDisplayAbsorbing() { return barrierData.absorbing; }
|
||||
|
||||
public int getBarrierDuration() {
|
||||
return barrierData.durationTicks;
|
||||
}
|
||||
|
||||
+8
@@ -25,6 +25,7 @@ import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Vec3;
|
||||
import noppes.npcs.api.ability.type.IAbilityEnergyProjectile;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -214,6 +215,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
entity.setEffects(this.effects);
|
||||
entity.setIgnoreIFrames(this.isIgnoreIFrames());
|
||||
entity.setSourceAbility(this);
|
||||
MagicData resolvedMagic = resolveMagicData(caster);
|
||||
if (resolvedMagic != null) {
|
||||
entity.setMagicData(resolvedMagic.copy());
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -634,6 +639,9 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
combatData.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return combatData.damage; }
|
||||
|
||||
@Override
|
||||
public float getKnockback() {
|
||||
return combatData.knockback;
|
||||
|
||||
@@ -49,6 +49,7 @@ public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> im
|
||||
this.lockMovement = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.rotationMode = RotationMode.TRACK;
|
||||
this.rotationPhase = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.trackDelay = 3;
|
||||
this.telegraphType = TelegraphType.LINE;
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Laser_Windup";
|
||||
|
||||
@@ -235,6 +235,9 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return damage; }
|
||||
|
||||
public int getDamageInterval() {
|
||||
return damageInterval;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,9 @@ public class AbilityWall extends AbilityBarrier {
|
||||
panelData.setLaunchDamage(damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDisplayDamage() { return panelData.launching ? panelData.launchDamage : 0; }
|
||||
|
||||
public float getLaunchKnockback() {
|
||||
return panelData.launchKnockback;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package kamkeel.npcs.controllers.data.energy;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
/**
|
||||
* Extension point for handling energy entity damage without a sourceAbility.
|
||||
* Registered with EnergyController. DBC Addon implements this to route
|
||||
* script-created energy entity damage through the DBC damage pipeline.
|
||||
*/
|
||||
public interface IEnergyHandler {
|
||||
|
||||
/**
|
||||
* Handle damage for an energy entity with custom damage data.
|
||||
* Chain of Responsibility — first handler returning true claims the damage.
|
||||
*
|
||||
* @param energyEntity The energy entity dealing damage
|
||||
* @param owner The entity that owns/created the energy entity
|
||||
* @param target The entity being damaged
|
||||
* @param damage Base damage amount
|
||||
* @param knockback Horizontal knockback strength
|
||||
* @param knockbackUp Vertical knockback strength
|
||||
* @param kbDirX Knockback direction X
|
||||
* @param kbDirZ Knockback direction Z
|
||||
* @param damageMultiplier Damage multiplier (e.g. falloff)
|
||||
* @param damageData Custom damage configuration NBT (e.g. DBC stats)
|
||||
* @return true if this handler processed the damage, false to pass to next handler
|
||||
*/
|
||||
boolean onEnergyDamage(Entity energyEntity, EntityLivingBase owner,
|
||||
EntityLivingBase target, float damage,
|
||||
float knockback, float knockbackUp,
|
||||
double kbDirX, double kbDirZ,
|
||||
float damageMultiplier,
|
||||
NBTTagCompound damageData);
|
||||
|
||||
/**
|
||||
* Modify outgoing damage for an energy entity with custom damage data.
|
||||
* Cumulative — each handler receives the previous handler's output.
|
||||
*
|
||||
* @param energyEntity The energy entity dealing damage
|
||||
* @param owner The entity that owns/created the energy entity
|
||||
* @param baseDamage Current damage value (after previous handlers)
|
||||
* @param damageData Custom damage configuration NBT
|
||||
* @return Modified damage value
|
||||
*/
|
||||
float modifyEnergyDamage(Entity energyEntity, EntityLivingBase owner,
|
||||
float baseDamage, NBTTagCompound damageData);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -224,11 +225,16 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
dirZ = 0.0;
|
||||
}
|
||||
|
||||
// Keep origin centered on look vector while active and clear owner bbox.
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
// Lower beam origin slightly so it doesn't obscure the player's crosshair
|
||||
posY -= 0.15;
|
||||
// Position: use anchor if launchFromAnchor, otherwise use look vector position
|
||||
if (anchorData.launchFromAnchor) {
|
||||
Vec3 anchorPos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData);
|
||||
setPosition(anchorPos.xCoord, anchorPos.yCoord, anchorPos.zCoord);
|
||||
} else {
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
// Lower beam origin slightly so it doesn't obscure the player's crosshair
|
||||
posY -= 0.15;
|
||||
}
|
||||
syncStartPositionToCurrent();
|
||||
syncPositionStateToCurrent(false);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
|
||||
@@ -24,7 +26,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Shared zone entity for Trap and Hazard abilities.
|
||||
@@ -166,12 +167,21 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
private transient boolean armed = false;
|
||||
private transient int triggerCount = 0;
|
||||
private transient int ticksSinceLastTrigger = 0;
|
||||
private transient Set<UUID> triggeredEntities = new HashSet<>();
|
||||
private transient Set<Integer> triggeredEntities = new HashSet<>();
|
||||
private transient int triggerFlashTick = -1;
|
||||
|
||||
private transient int ticksSinceDamage = 0;
|
||||
private transient Set<Integer> damagedThisTick = new HashSet<>();
|
||||
|
||||
private transient Ability sourceAbility = null;
|
||||
|
||||
/**
|
||||
* Custom damage data for script-created zones that don't have a sourceAbility.
|
||||
* Used by EnergyController handlers (e.g. DBC Addon) to carry damage configuration
|
||||
* directly on the entity. Synced via spawn data for client sync.
|
||||
*/
|
||||
private NBTTagCompound customDamageData = null;
|
||||
|
||||
private boolean previewMode = false;
|
||||
private EntityLivingBase previewOwner = null;
|
||||
|
||||
@@ -197,6 +207,30 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
this.setPosition(x, groundY, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize as a hazard zone with default values. Used by EnergyController factory.
|
||||
* Scripts can then configure properties before calling spawn().
|
||||
*/
|
||||
public void initAsHazard(Entity owner, double x, double y, double z) {
|
||||
this.zoneType = ZoneType.HAZARD;
|
||||
this.ownerEntityId = owner.getEntityId();
|
||||
double groundY = Ability.findGroundLevel(worldObj, x, y, z) + GROUND_OFFSET;
|
||||
this.setPosition(x, groundY, z);
|
||||
this.ticksSinceDamage = damageInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize as a trap zone with default values. Used by EnergyController factory.
|
||||
* Scripts can then configure properties before calling spawn().
|
||||
*/
|
||||
public void initAsTrap(Entity owner, double x, double y, double z) {
|
||||
this.zoneType = ZoneType.TRAP;
|
||||
this.ownerEntityId = owner.getEntityId();
|
||||
double groundY = Ability.findGroundLevel(worldObj, x, y, z) + GROUND_OFFSET;
|
||||
this.setPosition(x, groundY, z);
|
||||
this.ticksSinceLastTrigger = triggerCooldown;
|
||||
}
|
||||
|
||||
public static EntityAbilityZone createTrap(World world, EntityLivingBase owner,
|
||||
double x, double y, double z,
|
||||
ZoneShape shape,
|
||||
@@ -360,7 +394,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
for (EntityLivingBase entity : entities) {
|
||||
if (owner != null && entity == owner) continue;
|
||||
if (entity.isDead) continue;
|
||||
if (maxTriggers == 1 && triggeredEntities.contains(entity.getUniqueID())) continue;
|
||||
if (maxTriggers == 1 && triggeredEntities.contains(entity.getEntityId())) continue;
|
||||
if (owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
|
||||
|
||||
double dx = entity.posX - posX;
|
||||
@@ -439,7 +473,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
if (anyDamaged) {
|
||||
triggerCount++;
|
||||
ticksSinceLastTrigger = 0;
|
||||
triggeredEntities.add(triggerer.getUniqueID());
|
||||
triggeredEntities.add(triggerer.getEntityId());
|
||||
triggerFlashTick = ticksExisted;
|
||||
worldObj.setEntityState(this, TRIGGER_FLASH_STATUS);
|
||||
}
|
||||
@@ -505,15 +539,36 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
|
||||
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
return target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
return target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
return target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg);
|
||||
} else {
|
||||
return target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
|
||||
// Route through ability extender (e.g. DBC Addon damage scaling)
|
||||
boolean handled = false;
|
||||
if (sourceAbility != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
dmg, 0.0f, 0.0f, dx, dz, 1.0f);
|
||||
}
|
||||
// Fallback: route through EnergyController for script-created entities with custom damage data
|
||||
if (!handled && customDamageData != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = EnergyController.Instance.fireOnEnergyDamage(
|
||||
this, (EntityLivingBase) owner, target,
|
||||
dmg, 0.0f, 0.0f, dx, dz, 1.0f, customDamageData);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
return target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
return target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
return target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg);
|
||||
} else {
|
||||
return target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime);
|
||||
}
|
||||
@@ -541,6 +596,22 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
this.previewOwner = owner;
|
||||
}
|
||||
|
||||
public Ability getSourceAbility() {
|
||||
return sourceAbility;
|
||||
}
|
||||
|
||||
public void setSourceAbility(Ability ability) {
|
||||
this.sourceAbility = ability;
|
||||
}
|
||||
|
||||
public NBTTagCompound getCustomDamageData() {
|
||||
return customDamageData;
|
||||
}
|
||||
|
||||
public void setCustomDamageData(NBTTagCompound data) {
|
||||
this.customDamageData = data;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// RENDERING SUPPORT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -714,6 +785,51 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
this.particleGlow = particleGlow;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PUBLIC SETTERS (for EnergyController / scripting API)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public void setShape(ZoneShape shape) { this.shape = shape; }
|
||||
public void setRadius(float radius) { this.radius = radius; }
|
||||
public void setZoneHeight(float height) { this.zoneHeight = height; }
|
||||
public void setDurationTicks(int ticks) { this.durationTicks = ticks; this.maxTicks = ticks; }
|
||||
public void setInnerColor(int color) { this.innerColor = color; }
|
||||
public void setOuterColor(int color) { this.outerColor = color; }
|
||||
public void setOuterColorEnabled(boolean enabled) { this.outerColorEnabled = enabled; }
|
||||
public void setParticleDensity(float density) { this.particleDensity = density; }
|
||||
public void setParticleScale(float scale) { this.particleScale = scale; }
|
||||
public void setAnimSpeed(float speed) { this.animSpeed = speed; }
|
||||
public void setIgnoreIFrames(boolean ignore) { this.ignoreIFrames = ignore; }
|
||||
|
||||
// Hazard-specific
|
||||
public float getDamagePerSecond() { return damagePerSecond; }
|
||||
public void setDamagePerSecond(float dps) { this.damagePerSecond = dps; }
|
||||
public int getDamageInterval() { return damageInterval; }
|
||||
public void setDamageInterval(int ticks) { this.damageInterval = ticks; }
|
||||
public boolean isAffectsCaster() { return affectsCaster; }
|
||||
public void setAffectsCaster(boolean affects) { this.affectsCaster = affects; }
|
||||
|
||||
// Trap-specific
|
||||
public float getTriggerRadius() { return triggerRadius; }
|
||||
public void setTriggerRadius(float radius) { this.triggerRadius = radius; }
|
||||
public int getArmTime() { return armTime; }
|
||||
public void setArmTime(int ticks) { this.armTime = ticks; }
|
||||
public int getMaxTriggers() { return maxTriggers; }
|
||||
public void setMaxTriggers(int max) { this.maxTriggers = max; }
|
||||
public int getTriggerCooldown() { return triggerCooldown; }
|
||||
public void setTriggerCooldown(int ticks) { this.triggerCooldown = ticks; }
|
||||
public float getDamage() { return damage; }
|
||||
public void setDamage(float damage) { this.damage = damage; }
|
||||
public float getDamageRadius() { return damageRadius; }
|
||||
public void setDamageRadius(float radius) { this.damageRadius = radius; }
|
||||
public float getKnockback() { return knockback; }
|
||||
public void setKnockback(float knockback) { this.knockback = knockback; }
|
||||
public void setVisible(boolean visible) { this.visible = visible; }
|
||||
|
||||
public boolean isIgnoreIFrames() { return ignoreIFrames; }
|
||||
|
||||
public int getOwnerEntityId() { return ownerEntityId; }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// NBT SERIALIZATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -781,6 +897,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
} catch (java.io.IOException e) {
|
||||
noppes.npcs.LogWriter.error("Error writing zone effects spawn data", e);
|
||||
}
|
||||
|
||||
// Custom damage data
|
||||
boolean hasDamageData = customDamageData != null;
|
||||
buffer.writeBoolean(hasDamageData);
|
||||
if (hasDamageData) {
|
||||
try {
|
||||
ByteBufUtils.writeNBT(buffer, customDamageData);
|
||||
} catch (java.io.IOException e) {
|
||||
noppes.npcs.LogWriter.error("Error writing zone custom damage data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -843,5 +970,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom damage data
|
||||
if (buffer.readableBytes() > 0 && buffer.readBoolean()) {
|
||||
try {
|
||||
this.customDamageData = ByteBufUtils.readNBT(buffer);
|
||||
} catch (java.io.IOException e) {
|
||||
noppes.npcs.LogWriter.error("Error reading zone custom damage data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
|
||||
/**
|
||||
* Universal base class for all energy ability entities (Projectiles, Barriers, Sweeper).
|
||||
@@ -43,6 +44,17 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
* The ability that spawned this entity. Transient, not saved to NBT.
|
||||
*/
|
||||
protected transient Ability sourceAbility = null;
|
||||
|
||||
/**
|
||||
* Custom damage data for script-created entities that don't have a sourceAbility.
|
||||
* Used by EnergyController handlers (e.g. DBC Addon) to carry damage configuration
|
||||
* directly on the entity. Persistent — saved to spawn data for client sync.
|
||||
*/
|
||||
protected NBTTagCompound customDamageData = null;
|
||||
|
||||
/** Magic data for this entity — defines magic types for outgoing damage or barrier defense. */
|
||||
protected MagicData magicData = new MagicData();
|
||||
|
||||
protected boolean ignoreIFrames = false;
|
||||
protected boolean previewMode = false;
|
||||
protected EntityLivingBase previewOwner = null;
|
||||
@@ -141,6 +153,24 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
this.sourceAbility = ability;
|
||||
}
|
||||
|
||||
// ==================== CUSTOM DAMAGE DATA ====================
|
||||
|
||||
public NBTTagCompound getCustomDamageData() {
|
||||
return customDamageData;
|
||||
}
|
||||
|
||||
public void setCustomDamageData(NBTTagCompound data) {
|
||||
this.customDamageData = data;
|
||||
}
|
||||
|
||||
public MagicData getMagicData() {
|
||||
return magicData;
|
||||
}
|
||||
|
||||
public void setMagicData(MagicData data) {
|
||||
this.magicData = data != null ? data : new MagicData();
|
||||
}
|
||||
|
||||
public boolean isIgnoreIFrames() {
|
||||
return ignoreIFrames;
|
||||
}
|
||||
@@ -325,6 +355,10 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
nbt.setBoolean("IgnoreIFrames", ignoreIFrames);
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
if (customDamageData != null) {
|
||||
nbt.setTag("CustomDamageData", customDamageData);
|
||||
}
|
||||
magicData.writeToNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,6 +370,10 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
this.ignoreIFrames = nbt.getBoolean("IgnoreIFrames");
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
if (nbt.hasKey("CustomDamageData")) {
|
||||
this.customDamageData = nbt.getCompoundTag("CustomDamageData");
|
||||
}
|
||||
magicData.readToNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== SPAWN DATA ====================
|
||||
|
||||
@@ -2,6 +2,7 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
@@ -12,6 +13,8 @@ import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -248,6 +251,13 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
float multiplier = barrierData.getMultiplier(typeId);
|
||||
float finalDamage = damage * multiplier;
|
||||
|
||||
// Apply magic interactions if projectile is a CNPC+ energy entity
|
||||
if (projectileEntity instanceof EntityEnergyAbility) {
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
((EntityEnergyAbility) projectileEntity).getMagicData(), this.magicData);
|
||||
finalDamage *= magicMultiplier;
|
||||
}
|
||||
|
||||
if (!worldObj.isRemote) {
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, finalDamage);
|
||||
if (eventDamage < 0) {
|
||||
@@ -329,6 +339,11 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
float multiplier = barrierData.getMultiplier(typeId);
|
||||
float damage = baseDamage * multiplier;
|
||||
|
||||
// Apply magic interactions (e.g., Water projectile vs Fire barrier)
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
projectile.getMagicData(), this.magicData);
|
||||
damage *= magicMultiplier;
|
||||
|
||||
// Fire hit event (may cancel or modify damage)
|
||||
if (!worldObj.isRemote) {
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage);
|
||||
@@ -419,6 +434,24 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
|
||||
float damage = amount * barrierData.meleeDamageMultiplier;
|
||||
|
||||
// Apply magic interactions from attacker's magic vs barrier's magic
|
||||
Entity attacker = source.getEntity();
|
||||
if (attacker != null) {
|
||||
MagicData attackerMagic = null;
|
||||
if (attacker instanceof EntityPlayer) {
|
||||
PlayerData data = PlayerData.get((EntityPlayer) attacker);
|
||||
if (data != null) attackerMagic = data.magicData;
|
||||
} else if (attacker instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) attacker;
|
||||
if (npc.stats != null) attackerMagic = npc.stats.magicData;
|
||||
}
|
||||
if (attackerMagic != null) {
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
attackerMagic, this.magicData);
|
||||
damage *= magicMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire hit event with null projectile (melee hit)
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, damage);
|
||||
if (eventDamage < 0) return false;
|
||||
|
||||
@@ -2,16 +2,28 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Short-lived visual explosion entity for energy projectile impacts.
|
||||
* This is render-only and never applies damage itself.
|
||||
* Short-lived explosion entity for energy projectile impacts and standalone API use.
|
||||
* Renders an expanding voxel shell. When damage is enabled, applies area damage on the first tick.
|
||||
*/
|
||||
public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
|
||||
@@ -21,12 +33,23 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
private float renderRadius = 0.0f;
|
||||
private long renderSeed = 0L;
|
||||
|
||||
// ==================== DAMAGE (for standalone API use) ====================
|
||||
private float damage = 0f;
|
||||
private float knockback = 0f;
|
||||
private float knockbackUp = 0.1f;
|
||||
private float damageFalloff = 0.5f;
|
||||
private boolean damageEnabled = false;
|
||||
private boolean hasDamaged = false;
|
||||
|
||||
public EntityEnergyExplosion(World world) {
|
||||
super(world);
|
||||
this.noClip = true;
|
||||
this.setSize(0.1f, 0.1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for projectile-spawned explosions (render-only, no damage).
|
||||
*/
|
||||
public EntityEnergyExplosion(World world, EntityEnergyProjectile source, float radius) {
|
||||
this(world);
|
||||
|
||||
@@ -43,6 +66,19 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
this.renderSeed = world != null ? world.rand.nextLong() : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for standalone API-spawned explosions.
|
||||
*/
|
||||
public EntityEnergyExplosion(World world, Entity owner, double x, double y, double z, float radius) {
|
||||
this(world);
|
||||
if (owner != null) {
|
||||
this.ownerEntityId = owner.getEntityId();
|
||||
}
|
||||
this.setPosition(x, y, z);
|
||||
setExplosionRadius(radius);
|
||||
this.renderSeed = world != null ? world.rand.nextLong() : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
super.entityInit();
|
||||
@@ -58,6 +94,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
float eased = 1.0f - (1.0f - progress) * (1.0f - progress);
|
||||
this.renderRadius = maxRadius * eased;
|
||||
|
||||
// Apply damage on first server tick
|
||||
if (damageEnabled && !hasDamaged && !worldObj.isRemote && ticksExisted >= 1) {
|
||||
applyExplosionDamage();
|
||||
hasDamaged = true;
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
{
|
||||
boolean isClient = worldObj.isRemote;
|
||||
@@ -74,12 +116,110 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DAMAGE LOGIC ====================
|
||||
|
||||
private void applyExplosionDamage() {
|
||||
if (damage <= 0 || maxRadius <= 0) return;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
double explosionRadSq = maxRadius * maxRadius;
|
||||
|
||||
AxisAlignedBB explosionBox = AxisAlignedBB.getBoundingBox(
|
||||
posX - maxRadius, posY - maxRadius, posZ - maxRadius,
|
||||
posX + maxRadius, posY + maxRadius, posZ + maxRadius
|
||||
);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<EntityLivingBase> targets = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, explosionBox);
|
||||
|
||||
for (EntityLivingBase target : targets) {
|
||||
if (target == owner) continue;
|
||||
if (target.boundingBox == null) continue;
|
||||
|
||||
double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX));
|
||||
double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY));
|
||||
double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ));
|
||||
double dx = closestX - posX;
|
||||
double dy = closestY - posY;
|
||||
double dz = closestZ - posZ;
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
if (distSq > explosionRadSq) continue;
|
||||
|
||||
float falloff = 1.0f;
|
||||
if (damageFalloff != 0.0f && distSq > 0.0D) {
|
||||
float dist = (float) Math.sqrt(distSq);
|
||||
falloff = 1.0f - (dist / maxRadius) * damageFalloff;
|
||||
}
|
||||
|
||||
float finalDmg = damage * falloff;
|
||||
float finalKb = knockback * falloff;
|
||||
|
||||
// Route through EnergyController for addon integration (e.g. DBC)
|
||||
boolean handled = false;
|
||||
if (customDamageData != null && owner instanceof EntityLivingBase) {
|
||||
double kbDirX = target.posX - posX;
|
||||
double kbDirZ = target.posZ - posZ;
|
||||
handled = EnergyController.Instance.fireOnEnergyDamage(
|
||||
this, (EntityLivingBase) owner, target,
|
||||
finalDmg, finalKb, knockbackUp, kbDirX, kbDirZ, 1.0f, customDamageData);
|
||||
}
|
||||
|
||||
int prevHurtResist = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (!handled) {
|
||||
float dmgToApply = finalDmg;
|
||||
|
||||
// Magic pipeline
|
||||
if (this.magicData != null && !this.magicData.isEmpty() && owner instanceof EntityLivingBase) {
|
||||
dmgToApply = AttributeAttackUtil.calculateAbilityDamage(
|
||||
(EntityLivingBase) owner, target, finalDmg, this.magicData);
|
||||
}
|
||||
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmgToApply);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmgToApply);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmgToApply);
|
||||
} else {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmgToApply);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, prevHurtResist);
|
||||
}
|
||||
|
||||
// Apply knockback
|
||||
if (finalKb > 0) {
|
||||
double kbDx = target.posX - posX;
|
||||
double kbDz = target.posZ - posZ;
|
||||
double len = Math.sqrt(kbDx * kbDx + kbDz * kbDz);
|
||||
if (len > 0) {
|
||||
target.addVelocity((kbDx / len) * finalKb * 0.5, knockbackUp, (kbDz / len) * finalKb * 0.5);
|
||||
target.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// ==================== RADIUS / DURATION ====================
|
||||
|
||||
public void setExplosionRadius(float radius) {
|
||||
this.maxRadius = Math.max(0.5f, sanitize(radius, 2.0f, MAX_ENTITY_RADIUS));
|
||||
this.durationTicks = Math.max(6, Math.min(18, (int) (6 + this.maxRadius * 1.8f)));
|
||||
this.setSize(this.maxRadius * 2.0f, this.maxRadius * 2.0f);
|
||||
}
|
||||
|
||||
public float getExplosionRadius() {
|
||||
return maxRadius;
|
||||
}
|
||||
|
||||
public int getDurationTicks() {
|
||||
return durationTicks;
|
||||
}
|
||||
|
||||
public float getInterpolatedRadius(float partialTicks) {
|
||||
return this.prevRenderRadius + (this.renderRadius - this.prevRenderRadius) * partialTicks;
|
||||
}
|
||||
@@ -94,6 +234,47 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
return renderSeed;
|
||||
}
|
||||
|
||||
// ==================== DAMAGE GETTERS/SETTERS ====================
|
||||
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
public void setDamage(float damage) {
|
||||
this.damage = Math.max(0, damage);
|
||||
this.damageEnabled = this.damage > 0;
|
||||
}
|
||||
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
public void setKnockback(float knockback) {
|
||||
this.knockback = Math.max(0, knockback);
|
||||
}
|
||||
|
||||
public float getKnockbackUp() {
|
||||
return knockbackUp;
|
||||
}
|
||||
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
this.knockbackUp = knockbackUp;
|
||||
}
|
||||
|
||||
public float getDamageFalloff() {
|
||||
return damageFalloff;
|
||||
}
|
||||
|
||||
public void setDamageFalloff(float falloff) {
|
||||
this.damageFalloff = MathHelper.clamp_float(falloff, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public boolean isDamageEnabled() {
|
||||
return damageEnabled;
|
||||
}
|
||||
|
||||
// ==================== RENDER / COLLISION ====================
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public boolean isInRangeToRenderDist(double distance) {
|
||||
@@ -106,6 +287,8 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
||||
// Intentionally empty - transient visual entity.
|
||||
@@ -125,6 +308,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
nbt.setFloat("ExplosionRadius", maxRadius);
|
||||
nbt.setInteger("ExplosionDuration", durationTicks);
|
||||
nbt.setLong("ExplosionSeed", renderSeed);
|
||||
if (damageEnabled) {
|
||||
nbt.setFloat("Damage", damage);
|
||||
nbt.setFloat("Knockback", knockback);
|
||||
nbt.setFloat("KnockbackUp", knockbackUp);
|
||||
nbt.setFloat("DamageFalloff", damageFalloff);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,5 +335,12 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
this.renderSeed = nbt.hasKey("ExplosionSeed") ? nbt.getLong("ExplosionSeed") : 0L;
|
||||
this.renderRadius = 0.0f;
|
||||
this.prevRenderRadius = 0.0f;
|
||||
if (nbt.hasKey("Damage")) {
|
||||
this.damage = nbt.getFloat("Damage");
|
||||
this.knockback = nbt.getFloat("Knockback");
|
||||
this.knockbackUp = nbt.getFloat("KnockbackUp");
|
||||
this.damageFalloff = nbt.getFloat("DamageFalloff");
|
||||
this.damageEnabled = this.damage > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
@@ -217,12 +219,32 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
|
||||
if (panelData.launchDamage > 0) {
|
||||
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage);
|
||||
} else {
|
||||
target.attackEntityFrom(DamageSource.generic, panelData.launchDamage);
|
||||
// Route through ability extender (e.g. DBC Addon damage scaling)
|
||||
boolean handled = false;
|
||||
if (sourceAbility != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
panelData.launchDamage, panelData.launchKnockback, 0.0f, dx, dz, 1.0f);
|
||||
}
|
||||
// Fallback: route through EnergyController for script-created entities with custom damage data
|
||||
if (!handled && customDamageData != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = EnergyController.Instance.fireOnEnergyDamage(
|
||||
this, (EntityLivingBase) owner, target,
|
||||
panelData.launchDamage, panelData.launchKnockback, 0.0f, dx, dz, 1.0f, customDamageData);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage);
|
||||
} else {
|
||||
target.attackEntityFrom(DamageSource.generic, panelData.launchDamage);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime);
|
||||
@@ -347,8 +369,25 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
|
||||
}
|
||||
}
|
||||
|
||||
// Test the UPCOMING movement so the barrier intercepts before entity
|
||||
// collision runs in updateProjectile().
|
||||
if (mode == PanelMode.LAUNCHED) {
|
||||
// For moving panels, use RELATIVE motion in the panel's reference frame.
|
||||
// This correctly captures the combined closing speed when both objects
|
||||
// are moving toward each other, preventing phase-through at high speeds.
|
||||
double relMotionX = projectile.motionX - this.motionX;
|
||||
double relMotionY = projectile.motionY - this.motionY;
|
||||
double relMotionZ = projectile.motionZ - this.motionZ;
|
||||
|
||||
double nextX = projectile.posX + relMotionX;
|
||||
double nextY = projectile.posY + relMotionY;
|
||||
double nextZ = projectile.posZ + relMotionZ;
|
||||
|
||||
return isIncomingRay(
|
||||
nextX, nextY, nextZ,
|
||||
projectile.posX, projectile.posY, projectile.posZ,
|
||||
projectile.getOwnerEntityId());
|
||||
}
|
||||
|
||||
// Non-moving panels: standard test using projectile's absolute motion
|
||||
double nextX = projectile.posX + projectile.motionX;
|
||||
double nextY = projectile.posY + projectile.motionY;
|
||||
double nextZ = projectile.posZ + projectile.motionZ;
|
||||
@@ -366,6 +405,16 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
|
||||
double prevPosX, double prevPosY, double prevPosZ,
|
||||
int ownerEntityId)
|
||||
{
|
||||
if (mode == PanelMode.LAUNCHED) {
|
||||
// Use relative motion for moving panels
|
||||
double relMotionX = motionX - this.motionX;
|
||||
double relMotionY = motionY - this.motionY;
|
||||
double relMotionZ = motionZ - this.motionZ;
|
||||
double adjX = prevPosX + relMotionX;
|
||||
double adjY = prevPosY + relMotionY;
|
||||
double adjZ = prevPosZ + relMotionZ;
|
||||
return isIncomingRay(adjX, adjY, adjZ, prevPosX, prevPosY, prevPosZ, ownerEntityId);
|
||||
}
|
||||
return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId);
|
||||
}
|
||||
|
||||
@@ -642,6 +691,15 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
|
||||
return panelData;
|
||||
}
|
||||
|
||||
public void setPanelYaw(float yaw) {
|
||||
this.panelYaw = yaw;
|
||||
this.prevPanelYaw = yaw;
|
||||
}
|
||||
|
||||
public void setMode(PanelMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package kamkeel.npcs.entity;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile;
|
||||
import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect;
|
||||
@@ -17,6 +18,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket;
|
||||
import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
@@ -38,6 +40,7 @@ import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.config.ConfigEnergy;
|
||||
import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.controllers.PartyController;
|
||||
import noppes.npcs.controllers.data.Party;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
@@ -926,19 +929,36 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
dmg, kb, kbUp, dx, dz, damageMultiplier);
|
||||
}
|
||||
// Fallback: route through EnergyController for script-created entities with custom damage data
|
||||
if (!handled && customDamageData != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
float kbUp = getKnockbackUp() > 0 ? getKnockbackUp() : 0.1f;
|
||||
handled = EnergyController.Instance.fireOnEnergyDamage(
|
||||
this, (EntityLivingBase) owner, target,
|
||||
dmg, kb, kbUp, dx, dz, damageMultiplier, customDamageData);
|
||||
}
|
||||
|
||||
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (!handled) {
|
||||
float finalDmg = dmg;
|
||||
|
||||
// Apply magic pipeline for player and NPC casters
|
||||
if (this.magicData != null && !this.magicData.isEmpty() && owner instanceof EntityLivingBase) {
|
||||
finalDmg = AttributeAttackUtil.calculateAbilityDamage(
|
||||
(EntityLivingBase) owner, target, dmg, this.magicData);
|
||||
}
|
||||
|
||||
// Default damage path
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), finalDmg);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), finalDmg);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), finalDmg);
|
||||
} else {
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), finalDmg);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -2,6 +2,8 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
@@ -312,14 +314,34 @@ public class EntityEnergySweeper extends EntityEnergyAbility {
|
||||
boolean ignoreIFrames = isIgnoreIFrames();
|
||||
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), damage);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), damage);
|
||||
} else {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", null), damage);
|
||||
// Route through ability extender (e.g. DBC Addon damage scaling)
|
||||
boolean handled = false;
|
||||
if (sourceAbility != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
damage, 0.0f, 0.2f, dx, dz, 1.0f);
|
||||
}
|
||||
// Fallback: route through EnergyController for script-created entities with custom damage data
|
||||
if (!handled && customDamageData != null && owner instanceof EntityLivingBase) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
handled = EnergyController.Instance.fireOnEnergyDamage(
|
||||
this, (EntityLivingBase) owner, target,
|
||||
damage, 0.0f, 0.2f, dx, dz, 1.0f, customDamageData);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), damage);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), damage);
|
||||
} else {
|
||||
target.attackEntityFrom(new NpcDamageSource("npc_ability", null), damage);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime);
|
||||
@@ -381,6 +403,24 @@ public class EntityEnergySweeper extends EntityEnergyAbility {
|
||||
return baseYaw;
|
||||
}
|
||||
|
||||
// ==================== PUBLIC SETTERS (for EnergyController / scripting API) ====================
|
||||
|
||||
public void setBeamLength(float length) { this.beamLength = length; }
|
||||
public void setBeamWidth(float width) { this.beamWidth = width; }
|
||||
public void setBeamHeight(float height) { this.beamHeight = height; }
|
||||
public float getDamage() { return damage; }
|
||||
public void setDamage(float damage) { this.damage = damage; }
|
||||
public int getDamageInterval() { return damageInterval; }
|
||||
public void setDamageInterval(int interval) { this.damageInterval = interval; }
|
||||
public boolean isPiercing() { return piercing; }
|
||||
public void setPiercing(boolean piercing) { this.piercing = piercing; }
|
||||
public float getSweepSpeed() { return sweepSpeed; }
|
||||
public void setSweepSpeed(float speed) { this.sweepSpeed = speed; }
|
||||
public int getNumberOfRotations() { return numberOfRotations; }
|
||||
public void setNumberOfRotations(int rotations) { this.numberOfRotations = rotations; }
|
||||
public boolean isLockOnTarget() { return lockOnTarget; }
|
||||
public void setLockOnTarget(boolean lock) { this.lockOnTarget = lock; }
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,10 +21,37 @@ import noppes.npcs.items.ItemNpcTool;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class PacketUtil {
|
||||
|
||||
// ==================== SCRIPT SESSION TOKEN SYSTEM ====================
|
||||
// Prevents saving script data before the client has received it from the server.
|
||||
// Each GET generates a token sent to the client; each SAVE must echo it back.
|
||||
|
||||
private static final Map<UUID, String> scriptSessionTokens = new ConcurrentHashMap<>();
|
||||
|
||||
public static String createScriptSession(EntityPlayerMP player) {
|
||||
String token = Long.toHexString(ThreadLocalRandom.current().nextLong());
|
||||
scriptSessionTokens.put(player.getUniqueID(), token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static boolean verifyScriptSession(EntityPlayer player, String token) {
|
||||
if (token == null || token.isEmpty())
|
||||
return false;
|
||||
String expected = scriptSessionTokens.get(player.getUniqueID());
|
||||
return expected != null && expected.equals(token);
|
||||
}
|
||||
|
||||
public static void clearScriptSession(EntityPlayer player) {
|
||||
scriptSessionTokens.remove(player.getUniqueID());
|
||||
}
|
||||
|
||||
public static boolean verifyItemPacket(String name, EnumItemPacketType type, EntityPlayer player) {
|
||||
if (player == null)
|
||||
return false;
|
||||
@@ -216,11 +243,14 @@ public class PacketUtil {
|
||||
}
|
||||
|
||||
public static void getScripts(IScriptHandler data, EntityPlayerMP player) {
|
||||
String token = createScriptSession(player);
|
||||
|
||||
NBTTagCompound compound = new NBTTagCompound();
|
||||
compound.setBoolean("ScriptEnabled", data.getEnabled());
|
||||
compound.setString("ScriptLanguage", data.getLanguage());
|
||||
compound.setTag("Languages", ScriptController.Instance.nbtLanguages());
|
||||
compound.setTag("ScriptConsole", NBTTags.NBTLongStringMap(data.getConsoleText()));
|
||||
compound.setString("ScriptSessionToken", token);
|
||||
GuiDataPacket.sendGuiData(player, compound);
|
||||
List<IScriptUnit> containers = data.getScripts();
|
||||
for (int i = 0; i < containers.size(); i++) {
|
||||
@@ -237,9 +267,18 @@ public class PacketUtil {
|
||||
GuiDataPacket.sendGuiData(player, loadComplete);
|
||||
}
|
||||
|
||||
public static void saveScripts(IScriptHandler data, ByteBuf buffer) throws IOException {
|
||||
public static boolean saveScripts(IScriptHandler data, ByteBuf buffer, EntityPlayer player) throws IOException {
|
||||
int tab = buffer.readInt();
|
||||
int totalScripts = buffer.readInt();
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(buffer);
|
||||
|
||||
// Verify script session token to prevent saving unloaded defaults
|
||||
String token = compound.getString("ScriptSessionToken");
|
||||
if (!verifyScriptSession(player, token)) {
|
||||
LogWriter.error(String.format("Rejected script save from %s: session not verified (data not loaded)", player.getCommandSenderName()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (totalScripts == 0) {
|
||||
data.getScripts().clear();
|
||||
}
|
||||
@@ -250,12 +289,10 @@ public class PacketUtil {
|
||||
} else while (data.getScripts().size() < totalScripts) {
|
||||
data.getScripts().add(new ScriptContainer(data));
|
||||
}
|
||||
NBTTagCompound tabCompound = ByteBufUtils.readNBT(buffer);
|
||||
// Use factory method to create correct script unit type based on NBT
|
||||
IScriptUnit script = IScriptUnit.createFromNBT(tabCompound, data);
|
||||
IScriptUnit script = IScriptUnit.createFromNBT(compound, data);
|
||||
data.getScripts().set(tab, script);
|
||||
} else {
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(buffer);
|
||||
data.setLanguage(compound.getString("ScriptLanguage"));
|
||||
if (!ScriptController.Instance.languages.containsKey(data.getLanguage())) {
|
||||
if (!ScriptController.Instance.languages.isEmpty()) {
|
||||
@@ -266,5 +303,6 @@ public class PacketUtil {
|
||||
}
|
||||
data.setEnabled(compound.getBoolean("ScriptEnabled"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -87,14 +87,20 @@ public final class AbilityScriptPacket extends AbstractPacket {
|
||||
String id = ByteBufUtils.readUTF8String(in);
|
||||
|
||||
Ability ability = AbilityController.Instance.resolveAbility(id);
|
||||
if (ability == null)
|
||||
if (ability == null) {
|
||||
// Ability not yet saved — send default empty script data so the GUI is usable
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) new AbilityScript(id), (EntityPlayerMP) player);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AbilityScript data = ability.getOrCreateScriptHandler();
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
if (!data.saveScript(in, player))
|
||||
return;
|
||||
// Persist to disk
|
||||
AbilityController.Instance.saveCustomAbility(ability);
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.PacketUtil;
|
||||
import kamkeel.npcs.network.enums.EnumRequestPacket;
|
||||
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
@@ -15,6 +16,7 @@ import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import noppes.npcs.CustomNpcsPermissions;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.blocks.tiles.TileScripted;
|
||||
import noppes.npcs.config.ConfigScript;
|
||||
import noppes.npcs.controllers.ScriptController;
|
||||
@@ -81,8 +83,10 @@ public final class BlockScriptPacket extends AbstractPacket {
|
||||
TileEntity tile = player.worldObj.getTileEntity(in.readInt(), in.readInt(), in.readInt());
|
||||
if (!(tile instanceof TileScripted))
|
||||
return;
|
||||
String token = PacketUtil.createScriptSession((EntityPlayerMP) player);
|
||||
NBTTagCompound compound = ((TileScripted) tile).getNBT(new NBTTagCompound());
|
||||
compound.setTag("Languages", ScriptController.Instance.nbtLanguages());
|
||||
compound.setString("ScriptSessionToken", token);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
|
||||
NBTTagCompound loadComplete = new NBTTagCompound();
|
||||
@@ -95,8 +99,14 @@ public final class BlockScriptPacket extends AbstractPacket {
|
||||
TileEntity tile = player.worldObj.getTileEntity(in.readInt(), in.readInt(), in.readInt());
|
||||
if (!(tile instanceof TileScripted))
|
||||
return;
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(in);
|
||||
String token = compound.getString("ScriptSessionToken");
|
||||
if (!PacketUtil.verifyScriptSession(player, token)) {
|
||||
LogWriter.error(String.format("Rejected block script save from %s: session not verified", player.getCommandSenderName()));
|
||||
return;
|
||||
}
|
||||
TileScripted script = (TileScripted) tile;
|
||||
script.setNBT(ByteBufUtils.readNBT(in));
|
||||
script.setNBT(compound);
|
||||
script.lastInited = -1;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -87,14 +87,20 @@ public final class ChainedAbilityScriptPacket extends AbstractPacket {
|
||||
String id = ByteBufUtils.readUTF8String(in);
|
||||
|
||||
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id);
|
||||
if (chain == null)
|
||||
if (chain == null) {
|
||||
// Chain not yet saved — send default empty script data so the GUI is usable
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) new ChainedAbilityScript(id), (EntityPlayerMP) player);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ChainedAbilityScript data = chain.getOrCreateScriptHandler();
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
if (!data.saveScript(in, player))
|
||||
return;
|
||||
// Persist to disk
|
||||
AbilityController.Instance.saveChainedAbility(chain);
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
|
||||
@@ -90,7 +90,8 @@ public final class EffectScriptPacket extends AbstractPacket {
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
if (!data.saveScript(in, player))
|
||||
return;
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
LogWriter.script(String.format("[%s] (Player) %s SAVED EFFECT %s [%s]", "EFFECT SCRIPTS", player.getCommandSenderName(), effect.getName()));
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public final class EventScriptPacket extends AbstractPacket {
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts(data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
PacketUtil.saveScripts(data, in);
|
||||
if (!PacketUtil.saveScripts(data, in, player))
|
||||
return;
|
||||
npc.updateAI = true;
|
||||
npc.script.hasInited = false;
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
|
||||
@@ -84,7 +84,8 @@ public final class ForgeScriptPacket extends AbstractPacket {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
int tab = in.getInt(in.readerIndex());
|
||||
PacketUtil.saveScripts(data, in);
|
||||
if (!PacketUtil.saveScripts(data, in, player))
|
||||
return;
|
||||
ScriptController.Instance.lastForgeUpdate = System.currentTimeMillis();
|
||||
if (tab == -1)
|
||||
ScriptController.Instance.saveForgeScriptsSync();
|
||||
|
||||
+2
-1
@@ -84,7 +84,8 @@ public final class GlobalNPCScriptPacket extends AbstractPacket {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
int tab = in.getInt(in.readerIndex());
|
||||
PacketUtil.saveScripts(data, in);
|
||||
if (!PacketUtil.saveScripts(data, in, player))
|
||||
return;
|
||||
ScriptController.Instance.lastGlobalNpcUpdate = System.currentTimeMillis();
|
||||
if (tab == -1)
|
||||
ScriptController.Instance.saveGlobalScriptsSync();
|
||||
|
||||
@@ -81,11 +81,19 @@ public final class NPCScriptPacket extends AbstractPacket {
|
||||
|
||||
Action requestedAction = Action.values()[in.readInt()];
|
||||
if (requestedAction == Action.GET) {
|
||||
String token = PacketUtil.createScriptSession((EntityPlayerMP) player);
|
||||
NBTTagCompound compound = npc.script.writeToNBT(new NBTTagCompound());
|
||||
compound.setTag("Languages", ScriptController.Instance.nbtLanguages());
|
||||
compound.setString("ScriptSessionToken", token);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
} else {
|
||||
npc.script.readFromNBT(ByteBufUtils.readNBT(in));
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(in);
|
||||
String token = compound.getString("ScriptSessionToken");
|
||||
if (!PacketUtil.verifyScriptSession(player, token)) {
|
||||
LogWriter.error(String.format("Rejected NPC script save from %s: session not verified", player.getCommandSenderName()));
|
||||
return;
|
||||
}
|
||||
npc.script.readFromNBT(compound);
|
||||
npc.updateAI = true;
|
||||
npc.script.hasInited = false;
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
|
||||
@@ -84,7 +84,8 @@ public final class PlayerScriptPacket extends AbstractPacket {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
int tab = in.getInt(in.readerIndex());
|
||||
PacketUtil.saveScripts(data, in);
|
||||
if (!PacketUtil.saveScripts(data, in, player))
|
||||
return;
|
||||
ScriptController.Instance.lastPlayerUpdate = System.currentTimeMillis();
|
||||
if (tab == -1)
|
||||
ScriptController.Instance.savePlayerScriptsSync();
|
||||
|
||||
@@ -102,7 +102,8 @@ public final class RecipeScriptPacket extends AbstractPacket {
|
||||
if (action == Action.GET) {
|
||||
PacketUtil.getScripts(script, player);
|
||||
} else {
|
||||
script.saveScript(in);
|
||||
if (!script.saveScript(in, player))
|
||||
return;
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
// TODO: Log Recipe Script Saving
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.PacketUtil;
|
||||
import kamkeel.npcs.network.enums.EnumRequestPacket;
|
||||
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
@@ -14,6 +15,7 @@ import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.CustomNpcsPermissions;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.config.ConfigScript;
|
||||
import noppes.npcs.controllers.ScriptController;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
@@ -72,8 +74,10 @@ public final class ItemScriptPacket extends AbstractPacket {
|
||||
if (requestedAction == Action.GET) {
|
||||
ScriptCustomItem iw = (ScriptCustomItem) NpcAPI.Instance().getIItemStack(player.getHeldItem());
|
||||
iw.loadScriptData();
|
||||
String token = PacketUtil.createScriptSession((EntityPlayerMP) player);
|
||||
NBTTagCompound compound = iw.getMCNbt();
|
||||
compound.setTag("Languages", ScriptController.Instance.nbtLanguages());
|
||||
compound.setString("ScriptSessionToken", token);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
|
||||
NBTTagCompound loadComplete = new NBTTagCompound();
|
||||
@@ -85,6 +89,11 @@ public final class ItemScriptPacket extends AbstractPacket {
|
||||
}
|
||||
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(in);
|
||||
String token = compound.getString("ScriptSessionToken");
|
||||
if (!PacketUtil.verifyScriptSession(player, token)) {
|
||||
LogWriter.error(String.format("Rejected item script save from %s: session not verified", player.getCommandSenderName()));
|
||||
return;
|
||||
}
|
||||
ScriptCustomItem wrapper = (ScriptCustomItem) NpcAPI.Instance().getIItemStack(player.getHeldItem());
|
||||
wrapper.setMCNbt(compound);
|
||||
wrapper.saveScriptData();
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ public final class LinkedItemScriptPacket extends AbstractPacket {
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
if (!data.saveScript(in, player))
|
||||
return;
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
LogWriter.script(String.format("[%s] (Player) %s SAVED LINKED ITEM [%s]", "LINKED ITEM SCRIPTS", player.getCommandSenderName(), linkedItem.getName()));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicEntry;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -20,10 +22,17 @@ import static kamkeel.npcs.controllers.AttributeController.getTracker;
|
||||
|
||||
public class AttributeAttackUtil {
|
||||
private static final Random random = new Random();
|
||||
|
||||
/**
|
||||
* Crit decision from the last LivingAttackEvent for vanilla/modded mob targets.
|
||||
* Set in LivingAttackEvent, consumed in LivingHurtEvent.
|
||||
* null = no roll occurred, true = critted, false = did not crit.
|
||||
*/
|
||||
public static Boolean lastAttackCritted = null;
|
||||
// --- Helper Classes & Methods ---
|
||||
|
||||
// Container for allocation results.
|
||||
private static class AllocationResult {
|
||||
public static class AllocationResult {
|
||||
public Map<Integer, Float> allocation;
|
||||
public float leftover;
|
||||
|
||||
@@ -37,7 +46,7 @@ public class AttributeAttackUtil {
|
||||
* Allocates physical damage into magic allocations based on MagicData splits.
|
||||
* Returns both a mapping (magic ID -> allocated damage) and any leftover physical damage.
|
||||
*/
|
||||
private static AllocationResult allocateMagicDamage(float physicalDamage, Map<Integer, MagicEntry> magicData) {
|
||||
public static AllocationResult allocateMagicDamage(float physicalDamage, Map<Integer, MagicEntry> magicData) {
|
||||
Map<Integer, Float> allocation = new HashMap<>();
|
||||
float totalSplit = 0f;
|
||||
if (magicData != null) {
|
||||
@@ -57,7 +66,7 @@ public class AttributeAttackUtil {
|
||||
/**
|
||||
* Adds extra magic damage from attribute sources if attributes are enabled.
|
||||
*/
|
||||
private static void addAttributeMagicDamage(Map<Integer, Float> allocation,
|
||||
public static void addAttributeMagicDamage(Map<Integer, Float> allocation,
|
||||
Map<Integer, Float> attributeDamage,
|
||||
Map<Integer, Float> magicBoost) {
|
||||
if (!ConfigMain.AttributesEnabled) return;
|
||||
@@ -74,7 +83,7 @@ public class AttributeAttackUtil {
|
||||
* Applies magic interactions. For each magic in the allocation, multiplies its damage by (1 + bonus)
|
||||
* for each opposing magic the defender possesses.
|
||||
*/
|
||||
private static void applyMagicInteractions(Map<Integer, Float> allocation,
|
||||
public static void applyMagicInteractions(Map<Integer, Float> allocation,
|
||||
Set<Integer> defenderMagicIDs,
|
||||
MagicController magicController) {
|
||||
for (Map.Entry<Integer, Float> entry : allocation.entrySet()) {
|
||||
@@ -97,7 +106,7 @@ public class AttributeAttackUtil {
|
||||
* Applies defender magic defenses if attributes are enabled.
|
||||
* Subtracts tracker-based magic defense (scaled by resistance) from each magic allocation.
|
||||
*/
|
||||
private static float applyDefenderMagicDefense(Map<Integer, Float> allocation,
|
||||
public static float applyDefenderMagicDefense(Map<Integer, Float> allocation,
|
||||
PlayerAttributeTracker defender,
|
||||
MagicController magicController) {
|
||||
if (!ConfigMain.AttributesEnabled) return 0f;
|
||||
@@ -168,10 +177,31 @@ public class AttributeAttackUtil {
|
||||
|
||||
public static float applyCrit(float damage, PlayerAttributeTracker tracker) {
|
||||
if (ConfigMain.AttributesEnabled) {
|
||||
if (rollCrit(tracker))
|
||||
damage = applyCritDamage(damage, tracker);
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls for a critical hit based on the tracker's CRITICAL_CHANCE attribute.
|
||||
*/
|
||||
public static boolean rollCrit(PlayerAttributeTracker tracker) {
|
||||
if (ConfigMain.AttributesEnabled && tracker != null) {
|
||||
float critChance = tracker.getAttributeValue(CustomAttributes.CRITICAL_CHANCE);
|
||||
return random.nextFloat() < (critChance / 100f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies crit damage unconditionally (no chance roll).
|
||||
* Use when the crit decision was already made via {@link #rollCrit}.
|
||||
*/
|
||||
public static float applyCritDamage(float damage, PlayerAttributeTracker tracker) {
|
||||
if (ConfigMain.AttributesEnabled && tracker != null) {
|
||||
float critBonus = tracker.getAttributeValue(CustomAttributes.CRITICAL_DAMAGE);
|
||||
if (random.nextFloat() < (critChance / 100f))
|
||||
damage = (damage * (1 + (float) ConfigMain.AttributesCriticalBoost / 100f)) + critBonus;
|
||||
damage = (damage * (1 + (float) ConfigMain.AttributesCriticalBoost / 100f)) + critBonus;
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
@@ -256,4 +286,141 @@ public class AttributeAttackUtil {
|
||||
|
||||
return mainDamage + gearDamage;
|
||||
}
|
||||
|
||||
// --- Magic Interaction Utility ---
|
||||
|
||||
/**
|
||||
* Calculate magic interaction multiplier between attacker's magic types
|
||||
* and defender's magic types. Used for barrier defense and entity-vs-entity interactions.
|
||||
*
|
||||
* @param attackerMagic Attacker's magic data (projectile or weapon)
|
||||
* @param defenderMagic Defender's magic data (barrier or entity)
|
||||
* @return Multiplier (1.0 = no interaction, >1.0 = bonus damage, <1.0 = resistance)
|
||||
*/
|
||||
public static float calculateMagicInteractionMultiplier(MagicData attackerMagic, MagicData defenderMagic) {
|
||||
if (attackerMagic == null || defenderMagic == null) return 1.0f;
|
||||
if (attackerMagic.isEmpty() || defenderMagic.isEmpty()) return 1.0f;
|
||||
|
||||
MagicController mc = MagicController.getInstance();
|
||||
float multiplier = 1.0f;
|
||||
|
||||
for (int attackMagicId : attackerMagic.getMagics().keySet()) {
|
||||
Magic magic = mc.getMagic(attackMagicId);
|
||||
if (magic == null || magic.interactions == null) continue;
|
||||
for (Map.Entry<Integer, Float> interaction : magic.interactions.entrySet()) {
|
||||
if (defenderMagic.getMagics().containsKey(interaction.getKey())) {
|
||||
multiplier *= (1 + interaction.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// --- Ability Damage Pipeline ---
|
||||
|
||||
/**
|
||||
* Apply gear magic boost percentages to the split-allocated magic damage.
|
||||
* This multiplies each magic type's allocated damage by (1 + boost/100).
|
||||
* Unlike {@link #addAttributeMagicDamage}, this does NOT add flat gear damage —
|
||||
* it only applies the percentage multiplier from gear to the existing allocation.
|
||||
*/
|
||||
public static void applyMagicBoostToAllocation(Map<Integer, Float> allocation, Map<Integer, Float> magicBoost) {
|
||||
if (!ConfigMain.AttributesEnabled) return;
|
||||
for (Map.Entry<Integer, Float> entry : allocation.entrySet()) {
|
||||
float boost = magicBoost.getOrDefault(entry.getKey(), 0f);
|
||||
if (boost != 0f) {
|
||||
entry.setValue(entry.getValue() * (1 + boost / 100f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply gear magic boost percentages to barrier/dome health.
|
||||
* Each magic type's split determines what portion of health is affected by its boost.
|
||||
* E.g., Thunder [100%] dome + 50% Thunder gear boost = health * 1.5
|
||||
*/
|
||||
public static float applyMagicBoostToHealth(EntityPlayer caster, MagicData magicData, float health) {
|
||||
if (!ConfigMain.AttributesEnabled) return health;
|
||||
if (magicData == null || magicData.isEmpty()) return health;
|
||||
|
||||
PlayerAttributeTracker tracker = getTracker(caster);
|
||||
if (tracker == null) return health;
|
||||
|
||||
float multiplier = 1.0f;
|
||||
for (Map.Entry<Integer, MagicEntry> entry : magicData.getMagics().entrySet()) {
|
||||
float split = entry.getValue().split;
|
||||
float boost = tracker.magicBoost.getOrDefault(entry.getKey(), 0f);
|
||||
multiplier += split * (boost / 100f);
|
||||
}
|
||||
return health * multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the magic pipeline to ability damage. Works for both player and NPC casters.
|
||||
* Abilities do NOT get main attack or critical boosts — they have their own scaling.
|
||||
* This handles:
|
||||
* <ul>
|
||||
* <li>Magic allocation (splits from ability/caster magic data)</li>
|
||||
* <li>Gear magic boost multipliers on split damage (player casters only, no flat gear damage)</li>
|
||||
* <li>Magic interactions vs defender</li>
|
||||
* <li>Defender magic defense/resistance (player defenders only)</li>
|
||||
* </ul>
|
||||
* If no magic data is present, damage passes through unchanged.
|
||||
*
|
||||
* @param caster Caster (player or NPC)
|
||||
* @param target Target entity
|
||||
* @param damage Base ability damage (already scaled by ability's own system)
|
||||
* @param abilityMagic Resolved magic data (ability's own, or caster's fallback)
|
||||
* @return Final damage after magic splits, boosts, interactions, and defense
|
||||
*/
|
||||
public static float calculateAbilityDamage(EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, MagicData abilityMagic) {
|
||||
if (abilityMagic == null || abilityMagic.isEmpty()) return damage;
|
||||
|
||||
// 1. Allocate base damage into magic types using ability's splits
|
||||
AllocationResult result = allocateMagicDamage(damage, abilityMagic.getMagics());
|
||||
float leftover = result.leftover;
|
||||
|
||||
// 2. Apply gear magic boost multipliers to the split allocation (player casters only)
|
||||
if (caster instanceof EntityPlayer && ConfigMain.AttributesEnabled) {
|
||||
PlayerAttributeTracker tracker = getTracker((EntityPlayer) caster);
|
||||
if (tracker != null) {
|
||||
applyMagicBoostToAllocation(result.allocation, tracker.magicBoost);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Magic interactions against target
|
||||
MagicController magicController = MagicController.getInstance();
|
||||
Set<Integer> defenderMagicIDs = null;
|
||||
|
||||
if (target instanceof EntityPlayer) {
|
||||
PlayerData defenderData = PlayerData.get((EntityPlayer) target);
|
||||
if (defenderData != null) {
|
||||
defenderMagicIDs = new HashSet<>(defenderData.magicData.getMagics().keySet());
|
||||
}
|
||||
} else if (target instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) target;
|
||||
if (npc.stats != null && npc.stats.magicData != null) {
|
||||
defenderMagicIDs = new HashSet<>(npc.stats.magicData.getMagics().keySet());
|
||||
}
|
||||
}
|
||||
|
||||
if (defenderMagicIDs != null) {
|
||||
applyMagicInteractions(result.allocation, defenderMagicIDs, magicController);
|
||||
}
|
||||
|
||||
// 4. Apply defender magic defense (player defenders with attributes only)
|
||||
if (target instanceof EntityPlayer && ConfigMain.AttributesEnabled) {
|
||||
PlayerAttributeTracker defenderTracker = getTracker((EntityPlayer) target);
|
||||
if (defenderTracker != null) {
|
||||
float adjustedMagic = applyDefenderMagicDefense(result.allocation, defenderTracker, magicController);
|
||||
return leftover + adjustedMagic;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum magic damage
|
||||
float magicTotal = 0f;
|
||||
for (float val : result.allocation.values()) magicTotal += val;
|
||||
return leftover + magicTotal;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityAction;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.RotationMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
|
||||
import kamkeel.npcs.controllers.data.ability.data.IAbilityAction;
|
||||
import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry;
|
||||
@@ -87,6 +88,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
*/
|
||||
private transient boolean hitScanActive = false;
|
||||
private transient EntityLivingBase hitScanTarget = null;
|
||||
private transient float currentTrackDelay = 0;
|
||||
|
||||
/**
|
||||
* Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15
|
||||
@@ -227,9 +229,19 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
protected void onPostPhaseTick(Ability ability, EntityLivingBase target) {
|
||||
// Update hit scan state - actual facing is deferred to applyRotationControl()
|
||||
// which runs AFTER super.onLivingUpdate() to override AI look helper
|
||||
if (ability != null && ability.isExecuting()
|
||||
&& ability.isHitScanForCurrentPhase() && target != null) {
|
||||
enableHitScan(target);
|
||||
if (ability != null && ability.isExecuting() && target != null) {
|
||||
if (ability.isHitScanForCurrentPhase()) {
|
||||
enableHitScan(target, ability.getTrackDelay());
|
||||
} else {
|
||||
// Release hit scan if it was previously active
|
||||
if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
// FREE mode: use AI look helper during ability execution since combat AI may not run
|
||||
if (ability.getRotationMode() == RotationMode.FREE) {
|
||||
npc.getLookHelper().setLookPositionWithEntity(target, 30.0F, 30.0F);
|
||||
}
|
||||
}
|
||||
} else if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
@@ -867,34 +879,74 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
/**
|
||||
* Enable hit scan tracking for the given target.
|
||||
*/
|
||||
private void enableHitScan(EntityLivingBase target) {
|
||||
private void enableHitScan(EntityLivingBase target, float trackDelay) {
|
||||
if (!hitScanActive) {
|
||||
hitScanActive = true;
|
||||
npc.setBoolFlag(true, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
hitScanTarget = target;
|
||||
currentTrackDelay = trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap the NPC to face the target instantly.
|
||||
* Face the target with optional track delay.
|
||||
* When trackDelay <= 0, snaps instantly (legacy behavior).
|
||||
* When trackDelay > 0, lerps toward the target each tick.
|
||||
*/
|
||||
private void faceTarget(EntityLivingBase target) {
|
||||
private void faceTarget(EntityLivingBase target, float trackDelay) {
|
||||
double dx = target.posX - npc.posX;
|
||||
double dz = target.posZ - npc.posZ;
|
||||
double dy = (target.posY + target.getEyeHeight() * 0.5) - (npc.posY + npc.getEyeHeight());
|
||||
double distXZ = Math.sqrt(dx * dx + dz * dz);
|
||||
|
||||
float yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
||||
float pitch = (float) (-(Math.atan2(dy, distXZ)) * 180.0 / Math.PI);
|
||||
float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
||||
float targetPitch = (float) (-(Math.atan2(dy, distXZ)) * 180.0 / Math.PI);
|
||||
|
||||
npc.rotationYaw = yaw;
|
||||
npc.rotationYawHead = yaw;
|
||||
npc.renderYawOffset = yaw;
|
||||
npc.rotationPitch = pitch;
|
||||
npc.prevRotationYaw = yaw;
|
||||
npc.prevRotationYawHead = yaw;
|
||||
npc.prevRenderYawOffset = yaw;
|
||||
npc.prevRotationPitch = pitch;
|
||||
if (trackDelay <= 0) {
|
||||
// Instant snap (legacy behavior)
|
||||
npc.rotationYaw = targetYaw;
|
||||
npc.rotationYawHead = targetYaw;
|
||||
npc.renderYawOffset = targetYaw;
|
||||
npc.rotationPitch = targetPitch;
|
||||
npc.prevRotationYaw = targetYaw;
|
||||
npc.prevRotationYawHead = targetYaw;
|
||||
npc.prevRenderYawOffset = targetYaw;
|
||||
npc.prevRotationPitch = targetPitch;
|
||||
} else {
|
||||
float lerpFactor = 1.0f / (1.0f + trackDelay);
|
||||
|
||||
// Wrap yaw difference to [-180, 180] for correct interpolation
|
||||
float yawDiff = wrapAngle(targetYaw - npc.rotationYawHead);
|
||||
float pitchDiff = targetPitch - npc.rotationPitch;
|
||||
|
||||
float newYaw = npc.rotationYawHead + yawDiff * lerpFactor;
|
||||
float newPitch = npc.rotationPitch + pitchDiff * lerpFactor;
|
||||
|
||||
// Set prev to current before updating (for smooth client interpolation)
|
||||
npc.prevRotationYaw = npc.rotationYaw;
|
||||
npc.prevRotationYawHead = npc.rotationYawHead;
|
||||
npc.prevRenderYawOffset = npc.renderYawOffset;
|
||||
npc.prevRotationPitch = npc.rotationPitch;
|
||||
|
||||
npc.rotationYaw = newYaw;
|
||||
npc.rotationYawHead = newYaw;
|
||||
npc.renderYawOffset = newYaw;
|
||||
npc.rotationPitch = newPitch;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap the NPC to face the target instantly (no delay).
|
||||
*/
|
||||
private void faceTarget(EntityLivingBase target) {
|
||||
faceTarget(target, 0);
|
||||
}
|
||||
|
||||
private static float wrapAngle(float angle) {
|
||||
angle = angle % 360.0f;
|
||||
if (angle >= 180.0f) angle -= 360.0f;
|
||||
if (angle < -180.0f) angle += 360.0f;
|
||||
return angle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -922,7 +974,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
|
||||
// Server: apply the appropriate rotation
|
||||
if (hitScanActive && hitScanTarget != null && !hitScanTarget.isDead) {
|
||||
faceTarget(hitScanTarget);
|
||||
faceTarget(hitScanTarget, currentTrackDelay);
|
||||
} else if (rotationLocked) {
|
||||
npc.rotationYaw = lockedYaw;
|
||||
npc.rotationYawHead = lockedYawHead;
|
||||
|
||||
@@ -8,6 +8,7 @@ import cpw.mods.fml.relauncher.Side;
|
||||
import kamkeel.npcs.addon.DBCAddon;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.AttributeController;
|
||||
import kamkeel.npcs.controllers.data.attribute.tracker.PlayerAttributeTracker;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityCounter;
|
||||
@@ -623,8 +624,21 @@ public class ScriptPlayerEventHandler {
|
||||
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) event.source.getEntity());
|
||||
float attackAmount = event.ammount;
|
||||
if (ConfigMain.AttributesEnabled && !DBCAddon.IsAvailable())
|
||||
attackAmount = AttributeAttackUtil.calculateOutgoing((EntityPlayer) event.source.getEntity(), attackAmount);
|
||||
AttributeAttackUtil.lastAttackCritted = null;
|
||||
if (ConfigMain.AttributesEnabled && !DBCAddon.IsAvailable()) {
|
||||
EntityPlayer attackPlayer = (EntityPlayer) event.source.getEntity();
|
||||
attackAmount = AttributeAttackUtil.calculateOutgoing(attackPlayer, attackAmount);
|
||||
// For vanilla/modded mob targets, roll crit once and store for LivingHurtEvent
|
||||
if (!(event.entityLiving instanceof EntityPlayerMP) && !(event.entityLiving instanceof EntityNPCInterface)) {
|
||||
PlayerAttributeTracker tracker = AttributeController.getTracker(attackPlayer);
|
||||
if (AttributeAttackUtil.rollCrit(tracker)) {
|
||||
AttributeAttackUtil.lastAttackCritted = true;
|
||||
attackAmount = AttributeAttackUtil.applyCritDamage(attackAmount, tracker);
|
||||
} else {
|
||||
AttributeAttackUtil.lastAttackCritted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent pevent1 = new noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent((IPlayer) NpcAPI.Instance().getIEntity((EntityPlayer) event.source.getEntity()), event.entityLiving, attackAmount, event.source);
|
||||
cancel = cancel || EventHooks.onPlayerAttack(handler, pevent1);
|
||||
@@ -663,6 +677,15 @@ public class ScriptPlayerEventHandler {
|
||||
event.ammount = AttributeAttackUtil.calculateDamagePlayerToPlayer((EntityPlayer) source, (EntityPlayer) event.entityLiving, event.ammount);
|
||||
} else if (!(event.entityLiving instanceof EntityNPCInterface) && source instanceof EntityPlayer) {
|
||||
event.ammount = AttributeAttackUtil.calculateOutgoing((EntityPlayer) source, event.ammount);
|
||||
if (AttributeAttackUtil.lastAttackCritted != null) {
|
||||
// Use the crit decision from LivingAttackEvent
|
||||
if (AttributeAttackUtil.lastAttackCritted)
|
||||
event.ammount = AttributeAttackUtil.applyCritDamage(event.ammount, AttributeController.getTracker((EntityPlayer) source));
|
||||
AttributeAttackUtil.lastAttackCritted = null;
|
||||
} else {
|
||||
// Fallback: roll independently (no preceding LivingAttackEvent)
|
||||
event.ammount = AttributeAttackUtil.applyCrit(event.ammount, AttributeController.getTracker((EntityPlayer) source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,8 +188,11 @@ public class TileScripted extends TileEntity implements IScriptBlockHandler, ISc
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync() {
|
||||
sendSavePacket(-1, getScripts().size(), getNBT(new NBTTagCompound()));
|
||||
public void sync(String sessionToken) {
|
||||
NBTTagCompound compound = getNBT(new NBTTagCompound());
|
||||
if (sessionToken != null && !sessionToken.isEmpty())
|
||||
compound.setString("ScriptSessionToken", sessionToken);
|
||||
sendSavePacket(-1, getScripts().size(), compound);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -369,6 +369,8 @@ public class GuiScript extends GuiScriptInterface {
|
||||
@Override
|
||||
public void setGuiData(NBTTagCompound compound) {
|
||||
script.readFromNBT(compound);
|
||||
if (compound.hasKey("ScriptSessionToken"))
|
||||
scriptSessionToken = compound.getString("ScriptSessionToken");
|
||||
loadLanguagesData(compound);
|
||||
loaded = true;
|
||||
}
|
||||
@@ -377,7 +379,10 @@ public class GuiScript extends GuiScriptInterface {
|
||||
public void save() {
|
||||
if (loaded) {
|
||||
setScript();
|
||||
NPCScriptPacket.Save(script.writeToNBT(new NBTTagCompound()));
|
||||
NBTTagCompound compound = script.writeToNBT(new NBTTagCompound());
|
||||
if (scriptSessionToken != null)
|
||||
compound.setString("ScriptSessionToken", scriptSessionToken);
|
||||
NPCScriptPacket.Save(compound);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package noppes.npcs.client.gui.advanced;
|
||||
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import noppes.npcs.client.gui.util.GuiCustomScroll;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.GuiNpcLabel;
|
||||
import noppes.npcs.client.gui.util.GuiNpcTextField;
|
||||
import noppes.npcs.client.gui.util.ICustomScrollListener;
|
||||
import noppes.npcs.client.gui.util.ITextfieldListener;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sub-GUI for editing magic splits on an Ability.
|
||||
* Dual-scroll layout: available magics on left, assigned magics on right.
|
||||
* Only split values (no flat damage) are editable.
|
||||
*/
|
||||
public class SubGuiAbilityMagic extends SubGuiInterface implements ICustomScrollListener, ITextfieldListener {
|
||||
|
||||
public MagicData magicData;
|
||||
|
||||
private GuiCustomScroll availableScroll;
|
||||
private GuiCustomScroll assignedScroll;
|
||||
|
||||
/** Maps display name → magic ID for all available magics. */
|
||||
private final HashMap<String, Integer> allMagic = new HashMap<>();
|
||||
/** Maps formatted display string (right scroll) → base display name. */
|
||||
private final HashMap<String, String> assignedDisplayToName = new HashMap<>();
|
||||
private String search = "";
|
||||
|
||||
private GuiNpcTextField splitField;
|
||||
|
||||
public SubGuiAbilityMagic(MagicData data) {
|
||||
this.magicData = data;
|
||||
setBackground("menubg.png");
|
||||
xSize = 420;
|
||||
ySize = 222;
|
||||
|
||||
// Load all available magics from the controller
|
||||
MagicController controller = MagicController.getInstance();
|
||||
for (Map.Entry<Integer, Magic> entry : controller.magics.entrySet()) {
|
||||
allMagic.put(entry.getValue().getDisplayName(), entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
int scrollW = 150;
|
||||
int scrollH = 140;
|
||||
int leftX = guiLeft + 10;
|
||||
int rightX = guiLeft + 250;
|
||||
int topY = guiTop + 20;
|
||||
|
||||
// Title
|
||||
addLabel(new GuiNpcLabel(0, "ability.magic.editor", guiLeft + 10, guiTop + 5));
|
||||
|
||||
// Left scroll: available magics
|
||||
if (availableScroll == null) {
|
||||
availableScroll = new GuiCustomScroll(this, 0);
|
||||
availableScroll.setSize(scrollW, scrollH);
|
||||
}
|
||||
availableScroll.guiLeft = leftX;
|
||||
availableScroll.guiTop = topY;
|
||||
availableScroll.setList(getSearchList());
|
||||
addScroll(availableScroll);
|
||||
|
||||
// Search field below left scroll
|
||||
addTextField(new GuiNpcTextField(4, this, fontRendererObj, leftX, topY + scrollH + 5, scrollW, 20, search));
|
||||
|
||||
// Right scroll: assigned magics
|
||||
if (assignedScroll == null) {
|
||||
assignedScroll = new GuiCustomScroll(this, 1);
|
||||
assignedScroll.setSize(scrollW, scrollH);
|
||||
}
|
||||
assignedScroll.guiLeft = rightX;
|
||||
assignedScroll.guiTop = topY;
|
||||
updateAssignedList();
|
||||
addScroll(assignedScroll);
|
||||
|
||||
// Arrow buttons between scrolls
|
||||
int midX = guiLeft + 175;
|
||||
addButton(new GuiNpcButton(70, midX, topY + 40, 55, 20, ">"));
|
||||
addButton(new GuiNpcButton(71, midX, topY + 62, 55, 20, "<"));
|
||||
|
||||
// Standardize button
|
||||
addButton(new GuiNpcButton(72, midX, topY + 90, 55, 20, "magic.dist"));
|
||||
getButton(72).setHoverText("magic.distInfo");
|
||||
|
||||
// Split field below right scroll
|
||||
int tfY = topY + scrollH + 5;
|
||||
addLabel(new GuiNpcLabel(5002, "magic.split", rightX - 57, tfY + 5));
|
||||
splitField = new GuiNpcTextField(73, this, fontRendererObj, rightX, tfY, 60, 20, "");
|
||||
splitField.setFloatsOnly();
|
||||
splitField.setMinMaxDefaultFloat(0, 1, 0);
|
||||
splitField.enabled = false;
|
||||
addTextField(splitField);
|
||||
|
||||
// Done button
|
||||
addButton(new GuiNpcButton(80, guiLeft + xSize / 2 - 40, guiTop + ySize - 34, 80, 20, "gui.done"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buttonEvent(GuiButton guibutton) {
|
||||
// Add magic
|
||||
if (guibutton.id == 70) {
|
||||
if (availableScroll.hasSelected()) {
|
||||
String selected = availableScroll.getSelected();
|
||||
Integer id = allMagic.get(selected);
|
||||
if (id != null && !magicData.hasMagic(id)) {
|
||||
magicData.addMagic(id, 0, 0);
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
return;
|
||||
}
|
||||
// Remove magic
|
||||
if (guibutton.id == 71) {
|
||||
if (assignedScroll.hasSelected()) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
magicData.removeMagic(id);
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
if (splitField != null) {
|
||||
splitField.setText("");
|
||||
splitField.enabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Standardize splits
|
||||
if (guibutton.id == 72) {
|
||||
if (magicData.getMagics().size() > 0) {
|
||||
int count = magicData.getMagics().size();
|
||||
float stdSplit = 1.0f / count;
|
||||
for (Integer key : magicData.getMagics().keySet()) {
|
||||
magicData.getMagic(key).split = stdSplit;
|
||||
}
|
||||
// Update field if something is selected
|
||||
if (assignedScroll.getSelected() != null && splitField != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
splitField.setText(magicData.getMagic(id).split + "");
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Done
|
||||
if (guibutton.id == 80) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(char c, int i) {
|
||||
super.keyTyped(c, i);
|
||||
if (getTextField(4) != null && getTextField(4).isFocused()) {
|
||||
if (search.equals(getTextField(4).getText()))
|
||||
return;
|
||||
search = getTextField(4).getText().toLowerCase();
|
||||
availableScroll.setList(getSearchList());
|
||||
availableScroll.resetScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
|
||||
if (guiCustomScroll == assignedScroll) {
|
||||
if (assignedScroll.getSelected() != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
if (splitField != null) {
|
||||
splitField.setText(magicData.getMagic(id).split + "");
|
||||
splitField.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unFocused(GuiNpcTextField textField) {
|
||||
if (textField.id == 73) {
|
||||
if (assignedScroll != null && assignedScroll.getSelected() != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
try {
|
||||
float split = Float.parseFloat(textField.getText());
|
||||
magicData.getMagic(id).split = split;
|
||||
updateAssignedList();
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getSearchList() {
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(allMagic.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : allMagic.keySet()) {
|
||||
if (name.toLowerCase().contains(search))
|
||||
list.add(name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the magic ID from a right-scroll selection (which may be formatted as "Name - XX%").
|
||||
*/
|
||||
private Integer resolveAssignedId(String selection) {
|
||||
if (selection == null) return null;
|
||||
String baseName = assignedDisplayToName.get(selection);
|
||||
if (baseName == null) baseName = selection;
|
||||
return allMagic.get(baseName);
|
||||
}
|
||||
|
||||
private void updateAssignedList() {
|
||||
assignedDisplayToName.clear();
|
||||
List<String> selected = new ArrayList<>();
|
||||
for (Map.Entry<String, Integer> entry : allMagic.entrySet()) {
|
||||
if (magicData.hasMagic(entry.getValue())) {
|
||||
float split = magicData.getMagic(entry.getValue()).split;
|
||||
String display = entry.getKey() + " - " + Math.round(split * 100) + "%";
|
||||
assignedDisplayToName.put(display, entry.getKey());
|
||||
selected.add(display);
|
||||
}
|
||||
}
|
||||
if (assignedScroll != null) {
|
||||
assignedScroll.setList(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,17 @@ import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import tconstruct.client.tabs.AbstractTab;
|
||||
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.MagicEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/standardbg.png");
|
||||
@@ -92,6 +98,11 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
private AbilityIcon cachedDetailIcon = null;
|
||||
private int cachedDetailIndex = -1;
|
||||
|
||||
// ═══ Info panel scroll ═══
|
||||
private int infoScrollOffset = 0;
|
||||
private int infoContentHeight = 0;
|
||||
private int lastInfoIndex = -1;
|
||||
|
||||
// ═══ Hotbar state ═══
|
||||
private int hoveredSlotIndex = -1;
|
||||
private AbilityIcon[] hotbarIcons = new AbilityIcon[AbilityHotbarData.TOTAL_SLOTS];
|
||||
@@ -436,11 +447,17 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
int infoIndex = hoveredGridIndex >= 0 ? hoveredGridIndex : selectedIndex;
|
||||
if (infoIndex < 0 || infoIndex >= filteredKeys.size()) return;
|
||||
|
||||
// Reset scroll when ability changes
|
||||
if (infoIndex != lastInfoIndex) {
|
||||
infoScrollOffset = 0;
|
||||
lastInfoIndex = infoIndex;
|
||||
}
|
||||
|
||||
FontRenderer fr = mc.fontRenderer;
|
||||
int panelInnerLeft = guiLeft + infoLeft + infoPad;
|
||||
int panelInnerTop = guiTop + infoTop + infoPad;
|
||||
int panelInnerWidth = (infoRight - infoLeft) - infoPad * 2;
|
||||
int y = panelInnerTop;
|
||||
int panelHeight = infoBottom - infoTop;
|
||||
|
||||
// Cached icon for detail preview
|
||||
if (cachedDetailIndex != infoIndex) {
|
||||
@@ -448,14 +465,28 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
cachedDetailIndex = infoIndex;
|
||||
}
|
||||
|
||||
Ability ability = filteredAbilities.get(infoIndex);
|
||||
|
||||
// Get toggle state for icon rendering and state display
|
||||
int toggleState = 0;
|
||||
PlayerData togglePlayerData = ClientCacheHandler.playerData;
|
||||
Ability infoAbility = filteredAbilities.get(infoIndex);
|
||||
if (infoAbility != null && infoAbility.isToggleable() && togglePlayerData != null && togglePlayerData.abilityData != null) {
|
||||
if (ability != null && ability.isToggleable() && togglePlayerData != null && togglePlayerData.abilityData != null) {
|
||||
toggleState = togglePlayerData.abilityData.getToggleState(filteredKeys.get(infoIndex));
|
||||
}
|
||||
|
||||
// GL Scissor to clip info panel content
|
||||
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int scaleFactor = sr.getScaleFactor();
|
||||
int scissorX = (guiLeft + infoLeft) * scaleFactor;
|
||||
int scissorY = mc.displayHeight - (guiTop + infoBottom) * scaleFactor;
|
||||
int scissorW = (infoRight - infoLeft) * scaleFactor;
|
||||
int scissorH = panelHeight * scaleFactor;
|
||||
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GL11.glScissor(scissorX, scissorY, scissorW, scissorH);
|
||||
|
||||
int y = panelInnerTop - infoScrollOffset;
|
||||
|
||||
// Preview icon (centered, state-aware for multi-state toggles)
|
||||
if (cachedDetailIcon != null) {
|
||||
float drawSize = cachedDetailIcon.getDrawSize();
|
||||
@@ -490,9 +521,8 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
}
|
||||
y += 4;
|
||||
|
||||
// Type info
|
||||
Ability ability = filteredAbilities.get(infoIndex);
|
||||
if (ability != null) {
|
||||
// Type info
|
||||
String typeId = ability.getTypeId();
|
||||
if (typeId != null && !typeId.isEmpty()) {
|
||||
String translated = StatCollector.translateToLocal(typeId);
|
||||
@@ -503,6 +533,7 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle info
|
||||
if (ability.isToggleable()) {
|
||||
String toggleText = "[" + StatCollector.translateToLocal("gui.toggle") + "]";
|
||||
int textW = fr.getStringWidth(toggleText);
|
||||
@@ -510,12 +541,11 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Toggle state (uses pre-computed toggleState)
|
||||
// Toggle state
|
||||
if (ability.isToggleable()) {
|
||||
String stateText;
|
||||
String prefix;
|
||||
if (toggleState > 0) {
|
||||
// Active — check for state label (multi-state toggles like Ki Weapon)
|
||||
String stateLabel = ability.getToggleStateLabel(toggleState);
|
||||
stateText = stateLabel != null ? stateLabel : StatCollector.translateToLocal("gui.toggle.active");
|
||||
prefix = "\u00A72";
|
||||
@@ -528,20 +558,110 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Cooldown info
|
||||
// Cooldown info — yellow if per-ability cooldown
|
||||
if (ability.getCooldownTicks() > 0) {
|
||||
float seconds = ability.getCooldownTicks() / 20.0f;
|
||||
String cdText = StatCollector.translateToLocal("ability.cooldown") + ": " + String.format("%.1f", seconds) + "s";
|
||||
int textW2 = fr.getStringWidth(cdText);
|
||||
fr.drawStringWithShadow("\u00A77" + cdText, panelInnerLeft + (panelInnerWidth - textW2) / 2, y, 0xAAAAAA);
|
||||
boolean perAbility = ability.isPerAbilityCooldown();
|
||||
String cdColor = perAbility ? "\u00A7e" : "\u00A77";
|
||||
int cdColorInt = perAbility ? 0xFFFF55 : 0xAAAAAA;
|
||||
fr.drawStringWithShadow(cdColor + cdText, panelInnerLeft + (panelInnerWidth - textW2) / 2, y, cdColorInt);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
|
||||
if (ability.isPerAbilityCooldown()) {
|
||||
if (perAbility) {
|
||||
String perText = "[" + StatCollector.translateToLocal("ability.perAbilityCooldown") + "]";
|
||||
int ptW = fr.getStringWidth(perText);
|
||||
fr.drawStringWithShadow("\u00A7e" + perText, panelInnerLeft + (panelInnerWidth - ptW) / 2, y, 0xFFFF55);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Damage info
|
||||
float baseDamage = ability.getDisplayDamage();
|
||||
if (baseDamage > 0) {
|
||||
float displayDamage = baseDamage;
|
||||
if (AbilityController.Instance != null && mc.thePlayer != null) {
|
||||
displayDamage = AbilityController.Instance.fireModifyProjectileDamage(ability, mc.thePlayer, baseDamage);
|
||||
}
|
||||
String label = ability.isDisplayDamageDPS()
|
||||
? StatCollector.translateToLocal("gui.dps")
|
||||
: StatCollector.translateToLocal("ability.preview.damage");
|
||||
String dmgText = label + ": " + String.format("%.1f", displayDamage);
|
||||
int textW = fr.getStringWidth(dmgText);
|
||||
fr.drawStringWithShadow("\u00A7c" + dmgText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0xFF5555);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Barrier health
|
||||
float baseHealth = ability.getDisplayBarrierHealth();
|
||||
if (baseHealth > 0) {
|
||||
float displayHealth = baseHealth;
|
||||
if (AbilityController.Instance != null && mc.thePlayer != null) {
|
||||
displayHealth = AbilityController.Instance.fireModifyBarrierHealth(ability, mc.thePlayer, baseHealth);
|
||||
}
|
||||
String healthText = StatCollector.translateToLocal("ability.preview.health") + ": " + String.format("%.0f", displayHealth);
|
||||
int textW = fr.getStringWidth(healthText);
|
||||
fr.drawStringWithShadow("\u00A7a" + healthText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x55FF55);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Barrier reflection
|
||||
if (ability.isDisplayReflect()) {
|
||||
String reflectText = StatCollector.translateToLocal("ability.reflect") + ": " + String.format("%.0f%%", ability.getDisplayReflectStrength());
|
||||
int textW = fr.getStringWidth(reflectText);
|
||||
fr.drawStringWithShadow("\u00A7d" + reflectText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0xFF55FF);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Barrier absorbing
|
||||
if (ability.isDisplayAbsorbing()) {
|
||||
String absorbText = "[" + StatCollector.translateToLocal("ability.absorbing") + "]";
|
||||
int textW = fr.getStringWidth(absorbText);
|
||||
fr.drawStringWithShadow("\u00A7b" + absorbText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x55FFFF);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Magic info
|
||||
if (ability.hasMagic()) {
|
||||
y += 2;
|
||||
MagicData magicData = ability.getMagicData();
|
||||
if (magicData.isEmpty()) {
|
||||
String magicText = StatCollector.translateToLocal("ability.preview.magic.casters");
|
||||
int textW = fr.getStringWidth(magicText);
|
||||
fr.drawStringWithShadow("\u00A79" + magicText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x5555FF);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
} else {
|
||||
for (Map.Entry<Integer, MagicEntry> entry : magicData.getMagics().entrySet()) {
|
||||
Magic magic = MagicController.getInstance() != null
|
||||
? MagicController.getInstance().getMagic(entry.getKey()) : null;
|
||||
String magicName = magic != null ? magic.getDisplayName() : "Magic #" + entry.getKey();
|
||||
String splitText = magicName + ": " + Math.round(entry.getValue().split * 100) + "%";
|
||||
int textW = fr.getStringWidth(splitText);
|
||||
fr.drawStringWithShadow("\u00A79" + splitText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x5555FF);
|
||||
y += fr.FONT_HEIGHT + 1;
|
||||
}
|
||||
y += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track content height for scroll limits
|
||||
infoContentHeight = (y + infoScrollOffset) - panelInnerTop + infoPad * 2;
|
||||
|
||||
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
// Scroll indicator
|
||||
if (infoContentHeight > panelHeight) {
|
||||
int maxScroll = Math.max(1, infoContentHeight - panelHeight);
|
||||
if (infoScrollOffset > maxScroll) infoScrollOffset = maxScroll;
|
||||
float scrollPercent = (float) infoScrollOffset / maxScroll;
|
||||
int barHeight = panelHeight - 4;
|
||||
int thumbHeight = Math.max(8, barHeight * panelHeight / infoContentHeight);
|
||||
int thumbY = guiTop + infoTop + 2 + (int) ((barHeight - thumbHeight) * scrollPercent);
|
||||
int barX = guiLeft + infoRight - 3;
|
||||
drawRect(barX, guiTop + infoTop + 2, barX + 2, guiTop + infoTop + 2 + barHeight, 0x40FFFFFF);
|
||||
drawRect(barX, thumbY, barX + 2, thumbY + thumbHeight, 0xA0FFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,11 +1023,26 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
|
||||
int wheel = Mouse.getEventDWheel();
|
||||
if (wheel != 0) {
|
||||
int maxScroll = getMaxScrollRow();
|
||||
if (wheel > 0) {
|
||||
scrollRow = Math.max(0, scrollRow - 1);
|
||||
int mouseX = Mouse.getEventX() * width / mc.displayWidth;
|
||||
int mouseY = height - Mouse.getEventY() * height / mc.displayHeight - 1;
|
||||
|
||||
if (mouseX >= guiLeft + infoLeft && mouseX < guiLeft + infoRight
|
||||
&& mouseY >= guiTop + infoTop && mouseY < guiTop + infoBottom) {
|
||||
// Info panel scroll
|
||||
int panelHeight = infoBottom - infoTop;
|
||||
int maxInfoScroll = Math.max(0, infoContentHeight - panelHeight);
|
||||
if (wheel > 0) {
|
||||
infoScrollOffset = Math.max(0, infoScrollOffset - 10);
|
||||
} else {
|
||||
infoScrollOffset = Math.min(maxInfoScroll, infoScrollOffset + 10);
|
||||
}
|
||||
} else {
|
||||
scrollRow = Math.min(maxScroll, scrollRow + 1);
|
||||
int maxScroll = getMaxScrollRow();
|
||||
if (wheel > 0) {
|
||||
scrollRow = Math.max(0, scrollRow - 1);
|
||||
} else {
|
||||
scrollRow = Math.min(maxScroll, scrollRow + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
|
||||
public Map<String, List<String>> languages = new HashMap();
|
||||
protected int scriptLimit = 1;
|
||||
public List<String> hookList = new ArrayList<String>();
|
||||
protected boolean loaded = false;
|
||||
protected volatile boolean loaded = false;
|
||||
protected boolean serverDataReceived = false;
|
||||
protected String scriptSessionToken = null;
|
||||
|
||||
protected Map<Integer, GuiScriptTextArea> textAreas = new HashMap<>();
|
||||
protected GuiScreen parent;
|
||||
@@ -766,6 +767,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
|
||||
return;
|
||||
}
|
||||
if (result.kind == IScriptHandlerPacket.GuiDataKind.METADATA) {
|
||||
if (compound.hasKey("ScriptSessionToken"))
|
||||
scriptSessionToken = compound.getString("ScriptSessionToken");
|
||||
loadLanguagesData(compound);
|
||||
return;
|
||||
}
|
||||
@@ -776,6 +779,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
|
||||
return;
|
||||
}
|
||||
|
||||
if (compound.hasKey("ScriptSessionToken"))
|
||||
scriptSessionToken = compound.getString("ScriptSessionToken");
|
||||
loadLanguagesData(compound);
|
||||
}
|
||||
|
||||
@@ -825,9 +830,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
|
||||
this.setScript();
|
||||
|
||||
if (handler instanceof IScriptHandlerPacket)
|
||||
((IScriptHandlerPacket) handler).sync();
|
||||
|
||||
|
||||
((IScriptHandlerPacket) handler).sync(scriptSessionToken);
|
||||
}
|
||||
|
||||
public void textUpdate(String text) {
|
||||
|
||||
@@ -71,16 +71,29 @@ public interface IScriptHandlerPacket extends IScriptHandler {
|
||||
* GuiScriptInterface is responsible for updating the active {@link IScriptUnit} with current editor text first.
|
||||
*/
|
||||
default void sync() {
|
||||
sync(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit save packets with a session token for server-side verification.
|
||||
* The token prevents saving unloaded default data due to race conditions.
|
||||
*/
|
||||
default void sync(String sessionToken) {
|
||||
List<IScriptUnit> containers = getScripts();
|
||||
for (int i = 0; i < containers.size(); i++) {
|
||||
IScriptUnit container = containers.get(i);
|
||||
sendSavePacket(i, containers.size(), container.writeToNBT(new NBTTagCompound()));
|
||||
NBTTagCompound nbt = container.writeToNBT(new NBTTagCompound());
|
||||
if (sessionToken != null && !sessionToken.isEmpty())
|
||||
nbt.setString("ScriptSessionToken", sessionToken);
|
||||
sendSavePacket(i, containers.size(), nbt);
|
||||
}
|
||||
|
||||
NBTTagCompound scriptData = new NBTTagCompound();
|
||||
scriptData.setString("ScriptLanguage", getLanguage());
|
||||
scriptData.setBoolean("ScriptEnabled", getEnabled());
|
||||
scriptData.setTag("ScriptConsole", NBTTags.NBTLongStringMap(getConsoleText()));
|
||||
if (sessionToken != null && !sessionToken.isEmpty())
|
||||
scriptData.setString("ScriptSessionToken", sessionToken);
|
||||
sendSavePacket(-1, containers.size(), scriptData);
|
||||
}
|
||||
}
|
||||
@@ -76,4 +76,15 @@ public class MagicData implements IMagicData {
|
||||
MagicEntry entry = magics.get(id);
|
||||
return entry == null ? 0 : entry.split;
|
||||
}
|
||||
|
||||
public MagicData copy() {
|
||||
MagicData copy = new MagicData();
|
||||
for (java.util.Map.Entry<Integer, MagicEntry> e : magics.entrySet()) {
|
||||
MagicEntry entryCopy = new MagicEntry();
|
||||
entryCopy.damage = e.getValue().damage;
|
||||
entryCopy.split = e.getValue().split;
|
||||
copy.magics.put(e.getKey(), entryCopy);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package noppes.npcs.controllers.data;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.Event;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.network.PacketUtil;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
@@ -117,9 +119,16 @@ public abstract class SingleScriptHandler extends ScriptHandler {
|
||||
* - tab == 0: Script content NBT follows
|
||||
* - tab != 0 (typically -1): Script metadata (language, enabled) follows
|
||||
*/
|
||||
public void saveScript(ByteBuf buffer) throws IOException {
|
||||
public boolean saveScript(ByteBuf buffer, EntityPlayer player) throws IOException {
|
||||
int tab = buffer.readInt();
|
||||
int totalScripts = buffer.readInt();
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(buffer);
|
||||
|
||||
// Verify session token
|
||||
String token = compound.getString("ScriptSessionToken");
|
||||
if (!PacketUtil.verifyScriptSession(player, token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (totalScripts == 0) {
|
||||
this.container = null;
|
||||
@@ -127,15 +136,14 @@ public abstract class SingleScriptHandler extends ScriptHandler {
|
||||
|
||||
if (tab == 0) {
|
||||
// Script content
|
||||
NBTTagCompound tabCompound = ByteBufUtils.readNBT(buffer);
|
||||
this.container = IScriptUnit.createFromNBT(tabCompound, this);
|
||||
this.container = IScriptUnit.createFromNBT(compound, this);
|
||||
} else {
|
||||
// Metadata (language, enabled)
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(buffer);
|
||||
this.setLanguage(compound.getString("ScriptLanguage"));
|
||||
normalizeLanguage();
|
||||
this.setEnabled(compound.getBoolean("ScriptEnabled"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,15 +5,18 @@ import cpw.mods.fml.common.eventhandler.EventBus;
|
||||
import foxz.command.ScriptedCommand;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.AttributeController;
|
||||
import kamkeel.npcs.controllers.EnergyController;
|
||||
import kamkeel.npcs.controllers.ProfileController;
|
||||
import kamkeel.npcs.controllers.TelegraphController;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import kamkeel.npcs.entity.EntityEnergyPanel;
|
||||
import kamkeel.npcs.entity.EntityEnergySweeper;
|
||||
import kamkeel.npcs.entity.EntityEnergyExplosion;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergySlicer;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -60,6 +63,7 @@ import noppes.npcs.api.IBlock;
|
||||
import noppes.npcs.api.ICommand;
|
||||
import noppes.npcs.api.IContainer;
|
||||
import noppes.npcs.api.IDamageSource;
|
||||
import noppes.npcs.api.IEnergyController;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.IParticle;
|
||||
import noppes.npcs.api.IPos;
|
||||
@@ -99,12 +103,14 @@ import noppes.npcs.scripted.entity.ScriptDBCPlayer;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyBeam;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyDisc;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyDome;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyExplosion;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyLaser;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyOrb;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyPanel;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyProjectile;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergySlicer;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergySweeper;
|
||||
import noppes.npcs.scripted.entity.ScriptEnergyZone;
|
||||
import noppes.npcs.scripted.entity.ScriptEntity;
|
||||
import noppes.npcs.scripted.entity.ScriptEntityItem;
|
||||
import noppes.npcs.scripted.entity.ScriptFishHook;
|
||||
@@ -462,6 +468,10 @@ public class NpcAPI extends AbstractNpcAPI {
|
||||
data = new ScriptEntityData(new ScriptEnergyPanel<>((EntityEnergyPanel) entity));
|
||||
else if (entity instanceof EntityEnergySweeper)
|
||||
data = new ScriptEntityData(new ScriptEnergySweeper<>((EntityEnergySweeper) entity));
|
||||
else if (entity instanceof EntityEnergyExplosion)
|
||||
data = new ScriptEntityData(new ScriptEnergyExplosion<>((EntityEnergyExplosion) entity));
|
||||
else if (entity instanceof EntityAbilityZone)
|
||||
data = new ScriptEntityData(new ScriptEnergyZone<>((EntityAbilityZone) entity));
|
||||
else
|
||||
data = new ScriptEntityData(new ScriptEntity<>(entity));
|
||||
entity.registerExtendedProperties("ScriptedObject", data);
|
||||
@@ -612,6 +622,11 @@ public class NpcAPI extends AbstractNpcAPI {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyController getEnergyController() {
|
||||
return EnergyController.Instance;
|
||||
}
|
||||
|
||||
public EventBus events() {
|
||||
return EVENT_BUS;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ public class EntityType {
|
||||
public static final int ENERGY_BARRIER = 19;
|
||||
public static final int ENERGY_DOME = 20;
|
||||
public static final int ENERGY_PANEL = 21;
|
||||
public static final int ENERGY_EXPLOSION = 22;
|
||||
}
|
||||
@@ -2,8 +2,12 @@ package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyAbility;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.entity.IEnergyAbility;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.api.handler.data.IMagicData;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
@@ -128,4 +132,35 @@ public abstract class ScriptEnergyAbility<T extends EntityEnergyAbility> extends
|
||||
public void setIgnoreIFrames(boolean ignore) {
|
||||
entity.setIgnoreIFrames(ignore);
|
||||
}
|
||||
|
||||
// ==================== CUSTOM DAMAGE DATA ====================
|
||||
|
||||
public INbt getDamageData() {
|
||||
NBTTagCompound data = entity.getCustomDamageData();
|
||||
if (data == null) {
|
||||
data = new NBTTagCompound();
|
||||
entity.setCustomDamageData(data);
|
||||
}
|
||||
return NpcAPI.Instance().getINbt(data);
|
||||
}
|
||||
|
||||
public void setDamageData(INbt data) {
|
||||
if (data == null) {
|
||||
entity.setCustomDamageData(null);
|
||||
} else {
|
||||
entity.setCustomDamageData((NBTTagCompound) data.getMCNBT());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== MAGIC DATA ====================
|
||||
|
||||
public IMagicData getMagicData() {
|
||||
return entity.getMagicData();
|
||||
}
|
||||
|
||||
public void setMagicData(IMagicData data) {
|
||||
if (data instanceof MagicData) {
|
||||
entity.setMagicData((MagicData) data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class ScriptEnergyBeam<T extends EntityAbilityBeam> extends ScriptEnergyP
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
@@ -70,7 +70,7 @@ public class ScriptEnergyBeam<T extends EntityAbilityBeam> extends ScriptEnergyP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ScriptEnergyDisc<T extends EntityAbilityDisc> extends ScriptEnergyP
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
@@ -86,7 +86,7 @@ public class ScriptEnergyDisc<T extends EntityAbilityDisc> extends ScriptEnergyP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergyExplosion;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket;
|
||||
import noppes.npcs.api.entity.IEnergyExplosion;
|
||||
import noppes.npcs.scripted.constants.EntityType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Script wrapper for standalone energy explosion entities.
|
||||
*/
|
||||
public class ScriptEnergyExplosion<T extends EntityEnergyExplosion> extends ScriptEnergyAbility<T> implements IEnergyExplosion {
|
||||
|
||||
public ScriptEnergyExplosion(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
// ==================== TYPE ====================
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return EntityType.ENERGY_EXPLOSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean typeOf(int type) {
|
||||
return type == EntityType.ENERGY_EXPLOSION || super.typeOf(type);
|
||||
}
|
||||
|
||||
// ==================== RADIUS & DURATION ====================
|
||||
|
||||
@Override
|
||||
public float getRadius() {
|
||||
return entity.getExplosionRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadius(float radius) {
|
||||
entity.setExplosionRadius(radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() {
|
||||
return entity.getDurationTicks();
|
||||
}
|
||||
|
||||
// ==================== DAMAGE ====================
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return entity.getDamage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamage(float damage) {
|
||||
entity.setDamage(damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getKnockback() {
|
||||
return entity.getKnockback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKnockback(float knockback) {
|
||||
entity.setKnockback(knockback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getKnockbackUp() {
|
||||
return entity.getKnockbackUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
entity.setKnockbackUp(knockbackUp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamageFalloff() {
|
||||
return entity.getDamageFalloff();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamageFalloff(float falloff) {
|
||||
entity.setDamageFalloff(falloff);
|
||||
}
|
||||
|
||||
// ==================== SPAWNING ====================
|
||||
|
||||
@Override
|
||||
public void spawn() {
|
||||
if (entity.addedToChunk || entity.worldObj == null) return;
|
||||
|
||||
if (!entity.worldObj.isRemote) {
|
||||
// Send visual to clients via packet (explosion is packet-driven, not a tracked entity)
|
||||
String instanceId = "energy_explosion_" + UUID.randomUUID();
|
||||
PacketHandler.Instance.sendToAll(new EnergyExplosionSpawnPacket(
|
||||
instanceId, entity.exportSpawnNBT()));
|
||||
|
||||
// Spawn server-side for damage logic (if damage enabled)
|
||||
if (entity.isDamageEnabled()) {
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
@@ -102,7 +102,7 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class ScriptEnergyOrb<T extends EntityAbilityOrb> extends ScriptEnergyPro
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
@@ -38,7 +38,7 @@ public class ScriptEnergyOrb<T extends EntityAbilityOrb> extends ScriptEnergyPro
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
|
||||
@@ -54,4 +54,23 @@ public class ScriptEnergyPanel<T extends EntityEnergyPanel> extends ScriptEnergy
|
||||
public boolean isLaunched() {
|
||||
return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED;
|
||||
}
|
||||
|
||||
public void setPanelYaw(float yaw) {
|
||||
entity.setPanelYaw(yaw);
|
||||
}
|
||||
|
||||
public void setPanelMode(int mode) {
|
||||
EntityEnergyPanel.PanelMode[] values = EntityEnergyPanel.PanelMode.values();
|
||||
if (mode >= 0 && mode < values.length) {
|
||||
entity.setMode(values[mode]);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SPAWNING ====================
|
||||
|
||||
public void spawn() {
|
||||
if (!entity.addedToChunk && entity.worldObj != null) {
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,7 @@ public class ScriptEnergyProjectile<T extends EntityEnergyProjectile> extends Sc
|
||||
|
||||
// ==================== FIRE ====================
|
||||
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
if (target != null) {
|
||||
Entity mcTarget = target.getMCEntity();
|
||||
setMotionToward(mcTarget.posX, mcTarget.posY + mcTarget.getEyeHeight(), mcTarget.posZ);
|
||||
@@ -267,12 +267,12 @@ public class ScriptEnergyProjectile<T extends EntityEnergyProjectile> extends Sc
|
||||
ensureSpawned();
|
||||
}
|
||||
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
setMotionToward(x, y, z);
|
||||
ensureSpawned();
|
||||
}
|
||||
|
||||
public void fire(float yaw, float pitch) {
|
||||
public void fireDirection(float yaw, float pitch) {
|
||||
float yawRad = (float) Math.toRadians(yaw);
|
||||
float pitchRad = (float) Math.toRadians(pitch);
|
||||
entity.motionX = -Math.sin(yawRad) * Math.cos(pitchRad) * entity.getSpeed();
|
||||
@@ -281,16 +281,16 @@ public class ScriptEnergyProjectile<T extends EntityEnergyProjectile> extends Sc
|
||||
ensureSpawned();
|
||||
}
|
||||
|
||||
public void fire(IEntityLivingBase caster) {
|
||||
public void fireFrom(IEntityLivingBase caster) {
|
||||
if (caster == null) return;
|
||||
initFromCaster(caster);
|
||||
fire(caster.getRotation(), caster.getPitch());
|
||||
fireDirection(caster.getRotation(), caster.getPitch());
|
||||
}
|
||||
|
||||
public void fire(IEntityLivingBase caster, IEntity target) {
|
||||
public void fireFrom(IEntityLivingBase caster, IEntity target) {
|
||||
if (caster == null) return;
|
||||
initFromCaster(caster);
|
||||
fire(target);
|
||||
fireAt(target);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ScriptEnergySlicer<T extends EntityEnergySlicer> extends ScriptEner
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
public void fire(IEntity target) {
|
||||
public void fireAt(IEntity target) {
|
||||
ensureSpawned();
|
||||
EntityLivingBase mcTarget = null;
|
||||
if (target != null && target.getMCEntity() instanceof EntityLivingBase) {
|
||||
@@ -58,7 +58,7 @@ public class ScriptEnergySlicer<T extends EntityEnergySlicer> extends ScriptEner
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(double x, double y, double z) {
|
||||
public void fireAt(double x, double y, double z) {
|
||||
ensureSpawned();
|
||||
double dx = x - entity.posX;
|
||||
double dy = y - entity.posY;
|
||||
|
||||
@@ -1,9 +1,116 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityEnergySweeper;
|
||||
import noppes.npcs.api.entity.IEnergySweeper;
|
||||
|
||||
public class ScriptEnergySweeper<T extends EntityEnergySweeper> extends ScriptEnergyAbility<T> implements IEnergySweeper {
|
||||
|
||||
public class ScriptEnergySweeper<T extends EntityEnergySweeper> extends ScriptEnergyAbility<T> {
|
||||
public ScriptEnergySweeper(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
// ==================== BEAM DIMENSIONS ====================
|
||||
|
||||
@Override
|
||||
public float getBeamLength() {
|
||||
return entity.getBeamLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeamLength(float length) {
|
||||
entity.setBeamLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBeamWidth() {
|
||||
return entity.getBeamWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeamWidth(float width) {
|
||||
entity.setBeamWidth(width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBeamHeight() {
|
||||
return entity.getBeamHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeamHeight(float height) {
|
||||
entity.setBeamHeight(height);
|
||||
}
|
||||
|
||||
// ==================== ROTATION ====================
|
||||
|
||||
@Override
|
||||
public float getSweepSpeed() {
|
||||
return entity.getSweepSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSweepSpeed(float degreesPerTick) {
|
||||
entity.setSweepSpeed(degreesPerTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfRotations() {
|
||||
return entity.getNumberOfRotations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNumberOfRotations(int rotations) {
|
||||
entity.setNumberOfRotations(rotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockOnTarget() {
|
||||
return entity.isLockOnTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLockOnTarget(boolean lock) {
|
||||
entity.setLockOnTarget(lock);
|
||||
}
|
||||
|
||||
// ==================== DAMAGE ====================
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return entity.getDamage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamage(float damage) {
|
||||
entity.setDamage(damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDamageInterval() {
|
||||
return entity.getDamageInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamageInterval(int ticks) {
|
||||
entity.setDamageInterval(ticks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPiercing() {
|
||||
return entity.isPiercing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPiercing(boolean piercing) {
|
||||
entity.setPiercing(piercing);
|
||||
}
|
||||
|
||||
// ==================== SPAWNING ====================
|
||||
|
||||
@Override
|
||||
public void spawn() {
|
||||
if (!entity.addedToChunk && entity.worldObj != null) {
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package noppes.npcs.scripted.entity;
|
||||
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.entity.IEnergyZone;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
* Script wrapper for EntityAbilityZone (Hazard and Trap zones).
|
||||
*/
|
||||
public class ScriptEnergyZone<T extends EntityAbilityZone> extends ScriptEntity<T> implements IEnergyZone {
|
||||
|
||||
public ScriptEnergyZone(T entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
// ==================== ZONE TYPE ====================
|
||||
|
||||
@Override
|
||||
public int getZoneType() {
|
||||
return entity.getZoneType().ordinal();
|
||||
}
|
||||
|
||||
// ==================== COMMON ZONE PROPERTIES ====================
|
||||
|
||||
@Override
|
||||
public int getZoneShape() {
|
||||
return entity.getShape().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZoneShape(int shape) {
|
||||
EntityAbilityZone.ZoneShape[] values = EntityAbilityZone.ZoneShape.values();
|
||||
if (shape >= 0 && shape < values.length) {
|
||||
entity.setShape(values[shape]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRadius() {
|
||||
return entity.getRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadius(float radius) {
|
||||
entity.setRadius(radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getZoneHeight() {
|
||||
return entity.getZoneHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZoneHeight(float height) {
|
||||
entity.setZoneHeight(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() {
|
||||
return entity.getDurationTicks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(int ticks) {
|
||||
entity.setDurationTicks(ticks);
|
||||
}
|
||||
|
||||
// ==================== COLORS ====================
|
||||
|
||||
@Override
|
||||
public int getInnerColor() {
|
||||
return entity.getInnerColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInnerColor(int color) {
|
||||
entity.setInnerColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOuterColor() {
|
||||
return entity.getOuterColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOuterColor(int color) {
|
||||
entity.setOuterColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOuterColorEnabled() {
|
||||
return entity.isOuterColorEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOuterColorEnabled(boolean enabled) {
|
||||
entity.setOuterColorEnabled(enabled);
|
||||
}
|
||||
|
||||
// ==================== VISUALS ====================
|
||||
|
||||
@Override
|
||||
public float getParticleDensity() {
|
||||
return entity.getParticleDensity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParticleDensity(float density) {
|
||||
entity.setParticleDensity(density);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getParticleScale() {
|
||||
return entity.getParticleScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParticleScale(float scale) {
|
||||
entity.setParticleScale(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAnimSpeed() {
|
||||
return entity.getAnimSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnimSpeed(float speed) {
|
||||
entity.setAnimSpeed(speed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoreIFrames() {
|
||||
return entity.isIgnoreIFrames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIgnoreIFrames(boolean ignore) {
|
||||
entity.setIgnoreIFrames(ignore);
|
||||
}
|
||||
|
||||
// ==================== HAZARD-SPECIFIC ====================
|
||||
|
||||
@Override
|
||||
public float getDamagePerSecond() {
|
||||
return entity.getDamagePerSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamagePerSecond(float dps) {
|
||||
entity.setDamagePerSecond(dps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDamageInterval() {
|
||||
return entity.getDamageInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamageInterval(int ticks) {
|
||||
entity.setDamageInterval(ticks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffectsCaster() {
|
||||
return entity.isAffectsCaster();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAffectsCaster(boolean affects) {
|
||||
entity.setAffectsCaster(affects);
|
||||
}
|
||||
|
||||
// ==================== TRAP-SPECIFIC ====================
|
||||
|
||||
@Override
|
||||
public float getTriggerRadius() {
|
||||
return entity.getTriggerRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggerRadius(float radius) {
|
||||
entity.setTriggerRadius(radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArmTime() {
|
||||
return entity.getArmTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArmTime(int ticks) {
|
||||
entity.setArmTime(ticks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxTriggers() {
|
||||
return entity.getMaxTriggers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxTriggers(int max) {
|
||||
entity.setMaxTriggers(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTriggerCooldown() {
|
||||
return entity.getTriggerCooldown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggerCooldown(int ticks) {
|
||||
entity.setTriggerCooldown(ticks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return entity.getDamage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamage(float damage) {
|
||||
entity.setDamage(damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getKnockback() {
|
||||
return entity.getKnockback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKnockback(float knockback) {
|
||||
entity.setKnockback(knockback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return entity.isVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
entity.setVisible(visible);
|
||||
}
|
||||
|
||||
// ==================== CUSTOM DAMAGE DATA ====================
|
||||
|
||||
@Override
|
||||
public INbt getDamageData() {
|
||||
NBTTagCompound data = entity.getCustomDamageData();
|
||||
if (data == null) {
|
||||
data = new NBTTagCompound();
|
||||
entity.setCustomDamageData(data);
|
||||
}
|
||||
return NpcAPI.Instance().getINbt(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamageData(INbt data) {
|
||||
if (data == null) {
|
||||
entity.setCustomDamageData(null);
|
||||
} else {
|
||||
entity.setCustomDamageData((NBTTagCompound) data.getMCNBT());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SPAWNING ====================
|
||||
|
||||
@Override
|
||||
public void spawn() {
|
||||
if (!entity.addedToChunk && entity.worldObj != null) {
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,11 @@ public class ScriptCustomItem extends ScriptCustomizableItem implements IItemCus
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync() {
|
||||
sendSavePacket(-1, getScripts().size(), getMCNbt());
|
||||
public void sync(String sessionToken) {
|
||||
NBTTagCompound compound = getMCNbt();
|
||||
if (sessionToken != null && !sessionToken.isEmpty())
|
||||
compound.setString("ScriptSessionToken", sessionToken);
|
||||
sendSavePacket(-1, getScripts().size(), compound);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
|
||||
@@ -1791,6 +1791,13 @@ ability.section.explosive=Explosive
|
||||
ability.section.colors=Colors
|
||||
ability.section.effects=Effects
|
||||
ability.section.burst=Burst
|
||||
ability.section.magic=Magic
|
||||
ability.magic.note=Magic Note
|
||||
ability.magic.usesCaster=Uses the Caster's Magic Data
|
||||
ability.magic.editor=Magic Editor
|
||||
ability.preview.damage=Damage
|
||||
ability.preview.health=Health
|
||||
ability.preview.magic.casters=Magic: Caster's
|
||||
ability.section.dodgeAnimations=Dodge Animations
|
||||
ability.section.visual=Visual
|
||||
ability.section.projectile1=Projectile 1
|
||||
@@ -2051,6 +2058,8 @@ ability.hover.weight=Higher weight = more likely to be chosen when multiple abil
|
||||
ability.hover.lockMovement=Prevents NPC pathfinding during the selected phases (Windup, Active, or Both)
|
||||
ability.hover.rotationMode=Free: normal AI rotation. Locked: freeze facing direction. Track: force face target for precise aiming.
|
||||
ability.hover.rotationPhase=When the rotation mode applies (Windup, Active, or Both)
|
||||
ability.trackDelay=Track Delay
|
||||
ability.hover.trackDelay=How sluggish tracking is. 0 = instant (impossible to dodge). Higher = slower rotation toward target. Default 3 for lasers.
|
||||
ability.hover.interruptible=If enabled, ability can be interrupted by taking damage
|
||||
ability.hover.ignoreIFrames=Temporarily clears target hurt resistance before applying ability damage, then restores it.
|
||||
ability.hover.dazed=Stun duration in ticks if ability is interrupted (20 ticks = 1 second)
|
||||
|
||||
Reference in new issue
Block a user