mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 01:57:34 +00:00
Burst and Dual Mode + Variant Ability Support
This commit is contained in:
1 parent
af02b0ab1d
commit
74cae17ebd
27 files changed
+2055
-2982
No files matched your search
@@ -32,6 +32,7 @@ import noppes.npcs.scripted.event.player.PlayerAbilityEvent;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -101,6 +102,13 @@ public abstract class Ability implements IAbility {
|
||||
// Configurable potion effects
|
||||
protected List<AbilityEffect> effects = new ArrayList<>();
|
||||
|
||||
// Burst
|
||||
protected boolean burstEnabled = false;
|
||||
protected int burstAmount = 0; // number of EXTRA repetitions
|
||||
protected int burstDelay = 0; // ticks between bursts
|
||||
protected boolean burstReplayAnimations = true;
|
||||
protected boolean burstOverlap = false; // if true, don't wait for entity death between bursts
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// EXECUTION STATE (not saved, reset each combat)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -111,6 +119,10 @@ public abstract class Ability implements IAbility {
|
||||
protected transient long executionStartTime;
|
||||
protected transient TelegraphInstance telegraphInstance;
|
||||
|
||||
// Burst execution state
|
||||
protected transient int burstIndex = 0;
|
||||
protected transient List<Entity> burstEntities = new ArrayList<>();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PREVIEW STATE (client-side only, not saved)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -156,6 +168,50 @@ public abstract class Ability implements IAbility {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to provide variant templates for this ability type.
|
||||
* When a user creates this ability type, they will be shown a selection dialog
|
||||
* with the returned variants. If empty or single-entry, the dialog is skipped.
|
||||
*/
|
||||
public List<AbilityVariant> getVariants() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this ability type supports the burst system.
|
||||
* Override to return false for abilities that shouldn't repeat (Guard, Heal, Sweeper, etc.).
|
||||
*/
|
||||
public boolean allowBurst() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this ability supports overlap mode for burst (entities from previous bursts keep flying).
|
||||
* Override to return true for entity-spawning projectile abilities (Orb, Beam, Disc, LaserShot).
|
||||
*/
|
||||
public boolean allowOverlap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this ability is ready for auto-completion during burst overlap mode.
|
||||
* Override to delay auto-completion until all staggered projectiles have been fired.
|
||||
* @param activeTick the current tick within the ACTIVE phase
|
||||
*/
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
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, World world, int tick) {
|
||||
}
|
||||
|
||||
@@ -454,6 +510,25 @@ public abstract class Ability implements IAbility {
|
||||
.range(0, 1000).visibleWhen(this::isInterruptible)
|
||||
).tab("General"));
|
||||
|
||||
// ── Burst section ────────────────────────────────────────────
|
||||
if (allowBurst()) {
|
||||
defs.add(FieldDef.section("ability.section.burst").tab("General"));
|
||||
defs.add(FieldDef.boolField("ability.burstEnabled", this::isBurstEnabled, this::setBurstEnabled)
|
||||
.tab("General"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.burstAmount", this::getBurstAmount, this::setBurstAmount).range(1, 100),
|
||||
FieldDef.intField("ability.burstDelay", this::getBurstDelay, this::setBurstDelay).range(0, 1000)
|
||||
).tab("General").visibleWhen(this::isBurstEnabled));
|
||||
defs.add(FieldDef.boolField("ability.burstReplayAnimations", this::isBurstReplayAnimations, this::setBurstReplayAnimations)
|
||||
.tab("General").visibleWhen(this::isBurstEnabled)
|
||||
.hover("ability.hover.burstReplay"));
|
||||
if (allowOverlap()) {
|
||||
defs.add(FieldDef.boolField("ability.burstOverlap", this::isBurstOverlap, this::setBurstOverlap)
|
||||
.tab("General").visibleWhen(this::isBurstEnabled)
|
||||
.hover("ability.hover.burstOverlap"));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Target tab ───────────────────────────────────────────────
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.minRange", () -> (int) getMinRange(), v -> setMinRange(v)).range(0, 100),
|
||||
@@ -722,6 +797,8 @@ public abstract class Ability implements IAbility {
|
||||
this.currentTick = 0;
|
||||
this.currentTarget = target;
|
||||
this.executionStartTime = System.currentTimeMillis();
|
||||
this.burstIndex = 0;
|
||||
this.burstEntities.clear();
|
||||
|
||||
if (windUpTicks <= 0) {
|
||||
this.phase = AbilityPhase.ACTIVE;
|
||||
@@ -764,6 +841,19 @@ public abstract class Ability implements IAbility {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Waiting between burst repetitions - free movement/rotation
|
||||
if (currentTick >= burstDelay) {
|
||||
if (burstReplayAnimations && getWindUpTicks() > 0) {
|
||||
phase = AbilityPhase.WINDUP;
|
||||
} else {
|
||||
phase = AbilityPhase.ACTIVE;
|
||||
}
|
||||
currentTick = 0;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -777,6 +867,19 @@ public abstract class Ability implements IAbility {
|
||||
*/
|
||||
public boolean signalCompletion() {
|
||||
if (phase == AbilityPhase.ACTIVE) {
|
||||
// Check if more burst iterations remain
|
||||
if (burstEnabled && burstAmount > 0 && burstIndex < burstAmount) {
|
||||
if (!burstOverlap) {
|
||||
cleanup();
|
||||
}
|
||||
burstIndex++;
|
||||
phase = AbilityPhase.BURST_DELAY;
|
||||
currentTick = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Final completion - let burst entities die naturally (don't force-kill)
|
||||
burstEntities.clear();
|
||||
cleanup();
|
||||
phase = AbilityPhase.IDLE;
|
||||
currentTick = 0;
|
||||
@@ -790,9 +893,10 @@ public abstract class Ability implements IAbility {
|
||||
* Called when NPC takes direct damage during WINDUP.
|
||||
*/
|
||||
public void interrupt() {
|
||||
cleanupBurstEntities();
|
||||
cleanup();
|
||||
if (interruptible && phase == AbilityPhase.WINDUP) {
|
||||
// Interrupted during windup - go to dazed state
|
||||
if (interruptible && (phase == AbilityPhase.WINDUP || phase == AbilityPhase.BURST_DELAY)) {
|
||||
// Interrupted during windup or burst delay - go to dazed state
|
||||
phase = AbilityPhase.DAZED;
|
||||
currentTick = 0;
|
||||
} else {
|
||||
@@ -800,6 +904,7 @@ public abstract class Ability implements IAbility {
|
||||
phase = AbilityPhase.IDLE;
|
||||
currentTick = 0;
|
||||
}
|
||||
burstIndex = 0;
|
||||
currentTarget = null;
|
||||
telegraphInstance = null;
|
||||
}
|
||||
@@ -820,7 +925,7 @@ public abstract class Ability implements IAbility {
|
||||
* @return true if the ability should be interrupted
|
||||
*/
|
||||
public boolean canInterrupt(DamageSource source) {
|
||||
if (!interruptible || phase != AbilityPhase.WINDUP) {
|
||||
if (!interruptible || (phase != AbilityPhase.WINDUP && phase != AbilityPhase.BURST_DELAY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -847,9 +952,11 @@ public abstract class Ability implements IAbility {
|
||||
* Reset all execution state (called on combat end, NPC death, target lost, etc.)
|
||||
*/
|
||||
public void reset() {
|
||||
cleanupBurstEntities();
|
||||
cleanup();
|
||||
phase = AbilityPhase.IDLE;
|
||||
currentTick = 0;
|
||||
burstIndex = 0;
|
||||
currentTarget = null;
|
||||
telegraphInstance = null;
|
||||
previewMode = false;
|
||||
@@ -865,6 +972,18 @@ public abstract class Ability implements IAbility {
|
||||
// Override in subclasses to clean up spawned entities, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill all entities tracked during burst overlap and clear the list.
|
||||
*/
|
||||
protected void cleanupBurstEntities() {
|
||||
for (Entity e : burstEntities) {
|
||||
if (e != null && !e.isDead) {
|
||||
killAbilityEntity(e);
|
||||
}
|
||||
}
|
||||
burstEntities.clear();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PREVIEW MODE SUPPORT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -896,6 +1015,9 @@ public abstract class Ability implements IAbility {
|
||||
} else {
|
||||
world.spawnEntityInWorld(entity);
|
||||
}
|
||||
if (burstEnabled && burstOverlap) {
|
||||
burstEntities.add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -979,6 +1101,13 @@ public abstract class Ability implements IAbility {
|
||||
nbt.setInteger("allowedBy", allowedBy.ordinal());
|
||||
nbt.setBoolean("ignoreCooldown", ignoreCooldown);
|
||||
|
||||
// Burst
|
||||
nbt.setBoolean("burstEnabled", burstEnabled);
|
||||
nbt.setInteger("burstAmount", burstAmount);
|
||||
nbt.setInteger("burstDelay", burstDelay);
|
||||
nbt.setBoolean("burstReplayAnimations", burstReplayAnimations);
|
||||
nbt.setBoolean("burstOverlap", burstOverlap);
|
||||
|
||||
// Conditions
|
||||
NBTTagList condList = new NBTTagList();
|
||||
for (Condition c : conditions) condList.appendTag(c.writeNBT());
|
||||
@@ -1059,6 +1188,13 @@ public abstract class Ability implements IAbility {
|
||||
allowedBy = nbt.hasKey("allowedBy") ? UserType.fromOrdinal(nbt.getInteger("allowedBy")) : UserType.BOTH;
|
||||
ignoreCooldown = nbt.hasKey("ignoreCooldown") && nbt.getBoolean("ignoreCooldown");
|
||||
|
||||
// Burst
|
||||
burstEnabled = nbt.hasKey("burstEnabled") && nbt.getBoolean("burstEnabled");
|
||||
burstAmount = nbt.hasKey("burstAmount") ? nbt.getInteger("burstAmount") : 0;
|
||||
burstDelay = nbt.hasKey("burstDelay") ? nbt.getInteger("burstDelay") : 0;
|
||||
burstReplayAnimations = !nbt.hasKey("burstReplayAnimations") || nbt.getBoolean("burstReplayAnimations");
|
||||
burstOverlap = nbt.hasKey("burstOverlap") && nbt.getBoolean("burstOverlap");
|
||||
|
||||
// Conditions
|
||||
conditions.clear();
|
||||
NBTTagList condList = nbt.getTagList("conditions", 10);
|
||||
@@ -1313,6 +1449,8 @@ public abstract class Ability implements IAbility {
|
||||
return isMovementLockedDuringWindup();
|
||||
case ACTIVE:
|
||||
return isMovementLockedDuringActive();
|
||||
case BURST_DELAY:
|
||||
return false; // Free movement during burst delay
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1327,6 +1465,8 @@ public abstract class Ability implements IAbility {
|
||||
return isRotationLockedDuringWindup();
|
||||
case ACTIVE:
|
||||
return isRotationLockedDuringActive();
|
||||
case BURST_DELAY:
|
||||
return false; // Free rotation during burst delay
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1342,6 +1482,8 @@ public abstract class Ability implements IAbility {
|
||||
return rotationPhase.locksWindup();
|
||||
case ACTIVE:
|
||||
return rotationPhase.locksActive();
|
||||
case BURST_DELAY:
|
||||
return false; // Free rotation during burst delay
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1530,6 +1672,52 @@ public abstract class Ability implements IAbility {
|
||||
effects.clear();
|
||||
}
|
||||
|
||||
// Burst getters/setters
|
||||
|
||||
public boolean isBurstEnabled() {
|
||||
return burstEnabled;
|
||||
}
|
||||
|
||||
public void setBurstEnabled(boolean burstEnabled) {
|
||||
this.burstEnabled = burstEnabled;
|
||||
}
|
||||
|
||||
public int getBurstAmount() {
|
||||
return burstAmount;
|
||||
}
|
||||
|
||||
public void setBurstAmount(int burstAmount) {
|
||||
this.burstAmount = Math.max(0, burstAmount);
|
||||
}
|
||||
|
||||
public int getBurstDelay() {
|
||||
return burstDelay;
|
||||
}
|
||||
|
||||
public void setBurstDelay(int burstDelay) {
|
||||
this.burstDelay = Math.max(0, burstDelay);
|
||||
}
|
||||
|
||||
public boolean isBurstReplayAnimations() {
|
||||
return burstReplayAnimations;
|
||||
}
|
||||
|
||||
public void setBurstReplayAnimations(boolean burstReplayAnimations) {
|
||||
this.burstReplayAnimations = burstReplayAnimations;
|
||||
}
|
||||
|
||||
public boolean isBurstOverlap() {
|
||||
return burstOverlap;
|
||||
}
|
||||
|
||||
public void setBurstOverlap(boolean burstOverlap) {
|
||||
this.burstOverlap = burstOverlap;
|
||||
}
|
||||
|
||||
public int getBurstIndex() {
|
||||
return burstIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display keys for rotation phase dropdown (excludes "None" since mode=FREE handles that).
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,9 @@ public class AbilityController implements IAbilityHandler {
|
||||
/** External field providers for ability GUI (e.g., DBC Addon). Client-side only. */
|
||||
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
|
||||
|
||||
/** External variant definitions registered by addons, keyed by typeId. */
|
||||
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
|
||||
|
||||
public AbilityController() {
|
||||
registerBuiltinTypes();
|
||||
}
|
||||
@@ -432,13 +435,9 @@ public class AbilityController implements IAbilityHandler {
|
||||
registerType("cnpc:sweeper", AbilitySweeper::new);
|
||||
registerType("cnpc:projectile", AbilityProjectile::new);
|
||||
registerType("cnpc:orb", AbilityOrb::new);
|
||||
registerType("cnpc:dual_orb", AbilityOrbDual::new);
|
||||
registerType("cnpc:orb_barrage", AbilityOrbBarrage::new);
|
||||
registerType("cnpc:dual_disc", AbilityDisc::new);
|
||||
registerType("cnpc:dual_disc", AbilityDiscDual::new);
|
||||
registerType("cnpc:disc", AbilityDisc::new);
|
||||
registerType("cnpc:laser_shot", AbilityLaserShot::new);
|
||||
registerType("cnpc:beam", AbilityBeam::new);
|
||||
registerType("cnpc:dual_beam", AbilityBeamDual::new);
|
||||
|
||||
// Movement abilities
|
||||
registerType("cnpc:charge", AbilityCharge::new);
|
||||
@@ -490,4 +489,41 @@ public class AbilityController implements IAbilityHandler {
|
||||
public List<IAbilityFieldProvider> getFieldProviders() {
|
||||
return fieldProviders;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// EXTERNAL VARIANT REGISTRY
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Register an external variant for an ability type.
|
||||
* External mods can call this to add variant presets that appear
|
||||
* alongside built-in variants in the variant selection SubGui.
|
||||
*
|
||||
* @param typeId The ability typeId (e.g., "ability.cnpc.beam")
|
||||
* @param variant The variant to register
|
||||
*/
|
||||
public void registerVariant(String typeId, AbilityVariant variant) {
|
||||
externalVariants.computeIfAbsent(typeId, k -> new ArrayList<>()).add(variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all variants for a given ability type (built-in + external).
|
||||
* Returns the combined list of variants from the ability's getVariants()
|
||||
* and any externally registered variants.
|
||||
*
|
||||
* @param typeId The ability typeId
|
||||
* @return Combined list of variants, empty if none
|
||||
*/
|
||||
public List<AbilityVariant> getVariantsForType(String typeId) {
|
||||
List<AbilityVariant> result = new ArrayList<>();
|
||||
Ability temp = create(typeId);
|
||||
if (temp != null) {
|
||||
result.addAll(temp.getVariants());
|
||||
}
|
||||
List<AbilityVariant> ext = externalVariants.get(typeId);
|
||||
if (ext != null) {
|
||||
result.addAll(ext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@ public enum AbilityPhase {
|
||||
IDLE, // Not executing
|
||||
WINDUP, // Charging up (telegraph visible)
|
||||
ACTIVE, // Executing (damage happens)
|
||||
DAZED // Stunned after interrupt during WINDUP (cannot attack)
|
||||
DAZED, // Stunned after interrupt during WINDUP (cannot attack)
|
||||
BURST_DELAY // Waiting between burst repetitions (free movement/rotation)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a variant/template for an ability type.
|
||||
* When a user creates an ability that has registered variants,
|
||||
* they are presented with a selection dialog before the ability is created.
|
||||
*
|
||||
* Each variant has a display name (lang key) and a configurator that
|
||||
* applies preset values to the newly created ability.
|
||||
*/
|
||||
public class AbilityVariant {
|
||||
|
||||
private final String displayKey;
|
||||
private final Consumer<Ability> configurator;
|
||||
|
||||
/**
|
||||
* @param displayKey Lang key for the variant display name (e.g., "ability.variant.dual")
|
||||
* @param configurator Lambda that configures the ability with preset values
|
||||
*/
|
||||
public AbilityVariant(String displayKey, Consumer<Ability> configurator) {
|
||||
this.displayKey = displayKey;
|
||||
this.configurator = configurator;
|
||||
}
|
||||
|
||||
public String getDisplayKey() {
|
||||
return displayKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this variant's preset configuration to the given ability.
|
||||
*/
|
||||
public void apply(Ability ability) {
|
||||
if (configurator != null) {
|
||||
configurator.accept(ability);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package kamkeel.npcs.controllers.data.ability.data;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
/**
|
||||
* Per-projectile data: anchor point + optional color override.
|
||||
* All projectiles share the primary EnergyDisplayData; this class
|
||||
* allows individual projectiles to override just the inner/outer colors.
|
||||
*/
|
||||
public class ProjectileData {
|
||||
public EnergyAnchorData anchor;
|
||||
public boolean colorOverride = false;
|
||||
public int innerColor = 0xFFFFFF;
|
||||
public int outerColor = 0x8888FF;
|
||||
|
||||
public ProjectileData(AnchorPoint defaultAnchor) {
|
||||
this.anchor = new EnergyAnchorData(defaultAnchor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display data to use for this projectile.
|
||||
* If colorOverride is false, returns the primary display data directly.
|
||||
* If colorOverride is true, returns a copy with overridden inner/outer colors.
|
||||
*/
|
||||
public EnergyDisplayData resolveDisplay(EnergyDisplayData primary) {
|
||||
if (!colorOverride) return primary;
|
||||
EnergyDisplayData resolved = primary.copy();
|
||||
resolved.innerColor = this.innerColor;
|
||||
resolved.outerColor = this.outerColor;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public void writeNBT(NBTTagCompound nbt) {
|
||||
anchor.writeNBT(nbt);
|
||||
nbt.setBoolean("colorOverride", colorOverride);
|
||||
if (colorOverride) {
|
||||
nbt.setInteger("overrideInnerColor", innerColor);
|
||||
nbt.setInteger("overrideOuterColor", outerColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void readNBT(NBTTagCompound nbt) {
|
||||
anchor.readNBT(nbt);
|
||||
colorOverride = nbt.getBoolean("colorOverride");
|
||||
if (colorOverride) {
|
||||
innerColor = nbt.hasKey("overrideInnerColor") ? nbt.getInteger("overrideInnerColor") : 0xFFFFFF;
|
||||
outerColor = nbt.hasKey("overrideOuterColor") ? nbt.getInteger("overrideOuterColor") : 0x8888FF;
|
||||
}
|
||||
}
|
||||
|
||||
public ProjectileData copy() {
|
||||
ProjectileData copy = new ProjectileData(anchor.anchorPoint);
|
||||
copy.anchor = anchor.copy();
|
||||
copy.colorOverride = colorOverride;
|
||||
copy.innerColor = innerColor;
|
||||
copy.outerColor = outerColor;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
@@ -24,30 +23,39 @@ import noppes.npcs.api.ability.type.IAbilityEnergyBeam;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
|
||||
/**
|
||||
* Energy Beam ability: A homing head with a trailing path.
|
||||
* The beam stays attached to the origin and curves as it homes in on target.
|
||||
* Cannot cross over itself (self-intersection prevention).
|
||||
* Supports 1-8 projectiles with shared visuals and per-projectile anchor points.
|
||||
*/
|
||||
public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
|
||||
// Shape properties (standalone)
|
||||
private static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Beam shape properties (shared)
|
||||
private float beamWidth = 0.4f;
|
||||
private float headSize = 0.6f;
|
||||
|
||||
// Data classes
|
||||
private final EnergyDisplayData colorData = new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.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, 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 EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(25.0f, 200);
|
||||
private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND);
|
||||
private final EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Transient state for beam entity (used during windup charging)
|
||||
private transient EntityAbilityBeam beamEntity = null;
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
private ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
private transient EntityAbilityBeam[] beamEntities;
|
||||
|
||||
public AbilityBeam() {
|
||||
this.typeId = "ability.cnpc.beam";
|
||||
@@ -62,9 +70,88 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
this.rotationPhase = LockMovementType.WINDUP_AND_ACTIVE;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Legacy compat
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
return Arrays.asList(
|
||||
new AbilityVariant("ability.variant.single", a -> {
|
||||
a.setName("Beam");
|
||||
}),
|
||||
new AbilityVariant("ability.variant.dual", a -> {
|
||||
AbilityBeam beam = (AbilityBeam) a;
|
||||
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;
|
||||
a.setWindUpTicks(80);
|
||||
a.setWindUpAnimationName("Ability_BeamDual_Windup");
|
||||
a.setActiveAnimationName("Ability_BeamDual_Active");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,20 +164,34 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
// Start firing the beam that was spawned during windup
|
||||
if (beamEntity != null && !beamEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
beamEntity.startPreviewFiring();
|
||||
} else {
|
||||
beamEntity.startFiring(target);
|
||||
}
|
||||
private void fireBeamEntity(EntityAbilityBeam beam, EntityLivingBase target) {
|
||||
if (beam == null || beam.isDead) return;
|
||||
if (isPreview()) {
|
||||
beam.startPreviewFiring();
|
||||
} else {
|
||||
beam.startFiring(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,35 +199,73 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn beam in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
beamEntities = createBeamEntities(caster, target, world);
|
||||
float offsetDist = 1.0f;
|
||||
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData, offsetDist);
|
||||
beamEntity = new EntityAbilityBeam(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
beamWidth, headSize,
|
||||
colorData, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
lockMovement.locksActive());
|
||||
|
||||
if (isPreview()) {
|
||||
beamEntity.setupPreview(caster, beamWidth, headSize, colorData, lightningData, anchorData, windUpTicks, offsetDist);
|
||||
} else {
|
||||
beamEntity.setupCharging(anchorData, windUpTicks, offsetDist);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beamEntity.setEffects(this.effects);
|
||||
beamEntity.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, beamEntity);
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
beamEntities = createBeamEntities(caster, target, world);
|
||||
for (EntityAbilityBeam beam : beamEntities) {
|
||||
spawnAbilityEntity(world, beam);
|
||||
}
|
||||
for (EntityAbilityBeam beam : beamEntities) {
|
||||
fireBeamEntity(beam, 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) {
|
||||
// Signal completion when entity dies
|
||||
if (beamEntity == null || beamEntity.isDead) {
|
||||
beamEntity = null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -142,11 +281,15 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Despawn beam entity if still alive
|
||||
if (beamEntity != null && !beamEntity.isDead) {
|
||||
beamEntity.setDead();
|
||||
if (beamEntities != null) {
|
||||
for (int i = 0; i < beamEntities.length; i++) {
|
||||
if (beamEntities[i] != null && !beamEntities[i].isDead) {
|
||||
beamEntities[i].setDead();
|
||||
}
|
||||
beamEntities[i] = null;
|
||||
}
|
||||
}
|
||||
beamEntity = null;
|
||||
beamEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +298,6 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create circle telegraph at target position
|
||||
Telegraph telegraph = Telegraph.circle(headSize * 2.0f);
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
@@ -163,7 +305,6 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
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());
|
||||
@@ -180,172 +321,307 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("beamWidth", beamWidth);
|
||||
nbt.setFloat("headSize", headSize);
|
||||
anchorData.writeNBT(nbt);
|
||||
colorData.writeNBT(nbt);
|
||||
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);
|
||||
lightningData.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) {
|
||||
this.beamWidth = nbt.hasKey("beamWidth") ? nbt.getFloat("beamWidth") : 0.4f;
|
||||
this.headSize = nbt.hasKey("headSize") ? nbt.getFloat("headSize") : 0.6f;
|
||||
anchorData.readNBT(nbt);
|
||||
colorData.readNBT(nbt);
|
||||
|
||||
int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1;
|
||||
initProjectiles(count);
|
||||
|
||||
if (nbt.hasKey("fireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
} else if (nbt.hasKey("dualFireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("dualFireDelay");
|
||||
} else {
|
||||
this.fireDelay = 0;
|
||||
}
|
||||
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
if (nbt.hasKey("Projectile_0")) {
|
||||
NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0");
|
||||
if (proj0.hasKey("colorOverride")) {
|
||||
// NEW format
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// OLD per-projectile format
|
||||
displayData.readNBT(proj0);
|
||||
lightningData.readNBT(proj0);
|
||||
projectiles[0].anchor.readNBT(proj0);
|
||||
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i);
|
||||
projectiles[i].anchor.readNBT(projNbt);
|
||||
|
||||
EnergyDisplayData tempDisplay = new EnergyDisplayData();
|
||||
tempDisplay.readNBT(projNbt);
|
||||
if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) {
|
||||
projectiles[i].colorOverride = true;
|
||||
projectiles[i].innerColor = tempDisplay.innerColor;
|
||||
projectiles[i].outerColor = tempDisplay.outerColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LEGACY flat format
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
projectiles[0].anchor.readNBT(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters - Standalone fields
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public float getBeamWidth() { return beamWidth; }
|
||||
public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; }
|
||||
|
||||
public float getHeadSize() { return headSize; }
|
||||
public void setHeadSize(float headSize) { this.headSize = headSize; }
|
||||
|
||||
// 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 boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) { this.colorData.outerColorEnabled = outerColorEnabled; }
|
||||
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 float getRotationSpeed() { return colorData.rotationSpeed; }
|
||||
public void setRotationSpeed(float rotationSpeed) { this.colorData.rotationSpeed = rotationSpeed; }
|
||||
|
||||
// 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 - Homing data
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { this.homingData.speed = speed; }
|
||||
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; }
|
||||
|
||||
// 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; }
|
||||
|
||||
// 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 - Anchor point
|
||||
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 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 anchorData.anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
|
||||
@Override
|
||||
public void setAnchorPoint(int point) { this.anchorData.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
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); }
|
||||
|
||||
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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.section("ability.section.beam"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth),
|
||||
FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming)
|
||||
.hover("ability.hover.homing"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength)
|
||||
.visibleWhen(this::isHoming),
|
||||
FieldDef.floatField("gui.range", this::getHomingRange, this::setHomingRange)
|
||||
.visibleWhen(this::isHoming)
|
||||
),
|
||||
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),
|
||||
FieldDef.floatField("ability.anchor.offsetY", this::getAnchorOffsetY, this::setAnchorOffsetY)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", this::getAnchorOffsetZ, this::setAnchorOffsetZ)
|
||||
.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.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.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")
|
||||
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)
|
||||
));
|
||||
defs.add(FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback));
|
||||
defs.add(FieldDef.section("ability.section.beam"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth),
|
||||
FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize)
|
||||
));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
));
|
||||
defs.add(FieldDef.section("ability.section.homing"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming),
|
||||
FieldDef.floatField("gui.range", this::getHomingRange, this::setHomingRange).visibleWhen(this::isHoming)
|
||||
));
|
||||
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));
|
||||
|
||||
// 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),
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,528 +0,0 @@
|
||||
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.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.EntityAbilityBeam;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.util.ValueUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AbilityBeamDual extends Ability {
|
||||
private float beamWidth = 0.4f;
|
||||
private float headSize = 0.6f;
|
||||
private boolean dualFire= true;
|
||||
private int dualFireDelay = 0;
|
||||
|
||||
// Data classes
|
||||
private final EnergyDisplayData[] displayData = new EnergyDisplayData[]{
|
||||
new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f),
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 6.0f),
|
||||
};
|
||||
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 EnergyLightningData[] lightningData = new EnergyLightningData[]{
|
||||
new EnergyLightningData(), new EnergyLightningData()
|
||||
};
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(25.0f, 200);
|
||||
private final EnergyAnchorData[] anchorData = new EnergyAnchorData[]{
|
||||
new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND)
|
||||
};
|
||||
private final EnergyTrajectoryData[] trajectoryData = new EnergyTrajectoryData[]{
|
||||
new EnergyTrajectoryData(), new EnergyTrajectoryData()
|
||||
};
|
||||
|
||||
// Transient state for beam entity (used during windup charging)
|
||||
private transient EntityAbilityBeam beamEntity = null;
|
||||
private transient EntityAbilityBeam beamEntity2 = null;
|
||||
|
||||
public AbilityBeamDual() {
|
||||
this.typeId = "ability.cnpc.beam_dual";
|
||||
this.name = "Beam Dual";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
this.maxRange = 20.0f;
|
||||
this.minRange = 5.0f;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 80;
|
||||
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
this.windUpAnimationName = "Ability_BeamDual_Windup";
|
||||
this.activeAnimationName = "Ability_BeamDual_Active";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start firing the beam that was spawned during windup
|
||||
if (beamEntity != null && !beamEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
beamEntity.startPreviewFiring();
|
||||
} else {
|
||||
beamEntity.startFiring(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (dualFire) {
|
||||
if (beamEntity2 != null && !beamEntity2.isDead && dualFireDelay <= 0) {
|
||||
if (isPreview()) {
|
||||
beamEntity2.startPreviewFiring();
|
||||
} else {
|
||||
beamEntity2.startFiring(target);
|
||||
}
|
||||
}
|
||||
} else if (beamEntity2 != null) {
|
||||
beamEntity2.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn beam in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
float offsetDist = 1.0f;
|
||||
|
||||
Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0], offsetDist);
|
||||
beamEntity = new EntityAbilityBeam(
|
||||
world, caster, target,
|
||||
spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord,
|
||||
beamWidth, headSize,
|
||||
displayData[0], combatData, homingData, lightningData[0], lifespanData, trajectoryData[0],
|
||||
lockMovement.locksActive());
|
||||
|
||||
Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1], offsetDist);
|
||||
beamEntity2 = new EntityAbilityBeam(
|
||||
world, caster, target,
|
||||
spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord,
|
||||
beamWidth, headSize,
|
||||
displayData[1], combatData, homingData, lightningData[1], lifespanData, trajectoryData[1],
|
||||
lockMovement.locksActive());
|
||||
|
||||
beamEntity.setSiblingEntityId(beamEntity2.getEntityId());
|
||||
beamEntity2.setSiblingEntityId(beamEntity.getEntityId());
|
||||
|
||||
if (isPreview()) {
|
||||
beamEntity.setupPreview(caster, beamWidth, headSize, displayData[0], lightningData[0], anchorData[0], windUpTicks, offsetDist);
|
||||
beamEntity2.setupPreview(caster, beamWidth, headSize, displayData[1], lightningData[1], anchorData[1], windUpTicks, offsetDist);
|
||||
} else {
|
||||
beamEntity.setupCharging(anchorData[0], windUpTicks, offsetDist);
|
||||
beamEntity2.setupCharging(anchorData[1], windUpTicks, offsetDist);
|
||||
}
|
||||
|
||||
beamEntity.setEffects(this.effects);
|
||||
if (dualFire) beamEntity2.setEffects(this.effects);
|
||||
|
||||
beamEntity.setSourceAbility(this);
|
||||
beamEntity2.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, beamEntity);
|
||||
spawnAbilityEntity(world, beamEntity2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (dualFire) {
|
||||
if ((beamEntity == null || beamEntity.isDead) && (beamEntity2 == null || beamEntity2.isDead)) {
|
||||
beamEntity = null;
|
||||
beamEntity2 = null;
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
if (tick == dualFireDelay && beamEntity2 != null && !beamEntity2.isDead) {
|
||||
beamEntity2.startFiring(target);
|
||||
}
|
||||
} else {
|
||||
if (beamEntity == null || beamEntity.isDead) {
|
||||
beamEntity = null;
|
||||
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() {
|
||||
// Despawn beam entity if still alive
|
||||
if (beamEntity != null && !beamEntity.isDead) {
|
||||
beamEntity.setDead();
|
||||
}
|
||||
|
||||
if (beamEntity2 != null && !beamEntity2.isDead) {
|
||||
beamEntity2.setDead();
|
||||
}
|
||||
|
||||
beamEntity = null;
|
||||
beamEntity2 = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create circle telegraph at target position
|
||||
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);
|
||||
|
||||
// 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 headSize * 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("beamWidth", beamWidth);
|
||||
nbt.setFloat("headSize", headSize);
|
||||
nbt.setBoolean("dualFire", dualFire);
|
||||
nbt.setInteger("dualFireDelay", dualFireDelay);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
NBTTagCompound beam1 = new NBTTagCompound();
|
||||
NBTTagCompound beam2 = new NBTTagCompound();
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound beam = i == 0 ? beam1 : beam2;
|
||||
anchorData[i].writeNBT(beam);
|
||||
displayData[i].writeNBT(beam);
|
||||
lightningData[i].writeNBT(beam);
|
||||
trajectoryData[i].writeNBT(beam);
|
||||
}
|
||||
|
||||
nbt.setTag("Beam_1", beam1);
|
||||
nbt.setTag("Beam_2", beam2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
NBTTagCompound beam1 = nbt.hasKey("Beam_1") ? nbt.getCompoundTag("Beam_1") : new NBTTagCompound();
|
||||
NBTTagCompound beam2 = nbt.hasKey("Beam_2") ? nbt.getCompoundTag("Beam_2") : new NBTTagCompound();
|
||||
|
||||
this.beamWidth = nbt.hasKey("beamWidth") ? nbt.getFloat("beamWidth") : 0.4f;
|
||||
this.headSize = nbt.hasKey("headSize") ? nbt.getFloat("headSize") : 0.6f;
|
||||
this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire");
|
||||
this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0;
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound beam = i == 0 ? beam1 : beam2;
|
||||
anchorData[i].readNBT(beam);
|
||||
displayData[i].readNBT(beam);
|
||||
lightningData[i].readNBT(beam);
|
||||
trajectoryData[i].readNBT(beam);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters - Standalone fields
|
||||
public float getBeamWidth() { return beamWidth; }
|
||||
public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; }
|
||||
public float getHeadSize() { return headSize; }
|
||||
public void setHeadSize(float headSize) { this.headSize = headSize; }
|
||||
public boolean isDualFire() { return dualFire; }
|
||||
public void setDualFire(boolean dualFire) { this.dualFire = dualFire; }
|
||||
public int getDualFireDelay() { return dualFireDelay; }
|
||||
public void setDualFireDelay(int dualFireDelay) { this.dualFireDelay = dualFireDelay; }
|
||||
|
||||
private EnergyDisplayData getColorData(int beam) {
|
||||
beam = ValueUtil.clamp(beam, 0, 1);
|
||||
return displayData[beam];
|
||||
}
|
||||
private EnergyLightningData getLightningData(int beam) {
|
||||
beam = ValueUtil.clamp(beam, 0, 1);
|
||||
return lightningData[beam];
|
||||
}
|
||||
private EnergyAnchorData getAnchorData(int beam) {
|
||||
beam = ValueUtil.clamp(beam, 0, 1);
|
||||
return anchorData[beam];
|
||||
}
|
||||
|
||||
// Getters & Setters - Color data
|
||||
public int getInnerColor(int beam) { return getColorData(beam).innerColor; }
|
||||
public void setInnerColor(int beam, int innerColor) { getColorData(beam).innerColor = innerColor; }
|
||||
public int getOuterColor(int beam) { return getColorData(beam).outerColor; }
|
||||
public void setOuterColor(int beam, int outerColor) { getColorData(beam).outerColor = outerColor; }
|
||||
public boolean isOuterColorEnabled(int beam) { return getColorData(beam).outerColorEnabled; }
|
||||
public void setOuterColorEnabled(int beam, boolean enabled) { getColorData(beam).outerColorEnabled = enabled; }
|
||||
public float getOuterColorWidth(int beam) { return getColorData(beam).outerColorWidth; }
|
||||
public void setOuterColorWidth(int beam, float width) { getColorData(beam).outerColorWidth = width; }
|
||||
public float getOuterColorAlpha(int beam) { return getColorData(beam).outerColorAlpha; }
|
||||
public void setOuterColorAlpha(int beam, float alpha) { getColorData(beam).outerColorAlpha = alpha; }
|
||||
public float getRotationSpeed(int beam) { return getColorData(beam).rotationSpeed; }
|
||||
public void setRotationSpeed(int beam, float speed) { getColorData(beam).rotationSpeed = speed; }
|
||||
|
||||
|
||||
public int getInnerColor() { return getInnerColor(0); }
|
||||
public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); }
|
||||
public int getOuterColor() { return getOuterColor(0); }
|
||||
public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); }
|
||||
public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); }
|
||||
public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); }
|
||||
public float getOuterColorWidth() { return getOuterColorWidth(0); }
|
||||
public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); }
|
||||
public float getOuterColorAlpha() { return getOuterColorAlpha(0); }
|
||||
public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); }
|
||||
public float getRotationSpeed() { return getRotationSpeed(0); }
|
||||
public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); }
|
||||
|
||||
// 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 - Homing data
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { this.homingData.speed = speed; }
|
||||
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; }
|
||||
|
||||
// Getters & Setters - Lightning data
|
||||
public boolean hasLightningEffect(int beam) { return getLightningData(beam).lightningEffect; }
|
||||
public void setLightningEffect(int beam, boolean lightningEffect) { getLightningData(beam).lightningEffect = lightningEffect; }
|
||||
public float getLightningDensity(int beam) { return getLightningData(beam).lightningDensity; }
|
||||
public void setLightningDensity(int beam, float lightningDensity) { getLightningData(beam).lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius(int beam) { return getLightningData(beam).lightningRadius; }
|
||||
public void setLightningRadius(int beam, float lightningRadius) { getLightningData(beam).lightningRadius = lightningRadius; }
|
||||
|
||||
public boolean hasLightningEffect() { return hasLightningEffect(0); }
|
||||
public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); }
|
||||
public float getLightningDensity() { return getLightningDensity(0); }
|
||||
public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); }
|
||||
public float getLightningRadius() { return getLightningRadius(0); }
|
||||
public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); }
|
||||
|
||||
|
||||
// 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 - Anchor point
|
||||
public AnchorPoint getAnchorPointEnum(int beam) { return getAnchorData(beam).anchorPoint; }
|
||||
public void setAnchorPointEnum(int beam, AnchorPoint anchorPoint) { getAnchorData(beam).anchorPoint = anchorPoint; }
|
||||
public float getAnchorOffsetX(int beam) { return getAnchorData(beam).anchorOffsetX; }
|
||||
public void setAnchorOffsetX(int beam, float x) { getAnchorData(beam).anchorOffsetX = x; }
|
||||
public float getAnchorOffsetY(int beam) { return getAnchorData(beam).anchorOffsetY; }
|
||||
public void setAnchorOffsetY(int beam, float y) { getAnchorData(beam).anchorOffsetY = y; }
|
||||
public float getAnchorOffsetZ(int beam) { return getAnchorData(beam).anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(int beam, float z) { getAnchorData(beam).anchorOffsetZ = z; }
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); }
|
||||
public float getAnchorOffsetX() { return getAnchorOffsetX(0); }
|
||||
public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); }
|
||||
public float getAnchorOffsetY() { return getAnchorOffsetY(0); }
|
||||
public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); }
|
||||
public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); }
|
||||
public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); }
|
||||
|
||||
//@Override
|
||||
public int getAnchorPoint(int beam) { return getAnchorData(beam).anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); }
|
||||
|
||||
//@Override
|
||||
public void setAnchorPoint(int beam, int point) { getAnchorData(beam).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.section("ability.section.beam"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth),
|
||||
FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.dual"),
|
||||
FieldDef.boolField("ability.dualFire", this::isDualFire, this::setDualFire),
|
||||
FieldDef.intField("ability.dualFireDelay", this::getDualFireDelay, this::setDualFireDelay)
|
||||
.visibleWhen(this::isDualFire),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming)
|
||||
.hover("ability.hover.homing"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength)
|
||||
.visibleWhen(this::isHoming),
|
||||
FieldDef.floatField("gui.range", this::getHomingRange, this::setHomingRange)
|
||||
.visibleWhen(this::isHoming)
|
||||
),
|
||||
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 - Beam 1
|
||||
FieldDef.section("ability.section.beam1").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(0), v -> setAnchorPointEnum(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(0), v -> setAnchorOffsetX(0, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(0), v -> setAnchorOffsetY(0, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(0), v -> setAnchorOffsetZ(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(0), v -> setInnerColor(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(0), v -> setOuterColorEnabled(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(0), v -> setOuterColor(0, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(0))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(0), v -> setRotationSpeed(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(0), v -> setLightningEffect(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(0), v -> setLightningDensity(0, v))
|
||||
.range(0.01f, 100f).visibleWhen(() -> hasLightningEffect(0)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(0), v -> setLightningRadius(0, v))
|
||||
.range(0.1f, 100f).visibleWhen(() -> hasLightningEffect(0))
|
||||
).tab("ability.tab.visual"),
|
||||
// Visual tab - Beam 2
|
||||
FieldDef.section("ability.section.beam2").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(1), v -> setAnchorOffsetX(1, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(1), v -> setAnchorOffsetY(1, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(1), v -> setAnchorOffsetZ(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(1), v -> setInnerColor(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(1), v -> setOuterColorEnabled(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(1), v -> setOuterColor(1, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(1))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(1), v -> setRotationSpeed(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(1), v -> setLightningEffect(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(1), v -> setLightningDensity(1, v))
|
||||
.range(0.01f, 100f).visibleWhen(() -> hasLightningEffect(1)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(1), v -> setLightningRadius(1, v))
|
||||
.range(0.1f, 100f).visibleWhen(() -> hasLightningEffect(1))
|
||||
).tab("ability.tab.visual")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
}
|
||||
@@ -22,33 +22,45 @@ import noppes.npcs.api.ability.type.IAbilityDisc;
|
||||
|
||||
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 {
|
||||
|
||||
// Disc geometry
|
||||
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
|
||||
// Boomerang properties (shared)
|
||||
private boolean boomerang = false;
|
||||
private int boomerangDelay = 40;
|
||||
|
||||
// Energy data classes
|
||||
private final EnergyDisplayData colorData = new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.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, 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 EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(35.0f, 200);
|
||||
private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND);
|
||||
private final EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
// Transient state for disc entity (used during windup charging)
|
||||
private transient EntityAbilityDisc discEntity = null;
|
||||
// Per-projectile data (anchor + optional color override)
|
||||
private ProjectileData[] projectiles;
|
||||
|
||||
// Transient entity state
|
||||
private transient EntityAbilityDisc[] discEntities;
|
||||
|
||||
public AbilityDisc() {
|
||||
this.typeId = "ability.cnpc.disc";
|
||||
@@ -61,9 +73,90 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.LEFT_HAND;
|
||||
case 1: return AnchorPoint.RIGHT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Legacy compat
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
return Arrays.asList(
|
||||
new AbilityVariant("ability.variant.single", a -> {
|
||||
a.setName("Disc");
|
||||
}),
|
||||
new AbilityVariant("ability.variant.dual", a -> {
|
||||
AbilityDisc disc = (AbilityDisc) a;
|
||||
a.setName("Dual Disc");
|
||||
disc.setProjectileCount(2);
|
||||
disc.setFireDelay(10);
|
||||
// Set color override for second disc
|
||||
disc.projectiles[1].colorOverride = true;
|
||||
disc.projectiles[1].innerColor = 0xFFFFFF;
|
||||
disc.projectiles[1].outerColor = 0x8800FF;
|
||||
a.setWindUpAnimationName("Ability_DiscDual_Windup");
|
||||
a.setActiveAnimationName("Ability_DiscDual_Active");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,20 +169,33 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
// Start moving the disc that was spawned during windup
|
||||
if (discEntity != null && !discEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
discEntity.startPreviewFiring();
|
||||
} else {
|
||||
discEntity.startMoving(target);
|
||||
}
|
||||
private void fireDiscEntity(EntityAbilityDisc disc, EntityLivingBase target) {
|
||||
if (disc == null || disc.isDead) return;
|
||||
if (isPreview()) {
|
||||
disc.startPreviewFiring();
|
||||
} else {
|
||||
disc.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,33 +203,76 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn disc in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData);
|
||||
discEntity = new EntityAbilityDisc(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
discRadius, discThickness,
|
||||
colorData, combatData, homingData, lightningData, lifespanData, trajectoryData,
|
||||
boomerang, boomerangDelay);
|
||||
|
||||
if (isPreview()) {
|
||||
discEntity.setupPreview(caster, discRadius, discThickness, colorData, lightningData, anchorData, windUpTicks, vertical);
|
||||
} else {
|
||||
discEntity.setupCharging(anchorData, windUpTicks, vertical);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
discEntity.setEffects(this.effects);
|
||||
discEntity.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, discEntity);
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
discEntities = createDiscEntities(caster, target, world);
|
||||
for (EntityAbilityDisc disc : discEntities) {
|
||||
spawnAbilityEntity(world, disc);
|
||||
}
|
||||
for (EntityAbilityDisc disc : discEntities) {
|
||||
fireDiscEntity(disc, 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) {
|
||||
// Signal completion when entity dies
|
||||
if (discEntity == null || discEntity.isDead) {
|
||||
discEntity = null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -139,11 +288,15 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Despawn disc entity if still alive
|
||||
if (discEntity != null && !discEntity.isDead) {
|
||||
discEntity.setDead();
|
||||
if (discEntities != null) {
|
||||
for (int i = 0; i < discEntities.length; i++) {
|
||||
if (discEntities[i] != null && !discEntities[i].isDead) {
|
||||
discEntities[i].setDead();
|
||||
}
|
||||
discEntities[i] = null;
|
||||
}
|
||||
}
|
||||
discEntity = null;
|
||||
discEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,12 +333,24 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
nbt.setBoolean("vertical", vertical);
|
||||
nbt.setBoolean("boomerang", boomerang);
|
||||
nbt.setInteger("boomerangDelay", boomerangDelay);
|
||||
anchorData.writeNBT(nbt);
|
||||
colorData.writeNBT(nbt);
|
||||
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);
|
||||
lightningData.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
|
||||
@@ -195,157 +360,303 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
this.vertical = nbt.hasKey("vertical") && nbt.getBoolean("vertical");
|
||||
this.boomerang = nbt.hasKey("boomerang") && nbt.getBoolean("boomerang");
|
||||
this.boomerangDelay = nbt.hasKey("boomerangDelay") ? nbt.getInteger("boomerangDelay") : 40;
|
||||
anchorData.readNBT(nbt);
|
||||
colorData.readNBT(nbt);
|
||||
|
||||
int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1;
|
||||
initProjectiles(count);
|
||||
|
||||
// Read fireDelay with fallback to old dualFireDelay key
|
||||
if (nbt.hasKey("fireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
} else if (nbt.hasKey("dualFireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("dualFireDelay");
|
||||
} else {
|
||||
this.fireDelay = 0;
|
||||
}
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
// Determine format and read accordingly
|
||||
if (nbt.hasKey("Projectile_0")) {
|
||||
NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0");
|
||||
if (proj0.hasKey("colorOverride")) {
|
||||
// NEW format: shared visuals at root + ProjectileData per projectile
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// OLD format: full per-projectile arrays (displayData, lightningData, etc. in each Projectile_i)
|
||||
// Read primary visuals from Projectile_0
|
||||
displayData.readNBT(proj0);
|
||||
lightningData.readNBT(proj0);
|
||||
projectiles[0].anchor.readNBT(proj0);
|
||||
|
||||
// Read remaining projectiles, set color override if colors differ
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i);
|
||||
projectiles[i].anchor.readNBT(projNbt);
|
||||
|
||||
// Check if this projectile had different colors
|
||||
EnergyDisplayData tempDisplay = new EnergyDisplayData();
|
||||
tempDisplay.readNBT(projNbt);
|
||||
if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) {
|
||||
projectiles[i].colorOverride = true;
|
||||
projectiles[i].innerColor = tempDisplay.innerColor;
|
||||
projectiles[i].outerColor = tempDisplay.outerColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LEGACY flat format: all data at root level
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
projectiles[0].anchor.readNBT(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { homingData.speed = speed; }
|
||||
|
||||
public float getDiscRadius() { return discRadius; }
|
||||
public void setDiscRadius(float discRadius) { this.discRadius = discRadius; }
|
||||
|
||||
public float getDiscThickness() { return discThickness; }
|
||||
public void setDiscThickness(float discThickness) { this.discThickness = discThickness; }
|
||||
|
||||
public boolean isVertical() { return vertical; }
|
||||
public void setVertical(boolean vertical) { this.vertical = vertical; }
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; }
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; }
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float damage) { combatData.damage = damage; }
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float knockback) { combatData.knockback = knockback; }
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; }
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean homing) { homingData.homing = homing; }
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float homingStrength) { homingData.homingStrength = homingStrength; }
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float homingRange) { homingData.homingRange = homingRange; }
|
||||
|
||||
public boolean isBoomerang() { return boomerang; }
|
||||
public void setBoomerang(boolean boomerang) { this.boomerang = boomerang; }
|
||||
|
||||
public int getBoomerangDelay() { return boomerangDelay; }
|
||||
public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; }
|
||||
|
||||
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 explosive) { combatData.explosive = explosive; }
|
||||
public void setExplosive(boolean e) { combatData.explosive = e; }
|
||||
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; }
|
||||
public void setExplosionRadius(float r) { combatData.explosionRadius = r; }
|
||||
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) { combatData.explosionDamageFalloff = explosionDamageFalloff; }
|
||||
public int getInnerColor() { return colorData.innerColor; }
|
||||
public void setInnerColor(int innerColor) { colorData.innerColor = innerColor; }
|
||||
public int getOuterColor() { return colorData.outerColor; }
|
||||
public void setOuterColor(int outerColor) { colorData.outerColor = outerColor; }
|
||||
public float getOuterColorWidth() { return colorData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float outerColorWidth) { colorData.outerColorWidth = outerColorWidth; }
|
||||
public float getOuterColorAlpha() { return colorData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float outerColorAlpha) { colorData.outerColorAlpha = outerColorAlpha; }
|
||||
public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) { colorData.outerColorEnabled = outerColorEnabled; }
|
||||
public float getRotationSpeed() { return colorData.rotationSpeed; }
|
||||
public void setRotationSpeed(float rotationSpeed) { colorData.rotationSpeed = rotationSpeed; }
|
||||
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 lightningEffect) { lightningData.lightningEffect = lightningEffect; }
|
||||
public void setLightningEffect(boolean e) { lightningData.lightningEffect = e; }
|
||||
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float lightningDensity) { lightningData.lightningDensity = lightningDensity; }
|
||||
public void setLightningDensity(float d) { lightningData.lightningDensity = d; }
|
||||
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { 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 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 anchorData.anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); }
|
||||
|
||||
@Override
|
||||
public void setAnchorPoint(int point) { this.anchorData.anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
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); }
|
||||
|
||||
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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.section("ability.section.disc"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius),
|
||||
FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness)
|
||||
),
|
||||
FieldDef.boolField("ability.vertical", this::isVertical, this::setVertical)
|
||||
.hover("ability.hover.vertical"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming)
|
||||
.hover("ability.hover.homing"),
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength)
|
||||
.visibleWhen(this::isHoming),
|
||||
FieldDef.section("ability.section.boomerang"),
|
||||
FieldDef.boolField("gui.enabled", this::isBoomerang, this::setBoomerang)
|
||||
.hover("ability.hover.boomerang"),
|
||||
FieldDef.intField("gui.delay", this::getBoomerangDelay, this::setBoomerangDelay)
|
||||
.visibleWhen(this::isBoomerang),
|
||||
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),
|
||||
FieldDef.floatField("ability.anchor.offsetY", this::getAnchorOffsetY, this::setAnchorOffsetY)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", this::getAnchorOffsetZ, this::setAnchorOffsetZ)
|
||||
.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.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed)
|
||||
.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")
|
||||
// Type tab - projectile count and fire delay
|
||||
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)
|
||||
));
|
||||
defs.add(FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback));
|
||||
defs.add(FieldDef.section("ability.section.disc"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius),
|
||||
FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness)
|
||||
));
|
||||
defs.add(FieldDef.boolField("ability.vertical", this::isVertical, this::setVertical)
|
||||
.hover("ability.hover.vertical"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
));
|
||||
defs.add(FieldDef.section("ability.section.homing"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"));
|
||||
defs.add(FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming));
|
||||
defs.add(FieldDef.section("ability.section.boomerang"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isBoomerang, this::setBoomerang).hover("ability.hover.boomerang"));
|
||||
defs.add(FieldDef.intField("gui.delay", this::getBoomerangDelay, this::setBoomerangDelay).visibleWhen(this::isBoomerang));
|
||||
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));
|
||||
|
||||
// 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),
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
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.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.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.util.ValueUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AbilityDiscDual extends Ability {
|
||||
|
||||
// Disc geometry
|
||||
private float discRadius = 1.0f;
|
||||
private float discThickness = 0.2f;
|
||||
private boolean vertical = false;
|
||||
|
||||
// Boomerang properties
|
||||
private boolean boomerang = false;
|
||||
private int boomerangDelay = 40;
|
||||
|
||||
// Dual properties
|
||||
private boolean dualFire = true;
|
||||
private int dualFireDelay = 10;
|
||||
|
||||
// Energy data classes
|
||||
private final EnergyDisplayData[] displayData = new EnergyDisplayData[]{
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f),
|
||||
new EnergyDisplayData(0xFFFFFF, 0x8800FF, true, 0.4f, 0.5f, 5.0f)
|
||||
};
|
||||
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 EnergyLightningData[] lightningData = new EnergyLightningData[]{
|
||||
new EnergyLightningData(), new EnergyLightningData()
|
||||
};
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData(35.0f, 200);
|
||||
private final EnergyAnchorData[] anchorData = new EnergyAnchorData[]{
|
||||
new EnergyAnchorData(AnchorPoint.LEFT_HAND), new EnergyAnchorData(AnchorPoint.RIGHT_HAND)
|
||||
};
|
||||
private final EnergyTrajectoryData[] trajectoryData = new EnergyTrajectoryData[]{
|
||||
new EnergyTrajectoryData(), new EnergyTrajectoryData()
|
||||
};
|
||||
|
||||
// Transient state for disc entity (used during windup charging)
|
||||
private transient EntityAbilityDisc discEntity = null;
|
||||
private transient EntityAbilityDisc discEntity2 = null;
|
||||
|
||||
public AbilityDiscDual() {
|
||||
this.typeId = "ability.cnpc.disc_dual";
|
||||
this.name = "Disc Dual";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
this.maxRange = 30.0f;
|
||||
this.minRange = 5.0f;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 60;
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
this.windUpAnimationName = "Ability_DiscDual_Windup";
|
||||
this.activeAnimationName = "Ability_DiscDual_Active";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start moving the disc that was spawned during windup
|
||||
if (discEntity != null && !discEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
discEntity.startPreviewFiring();
|
||||
} else {
|
||||
discEntity.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (dualFire) {
|
||||
if (discEntity2 != null && !discEntity2.isDead && dualFireDelay <= 0) {
|
||||
if (isPreview()) {
|
||||
discEntity2.startPreviewFiring();
|
||||
} else {
|
||||
discEntity2.startMoving(target);
|
||||
}
|
||||
}
|
||||
} else if (discEntity2 != null) {
|
||||
discEntity2.setDead();
|
||||
}
|
||||
|
||||
// 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 onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn disc in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
// Create disc in charging mode - follows caster based on anchor point during windup
|
||||
Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0]);
|
||||
discEntity = new EntityAbilityDisc(
|
||||
world, caster, target,
|
||||
spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord,
|
||||
discRadius, discThickness,
|
||||
displayData[0], combatData, homingData, lightningData[0], lifespanData, trajectoryData[0],
|
||||
boomerang, boomerangDelay);
|
||||
|
||||
Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1]);
|
||||
discEntity2 = new EntityAbilityDisc(
|
||||
world, caster, target,
|
||||
spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord,
|
||||
discRadius, discThickness,
|
||||
displayData[1], combatData, homingData, lightningData[1], lifespanData, trajectoryData[1],
|
||||
boomerang, boomerangDelay);
|
||||
|
||||
discEntity.setSiblingEntityId(discEntity2.getEntityId());
|
||||
discEntity2.setSiblingEntityId(discEntity.getEntityId());
|
||||
|
||||
if (isPreview()) {
|
||||
discEntity.setupPreview(caster, discRadius, discThickness, displayData[0], lightningData[0], anchorData[0], windUpTicks, vertical);
|
||||
discEntity2.setupPreview(caster, discRadius, discThickness, displayData[1], lightningData[1], anchorData[1], windUpTicks, vertical);
|
||||
} else {
|
||||
discEntity.setupCharging(anchorData[0], windUpTicks, vertical);
|
||||
discEntity2.setupCharging(anchorData[1], windUpTicks, vertical);
|
||||
}
|
||||
|
||||
discEntity.setEffects(this.effects);
|
||||
|
||||
if (dualFire) {
|
||||
discEntity2.setEffects(this.effects);
|
||||
}
|
||||
|
||||
discEntity.setSourceAbility(this);
|
||||
discEntity2.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, discEntity);
|
||||
spawnAbilityEntity(world, discEntity2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (dualFire) {
|
||||
if ((discEntity == null || discEntity.isDead) && (discEntity2 == null || discEntity2.isDead)) {
|
||||
discEntity = null;
|
||||
discEntity2 = null;
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
if (tick == dualFireDelay && discEntity2 != null && !discEntity2.isDead) {
|
||||
if (isPreview()) {
|
||||
discEntity2.startPreviewFiring();
|
||||
} else {
|
||||
discEntity2.startMoving(target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (discEntity == null || discEntity.isDead) {
|
||||
discEntity = null;
|
||||
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() {
|
||||
// Despawn disc entity if still alive
|
||||
if (discEntity != null && !discEntity.isDead) {
|
||||
discEntity.setDead();
|
||||
}
|
||||
|
||||
if (discEntity2 != null && !discEntity2.isDead) {
|
||||
discEntity2.setDead();
|
||||
}
|
||||
|
||||
discEntity = null;
|
||||
discEntity2 = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || 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) {
|
||||
nbt.setFloat("discRadius", discRadius);
|
||||
nbt.setFloat("discThickness", discThickness);
|
||||
nbt.setBoolean("vertical", vertical);
|
||||
nbt.setBoolean("boomerang", boomerang);
|
||||
nbt.setInteger("boomerangDelay", boomerangDelay);
|
||||
nbt.setBoolean("dualFire", dualFire);
|
||||
nbt.setInteger("dualFireDelay", dualFireDelay);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
|
||||
NBTTagCompound disc1 = new NBTTagCompound();
|
||||
NBTTagCompound disc2 = new NBTTagCompound();
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound disc = i == 0 ? disc1 : disc2;
|
||||
anchorData[i].writeNBT(disc);
|
||||
displayData[i].writeNBT(disc);
|
||||
lightningData[i].writeNBT(disc);
|
||||
trajectoryData[i].writeNBT(disc);
|
||||
}
|
||||
|
||||
nbt.setTag("Disc_1", disc1);
|
||||
nbt.setTag("Disc_2", disc2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
NBTTagCompound disc1 = nbt.hasKey("Disc_1") ? nbt.getCompoundTag("Disc_1") : new NBTTagCompound();
|
||||
NBTTagCompound disc2 = nbt.hasKey("Disc_2") ? nbt.getCompoundTag("Disc_2") : new NBTTagCompound();
|
||||
|
||||
this.discRadius = nbt.hasKey("discRadius") ? nbt.getFloat("discRadius") : 1.0f;
|
||||
this.discThickness = nbt.hasKey("discThickness") ? nbt.getFloat("discThickness") : 0.2f;
|
||||
this.vertical = nbt.hasKey("vertical") && nbt.getBoolean("vertical");
|
||||
this.boomerang = nbt.hasKey("boomerang") && nbt.getBoolean("boomerang");
|
||||
this.boomerangDelay = nbt.hasKey("boomerangDelay") ? nbt.getInteger("boomerangDelay") : 40;
|
||||
this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire");
|
||||
this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0;
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound disc = i == 0 ? disc1 : disc2;
|
||||
anchorData[i].readNBT(disc);
|
||||
displayData[i].readNBT(disc);
|
||||
lightningData[i].readNBT(disc);
|
||||
trajectoryData[i].readNBT(disc);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
private EnergyDisplayData getColorData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return displayData[orb];
|
||||
}
|
||||
private EnergyLightningData getLightningData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return lightningData[orb];
|
||||
}
|
||||
private EnergyAnchorData getAnchorData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return anchorData[orb];
|
||||
}
|
||||
|
||||
public boolean isDualFire() { return dualFire; }
|
||||
public void setDualFire(boolean dualFire) { this.dualFire = dualFire; }
|
||||
public int getDualFireDelay() { return dualFireDelay; }
|
||||
public void setDualFireDelay(int dualFireDelay) { this.dualFireDelay = dualFireDelay; }
|
||||
public float getSpeed() { return homingData.speed; }
|
||||
public void setSpeed(float speed) { homingData.speed = speed; }
|
||||
public float getDiscRadius() { return discRadius; }
|
||||
public void setDiscRadius(float discRadius) { this.discRadius = discRadius; }
|
||||
public float getDiscThickness() { return discThickness; }
|
||||
public void setDiscThickness(float discThickness) { this.discThickness = discThickness; }
|
||||
public boolean isVertical() { return vertical; }
|
||||
public void setVertical(boolean vertical) { this.vertical = vertical; }
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; }
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; }
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float damage) { combatData.damage = damage; }
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float knockback) { combatData.knockback = knockback; }
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; }
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean homing) { homingData.homing = homing; }
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float homingStrength) { homingData.homingStrength = homingStrength; }
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float homingRange) { homingData.homingRange = homingRange; }
|
||||
public boolean isBoomerang() { return boomerang; }
|
||||
public void setBoomerang(boolean boomerang) { this.boomerang = boomerang; }
|
||||
public int getBoomerangDelay() { return boomerangDelay; }
|
||||
public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; }
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean explosive) { combatData.explosive = explosive; }
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; }
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) { combatData.explosionDamageFalloff = explosionDamageFalloff; }
|
||||
|
||||
|
||||
public int getInnerColor(int orb) { return getColorData(orb).innerColor; }
|
||||
public void setInnerColor(int orb, int innerColor) { getColorData(orb).innerColor = innerColor; }
|
||||
public int getOuterColor(int orb) { return getColorData(orb).outerColor; }
|
||||
public void setOuterColor(int orb, int outerColor) { getColorData(orb).outerColor = outerColor; }
|
||||
public boolean isOuterColorEnabled(int orb) { return getColorData(orb).outerColorEnabled; }
|
||||
public void setOuterColorEnabled(int orb, boolean enabled) { getColorData(orb).outerColorEnabled = enabled; }
|
||||
public float getOuterColorWidth(int orb) { return getColorData(orb).outerColorWidth; }
|
||||
public void setOuterColorWidth(int orb, float width) { getColorData(orb).outerColorWidth = width; }
|
||||
public float getOuterColorAlpha(int orb) { return getColorData(orb).outerColorAlpha; }
|
||||
public void setOuterColorAlpha(int orb, float alpha) { getColorData(orb).outerColorAlpha = alpha; }
|
||||
public float getRotationSpeed(int orb) { return getColorData(orb).rotationSpeed; }
|
||||
public void setRotationSpeed(int orb, float speed) { getColorData(orb).rotationSpeed = speed; }
|
||||
|
||||
public int getInnerColor() { return getInnerColor(0); }
|
||||
public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); }
|
||||
public int getOuterColor() { return getOuterColor(0); }
|
||||
public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); }
|
||||
public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); }
|
||||
public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); }
|
||||
public float getOuterColorWidth() { return getOuterColorWidth(0); }
|
||||
public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); }
|
||||
public float getOuterColorAlpha() { return getOuterColorAlpha(0); }
|
||||
public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); }
|
||||
public float getRotationSpeed() { return getRotationSpeed(0); }
|
||||
public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); }
|
||||
|
||||
|
||||
public boolean hasLightningEffect(int orb) { return getLightningData(orb).lightningEffect; }
|
||||
public void setLightningEffect(int orb, boolean lightningEffect) { getLightningData(orb).lightningEffect = lightningEffect; }
|
||||
public float getLightningDensity(int orb) { return getLightningData(orb).lightningDensity; }
|
||||
public void setLightningDensity(int orb, float lightningDensity) { getLightningData(orb).lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius(int orb) { return getLightningData(orb).lightningRadius; }
|
||||
public void setLightningRadius(int orb, float lightningRadius) { getLightningData(orb).lightningRadius = lightningRadius; }
|
||||
|
||||
public boolean hasLightningEffect() { return hasLightningEffect(0); }
|
||||
public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); }
|
||||
public float getLightningDensity() { return getLightningDensity(0); }
|
||||
public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); }
|
||||
public float getLightningRadius() { return getLightningRadius(0); }
|
||||
public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); }
|
||||
|
||||
|
||||
public AnchorPoint getAnchorPointEnum(int orb) { return getAnchorData(orb).anchorPoint; }
|
||||
public void setAnchorPointEnum(int orb, AnchorPoint anchorPoint) { getAnchorData(orb).anchorPoint = anchorPoint; }
|
||||
public float getAnchorOffsetX(int orb) { return getAnchorData(orb).anchorOffsetX; }
|
||||
public void setAnchorOffsetX(int orb, float x) { getAnchorData(orb).anchorOffsetX = x; }
|
||||
public float getAnchorOffsetY(int orb) { return getAnchorData(orb).anchorOffsetY; }
|
||||
public void setAnchorOffsetY(int orb, float y) { getAnchorData(orb).anchorOffsetY = y; }
|
||||
public float getAnchorOffsetZ(int orb) { return getAnchorData(orb).anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(int orb, float z) { getAnchorData(orb).anchorOffsetZ = z; }
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); }
|
||||
public float getAnchorOffsetX() { return getAnchorOffsetX(0); }
|
||||
public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); }
|
||||
public float getAnchorOffsetY() { return getAnchorOffsetY(0); }
|
||||
public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); }
|
||||
public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); }
|
||||
public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); }
|
||||
|
||||
//@Override
|
||||
public int getAnchorPoint(int orb) { return getAnchorData(orb).anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); }
|
||||
|
||||
//@Override
|
||||
public void setAnchorPoint(int orb, int point) { getAnchorData(orb).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed)
|
||||
),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
|
||||
FieldDef.section("ability.section.disc"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius),
|
||||
FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness)
|
||||
),
|
||||
FieldDef.boolField("ability.vertical", this::isVertical, this::setVertical)
|
||||
.hover("ability.hover.vertical"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.dual"),
|
||||
FieldDef.boolField("ability.dualFire", this::isDualFire, this::setDualFire),
|
||||
FieldDef.intField("ability.dualFireDelay", this::getDualFireDelay, this::setDualFireDelay)
|
||||
.visibleWhen(this::isDualFire),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming)
|
||||
.hover("ability.hover.homing"),
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength)
|
||||
.visibleWhen(this::isHoming),
|
||||
FieldDef.section("ability.section.boomerang"),
|
||||
FieldDef.boolField("gui.enabled", this::isBoomerang, this::setBoomerang)
|
||||
.hover("ability.hover.boomerang"),
|
||||
FieldDef.intField("gui.delay", this::getBoomerangDelay, this::setBoomerangDelay)
|
||||
.visibleWhen(this::isBoomerang),
|
||||
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 - Disc 1
|
||||
FieldDef.section("ability.section.disc1").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(0), v -> setAnchorPointEnum(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(0), v -> setAnchorOffsetX(0, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(0), v -> setAnchorOffsetY(0, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(0), v -> setAnchorOffsetZ(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(0), v -> setInnerColor(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(0), v -> setOuterColorEnabled(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(0), v -> setOuterColor(0, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(0))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(0), v -> setRotationSpeed(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(0), v -> setLightningEffect(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(0), v -> setLightningDensity(0, v))
|
||||
.range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(0)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(0), v -> setLightningRadius(0, v))
|
||||
.range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(0))
|
||||
).tab("ability.tab.visual"),
|
||||
// Visual tab - Disc 2
|
||||
FieldDef.section("ability.section.disc2").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(1), v -> setAnchorOffsetX(1, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(1), v -> setAnchorOffsetY(1, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(1), v -> setAnchorOffsetZ(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(1), v -> setInnerColor(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(1), v -> setOuterColorEnabled(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(1), v -> setOuterColor(1, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(1))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(1), v -> setRotationSpeed(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(1), v -> setLightningEffect(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(1), v -> setLightningDensity(1, v))
|
||||
.range(0.01f, 100f).visibleWhen(() -> hasLightningEffect(1)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(1), v -> setLightningRadius(1, v))
|
||||
.range(0.1f, 100f).visibleWhen(() -> hasLightningEffect(1))
|
||||
).tab("ability.tab.visual")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,11 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public enum CounterType {
|
||||
FLAT,
|
||||
PERCENT;
|
||||
|
||||
@@ -113,6 +113,11 @@ public class AbilityHazard extends Ability implements IAbilityHazard {
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
|
||||
@@ -63,6 +63,11 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
|
||||
@@ -63,6 +63,11 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
this.activeAnimationName = "Ability_Laser_Active";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return false;
|
||||
@@ -83,32 +88,47 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
return laserWidth * 2.0f; // Make telegraph slightly wider for visibility
|
||||
}
|
||||
|
||||
private EntityAbilityLaser spawnLaserEntity(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData);
|
||||
EntityAbilityLaser entity = new EntityAbilityLaser(
|
||||
world, 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;
|
||||
}
|
||||
|
||||
private void fireLaserEntity(EntityAbilityLaser laser, EntityLivingBase target) {
|
||||
if (laser == null || laser.isDead) return;
|
||||
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) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData);
|
||||
laserEntity = new EntityAbilityLaser(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
laserWidth,
|
||||
colorData, combatData, lightningData, lifespanData, trajectoryData,
|
||||
expansionSpeed, lingerTicks
|
||||
);
|
||||
|
||||
laserEntity = spawnLaserEntity(caster, target, world);
|
||||
if (isPreview()) {
|
||||
laserEntity.setupPreview(caster, laserWidth, colorData, lightningData, expansionSpeed, lifespanData.maxDistance);
|
||||
} else {
|
||||
laserEntity.setupCharging(anchorData, windUpTicks);
|
||||
}
|
||||
|
||||
laserEntity.setEffects(this.effects);
|
||||
laserEntity.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, laserEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBurstRefire(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
laserEntity = spawnLaserEntity(caster, target, world);
|
||||
fireLaserEntity(laserEntity, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
@@ -121,8 +141,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
return;
|
||||
}
|
||||
|
||||
laserEntity.setLockVerticalDirection(true);
|
||||
laserEntity.startMoving(target);
|
||||
fireLaserEntity(laserEntity, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,6 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityOrb;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
@@ -23,29 +22,38 @@ import noppes.npcs.api.ability.type.IAbilityOrb;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
|
||||
/**
|
||||
* Orb ability: Spawns a homing projectile sphere that tracks target.
|
||||
* The EntityAbilityOrb handles all movement, collision, and damage logic.
|
||||
* This ability just configures and spawns the orb entity.
|
||||
* 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 {
|
||||
|
||||
// Ability-specific properties
|
||||
private static final int MAX_PROJECTILES = 8;
|
||||
|
||||
// Orb properties
|
||||
private float orbSize = 1.0f;
|
||||
|
||||
// Data classes for energy properties
|
||||
private final EnergyAnchorData anchorData = new EnergyAnchorData(AnchorPoint.RIGHT_HAND);
|
||||
private final EnergyCombatData combatData = new EnergyCombatData();
|
||||
private final EnergyDisplayData displayData = new EnergyDisplayData();
|
||||
private final EnergyHomingData homingData = new EnergyHomingData();
|
||||
private final EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData();
|
||||
private final EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
// Projectile count and fire stagger
|
||||
private int projectileCount = 1;
|
||||
private int fireDelay = 0;
|
||||
|
||||
// Transient state for orb entity (used during windup charging)
|
||||
private transient EntityAbilityOrb orbEntity = null;
|
||||
// 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() {
|
||||
this.typeId = "ability.cnpc.orb";
|
||||
@@ -58,9 +66,100 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animation
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorPoint getDefaultAnchor(int index) {
|
||||
switch (index) {
|
||||
case 0: return AnchorPoint.RIGHT_HAND;
|
||||
case 1: return AnchorPoint.LEFT_HAND;
|
||||
default: return AnchorPoint.FRONT;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getProjectileCount() {
|
||||
return projectileCount;
|
||||
}
|
||||
|
||||
public int getFireDelay() {
|
||||
return fireDelay;
|
||||
}
|
||||
|
||||
public void setFireDelay(int delay) {
|
||||
this.fireDelay = Math.max(0, delay);
|
||||
}
|
||||
|
||||
// Legacy compat
|
||||
public int getDualFireDelay() { return fireDelay; }
|
||||
public void setDualFireDelay(int delay) { setFireDelay(delay); }
|
||||
|
||||
@Override
|
||||
public List<AbilityVariant> getVariants() {
|
||||
return Arrays.asList(
|
||||
new AbilityVariant("ability.variant.single", a -> {
|
||||
a.setName("Orb");
|
||||
}),
|
||||
new AbilityVariant("ability.variant.dual", a -> {
|
||||
AbilityOrb orb = (AbilityOrb) a;
|
||||
a.setName("Dual Orb");
|
||||
orb.setProjectileCount(2);
|
||||
orb.setFireDelay(5);
|
||||
a.setWindUpAnimationName("Ability_OrbDual_Windup");
|
||||
a.setActiveAnimationName("Ability_OrbDual_Active");
|
||||
}),
|
||||
new AbilityVariant("ability.variant.barrage", a -> {
|
||||
AbilityOrb orb = (AbilityOrb) a;
|
||||
a.setName("Orb Barrage");
|
||||
orb.setOrbSize(0.5f);
|
||||
orb.setDamage(4.0f);
|
||||
orb.setKnockback(0.0f);
|
||||
orb.setKnockbackUp(0.0f);
|
||||
a.setMaxRange(75.0f);
|
||||
a.setBurstEnabled(true);
|
||||
a.setBurstAmount(15);
|
||||
a.setBurstDelay(10);
|
||||
a.setBurstReplayAnimations(false);
|
||||
a.setBurstOverlap(true);
|
||||
a.setWindUpAnimationName("Ability_BeamDual_Windup");
|
||||
a.setActiveAnimationName("Ability_BeamDual_Active");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowOverlap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,6 +172,63 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
for (EntityAbilityOrb orb : orbEntities) {
|
||||
fireOrbEntity(orb, target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
@@ -80,55 +236,50 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start moving the orb that was spawned during windup
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
orbEntity.startPreviewFiring();
|
||||
} else {
|
||||
orbEntity.startMoving(target);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn orb in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData);
|
||||
orbEntity = new EntityAbilityOrb(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize,
|
||||
displayData, combatData, homingData, lightningData, lifespanData, trajectoryData);
|
||||
|
||||
if (isPreview()) {
|
||||
orbEntity.setupPreview(caster, orbSize, displayData, lightningData, anchorData, windUpTicks);
|
||||
} else {
|
||||
orbEntity.setupCharging(anchorData, windUpTicks);
|
||||
}
|
||||
|
||||
orbEntity.setEffects(this.effects);
|
||||
orbEntity.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, orbEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (orbEntity == null || orbEntity.isDead) {
|
||||
orbEntity = null;
|
||||
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) {
|
||||
// Nothing to clean up - entity manages itself
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,11 +289,15 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Despawn orb entity if still alive
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
orbEntity.setDead();
|
||||
if (orbEntities != null) {
|
||||
for (int i = 0; i < orbEntities.length; i++) {
|
||||
if (orbEntities[i] != null && !orbEntities[i].isDead) {
|
||||
orbEntities[i].setDead();
|
||||
}
|
||||
orbEntities[i] = null;
|
||||
}
|
||||
}
|
||||
orbEntity = null;
|
||||
orbEntities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,7 +306,6 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create small circle telegraph at target position
|
||||
Telegraph telegraph = Telegraph.circle(orbSize * 1.5f);
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
@@ -159,7 +313,6 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
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());
|
||||
@@ -175,241 +328,217 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("orbSize", orbSize);
|
||||
anchorData.writeNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
nbt.setInteger("projectileCount", projectileCount);
|
||||
nbt.setInteger("fireDelay", fireDelay);
|
||||
|
||||
// Shared visual data
|
||||
displayData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
trajectoryData.writeNBT(nbt);
|
||||
// Backward compat: old key was "orbSpeed"
|
||||
|
||||
// Per-projectile data
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
NBTTagCompound projNbt = new NBTTagCompound();
|
||||
projectiles[i].writeNBT(projNbt);
|
||||
nbt.setTag("Projectile_" + i, projNbt);
|
||||
}
|
||||
|
||||
// Backward compat write
|
||||
nbt.setFloat("orbSpeed", homingData.speed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
this.orbSize = nbt.hasKey("orbSize") ? nbt.getFloat("orbSize") : 1.0f;
|
||||
anchorData.readNBT(nbt);
|
||||
|
||||
int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1;
|
||||
initProjectiles(count);
|
||||
|
||||
// Read fireDelay with fallback to old dualFireDelay key
|
||||
if (nbt.hasKey("fireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("fireDelay");
|
||||
} else if (nbt.hasKey("dualFireDelay")) {
|
||||
this.fireDelay = nbt.getInteger("dualFireDelay");
|
||||
} else {
|
||||
this.fireDelay = 0;
|
||||
}
|
||||
|
||||
// Shared combat/movement data
|
||||
combatData.readNBT(nbt);
|
||||
displayData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
trajectoryData.readNBT(nbt);
|
||||
// Backward compat: old key was "orbSpeed"
|
||||
|
||||
// Determine format and read accordingly
|
||||
if (nbt.hasKey("Projectile_0")) {
|
||||
NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0");
|
||||
if (proj0.hasKey("colorOverride")) {
|
||||
// NEW format: shared visuals at root + ProjectileData per projectile
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
for (int i = 0; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// OLD format: full per-projectile arrays (displayData, lightningData, etc. in each Projectile_i)
|
||||
// Read primary visuals from Projectile_0
|
||||
displayData.readNBT(proj0);
|
||||
lightningData.readNBT(proj0);
|
||||
projectiles[0].anchor.readNBT(proj0);
|
||||
|
||||
// Read remaining projectiles, set color override if colors differ
|
||||
for (int i = 1; i < projectileCount; i++) {
|
||||
if (nbt.hasKey("Projectile_" + i)) {
|
||||
NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i);
|
||||
projectiles[i].anchor.readNBT(projNbt);
|
||||
|
||||
// Check if this projectile had different colors
|
||||
EnergyDisplayData tempDisplay = new EnergyDisplayData();
|
||||
tempDisplay.readNBT(projNbt);
|
||||
if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) {
|
||||
projectiles[i].colorOverride = true;
|
||||
projectiles[i].innerColor = tempDisplay.innerColor;
|
||||
projectiles[i].outerColor = tempDisplay.outerColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LEGACY flat format: all data at root level
|
||||
displayData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
projectiles[0].anchor.readNBT(nbt);
|
||||
}
|
||||
|
||||
// Backward compat
|
||||
if (nbt.hasKey("orbSpeed")) {
|
||||
homingData.speed = nbt.getFloat("orbSpeed");
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
public float getOrbSpeed() {
|
||||
return homingData.speed;
|
||||
}
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// API GETTERS & SETTERS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public void setOrbSpeed(float orbSpeed) {
|
||||
homingData.speed = orbSpeed;
|
||||
}
|
||||
public float getOrbSpeed() { return homingData.speed; }
|
||||
public void setOrbSpeed(float speed) { homingData.speed = speed; }
|
||||
|
||||
public float getOrbSize() {
|
||||
return orbSize;
|
||||
}
|
||||
public float getOrbSize() { return orbSize; }
|
||||
public void setOrbSize(float size) { this.orbSize = size; }
|
||||
|
||||
public void setOrbSize(float orbSize) {
|
||||
this.orbSize = orbSize;
|
||||
}
|
||||
public float getMaxDistance() { return lifespanData.maxDistance; }
|
||||
public void setMaxDistance(float d) { lifespanData.maxDistance = d; }
|
||||
|
||||
public float getMaxDistance() {
|
||||
return lifespanData.maxDistance;
|
||||
}
|
||||
public int getMaxLifetime() { return lifespanData.maxLifetime; }
|
||||
public void setMaxLifetime(int t) { lifespanData.maxLifetime = t; }
|
||||
|
||||
public void setMaxDistance(float maxDistance) {
|
||||
lifespanData.maxDistance = maxDistance;
|
||||
}
|
||||
public float getDamage() { return combatData.damage; }
|
||||
public void setDamage(float d) { combatData.damage = d; }
|
||||
|
||||
public int getMaxLifetime() {
|
||||
return lifespanData.maxLifetime;
|
||||
}
|
||||
public float getKnockback() { return combatData.knockback; }
|
||||
public void setKnockback(float k) { combatData.knockback = k; }
|
||||
|
||||
public void setMaxLifetime(int maxLifetime) {
|
||||
lifespanData.maxLifetime = maxLifetime;
|
||||
}
|
||||
public float getKnockbackUp() { return combatData.knockbackUp; }
|
||||
public void setKnockbackUp(float k) { combatData.knockbackUp = k; }
|
||||
|
||||
public float getDamage() {
|
||||
return combatData.damage;
|
||||
}
|
||||
public boolean isHoming() { return homingData.homing; }
|
||||
public void setHoming(boolean h) { homingData.homing = h; }
|
||||
|
||||
public void setDamage(float damage) {
|
||||
combatData.damage = damage;
|
||||
}
|
||||
public float getHomingStrength() { return homingData.homingStrength; }
|
||||
public void setHomingStrength(float s) { homingData.homingStrength = s; }
|
||||
|
||||
public float getKnockback() {
|
||||
return combatData.knockback;
|
||||
}
|
||||
public float getHomingRange() { return homingData.homingRange; }
|
||||
public void setHomingRange(float r) { homingData.homingRange = r; }
|
||||
|
||||
public void setKnockback(float knockback) {
|
||||
combatData.knockback = knockback;
|
||||
}
|
||||
public boolean isExplosive() { return combatData.explosive; }
|
||||
public void setExplosive(boolean e) { combatData.explosive = e; }
|
||||
|
||||
public float getKnockbackUp() {
|
||||
return combatData.knockbackUp;
|
||||
}
|
||||
public float getExplosionRadius() { return combatData.explosionRadius; }
|
||||
public void setExplosionRadius(float r) { combatData.explosionRadius = r; }
|
||||
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
combatData.knockbackUp = knockbackUp;
|
||||
}
|
||||
public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; }
|
||||
public void setExplosionDamageFalloff(float f) { combatData.explosionDamageFalloff = f; }
|
||||
|
||||
public boolean isHoming() {
|
||||
return homingData.homing;
|
||||
}
|
||||
// Shared display data (primary colors)
|
||||
public int getInnerColor() { return displayData.innerColor; }
|
||||
public void setInnerColor(int c) { displayData.innerColor = c; }
|
||||
|
||||
public void setHoming(boolean homing) {
|
||||
homingData.homing = homing;
|
||||
}
|
||||
public int getOuterColor() { return displayData.outerColor; }
|
||||
public void setOuterColor(int c) { displayData.outerColor = c; }
|
||||
|
||||
public float getHomingStrength() {
|
||||
return homingData.homingStrength;
|
||||
}
|
||||
public float getOuterColorWidth() { return displayData.outerColorWidth; }
|
||||
public void setOuterColorWidth(float w) { displayData.outerColorWidth = w; }
|
||||
|
||||
public void setHomingStrength(float homingStrength) {
|
||||
homingData.homingStrength = homingStrength;
|
||||
}
|
||||
public float getOuterColorAlpha() { return displayData.outerColorAlpha; }
|
||||
public void setOuterColorAlpha(float a) { displayData.outerColorAlpha = a; }
|
||||
|
||||
public float getHomingRange() {
|
||||
return homingData.homingRange;
|
||||
}
|
||||
public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; }
|
||||
public void setOuterColorEnabled(boolean e) { displayData.outerColorEnabled = e; }
|
||||
|
||||
public void setHomingRange(float homingRange) {
|
||||
homingData.homingRange = homingRange;
|
||||
}
|
||||
public float getRotationSpeed() { return displayData.rotationSpeed; }
|
||||
public void setRotationSpeed(float s) { displayData.rotationSpeed = s; }
|
||||
|
||||
public boolean isExplosive() {
|
||||
return combatData.explosive;
|
||||
}
|
||||
// Shared lightning data
|
||||
public boolean hasLightningEffect() { return lightningData.lightningEffect; }
|
||||
public void setLightningEffect(boolean e) { lightningData.lightningEffect = e; }
|
||||
|
||||
public void setExplosive(boolean explosive) {
|
||||
combatData.explosive = explosive;
|
||||
}
|
||||
public float getLightningDensity() { return lightningData.lightningDensity; }
|
||||
public void setLightningDensity(float d) { lightningData.lightningDensity = d; }
|
||||
|
||||
public float getExplosionRadius() {
|
||||
return combatData.explosionRadius;
|
||||
}
|
||||
public float getLightningRadius() { return lightningData.lightningRadius; }
|
||||
public void setLightningRadius(float r) { lightningData.lightningRadius = r; }
|
||||
|
||||
public void setExplosionRadius(float explosionRadius) {
|
||||
combatData.explosionRadius = explosionRadius;
|
||||
}
|
||||
public int getLightningFadeTime() { return lightningData.lightningFadeTime; }
|
||||
public void setLightningFadeTime(int t) { lightningData.lightningFadeTime = t; }
|
||||
|
||||
public float getExplosionDamageFalloff() {
|
||||
return combatData.explosionDamageFalloff;
|
||||
}
|
||||
// Anchor - default to projectile 0
|
||||
public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint p) { projectiles[0].anchor.anchorPoint = p; }
|
||||
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) {
|
||||
combatData.explosionDamageFalloff = explosionDamageFalloff;
|
||||
}
|
||||
public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; }
|
||||
public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; }
|
||||
|
||||
public int getInnerColor() {
|
||||
return displayData.innerColor;
|
||||
}
|
||||
public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; }
|
||||
public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; }
|
||||
|
||||
public void setInnerColor(int innerColor) {
|
||||
displayData.innerColor = innerColor;
|
||||
}
|
||||
|
||||
public int getOuterColor() {
|
||||
return displayData.outerColor;
|
||||
}
|
||||
|
||||
public void setOuterColor(int outerColor) {
|
||||
displayData.outerColor = outerColor;
|
||||
}
|
||||
|
||||
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 boolean isOuterColorEnabled() {
|
||||
return displayData.outerColorEnabled;
|
||||
}
|
||||
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) {
|
||||
displayData.outerColorEnabled = outerColorEnabled;
|
||||
}
|
||||
|
||||
public float getRotationSpeed() {
|
||||
return displayData.rotationSpeed;
|
||||
}
|
||||
|
||||
public void setRotationSpeed(float rotationSpeed) {
|
||||
displayData.rotationSpeed = rotationSpeed;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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) {
|
||||
this.anchorData.anchorPoint = AnchorPoint.fromOrdinal(point);
|
||||
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); }
|
||||
|
||||
private int clampIndex(int index) {
|
||||
return Math.max(0, Math.min(index, projectileCount - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,61 +546,95 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// GUI FIELD DEFINITIONS
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"),
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming),
|
||||
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),
|
||||
FieldDef.floatField("ability.anchor.offsetY", this::getAnchorOffsetY, this::setAnchorOffsetY)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", this::getAnchorOffsetZ, this::setAnchorOffsetZ)
|
||||
.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.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed).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)
|
||||
.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")
|
||||
// Type tab - projectile count and fire delay
|
||||
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)
|
||||
));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
|
||||
));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
));
|
||||
defs.add(FieldDef.section("ability.section.homing"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"));
|
||||
defs.add(FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming));
|
||||
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));
|
||||
|
||||
// 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),
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,612 +0,0 @@
|
||||
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.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.EntityAbilityOrb;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
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 net.minecraftforge.common.util.Constants;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.util.ValueUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AbilityOrbBarrage extends Ability {
|
||||
|
||||
// Ability-specific properties
|
||||
private float orbSize = 0.5f;
|
||||
private int orbAmount = 16;
|
||||
private int firingSpeed = 2;
|
||||
private boolean dualCharging = true;
|
||||
|
||||
// Data classes for energy properties
|
||||
private final EnergyDisplayData colorData = new EnergyDisplayData();
|
||||
private final EnergyCombatData combatData = new EnergyCombatData(4.0f, 0.0f, 0.0f, false, 1.5f, 0.5f);
|
||||
private final EnergyHomingData homingData = new EnergyHomingData();
|
||||
private final EnergyLightningData lightningData = new EnergyLightningData();
|
||||
private final EnergyLifespanData lifespanData = new EnergyLifespanData();
|
||||
private final EnergyAnchorData[] anchorData = new EnergyAnchorData[]{
|
||||
new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND)
|
||||
};
|
||||
private final EnergyTrajectoryData trajectoryData = new EnergyTrajectoryData();
|
||||
|
||||
|
||||
// Array to store entities
|
||||
private transient List<EntityAbilityOrb> orbEntities = new ArrayList<>();
|
||||
private transient EntityAbilityOrb[] chargingEntities = null;
|
||||
|
||||
// Field to keep track of the currently firing orb
|
||||
private int currentOrb = 0;
|
||||
|
||||
public AbilityOrbBarrage() {
|
||||
this.typeId = "ability.cnpc.orb_barrage";
|
||||
this.name = "Orb Barrage";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
this.maxRange = 75.0f;
|
||||
this.minRange = 5.0f;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 30;
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animation
|
||||
this.windUpAnimationName = "Ability_BeamDual_Windup";
|
||||
this.activeAnimationName = "Ability_BeamDual_Active";
|
||||
}
|
||||
|
||||
// @Override
|
||||
// @SideOnly(Side.CLIENT)
|
||||
// public SubGuiAbilityConfig createConfigGui(IAbilityConfigCallback callback) {
|
||||
// return new SubGuiAbilityOrbBarrage(this, callback);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chargingEntities.length; i++) {
|
||||
EntityAbilityOrb orbEntity = chargingEntities[i];
|
||||
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
orbEntity.setDead();
|
||||
}
|
||||
|
||||
chargingEntities[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn orb in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
if (dualCharging) {
|
||||
chargingEntities = new EntityAbilityOrb[2];
|
||||
} else {
|
||||
chargingEntities = new EntityAbilityOrb[1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < chargingEntities.length; i++) {
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[i]);
|
||||
|
||||
EntityAbilityOrb orbEntity = new EntityAbilityOrb(
|
||||
world, caster, target,
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize,
|
||||
colorData, combatData, homingData, lightningData, lifespanData, trajectoryData
|
||||
);
|
||||
|
||||
if (isPreview()) {
|
||||
orbEntity.setupPreview(caster, orbSize, colorData, lightningData, anchorData[i], windUpTicks);
|
||||
} else {
|
||||
orbEntity.setupCharging(anchorData[i], windUpTicks);
|
||||
}
|
||||
orbEntity.setSourceAbility(this);
|
||||
|
||||
chargingEntities[i] = orbEntity;
|
||||
spawnAbilityEntity(world, orbEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (!orbEntities.isEmpty()) {
|
||||
boolean allDead = true;
|
||||
|
||||
for (EntityAbilityOrb orbEntity : orbEntities) {
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
allDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDead && currentOrb >= orbAmount) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentOrb >= orbAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ticksPerOrb = Math.max(1, 20 / firingSpeed);
|
||||
|
||||
if (tick % ticksPerOrb != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnergyAnchorData anchor;
|
||||
|
||||
if (dualCharging) {
|
||||
anchor = anchorData[currentOrb % 2];
|
||||
} else {
|
||||
anchor = anchorData[0];
|
||||
}
|
||||
|
||||
Vec3 spawnPos = AnchorPointHelper.calculateAnchorPosition(caster, anchor);
|
||||
|
||||
EntityAbilityOrb orb = new EntityAbilityOrb(
|
||||
world, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
orbSize, colorData, combatData, homingData, lightningData, lifespanData, trajectoryData
|
||||
);
|
||||
|
||||
orb.setSourceAbility(this);
|
||||
world.spawnEntityInWorld(orb);
|
||||
|
||||
orbEntities.add(orb);
|
||||
currentOrb++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInterrupt(DamageSource source) {
|
||||
// Can be interrupted if the caster is hit when the beam is moving
|
||||
if (!interruptible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only direct physical hits can interrupt, not magic, fire, or other indirect damage
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject indirect damage types
|
||||
if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject damage without a direct attacker entity
|
||||
if (source.getEntity() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Direct hit from an entity - can interrupt
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
currentOrb = 0;
|
||||
|
||||
if (chargingEntities != null) {
|
||||
for (int i = 0; i < chargingEntities.length; i++) {
|
||||
if (chargingEntities[i] != null && !chargingEntities[i].isDead) {
|
||||
chargingEntities[i].setDead();
|
||||
}
|
||||
chargingEntities[i] = null;
|
||||
}
|
||||
chargingEntities = null;
|
||||
}
|
||||
|
||||
if (orbEntities != null && !orbEntities.isEmpty()) {
|
||||
for (EntityAbilityOrb orbEntity : orbEntities) {
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
orbEntity.setDead();
|
||||
}
|
||||
}
|
||||
orbEntities.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create small circle telegraph at target position
|
||||
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);
|
||||
|
||||
// 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 orbSize * 1.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("orbSize", orbSize);
|
||||
nbt.setInteger("orbAmount", orbAmount);
|
||||
nbt.setInteger("firingSpeed", firingSpeed);
|
||||
nbt.setBoolean("dualCharging", dualCharging);
|
||||
colorData.writeNBT(nbt);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lightningData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
trajectoryData.writeNBT(nbt);
|
||||
|
||||
for (int i = 0; i < anchorData.length; i++) {
|
||||
NBTTagCompound comp = new NBTTagCompound();
|
||||
anchorData[i].writeNBT(comp);
|
||||
nbt.setTag("Anchor_" + i, comp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
this.orbSize = nbt.hasKey("orbSize") ? nbt.getFloat("orbSize") : 1.0f;
|
||||
this.orbAmount = nbt.hasKey("orbAmount") ? nbt.getInteger("orbAmount") : 16;
|
||||
this.firingSpeed = nbt.hasKey("firingSpeed") ? nbt.getInteger("firingSpeed") : 2;
|
||||
this.dualCharging = !nbt.hasKey("dualCharging") || nbt.getBoolean("dualCharging");
|
||||
colorData.readNBT(nbt);
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lightningData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
trajectoryData.readNBT(nbt);
|
||||
|
||||
for (int i = 0; i < anchorData.length; i++) {
|
||||
if (nbt.hasKey("Anchor_" + i, Constants.NBT.TAG_COMPOUND)) {
|
||||
anchorData[i].readNBT(nbt.getCompoundTag("Anchor_" + i));
|
||||
} else {
|
||||
anchorData[i] = new EnergyAnchorData(i == 0 ? AnchorPoint.RIGHT_HAND : AnchorPoint.LEFT_HAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
public float getOrbSpeed() {
|
||||
return homingData.speed;
|
||||
}
|
||||
|
||||
public void setOrbSpeed(float orbSpeed) {
|
||||
homingData.speed = orbSpeed;
|
||||
}
|
||||
|
||||
public float getOrbSize() {
|
||||
return orbSize;
|
||||
}
|
||||
|
||||
public void setOrbSize(float orbSize) {
|
||||
this.orbSize = orbSize;
|
||||
}
|
||||
|
||||
public int getOrbAmount() { return orbAmount; }
|
||||
|
||||
public void setOrbAmount(int orbAmount) { this.orbAmount = orbAmount; }
|
||||
|
||||
public int getFiringSpeed() { return firingSpeed; }
|
||||
|
||||
public void setFiringSpeed(int firingSpeed) { this.firingSpeed = firingSpeed; }
|
||||
|
||||
public boolean isDualCharging() { return dualCharging; }
|
||||
|
||||
public void setDualCharging(boolean dualCharging) { this.dualCharging = dualCharging; }
|
||||
|
||||
public float getMaxDistance() {
|
||||
return lifespanData.maxDistance;
|
||||
}
|
||||
|
||||
public void setMaxDistance(float maxDistance) {
|
||||
lifespanData.maxDistance = maxDistance;
|
||||
}
|
||||
|
||||
public int getMaxLifetime() {
|
||||
return lifespanData.maxLifetime;
|
||||
}
|
||||
|
||||
public void setMaxLifetime(int maxLifetime) {
|
||||
lifespanData.maxLifetime = maxLifetime;
|
||||
}
|
||||
|
||||
public float getDamage() {
|
||||
return combatData.damage;
|
||||
}
|
||||
|
||||
public void setDamage(float damage) {
|
||||
combatData.damage = damage;
|
||||
}
|
||||
|
||||
public float getKnockback() {
|
||||
return combatData.knockback;
|
||||
}
|
||||
|
||||
public void setKnockback(float knockback) {
|
||||
combatData.knockback = knockback;
|
||||
}
|
||||
|
||||
public float getKnockbackUp() {
|
||||
return combatData.knockbackUp;
|
||||
}
|
||||
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
combatData.knockbackUp = knockbackUp;
|
||||
}
|
||||
|
||||
public boolean isHoming() {
|
||||
return homingData.homing;
|
||||
}
|
||||
|
||||
public void setHoming(boolean homing) {
|
||||
homingData.homing = homing;
|
||||
}
|
||||
|
||||
public float getHomingStrength() {
|
||||
return homingData.homingStrength;
|
||||
}
|
||||
|
||||
public void setHomingStrength(float homingStrength) {
|
||||
homingData.homingStrength = homingStrength;
|
||||
}
|
||||
|
||||
public float getHomingRange() {
|
||||
return homingData.homingRange;
|
||||
}
|
||||
|
||||
public void setHomingRange(float homingRange) {
|
||||
homingData.homingRange = homingRange;
|
||||
}
|
||||
|
||||
public boolean isExplosive() {
|
||||
return combatData.explosive;
|
||||
}
|
||||
|
||||
public void setExplosive(boolean explosive) {
|
||||
combatData.explosive = explosive;
|
||||
}
|
||||
|
||||
public float getExplosionRadius() {
|
||||
return combatData.explosionRadius;
|
||||
}
|
||||
|
||||
public void setExplosionRadius(float explosionRadius) {
|
||||
combatData.explosionRadius = explosionRadius;
|
||||
}
|
||||
|
||||
public float getExplosionDamageFalloff() {
|
||||
return combatData.explosionDamageFalloff;
|
||||
}
|
||||
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) {
|
||||
combatData.explosionDamageFalloff = explosionDamageFalloff;
|
||||
}
|
||||
|
||||
public int getInnerColor() {
|
||||
return colorData.innerColor;
|
||||
}
|
||||
|
||||
public void setInnerColor(int innerColor) {
|
||||
colorData.innerColor = innerColor;
|
||||
}
|
||||
|
||||
public int getOuterColor() {
|
||||
return colorData.outerColor;
|
||||
}
|
||||
|
||||
public void setOuterColor(int outerColor) {
|
||||
colorData.outerColor = outerColor;
|
||||
}
|
||||
|
||||
public float getOuterColorWidth() {
|
||||
return colorData.outerColorWidth;
|
||||
}
|
||||
|
||||
public void setOuterColorWidth(float outerColorWidth) {
|
||||
colorData.outerColorWidth = outerColorWidth;
|
||||
}
|
||||
|
||||
public float getOuterColorAlpha() {
|
||||
return colorData.outerColorAlpha;
|
||||
}
|
||||
|
||||
public void setOuterColorAlpha(float outerColorAlpha) {
|
||||
colorData.outerColorAlpha = outerColorAlpha;
|
||||
}
|
||||
|
||||
public boolean isOuterColorEnabled() {
|
||||
return colorData.outerColorEnabled;
|
||||
}
|
||||
|
||||
public void setOuterColorEnabled(boolean outerColorEnabled) {
|
||||
colorData.outerColorEnabled = outerColorEnabled;
|
||||
}
|
||||
|
||||
public float getRotationSpeed() {
|
||||
return colorData.rotationSpeed;
|
||||
}
|
||||
|
||||
public void setRotationSpeed(float rotationSpeed) {
|
||||
colorData.rotationSpeed = rotationSpeed;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private EnergyAnchorData getAnchorData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return anchorData[orb];
|
||||
}
|
||||
|
||||
public AnchorPoint getAnchorPointEnum(int orb) { return getAnchorData(orb).anchorPoint; }
|
||||
public void setAnchorPointEnum(int orb, AnchorPoint anchorPoint) { getAnchorData(orb).anchorPoint = anchorPoint; }
|
||||
public float getAnchorOffsetX(int orb) { return getAnchorData(orb).anchorOffsetX; }
|
||||
public void setAnchorOffsetX(int orb, float x) { getAnchorData(orb).anchorOffsetX = x; }
|
||||
public float getAnchorOffsetY(int orb) { return getAnchorData(orb).anchorOffsetY; }
|
||||
public void setAnchorOffsetY(int orb, float y) { getAnchorData(orb).anchorOffsetY = y; }
|
||||
public float getAnchorOffsetZ(int orb) { return getAnchorData(orb).anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(int orb, float z) { getAnchorData(orb).anchorOffsetZ = z; }
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); }
|
||||
public float getAnchorOffsetX() { return getAnchorOffsetX(0); }
|
||||
public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); }
|
||||
public float getAnchorOffsetY() { return getAnchorOffsetY(0); }
|
||||
public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); }
|
||||
public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); }
|
||||
public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); }
|
||||
|
||||
//@Override
|
||||
public int getAnchorPoint(int orb) { return getAnchorData(orb).anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); }
|
||||
|
||||
//@Override
|
||||
public void setAnchorPoint(int orb, int point) { getAnchorData(orb).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.barrage"),
|
||||
FieldDef.row(
|
||||
FieldDef.intField("ability.amount", this::getOrbAmount, this::setOrbAmount),
|
||||
FieldDef.intField("ability.firingSpeed", this::getFiringSpeed, this::setFiringSpeed)
|
||||
),
|
||||
FieldDef.boolField("ability.dualCharge", this::isDualCharging, this::setDualCharging),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"),
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming),
|
||||
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),
|
||||
FieldDef.floatField("ability.anchor.offsetY", this::getAnchorOffsetY, this::setAnchorOffsetY)
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", this::getAnchorOffsetZ, this::setAnchorOffsetZ)
|
||||
.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.floatField("ability.rotationSpeed", this::getRotationSpeed, this::setRotationSpeed).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")
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,600 +0,0 @@
|
||||
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.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.EntityAbilityOrb;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.util.ValueUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AbilityOrbDual extends Ability {
|
||||
// Ability-specific properties
|
||||
private float orbSize = 1.0f;
|
||||
private boolean dualFire= true;
|
||||
private int dualFireDelay = 5;
|
||||
|
||||
// Data classes for energy properties
|
||||
public final EnergyDisplayData[] displayData = new EnergyDisplayData[]{
|
||||
new EnergyDisplayData(), new EnergyDisplayData()
|
||||
};
|
||||
public final EnergyCombatData combatData = new EnergyCombatData();
|
||||
public final EnergyHomingData homingData = new EnergyHomingData();
|
||||
public final EnergyLightningData[] lightningData = new EnergyLightningData[]{
|
||||
new EnergyLightningData(), new EnergyLightningData()
|
||||
};
|
||||
public final EnergyLifespanData lifespanData = new EnergyLifespanData();
|
||||
private final EnergyAnchorData[] anchorData = new EnergyAnchorData[]{
|
||||
new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND)
|
||||
};
|
||||
private EnergyTrajectoryData[] trajectoryData = new EnergyTrajectoryData[]{
|
||||
new EnergyTrajectoryData(), new EnergyTrajectoryData()
|
||||
};
|
||||
|
||||
// Transient state for orb entity (used during windup charging)
|
||||
private transient EntityAbilityOrb orbEntity = null;
|
||||
private transient EntityAbilityOrb orbEntity2 = null;
|
||||
|
||||
public AbilityOrbDual() {
|
||||
this.typeId = "ability.cnpc.orb_dual";
|
||||
this.name = "Orb Dual";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
this.maxRange = 25.0f;
|
||||
this.minRange = 5.0f;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 30;
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animation
|
||||
this.windUpAnimationName = "Ability_OrbDual_Windup";
|
||||
this.activeAnimationName = "Ability_OrbDual_Active";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetingMode[] getAllowedTargetingModes() {
|
||||
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
|
||||
if (world.isRemote && !isPreview()) {
|
||||
signalCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start moving the orb that was spawned during windup
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
if (isPreview()) {
|
||||
orbEntity.startPreviewFiring();
|
||||
} else {
|
||||
orbEntity.startMoving(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (dualFire) {
|
||||
if (orbEntity2 != null && !orbEntity2.isDead && dualFireDelay <= 0) {
|
||||
if (isPreview()) {
|
||||
orbEntity2.startPreviewFiring();
|
||||
} else {
|
||||
orbEntity2.startMoving(target);
|
||||
}
|
||||
}
|
||||
} else if (orbEntity2 != null) {
|
||||
orbEntity2.setDead();
|
||||
}
|
||||
|
||||
// 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 onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (world.isRemote && !isPreview()) return;
|
||||
|
||||
// Spawn orb in charging mode on first tick of windup
|
||||
if (tick == 1) {
|
||||
Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0]);
|
||||
orbEntity = new EntityAbilityOrb(
|
||||
world, caster, target,
|
||||
spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord, orbSize,
|
||||
displayData[0], combatData, homingData, lightningData[0], lifespanData, trajectoryData[0]);
|
||||
|
||||
Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1]);
|
||||
orbEntity2 = new EntityAbilityOrb(
|
||||
world, caster, target,
|
||||
spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord, orbSize,
|
||||
displayData[1], combatData, homingData, lightningData[1], lifespanData, trajectoryData[1]);
|
||||
|
||||
orbEntity.setSiblingEntityId(orbEntity2.getEntityId());
|
||||
orbEntity2.setSiblingEntityId(orbEntity.getEntityId());
|
||||
|
||||
if (isPreview()) {
|
||||
orbEntity.setupPreview(caster, orbSize, displayData[0], lightningData[0], anchorData[0], windUpTicks);
|
||||
orbEntity2.setupPreview(caster, orbSize, displayData[1], lightningData[1], anchorData[1], windUpTicks);
|
||||
} else {
|
||||
orbEntity.setupCharging(anchorData[0], windUpTicks);
|
||||
orbEntity2.setupCharging(anchorData[1], windUpTicks);
|
||||
}
|
||||
|
||||
orbEntity.setEffects(this.effects);
|
||||
|
||||
if (dualFire) {
|
||||
orbEntity2.setEffects(this.effects);
|
||||
}
|
||||
|
||||
orbEntity.setSourceAbility(this);
|
||||
orbEntity2.setSourceAbility(this);
|
||||
spawnAbilityEntity(world, orbEntity);
|
||||
spawnAbilityEntity(world, orbEntity2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
// Signal completion when entity dies
|
||||
if (dualFire) {
|
||||
if ((orbEntity == null || orbEntity.isDead) && (orbEntity2 == null || orbEntity2.isDead)) {
|
||||
orbEntity = null;
|
||||
orbEntity2 = null;
|
||||
signalCompletion();
|
||||
}
|
||||
|
||||
if (tick == dualFireDelay && orbEntity2 != null && !orbEntity2.isDead) {
|
||||
orbEntity2.startMoving(target);
|
||||
}
|
||||
} else {
|
||||
if (orbEntity == null || orbEntity.isDead) {
|
||||
orbEntity = null;
|
||||
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() {
|
||||
// Despawn orb entity if still alive
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
orbEntity.setDead();
|
||||
}
|
||||
|
||||
if (orbEntity2 != null && !orbEntity2.isDead) {
|
||||
orbEntity2.setDead();
|
||||
}
|
||||
|
||||
orbEntity = null;
|
||||
orbEntity2 = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create small circle telegraph at target position
|
||||
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);
|
||||
|
||||
// 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 orbSize * 1.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("orbSize", orbSize);
|
||||
nbt.setBoolean("dualFire", dualFire);
|
||||
nbt.setInteger("dualFireDelay", dualFireDelay);
|
||||
combatData.writeNBT(nbt);
|
||||
homingData.writeNBT(nbt);
|
||||
lifespanData.writeNBT(nbt);
|
||||
// Backward compat: old key was "orbSpeed"
|
||||
nbt.setFloat("orbSpeed", homingData.speed);
|
||||
|
||||
NBTTagCompound orb1 = new NBTTagCompound();
|
||||
NBTTagCompound orb2 = new NBTTagCompound();
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound orb = i == 0 ? orb1 : orb2;
|
||||
anchorData[i].writeNBT(orb);
|
||||
displayData[i].writeNBT(orb);
|
||||
lightningData[i].writeNBT(orb);
|
||||
trajectoryData[i].writeNBT(orb);
|
||||
}
|
||||
|
||||
nbt.setTag("Orb_1", orb1);
|
||||
nbt.setTag("Orb_2", orb2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
NBTTagCompound orb1 = nbt.hasKey("Orb_1") ? nbt.getCompoundTag("Orb_1") : new NBTTagCompound();
|
||||
NBTTagCompound orb2 = nbt.hasKey("Orb_2") ? nbt.getCompoundTag("Orb_2") : new NBTTagCompound();
|
||||
|
||||
this.orbSize = nbt.hasKey("orbSize") ? nbt.getFloat("orbSize") : 1.0f;
|
||||
this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire");
|
||||
this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0;
|
||||
combatData.readNBT(nbt);
|
||||
homingData.readNBT(nbt);
|
||||
lifespanData.readNBT(nbt);
|
||||
// Backward compat: old key was "orbSpeed"
|
||||
if (nbt.hasKey("orbSpeed")) {
|
||||
homingData.speed = nbt.getFloat("orbSpeed");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NBTTagCompound orb = i == 0 ? orb1 : orb2;
|
||||
anchorData[i].readNBT(orb);
|
||||
displayData[i].readNBT(orb);
|
||||
lightningData[i].readNBT(orb);
|
||||
trajectoryData[i].readNBT(orb);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
private EnergyDisplayData getColorData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return displayData[orb];
|
||||
}
|
||||
private EnergyLightningData getLightningData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return lightningData[orb];
|
||||
}
|
||||
private EnergyAnchorData getAnchorData(int orb) {
|
||||
orb = ValueUtil.clamp(orb, 0, 1);
|
||||
return anchorData[orb];
|
||||
}
|
||||
|
||||
public float getOrbSpeed() {
|
||||
return homingData.speed;
|
||||
}
|
||||
|
||||
public void setOrbSpeed(float orbSpeed) {
|
||||
homingData.speed = orbSpeed;
|
||||
}
|
||||
|
||||
public float getOrbSize() {
|
||||
return orbSize;
|
||||
}
|
||||
|
||||
public void setOrbSize(float orbSize) {
|
||||
this.orbSize = orbSize;
|
||||
}
|
||||
|
||||
public boolean isDualFire() {
|
||||
return dualFire;
|
||||
}
|
||||
|
||||
public void setDualFire(boolean dualFire) {
|
||||
this.dualFire = dualFire;
|
||||
}
|
||||
|
||||
public int getDualFireDelay() {
|
||||
return dualFireDelay;
|
||||
}
|
||||
|
||||
public void setDualFireDelay(int dualFireDelay) {
|
||||
this.dualFireDelay = dualFireDelay;
|
||||
}
|
||||
|
||||
public float getMaxDistance() {
|
||||
return lifespanData.maxDistance;
|
||||
}
|
||||
|
||||
public void setMaxDistance(float maxDistance) {
|
||||
lifespanData.maxDistance = maxDistance;
|
||||
}
|
||||
|
||||
public int getMaxLifetime() {
|
||||
return lifespanData.maxLifetime;
|
||||
}
|
||||
|
||||
public void setMaxLifetime(int maxLifetime) {
|
||||
lifespanData.maxLifetime = maxLifetime;
|
||||
}
|
||||
|
||||
public float getDamage() {
|
||||
return combatData.damage;
|
||||
}
|
||||
|
||||
public void setDamage(float damage) {
|
||||
combatData.damage = damage;
|
||||
}
|
||||
|
||||
public float getKnockback() {
|
||||
return combatData.knockback;
|
||||
}
|
||||
|
||||
public void setKnockback(float knockback) {
|
||||
combatData.knockback = knockback;
|
||||
}
|
||||
|
||||
public float getKnockbackUp() {
|
||||
return combatData.knockbackUp;
|
||||
}
|
||||
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
combatData.knockbackUp = knockbackUp;
|
||||
}
|
||||
|
||||
public boolean isHoming() {
|
||||
return homingData.homing;
|
||||
}
|
||||
|
||||
public void setHoming(boolean homing) {
|
||||
homingData.homing = homing;
|
||||
}
|
||||
|
||||
public float getHomingStrength() {
|
||||
return homingData.homingStrength;
|
||||
}
|
||||
|
||||
public void setHomingStrength(float homingStrength) {
|
||||
homingData.homingStrength = homingStrength;
|
||||
}
|
||||
|
||||
public float getHomingRange() {
|
||||
return homingData.homingRange;
|
||||
}
|
||||
|
||||
public void setHomingRange(float homingRange) {
|
||||
homingData.homingRange = homingRange;
|
||||
}
|
||||
|
||||
public boolean isExplosive() {
|
||||
return combatData.explosive;
|
||||
}
|
||||
|
||||
public void setExplosive(boolean explosive) {
|
||||
combatData.explosive = explosive;
|
||||
}
|
||||
|
||||
public float getExplosionRadius() {
|
||||
return combatData.explosionRadius;
|
||||
}
|
||||
|
||||
public void setExplosionRadius(float explosionRadius) {
|
||||
combatData.explosionRadius = explosionRadius;
|
||||
}
|
||||
|
||||
public float getExplosionDamageFalloff() {
|
||||
return combatData.explosionDamageFalloff;
|
||||
}
|
||||
|
||||
public void setExplosionDamageFalloff(float explosionDamageFalloff) {
|
||||
combatData.explosionDamageFalloff = explosionDamageFalloff;
|
||||
}
|
||||
|
||||
|
||||
public int getInnerColor(int orb) { return getColorData(orb).innerColor; }
|
||||
public void setInnerColor(int orb, int innerColor) { getColorData(orb).innerColor = innerColor; }
|
||||
public int getOuterColor(int orb) { return getColorData(orb).outerColor; }
|
||||
public void setOuterColor(int orb, int outerColor) { getColorData(orb).outerColor = outerColor; }
|
||||
public boolean isOuterColorEnabled(int orb) { return getColorData(orb).outerColorEnabled; }
|
||||
public void setOuterColorEnabled(int orb, boolean enabled) { getColorData(orb).outerColorEnabled = enabled; }
|
||||
public float getOuterColorWidth(int orb) { return getColorData(orb).outerColorWidth; }
|
||||
public void setOuterColorWidth(int orb, float width) { getColorData(orb).outerColorWidth = width; }
|
||||
public float getOuterColorAlpha(int orb) { return getColorData(orb).outerColorAlpha; }
|
||||
public void setOuterColorAlpha(int orb, float alpha) { getColorData(orb).outerColorAlpha = alpha; }
|
||||
public float getRotationSpeed(int orb) { return getColorData(orb).rotationSpeed; }
|
||||
public void setRotationSpeed(int orb, float speed) { getColorData(orb).rotationSpeed = speed; }
|
||||
|
||||
public int getInnerColor() { return getInnerColor(0); }
|
||||
public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); }
|
||||
public int getOuterColor() { return getOuterColor(0); }
|
||||
public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); }
|
||||
public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); }
|
||||
public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); }
|
||||
public float getOuterColorWidth() { return getOuterColorWidth(0); }
|
||||
public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); }
|
||||
public float getOuterColorAlpha() { return getOuterColorAlpha(0); }
|
||||
public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); }
|
||||
public float getRotationSpeed() { return getRotationSpeed(0); }
|
||||
public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); }
|
||||
|
||||
|
||||
public boolean hasLightningEffect(int orb) { return getLightningData(orb).lightningEffect; }
|
||||
public void setLightningEffect(int orb, boolean lightningEffect) { getLightningData(orb).lightningEffect = lightningEffect; }
|
||||
public float getLightningDensity(int orb) { return getLightningData(orb).lightningDensity; }
|
||||
public void setLightningDensity(int orb, float lightningDensity) { getLightningData(orb).lightningDensity = lightningDensity; }
|
||||
public float getLightningRadius(int orb) { return getLightningData(orb).lightningRadius; }
|
||||
public void setLightningRadius(int orb, float lightningRadius) { getLightningData(orb).lightningRadius = lightningRadius; }
|
||||
|
||||
public boolean hasLightningEffect() { return hasLightningEffect(0); }
|
||||
public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); }
|
||||
public float getLightningDensity() { return getLightningDensity(0); }
|
||||
public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); }
|
||||
public float getLightningRadius() { return getLightningRadius(0); }
|
||||
public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); }
|
||||
|
||||
|
||||
public AnchorPoint getAnchorPointEnum(int orb) { return getAnchorData(orb).anchorPoint; }
|
||||
public void setAnchorPointEnum(int orb, AnchorPoint anchorPoint) { getAnchorData(orb).anchorPoint = anchorPoint; }
|
||||
public float getAnchorOffsetX(int orb) { return getAnchorData(orb).anchorOffsetX; }
|
||||
public void setAnchorOffsetX(int orb, float x) { getAnchorData(orb).anchorOffsetX = x; }
|
||||
public float getAnchorOffsetY(int orb) { return getAnchorData(orb).anchorOffsetY; }
|
||||
public void setAnchorOffsetY(int orb, float y) { getAnchorData(orb).anchorOffsetY = y; }
|
||||
public float getAnchorOffsetZ(int orb) { return getAnchorData(orb).anchorOffsetZ; }
|
||||
public void setAnchorOffsetZ(int orb, float z) { getAnchorData(orb).anchorOffsetZ = z; }
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); }
|
||||
public float getAnchorOffsetX() { return getAnchorOffsetX(0); }
|
||||
public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); }
|
||||
public float getAnchorOffsetY() { return getAnchorOffsetY(0); }
|
||||
public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); }
|
||||
public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); }
|
||||
public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); }
|
||||
|
||||
//@Override
|
||||
public int getAnchorPoint(int orb) { return getAnchorData(orb).anchorPoint.ordinal(); }
|
||||
public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); }
|
||||
|
||||
//@Override
|
||||
public void setAnchorPoint(int orb, int point) { getAnchorData(orb).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); }
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
defs.addAll(Arrays.asList(
|
||||
// Type tab
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
|
||||
FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize),
|
||||
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
|
||||
),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
|
||||
),
|
||||
FieldDef.section("ability.section.dual"),
|
||||
FieldDef.boolField("ability.dualFire", this::isDualFire, this::setDualFire),
|
||||
FieldDef.intField("ability.dualFireDelay", this::getDualFireDelay, this::setDualFireDelay)
|
||||
.visibleWhen(this::isDualFire),
|
||||
FieldDef.section("ability.section.homing"),
|
||||
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"),
|
||||
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming),
|
||||
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 - Orb 1
|
||||
FieldDef.section("ability.section.orb1").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(0), v -> setAnchorPointEnum(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(0), v -> setAnchorOffsetX(0, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(0), v -> setAnchorOffsetY(0, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(0), v -> setAnchorOffsetZ(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(0), v -> setInnerColor(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(0), v -> setOuterColorEnabled(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(0), v -> setOuterColor(0, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(0)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(0))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(0), v -> setRotationSpeed(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(0), v -> setLightningEffect(0, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(0), v -> setLightningDensity(0, v))
|
||||
.range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(0)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(0), v -> setLightningRadius(0, v))
|
||||
.range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(0))
|
||||
).tab("ability.tab.visual"),
|
||||
// Visual tab - Orb 2
|
||||
FieldDef.section("ability.section.orb2").tab("ability.tab.visual"),
|
||||
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class,
|
||||
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.anchor.offsetX", () -> getAnchorOffsetX(1), v -> setAnchorOffsetX(1, v)),
|
||||
FieldDef.floatField("ability.anchor.offsetY", () -> getAnchorOffsetY(1), v -> setAnchorOffsetY(1, v))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.anchor.offsetZ", () -> getAnchorOffsetZ(1), v -> setAnchorOffsetZ(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.innerColor",
|
||||
() -> getInnerColor(1), v -> setInnerColor(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.outerEnabled",
|
||||
() -> isOuterColorEnabled(1), v -> setOuterColorEnabled(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.colorSubGui("ability.outerColor",
|
||||
() -> getOuterColor(1), v -> setOuterColor(1, v))
|
||||
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("ability.outerWidth",
|
||||
() -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
|
||||
.visibleWhen(() -> isOuterColorEnabled(1)),
|
||||
FieldDef.floatField("ability.outerAlpha",
|
||||
() -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
|
||||
.range(0, 1).visibleWhen(() -> isOuterColorEnabled(1))
|
||||
).tab("ability.tab.visual"),
|
||||
FieldDef.floatField("ability.rotationSpeed",
|
||||
() -> getRotationSpeed(1), v -> setRotationSpeed(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.boolField("ability.lightning",
|
||||
() -> hasLightningEffect(1), v -> setLightningEffect(1, v))
|
||||
.tab("ability.tab.visual"),
|
||||
FieldDef.row(
|
||||
FieldDef.floatField("gui.density",
|
||||
() -> getLightningDensity(1), v -> setLightningDensity(1, v))
|
||||
.range(0.01f, 100f).visibleWhen(() -> hasLightningEffect(1)),
|
||||
FieldDef.floatField("gui.radius",
|
||||
() -> getLightningRadius(1), v -> setLightningRadius(1, v))
|
||||
.range(0.1f, 100f).visibleWhen(() -> hasLightningEffect(1))
|
||||
).tab("ability.tab.visual")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,11 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
|
||||
this.showTelegraph = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
|
||||
@@ -61,6 +61,11 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
this.showTelegraph = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
|
||||
@@ -85,6 +85,11 @@ public class AbilityTrap extends Ability implements IAbilityTrap {
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowBurst() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return true;
|
||||
|
||||
@@ -174,11 +174,23 @@ public class DataAbilities {
|
||||
// Handle phase-specific logic
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
// Burst replay: re-enter windup - set up telegraph, sound, animation, locks
|
||||
if (currentAbility.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation();
|
||||
}
|
||||
if (currentAbility.isMovementLockedDuringWindup() && !currentAbility.hasAbilityMovement()) {
|
||||
captureLockedPosition();
|
||||
}
|
||||
spawnTelegraph(currentAbility, target);
|
||||
playAbilitySound(currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(npc, target, npc.worldObj, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.WINDUP) {
|
||||
if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) {
|
||||
// Just entered ACTIVE phase - lock telegraph position if it was following
|
||||
// This commits the ability to its current target position
|
||||
TelegraphInstance telegraph = currentAbility.getTelegraphInstance();
|
||||
@@ -207,6 +219,11 @@ 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
|
||||
playAbilitySound(currentAbility.getActiveSound());
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
@@ -240,6 +257,31 @@ public class DataAbilities {
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-complete for burst overlap mode (entities fly independently)
|
||||
// This allows entity-spawning abilities to immediately chain next burst
|
||||
// without each type needing to check burst state in their onActiveTick()
|
||||
// Wait until all staggered projectiles have been fired before auto-completing
|
||||
if (currentAbility.isBurstEnabled() && currentAbility.isBurstOverlap()
|
||||
&& currentAbility.getBurstIndex() < currentAbility.getBurstAmount()
|
||||
&& currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) {
|
||||
currentAbility.signalCompletion();
|
||||
}
|
||||
|
||||
// Check if ability entered burst delay during onActiveTick or overlap auto-complete
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
// Release all locks - free movement/rotation during delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
if (hitScanActive) releaseRotationControl();
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Free movement and rotation during burst delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
break;
|
||||
|
||||
case DAZED:
|
||||
|
||||
@@ -3,6 +3,7 @@ package noppes.npcs.client.gui.advanced;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilitySlot;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket;
|
||||
@@ -61,6 +62,9 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
// Existing preset names for duplicate checking
|
||||
private final Set<String> existingPresetNames = new HashSet<>();
|
||||
|
||||
// Pending variant selection
|
||||
private String pendingTypeId = null;
|
||||
|
||||
public static int modIndex = 0;
|
||||
public static ScrollType scrollType = ScrollType.CNPC;
|
||||
|
||||
@@ -197,8 +201,17 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
String displayName = availableTypesScroll.getSelected();
|
||||
String typeId = displayNameToTypeId.get(displayName);
|
||||
if (typeId != null) {
|
||||
java.util.List<AbilityVariant> variants = AbilityController.Instance.getVariantsForType(typeId);
|
||||
if (variants.size() > 1) {
|
||||
pendingTypeId = typeId;
|
||||
setSubGui(new SubGuiAbilityVariantSelect(variants));
|
||||
return;
|
||||
}
|
||||
Ability newAbility = AbilityController.Instance.create(typeId);
|
||||
if (newAbility != null) {
|
||||
if (variants.size() == 1) {
|
||||
variants.get(0).apply(newAbility);
|
||||
}
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
npcSlots.add(AbilitySlot.inline(newAbility));
|
||||
selectedAbilityIndex = npcSlots.size() - 1;
|
||||
@@ -633,7 +646,24 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
|
||||
@Override
|
||||
public void subGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiAbilityEditMode) {
|
||||
if (subgui instanceof SubGuiAbilityVariantSelect) {
|
||||
SubGuiAbilityVariantSelect variantGui = (SubGuiAbilityVariantSelect) subgui;
|
||||
int idx = variantGui.getSelectedIndex();
|
||||
if (idx >= 0 && pendingTypeId != null) {
|
||||
Ability newAbility = AbilityController.Instance.create(pendingTypeId);
|
||||
if (newAbility != null) {
|
||||
variantGui.getVariants().get(idx).apply(newAbility);
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
npcSlots.add(AbilitySlot.inline(newAbility));
|
||||
selectedAbilityIndex = npcSlots.size() - 1;
|
||||
updateNpcAbilitiesList();
|
||||
selectAbilityByIndex(selectedAbilityIndex);
|
||||
save();
|
||||
}
|
||||
}
|
||||
pendingTypeId = null;
|
||||
initGui();
|
||||
} else if (subgui instanceof SubGuiAbilityEditMode) {
|
||||
int mode = ((SubGuiAbilityEditMode) subgui).getResult();
|
||||
if (mode < 0) return; // cancelled
|
||||
|
||||
|
||||
@@ -324,7 +324,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
@Override
|
||||
public void unFocused(GuiNpcTextField textField) {
|
||||
if (textField.id < DECLARATIVE_ID_START) return;
|
||||
builder.handleTextFieldEvent(textField.id, textField);
|
||||
if (builder.handleTextFieldEvent(textField.id, textField)) {
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package noppes.npcs.client.gui.advanced;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.GuiNpcLabel;
|
||||
import noppes.npcs.client.gui.util.GuiScrollWindow;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SubGui for selecting a variant/template when creating a new ability.
|
||||
* Uses a scroll window so external mods can register many variants.
|
||||
*/
|
||||
public class SubGuiAbilityVariantSelect extends SubGuiInterface {
|
||||
|
||||
private static final int VARIANT_BTN_START = 10;
|
||||
private static final int BTN_H = 20;
|
||||
private static final int BTN_PAD = 2;
|
||||
|
||||
private final List<AbilityVariant> variants;
|
||||
private final List<String> displayNames = new ArrayList<>();
|
||||
private int selectedIndex = -1;
|
||||
|
||||
public SubGuiAbilityVariantSelect(List<AbilityVariant> variants) {
|
||||
this.variants = variants;
|
||||
for (AbilityVariant variant : variants) {
|
||||
displayNames.add(StatCollector.translateToLocal(variant.getDisplayKey()));
|
||||
}
|
||||
setBackground("menubg.png");
|
||||
xSize = 200;
|
||||
ySize = 216;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
addLabel(new GuiNpcLabel(0, "ability.selectVariant", guiLeft + 10, guiTop + 8));
|
||||
|
||||
// Scroll window for variant buttons
|
||||
int swX = guiLeft + 5;
|
||||
int swY = guiTop + 22;
|
||||
int swW = 190;
|
||||
int swH = 155;
|
||||
|
||||
GuiScrollWindow sw = new GuiScrollWindow(this, swX, swY, swW, swH, 0);
|
||||
addScrollableGui(0, sw); // Must register BEFORE adding components (initGui clears them)
|
||||
|
||||
int localY = 2;
|
||||
int btnW = swW - 14; // Leave room for scrollbar
|
||||
for (int i = 0; i < displayNames.size(); i++) {
|
||||
GuiNpcButton btn = new GuiNpcButton(VARIANT_BTN_START + i, 2, localY, btnW, BTN_H, displayNames.get(i));
|
||||
if (i == selectedIndex) {
|
||||
btn.packedFGColour = 0x55FF55;
|
||||
}
|
||||
sw.addButton(btn);
|
||||
localY += BTN_H + BTN_PAD;
|
||||
}
|
||||
sw.maxScrollY = Math.max(localY - swH, 0);
|
||||
|
||||
// Select / Cancel buttons
|
||||
addButton(new GuiNpcButton(0, guiLeft + 5, guiTop + 188, 90, 20, "gui.select"));
|
||||
getButton(0).setEnabled(selectedIndex >= 0);
|
||||
addButton(new GuiNpcButton(1, guiLeft + 105, guiTop + 188, 90, 20, "gui.cancel"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buttonEvent(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
if (id == 0 && selectedIndex >= 0) {
|
||||
close();
|
||||
} else if (id == 1) {
|
||||
selectedIndex = -1;
|
||||
close();
|
||||
} else if (id >= VARIANT_BTN_START && id < VARIANT_BTN_START + displayNames.size()) {
|
||||
selectedIndex = id - VARIANT_BTN_START;
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSelectedIndex() {
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
public List<AbilityVariant> getVariants() {
|
||||
return variants;
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,9 @@ public class GuiFieldBuilder {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip invisible fields
|
||||
if (!def.isVisible()) continue;
|
||||
|
||||
// Section header
|
||||
if (def.getType() == FieldType.SECTION_HEADER) {
|
||||
y += 3;
|
||||
@@ -391,7 +394,7 @@ public class GuiFieldBuilder {
|
||||
|
||||
/**
|
||||
* Handles text field unfocus events for declarative fields.
|
||||
* Call from parent GUI's unFocused(). Returns true if the event was handled.
|
||||
* Call from parent GUI's unFocused(). Returns true if the value actually changed.
|
||||
*/
|
||||
public boolean handleTextFieldEvent(int textFieldId, GuiNpcTextField field) {
|
||||
FieldDef def = textFieldMap.get(textFieldId);
|
||||
@@ -399,14 +402,36 @@ public class GuiFieldBuilder {
|
||||
|
||||
switch (def.getType()) {
|
||||
case FLOAT:
|
||||
try { def.setValue(Float.parseFloat(field.getText())); } catch (NumberFormatException ignored) {}
|
||||
return true;
|
||||
try {
|
||||
float newVal = Float.parseFloat(field.getText());
|
||||
Object old = def.getValue();
|
||||
float oldVal = old instanceof Number ? ((Number) old).floatValue() : 0f;
|
||||
if (newVal != oldVal) {
|
||||
def.setValue(newVal);
|
||||
return true;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
return false;
|
||||
case INT:
|
||||
try { def.setValue(Integer.parseInt(field.getText())); } catch (NumberFormatException ignored) {}
|
||||
return true;
|
||||
try {
|
||||
int newVal = Integer.parseInt(field.getText());
|
||||
Object old = def.getValue();
|
||||
int oldVal = old instanceof Number ? ((Number) old).intValue() : 0;
|
||||
if (newVal != oldVal) {
|
||||
def.setValue(newVal);
|
||||
return true;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
return false;
|
||||
case STRING:
|
||||
def.setValue(field.getText());
|
||||
return true;
|
||||
String newVal = field.getText();
|
||||
Object old = def.getValue();
|
||||
String oldVal = old != null ? old.toString() : "";
|
||||
if (!newVal.equals(oldVal)) {
|
||||
def.setValue(newVal);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import net.minecraft.client.gui.GuiYesNoCallback;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.NoppesUtil;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiDuplicateNameConfirm;
|
||||
import noppes.npcs.client.gui.util.AbilityPreviewExecutor;
|
||||
import noppes.npcs.client.gui.util.GuiAbilityInterface;
|
||||
@@ -68,6 +70,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
private Ability pendingSaveAbility = null;
|
||||
private boolean pendingNewCreation = false;
|
||||
|
||||
// ==================== PENDING VARIANT ====================
|
||||
private String pendingTypeId = null;
|
||||
|
||||
// ==================== PREVIEW ====================
|
||||
private AbilityPreviewExecutor previewExecutor;
|
||||
private long prevTick = 0;
|
||||
@@ -343,11 +348,41 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
|
||||
@Override
|
||||
public void subGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiAbilityTypeSelect) {
|
||||
if (subgui instanceof SubGuiAbilityVariantSelect) {
|
||||
SubGuiAbilityVariantSelect variantGui = (SubGuiAbilityVariantSelect) subgui;
|
||||
int idx = variantGui.getSelectedIndex();
|
||||
if (idx >= 0 && pendingTypeId != null) {
|
||||
Ability newAbility = AbilityController.Instance.create(pendingTypeId);
|
||||
if (newAbility != null) {
|
||||
variantGui.getVariants().get(idx).apply(newAbility);
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
if (hasDuplicateName(newAbility)) {
|
||||
pendingSaveAbility = newAbility;
|
||||
pendingNewCreation = true;
|
||||
pendingTypeId = null;
|
||||
setSubGui(new SubGuiDuplicateNameConfirm());
|
||||
return;
|
||||
}
|
||||
pendingTypeId = null;
|
||||
saveAndOpenConfig(newAbility);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingTypeId = null;
|
||||
} else if (subgui instanceof SubGuiAbilityTypeSelect) {
|
||||
String typeId = ((SubGuiAbilityTypeSelect) subgui).getSelectedTypeId();
|
||||
if (typeId != null) {
|
||||
java.util.List<AbilityVariant> variants = AbilityController.Instance.getVariantsForType(typeId);
|
||||
if (variants.size() > 1) {
|
||||
pendingTypeId = typeId;
|
||||
setSubGui(new SubGuiAbilityVariantSelect(variants));
|
||||
return;
|
||||
}
|
||||
Ability newAbility = AbilityController.Instance.create(typeId);
|
||||
if (newAbility != null) {
|
||||
if (variants.size() == 1) {
|
||||
variants.get(0).apply(newAbility);
|
||||
}
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
if (hasDuplicateName(newAbility)) {
|
||||
pendingSaveAbility = newAbility;
|
||||
|
||||
Reference in new issue
Block a user