mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 16:37:45 +00:00
Ability Anchor Points + Orb / Disc Summoning
This commit is contained in:
1 parent
0484596a91
commit
0f44a3c6c6
12 files changed
+984
-95
No files matched your search
@@ -0,0 +1,55 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
/**
|
||||
* Defines anchor points for ability charging effects.
|
||||
* Determines where the charging visual (orb, disc, etc.) appears relative to the entity.
|
||||
*/
|
||||
public enum AnchorPoint {
|
||||
FRONT(0, "Front"), // In front of entity face (default)
|
||||
CENTER(1, "Center"), // Entity center
|
||||
RIGHT_HAND(2, "Right Hand"), // Right arm/hand position
|
||||
LEFT_HAND(3, "Left Hand"), // Left arm/hand position
|
||||
ABOVE_HEAD(4, "Above Head"), // Above the entity
|
||||
CHEST(5, "Chest"); // Chest level, centered
|
||||
|
||||
private final int id;
|
||||
private final String displayName;
|
||||
|
||||
AnchorPoint(int id, String displayName) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get AnchorPoint from NBT id value.
|
||||
* Returns FRONT if id is invalid.
|
||||
*/
|
||||
public static AnchorPoint fromId(int id) {
|
||||
for (AnchorPoint anchor : values()) {
|
||||
if (anchor.id == id) {
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
return FRONT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all display names for GUI dropdowns.
|
||||
*/
|
||||
public static String[] getDisplayNames() {
|
||||
AnchorPoint[] values = values();
|
||||
String[] names = new String[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
names[i] = values[i].displayName;
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@ package kamkeel.npcs.controllers.data.ability.type;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
@@ -28,7 +30,7 @@ public class AbilityDisc extends Ability {
|
||||
private float discRadius = 1.0f;
|
||||
private float discThickness = 0.2f;
|
||||
private float maxDistance = 35.0f;
|
||||
private int maxLifetime = 250;
|
||||
private int maxLifetime = 200;
|
||||
|
||||
// Combat properties
|
||||
private float damage = 8.0f;
|
||||
@@ -66,6 +68,12 @@ public class AbilityDisc extends Ability {
|
||||
private float lightningDensity = 0.15f;
|
||||
private float lightningRadius = 0.5f;
|
||||
|
||||
// Anchor point for charging position
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
|
||||
// Transient state for disc entity (used during windup charging)
|
||||
private transient int discEntityId = -1;
|
||||
|
||||
public AbilityDisc() {
|
||||
this.typeId = "ability.cnpc.disc";
|
||||
this.name = "Disc";
|
||||
@@ -105,24 +113,41 @@ public class AbilityDisc extends Ability {
|
||||
public void onExecute(EntityNPCInterface npc, EntityLivingBase target, World world) {
|
||||
if (world.isRemote) return;
|
||||
|
||||
double spawnX = npc.posX;
|
||||
double spawnY = npc.posY + npc.getEyeHeight() * 0.8;
|
||||
double spawnZ = npc.posZ;
|
||||
// Find and start moving the disc that was spawned during windup
|
||||
if (discEntityId != -1) {
|
||||
Entity entity = world.getEntityByID(discEntityId);
|
||||
if (entity instanceof EntityAbilityDisc) {
|
||||
EntityAbilityDisc disc = (EntityAbilityDisc) entity;
|
||||
disc.startMoving(target);
|
||||
}
|
||||
discEntityId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
EntityAbilityDisc disc = new EntityAbilityDisc(
|
||||
world, npc, target,
|
||||
spawnX, spawnY, spawnZ,
|
||||
discRadius, discThickness, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp,
|
||||
speed, homing, homingStrength, homingRange,
|
||||
boomerang, boomerangDelay,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel,
|
||||
maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius
|
||||
);
|
||||
@Override
|
||||
public void onWindUpTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote) return;
|
||||
|
||||
world.spawnEntityInWorld(disc);
|
||||
// Spawn disc in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
// Create disc in charging mode - follows NPC based on anchor point during windup
|
||||
EntityAbilityDisc disc = EntityAbilityDisc.createCharging(
|
||||
world, npc, target,
|
||||
discRadius, discThickness, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp,
|
||||
speed, homing, homingStrength, homingRange,
|
||||
boomerang, boomerangDelay,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel,
|
||||
maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius,
|
||||
anchorPoint, // Anchor point for charging position
|
||||
windUpTicks // Charge duration = windup duration
|
||||
);
|
||||
|
||||
world.spawnEntityInWorld(disc);
|
||||
discEntityId = disc.getEntityId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,6 +160,14 @@ public class AbilityDisc extends Ability {
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityNPCInterface npc, DamageSource source, float damage) {
|
||||
// Despawn charging disc if interrupted during windup
|
||||
if (discEntityId != -1) {
|
||||
Entity entity = npc.worldObj.getEntityByID(discEntityId);
|
||||
if (entity != null) {
|
||||
entity.setDead();
|
||||
}
|
||||
discEntityId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,6 +226,7 @@ public class AbilityDisc extends Ability {
|
||||
nbt.setBoolean("lightningEffect", lightningEffect);
|
||||
nbt.setFloat("lightningDensity", lightningDensity);
|
||||
nbt.setFloat("lightningRadius", lightningRadius);
|
||||
nbt.setInteger("anchorPoint", anchorPoint.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,7 +235,7 @@ public class AbilityDisc extends Ability {
|
||||
this.discRadius = nbt.hasKey("discRadius") ? nbt.getFloat("discRadius") : 1.0f;
|
||||
this.discThickness = nbt.hasKey("discThickness") ? nbt.getFloat("discThickness") : 0.2f;
|
||||
this.maxDistance = nbt.hasKey("maxDistance") ? nbt.getFloat("maxDistance") : 35.0f;
|
||||
this.maxLifetime = nbt.hasKey("maxLifetime") ? nbt.getInteger("maxLifetime") : 250;
|
||||
this.maxLifetime = nbt.hasKey("maxLifetime") ? nbt.getInteger("maxLifetime") : 200;
|
||||
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
|
||||
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.2f;
|
||||
this.knockbackUp = nbt.hasKey("knockbackUp") ? nbt.getFloat("knockbackUp") : 0.15f;
|
||||
@@ -224,6 +258,7 @@ public class AbilityDisc extends Ability {
|
||||
this.lightningEffect = nbt.hasKey("lightningEffect") && nbt.getBoolean("lightningEffect");
|
||||
this.lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 0.15f;
|
||||
this.lightningRadius = nbt.hasKey("lightningRadius") ? nbt.getFloat("lightningRadius") : 0.5f;
|
||||
this.anchorPoint = nbt.hasKey("anchorPoint") ? AnchorPoint.fromId(nbt.getInteger("anchorPoint")) : AnchorPoint.FRONT;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -281,4 +316,6 @@ public class AbilityDisc extends Ability {
|
||||
public void setLightningDensity(float lightningDensity) { this.lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius() { return lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { this.lightningRadius = lightningRadius; }
|
||||
public AnchorPoint getAnchorPoint() { return anchorPoint; }
|
||||
public void setAnchorPoint(AnchorPoint anchorPoint) { this.anchorPoint = anchorPoint; }
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
@@ -66,6 +67,9 @@ public class AbilityEnergyBeam extends Ability {
|
||||
private float lightningDensity = 0.15f;
|
||||
private float lightningRadius = 0.5f;
|
||||
|
||||
// Anchor point for charging position
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
|
||||
// Transient state for beam entity (used during windup charging)
|
||||
private transient int beamEntityId = -1;
|
||||
|
||||
@@ -127,7 +131,7 @@ public class AbilityEnergyBeam extends Ability {
|
||||
if (tick == 1) {
|
||||
float offsetDist = 1.0f;
|
||||
|
||||
// Create beam in charging mode - follows NPC's look direction during windup
|
||||
// Create beam in charging mode - follows NPC based on anchor point during windup
|
||||
EntityAbilityBeam beam = EntityAbilityBeam.createCharging(
|
||||
world, npc, target,
|
||||
beamWidth, headSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
@@ -139,7 +143,8 @@ public class AbilityEnergyBeam extends Ability {
|
||||
lightningEffect, lightningDensity, lightningRadius,
|
||||
lockMovement, // Anchored mode
|
||||
windUpTicks, // Charge duration = windup duration
|
||||
offsetDist // Offset distance in front of NPC
|
||||
offsetDist, // Offset distance (used by FRONT anchor)
|
||||
anchorPoint // Anchor point for charging position
|
||||
);
|
||||
|
||||
world.spawnEntityInWorld(beam);
|
||||
@@ -221,6 +226,7 @@ public class AbilityEnergyBeam extends Ability {
|
||||
nbt.setBoolean("lightningEffect", lightningEffect);
|
||||
nbt.setFloat("lightningDensity", lightningDensity);
|
||||
nbt.setFloat("lightningRadius", lightningRadius);
|
||||
nbt.setInteger("anchorPoint", anchorPoint.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,6 +256,7 @@ public class AbilityEnergyBeam extends Ability {
|
||||
this.lightningEffect = nbt.hasKey("lightningEffect") && nbt.getBoolean("lightningEffect");
|
||||
this.lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 0.15f;
|
||||
this.lightningRadius = nbt.hasKey("lightningRadius") ? nbt.getFloat("lightningRadius") : 0.5f;
|
||||
this.anchorPoint = nbt.hasKey("anchorPoint") ? AnchorPoint.fromId(nbt.getInteger("anchorPoint")) : AnchorPoint.FRONT;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -303,4 +310,6 @@ public class AbilityEnergyBeam extends Ability {
|
||||
public void setLightningDensity(float lightningDensity) { this.lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius() { return lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { this.lightningRadius = lightningRadius; }
|
||||
public AnchorPoint getAnchorPoint() { return anchorPoint; }
|
||||
public void setAnchorPoint(AnchorPoint anchorPoint) { this.anchorPoint = anchorPoint; }
|
||||
}
|
||||
@@ -3,11 +3,13 @@ package kamkeel.npcs.controllers.data.ability.type;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
@@ -63,6 +65,12 @@ public class AbilityOrb extends Ability {
|
||||
private float lightningRadius = 0.5f; // Max distance lightning arcs - scales with orbSize
|
||||
private int lightningFadeTime = 6; // Ticks before lightning fades out
|
||||
|
||||
// Anchor point for charging position
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
|
||||
// Transient state for orb entity (used during windup charging)
|
||||
private transient int orbEntityId = -1;
|
||||
|
||||
public AbilityOrb() {
|
||||
this.typeId = "ability.cnpc.orb";
|
||||
this.name = "Orb";
|
||||
@@ -102,25 +110,40 @@ public class AbilityOrb extends Ability {
|
||||
public void onExecute(EntityNPCInterface npc, EntityLivingBase target, World world) {
|
||||
if (world.isRemote) return;
|
||||
|
||||
// Spawn position at NPC's eye level
|
||||
double spawnX = npc.posX;
|
||||
double spawnY = npc.posY + npc.getEyeHeight() * 0.8;
|
||||
double spawnZ = npc.posZ;
|
||||
// Find and start moving the orb that was spawned during windup
|
||||
if (orbEntityId != -1) {
|
||||
Entity entity = world.getEntityByID(orbEntityId);
|
||||
if (entity instanceof EntityAbilityOrb) {
|
||||
EntityAbilityOrb orb = (EntityAbilityOrb) entity;
|
||||
orb.startMoving(target);
|
||||
}
|
||||
orbEntityId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and spawn the self-managing orb entity
|
||||
EntityAbilityOrb orb = new EntityAbilityOrb(
|
||||
world, npc, target,
|
||||
spawnX, spawnY, spawnZ,
|
||||
orbSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp,
|
||||
orbSpeed, homing, homingStrength, homingRange,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel,
|
||||
maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius, lightningFadeTime
|
||||
);
|
||||
@Override
|
||||
public void onWindUpTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote) return;
|
||||
|
||||
world.spawnEntityInWorld(orb);
|
||||
// Spawn orb in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
// Create orb in charging mode - follows NPC based on anchor point during windup
|
||||
EntityAbilityOrb orb = EntityAbilityOrb.createCharging(
|
||||
world, npc, target,
|
||||
orbSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp,
|
||||
orbSpeed, homing, homingStrength, homingRange,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel,
|
||||
maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius, lightningFadeTime,
|
||||
anchorPoint, // Anchor point for charging position
|
||||
windUpTicks // Charge duration = windup duration
|
||||
);
|
||||
|
||||
world.spawnEntityInWorld(orb);
|
||||
orbEntityId = orb.getEntityId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,7 +158,14 @@ public class AbilityOrb extends Ability {
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityNPCInterface npc, DamageSource source, float damage) {
|
||||
// Nothing to clean up - entity manages itself
|
||||
// Despawn charging orb if interrupted during windup
|
||||
if (orbEntityId != -1) {
|
||||
Entity entity = npc.worldObj.getEntityByID(orbEntityId);
|
||||
if (entity != null) {
|
||||
entity.setDead();
|
||||
}
|
||||
orbEntityId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,6 +222,7 @@ public class AbilityOrb extends Ability {
|
||||
nbt.setFloat("lightningDensity", lightningDensity);
|
||||
nbt.setFloat("lightningRadius", lightningRadius);
|
||||
nbt.setInteger("lightningFadeTime", lightningFadeTime);
|
||||
nbt.setInteger("anchorPoint", anchorPoint.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,6 +252,7 @@ public class AbilityOrb extends Ability {
|
||||
this.lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 0.15f;
|
||||
this.lightningRadius = nbt.hasKey("lightningRadius") ? nbt.getFloat("lightningRadius") : 0.5f;
|
||||
this.lightningFadeTime = nbt.hasKey("lightningFadeTime") ? nbt.getInteger("lightningFadeTime") : 6;
|
||||
this.anchorPoint = nbt.hasKey("anchorPoint") ? AnchorPoint.fromId(nbt.getInteger("anchorPoint")) : AnchorPoint.FRONT;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -423,4 +455,12 @@ public class AbilityOrb extends Ability {
|
||||
public void setLightningFadeTime(int lightningFadeTime) {
|
||||
this.lightningFadeTime = lightningFadeTime;
|
||||
}
|
||||
|
||||
public AnchorPoint getAnchorPoint() {
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
public void setAnchorPoint(AnchorPoint anchorPoint) {
|
||||
this.anchorPoint = anchorPoint;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
@@ -55,6 +57,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
private float chargeOffsetDistance = 1.0f;
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
|
||||
// Trail fading for non-anchored beams (comet effect)
|
||||
private boolean fadeTrail = false;
|
||||
@@ -214,7 +217,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
/**
|
||||
* Create a beam in charging mode (for windup phase).
|
||||
* The beam will grow from 0 to headSize over chargeDuration ticks.
|
||||
* Position follows the owner's look direction.
|
||||
* Position follows the owner based on anchor point.
|
||||
*/
|
||||
public static EntityAbilityBeam createCharging(World world, EntityNPCInterface owner, EntityLivingBase target,
|
||||
float beamWidth, float headSize, int innerColor, int outerColor,
|
||||
@@ -225,12 +228,13 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
int stunDuration, int slowDuration, int slowLevel,
|
||||
float maxDistance, int maxLifetime,
|
||||
boolean lightningEffect, float lightningDensity, float lightningRadius,
|
||||
boolean anchoredMode, int chargeDuration, float chargeOffsetDistance) {
|
||||
// Calculate initial position in front of owner's look direction
|
||||
float yaw = (float) Math.toRadians(owner.rotationYawHead);
|
||||
double spawnX = owner.posX - Math.sin(yaw) * chargeOffsetDistance;
|
||||
double spawnY = owner.posY + owner.getEyeHeight() * 0.7;
|
||||
double spawnZ = owner.posZ + Math.cos(yaw) * chargeOffsetDistance;
|
||||
boolean anchoredMode, int chargeDuration, float chargeOffsetDistance,
|
||||
AnchorPoint anchorPoint) {
|
||||
// Calculate initial position based on anchor point
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(owner, anchorPoint, chargeOffsetDistance);
|
||||
double spawnX = spawnPos.xCoord;
|
||||
double spawnY = spawnPos.yCoord;
|
||||
double spawnZ = spawnPos.zCoord;
|
||||
|
||||
EntityAbilityBeam beam = new EntityAbilityBeam(
|
||||
world, owner, target,
|
||||
@@ -247,6 +251,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
beam.chargeDuration = chargeDuration;
|
||||
beam.chargeTick = 0;
|
||||
beam.chargeOffsetDistance = chargeOffsetDistance;
|
||||
beam.anchorPoint = anchorPoint;
|
||||
|
||||
// Non-anchored beams have fading trail (comet effect)
|
||||
beam.fadeTrail = !anchoredMode;
|
||||
@@ -420,7 +425,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner's look direction.
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
chargeTick++;
|
||||
@@ -431,27 +436,28 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
return;
|
||||
}
|
||||
|
||||
// Follow owner's head/look direction (rotationYawHead for NPCs/players)
|
||||
float yaw;
|
||||
// Calculate position based on anchor point
|
||||
Vec3 pos;
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
yaw = (float) Math.toRadians(((EntityLivingBase) owner).rotationYawHead);
|
||||
pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorPoint, chargeOffsetDistance);
|
||||
} else {
|
||||
yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
// Fallback for non-living entities (shouldn't happen normally)
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
double offsetX = -Math.sin(yaw) * chargeOffsetDistance;
|
||||
double offsetZ = Math.cos(yaw) * chargeOffsetDistance;
|
||||
pos = Vec3.createVectorHelper(
|
||||
owner.posX + offsetX,
|
||||
owner.posY + owner.getEyeHeight() * 0.7,
|
||||
owner.posZ + offsetZ
|
||||
);
|
||||
}
|
||||
|
||||
double offsetX = -Math.sin(yaw) * chargeOffsetDistance;
|
||||
double offsetZ = Math.cos(yaw) * chargeOffsetDistance;
|
||||
|
||||
double newX = owner.posX + offsetX;
|
||||
double newY = owner.posY + owner.getEyeHeight() * 0.7;
|
||||
double newZ = owner.posZ + offsetZ;
|
||||
|
||||
setPosition(newX, newY, newZ);
|
||||
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
||||
|
||||
// Also update origin for when firing starts
|
||||
startX = newX;
|
||||
startY = newY;
|
||||
startZ = newZ;
|
||||
startX = pos.xCoord;
|
||||
startY = pos.yCoord;
|
||||
startZ = pos.zCoord;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -726,6 +732,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40;
|
||||
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
|
||||
this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f;
|
||||
this.anchorPoint = nbt.hasKey("AnchorPoint") ? AnchorPoint.fromId(nbt.getInteger("AnchorPoint")) : AnchorPoint.FRONT;
|
||||
this.fadeTrail = nbt.hasKey("FadeTrail") && nbt.getBoolean("FadeTrail");
|
||||
this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20;
|
||||
|
||||
@@ -761,6 +768,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance);
|
||||
nbt.setInteger("AnchorPoint", anchorPoint.getId());
|
||||
nbt.setBoolean("FadeTrail", fadeTrail);
|
||||
nbt.setInteger("TrailFadeTime", trailFadeTime);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
@@ -35,10 +37,45 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
private float discRadius = 1.0f; // Width of disc
|
||||
private float discThickness = 0.2f; // Height of disc
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
private float targetDiscRadius = 1.0f; // Full radius to grow to during charging
|
||||
private float targetDiscThickness = 0.2f; // Full thickness to grow to during charging
|
||||
|
||||
// Data watcher index for charging state (synced to clients)
|
||||
private static final int DW_CHARGING = 20;
|
||||
|
||||
public EntityAbilityDisc(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
super.entityInit();
|
||||
// Register data watcher for charging state
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if disc is in charging state (synced via data watcher).
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
private void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full constructor with all parameters (no lightning).
|
||||
*/
|
||||
@@ -117,6 +154,97 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a disc in charging mode (for windup phase).
|
||||
* The disc will grow from 0 to discRadius over chargeDuration ticks.
|
||||
* Position follows the owner based on anchor point.
|
||||
*/
|
||||
public static EntityAbilityDisc createCharging(World world, EntityNPCInterface owner, EntityLivingBase target,
|
||||
float discRadius, float discThickness, int innerColor, int outerColor,
|
||||
boolean outerColorEnabled, float outerColorWidth, float rotationSpeed,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
float speed, boolean homing, float homingStrength, float homingRange,
|
||||
boolean boomerang, int boomerangDelay,
|
||||
boolean explosive, float explosionRadius, float explosionDamageFalloff,
|
||||
int stunDuration, int slowDuration, int slowLevel,
|
||||
float maxDistance, int maxLifetime,
|
||||
boolean lightningEffect, float lightningDensity, float lightningRadius,
|
||||
AnchorPoint anchorPoint, int chargeDuration) {
|
||||
// Calculate initial position based on anchor point
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(owner, anchorPoint);
|
||||
double spawnX = spawnPos.xCoord;
|
||||
double spawnY = spawnPos.yCoord;
|
||||
double spawnZ = spawnPos.zCoord;
|
||||
|
||||
EntityAbilityDisc disc = new EntityAbilityDisc(
|
||||
world, owner, target,
|
||||
spawnX, spawnY, spawnZ,
|
||||
discRadius, discThickness, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp, speed, homing, homingStrength, homingRange,
|
||||
boomerang, boomerangDelay,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel, maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius);
|
||||
|
||||
// Set charging state (uses data watcher for client sync)
|
||||
disc.setCharging(true);
|
||||
disc.chargeDuration = chargeDuration;
|
||||
disc.chargeTick = 0;
|
||||
disc.anchorPoint = anchorPoint;
|
||||
|
||||
// Store target size and start at 0 for grow effect
|
||||
disc.targetDiscRadius = disc.discRadius;
|
||||
disc.targetDiscThickness = disc.discThickness;
|
||||
disc.discRadius = 0.01f; // Start very small
|
||||
disc.discThickness = 0.01f;
|
||||
disc.size = 0.01f; // Base size used for rendering
|
||||
disc.renderCurrentSize = 0.01f;
|
||||
disc.prevRenderSize = 0.01f;
|
||||
|
||||
// Clear motion while charging
|
||||
disc.motionX = 0;
|
||||
disc.motionY = 0;
|
||||
disc.motionZ = 0;
|
||||
|
||||
return disc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the disc moving (exit charging mode).
|
||||
* Called by ability when windup ends.
|
||||
*/
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Update start position to current position
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
|
||||
// Calculate velocity toward target
|
||||
Entity owner = getOwner();
|
||||
|
||||
if (target != null) {
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.height * 0.5) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
motionX = (dx / len) * speed;
|
||||
motionY = (dy / len) * speed;
|
||||
motionZ = (dz / len) * speed;
|
||||
}
|
||||
} else if (owner != null) {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
motionX = -Math.sin(yaw) * Math.cos(pitch) * speed;
|
||||
motionY = -Math.sin(pitch) * speed;
|
||||
motionZ = Math.cos(yaw) * Math.cos(pitch) * speed;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateRotation() {
|
||||
// Disc spins ONLY on Y axis (flat spin like a saw blade)
|
||||
@@ -127,6 +255,12 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
|
||||
@Override
|
||||
protected void updateProjectile() {
|
||||
// Handle charging state (windup phase)
|
||||
if (isCharging()) {
|
||||
updateCharging();
|
||||
return;
|
||||
}
|
||||
|
||||
if (worldObj.isRemote) {
|
||||
handleClientInterpolation();
|
||||
} else {
|
||||
@@ -141,17 +275,8 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
checkEntityCollision();
|
||||
this.moveEntity(motionX, motionY, motionZ);
|
||||
|
||||
// Boomerang: if we've traveled far without hitting, start returning
|
||||
// Track ticks for boomerang delay (checkMaxDistance handles the actual return trigger)
|
||||
if (boomerang && !returning && !hasHit) {
|
||||
double distTraveled = Math.sqrt(
|
||||
(posX - startX) * (posX - startX) +
|
||||
(posY - startY) * (posY - startY) +
|
||||
(posZ - startZ) * (posZ - startZ)
|
||||
);
|
||||
// Start returning at 80% of max distance or after delay
|
||||
if (distTraveled >= maxDistance * 0.8 || ticksSinceMiss >= boomerangDelay) {
|
||||
returning = true;
|
||||
}
|
||||
ticksSinceMiss++;
|
||||
}
|
||||
}
|
||||
@@ -170,20 +295,41 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
|
||||
@Override
|
||||
protected boolean checkMaxDistance() {
|
||||
// If boomerang and returning, don't die from max distance
|
||||
if (boomerang && returning) {
|
||||
// Die if we get close to owner
|
||||
Entity owner = getOwner();
|
||||
if (owner != null) {
|
||||
double distToOwner = Math.sqrt(
|
||||
(posX - owner.posX) * (posX - owner.posX) +
|
||||
(posY - owner.posY) * (posY - owner.posY) +
|
||||
(posZ - owner.posZ) * (posZ - owner.posZ)
|
||||
);
|
||||
return distToOwner < 1.0;
|
||||
double distTraveled = Math.sqrt(
|
||||
(posX - startX) * (posX - startX) +
|
||||
(posY - startY) * (posY - startY) +
|
||||
(posZ - startZ) * (posZ - startZ)
|
||||
);
|
||||
|
||||
if (boomerang) {
|
||||
// If not returning yet, check if we should start returning
|
||||
if (!returning && !hasHit) {
|
||||
if (distTraveled >= maxDistance * 0.8 || ticksSinceMiss >= boomerangDelay) {
|
||||
returning = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If returning, only die when close to owner
|
||||
if (returning) {
|
||||
Entity owner = getOwner();
|
||||
if (owner != null) {
|
||||
double distToOwner = Math.sqrt(
|
||||
(posX - owner.posX) * (posX - owner.posX) +
|
||||
(posY - owner.posY) * (posY - owner.posY) +
|
||||
(posZ - owner.posZ) * (posZ - owner.posZ)
|
||||
);
|
||||
return distToOwner < 1.5;
|
||||
}
|
||||
// Owner gone but returning - keep going toward last known position
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not returning yet - don't die from distance (we'll trigger return above)
|
||||
return false;
|
||||
}
|
||||
return super.checkMaxDistance();
|
||||
|
||||
// Non-boomerang: standard max distance check
|
||||
return distTraveled >= maxDistance;
|
||||
}
|
||||
|
||||
private void updateHoming() {
|
||||
@@ -225,19 +371,28 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
|
||||
private void updateReturnToOwner() {
|
||||
Entity owner = getOwner();
|
||||
if (owner == null) {
|
||||
this.setDead();
|
||||
return;
|
||||
|
||||
// If owner is gone (unloaded/dead), head back toward start position
|
||||
double targetX, targetY, targetZ;
|
||||
if (owner != null) {
|
||||
targetX = owner.posX;
|
||||
targetY = owner.posY + owner.height * 0.5;
|
||||
targetZ = owner.posZ;
|
||||
} else {
|
||||
// Return to start position if owner not available
|
||||
targetX = startX;
|
||||
targetY = startY;
|
||||
targetZ = startZ;
|
||||
}
|
||||
|
||||
double dx = owner.posX - posX;
|
||||
double dy = (owner.posY + owner.height * 0.5) - posY;
|
||||
double dz = owner.posZ - posZ;
|
||||
double dx = targetX - posX;
|
||||
double dy = targetY - posY;
|
||||
double dz = targetZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist > 0) {
|
||||
// Stronger homing when returning
|
||||
double returnStrength = homingStrength * 2.0;
|
||||
double returnStrength = Math.min(1.0, homingStrength * 2.5);
|
||||
double desiredVX = (dx / dist) * speed;
|
||||
double desiredVY = (dy / dist) * speed;
|
||||
double desiredVZ = (dz / dist) * speed;
|
||||
@@ -297,6 +452,52 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwner();
|
||||
if (owner == null || owner.isDead) {
|
||||
setDead();
|
||||
return;
|
||||
}
|
||||
|
||||
// Grow size based on charge progress
|
||||
float progress = getChargeProgress();
|
||||
this.discRadius = targetDiscRadius * progress;
|
||||
this.discThickness = targetDiscThickness * progress;
|
||||
this.size = this.discRadius; // Base size for interpolation
|
||||
|
||||
// Calculate position based on anchor point
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorPoint);
|
||||
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
||||
}
|
||||
|
||||
// Update rotation during charging (disc still spins)
|
||||
updateRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charge progress (0-1).
|
||||
*/
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interpolated charge progress for smooth rendering.
|
||||
*/
|
||||
public float getInterpolatedChargeProgress(float partialTicks) {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
|
||||
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
return prevProgress + (currProgress - prevProgress) * partialTicks;
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getSpeed() {
|
||||
@@ -336,6 +537,15 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
this.discRadius = nbt.hasKey("DiscRadius") ? nbt.getFloat("DiscRadius") : 1.0f;
|
||||
this.discThickness = nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f;
|
||||
this.returning = nbt.hasKey("Returning") && nbt.getBoolean("Returning");
|
||||
// Charging state
|
||||
boolean isCharging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
|
||||
this.charging = isCharging;
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (isCharging ? 1 : 0));
|
||||
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40;
|
||||
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
|
||||
this.anchorPoint = nbt.hasKey("AnchorPoint") ? AnchorPoint.fromId(nbt.getInteger("AnchorPoint")) : AnchorPoint.FRONT;
|
||||
this.targetDiscRadius = nbt.hasKey("TargetDiscRadius") ? nbt.getFloat("TargetDiscRadius") : this.discRadius;
|
||||
this.targetDiscThickness = nbt.hasKey("TargetDiscThickness") ? nbt.getFloat("TargetDiscThickness") : this.discThickness;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,5 +559,12 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
nbt.setFloat("DiscRadius", discRadius);
|
||||
nbt.setFloat("DiscThickness", discThickness);
|
||||
nbt.setBoolean("Returning", returning);
|
||||
// Charging state
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setInteger("AnchorPoint", anchorPoint.getId());
|
||||
nbt.setFloat("TargetDiscRadius", targetDiscRadius);
|
||||
nbt.setFloat("TargetDiscThickness", targetDiscThickness);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
@@ -25,10 +27,44 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
private float homingStrength = 0.35f; // Increased from 0.15 for better tracking
|
||||
private float homingRange = 20.0f;
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
private float targetSize = 1.0f; // Full size to grow to during charging
|
||||
|
||||
// Data watcher index for charging state (synced to clients)
|
||||
private static final int DW_CHARGING = 20;
|
||||
|
||||
public EntityAbilityOrb(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
super.entityInit();
|
||||
// Register data watcher for charging state
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if orb is in charging state (synced via data watcher).
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
private void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full constructor with all parameters.
|
||||
*/
|
||||
@@ -102,8 +138,100 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an orb in charging mode (for windup phase).
|
||||
* The orb will grow from 0 to orbSize over chargeDuration ticks.
|
||||
* Position follows the owner based on anchor point.
|
||||
*/
|
||||
public static EntityAbilityOrb createCharging(World world, EntityNPCInterface owner, EntityLivingBase target,
|
||||
float orbSize, int innerColor, int outerColor,
|
||||
boolean outerColorEnabled, float outerColorWidth, float rotationSpeed,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
float speed, boolean homing, float homingStrength, float homingRange,
|
||||
boolean explosive, float explosionRadius, float explosionDamageFalloff,
|
||||
int stunDuration, int slowDuration, int slowLevel,
|
||||
float maxDistance, int maxLifetime,
|
||||
boolean lightningEffect, float lightningDensity, float lightningRadius, int lightningFadeTime,
|
||||
AnchorPoint anchorPoint, int chargeDuration) {
|
||||
// Calculate initial position based on anchor point
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(owner, anchorPoint);
|
||||
double spawnX = spawnPos.xCoord;
|
||||
double spawnY = spawnPos.yCoord;
|
||||
double spawnZ = spawnPos.zCoord;
|
||||
|
||||
EntityAbilityOrb orb = new EntityAbilityOrb(
|
||||
world, owner, target,
|
||||
spawnX, spawnY, spawnZ,
|
||||
orbSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed,
|
||||
damage, knockback, knockbackUp, speed, homing, homingStrength, homingRange,
|
||||
explosive, explosionRadius, explosionDamageFalloff,
|
||||
stunDuration, slowDuration, slowLevel, maxDistance, maxLifetime,
|
||||
lightningEffect, lightningDensity, lightningRadius, lightningFadeTime);
|
||||
|
||||
// Set charging state (uses data watcher for client sync)
|
||||
orb.setCharging(true);
|
||||
orb.chargeDuration = chargeDuration;
|
||||
orb.chargeTick = 0;
|
||||
orb.anchorPoint = anchorPoint;
|
||||
|
||||
// Store target size and start at 0 for grow effect
|
||||
orb.targetSize = orb.size;
|
||||
orb.size = 0.01f; // Start very small
|
||||
orb.renderCurrentSize = 0.01f;
|
||||
orb.prevRenderSize = 0.01f;
|
||||
|
||||
// Clear motion while charging
|
||||
orb.motionX = 0;
|
||||
orb.motionY = 0;
|
||||
orb.motionZ = 0;
|
||||
|
||||
return orb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the orb moving (exit charging mode).
|
||||
* Called by ability when windup ends.
|
||||
*/
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Update start position to current position
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
|
||||
// Calculate velocity toward target
|
||||
Entity owner = getOwner();
|
||||
|
||||
if (target != null) {
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.height * 0.5) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
motionX = (dx / len) * speed;
|
||||
motionY = (dy / len) * speed;
|
||||
motionZ = (dz / len) * speed;
|
||||
}
|
||||
} else if (owner != null) {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
motionX = -Math.sin(yaw) * Math.cos(pitch) * speed;
|
||||
motionY = -Math.sin(pitch) * speed;
|
||||
motionZ = Math.cos(yaw) * Math.cos(pitch) * speed;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProjectile() {
|
||||
// Handle charging state (windup phase)
|
||||
if (isCharging()) {
|
||||
updateCharging();
|
||||
return;
|
||||
}
|
||||
|
||||
if (worldObj.isRemote) {
|
||||
handleClientInterpolation();
|
||||
} else {
|
||||
@@ -207,6 +335,47 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwner();
|
||||
if (owner == null || owner.isDead) {
|
||||
setDead();
|
||||
return;
|
||||
}
|
||||
|
||||
// Grow size based on charge progress
|
||||
float progress = getChargeProgress();
|
||||
this.size = targetSize * progress;
|
||||
|
||||
// Calculate position based on anchor point
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorPoint);
|
||||
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charge progress (0-1).
|
||||
*/
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interpolated charge progress for smooth rendering.
|
||||
*/
|
||||
public float getInterpolatedChargeProgress(float partialTicks) {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
|
||||
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
return prevProgress + (currProgress - prevProgress) * partialTicks;
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getSpeed() {
|
||||
@@ -238,6 +407,14 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
this.homing = !nbt.hasKey("Homing") || nbt.getBoolean("Homing");
|
||||
this.homingStrength = nbt.hasKey("HomingStrength") ? nbt.getFloat("HomingStrength") : 0.15f;
|
||||
this.homingRange = nbt.hasKey("HomingRange") ? nbt.getFloat("HomingRange") : 20.0f;
|
||||
// Charging state
|
||||
boolean isCharging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
|
||||
this.charging = isCharging;
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (isCharging ? 1 : 0));
|
||||
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40;
|
||||
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
|
||||
this.anchorPoint = nbt.hasKey("AnchorPoint") ? AnchorPoint.fromId(nbt.getInteger("AnchorPoint")) : AnchorPoint.FRONT;
|
||||
this.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,5 +423,11 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
nbt.setBoolean("Homing", homing);
|
||||
nbt.setFloat("HomingStrength", homingStrength);
|
||||
nbt.setFloat("HomingRange", homingRange);
|
||||
// Charging state
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setInteger("AnchorPoint", anchorPoint.getId());
|
||||
nbt.setFloat("TargetSize", targetSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package kamkeel.npcs.util;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.Vec3;
|
||||
import noppes.npcs.constants.EnumAnimationPart;
|
||||
import noppes.npcs.controllers.data.AnimationData;
|
||||
import noppes.npcs.controllers.data.Frame;
|
||||
import noppes.npcs.controllers.data.FramePart;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
/**
|
||||
* Utility class for calculating world positions based on anchor points.
|
||||
* Used for positioning charging effects on different body parts.
|
||||
* Animation-aware: reads arm positions from NPC animation data when available.
|
||||
*/
|
||||
public class AnchorPointHelper {
|
||||
|
||||
// Model coordinate scale factor (model coords to blocks)
|
||||
private static final float MODEL_SCALE = 0.0625f; // 1/16
|
||||
|
||||
// Default offsets (in blocks)
|
||||
private static final float ARM_LATERAL_OFFSET = 0.35f; // ~5.5 pixels lateral from center
|
||||
private static final float ARM_FORWARD_OFFSET = 0.25f; // Arm extended slightly forward
|
||||
private static final float FRONT_OFFSET = 1.0f; // Distance in front for FRONT anchor
|
||||
|
||||
// Height multipliers relative to eye height
|
||||
private static final float FRONT_HEIGHT_MULT = 0.7f;
|
||||
private static final float CENTER_HEIGHT_MULT = 0.5f;
|
||||
private static final float ARM_HEIGHT_MULT = 0.75f;
|
||||
private static final float ABOVE_HEAD_HEIGHT_MULT = 1.3f;
|
||||
private static final float CHEST_HEIGHT_MULT = 0.5f;
|
||||
|
||||
/**
|
||||
* Calculate the world position for a given anchor point on an entity.
|
||||
*
|
||||
* @param entity The entity to calculate the anchor position for
|
||||
* @param anchor The anchor point type
|
||||
* @return Vec3 containing the world coordinates for the anchor point
|
||||
*/
|
||||
public static Vec3 calculateAnchorPosition(EntityLivingBase entity, AnchorPoint anchor) {
|
||||
return calculateAnchorPosition(entity, anchor, FRONT_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the world position for a given anchor point on an entity.
|
||||
* For NPCs with active animations, this will read the actual arm positions.
|
||||
*
|
||||
* @param entity The entity to calculate the anchor position for
|
||||
* @param anchor The anchor point type
|
||||
* @param frontOffset Custom offset distance for FRONT anchor point
|
||||
* @return Vec3 containing the world coordinates for the anchor point
|
||||
*/
|
||||
public static Vec3 calculateAnchorPosition(EntityLivingBase entity, AnchorPoint anchor, float frontOffset) {
|
||||
double x = entity.posX;
|
||||
double y = entity.posY;
|
||||
double z = entity.posZ;
|
||||
float eyeHeight = entity.getEyeHeight();
|
||||
|
||||
// Get yaw in radians (use body yaw for arm positioning, head yaw for facing)
|
||||
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
||||
float headYaw = (float) Math.toRadians(entity.rotationYawHead);
|
||||
|
||||
switch (anchor) {
|
||||
case FRONT:
|
||||
// In front of entity face (use head yaw)
|
||||
x -= Math.sin(headYaw) * frontOffset;
|
||||
y += eyeHeight * FRONT_HEIGHT_MULT;
|
||||
z += Math.cos(headYaw) * frontOffset;
|
||||
break;
|
||||
|
||||
case CENTER:
|
||||
// Entity center
|
||||
y += eyeHeight * CENTER_HEIGHT_MULT;
|
||||
break;
|
||||
|
||||
case RIGHT_HAND:
|
||||
// Try to get animated arm position
|
||||
Vec3 rightArmPos = getAnimatedArmPosition(entity, true);
|
||||
if (rightArmPos != null) {
|
||||
return rightArmPos;
|
||||
}
|
||||
// Fallback: calculated position using body yaw
|
||||
x += calculateHandOffsetX(bodyYaw, true);
|
||||
y += eyeHeight * ARM_HEIGHT_MULT;
|
||||
z += calculateHandOffsetZ(bodyYaw, true);
|
||||
break;
|
||||
|
||||
case LEFT_HAND:
|
||||
// Try to get animated arm position
|
||||
Vec3 leftArmPos = getAnimatedArmPosition(entity, false);
|
||||
if (leftArmPos != null) {
|
||||
return leftArmPos;
|
||||
}
|
||||
// Fallback: calculated position using body yaw
|
||||
x += calculateHandOffsetX(bodyYaw, false);
|
||||
y += eyeHeight * ARM_HEIGHT_MULT;
|
||||
z += calculateHandOffsetZ(bodyYaw, false);
|
||||
break;
|
||||
|
||||
case ABOVE_HEAD:
|
||||
// Above the entity
|
||||
y += eyeHeight * ABOVE_HEAD_HEIGHT_MULT;
|
||||
break;
|
||||
|
||||
case CHEST:
|
||||
// Chest level, centered
|
||||
y += eyeHeight * CHEST_HEIGHT_MULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return Vec3.createVectorHelper(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the animated arm position from NPC animation data.
|
||||
* Uses Minecraft's hand positioning logic from RenderNPCHumanMale.
|
||||
* Hand offset values: (-0.0625, 0.4375, 0.0625) for right hand after arm postRender.
|
||||
*
|
||||
* @param entity The entity (must be EntityNPCInterface for animation support)
|
||||
* @param rightArm True for right arm, false for left arm
|
||||
* @return Vec3 world position if animation data available, null otherwise
|
||||
*/
|
||||
private static Vec3 getAnimatedArmPosition(EntityLivingBase entity, boolean rightArm) {
|
||||
if (!(entity instanceof EntityNPCInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityNPCInterface npc = (EntityNPCInterface) entity;
|
||||
AnimationData animData = npc.display.animationData;
|
||||
|
||||
if (animData == null || !animData.isActive() || animData.animation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Frame frame = (Frame) animData.animation.currentFrame();
|
||||
if (frame == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EnumAnimationPart armPart = rightArm ? EnumAnimationPart.RIGHT_ARM : EnumAnimationPart.LEFT_ARM;
|
||||
if (!frame.frameParts.containsKey(armPart)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FramePart part = frame.frameParts.get(armPart);
|
||||
if (part == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure interpolation is up to date
|
||||
part.interpolateOffset();
|
||||
part.interpolateAngles();
|
||||
|
||||
// Get body yaw for world rotation
|
||||
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
||||
|
||||
// === SHOULDER POSITION (model coordinates, in model units) ===
|
||||
// Right arm pivot: X = -5 (right side), Y = -2 (below head), Z = 0
|
||||
// Left arm pivot: X = 5 (left side), Y = -2, Z = 0
|
||||
// Note: In Minecraft model coords, Y=0 is at head level, shoulder is at Y=-2
|
||||
float shoulderModelX = rightArm ? -5.0f : 5.0f;
|
||||
float shoulderModelY = -2.0f;
|
||||
float shoulderModelZ = 0.0f;
|
||||
|
||||
// Add animation pivot offsets (if any)
|
||||
if (part.prevPivots != null) {
|
||||
shoulderModelX += part.prevPivots[0];
|
||||
shoulderModelY += part.prevPivots[1];
|
||||
shoulderModelZ += part.prevPivots[2];
|
||||
}
|
||||
|
||||
// === HAND OFFSET IN ARM'S LOCAL SPACE (from RenderNPCHumanMale) ===
|
||||
// These values are in blocks, applied AFTER postRender transforms
|
||||
// Right: (-0.0625, 0.4375, 0.0625), Left: (0.0625, 0.4375, 0.0625)
|
||||
float handLocalX = rightArm ? -0.0625f : 0.0625f;
|
||||
float handLocalY = 0.4375f; // 7 model units down the arm
|
||||
float handLocalZ = 0.0625f; // Slightly forward
|
||||
|
||||
// === ARM ROTATION ANGLES ===
|
||||
float rotX = part.prevRotations != null ? part.prevRotations[0] : 0;
|
||||
float rotY = part.prevRotations != null ? part.prevRotations[1] : 0;
|
||||
float rotZ = part.prevRotations != null ? part.prevRotations[2] : 0;
|
||||
|
||||
// === APPLY ARM ROTATIONS TO HAND OFFSET ===
|
||||
// Rotation order matches ModelRenderer.postRender: Z, Y, X
|
||||
double hx = handLocalX;
|
||||
double hy = handLocalY;
|
||||
double hz = handLocalZ;
|
||||
|
||||
// Rotate around Z axis
|
||||
double cosZ = Math.cos(rotZ);
|
||||
double sinZ = Math.sin(rotZ);
|
||||
double tempX = hx * cosZ - hy * sinZ;
|
||||
double tempY = hx * sinZ + hy * cosZ;
|
||||
hx = tempX;
|
||||
hy = tempY;
|
||||
|
||||
// Rotate around Y axis
|
||||
double cosY = Math.cos(rotY);
|
||||
double sinY = Math.sin(rotY);
|
||||
tempX = hx * cosY + hz * sinY;
|
||||
double tempZ = -hx * sinY + hz * cosY;
|
||||
hx = tempX;
|
||||
hz = tempZ;
|
||||
|
||||
// Rotate around X axis
|
||||
double cosX = Math.cos(rotX);
|
||||
double sinX = Math.sin(rotX);
|
||||
tempY = hy * cosX - hz * sinX;
|
||||
tempZ = hy * sinX + hz * cosX;
|
||||
hy = tempY;
|
||||
hz = tempZ;
|
||||
|
||||
// === COMBINE SHOULDER + ROTATED HAND OFFSET ===
|
||||
// Convert shoulder from model units to blocks
|
||||
double worldOffsetX = shoulderModelX * MODEL_SCALE + hx;
|
||||
double worldOffsetY = shoulderModelY * MODEL_SCALE + hy;
|
||||
double worldOffsetZ = shoulderModelZ * MODEL_SCALE + hz;
|
||||
|
||||
// === ROTATE BY BODY YAW ===
|
||||
double cosYaw = Math.cos(bodyYaw);
|
||||
double sinYaw = Math.sin(bodyYaw);
|
||||
double rotatedX = worldOffsetX * cosYaw + worldOffsetZ * sinYaw;
|
||||
double rotatedZ = -worldOffsetX * sinYaw + worldOffsetZ * cosYaw;
|
||||
|
||||
// === FINAL WORLD POSITION ===
|
||||
// Entity posY is at feet, shoulder height is approximately eyeHeight * 0.9
|
||||
double shoulderWorldY = entity.posY + entity.getEyeHeight() * 0.9;
|
||||
|
||||
double worldX = entity.posX + rotatedX;
|
||||
double worldY = shoulderWorldY + worldOffsetY;
|
||||
double worldZ = entity.posZ + rotatedZ;
|
||||
|
||||
return Vec3.createVectorHelper(worldX, worldY, worldZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the X offset for hand positions (fallback when no animation).
|
||||
*
|
||||
* @param yaw Entity's body yaw in radians
|
||||
* @param rightHand True for right hand, false for left hand
|
||||
* @return X offset in world coordinates
|
||||
*/
|
||||
private static double calculateHandOffsetX(float yaw, boolean rightHand) {
|
||||
// Perpendicular direction (90 degrees from look direction)
|
||||
// Right hand: +90 degrees, Left hand: -90 degrees
|
||||
float perpYaw = yaw + (float) (rightHand ? Math.PI / 2 : -Math.PI / 2);
|
||||
|
||||
// Lateral offset (perpendicular to look direction)
|
||||
double lateralX = Math.sin(perpYaw) * ARM_LATERAL_OFFSET;
|
||||
|
||||
// Forward offset (in look direction)
|
||||
double forwardX = -Math.sin(yaw) * ARM_FORWARD_OFFSET;
|
||||
|
||||
return lateralX + forwardX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the Z offset for hand positions (fallback when no animation).
|
||||
*
|
||||
* @param yaw Entity's body yaw in radians
|
||||
* @param rightHand True for right hand, false for left hand
|
||||
* @return Z offset in world coordinates
|
||||
*/
|
||||
private static double calculateHandOffsetZ(float yaw, boolean rightHand) {
|
||||
// Perpendicular direction (90 degrees from look direction)
|
||||
float perpYaw = yaw + (float) (rightHand ? Math.PI / 2 : -Math.PI / 2);
|
||||
|
||||
// Lateral offset (perpendicular to look direction)
|
||||
double lateralZ = -Math.cos(perpYaw) * ARM_LATERAL_OFFSET;
|
||||
|
||||
// Forward offset (in look direction)
|
||||
double forwardZ = Math.cos(yaw) * ARM_FORWARD_OFFSET;
|
||||
|
||||
return lateralZ + forwardZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height multiplier for a given anchor point.
|
||||
* Useful for external code that needs to know the Y position offset.
|
||||
*
|
||||
* @param anchor The anchor point type
|
||||
* @return Height multiplier relative to eye height
|
||||
*/
|
||||
public static float getHeightMultiplier(AnchorPoint anchor) {
|
||||
switch (anchor) {
|
||||
case FRONT:
|
||||
return FRONT_HEIGHT_MULT;
|
||||
case CENTER:
|
||||
return CENTER_HEIGHT_MULT;
|
||||
case RIGHT_HAND:
|
||||
case LEFT_HAND:
|
||||
return ARM_HEIGHT_MULT;
|
||||
case ABOVE_HEAD:
|
||||
return ABOVE_HEAD_HEIGHT_MULT;
|
||||
case CHEST:
|
||||
return CHEST_HEIGHT_MULT;
|
||||
default:
|
||||
return FRONT_HEIGHT_MULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package noppes.npcs.client.gui.advanced.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityDisc;
|
||||
import noppes.npcs.client.gui.SubGuiColorSelector;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
@@ -92,8 +93,11 @@ public class SubGuiAbilityDisc extends SubGuiAbilityConfig {
|
||||
|
||||
y += 24;
|
||||
|
||||
// Row 7: Effects button
|
||||
addButton(new GuiNpcButton(150, labelX, y, 80, 20, "ability.effects"));
|
||||
// Row 7: Max Lifetime + Effects button
|
||||
addLabel(new GuiNpcLabel(112, "ability.lifetime", labelX, y + 5));
|
||||
addTextField(createIntField(112, fieldX, y, 50, disc.getMaxLifetime()));
|
||||
|
||||
addButton(new GuiNpcButton(150, col2LabelX, y, 80, 20, "ability.effects"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,7 +108,13 @@ public class SubGuiAbilityDisc extends SubGuiAbilityConfig {
|
||||
int col2LabelX = guiLeft + 180;
|
||||
int col2FieldX = guiLeft + 260;
|
||||
|
||||
// Row 1: Inner Color + Outer Color
|
||||
// Row 1: Anchor Point
|
||||
addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5));
|
||||
addButton(new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), disc.getAnchorPoint().getId()));
|
||||
|
||||
y += 24;
|
||||
|
||||
// Row 2: Inner Color + Outer Color
|
||||
addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5));
|
||||
String innerHex = String.format("%06X", disc.getInnerColor() & 0xFFFFFF);
|
||||
GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex);
|
||||
@@ -197,6 +207,9 @@ public class SubGuiAbilityDisc extends SubGuiAbilityConfig {
|
||||
disc.setLightningEffect(value == 1);
|
||||
initGui();
|
||||
break;
|
||||
case 210:
|
||||
disc.setAnchorPoint(AnchorPoint.fromId(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +266,9 @@ public class SubGuiAbilityDisc extends SubGuiAbilityConfig {
|
||||
case 111:
|
||||
disc.setMaxDistance(parseFloat(field, disc.getMaxDistance()));
|
||||
break;
|
||||
case 112:
|
||||
disc.setMaxLifetime(field.getInteger());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package noppes.npcs.client.gui.advanced.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityEnergyBeam;
|
||||
import noppes.npcs.client.gui.SubGuiColorSelector;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
@@ -105,7 +106,13 @@ public class SubGuiAbilityEnergyBeam extends SubGuiAbilityConfig {
|
||||
int col2LabelX = guiLeft + 180;
|
||||
int col2FieldX = guiLeft + 260;
|
||||
|
||||
// Row 1: Inner Color + Outer Color
|
||||
// Row 1: Anchor Point
|
||||
addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5));
|
||||
addButton(new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), beam.getAnchorPoint().getId()));
|
||||
|
||||
y += 24;
|
||||
|
||||
// Row 2: Inner Color + Outer Color
|
||||
addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5));
|
||||
String innerHex = String.format("%06X", beam.getInnerColor() & 0xFFFFFF);
|
||||
GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex);
|
||||
@@ -195,6 +202,9 @@ public class SubGuiAbilityEnergyBeam extends SubGuiAbilityConfig {
|
||||
beam.setLightningEffect(value == 1);
|
||||
initGui();
|
||||
break;
|
||||
case 210:
|
||||
beam.setAnchorPoint(AnchorPoint.fromId(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package noppes.npcs.client.gui.advanced.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityOrb;
|
||||
import noppes.npcs.client.gui.SubGuiColorSelector;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
@@ -95,7 +96,13 @@ public class SubGuiAbilityOrb extends SubGuiAbilityConfig {
|
||||
int col2LabelX = guiLeft + 180;
|
||||
int col2FieldX = guiLeft + 260;
|
||||
|
||||
// Row 1: Inner Color + Rotation Speed
|
||||
// Row 1: Anchor Point
|
||||
addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5));
|
||||
addButton(new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), orb.getAnchorPoint().getId()));
|
||||
|
||||
y += 24;
|
||||
|
||||
// Row 2: Inner Color + Rotation Speed
|
||||
addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5));
|
||||
String innerHex = String.format("%06X", orb.getInnerColor() & 0xFFFFFF);
|
||||
GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex);
|
||||
@@ -185,6 +192,9 @@ public class SubGuiAbilityOrb extends SubGuiAbilityConfig {
|
||||
orb.setLightningEffect(value == 1);
|
||||
initGui();
|
||||
break;
|
||||
case 210:
|
||||
orb.setAnchorPoint(AnchorPoint.fromId(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user