mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 06:07:27 +00:00
Refactoring for Code Cleanup and Projectile Abstract Class Handling
This commit is contained in:
1 parent
a1f3f5184b
commit
a23b199e7e
30 files changed
+1226
-2395
No files matched your search
@@ -7,6 +7,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.util.DamageSource;
|
||||
@@ -141,14 +142,17 @@ public abstract class Ability implements IAbility {
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Called first tick of ACTIVE phase
|
||||
* Called first tick of ACTIVE phase.
|
||||
* For burst without replay (burstReplayAnimations=false), this is called directly
|
||||
* after BURST_DELAY without a preceding windup. Check isBurstRefire() or null-check
|
||||
* entity state to detect and handle the burst refire case.
|
||||
*/
|
||||
public abstract void onExecute(EntityLivingBase caster, EntityLivingBase target, World world);
|
||||
public abstract void onExecute(EntityLivingBase caster, EntityLivingBase target);
|
||||
|
||||
/**
|
||||
* Called every tick of ACTIVE phase
|
||||
*/
|
||||
public abstract void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick);
|
||||
public abstract void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick);
|
||||
|
||||
/**
|
||||
* Write type-specific config to NBT
|
||||
@@ -208,17 +212,15 @@ public abstract class Ability implements IAbility {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when burst re-fires without replay animations (burstReplayAnimations=false).
|
||||
* Entity-spawning abilities should override to spawn and fire entities in one step,
|
||||
* since onWindUpTick was not called (windup was skipped).
|
||||
* Default: does nothing (ability goes directly to onExecute with existing state).
|
||||
*/
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
// Override in entity-spawning abilities (Orb, Beam, Disc, LaserShot)
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
}
|
||||
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
/**
|
||||
* Whether this execution is a burst refire (not the first execution).
|
||||
* Useful in onExecute() to detect when windup was skipped and entities need fresh creation.
|
||||
*/
|
||||
public boolean isBurstRefire() {
|
||||
return burstEnabled && burstIndex > 0;
|
||||
}
|
||||
|
||||
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
|
||||
@@ -1051,11 +1053,11 @@ public abstract class Ability implements IAbility {
|
||||
* Spawn an entity during ability execution.
|
||||
* In preview mode, routes to PreviewEntityHandler instead of world.spawnEntityInWorld().
|
||||
*/
|
||||
protected void spawnAbilityEntity(World world, Entity entity) {
|
||||
protected void spawnAbilityEntity(Entity entity) {
|
||||
if (previewMode && previewEntityHandler != null) {
|
||||
previewEntityHandler.onEntitySpawned(entity);
|
||||
} else {
|
||||
world.spawnEntityInWorld(entity);
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
}
|
||||
if (burstEnabled && burstOverlap) {
|
||||
burstEntities.add(entity);
|
||||
@@ -1800,6 +1802,52 @@ public abstract class Ability implements IAbility {
|
||||
return totalDuration;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// MOVEMENT COLLISION UTILITY
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if a caster's movement in a given direction is blocked by walls/blocks.
|
||||
* Used by Charge and Dash abilities to detect wall collision.
|
||||
*
|
||||
* @param caster The entity moving
|
||||
* @param dirX X component of normalized movement direction
|
||||
* @param dirZ Z component of normalized movement direction
|
||||
* @param speed Movement speed (blocks per tick)
|
||||
* @return true if movement is blocked
|
||||
*/
|
||||
protected static boolean isMovementBlocked(EntityLivingBase caster, double dirX, double dirZ, double speed) {
|
||||
double nextX = dirX * speed;
|
||||
double nextZ = dirZ * speed;
|
||||
AxisAlignedBB nextBox = caster.boundingBox.copy().offset(nextX, 0, nextZ);
|
||||
double stepThreshold = nextBox.minY + Math.max(caster.stepHeight, 0.5);
|
||||
|
||||
int x1 = (int) Math.floor(nextBox.minX);
|
||||
int x2 = (int) Math.floor(nextBox.maxX + 1.0);
|
||||
int y1 = (int) Math.floor(nextBox.minY) - 1;
|
||||
int y2 = (int) Math.floor(nextBox.maxY + 1.0);
|
||||
int z1 = (int) Math.floor(nextBox.minZ);
|
||||
int z2 = (int) Math.floor(nextBox.maxZ + 1.0);
|
||||
|
||||
java.util.ArrayList<AxisAlignedBB> collisionBoxes = new java.util.ArrayList<>();
|
||||
for (int bx = x1; bx < x2; bx++) {
|
||||
for (int bz = z1; bz < z2; bz++) {
|
||||
for (int by = y1; by < y2; by++) {
|
||||
Block block = caster.worldObj.getBlock(bx, by, bz);
|
||||
if (!block.getMaterial().blocksMovement()) continue;
|
||||
collisionBoxes.clear();
|
||||
block.addCollisionBoxesToList(caster.worldObj, bx, by, bz, nextBox, collisionBoxes, caster);
|
||||
for (AxisAlignedBB box : collisionBoxes) {
|
||||
if (box.maxY > stepThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// IAbility API METHODS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -2,62 +2,41 @@ 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.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.RotationMode;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import noppes.npcs.api.ability.type.IAbilityEnergyBeam;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
|
||||
/**
|
||||
* Energy Beam ability: A homing head with a trailing path.
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
*/
|
||||
public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
public class AbilityBeam extends AbstractEnergyProjectileAbility<EntityAbilityBeam> implements IAbilityEnergyBeam {
|
||||
|
||||
private static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Beam shape properties (shared)
|
||||
private float beamWidth = 0.4f;
|
||||
private float headSize = 0.6f;
|
||||
|
||||
// Projectile count and fire stagger
|
||||
private int projectileCount = 1;
|
||||
private int fireDelay = 0;
|
||||
|
||||
// Shared visual data (all projectiles use these)
|
||||
private EnergyDisplayData displayData = new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f);
|
||||
private EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Shared combat/movement data
|
||||
private final EnergyCombatData combatData = new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f);
|
||||
private final EnergyHomingData homingData = new EnergyHomingData(0.4f, true, 0.1f, 15.0f);
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(25.0f, 200);
|
||||
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
private ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
private transient EntityAbilityBeam[] beamEntities;
|
||||
|
||||
public AbilityBeam() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f),
|
||||
new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f),
|
||||
new EnergyHomingData(0.4f, true, 0.1f, 15.0f),
|
||||
new EnergyLifespanData(25.0f, 200)
|
||||
);
|
||||
this.typeId = "ability.cnpc.beam";
|
||||
this.name = "Beam";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -72,56 +51,57 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Beam_Windup";
|
||||
this.activeAnimationName = "Ability_Beam_Active";
|
||||
initProjectiles(1);
|
||||
}
|
||||
|
||||
private void initProjectiles(int count) {
|
||||
this.projectileCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
this.projectiles = new ProjectileData[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
projectiles[i] = new ProjectileData(getDefaultAnchor(i));
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityAbilityBeam createEntity(EntityLivingBase caster, EntityLivingBase target,
|
||||
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
|
||||
return new EntityAbilityBeam(
|
||||
caster.worldObj, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
beamWidth, headSize,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
lockMovement.locksActive());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireEntity(EntityAbilityBeam beam, EntityLivingBase target) {
|
||||
if (isPreview()) {
|
||||
beam.startPreviewFiring();
|
||||
} else {
|
||||
beam.startFiring(target);
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityCharging(EntityAbilityBeam beam, ProjectileData projData, int index) {
|
||||
beam.setupCharging(projData.anchor, windUpTicks, 1.0f);
|
||||
}
|
||||
|
||||
public void setProjectileCount(int count) {
|
||||
int newCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
if (newCount == projectileCount) return;
|
||||
|
||||
ProjectileData[] oldProjectiles = projectiles;
|
||||
initProjectiles(newCount);
|
||||
|
||||
if (oldProjectiles != null) {
|
||||
int copyCount = Math.min(oldProjectiles.length, newCount);
|
||||
for (int i = 0; i < copyCount; i++) {
|
||||
if (oldProjectiles[i] != null) projectiles[i] = oldProjectiles[i];
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityPreview(EntityAbilityBeam beam, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index) {
|
||||
beam.setupPreview(caster, beamWidth, headSize, resolved, lightningData, projData.anchor, windUpTicks, 1.0f);
|
||||
}
|
||||
|
||||
public int getProjectileCount() {
|
||||
return projectileCount;
|
||||
@Override
|
||||
protected EntityAbilityBeam[] createEntityArray(int size) {
|
||||
return new EntityAbilityBeam[size];
|
||||
}
|
||||
|
||||
public int getFireDelay() {
|
||||
return fireDelay;
|
||||
@Override
|
||||
protected float getProjectileTelegraphRadius() {
|
||||
return headSize * 2.0f;
|
||||
}
|
||||
|
||||
public void setFireDelay(int delay) {
|
||||
this.fireDelay = Math.max(0, delay);
|
||||
@Override
|
||||
protected Vec3 getSpawnPosition(EntityLivingBase caster, int index) {
|
||||
return AnchorPointHelper.calculateAnchorPosition(caster, projectiles[index].anchor, 1.0f);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #getFireDelay()} */
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
/** @deprecated Use {@link #setFireDelay(int)} */
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
// ==================== VARIANTS ====================
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
@@ -134,7 +114,6 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
a.setName("Dual Beam");
|
||||
beam.setProjectileCount(2);
|
||||
beam.setFireDelay(0);
|
||||
// Set color override for second beam
|
||||
beam.projectiles[1].colorOverride = true;
|
||||
beam.projectiles[1].innerColor = 0xFFFFFF;
|
||||
beam.projectiles[1].outerColor = 0xFF0000;
|
||||
@@ -145,244 +124,24 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
// ==================== TYPE-SPECIFIC NBT ====================
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create beam entities.
|
||||
* NPC: target is the aggro target — beams will home toward it.
|
||||
* Player: target is null — beams fire in caster's look direction.
|
||||
*/
|
||||
private EntityAbilityBeam[] createBeamEntities(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
EntityAbilityBeam[] entities = new EntityAbilityBeam[projectileCount];
|
||||
float offsetDist = 1.0f;
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, projectiles[i].anchor, offsetDist);
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
entities[i] = new EntityAbilityBeam(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
beamWidth, headSize,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
lockMovement.locksActive());
|
||||
entities[i].setEffects(this.effects);
|
||||
entities[i].setSourceAbility(this);
|
||||
}
|
||||
if (projectileCount == 2 && entities[0] != null && entities[1] != null) {
|
||||
entities[0].setSiblingEntityId(entities[1].getEntityId());
|
||||
entities[1].setSiblingEntityId(entities[0].getEntityId());
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private void fireBeamEntity(EntityAbilityBeam beam, EntityLivingBase target) {
|
||||
if (beam == null || beam.isDead) return;
|
||||
if (isPreview()) {
|
||||
beam.startPreviewFiring();
|
||||
} else {
|
||||
beam.startFiring(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
if (tick == 1) {
|
||||
beamEntities = createBeamEntities(caster, target, world);
|
||||
float offsetDist = 1.0f;
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
if (isPreview()) {
|
||||
beamEntities[i].setupPreview(caster, beamWidth, headSize, resolved, lightningData, projectiles[i].anchor, windUpTicks, offsetDist);
|
||||
} else {
|
||||
beamEntities[i].setupCharging(projectiles[i].anchor, windUpTicks, offsetDist);
|
||||
}
|
||||
spawnAbilityEntity(world, beamEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
beamEntities = createBeamEntities(caster, target, world);
|
||||
for (EntityAbilityBeam beam : beamEntities) {
|
||||
spawnAbilityEntity(world, beam);
|
||||
}
|
||||
|
||||
// Fire first projectile immediately, let onActiveTick handle the rest with fireDelay
|
||||
fireBeamEntity(beamEntities[0], target);
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireBeamEntity(beamEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (beamEntities == null) return;
|
||||
|
||||
fireBeamEntity(beamEntities[0], target);
|
||||
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireBeamEntity(beamEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (beamEntities == null) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fireDelay > 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (tick == fireDelay * i) {
|
||||
fireBeamEntity(beamEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean allDead = true;
|
||||
for (EntityAbilityBeam beam : beamEntities) {
|
||||
if (beam != null && !beam.isDead) {
|
||||
allDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDead) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (beamEntities != null) {
|
||||
for (int i = 0; i < beamEntities.length; i++) {
|
||||
if (beamEntities[i] != null && !beamEntities[i].isDead) {
|
||||
beamEntities[i].setDead();
|
||||
}
|
||||
beamEntities[i] = null;
|
||||
}
|
||||
}
|
||||
beamEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// NPC: telegraph on aggro target position
|
||||
// Player: no telegraph (beam fires in look direction, no fixed position to mark)
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster) || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Telegraph telegraph = Telegraph.circle(headSize * 2.0f);
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
telegraph.setWarningColor(activeColor);
|
||||
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
|
||||
telegraph.setHeightOffset(telegraphHeightOffset);
|
||||
|
||||
TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(target.getEntityId());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTelegraphRadius() {
|
||||
return headSize * 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("beamWidth", beamWidth);
|
||||
nbt.setFloat("headSize", headSize);
|
||||
nbt.setInteger("projectileCount", projectileCount);
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared visual data
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
NBTTagCompound projNbt = new NBTTagCompound();
|
||||
projectiles[i].writeNBT(projNbt);
|
||||
nbt.setTag("Projectile_" + i, projNbt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
this.beamWidth = nbt.getFloat("beamWidth");
|
||||
this.headSize = nbt.getFloat("headSize");
|
||||
|
||||
int count = nbt.getInteger("projectileCount");
|
||||
initProjectiles(count);
|
||||
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
// Shared visual data
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GETTERS ====================
|
||||
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { homingData.speed = speed; }
|
||||
|
||||
public float getBeamWidth() { return beamWidth; }
|
||||
public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; }
|
||||
@@ -390,142 +149,16 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
public float getHeadSize() { return headSize; }
|
||||
public void setHeadSize(float headSize) { this.headSize = headSize; }
|
||||
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { this.homingData.speed = speed; }
|
||||
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float maxDistance) { this.lifespanData.maxDistance = maxDistance; }
|
||||
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int maxLifetime) { this.lifespanData.maxLifetime = maxLifetime; }
|
||||
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float damage) { this.combatData.damage = damage; }
|
||||
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float knockback) { this.combatData.knockback = knockback; }
|
||||
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float knockbackUp) { this.combatData.knockbackUp = knockbackUp; }
|
||||
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean homing) { this.homingData.homing = homing; }
|
||||
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float homingStrength) { this.homingData.homingStrength = homingStrength; }
|
||||
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float homingRange) { this.homingData.homingRange = homingRange; }
|
||||
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean explosive) { this.combatData.explosive = explosive; }
|
||||
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float explosionRadius) { this.combatData.explosionRadius = explosionRadius; }
|
||||
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) { this.combatData.explosionDamageFalloff = explosionDamageFalloff; }
|
||||
|
||||
// Shared display data (primary colors)
|
||||
public int getInnerColor() { return displayData.innerColor; }
|
||||
public void setInnerColor(int innerColor) { displayData.innerColor = innerColor; }
|
||||
|
||||
public int getOuterColor() { return displayData.outerColor; }
|
||||
public void setOuterColor(int outerColor) { displayData.outerColor = outerColor; }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) { displayData.outerColorEnabled = outerColorEnabled; }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float outerColorWidth) { displayData.outerColorWidth = outerColorWidth; }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float outerColorAlpha) { displayData.outerColorAlpha = outerColorAlpha; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float rotationSpeed) { displayData.rotationSpeed = rotationSpeed; }
|
||||
|
||||
// Shared lightning data
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean lightningEffect) { lightningData.lightningEffect = lightningEffect; }
|
||||
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float lightningDensity) { lightningData.lightningDensity = lightningDensity; }
|
||||
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { lightningData.lightningRadius = lightningRadius; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int lightningFadeTime) { lightningData.lightningFadeTime = lightningFadeTime; }
|
||||
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { projectiles[0].anchor.anchorPoint = anchorPoint; }
|
||||
|
||||
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
|
||||
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
|
||||
|
||||
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
|
||||
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
|
||||
|
||||
public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; }
|
||||
|
||||
@Override
|
||||
public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
|
||||
@Override
|
||||
public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
// Indexed API methods
|
||||
public int getInnerColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.innerColor : displayData.innerColor;
|
||||
}
|
||||
public void setInnerColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.innerColor = color;
|
||||
}
|
||||
|
||||
public int getOuterColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.outerColor : displayData.outerColor;
|
||||
}
|
||||
public void setOuterColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.outerColor = color;
|
||||
}
|
||||
|
||||
public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); }
|
||||
public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; }
|
||||
public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; }
|
||||
public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; }
|
||||
|
||||
private int clampIndex(int index) {
|
||||
return Math.max(0, Math.min(index, projectileCount - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// GUI FIELD DEFINITIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
protected void addTypeDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.projectileCount", this::getProjectileCount, this::setProjectileCount).range(1, MAX_PROJECTILES),
|
||||
FieldDef.intField("ability.fireDelay", this::getFireDelay, this::setFireDelay)
|
||||
.range(0, 200).visibleWhen(() -> projectileCount > 1)
|
||||
));
|
||||
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
@@ -550,63 +183,5 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
|
||||
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive));
|
||||
defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects));
|
||||
|
||||
// Visual tab - shared primary colors
|
||||
defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - shared effects
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.visibleWhen(this::hasLightningEffect).range(0.01f, 100f),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - per-projectile sections
|
||||
for (int i = 0; i < MAX_PROJECTILES; i++) {
|
||||
final int idx = i;
|
||||
String sectionKey = "ability.section.projectile" + (i + 1);
|
||||
|
||||
defs.add(FieldDef.section(sectionKey).tab("ability.tab.visual")
|
||||
.visibleWhen(() -> idx < projectileCount));
|
||||
|
||||
defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX,
|
||||
v -> projectiles[idx].anchor.anchorOffsetX = v).min(Float.NEGATIVE_INFINITY),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> projectiles[idx].anchor.anchorOffsetY,
|
||||
v -> projectiles[idx].anchor.anchorOffsetY = v).min(Float.NEGATIVE_INFINITY)
|
||||
).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ,
|
||||
v -> projectiles[idx].anchor.anchorOffsetZ = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount).min(Float.NEGATIVE_INFINITY));
|
||||
|
||||
// Color override (only when multiple projectiles)
|
||||
defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", () -> projectiles[idx].innerColor, v -> projectiles[idx].innerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", () -> projectiles[idx].outerColor, v -> projectiles[idx].outerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
@@ -81,7 +80,7 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
* Called on the first tick of windup - lock direction here so it matches telegraph.
|
||||
*/
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (tick == 1) {
|
||||
// Lock direction on first windup tick (same time telegraph is created)
|
||||
lockChargeDirection(caster, target);
|
||||
@@ -116,7 +115,7 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// Initialize charge - direction was already locked during windup
|
||||
startX = caster.posX;
|
||||
startY = caster.posY;
|
||||
@@ -146,7 +145,7 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
// Safety timeout or missing state: force-complete to prevent stuck NPC
|
||||
if (!isPreview() && (chargeDirection == null || tick > maxActiveTicks)) {
|
||||
stopMomentum(caster);
|
||||
@@ -206,11 +205,11 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
}
|
||||
|
||||
// Server-side collision damage (skip in preview)
|
||||
if (!world.isRemote && !isPreview()) {
|
||||
if (!caster.worldObj.isRemote && !isPreview()) {
|
||||
AxisAlignedBB hitBox = caster.boundingBox.expand(hitWidth, hitWidth * 0.5, hitWidth);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Entity> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, hitBox);
|
||||
List<Entity> entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox);
|
||||
|
||||
for (Entity entity : entities) {
|
||||
if (!(entity instanceof EntityLivingBase)) continue;
|
||||
@@ -226,9 +225,10 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
boolean wasHit = applyAbilityDamageWithDirection(caster, livingEntity, damage, knockback,
|
||||
chargeDirection.xCoord, chargeDirection.zCoord);
|
||||
|
||||
// Play impact sound if hit wasn't cancelled
|
||||
// Apply effects and play impact sound if hit wasn't cancelled
|
||||
if (wasHit) {
|
||||
world.playSoundAtEntity(livingEntity, "random.explode", 0.5f, 1.2f);
|
||||
applyEffects(livingEntity);
|
||||
caster.worldObj.playSoundAtEntity(livingEntity, "random.explode", 0.5f, 1.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,35 +255,7 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
|
||||
}
|
||||
|
||||
private boolean isChargeBlocked(EntityLivingBase caster) {
|
||||
double nextX = chargeDirection.xCoord * chargeSpeed;
|
||||
double nextZ = chargeDirection.zCoord * chargeSpeed;
|
||||
AxisAlignedBB nextBox = caster.boundingBox.copy().offset(nextX, 0, nextZ);
|
||||
double stepThreshold = nextBox.minY + Math.max(caster.stepHeight, 0.5);
|
||||
|
||||
int x1 = (int) Math.floor(nextBox.minX);
|
||||
int x2 = (int) Math.floor(nextBox.maxX + 1.0);
|
||||
int y1 = (int) Math.floor(nextBox.minY) - 1;
|
||||
int y2 = (int) Math.floor(nextBox.maxY + 1.0);
|
||||
int z1 = (int) Math.floor(nextBox.minZ);
|
||||
int z2 = (int) Math.floor(nextBox.maxZ + 1.0);
|
||||
|
||||
java.util.ArrayList<AxisAlignedBB> collisionBoxes = new java.util.ArrayList<>();
|
||||
for (int bx = x1; bx < x2; bx++) {
|
||||
for (int bz = z1; bz < z2; bz++) {
|
||||
for (int by = y1; by < y2; by++) {
|
||||
net.minecraft.block.Block block = caster.worldObj.getBlock(bx, by, bz);
|
||||
if (!block.getMaterial().blocksMovement()) continue;
|
||||
collisionBoxes.clear();
|
||||
block.addCollisionBoxesToList(caster.worldObj, bx, by, bz, nextBox, collisionBoxes, caster);
|
||||
for (AxisAlignedBB box : collisionBoxes) {
|
||||
if (box.maxY > stepThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return isMovementBlocked(caster, chargeDirection.xCoord, chargeDirection.zCoord, chargeSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -154,7 +154,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
hitEntities.clear();
|
||||
currentRotation = -arcAngle / 2.0f;
|
||||
activeSoundPlayed = false;
|
||||
@@ -175,8 +175,8 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) return;
|
||||
|
||||
switch (sweepMode) {
|
||||
case SWIPE:
|
||||
@@ -185,25 +185,25 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
|
||||
|
||||
// Play active sound when sweep reaches center (where target typically stands)
|
||||
if (!activeSoundPlayed && currentRotation >= 0) {
|
||||
playActiveSound(caster, world);
|
||||
playActiveSound(caster, caster.worldObj);
|
||||
}
|
||||
|
||||
if (currentRotation > arcAngle / 2.0f) {
|
||||
if (!isPreview()) {
|
||||
performSweepDamageRange(caster, world, innerRadius, range, prevRotation, arcAngle / 2.0f);
|
||||
performSweepDamageRange(caster, caster.worldObj, innerRadius, range, prevRotation, arcAngle / 2.0f);
|
||||
}
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
if (!isPreview()) {
|
||||
performSweepDamageRange(caster, world, innerRadius, range, prevRotation, currentRotation);
|
||||
performSweepDamageRange(caster, caster.worldObj, innerRadius, range, prevRotation, currentRotation);
|
||||
}
|
||||
break;
|
||||
|
||||
case SPIN:
|
||||
// Play active sound at spin start
|
||||
if (!activeSoundPlayed) {
|
||||
playActiveSound(caster, world);
|
||||
playActiveSound(caster, caster.worldObj);
|
||||
}
|
||||
|
||||
if (tick >= spinDurationTicks) {
|
||||
@@ -214,7 +214,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
|
||||
currentRotation = (currentRotation + sweepSpeed) % 360.0f;
|
||||
if (!isPreview()) {
|
||||
hitEntities.clear();
|
||||
performSweepDamageRange(caster, world, innerRadius, range, prevSpin, currentRotation);
|
||||
performSweepDamageRange(caster, caster.worldObj, innerRadius, range, prevSpin, currentRotation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
@@ -133,7 +132,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
startX = caster.posX;
|
||||
startY = caster.posY;
|
||||
startZ = caster.posZ;
|
||||
@@ -175,7 +174,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
// Safety timeout or missing state: force-complete to prevent stuck NPC
|
||||
if (!isPreview() && (dashDirection == null || tick > maxActiveTicks)) {
|
||||
stopDash(caster);
|
||||
@@ -224,7 +223,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
|
||||
caster.velocityChanged = true;
|
||||
|
||||
// Trail particles
|
||||
world.spawnParticle("smoke", caster.posX, caster.posY + 0.5, caster.posZ, 0, 0, 0);
|
||||
caster.worldObj.spawnParticle("smoke", caster.posX, caster.posY + 0.5, caster.posZ, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,35 +254,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
|
||||
|
||||
private boolean isDashBlocked(EntityLivingBase caster) {
|
||||
if (dashDirection == null) return true;
|
||||
double nextX = dashDirection.xCoord * dashSpeed;
|
||||
double nextZ = dashDirection.zCoord * dashSpeed;
|
||||
AxisAlignedBB nextBox = caster.boundingBox.copy().offset(nextX, 0, nextZ);
|
||||
double stepThreshold = nextBox.minY + Math.max(caster.stepHeight, 0.5);
|
||||
|
||||
int x1 = (int) Math.floor(nextBox.minX);
|
||||
int x2 = (int) Math.floor(nextBox.maxX + 1.0);
|
||||
int y1 = (int) Math.floor(nextBox.minY) - 1;
|
||||
int y2 = (int) Math.floor(nextBox.maxY + 1.0);
|
||||
int z1 = (int) Math.floor(nextBox.minZ);
|
||||
int z2 = (int) Math.floor(nextBox.maxZ + 1.0);
|
||||
|
||||
java.util.ArrayList<AxisAlignedBB> collisionBoxes = new java.util.ArrayList<>();
|
||||
for (int bx = x1; bx < x2; bx++) {
|
||||
for (int bz = z1; bz < z2; bz++) {
|
||||
for (int by = y1; by < y2; by++) {
|
||||
net.minecraft.block.Block block = caster.worldObj.getBlock(bx, by, bz);
|
||||
if (!block.getMaterial().blocksMovement()) continue;
|
||||
collisionBoxes.clear();
|
||||
block.addCollisionBoxesToList(caster.worldObj, bx, by, bz, nextBox, collisionBoxes, caster);
|
||||
for (AxisAlignedBB box : collisionBoxes) {
|
||||
if (box.maxY > stepThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return isMovementBlocked(caster, dashDirection.xCoord, dashDirection.zCoord, dashSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,67 +2,42 @@ 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.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import noppes.npcs.api.ability.type.IAbilityDisc;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
|
||||
/**
|
||||
* Disc ability: Spawns a flat spinning disc projectile.
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
* Has optional boomerang behavior to return to owner.
|
||||
*/
|
||||
public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
public class AbilityDisc extends AbstractEnergyProjectileAbility<EntityAbilityDisc> implements IAbilityDisc {
|
||||
|
||||
private static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Disc geometry (shared)
|
||||
private float discRadius = 1.0f;
|
||||
private float discThickness = 0.2f;
|
||||
private boolean vertical = false; // false = horizontal (flat), true = vertical (thin edge forward)
|
||||
|
||||
// Boomerang properties (shared)
|
||||
private boolean vertical = false;
|
||||
private boolean boomerang = false;
|
||||
private int boomerangDelay = 40;
|
||||
|
||||
// Projectile count and fire stagger
|
||||
private int projectileCount = 1;
|
||||
private int fireDelay = 0;
|
||||
|
||||
// Shared visual data (all projectiles use these)
|
||||
private EnergyDisplayData displayData = new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f);
|
||||
private EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Shared combat/movement data
|
||||
private final EnergyCombatData combatData = new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f);
|
||||
private final EnergyHomingData homingData = new EnergyHomingData(0.6f, true, 0.12f, 18.0f);
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(35.0f, 200);
|
||||
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
private ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
private transient EntityAbilityDisc[] discEntities;
|
||||
|
||||
public AbilityDisc() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f),
|
||||
new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f),
|
||||
new EnergyHomingData(0.6f, true, 0.12f, 18.0f),
|
||||
new EnergyLifespanData(35.0f, 200)
|
||||
);
|
||||
this.typeId = "ability.cnpc.disc";
|
||||
this.name = "Disc";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -75,59 +50,52 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Disc_Windup";
|
||||
this.activeAnimationName = "Ability_Disc_Active";
|
||||
initProjectiles(1);
|
||||
}
|
||||
|
||||
private void initProjectiles(int count) {
|
||||
this.projectileCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
this.projectiles = new ProjectileData[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
projectiles[i] = new ProjectileData(getDefaultAnchor(i));
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityAbilityDisc createEntity(EntityLivingBase caster, EntityLivingBase target,
|
||||
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
|
||||
return new EntityAbilityDisc(
|
||||
caster.worldObj, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
discRadius, discThickness,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
boomerang, boomerangDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireEntity(EntityAbilityDisc disc, EntityLivingBase target) {
|
||||
if (isPreview()) {
|
||||
disc.startPreviewFiring();
|
||||
} else {
|
||||
disc.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disc anchor order is REVERSED from Orb/Beam: [0]=LEFT_HAND, [1]=RIGHT_HAND
|
||||
*/
|
||||
private AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityCharging(EntityAbilityDisc disc, ProjectileData projData, int index) {
|
||||
disc.setupCharging(projData.anchor, windUpTicks, vertical);
|
||||
}
|
||||
|
||||
public void setProjectileCount(int count) {
|
||||
int newCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
if (newCount == projectileCount) return;
|
||||
|
||||
ProjectileData[] oldProjectiles = projectiles;
|
||||
initProjectiles(newCount);
|
||||
|
||||
if (oldProjectiles != null) {
|
||||
int copyCount = Math.min(oldProjectiles.length, newCount);
|
||||
for (int i = 0; i < copyCount; i++) {
|
||||
if (oldProjectiles[i] != null) projectiles[i] = oldProjectiles[i];
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityPreview(EntityAbilityDisc disc, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index) {
|
||||
disc.setupPreview(caster, discRadius, discThickness, resolved, lightningData, projData.anchor, windUpTicks, vertical);
|
||||
}
|
||||
|
||||
public int getProjectileCount() {
|
||||
return projectileCount;
|
||||
@Override
|
||||
protected EntityAbilityDisc[] createEntityArray(int size) {
|
||||
return new EntityAbilityDisc[size];
|
||||
}
|
||||
|
||||
public int getFireDelay() {
|
||||
return fireDelay;
|
||||
@Override
|
||||
protected float getProjectileTelegraphRadius() {
|
||||
return discRadius * 1.5f;
|
||||
}
|
||||
|
||||
public void setFireDelay(int delay) {
|
||||
this.fireDelay = Math.max(0, delay);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #getFireDelay()} */
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
/** @deprecated Use {@link #setFireDelay(int)} */
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
// ==================== VARIANTS ====================
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
@@ -140,7 +108,6 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
a.setName("Dual Disc");
|
||||
disc.setProjectileCount(2);
|
||||
disc.setFireDelay(5);
|
||||
// Set color override for second disc
|
||||
disc.projectiles[1].colorOverride = true;
|
||||
disc.projectiles[1].innerColor = 0xFFFFFF;
|
||||
disc.projectiles[1].outerColor = 0x8800FF;
|
||||
@@ -150,255 +117,27 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
// ==================== TYPE-SPECIFIC NBT ====================
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create disc entities.
|
||||
* NPC: target is the aggro target — discs will home toward it.
|
||||
* Player: target is null — discs fire in caster's look direction.
|
||||
*/
|
||||
private EntityAbilityDisc[] createDiscEntities(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
EntityAbilityDisc[] entities = new EntityAbilityDisc[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, projectiles[i].anchor);
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
entities[i] = new EntityAbilityDisc(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
discRadius, discThickness,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
boomerang, boomerangDelay);
|
||||
entities[i].setEffects(this.effects);
|
||||
entities[i].setSourceAbility(this);
|
||||
}
|
||||
if (projectileCount == 2 && entities[0] != null && entities[1] != null) {
|
||||
entities[0].setSiblingEntityId(entities[1].getEntityId());
|
||||
entities[1].setSiblingEntityId(entities[0].getEntityId());
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private void fireDiscEntity(EntityAbilityDisc disc, EntityLivingBase target) {
|
||||
if (disc == null || disc.isDead) return;
|
||||
if (isPreview()) {
|
||||
disc.startPreviewFiring();
|
||||
} else {
|
||||
disc.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
if (tick == 1) {
|
||||
discEntities = createDiscEntities(caster, target, world);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
if (isPreview()) {
|
||||
discEntities[i].setupPreview(caster, discRadius, discThickness, resolved, lightningData, projectiles[i].anchor, windUpTicks, vertical);
|
||||
} else {
|
||||
discEntities[i].setupCharging(projectiles[i].anchor, windUpTicks, vertical);
|
||||
}
|
||||
spawnAbilityEntity(world, discEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
discEntities = createDiscEntities(caster, target, world);
|
||||
for (EntityAbilityDisc disc : discEntities) {
|
||||
spawnAbilityEntity(world, disc);
|
||||
}
|
||||
|
||||
// Fire first projectile immediately, let onActiveTick handle the rest with fireDelay
|
||||
fireDiscEntity(discEntities[0], target);
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireDiscEntity(discEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (discEntities == null) return;
|
||||
|
||||
// Fire first projectile immediately
|
||||
fireDiscEntity(discEntities[0], target);
|
||||
|
||||
// Fire remaining projectiles if no delay
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireDiscEntity(discEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (discEntities == null) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire staggered projectiles
|
||||
if (fireDelay > 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (tick == fireDelay * i) {
|
||||
fireDiscEntity(discEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all entities are dead
|
||||
boolean allDead = true;
|
||||
for (EntityAbilityDisc disc : discEntities) {
|
||||
if (disc != null && !disc.isDead) {
|
||||
allDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDead) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (discEntities != null) {
|
||||
for (int i = 0; i < discEntities.length; i++) {
|
||||
if (discEntities[i] != null && !discEntities[i].isDead) {
|
||||
discEntities[i].setDead();
|
||||
}
|
||||
discEntities[i] = null;
|
||||
}
|
||||
}
|
||||
discEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// NPC: telegraph on aggro target position
|
||||
// Player: no telegraph (discs fire in look direction, no fixed position to mark)
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster) || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create circle telegraph at target position
|
||||
Telegraph telegraph = Telegraph.circle(discRadius * 1.5f);
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
telegraph.setWarningColor(activeColor);
|
||||
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
|
||||
telegraph.setHeightOffset(telegraphHeightOffset);
|
||||
|
||||
// Position at target and follow target during windup
|
||||
TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(target.getEntityId());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTelegraphRadius() {
|
||||
return discRadius * 1.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("discRadius", discRadius);
|
||||
nbt.setFloat("discThickness", discThickness);
|
||||
nbt.setBoolean("vertical", vertical);
|
||||
nbt.setBoolean("boomerang", boomerang);
|
||||
nbt.setInteger("boomerangDelay", boomerangDelay);
|
||||
nbt.setInteger("projectileCount", projectileCount);
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared visual data
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
NBTTagCompound projNbt = new NBTTagCompound();
|
||||
projectiles[i].writeNBT(projNbt);
|
||||
nbt.setTag("Projectile_" + i, projNbt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
this.discRadius = nbt.getFloat("discRadius");
|
||||
this.discThickness = nbt.getFloat("discThickness");
|
||||
this.vertical = nbt.getBoolean("vertical");
|
||||
this.boomerang = nbt.getBoolean("boomerang");
|
||||
this.boomerangDelay = nbt.getInteger("boomerangDelay");
|
||||
|
||||
int count = nbt.getInteger("projectileCount");
|
||||
initProjectiles(count);
|
||||
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
// Shared visual data
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GETTERS ====================
|
||||
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { homingData.speed = speed; }
|
||||
@@ -418,141 +157,16 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
public int getBoomerangDelay() { return boomerangDelay; }
|
||||
public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; }
|
||||
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float d) { lifespanData.maxDistance = d; }
|
||||
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int t) { lifespanData.maxLifetime = t; }
|
||||
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float d) { combatData.damage = d; }
|
||||
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float k) { combatData.knockback = k; }
|
||||
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float k) { combatData.knockbackUp = k; }
|
||||
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean h) { homingData.homing = h; }
|
||||
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float s) { homingData.homingStrength = s; }
|
||||
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float r) { homingData.homingRange = r; }
|
||||
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean e) { combatData.explosive = e; }
|
||||
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float r) { combatData.explosionRadius = r; }
|
||||
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float f) { combatData.explosionDamageFalloff = f; }
|
||||
|
||||
// Shared display data (primary colors)
|
||||
public int getInnerColor() { return displayData.innerColor; }
|
||||
public void setInnerColor(int c) { displayData.innerColor = c; }
|
||||
|
||||
public int getOuterColor() { return displayData.outerColor; }
|
||||
public void setOuterColor(int c) { displayData.outerColor = c; }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float w) { displayData.outerColorWidth = w; }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float a) { displayData.outerColorAlpha = a; }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean e) { displayData.outerColorEnabled = e; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float s) { displayData.rotationSpeed = s; }
|
||||
|
||||
// Shared lightning data
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean e) { lightningData.lightningEffect = e; }
|
||||
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float d) { lightningData.lightningDensity = d; }
|
||||
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float r) { lightningData.lightningRadius = r; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int t) { lightningData.lightningFadeTime = t; }
|
||||
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint p) { projectiles[0].anchor.anchorPoint = p; }
|
||||
|
||||
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
|
||||
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
|
||||
|
||||
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
|
||||
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
|
||||
|
||||
public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; }
|
||||
|
||||
@Override
|
||||
public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
|
||||
@Override
|
||||
public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
// Indexed API methods
|
||||
public int getInnerColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.innerColor : displayData.innerColor;
|
||||
}
|
||||
public void setInnerColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.innerColor = color;
|
||||
}
|
||||
|
||||
public int getOuterColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.outerColor : displayData.outerColor;
|
||||
}
|
||||
public void setOuterColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.outerColor = color;
|
||||
}
|
||||
|
||||
public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); }
|
||||
public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; }
|
||||
public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; }
|
||||
public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; }
|
||||
|
||||
private int clampIndex(int index) {
|
||||
return Math.max(0, Math.min(index, projectileCount - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// GUI FIELD DEFINITIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
// Type tab - projectile count and fire delay
|
||||
protected void addTypeDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.projectileCount", this::getProjectileCount, this::setProjectileCount).range(1, MAX_PROJECTILES),
|
||||
FieldDef.intField("ability.fireDelay", this::getFireDelay, this::setFireDelay)
|
||||
.range(0, 200).visibleWhen(() -> projectileCount > 1)
|
||||
));
|
||||
|
||||
// Type tab - disc properties
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
@@ -579,64 +193,5 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
|
||||
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive));
|
||||
defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects));
|
||||
|
||||
// Visual tab - shared primary colors
|
||||
defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - shared effects
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.visibleWhen(this::hasLightningEffect).range(0.01f, 100f),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - per-projectile sections
|
||||
for (int i = 0; i < MAX_PROJECTILES; i++) {
|
||||
final int idx = i;
|
||||
String sectionKey = "ability.section.projectile" + (i + 1);
|
||||
|
||||
defs.add(FieldDef.section(sectionKey).tab("ability.tab.visual")
|
||||
.visibleWhen(() -> idx < projectileCount));
|
||||
|
||||
// Anchor point
|
||||
defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX,
|
||||
v -> projectiles[idx].anchor.anchorOffsetX = v).min(Float.NEGATIVE_INFINITY),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> projectiles[idx].anchor.anchorOffsetY,
|
||||
v -> projectiles[idx].anchor.anchorOffsetY = v).min(Float.NEGATIVE_INFINITY)
|
||||
).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ,
|
||||
v -> projectiles[idx].anchor.anchorOffsetZ = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount).min(Float.NEGATIVE_INFINITY));
|
||||
|
||||
// Color override (only when multiple projectiles)
|
||||
defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", () -> projectiles[idx].innerColor, v -> projectiles[idx].innerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", () -> projectiles[idx].outerColor, v -> projectiles[idx].outerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
lastAttacker = null;
|
||||
counterTriggered = false;
|
||||
lastDamageTaken = 0.0f;
|
||||
@@ -103,9 +103,9 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (canCounter && counterEligible && counterTriggered && lastAttacker != null && !world.isRemote) {
|
||||
performCounter(caster, world);
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (canCounter && counterEligible && counterTriggered && lastAttacker != null && !caster.worldObj.isRemote) {
|
||||
performCounter(caster, caster.worldObj);
|
||||
}
|
||||
|
||||
if (tick > counterWindow && counterEligible) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
|
||||
import noppes.npcs.api.ability.type.IAbilityHazard;
|
||||
|
||||
@@ -48,12 +48,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
activeEntities.clear();
|
||||
|
||||
// Use pre-calculated positions from telegraph phase, or generate new ones
|
||||
@@ -75,7 +70,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
|
||||
for (int i = 0; i < zoneCount; i++) {
|
||||
double[] pos = positions.get(i);
|
||||
|
||||
EntityAbilityZone entity = EntityAbilityZone.createHazard(world, caster,
|
||||
EntityAbilityZone entity = EntityAbilityZone.createHazard(caster.worldObj, caster,
|
||||
pos[0], caster.posY, pos[1],
|
||||
zoneShape,
|
||||
radius,
|
||||
@@ -93,7 +88,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
|
||||
entity.setupPreview(caster);
|
||||
}
|
||||
|
||||
spawnAbilityEntity(world, entity);
|
||||
spawnAbilityEntity(entity);
|
||||
activeEntities.add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,27 +79,27 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) return;
|
||||
|
||||
if (!isPreview()) {
|
||||
getHealedAllies().clear();
|
||||
|
||||
// Always find allies if we're healing them (needed for both instant and HoT)
|
||||
if (healAllies && healRadius > 0) {
|
||||
findAlliesInRadius(caster, world);
|
||||
findAlliesInRadius(caster, caster.worldObj);
|
||||
}
|
||||
|
||||
if (instantHeal) {
|
||||
// Instant heal - apply all healing now
|
||||
if (healSelf) {
|
||||
healEntity(caster);
|
||||
spawnHealParticles(world, caster);
|
||||
spawnHealParticles(caster.worldObj, caster);
|
||||
}
|
||||
|
||||
for (EntityLivingBase ally : getHealedAllies()) {
|
||||
healEntity(ally);
|
||||
spawnHealParticles(world, ally);
|
||||
spawnHealParticles(caster.worldObj, ally);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,8 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if ((world.isRemote && !isPreview()) || instantHeal) return;
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if ((caster.worldObj.isRemote && !isPreview()) || instantHeal) return;
|
||||
|
||||
// Heal over time - distribute heal across duration ticks
|
||||
if (tick % 10 == 0) {
|
||||
@@ -128,7 +128,7 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
|
||||
}
|
||||
caster.heal(selfTickHeal);
|
||||
if (tick % 20 == 0) {
|
||||
spawnHealParticles(world, caster);
|
||||
spawnHealParticles(caster.worldObj, caster);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
|
||||
}
|
||||
ally.heal(allyTickHeal);
|
||||
if (tick % 20 == 0) {
|
||||
spawnHealParticles(world, ally);
|
||||
spawnHealParticles(caster.worldObj, ally);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.world.World;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import noppes.npcs.api.ability.type.IAbilityHeavyHit;
|
||||
@@ -28,6 +27,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
private float knockback = 2.0f;
|
||||
private float hitLength = 4.0f; // How far in front of the caster the hit reaches
|
||||
private float hitWidth = 3.0f; // How wide to each side (total width = hitWidth * 2)
|
||||
private int activeDisplayTicks = 10; // How long active animation plays before completing
|
||||
|
||||
public AbilityHeavyHit() {
|
||||
this.typeId = "ability.cnpc.heavy_hit";
|
||||
@@ -67,8 +67,8 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
// Search area: AABB that encompasses the rectangle
|
||||
float searchDist = Math.max(hitLength, hitWidth) + 1.0f;
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Entity> entities = world.getEntitiesWithinAABBExcludingEntity(
|
||||
List<Entity> entities = caster.worldObj.getEntitiesWithinAABBExcludingEntity(
|
||||
caster, caster.boundingBox.expand(searchDist, 2, searchDist));
|
||||
|
||||
boolean anyHit = false;
|
||||
@@ -113,15 +113,14 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
|
||||
// Play hit sound even if nothing was hit (the attack still happens)
|
||||
if (!anyHit) {
|
||||
world.playSoundAtEntity(caster, "random.anvil_land", 0.5f, 1.2f);
|
||||
caster.worldObj.playSoundAtEntity(caster, "random.anvil_land", 0.5f, 1.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Just enough delay to see the active animation
|
||||
if (tick == 10)
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (tick >= activeDisplayTicks)
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
@@ -131,6 +130,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
nbt.setFloat("knockback", knockback);
|
||||
nbt.setFloat("hitLength", hitLength);
|
||||
nbt.setFloat("hitWidth", hitWidth);
|
||||
nbt.setInteger("activeDisplayTicks", activeDisplayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,6 +139,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
this.knockback = nbt.getFloat("knockback");
|
||||
this.hitLength = nbt.getFloat("hitLength");
|
||||
this.hitWidth = nbt.getFloat("hitWidth");
|
||||
this.activeDisplayTicks = nbt.hasKey("activeDisplayTicks") ? nbt.getInteger("activeDisplayTicks") : 10;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -174,6 +175,14 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
this.hitWidth = hitWidth;
|
||||
}
|
||||
|
||||
public int getActiveDisplayTicks() {
|
||||
return activeDisplayTicks;
|
||||
}
|
||||
|
||||
public void setActiveDisplayTicks(int activeDisplayTicks) {
|
||||
this.activeDisplayTicks = activeDisplayTicks;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
@@ -187,6 +196,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
|
||||
FieldDef.floatField("ability.hitLength", this::getHitLength, this::setHitLength),
|
||||
FieldDef.floatField("ability.hitWidth", this::getHitWidth, this::setHitWidth)
|
||||
),
|
||||
FieldDef.intField("ability.activeDisplayTicks", this::getActiveDisplayTicks, this::setActiveDisplayTicks).range(1, 200),
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,50 +2,42 @@ 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.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.RotationMode;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.api.ability.type.IAbilityLaserShot;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Laser Shot ability: Fast expanding thin line that pierces through targets.
|
||||
* Travels in a straight line from origin, damaging all entities it passes through.
|
||||
* Single-projectile only (always projectileCount=1).
|
||||
*/
|
||||
public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
public class AbilityLaserShot extends AbstractEnergyProjectileAbility<EntityAbilityLaser> implements IAbilityLaserShot {
|
||||
|
||||
// Laser properties
|
||||
private float laserWidth = 0.3f;
|
||||
private float expansionSpeed = 3.0f;
|
||||
private int lingerTicks = 8;
|
||||
|
||||
// Data classes
|
||||
private final EnergyDisplayData colorData = new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f);
|
||||
private final EnergyCombatData combatData = new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f);
|
||||
private final EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(40.0f, 100);
|
||||
private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.FRONT);
|
||||
private final EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Transient state for laser entity (used for movement locking)
|
||||
private transient EntityAbilityLaser laserEntity = null;
|
||||
|
||||
public AbilityLaserShot() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
|
||||
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f),
|
||||
new EnergyHomingData(),
|
||||
new EnergyLifespanData(40.0f, 100)
|
||||
);
|
||||
this.typeId = "ability.cnpc.laser_shot";
|
||||
this.name = "Laser Shot";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -58,14 +50,15 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
this.rotationPhase = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.LINE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
this.windUpAnimationName = "Ability_Laser_Windup";
|
||||
this.activeAnimationName = "Ability_Laser_Active";
|
||||
}
|
||||
|
||||
// ==================== OVERRIDES ====================
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
protected AnchorPoint getDefaultAnchor(int index) {
|
||||
return AnchorPoint.FRONT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,8 +67,34 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 60) : 60;
|
||||
}
|
||||
|
||||
/**
|
||||
* LINE telegraph: positioned at caster, aimed toward target.
|
||||
* Overrides the circle telegraph from AbstractEnergyProjectileAbility.
|
||||
*/
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Telegraph telegraph = Telegraph.line(getTelegraphLength(), getTelegraphWidth());
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
telegraph.setWarningColor(activeColor);
|
||||
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
|
||||
telegraph.setHeightOffset(telegraphHeightOffset);
|
||||
|
||||
TelegraphInstance instance = new TelegraphInstance(telegraph, caster.posX, caster.posY, caster.posZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(caster.getEntityId());
|
||||
if (target != null) {
|
||||
instance.setTargetEntityId(target.getEntityId());
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,242 +104,99 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
|
||||
@Override
|
||||
public float getTelegraphWidth() {
|
||||
return laserWidth * 2.0f; // Make telegraph slightly wider for visibility
|
||||
return laserWidth * 2.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn laser entity.
|
||||
* NPC: target is the aggro target — laser expands toward it.
|
||||
* Player: target is null — laser expands in caster's look direction.
|
||||
*/
|
||||
private EntityAbilityLaser spawnLaserEntity(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData);
|
||||
EntityAbilityLaser entity = new EntityAbilityLaser(
|
||||
world, caster, target,
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityAbilityLaser createEntity(EntityLivingBase caster, EntityLivingBase target,
|
||||
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
|
||||
return new EntityAbilityLaser(
|
||||
caster.worldObj, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
laserWidth,
|
||||
colorData, combatData, lightningData, lifespanData, trajectoryData,
|
||||
expansionSpeed, lingerTicks
|
||||
);
|
||||
entity.setEffects(this.effects);
|
||||
entity.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, entity);
|
||||
return entity;
|
||||
resolved, combatData, lightningData, lifespanData, trajectoryData,
|
||||
expansionSpeed, lingerTicks);
|
||||
}
|
||||
|
||||
private void fireLaserEntity(EntityAbilityLaser laser, EntityLivingBase target) {
|
||||
if (laser == null || laser.isDead) return;
|
||||
@Override
|
||||
protected void fireEntity(EntityAbilityLaser laser, EntityLivingBase target) {
|
||||
laser.setLockVerticalDirection(true);
|
||||
laser.startMoving(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
if (tick == 1) {
|
||||
laserEntity = spawnLaserEntity(caster, target, world);
|
||||
if (isPreview()) {
|
||||
laserEntity.setupPreview(caster, laserWidth, colorData, lightningData, expansionSpeed, lifespanData.maxDistance);
|
||||
} else {
|
||||
laserEntity.setupCharging(anchorData, windUpTicks);
|
||||
}
|
||||
}
|
||||
protected void setupEntityCharging(EntityAbilityLaser laser, ProjectileData projData, int index) {
|
||||
laser.setupCharging(projData.anchor, windUpTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
laserEntity = spawnLaserEntity(caster, target, world);
|
||||
fireLaserEntity(laserEntity, target);
|
||||
protected void setupEntityPreview(EntityAbilityLaser laser, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index) {
|
||||
laser.setupPreview(caster, laserWidth, resolved, lightningData, expansionSpeed, lifespanData.maxDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (laserEntity == null) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
fireLaserEntity(laserEntity, target);
|
||||
protected EntityAbilityLaser[] createEntityArray(int size) {
|
||||
return new EntityAbilityLaser[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (laserEntity == null || laserEntity.isDead) {
|
||||
laserEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
protected float getProjectileTelegraphRadius() {
|
||||
return laserWidth * 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
|
||||
}
|
||||
// ==================== TYPE-SPECIFIC NBT ====================
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Despawn laser entity if still alive
|
||||
if (laserEntity != null && !laserEntity.isDead) {
|
||||
laserEntity.setDead();
|
||||
}
|
||||
laserEntity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("laserWidth", laserWidth);
|
||||
nbt.setFloat("expansionSpeed", expansionSpeed);
|
||||
nbt.setInteger("lingerTicks", lingerTicks);
|
||||
anchorData.writeNBT(nbt);
|
||||
colorData.writeNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
this.laserWidth = nbt.getFloat("laserWidth");
|
||||
this.expansionSpeed = nbt.getFloat("expansionSpeed");
|
||||
this.lingerTicks = nbt.getInteger("lingerTicks");
|
||||
anchorData.readNBT(nbt);
|
||||
colorData.readNBT(nbt);
|
||||
combatData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
}
|
||||
|
||||
// Getters & Setters - Standalone fields
|
||||
// ==================== TYPE-SPECIFIC GETTERS ====================
|
||||
|
||||
public float getLaserWidth() { return laserWidth; }
|
||||
public void setLaserWidth(float laserWidth) { this.laserWidth = laserWidth; }
|
||||
|
||||
public float getExpansionSpeed() { return expansionSpeed; }
|
||||
public void setExpansionSpeed(float expansionSpeed) { this.expansionSpeed = expansionSpeed; }
|
||||
|
||||
public int getLingerTicks() { return lingerTicks; }
|
||||
public void setLingerTicks(int lingerTicks) { this.lingerTicks = lingerTicks; }
|
||||
|
||||
// Getters & Setters - Lifespan data
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float maxDistance) { this.lifespanData.maxDistance = maxDistance; }
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int maxLifetime) { this.lifespanData.maxLifetime = maxLifetime; }
|
||||
|
||||
// Getters & Setters - Combat data
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float damage) { this.combatData.damage = damage; }
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float knockback) { this.combatData.knockback = knockback; }
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float knockbackUp) { this.combatData.knockbackUp = knockbackUp; }
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean explosive) { this.combatData.explosive = explosive; }
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float explosionRadius) { this.combatData.explosionRadius = explosionRadius; }
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) { this.combatData.explosionDamageFalloff = explosionDamageFalloff; }
|
||||
// Getters & Setters - Color data
|
||||
public int getInnerColor() { return colorData.innerColor; }
|
||||
public void setInnerColor(int innerColor) { this.colorData.innerColor = innerColor; }
|
||||
public int getOuterColor() { return colorData.outerColor; }
|
||||
public void setOuterColor(int outerColor) { this.colorData.outerColor = outerColor; }
|
||||
public float getOuterColorWidth() { return colorData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float outerColorWidth) { this.colorData.outerColorWidth = outerColorWidth; }
|
||||
public float getOuterColorAlpha() { return colorData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float outerColorAlpha) { this.colorData.outerColorAlpha = outerColorAlpha; }
|
||||
public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) { this.colorData.outerColorEnabled = outerColorEnabled; }
|
||||
|
||||
// Getters & Setters - Lightning data
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean lightningEffect) { this.lightningData.lightningEffect = lightningEffect; }
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float lightningDensity) { this.lightningData.lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { this.lightningData.lightningRadius = lightningRadius; }
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return anchorData.anchorPoint; }
|
||||
|
||||
public float getAnchorOffsetX() { return anchorData.anchorOffsetX; }
|
||||
public float getAnchorOffsetY() { return anchorData.anchorOffsetY; }
|
||||
public float getAnchorOffsetZ() { return anchorData.anchorOffsetZ; }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { this.anchorData.anchorPoint = anchorPoint; }
|
||||
public void setAnchorOffsetX(float x) { this.anchorData.anchorOffsetX = x; }
|
||||
public void setAnchorOffsetY(float y) { this.anchorData.anchorOffsetY = y; }
|
||||
public void setAnchorOffsetZ(float z) { this.anchorData.anchorOffsetZ = z; }
|
||||
public int getAnchorPoint() {
|
||||
return anchorData.anchorPoint.ordinal();
|
||||
}
|
||||
public void setAnchorPoint(int point) { this.anchorData.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 60) : 60;
|
||||
}
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.floatField("ability.knockbackUp", this::getKnockbackUp, this::setKnockbackUp)
|
||||
),
|
||||
FieldDef.section("ability.section.beam"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth),
|
||||
FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.section("ability.section.explosive"),
|
||||
FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"),
|
||||
FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive),
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects),
|
||||
|
||||
// Visual tab
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class, this::getAnchorPointEnum, this::setAnchorPointEnum)
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", this::getAnchorOffsetX, this::setAnchorOffsetX)
|
||||
.min(Float.NEGATIVE_INFINITY),
|
||||
FieldDef.floatField("ability.anchor.offsetY", this::getAnchorOffsetY, this::setAnchorOffsetY)
|
||||
.min(Float.NEGATIVE_INFINITY)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", this::getAnchorOffsetZ, this::setAnchorOffsetZ)
|
||||
.min(Float.NEGATIVE_INFINITY).tab("ability.tab.visual"),
|
||||
FieldDef.section("ability.section.colors").tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor).tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled).tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.section("ability.section.effects").tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect).tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.range(0.01f, 100f).visibleWhen(this::hasLightningEffect),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual")
|
||||
protected void addTypeDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.floatField("ability.knockbackUp", this::getKnockbackUp, this::setKnockbackUp)
|
||||
));
|
||||
defs.add(FieldDef.section("ability.section.beam"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth),
|
||||
FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed)
|
||||
));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
));
|
||||
defs.add(FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance));
|
||||
defs.add(FieldDef.section("ability.section.explosive"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
|
||||
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive));
|
||||
defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects));
|
||||
}
|
||||
}
|
||||
@@ -2,60 +2,37 @@ 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.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import noppes.npcs.api.ability.type.IAbilityOrb;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
|
||||
/**
|
||||
* Orb ability: Spawns homing projectile sphere(s) that track target.
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
*/
|
||||
public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
public class AbilityOrb extends AbstractEnergyProjectileAbility<EntityAbilityOrb> implements IAbilityOrb {
|
||||
|
||||
private static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Orb properties
|
||||
private float orbSize = 1.0f;
|
||||
|
||||
// Projectile count and fire stagger
|
||||
private int projectileCount = 1;
|
||||
private int fireDelay = 0;
|
||||
|
||||
// Shared visual data (all projectiles use these)
|
||||
private EnergyDisplayData displayData = new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f);
|
||||
private EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Shared combat/movement data
|
||||
private final EnergyCombatData combatData = new EnergyCombatData();
|
||||
private final EnergyHomingData homingData = new EnergyHomingData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData();
|
||||
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
private ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
private transient EntityAbilityOrb[] orbEntities;
|
||||
|
||||
public AbilityOrb() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
|
||||
new EnergyCombatData(),
|
||||
new EnergyHomingData(),
|
||||
new EnergyLifespanData()
|
||||
);
|
||||
this.typeId = "ability.cnpc.orb";
|
||||
this.name = "Orb";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -68,57 +45,50 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Orb_Windup";
|
||||
this.activeAnimationName = "Ability_Orb_Active";
|
||||
initProjectiles(1);
|
||||
}
|
||||
|
||||
private void initProjectiles(int count) {
|
||||
this.projectileCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
this.projectiles = new ProjectileData[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
projectiles[i] = new ProjectileData(getDefaultAnchor(i));
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@Override
|
||||
protected EntityAbilityOrb createEntity(EntityLivingBase caster, EntityLivingBase target,
|
||||
Vec3 spawnPos, EnergyDisplayData resolved, int index) {
|
||||
return new EntityAbilityOrb(
|
||||
caster.worldObj, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireEntity(EntityAbilityOrb orb, EntityLivingBase target) {
|
||||
if (isPreview()) {
|
||||
orb.startPreviewFiring();
|
||||
} else {
|
||||
orb.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityCharging(EntityAbilityOrb orb, ProjectileData projData, int index) {
|
||||
orb.setupCharging(projData.anchor, windUpTicks);
|
||||
}
|
||||
|
||||
public void setProjectileCount(int count) {
|
||||
int newCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
if (newCount == projectileCount) return;
|
||||
|
||||
ProjectileData[] oldProjectiles = projectiles;
|
||||
initProjectiles(newCount);
|
||||
|
||||
// Preserve existing projectile data
|
||||
if (oldProjectiles != null) {
|
||||
int copyCount = Math.min(oldProjectiles.length, newCount);
|
||||
for (int i = 0; i < copyCount; i++) {
|
||||
if (oldProjectiles[i] != null) projectiles[i] = oldProjectiles[i];
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void setupEntityPreview(EntityAbilityOrb orb, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index) {
|
||||
orb.setupPreview(caster, orbSize, resolved, lightningData, projData.anchor, windUpTicks);
|
||||
}
|
||||
|
||||
public int getProjectileCount() {
|
||||
return projectileCount;
|
||||
@Override
|
||||
protected EntityAbilityOrb[] createEntityArray(int size) {
|
||||
return new EntityAbilityOrb[size];
|
||||
}
|
||||
|
||||
public int getFireDelay() {
|
||||
return fireDelay;
|
||||
@Override
|
||||
protected float getProjectileTelegraphRadius() {
|
||||
return orbSize * 1.5f;
|
||||
}
|
||||
|
||||
public void setFireDelay(int delay) {
|
||||
this.fireDelay = Math.max(0, delay);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #getFireDelay()} */
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
/** @deprecated Use {@link #setFireDelay(int)} */
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
// ==================== VARIANTS ====================
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
@@ -153,244 +123,19 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
// ==================== TYPE-SPECIFIC NBT ====================
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create orb entities.
|
||||
* NPC: target is the aggro target — orbs will home toward it.
|
||||
* Player: target is null — orbs fire in caster's look direction.
|
||||
*/
|
||||
private EntityAbilityOrb[] createOrbEntities(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
EntityAbilityOrb[] entities = new EntityAbilityOrb[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, projectiles[i].anchor);
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
entities[i] = new EntityAbilityOrb(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize,
|
||||
resolved, combatData, homingData, lightningData, lifespanData, trajectoryData);
|
||||
entities[i].setEffects(this.effects);
|
||||
entities[i].setSourceAbility(this);
|
||||
}
|
||||
if (projectileCount == 2 && entities[0] != null && entities[1] != null) {
|
||||
entities[0].setSiblingEntityId(entities[1].getEntityId());
|
||||
entities[1].setSiblingEntityId(entities[0].getEntityId());
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private void fireOrbEntity(EntityAbilityOrb orb, EntityLivingBase target) {
|
||||
if (orb == null || orb.isDead) return;
|
||||
if (isPreview()) {
|
||||
orb.startPreviewFiring();
|
||||
} else {
|
||||
orb.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
if (tick == 1) {
|
||||
orbEntities = createOrbEntities(caster, target, world);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
if (isPreview()) {
|
||||
orbEntities[i].setupPreview(caster, orbSize, resolved, lightningData, projectiles[i].anchor, windUpTicks);
|
||||
} else {
|
||||
orbEntities[i].setupCharging(projectiles[i].anchor, windUpTicks);
|
||||
}
|
||||
spawnAbilityEntity(world, orbEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
orbEntities = createOrbEntities(caster, target, world);
|
||||
for (EntityAbilityOrb orb : orbEntities) {
|
||||
spawnAbilityEntity(world, orb);
|
||||
}
|
||||
|
||||
// Fire first projectile immediately, let onActiveTick handle the rest with fireDelay
|
||||
fireOrbEntity(orbEntities[0], target);
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireOrbEntity(orbEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (orbEntities == null) return;
|
||||
|
||||
// Fire first projectile immediately
|
||||
fireOrbEntity(orbEntities[0], target);
|
||||
|
||||
// Fire remaining projectiles if no delay
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireOrbEntity(orbEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (orbEntities == null) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire staggered projectiles
|
||||
if (fireDelay > 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (tick == fireDelay * i) {
|
||||
fireOrbEntity(orbEntities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all entities are dead
|
||||
boolean allDead = true;
|
||||
for (EntityAbilityOrb orb : orbEntities) {
|
||||
if (orb != null && !orb.isDead) {
|
||||
allDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDead) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (orbEntities != null) {
|
||||
for (int i = 0; i < orbEntities.length; i++) {
|
||||
if (orbEntities[i] != null && !orbEntities[i].isDead) {
|
||||
orbEntities[i].setDead();
|
||||
}
|
||||
orbEntities[i] = null;
|
||||
}
|
||||
}
|
||||
orbEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// NPC: telegraph on aggro target position
|
||||
// Player: no telegraph (orbs fire in look direction, no fixed position to mark)
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster) || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Telegraph telegraph = Telegraph.circle(orbSize * 1.5f);
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
telegraph.setWarningColor(activeColor);
|
||||
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
|
||||
telegraph.setHeightOffset(telegraphHeightOffset);
|
||||
|
||||
TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(target.getEntityId());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTelegraphRadius() {
|
||||
return orbSize * 1.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("orbSize", orbSize);
|
||||
nbt.setInteger("projectileCount", projectileCount);
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared visual data
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
NBTTagCompound projNbt = new NBTTagCompound();
|
||||
projectiles[i].writeNBT(projNbt);
|
||||
nbt.setTag("Projectile_" + i, projNbt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
this.orbSize = nbt.getFloat("orbSize");
|
||||
|
||||
int count = nbt.getInteger("projectileCount");
|
||||
initProjectiles(count);
|
||||
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
// Shared visual data
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GETTERS ====================
|
||||
|
||||
public float getOrbSpeed() { return homingData.speed; }
|
||||
public void setOrbSpeed(float speed) { homingData.speed = speed; }
|
||||
@@ -398,139 +143,16 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
public float getOrbSize() { return orbSize; }
|
||||
public void setOrbSize(float size) { this.orbSize = size; }
|
||||
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float d) { lifespanData.maxDistance = d; }
|
||||
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int t) { lifespanData.maxLifetime = t; }
|
||||
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float d) { combatData.damage = d; }
|
||||
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float k) { combatData.knockback = k; }
|
||||
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float k) { combatData.knockbackUp = k; }
|
||||
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean h) { homingData.homing = h; }
|
||||
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float s) { homingData.homingStrength = s; }
|
||||
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float r) { homingData.homingRange = r; }
|
||||
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean e) { combatData.explosive = e; }
|
||||
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float r) { combatData.explosionRadius = r; }
|
||||
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float f) { combatData.explosionDamageFalloff = f; }
|
||||
|
||||
// Shared display data (primary colors)
|
||||
public int getInnerColor() { return displayData.innerColor; }
|
||||
public void setInnerColor(int c) { displayData.innerColor = c; }
|
||||
|
||||
public int getOuterColor() { return displayData.outerColor; }
|
||||
public void setOuterColor(int c) { displayData.outerColor = c; }
|
||||
|
||||
public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float w) { displayData.outerColorWidth = w; }
|
||||
|
||||
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float a) { displayData.outerColorAlpha = a; }
|
||||
|
||||
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean e) { displayData.outerColorEnabled = e; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float s) { displayData.rotationSpeed = s; }
|
||||
|
||||
// Shared lightning data
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean e) { lightningData.lightningEffect = e; }
|
||||
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float d) { lightningData.lightningDensity = d; }
|
||||
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float r) { lightningData.lightningRadius = r; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int t) { lightningData.lightningFadeTime = t; }
|
||||
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint p) { projectiles[0].anchor.anchorPoint = p; }
|
||||
|
||||
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
|
||||
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
|
||||
|
||||
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
|
||||
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
|
||||
|
||||
public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; }
|
||||
|
||||
public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
@Override
|
||||
public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
// Indexed API methods
|
||||
public int getInnerColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.innerColor : displayData.innerColor;
|
||||
}
|
||||
public void setInnerColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.innerColor = color;
|
||||
}
|
||||
|
||||
public int getOuterColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.outerColor : displayData.outerColor;
|
||||
}
|
||||
public void setOuterColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.outerColor = color;
|
||||
}
|
||||
|
||||
public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); }
|
||||
public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; }
|
||||
public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; }
|
||||
public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; }
|
||||
|
||||
private int clampIndex(int index) {
|
||||
return Math.max(0, Math.min(index, projectileCount - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// GUI FIELD DEFINITIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
// Type tab - projectile count and fire delay
|
||||
protected void addTypeDefinitions(List<FieldDef> defs) {
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.projectileCount", this::getProjectileCount, this::setProjectileCount).range(1, MAX_PROJECTILES),
|
||||
FieldDef.intField("ability.fireDelay", this::getFireDelay, this::setFireDelay)
|
||||
.range(0, 200).visibleWhen(() -> projectileCount > 1)
|
||||
));
|
||||
|
||||
// Type tab - orb properties
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed)
|
||||
@@ -550,64 +172,5 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
|
||||
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive));
|
||||
defs.add(AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects));
|
||||
|
||||
// Visual tab - shared primary colors
|
||||
defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - shared effects
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.visibleWhen(this::hasLightningEffect).range(0.01f, 100f),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Visual tab - per-projectile sections
|
||||
for (int i = 0; i < MAX_PROJECTILES; i++) {
|
||||
final int idx = i;
|
||||
String sectionKey = "ability.section.projectile" + (i + 1);
|
||||
|
||||
defs.add(FieldDef.section(sectionKey).tab("ability.tab.visual")
|
||||
.visibleWhen(() -> idx < projectileCount));
|
||||
|
||||
// Anchor point
|
||||
defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX,
|
||||
v -> projectiles[idx].anchor.anchorOffsetX = v).min(Float.NEGATIVE_INFINITY),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> projectiles[idx].anchor.anchorOffsetY,
|
||||
v -> projectiles[idx].anchor.anchorOffsetY = v).min(Float.NEGATIVE_INFINITY)
|
||||
).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ,
|
||||
v -> projectiles[idx].anchor.anchorOffsetZ = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount).min(Float.NEGATIVE_INFINITY));
|
||||
|
||||
// Color override (only when multiple projectiles)
|
||||
defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", () -> projectiles[idx].innerColor, v -> projectiles[idx].innerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", () -> projectiles[idx].outerColor, v -> projectiles[idx].outerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,18 +67,18 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPlayerCaster(caster)) {
|
||||
// Player: instant hit-scan in look direction
|
||||
executePlayerProjectile(caster, world);
|
||||
executePlayerProjectile(caster, caster.worldObj);
|
||||
} else if (target != null) {
|
||||
// NPC: instant hit-scan on aggro target
|
||||
executeNpcProjectile(caster, target, world);
|
||||
executeNpcProjectile(caster, target, caster.worldObj);
|
||||
}
|
||||
|
||||
signalCompletion();
|
||||
@@ -250,7 +250,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
// Projectile is instant, nothing to do per-tick
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
@@ -30,6 +29,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
private float pushStrength = 1.5f;
|
||||
private float damage = 8.0f;
|
||||
private int maxTargets = 10;
|
||||
private int activeDisplayTicks = 10;
|
||||
|
||||
public AbilityShockwave() {
|
||||
this.typeId = "ability.cnpc.shockwave";
|
||||
@@ -62,14 +62,14 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!isPreview()) {
|
||||
// Shockwave is instant - apply effect immediately after windup
|
||||
|
||||
// Get all entities in radius
|
||||
AxisAlignedBB box = caster.boundingBox.expand(pushRadius, pushRadius / 2, pushRadius);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<EntityLivingBase> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, box);
|
||||
List<EntityLivingBase> entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box);
|
||||
|
||||
int count = 0;
|
||||
for (EntityLivingBase entity : entities) {
|
||||
@@ -112,8 +112,8 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (tick == 10)
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (tick >= activeDisplayTicks)
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
nbt.setFloat("pushStrength", pushStrength);
|
||||
nbt.setFloat("damage", damage);
|
||||
nbt.setInteger("maxTargets", maxTargets);
|
||||
nbt.setInteger("activeDisplayTicks", activeDisplayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,6 +132,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
this.pushStrength = nbt.getFloat("pushStrength");
|
||||
this.damage = nbt.getFloat("damage");
|
||||
this.maxTargets = nbt.getInteger("maxTargets");
|
||||
this.activeDisplayTicks = nbt.hasKey("activeDisplayTicks") ? nbt.getInteger("activeDisplayTicks") : 10;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
@@ -166,6 +168,14 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
this.maxTargets = maxTargets;
|
||||
}
|
||||
|
||||
public int getActiveDisplayTicks() {
|
||||
return activeDisplayTicks;
|
||||
}
|
||||
|
||||
public void setActiveDisplayTicks(int activeDisplayTicks) {
|
||||
this.activeDisplayTicks = activeDisplayTicks;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
@@ -177,6 +187,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
|
||||
FieldDef.floatField("gui.strength", this::getPushStrength, this::setPushStrength)
|
||||
),
|
||||
FieldDef.intField("ability.maxTargets", this::getMaxTargets, this::setMaxTargets),
|
||||
FieldDef.intField("ability.activeDisplayTicks", this::getActiveDisplayTicks, this::setActiveDisplayTicks).range(1, 200),
|
||||
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (isPlayerCaster(caster)) {
|
||||
// Player: update target along look direction during windup
|
||||
if (targetingMode == TargetingMode.AOE_SELF) {
|
||||
@@ -115,7 +115,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
hasLaunched = false;
|
||||
hasLanded = false;
|
||||
airTicks = 0;
|
||||
@@ -286,7 +286,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (!hasLaunched) return;
|
||||
if (hasLanded) return;
|
||||
|
||||
@@ -300,14 +300,14 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
// Check for landing: hit the ground or hit a wall mid-flight
|
||||
if (airTicks > 3) {
|
||||
if (caster.onGround || caster.isCollidedHorizontally) {
|
||||
onLanding(caster, world);
|
||||
onLanding(caster, caster.worldObj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout protection - force landing after max air time
|
||||
if (airTicks >= maxAirTicks) {
|
||||
onLanding(caster, world);
|
||||
onLanding(caster, caster.worldObj);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilitySweeper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import noppes.npcs.api.ability.type.IAbilitySweeper;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -77,16 +77,11 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// Spawn the sweeper entity that handles BOTH visuals AND damage.
|
||||
// NPC: target is the aggro target — lockOnTarget tracks it during sweep.
|
||||
// Player: target is null — sweep rotates around caster's facing direction, lockOnTarget has no effect.
|
||||
activeEntity = new EntityAbilitySweeper(world, caster, target,
|
||||
activeEntity = new EntityAbilitySweeper(caster.worldObj, caster, target,
|
||||
beamLength, beamWidth, beamHeight,
|
||||
colorData,
|
||||
sweepSpeed, numberOfRotations,
|
||||
@@ -97,14 +92,14 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
activeEntity.setupPreview(caster);
|
||||
}
|
||||
|
||||
spawnAbilityEntity(world, activeEntity);
|
||||
spawnAbilityEntity(activeEntity);
|
||||
|
||||
// Ability stays active until entity dies (prevents firing another while projectile is alive)
|
||||
// Movement locking is handled separately by the base class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (activeEntity == null || activeEntity.isDead) {
|
||||
activeEntity = null;
|
||||
|
||||
@@ -97,13 +97,13 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
currentBlink = 0;
|
||||
ticksSinceLastBlink = blinkDelayTicks; // Trigger first blink immediately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
int blinkLimit = mode == TeleportMode.BLINK ? blinkCount : 1;
|
||||
if (currentBlink >= blinkLimit) {
|
||||
signalCompletion();
|
||||
@@ -113,7 +113,7 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
|
||||
ticksSinceLastBlink++;
|
||||
|
||||
if (ticksSinceLastBlink >= blinkDelayTicks) {
|
||||
performBlink(caster, target, world);
|
||||
performBlink(caster, target, caster.worldObj);
|
||||
currentBlink++;
|
||||
ticksSinceLastBlink = 0;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
|
||||
import noppes.npcs.api.ability.type.IAbilityTrap;
|
||||
|
||||
@@ -51,12 +51,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
activeEntities.clear();
|
||||
|
||||
// Use pre-calculated positions from telegraph phase, or generate new ones
|
||||
@@ -78,7 +73,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
for (int i = 0; i < zoneCount; i++) {
|
||||
double[] pos = positions.get(i);
|
||||
|
||||
EntityAbilityZone entity = EntityAbilityZone.createTrap(world, caster,
|
||||
EntityAbilityZone entity = EntityAbilityZone.createTrap(caster.worldObj, caster,
|
||||
pos[0], caster.posY, pos[1],
|
||||
zoneShape,
|
||||
triggerRadius, armTime, maxTriggers, triggerCooldown,
|
||||
@@ -94,7 +89,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
|
||||
entity.setupPreview(caster);
|
||||
}
|
||||
|
||||
spawnAbilityEntity(world, entity);
|
||||
spawnAbilityEntity(entity);
|
||||
activeEntities.add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
getPulledEntities().clear();
|
||||
pullComplete = false;
|
||||
ticksSincePullDamage = 0;
|
||||
@@ -88,7 +88,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
if (aoe) {
|
||||
AxisAlignedBB box = caster.boundingBox.expand(pullRadius, pullRadius / 2, pullRadius);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<EntityLivingBase> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, box);
|
||||
List<EntityLivingBase> entities = caster.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box);
|
||||
|
||||
int count = 0;
|
||||
for (EntityLivingBase entity : entities) {
|
||||
@@ -116,7 +116,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (isPreview()) {
|
||||
// No entities to pull in preview, just run animation for a duration
|
||||
if (tick >= 60) signalCompletion();
|
||||
@@ -136,7 +136,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
ticksSincePullDamage++;
|
||||
|
||||
for (UUID uuid : new HashSet<>(getPulledEntities())) {
|
||||
EntityLivingBase entity = findEntity(caster, world, uuid);
|
||||
EntityLivingBase entity = findEntity(caster, caster.worldObj, uuid);
|
||||
if (entity == null || entity.isDead) {
|
||||
getPulledEntities().remove(uuid);
|
||||
continue;
|
||||
@@ -149,7 +149,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
|
||||
if (dist <= 1.5f) {
|
||||
getPulledEntities().remove(uuid);
|
||||
onTargetArrived(caster, entity, world);
|
||||
onTargetArrived(caster, entity, caster.worldObj);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
|
||||
double nextZ = dz * factor;
|
||||
|
||||
AxisAlignedBB nextBox = entity.boundingBox.copy().offset(nextX, nextY, nextZ);
|
||||
if (!world.getCollidingBoundingBoxes(entity, nextBox).isEmpty()) {
|
||||
if (!caster.worldObj.getCollidingBoundingBoxes(entity, nextBox).isEmpty()) {
|
||||
getPulledEntities().remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.constants.EnumPotionType;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
|
||||
import noppes.npcs.client.gui.SubGuiZonePresetSelector;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
@@ -151,7 +151,7 @@ public abstract class AbilityZone extends Ability {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
// Remove dead entities
|
||||
Iterator<EntityAbilityZone> it = activeEntities.iterator();
|
||||
while (it.hasNext()) {
|
||||
|
||||
+590
@@ -0,0 +1,590 @@
|
||||
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.ability.data.*;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityProjectile;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base for energy projectile abilities (Orb, Disc, Beam, LaserShot).
|
||||
* Handles shared lifecycle (windup → execute → active → completion), multi-projectile
|
||||
* support with staggered firing, telegraph creation, NBT persistence, and GUI definitions.
|
||||
*
|
||||
* Subclasses provide entity creation/firing/charging logic and type-specific fields.
|
||||
*
|
||||
* @param <E> The entity type this ability spawns
|
||||
*/
|
||||
public abstract class AbstractEnergyProjectileAbility<E extends EntityAbilityProjectile>
|
||||
extends Ability implements IAbilityEnergyProjectile {
|
||||
|
||||
protected static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Multi-projectile support
|
||||
protected int projectileCount = 1;
|
||||
protected int fireDelay = 0;
|
||||
|
||||
// Shared visual data
|
||||
protected EnergyDisplayData displayData;
|
||||
protected EnergyLightningData lightningData;
|
||||
protected EnergyTrajectoryData trajectoryData;
|
||||
|
||||
// Shared combat/movement data
|
||||
protected final EnergyCombatData combatData;
|
||||
protected final EnergyHomingData homingData;
|
||||
protected final EnergyLifespanData lifespanData;
|
||||
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
protected ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
protected transient E[] entities;
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
protected AbstractEnergyProjectileAbility(
|
||||
EnergyDisplayData displayData,
|
||||
EnergyCombatData combatData,
|
||||
EnergyHomingData homingData,
|
||||
EnergyLifespanData lifespanData
|
||||
) {
|
||||
this.displayData = displayData;
|
||||
this.combatData = combatData;
|
||||
this.homingData = homingData;
|
||||
this.lifespanData = lifespanData;
|
||||
this.lightningData = new EnergyLightningData();
|
||||
this.trajectoryData = new EnergyTrajectoryData();
|
||||
initProjectiles(1);
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT METHODS ====================
|
||||
|
||||
/**
|
||||
* Create a single entity for the given projectile index.
|
||||
* Called during windup (tick 1) and burst refire (onExecute when entities are null).
|
||||
*/
|
||||
protected abstract E createEntity(EntityLivingBase caster, EntityLivingBase target,
|
||||
Vec3 spawnPos, EnergyDisplayData resolved, int index);
|
||||
|
||||
/**
|
||||
* Fire an entity (start moving toward target). Entity is guaranteed non-null and alive.
|
||||
*/
|
||||
protected abstract void fireEntity(E entity, EntityLivingBase target);
|
||||
|
||||
/**
|
||||
* Setup entity for charging animation during windup (non-preview).
|
||||
*/
|
||||
protected abstract void setupEntityCharging(E entity, ProjectileData projData, int index);
|
||||
|
||||
/**
|
||||
* Setup entity for preview mode during windup.
|
||||
*/
|
||||
protected abstract void setupEntityPreview(E entity, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index);
|
||||
|
||||
/**
|
||||
* Create typed array (Java generics can't create generic arrays).
|
||||
*/
|
||||
protected abstract E[] createEntityArray(int size);
|
||||
|
||||
/**
|
||||
* Get the telegraph radius for this projectile type.
|
||||
*/
|
||||
protected abstract float getProjectileTelegraphRadius();
|
||||
|
||||
/**
|
||||
* Add type-specific GUI field definitions (Type tab).
|
||||
* Called before shared visual tab definitions.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
protected abstract void addTypeDefinitions(List<FieldDef> defs);
|
||||
|
||||
/**
|
||||
* Write type-specific fields to NBT (e.g., orbSize, discRadius).
|
||||
* Shared data classes are written by the base class.
|
||||
*/
|
||||
protected abstract void writeTypeSpecificNBT(NBTTagCompound nbt);
|
||||
|
||||
/**
|
||||
* Read type-specific fields from NBT.
|
||||
* Shared data classes are read by the base class.
|
||||
*/
|
||||
protected abstract void readTypeSpecificNBT(NBTTagCompound nbt);
|
||||
|
||||
// ==================== OVERRIDABLE HOOKS ====================
|
||||
|
||||
/**
|
||||
* Get the default anchor point for a projectile index.
|
||||
* Override for types with different anchor defaults.
|
||||
*/
|
||||
protected AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the spawn position for a projectile. Override for types that need
|
||||
* an offset distance (e.g., Beam uses offsetDist=1.0).
|
||||
*/
|
||||
protected Vec3 getSpawnPosition(EntityLivingBase caster, int index) {
|
||||
return AnchorPointHelper.calculateAnchorPosition(caster, projectiles[index].anchor);
|
||||
}
|
||||
|
||||
// ==================== PROJECTILE MANAGEMENT ====================
|
||||
|
||||
protected void initProjectiles(int count) {
|
||||
this.projectileCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
this.projectiles = new ProjectileData[projectileCount];
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
projectiles[i] = new ProjectileData(getDefaultAnchor(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void setProjectileCount(int count) {
|
||||
int newCount = Math.max(1, Math.min(count, MAX_PROJECTILES));
|
||||
if (newCount == projectileCount) return;
|
||||
|
||||
ProjectileData[] oldProjectiles = projectiles;
|
||||
initProjectiles(newCount);
|
||||
|
||||
if (oldProjectiles != null) {
|
||||
int copyCount = Math.min(oldProjectiles.length, newCount);
|
||||
for (int i = 0; i < copyCount; i++) {
|
||||
if (oldProjectiles[i] != null) projectiles[i] = oldProjectiles[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getProjectileCount() { return projectileCount; }
|
||||
|
||||
public int getFireDelay() { return fireDelay; }
|
||||
public void setFireDelay(int delay) { this.fireDelay = Math.max(0, delay); }
|
||||
|
||||
/** @deprecated Use {@link #getFireDelay()} */
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
/** @deprecated Use {@link #setFireDelay(int)} */
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
|
||||
protected int clampIndex(int index) {
|
||||
return Math.max(0, Math.min(index, projectileCount - 1));
|
||||
}
|
||||
|
||||
// ==================== ENTITY CREATION ====================
|
||||
|
||||
/**
|
||||
* Create all entities for this ability.
|
||||
* Sets effects, source ability, and sibling links.
|
||||
*/
|
||||
protected E[] createAllEntities(EntityLivingBase caster, EntityLivingBase target) {
|
||||
E[] newEntities = createEntityArray(projectileCount);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
Vec3 spawnPos = getSpawnPosition(caster, i);
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
newEntities[i] = createEntity(caster, target, spawnPos, resolved, i);
|
||||
newEntities[i].setEffects(this.effects);
|
||||
newEntities[i].setSourceAbility(this);
|
||||
}
|
||||
return newEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire an entity if it's valid.
|
||||
*/
|
||||
protected void fireEntitySafe(E entity, EntityLivingBase target) {
|
||||
if (entity != null && !entity.isDead) {
|
||||
fireEntity(entity, target);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LIFECYCLE ====================
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (caster.worldObj.isRemote && !isPreview()) return;
|
||||
|
||||
if (tick == 1) {
|
||||
entities = createAllEntities(caster, target);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData);
|
||||
if (isPreview()) {
|
||||
setupEntityPreview(entities[i], caster, resolved, projectiles[i], i);
|
||||
} else {
|
||||
setupEntityCharging(entities[i], projectiles[i], i);
|
||||
}
|
||||
spawnAbilityEntity(entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
// Burst refire: entities not created during windup (it was skipped)
|
||||
if (entities == null) {
|
||||
entities = createAllEntities(caster, target);
|
||||
for (E entity : entities) {
|
||||
spawnAbilityEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Fire first projectile immediately
|
||||
fireEntitySafe(entities[0], target);
|
||||
|
||||
// Fire remaining projectiles if no delay
|
||||
if (fireDelay <= 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
fireEntitySafe(entities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (entities == null) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire staggered projectiles
|
||||
if (fireDelay > 0) {
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (tick == fireDelay * i) {
|
||||
fireEntitySafe(entities[i], target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all entities are dead
|
||||
boolean allDead = true;
|
||||
for (E entity : entities) {
|
||||
if (entity != null && !entity.isDead) {
|
||||
allDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDead) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (entities != null) {
|
||||
for (int i = 0; i < entities.length; i++) {
|
||||
if (entities[i] != null && !entities[i].isDead) {
|
||||
entities[i].setDead();
|
||||
}
|
||||
entities[i] = null;
|
||||
}
|
||||
}
|
||||
entities = null;
|
||||
}
|
||||
|
||||
// ==================== TELEGRAPH ====================
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster) || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Telegraph telegraph = Telegraph.circle(getProjectileTelegraphRadius());
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
telegraph.setWarningColor(activeColor);
|
||||
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
|
||||
telegraph.setHeightOffset(telegraphHeightOffset);
|
||||
|
||||
TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(target.getEntityId());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTelegraphRadius() {
|
||||
return getProjectileTelegraphRadius();
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
public final void writeTypeNBT(NBTTagCompound nbt) {
|
||||
// Type-specific fields
|
||||
writeTypeSpecificNBT(nbt);
|
||||
|
||||
// Shared fields
|
||||
nbt.setInteger("projectileCount", projectileCount);
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared data classes
|
||||
displayData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
NBTTagCompound projNbt = new NBTTagCompound();
|
||||
projectiles[i].writeNBT(projNbt);
|
||||
nbt.setTag("Projectile_" + i, projNbt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readTypeNBT(NBTTagCompound nbt) {
|
||||
// Type-specific fields
|
||||
readTypeSpecificNBT(nbt);
|
||||
|
||||
// Shared fields
|
||||
int count = nbt.getInteger("projectileCount");
|
||||
initProjectiles(count);
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
|
||||
// Shared data classes
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PREVIEW ====================
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
// ==================== API GETTERS & SETTERS ====================
|
||||
|
||||
// Combat data
|
||||
@Override public float getDamage() { return combatData.damage; }
|
||||
@Override public void setDamage(float damage) { combatData.damage = damage; }
|
||||
|
||||
@Override public float getKnockback() { return combatData.knockback; }
|
||||
@Override public void setKnockback(float knockback) { combatData.knockback = knockback; }
|
||||
|
||||
@Override public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
@Override public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; }
|
||||
|
||||
@Override public boolean isExplosive() { return combatData.explosive; }
|
||||
@Override public void setExplosive(boolean explosive) { combatData.explosive = explosive; }
|
||||
|
||||
@Override public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
@Override public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; }
|
||||
|
||||
@Override public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
@Override public void setExplosionDamageFalloff(float falloff) { combatData.explosionDamageFalloff = falloff; }
|
||||
|
||||
// Lifespan data
|
||||
@Override public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
@Override public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; }
|
||||
|
||||
@Override public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
@Override public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; }
|
||||
|
||||
// Homing data
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean homing) { homingData.homing = homing; }
|
||||
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float strength) { homingData.homingStrength = strength; }
|
||||
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float range) { homingData.homingRange = range; }
|
||||
|
||||
// Display data - primary colors
|
||||
@Override public int getInnerColor() { return displayData.innerColor; }
|
||||
@Override public void setInnerColor(int color) { displayData.innerColor = color; }
|
||||
|
||||
@Override public int getOuterColor() { return displayData.outerColor; }
|
||||
@Override public void setOuterColor(int color) { displayData.outerColor = color; }
|
||||
|
||||
@Override public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
@Override public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; }
|
||||
|
||||
@Override public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
@Override public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; }
|
||||
|
||||
@Override public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
@Override public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; }
|
||||
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; }
|
||||
|
||||
// Lightning data
|
||||
@Override public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
@Override public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; }
|
||||
|
||||
@Override public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
@Override public void setLightningDensity(float density) { lightningData.lightningDensity = density; }
|
||||
|
||||
@Override public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
@Override public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; }
|
||||
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; }
|
||||
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint point) { projectiles[0].anchor.anchorPoint = point; }
|
||||
|
||||
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
|
||||
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
|
||||
|
||||
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
|
||||
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
|
||||
|
||||
public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; }
|
||||
|
||||
@Override public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
@Override public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
// Indexed API methods
|
||||
public int getInnerColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.innerColor : displayData.innerColor;
|
||||
}
|
||||
public void setInnerColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.innerColor = color;
|
||||
}
|
||||
|
||||
public int getOuterColor(int index) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
return p.colorOverride ? p.outerColor : displayData.outerColor;
|
||||
}
|
||||
public void setOuterColor(int index, int color) {
|
||||
ProjectileData p = projectiles[clampIndex(index)];
|
||||
p.colorOverride = true;
|
||||
p.outerColor = color;
|
||||
}
|
||||
|
||||
public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); }
|
||||
public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; }
|
||||
public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; }
|
||||
public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; }
|
||||
|
||||
// ==================== GUI FIELD DEFINITIONS ====================
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public final void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
// Type-specific fields first
|
||||
addTypeDefinitions(defs);
|
||||
|
||||
// Shared visual tab - primary colors
|
||||
defs.add(FieldDef.section("ability.section.colors").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.outerEnabled", this::isOuterColorEnabled, this::setOuterColorEnabled)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
|
||||
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
|
||||
.visibleWhen(this::isOuterColorEnabled),
|
||||
FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
|
||||
.range(0, 1).visibleWhen(this::isOuterColorEnabled)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Shared visual tab - effects
|
||||
defs.add(FieldDef.section("ability.section.effects").tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect)
|
||||
.tab("ability.tab.visual"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
|
||||
.visibleWhen(this::hasLightningEffect).range(0.01f, 100f),
|
||||
FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
|
||||
.range(0.1f, 100f).visibleWhen(this::hasLightningEffect)
|
||||
).tab("ability.tab.visual"));
|
||||
|
||||
// Shared visual tab - per-projectile sections
|
||||
for (int i = 0; i < MAX_PROJECTILES; i++) {
|
||||
final int idx = i;
|
||||
String sectionKey = "ability.section.projectile" + (i + 1);
|
||||
|
||||
defs.add(FieldDef.section(sectionKey).tab("ability.tab.visual")
|
||||
.visibleWhen(() -> idx < projectileCount));
|
||||
|
||||
defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX, v -> projectiles[idx].anchor.anchorOffsetX = v),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> projectiles[idx].anchor.anchorOffsetY, v -> projectiles[idx].anchor.anchorOffsetY = v)
|
||||
).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ, v -> projectiles[idx].anchor.anchorOffsetZ = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
|
||||
|
||||
// Color override (only when multiple projectiles)
|
||||
defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1));
|
||||
defs.add(FieldDef.colorSubGui("ability.innerColor", () -> projectiles[idx].innerColor, v -> projectiles[idx].innerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
defs.add(FieldDef.colorSubGui("ability.outerColor", () -> projectiles[idx].outerColor, v -> projectiles[idx].outerColor = v)
|
||||
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount && projectileCount > 1 && projectiles[idx].colorOverride));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
// Whether to render tail orb (only when anchored)
|
||||
private boolean renderTailOrb = true;
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
// Charging state (beam-specific)
|
||||
private float chargeOffsetDistance = 1.0f;
|
||||
|
||||
// Trail fading for non-anchored beams (comet effect)
|
||||
@@ -56,9 +53,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
private int trailFadeTime = 20; // Ticks for trail to fully fade
|
||||
private List<Integer> trailPointAges = new ArrayList<>();
|
||||
|
||||
// Data watcher index for charging state (synced to clients)
|
||||
private static final int DW_CHARGING = 20;
|
||||
|
||||
// Debug logging
|
||||
private static final boolean DEBUG_LOGGING = false;
|
||||
|
||||
@@ -66,34 +60,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
super.entityInit();
|
||||
// Register data watcher for charging state
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if beam is in charging state (synced via data watcher).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) {
|
||||
return this.charging;
|
||||
}
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
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 using data classes.
|
||||
* @param anchoredMode If true, origin follows owner and tail orb is rendered.
|
||||
@@ -137,23 +103,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
}
|
||||
|
||||
// Calculate initial velocity toward target
|
||||
if (target != null) {
|
||||
double dx = target.posX - x;
|
||||
double dy = (target.posY + target.getEyeHeight()) - y;
|
||||
double dz = target.posZ - z;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
this.motionX = (dx / len) * getSpeed();
|
||||
this.motionY = (dy / len) * getSpeed();
|
||||
this.motionZ = (dz / len) * getSpeed();
|
||||
}
|
||||
} else {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
this.motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
|
||||
this.motionY = -Math.sin(pitch) * getSpeed();
|
||||
this.motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
|
||||
}
|
||||
calculateInitialVelocity(owner, target, x, y, z);
|
||||
}
|
||||
|
||||
public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) {
|
||||
@@ -218,8 +168,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
* Start preview firing (simulates firing toward a point in front of NPC).
|
||||
*/
|
||||
public void startPreviewFiring() {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Origin (tail) stays at the charged position
|
||||
@@ -270,8 +218,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
* For non-anchored beams: origin fixed at charged position, head starts there
|
||||
*/
|
||||
public void startFiring(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// For player casters, snap to look vector for crosshair-aligned launch
|
||||
@@ -433,8 +379,10 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
* Beam-specific: positions based on chargeOffsetDistance and updates startX/Y/Z.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
@Override
|
||||
protected void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
@@ -548,7 +496,8 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
addTrailPoint();
|
||||
}
|
||||
|
||||
private void updateHoming() {
|
||||
@Override
|
||||
protected void updateHoming() {
|
||||
if (!isHoming()) return;
|
||||
|
||||
Entity target = getTargetEntity();
|
||||
@@ -704,24 +653,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
return attachedToOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether trail should fade (comet effect for non-anchored beams).
|
||||
*/
|
||||
@@ -757,12 +688,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
this.prevHeadOffsetZ = this.headOffsetZ;
|
||||
this.attachedToOwner = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner");
|
||||
this.renderTailOrb = !nbt.hasKey("RenderTailOrb") || nbt.getBoolean("RenderTailOrb");
|
||||
// Read charging state and sync to data watcher
|
||||
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;
|
||||
readChargingNBT(nbt);
|
||||
this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f;
|
||||
this.fadeTrail = nbt.hasKey("FadeTrail") && nbt.getBoolean("FadeTrail");
|
||||
this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20;
|
||||
@@ -792,15 +718,11 @@ public class EntityAbilityBeam extends EntityAbilityProjectile {
|
||||
nbt.setDouble("HeadOffsetZ", headOffsetZ);
|
||||
nbt.setBoolean("AttachedToOwner", attachedToOwner);
|
||||
nbt.setBoolean("RenderTailOrb", renderTailOrb);
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
writeChargingNBT(nbt);
|
||||
nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance);
|
||||
nbt.setBoolean("FadeTrail", fadeTrail);
|
||||
nbt.setInteger("TrailFadeTime", trailFadeTime);
|
||||
|
||||
this.anchorData.writeNBT(nbt);
|
||||
|
||||
// Write trail points
|
||||
NBTTagList trailList = new NBTTagList();
|
||||
for (Vec3 point : trailPoints) {
|
||||
|
||||
@@ -33,48 +33,14 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
private float discThickness = 0.2f; // Height of disc
|
||||
private boolean vertical = false; // false = horizontal (flat), true = vertical (thin edge forward)
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
// Charging state (disc-specific target sizes)
|
||||
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).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) {
|
||||
return this.charging;
|
||||
}
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
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 using data classes.
|
||||
*/
|
||||
@@ -99,23 +65,7 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
this.discThickness = discThickness;
|
||||
|
||||
// Calculate initial velocity toward target
|
||||
if (target != null) {
|
||||
double dx = target.posX - x;
|
||||
double dy = (target.posY + target.getEyeHeight()) - y;
|
||||
double dz = target.posZ - z;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
this.motionX = (dx / len) * getSpeed();
|
||||
this.motionY = (dy / len) * getSpeed();
|
||||
this.motionZ = (dz / len) * getSpeed();
|
||||
}
|
||||
} else {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
this.motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
|
||||
this.motionY = -Math.sin(pitch) * getSpeed();
|
||||
this.motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
|
||||
}
|
||||
calculateInitialVelocity(owner, target, x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,21 +74,12 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
* Position follows the owner based on anchor point.
|
||||
*/
|
||||
public void setupCharging(EnergyAnchorData anchor, int chargeDuration, boolean vertical) {
|
||||
setCharging(true);
|
||||
this.chargeDuration = chargeDuration;
|
||||
this.chargeTick = 0;
|
||||
this.anchorData = anchor;
|
||||
this.vertical = vertical;
|
||||
this.targetDiscRadius = this.discRadius;
|
||||
this.targetDiscThickness = this.discThickness;
|
||||
super.setupCharging(anchor, chargeDuration);
|
||||
this.vertical = vertical;
|
||||
this.discRadius = 0.01f;
|
||||
this.discThickness = 0.01f;
|
||||
this.size = 0.01f;
|
||||
this.renderCurrentSize = 0.01f;
|
||||
this.prevRenderSize = 0.01f;
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,8 +129,6 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
* Start preview firing (simulates firing toward a point in front of NPC).
|
||||
*/
|
||||
public void startPreviewFiring() {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Update start position to current position
|
||||
@@ -222,8 +161,6 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
* Called by ability when windup ends.
|
||||
*/
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// For player casters, snap to look vector for crosshair-aligned launch
|
||||
@@ -359,43 +296,6 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
this.posZ += motionZ;
|
||||
}
|
||||
|
||||
private void updateHoming() {
|
||||
if (!isHoming()) return;
|
||||
|
||||
Entity target = getTargetEntity();
|
||||
if (target == null || !target.isEntityAlive()) return;
|
||||
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.getEyeHeight()) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= getHomingRange() && dist > 0) {
|
||||
// Calculate effective homing strength - increases when closer to commit to target
|
||||
float effectiveStrength = getHomingStrength();
|
||||
if (dist < getHomingRange() * 0.3) {
|
||||
effectiveStrength = Math.min(1.0f, getHomingStrength() * 2.5f);
|
||||
} else if (dist < getHomingRange() * 0.6) {
|
||||
effectiveStrength = Math.min(0.8f, getHomingStrength() * 1.5f);
|
||||
}
|
||||
|
||||
double desiredVX = (dx / dist) * getSpeed();
|
||||
double desiredVY = (dy / dist) * getSpeed();
|
||||
double desiredVZ = (dz / dist) * getSpeed();
|
||||
|
||||
motionX += (desiredVX - motionX) * effectiveStrength;
|
||||
motionY += (desiredVY - motionY) * effectiveStrength;
|
||||
motionZ += (desiredVZ - motionZ) * effectiveStrength;
|
||||
|
||||
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
|
||||
if (vLen > 0) {
|
||||
motionX = (motionX / vLen) * getSpeed();
|
||||
motionY = (motionY / vLen) * getSpeed();
|
||||
motionZ = (motionZ / vLen) * getSpeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateReturnToOwner() {
|
||||
Entity owner = getOwnerEntity();
|
||||
|
||||
@@ -485,8 +385,10 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
* Overrides base to grow disc-specific radius/thickness instead of just size.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
@Override
|
||||
protected void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
@@ -511,24 +413,6 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
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 getDiscRadius() {
|
||||
@@ -579,12 +463,8 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
this.discThickness = nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f;
|
||||
this.vertical = nbt.hasKey("Vertical") ? nbt.getBoolean("Vertical") : false;
|
||||
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;
|
||||
// Charging state (common fields handled by base)
|
||||
readChargingNBT(nbt);
|
||||
this.targetDiscRadius = nbt.hasKey("TargetDiscRadius") ? nbt.getFloat("TargetDiscRadius") : this.discRadius;
|
||||
this.targetDiscThickness = nbt.hasKey("TargetDiscThickness") ? nbt.getFloat("TargetDiscThickness") : this.discThickness;
|
||||
}
|
||||
@@ -597,10 +477,8 @@ public class EntityAbilityDisc extends EntityAbilityProjectile {
|
||||
nbt.setFloat("DiscThickness", discThickness);
|
||||
nbt.setBoolean("Vertical", vertical);
|
||||
nbt.setBoolean("Returning", returning);
|
||||
// Charging state
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
// Charging state (common fields handled by base)
|
||||
writeChargingNBT(nbt);
|
||||
nbt.setFloat("TargetDiscRadius", targetDiscRadius);
|
||||
nbt.setFloat("TargetDiscThickness", targetDiscThickness);
|
||||
}
|
||||
|
||||
@@ -44,47 +44,10 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
// End point for rendering
|
||||
private double endX, endY, endZ;
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
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 EntityAbilityLaser(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).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) {
|
||||
return this.charging;
|
||||
}
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
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 using data classes.
|
||||
*/
|
||||
@@ -131,25 +94,6 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
this.endZ = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup this 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 void setupCharging(EnergyAnchorData anchor, int chargeDuration) {
|
||||
setCharging(true);
|
||||
this.chargeDuration = chargeDuration;
|
||||
this.chargeTick = 0;
|
||||
this.anchorData = anchor;
|
||||
this.targetSize = this.size;
|
||||
this.size = 0.01f;
|
||||
this.renderCurrentSize = 0.01f;
|
||||
this.prevRenderSize = 0.01f;
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateRotation() {
|
||||
// Laser doesn't rotate
|
||||
@@ -252,8 +196,6 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
}
|
||||
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Initialize from current anchor position
|
||||
@@ -322,44 +264,6 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
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, offset downward by half size to center
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void checkEntityCollisionAlongLine() {
|
||||
// Expand search AABB by entity sizes to ensure we find all potential targets
|
||||
double expand = Math.max(laserWidth, 1.0);
|
||||
@@ -520,13 +424,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
this.endY = nbt.getDouble("EndY");
|
||||
this.endZ = nbt.getDouble("EndZ");
|
||||
|
||||
// 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.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size;
|
||||
readChargingNBT(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -543,11 +441,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
|
||||
nbt.setDouble("EndY", endY);
|
||||
nbt.setDouble("EndZ", endZ);
|
||||
|
||||
// Charging state
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setFloat("TargetSize", targetSize);
|
||||
writeChargingNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,47 +20,10 @@ import java.util.List;
|
||||
*/
|
||||
public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
|
||||
// Charging state (during windup)
|
||||
private boolean charging = false;
|
||||
private int chargeDuration = 40;
|
||||
private int chargeTick = 0;
|
||||
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).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) {
|
||||
return this.charging;
|
||||
}
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
private void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full constructor using data classes for grouped parameters.
|
||||
*/
|
||||
@@ -77,43 +40,7 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
this.homingData = homing;
|
||||
|
||||
// Calculate initial velocity toward target
|
||||
if (target != null) {
|
||||
double dx = target.posX - x;
|
||||
double dy = (target.posY + target.getEyeHeight()) - y;
|
||||
double dz = target.posZ - z;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
this.motionX = (dx / len) * homing.speed;
|
||||
this.motionY = (dy / len) * homing.speed;
|
||||
this.motionZ = (dz / len) * homing.speed;
|
||||
}
|
||||
} else {
|
||||
// Fire in NPC's facing direction
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
this.motionX = -Math.sin(yaw) * Math.cos(pitch) * homing.speed;
|
||||
this.motionY = -Math.sin(pitch) * homing.speed;
|
||||
this.motionZ = Math.cos(yaw) * Math.cos(pitch) * homing.speed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup this 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 void setupCharging(EnergyAnchorData anchor, int chargeDuration) {
|
||||
setCharging(true);
|
||||
this.chargeDuration = chargeDuration;
|
||||
this.chargeTick = 0;
|
||||
this.anchorData = anchor;
|
||||
this.targetSize = this.size;
|
||||
this.size = 0.01f;
|
||||
this.renderCurrentSize = 0.01f;
|
||||
this.prevRenderSize = 0.01f;
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
calculateInitialVelocity(owner, target, x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,8 +88,6 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
* Start preview firing (simulates firing toward a point in front of NPC).
|
||||
*/
|
||||
public void startPreviewFiring() {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// Update start position to current position
|
||||
@@ -196,8 +121,6 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
* Called by ability when windup ends.
|
||||
*/
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
if (!isCharging()) return;
|
||||
|
||||
setCharging(false);
|
||||
|
||||
// For player casters, snap to look vector for crosshair-aligned launch
|
||||
@@ -302,47 +225,6 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateHoming() {
|
||||
if (!isHoming()) return;
|
||||
|
||||
Entity target = getTargetEntity();
|
||||
if (target == null || !target.isEntityAlive()) return;
|
||||
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.getEyeHeight()) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= getHomingRange() && dist > 0) {
|
||||
// Calculate effective homing strength - increases when closer to commit to target
|
||||
// This prevents orbiting behavior at close range
|
||||
float effectiveStrength = getHomingStrength();
|
||||
if (dist < getHomingRange() * 0.3) {
|
||||
// Within 30% of homing range, dramatically increase strength to commit
|
||||
effectiveStrength = Math.min(1.0f, getHomingStrength() * 2.5f);
|
||||
} else if (dist < getHomingRange() * 0.6) {
|
||||
// Within 60% of homing range, moderately increase strength
|
||||
effectiveStrength = Math.min(0.8f, getHomingStrength() * 1.5f);
|
||||
}
|
||||
|
||||
double desiredVX = (dx / dist) * getSpeed();
|
||||
double desiredVY = (dy / dist) * getSpeed();
|
||||
double desiredVZ = (dz / dist) * getSpeed();
|
||||
|
||||
motionX += (desiredVX - motionX) * effectiveStrength;
|
||||
motionY += (desiredVY - motionY) * effectiveStrength;
|
||||
motionZ += (desiredVZ - motionZ) * effectiveStrength;
|
||||
|
||||
// Normalize to maintain speed
|
||||
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
|
||||
if (vLen > 0) {
|
||||
motionX = (motionX / vLen) * getSpeed();
|
||||
motionY = (motionY / vLen) * getSpeed();
|
||||
motionZ = (motionZ / vLen) * getSpeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBlockCollision() {
|
||||
Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ);
|
||||
Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ);
|
||||
@@ -387,47 +269,6 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - follow owner based on anchor point.
|
||||
*/
|
||||
private void updateCharging() {
|
||||
chargeTick++;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
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, offset downward by half size to center
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
|
||||
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 ====================
|
||||
|
||||
// Legacy getter for renderer compatibility
|
||||
@@ -439,22 +280,11 @@ public class EntityAbilityOrb extends EntityAbilityProjectile {
|
||||
|
||||
@Override
|
||||
protected void readProjectileNBT(NBTTagCompound nbt) {
|
||||
// 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.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size;
|
||||
readChargingNBT(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeProjectileNBT(NBTTagCompound nbt) {
|
||||
// Charging state
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
anchorData.writeNBT(nbt);
|
||||
nbt.setFloat("TargetSize", targetSize);
|
||||
writeChargingNBT(nbt);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.IAbilityDamageHandler;
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -74,7 +75,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
protected double startX, startY, startZ;
|
||||
protected int ownerEntityId = -1;
|
||||
protected int targetEntityId = -1;
|
||||
protected int siblingEntityId = -1;
|
||||
|
||||
// ==================== STATE ====================
|
||||
protected boolean hasHit = false;
|
||||
@@ -84,6 +84,13 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
/** The ability that spawned this projectile. Transient, not saved to NBT. */
|
||||
protected transient Ability sourceAbility = null;
|
||||
|
||||
// ==================== CHARGING STATE ====================
|
||||
protected boolean charging = false;
|
||||
protected int chargeDuration = 40;
|
||||
protected int chargeTick = 0;
|
||||
protected float targetSize = 1.0f;
|
||||
protected static final int DW_CHARGING = 20;
|
||||
|
||||
// ==================== ROTATION INTERPOLATION ====================
|
||||
public float prevRotationValX, prevRotationValY, prevRotationValZ;
|
||||
public float rotationValX, rotationValY, rotationValZ;
|
||||
@@ -108,7 +115,7 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
/**
|
||||
* Initialize common properties using data classes. Call from subclass constructors.
|
||||
*/
|
||||
protected void initProjectile(EntityLivingBase owner, EntityLivingBase target, EntityAbilityProjectile sibling,
|
||||
protected void initProjectile(EntityLivingBase owner, EntityLivingBase target,
|
||||
double x, double y, double z, float size,
|
||||
EnergyDisplayData display, EnergyCombatData combat,
|
||||
EnergyLightningData lightning, EnergyLifespanData lifespan,
|
||||
@@ -120,7 +127,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
|
||||
this.ownerEntityId = owner.getEntityId();
|
||||
this.targetEntityId = target != null ? target.getEntityId() : -1;
|
||||
this.siblingEntityId = sibling != null ? sibling.getEntityId() : -1;
|
||||
|
||||
// Visual
|
||||
this.size = size;
|
||||
@@ -136,18 +142,9 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
this.prevRenderSize = size;
|
||||
}
|
||||
|
||||
// Initialize projectile without a sibling
|
||||
protected void initProjectile(EntityLivingBase owner, EntityLivingBase target,
|
||||
double x, double y, double z, float size,
|
||||
EnergyDisplayData display, EnergyCombatData combat,
|
||||
EnergyLightningData lightning, EnergyLifespanData lifespan,
|
||||
EnergyTrajectoryData trajectory) {
|
||||
initProjectile(owner, target, null, x, y, z, size, display, combat, lightning, lifespan, trajectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
// No DataWatcher needed - using IEntityAdditionalSpawnData with NBT
|
||||
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -455,6 +452,170 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
prevPosZ = newZ;
|
||||
}
|
||||
|
||||
// ==================== CHARGING METHODS ====================
|
||||
|
||||
/**
|
||||
* Check if entity is in charging state (synced via data watcher).
|
||||
* In preview mode, uses local field since data watcher isn't synced.
|
||||
*/
|
||||
public boolean isCharging() {
|
||||
if (previewMode) return this.charging;
|
||||
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging state (server only, synced to clients via data watcher).
|
||||
*/
|
||||
protected void setCharging(boolean value) {
|
||||
this.charging = value;
|
||||
if (!worldObj.isRemote) {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
}
|
||||
|
||||
public float getInterpolatedChargeProgress(float partialTicks) {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
|
||||
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
return prevProgress + (currProgress - prevProgress) * partialTicks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup this entity in charging mode (for windup phase).
|
||||
* Grows from 0 to current size over chargeDuration ticks.
|
||||
* Subclasses with different charging behavior should override or use their own method.
|
||||
*/
|
||||
public void setupCharging(EnergyAnchorData anchor, int chargeDuration) {
|
||||
setCharging(true);
|
||||
this.chargeDuration = chargeDuration;
|
||||
this.chargeTick = 0;
|
||||
this.anchorData = anchor;
|
||||
this.targetSize = this.size;
|
||||
this.size = 0.01f;
|
||||
this.renderCurrentSize = 0.01f;
|
||||
this.prevRenderSize = 0.01f;
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update during charging state - grow size and follow anchor.
|
||||
* Default implementation grows {@code size} from 0 to {@code targetSize}.
|
||||
* Override in subclasses for type-specific charging (Disc, Beam).
|
||||
*/
|
||||
protected void updateCharging() {
|
||||
chargeTick++;
|
||||
Entity owner = getOwnerEntity();
|
||||
if (owner == null || owner.isDead) {
|
||||
setDead();
|
||||
return;
|
||||
}
|
||||
float progress = getChargeProgress();
|
||||
this.size = targetSize * progress;
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
|
||||
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== HOMING ====================
|
||||
|
||||
/**
|
||||
* Update homing toward target. Uses position-based homing with distance-scaled strength.
|
||||
* Override in subclasses for type-specific homing (e.g. Beam uses head offset).
|
||||
*/
|
||||
protected void updateHoming() {
|
||||
if (!isHoming()) return;
|
||||
Entity target = getTargetEntity();
|
||||
if (target == null || !target.isEntityAlive()) return;
|
||||
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.getEyeHeight()) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= getHomingRange() && dist > 0) {
|
||||
float effectiveStrength = getHomingStrength();
|
||||
if (dist < getHomingRange() * 0.3) {
|
||||
effectiveStrength = Math.min(1.0f, getHomingStrength() * 2.5f);
|
||||
} else if (dist < getHomingRange() * 0.6) {
|
||||
effectiveStrength = Math.min(0.8f, getHomingStrength() * 1.5f);
|
||||
}
|
||||
|
||||
double desiredVX = (dx / dist) * getSpeed();
|
||||
double desiredVY = (dy / dist) * getSpeed();
|
||||
double desiredVZ = (dz / dist) * getSpeed();
|
||||
|
||||
motionX += (desiredVX - motionX) * effectiveStrength;
|
||||
motionY += (desiredVY - motionY) * effectiveStrength;
|
||||
motionZ += (desiredVZ - motionZ) * effectiveStrength;
|
||||
|
||||
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
|
||||
if (vLen > 0) {
|
||||
motionX = (motionX / vLen) * getSpeed();
|
||||
motionY = (motionY / vLen) * getSpeed();
|
||||
motionZ = (motionZ / vLen) * getSpeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== VELOCITY HELPERS ====================
|
||||
|
||||
/**
|
||||
* Calculate initial velocity toward target or forward based on owner facing.
|
||||
* Call from subclass constructors after initProjectile().
|
||||
*/
|
||||
protected void calculateInitialVelocity(EntityLivingBase owner, EntityLivingBase target,
|
||||
double x, double y, double z) {
|
||||
if (target != null) {
|
||||
double dx = target.posX - x;
|
||||
double dy = (target.posY + target.getEyeHeight()) - y;
|
||||
double dz = target.posZ - z;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
this.motionX = (dx / len) * getSpeed();
|
||||
this.motionY = (dy / len) * getSpeed();
|
||||
this.motionZ = (dz / len) * getSpeed();
|
||||
}
|
||||
} else {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
this.motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
|
||||
this.motionY = -Math.sin(pitch) * getSpeed();
|
||||
this.motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== CHARGING NBT ====================
|
||||
|
||||
/**
|
||||
* Read common charging state from NBT. Call from subclass readProjectileNBT().
|
||||
*/
|
||||
protected void readChargingNBT(NBTTagCompound nbt) {
|
||||
boolean isChargingVal = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
|
||||
this.charging = isChargingVal;
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (isChargingVal ? 1 : 0));
|
||||
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40;
|
||||
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
|
||||
this.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write common charging state to NBT. Call from subclass writeProjectileNBT().
|
||||
*/
|
||||
protected void writeChargingNBT(NBTTagCompound nbt) {
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setFloat("TargetSize", targetSize);
|
||||
}
|
||||
|
||||
// ==================== ENTITY HELPERS ====================
|
||||
|
||||
public Entity getOwnerEntity() {
|
||||
@@ -502,26 +663,9 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
return NpcAPI.Instance().getIEntity(getTargetEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSiblingEntityId() {
|
||||
return siblingEntityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiblingEntityId(int siblingEntityId) {
|
||||
if (!(worldObj.getEntityByID(siblingEntityId) instanceof EntityAbilityProjectile)) return;
|
||||
|
||||
this.siblingEntityId = siblingEntityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyProjectile getSibling() {
|
||||
if (siblingEntityId == -1) return null;
|
||||
return (EntityAbilityProjectile) worldObj.getEntityByID(siblingEntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity should be ignored for collision.
|
||||
* Ignores: the owner, same-faction NPCs, and other projectiles from the same caster.
|
||||
*/
|
||||
protected boolean shouldIgnoreEntity(Entity entity) {
|
||||
Entity owner = getOwnerEntity();
|
||||
@@ -533,8 +677,11 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
if (ownerNpc.faction.id == targetNpc.faction.id) return true;
|
||||
}
|
||||
|
||||
if (entity.getEntityId() == getSiblingEntityId())
|
||||
return true;
|
||||
// Ignore other projectiles from the same caster (e.g. multi-projectile abilities)
|
||||
if (entity instanceof EntityAbilityProjectile) {
|
||||
EntityAbilityProjectile other = (EntityAbilityProjectile) entity;
|
||||
if (other.ownerEntityId == this.ownerEntityId) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -746,7 +893,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
|
||||
this.ownerEntityId = nbt.getInteger("OwnerId");
|
||||
this.targetEntityId = nbt.getInteger("TargetId");
|
||||
this.siblingEntityId = nbt.getInteger("SiblingId");
|
||||
|
||||
this.motionX = nbt.getDouble("MotionX");
|
||||
this.motionY = nbt.getDouble("MotionY");
|
||||
@@ -789,7 +935,6 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
|
||||
nbt.setInteger("OwnerId", ownerEntityId);
|
||||
nbt.setInteger("TargetId", targetEntityId);
|
||||
nbt.setInteger("SiblingId", targetEntityId);
|
||||
|
||||
nbt.setDouble("MotionX", motionX);
|
||||
nbt.setDouble("MotionY", motionY);
|
||||
|
||||
@@ -186,7 +186,7 @@ public class DataAbilities {
|
||||
playAbilitySound(currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(npc, target, npc.worldObj, currentAbility.getCurrentTick());
|
||||
currentAbility.onWindUpTick(npc, target, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
@@ -221,11 +221,6 @@ public class DataAbilities {
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Burst re-fire without replay: let entity-spawning abilities spawn+fire in one step
|
||||
if (oldPhase == AbilityPhase.BURST_DELAY && !currentAbility.isBurstReplayAnimations()) {
|
||||
currentAbility.onBurstRefire(npc, target, npc.worldObj);
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
// Abilities that keep telegraph during active manage their own sound timing
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
@@ -247,7 +242,7 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
currentAbility.onExecute(npc, target, npc.worldObj);
|
||||
currentAbility.onExecute(npc, target);
|
||||
|
||||
// Check if ability completed during onExecute (signalCompletion was called)
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
@@ -255,7 +250,7 @@ public class DataAbilities {
|
||||
return;
|
||||
}
|
||||
}
|
||||
currentAbility.onActiveTick(npc, target, npc.worldObj, currentAbility.getCurrentTick());
|
||||
currentAbility.onActiveTick(npc, target, currentAbility.getCurrentTick());
|
||||
|
||||
// Check if ability completed during onActiveTick (signalCompletion was called)
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
@@ -554,7 +549,7 @@ public class DataAbilities {
|
||||
}
|
||||
|
||||
// Call onExecute
|
||||
ability.onExecute(npc, target, npc.worldObj);
|
||||
ability.onExecute(npc, target);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (ability.getPhase() == AbilityPhase.IDLE) {
|
||||
|
||||
@@ -290,7 +290,7 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler {
|
||||
|
||||
private void tickWindup() {
|
||||
// Call the REAL windup tick method
|
||||
previewAbility.onWindUpTick(previewNpc, fakeTarget, previewNpc.worldObj, currentTick);
|
||||
previewAbility.onWindUpTick(previewNpc, fakeTarget, currentTick);
|
||||
|
||||
if (currentTick >= previewAbility.getWindUpTicks()) {
|
||||
transitionToActive();
|
||||
@@ -299,7 +299,7 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler {
|
||||
|
||||
private void tickActive() {
|
||||
// Call the REAL active tick method
|
||||
previewAbility.onActiveTick(previewNpc, fakeTarget, previewNpc.worldObj, currentTick);
|
||||
previewAbility.onActiveTick(previewNpc, fakeTarget, currentTick);
|
||||
|
||||
// Simulate physics for movement abilities
|
||||
simulatePhysics();
|
||||
@@ -330,7 +330,7 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler {
|
||||
previewTelegraph = null;
|
||||
|
||||
// Call the REAL onExecute method (fires entities, initiates movement, etc.)
|
||||
previewAbility.onExecute(previewNpc, fakeTarget, previewNpc.worldObj);
|
||||
previewAbility.onExecute(previewNpc, fakeTarget);
|
||||
|
||||
// Start active animation
|
||||
Animation activeAnim = previewAbility.getActiveAnimation();
|
||||
|
||||
@@ -161,7 +161,7 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
playAbilitySound(player, currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(player, currentTarget, player.worldObj, currentAbility.getCurrentTick());
|
||||
currentAbility.onWindUpTick(player, currentTarget, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
@@ -170,7 +170,10 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) {
|
||||
telegraph.lockPosition();
|
||||
}
|
||||
removeTelegraph(currentAbility, player);
|
||||
// Remove telegraph unless the ability keeps it during active phase
|
||||
if (!currentAbility.keepTelegraphDuringActive()) {
|
||||
removeTelegraph(currentAbility, player);
|
||||
}
|
||||
|
||||
// Handle rotation control transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isRotationLockedDuringActive()) {
|
||||
@@ -195,20 +198,28 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
|
||||
// Call onExecute
|
||||
currentAbility.onExecute(player, currentTarget, player.worldObj);
|
||||
currentAbility.onExecute(player, currentTarget);
|
||||
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(player);
|
||||
return;
|
||||
}
|
||||
}
|
||||
currentAbility.onActiveTick(player, currentTarget, player.worldObj, currentAbility.getCurrentTick());
|
||||
currentAbility.onActiveTick(player, currentTarget, currentAbility.getCurrentTick());
|
||||
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-complete for burst overlap mode (entities fly independently)
|
||||
if (currentAbility.isBurstEnabled() && currentAbility.isBurstOverlap()
|
||||
&& currentAbility.getBurstIndex() < currentAbility.getBurstAmount()
|
||||
&& currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) {
|
||||
currentAbility.signalCompletion();
|
||||
}
|
||||
|
||||
// Release locks during burst delay
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
@@ -348,7 +359,7 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
playAbilityAnimation(ability.getActiveAnimation());
|
||||
|
||||
// Call onExecute
|
||||
ability.onExecute(player, currentTarget, player.worldObj);
|
||||
ability.onExecute(player, currentTarget);
|
||||
|
||||
// Check if ability completed during onExecute
|
||||
if (ability.getPhase() == AbilityPhase.IDLE) {
|
||||
|
||||
Reference in new issue
Block a user