diff --git a/dependencies.gradle b/dependencies.gradle
index 2e3e5db52..acf5c1bec 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -38,7 +38,7 @@ dependencies {
runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.5.4-GTNH:dev")
runtimeOnlyNonPublishable("curse.maven:Nashorn-374413:2924946")
- compileOnly(files("./tools/bukkit-api/spigot-api-ONLY.jar"))
+// compileOnly(files("./tools/bukkit-api/spigot-api-ONLY.jar"))
// compileOnly(files("./tools/bukkit-api/Vault-1.4.1.jar"))
shadowImplementation(project.files("tools/standalone-JaninoLoader-1.0.1-ALPHA.jar"))
diff --git a/src/api b/src/api
index 2054012fa..03be7a5ca 160000
--- a/src/api
+++ b/src/api
@@ -1 +1 @@
-Subproject commit 2054012fae799fa233b99824b4f649d311eff86f
+Subproject commit 03be7a5cac601cd19509df9dc7bac17081aea591
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java
index c75d7d5d6..015f6361b 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java
@@ -29,15 +29,12 @@ import noppes.npcs.scripted.NpcAPI;
import noppes.npcs.scripted.event.AbilityEvent;
import noppes.npcs.scripted.event.player.PlayerAbilityEvent;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import net.minecraft.entity.Entity;
-import somehussar.gui.annotationHandling.GuiEditable;
public abstract class Ability implements IAbility {
@@ -149,6 +146,9 @@ public abstract class Ability implements IAbility {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
+ public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) {
+ }
+
/**
* Apply damage to an entity with ability hit event support.
* Fires the abilityHit script event, allowing scripts to modify or cancel the damage.
@@ -342,40 +342,51 @@ public abstract class Ability implements IAbility {
}
/**
- * Returns the declarative field definitions for this ability's GUI.
- * Override in subclasses to declare type-specific and visual fields.
- * The GUI rendering engine in SubGuiAbilityConfig uses this list to
- * automatically build the Type and Visual tabs.
+ * Add field definitions for this ability's GUI.
+ * Override in subclasses to add type-specific fields. The list already contains
+ * the base fields (General, Target, Effects tabs), so subclasses can also
+ * modify, insert into, or remove base fields using {@link FieldDef#insertBefore},
+ * {@link FieldDef#insertAfter}, or direct list manipulation.
+ *
+ * Fields added here without an explicit {@code .tab()} will default to the "Type" tab.
+ *
+ * @param defs The mutable list of field definitions to add to or modify
*/
@SideOnly(Side.CLIENT)
- public List getFieldDefinitions() {
- return Collections.emptyList();
+ public void getAbilityDefinitions(List defs) {
}
/**
- * Returns field definitions for all base Ability fields (General, Target, Effects tabs).
+ * Builds and returns all field definitions for this ability's GUI.
+ * Base fields (General, Target, Effects) are added first, then
+ * {@link #getAbilityDefinitions(List)} is called so subclasses can add
+ * type-specific fields and modify any existing fields.
*/
@SideOnly(Side.CLIENT)
- public List getBaseFieldDefinitions() {
+ public final List getAllDefinitions() {
List defs = new ArrayList<>();
- // General tab
+ // ── General tab ──────────────────────────────────────────────
defs.add(FieldDef.stringField("gui.name", this::getName, this::setName)
.tab("General"));
- defs.add(FieldDef.intField("ability.weight", this::getWeight, this::setWeight)
- .tab("General").range(1, 1000).column(ColumnHint.LEFT));
- defs.add(FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled)
- .tab("General").column(ColumnHint.RIGHT));
+ defs.add(FieldDef.row(
+ FieldDef.intField("ability.weight", this::getWeight, this::setWeight).range(1, 1000),
+ FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled)
+ ).tab("General"));
defs.add(FieldDef.section("ability.section.timing").tab("General"));
defs.add(FieldDef.intField("ability.windUpTicks", this::getRawWindUpTicks, this::setWindUpTicks)
- .tab("General").range(0, 1000).column(ColumnHint.LEFT)
- .visibleWhen(() -> !hasWindUpAnimation() || !isSyncWindupWithAnimation()));
- defs.add(FieldDef.labelField("ability.windUpTicks", () -> getWindUpTicks() + "t")
- .tab("General").column(ColumnHint.LEFT)
- .visibleWhen(() -> hasWindUpAnimation() && isSyncWindupWithAnimation()));
- defs.add(FieldDef.boolField("ability.syncWindup", this::isSyncWindupWithAnimation, this::setSyncWindupWithAnimation)
- .tab("General").hover("ability.hover.sync").column(ColumnHint.RIGHT)
- .visibleWhen(this::hasWindUpAnimation));
+ .tab("General").range(0, 1000)
+ .visibleWhen(() -> !hasWindUpAnimation()));
+ defs.add(FieldDef.row(
+ FieldDef.intField("ability.windUpTicks", this::getRawWindUpTicks, this::setWindUpTicks).range(0, 1000),
+ FieldDef.boolField("ability.syncWindup", this::isSyncWindupWithAnimation, this::setSyncWindupWithAnimation)
+ .hover("ability.hover.sync")
+ ).tab("General").visibleWhen(() -> hasWindUpAnimation() && !isSyncWindupWithAnimation()));
+ defs.add(FieldDef.row(
+ FieldDef.labelField("ability.windUpTicks", () -> getWindUpTicks() + "t"),
+ FieldDef.boolField("ability.syncWindup", this::isSyncWindupWithAnimation, this::setSyncWindupWithAnimation)
+ .hover("ability.hover.sync")
+ ).tab("General").visibleWhen(() -> hasWindUpAnimation() && isSyncWindupWithAnimation()));
defs.add(FieldDef.intField("ability.cooldownTicks", this::getCooldownTicks, this::setCooldownTicks)
.tab("General").range(0, 10000));
defs.add(FieldDef.section("ability.section.movement").tab("General"));
@@ -388,30 +399,45 @@ public abstract class Ability implements IAbility {
}
})
.tab("General").hover("ability.hover.lockMove"));
- defs.add(FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible)
- .tab("General").hover("ability.hover.interruptible").column(ColumnHint.LEFT));
- defs.add(FieldDef.intField("ability.dazedTicks", this::getDazedTicks, this::setDazedTicks)
- .tab("General").range(0, 1000)
- .visibleWhen(this::isInterruptible).column(ColumnHint.RIGHT));
+ defs.add(FieldDef.row(
+ FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible)
+ .hover("ability.hover.interruptible"),
+ FieldDef.intField("ability.dazedTicks", this::getDazedTicks, this::setDazedTicks)
+ .range(0, 1000).visibleWhen(this::isInterruptible)
+ ).tab("General"));
- // Target tab
- defs.add(FieldDef.intField("ability.minRange", () -> (int) getMinRange(), v -> setMinRange(v))
- .tab("Target").range(0, 100).column(ColumnHint.LEFT));
- defs.add(FieldDef.intField("ability.maxRange", () -> (int) getMaxRange(), v -> setMaxRange(v))
- .tab("Target").range(1, 100).column(ColumnHint.RIGHT));
+ // ── Target tab ───────────────────────────────────────────────
+ defs.add(FieldDef.row(
+ FieldDef.intField("ability.minRange", () -> (int) getMinRange(), v -> setMinRange(v)).range(0, 100),
+ FieldDef.intField("ability.maxRange", () -> (int) getMaxRange(), v -> setMaxRange(v)).range(1, 100)
+ ).tab("Target"));
if (!isTargetingModeLocked()) {
- defs.add(FieldDef.enumField("ability.targetingMode", TargetingMode.class,
- this::getTargetingMode, this::setTargetingMode)
- .tab("Target").hover("ability.hover.targeting"));
+ TargetingMode[] allowed = getAllowedTargetingModes();
+ if (allowed != null && allowed.length > 0) {
+ String[] allowedKeys = new String[allowed.length];
+ for (int i = 0; i < allowed.length; i++) {
+ allowedKeys[i] = allowed[i].name();
+ }
+ defs.add(FieldDef.stringEnumField("ability.targetingMode", allowedKeys,
+ () -> this.getTargetingMode().name(),
+ v -> {
+ try { this.setTargetingMode(TargetingMode.valueOf(v)); }
+ catch (Exception ignored) {}
+ })
+ .tab("Target").hover("ability.hover.targeting"));
+ } else {
+ defs.add(FieldDef.enumField("ability.targetingMode", TargetingMode.class,
+ this::getTargetingMode, this::setTargetingMode)
+ .tab("Target").hover("ability.hover.targeting"));
+ }
}
- // Effects tab - Sounds
+ // ── Effects tab ──────────────────────────────────────────────
defs.add(FieldDef.section("ability.section.sounds").tab("Effects"));
defs.add(FieldDef.soundSubGui("ability.windUpSound", this::getWindUpSound, this::setWindUpSound)
.tab("Effects"));
defs.add(FieldDef.soundSubGui("ability.activeSound", this::getActiveSound, this::setActiveSound)
.tab("Effects"));
- // Effects tab - Animations
defs.add(FieldDef.section("ability.section.animations").tab("Effects"));
defs.add(FieldDef.animSubGui("ability.windUpAnimation",
this::getWindUpAnimationId, this::setWindUpAnimationId,
@@ -422,11 +448,10 @@ public abstract class Ability implements IAbility {
this::getActiveAnimationName, this::setActiveAnimationName)
.tab("Effects"));
- // Effects tab - Telegraph
TelegraphType tType = getTelegraphType();
if (tType != null && tType != TelegraphType.NONE) {
defs.add(FieldDef.section("ability.section.telegraph").tab("Effects")
- .tooltip("telegraph." + tType.name().toLowerCase()));
+ .hover("telegraph." + tType.name().toLowerCase()));
defs.add(FieldDef.boolField("ability.showTelegraph", this::isShowTelegraph, this::setShowTelegraph)
.tab("Effects").hover("ability.hover.showTelegraph"));
defs.add(FieldDef.colorSubGui("ability.windUpColor", this::getWindUpColor, this::setWindUpColor)
@@ -435,24 +460,17 @@ public abstract class Ability implements IAbility {
.tab("Effects").visibleWhen(this::isShowTelegraph));
}
- return defs;
- }
-
- /**
- * Returns ALL field definitions: base (General/Target/Effects) + type-specific (Type/Visual).
- */
- @SideOnly(Side.CLIENT)
- public final List getAllFieldDefinitions() {
- List all = new ArrayList<>(getBaseFieldDefinitions());
- List typeDefs = getFieldDefinitions();
- // Default tab for type-specific fields that don't explicitly set one
- for (FieldDef def : typeDefs) {
- if (def.getTab() == null) {
- def.tab("Type");
+ // ── Type-specific fields ─────────────────────────────────────
+ int baseSize = defs.size();
+ getAbilityDefinitions(defs);
+ // Default tab for ability-added fields that don't specify one
+ for (int i = baseSize; i < defs.size(); i++) {
+ if (defs.get(i).getTab() == null) {
+ defs.get(i).tab("Type");
}
}
- all.addAll(typeDefs);
- return all;
+
+ return defs;
}
/**
@@ -543,8 +561,12 @@ public abstract class Ability implements IAbility {
instance.setEntityIdToFollow(caster.getEntityId());
// For LINE/CONE telegraphs: track target direction during windup
+ // Only track if movement is NOT locked during windup (locked = NPC can't rotate)
if ((telegraphType == TelegraphType.LINE || telegraphType == TelegraphType.CONE) && target != null) {
- instance.setTargetEntityId(target.getEntityId());
+ if (!lockMovement.locksWindup()) {
+ instance.setTargetEntityId(target.getEntityId());
+ }
+ // If windup is locked, yaw stays fixed at creation time
}
} else if (target != null) {
// AOE_TARGET abilities: telegraph follows target during windup
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java
index eba8795f7..f12ebd421 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java
@@ -364,10 +364,11 @@ public class AbilityController implements IAbilityHandler {
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:laser_shot", AbilityLaserShot::new);
- registerType("cnpc:beam", AbilityEnergyBeam::new);
+ registerType("cnpc:beam", AbilityBeam::new);
registerType("cnpc:dual_beam", AbilityBeamDual::new);
// Movement abilities
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyColorData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyColorData.java
index 4bd841742..9ed99e22e 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyColorData.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyColorData.java
@@ -16,6 +16,11 @@ public class EnergyColorData {
public EnergyColorData() {}
+ public EnergyColorData(int innerColor, int outerColor) {
+ this.innerColor = innerColor;
+ this.outerColor = outerColor;
+ }
+
public EnergyColorData(int innerColor, int outerColor, boolean outerColorEnabled,
float outerColorWidth, float outerColorAlpha, float rotationSpeed) {
this.innerColor = innerColor;
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java
index a044e1cf4..5edd0a718 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.GuiButton;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.builder.FieldType;
import noppes.npcs.client.gui.builder.GuiFieldBuilder;
@@ -111,5 +112,69 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
return y;
}
+ // ═══════════════════════════════════════════════════════════════════
+ // EVENT HANDLING OVERRIDES
+ // ═══════════════════════════════════════════════════════════════════
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean handleButtonEvent(int buttonId, GuiButton button) {
+ // Check effects list buttons via metadata
+ int[] meta = effectWidgetMeta.get(buttonId);
+ if (meta != null) {
+ FieldDef def = buttonFieldMap.get(buttonId);
+ if (def == null) def = clearFieldMap.get(buttonId);
+ if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
+ List effects = (List) def.getValue();
+ if (effects == null) return false;
+ int effectIdx = meta[0];
+ int action = meta[1];
+
+ switch (action) {
+ case 0: // Type changed
+ if (effectIdx < effects.size()) {
+ effects.get(effectIdx).setType(AbilityEffect.EffectType.fromOrdinal(((GuiNpcButton) button).getValue()));
+ }
+ return true;
+ case 2: // Amp changed
+ if (effectIdx < effects.size()) {
+ effects.get(effectIdx).setAmplifier(((GuiNpcButton) button).getValue());
+ }
+ return true;
+ case 3: // Delete
+ if (effectIdx < effects.size()) {
+ effects.remove(effectIdx);
+ }
+ return true;
+ case 4: // Add
+ if (effects.size() < 5) {
+ effects.add(new AbilityEffect(AbilityEffect.EffectType.SLOWNESS, 60, 0));
+ }
+ return true;
+ }
+ }
+ }
+
+ return super.handleButtonEvent(buttonId, button);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean handleTextFieldEvent(int textFieldId, GuiNpcTextField field) {
+ int[] meta = effectWidgetMeta.get(textFieldId);
+ if (meta != null && meta[1] == 1) { // duration field
+ FieldDef def = textFieldMap.get(textFieldId);
+ if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
+ List effects = (List) def.getValue();
+ if (effects != null && meta[0] < effects.size()) {
+ effects.get(meta[0]).setDurationTicks(field.getInteger());
+ }
+ return true;
+ }
+ }
+
+ return super.handleTextFieldEvent(textFieldId, field);
+ }
+
public Map getEffectWidgetMeta() { return effectWidgetMeta; }
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java
similarity index 87%
rename from src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java
rename to src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java
index 2918a4b08..25a4edf76 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java
@@ -17,7 +17,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityEnergyBeam;
@@ -31,7 +30,7 @@ import noppes.npcs.entity.EntityNPCInterface;
* The beam stays attached to the origin and curves as it homes in on target.
* Cannot cross over itself (self-intersection prevention).
*/
-public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
+public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
// Shape properties (standalone)
private float beamWidth = 0.4f;
@@ -48,7 +47,7 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
// Transient state for beam entity (used during windup charging)
private transient EntityAbilityBeam beamEntity = null;
- public AbilityEnergyBeam() {
+ public AbilityBeam() {
this.typeId = "ability.cnpc.beam";
this.name = "Beam";
this.targetingMode = TargetingMode.AGGRO_TARGET;
@@ -277,24 +276,32 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed).column(ColumnHint.RIGHT),
+ 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.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ 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.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength)
- .visibleWhen(this::isHoming).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.range", this::getHomingRange, this::setHomingRange)
- .visibleWhen(this::isHoming).column(ColumnHint.RIGHT),
+ 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"),
@@ -311,19 +318,23 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
.tab("ability.tab.visual"),
FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled),
- FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
- .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
- .tab("ability.tab.visual").range(0, 1).visibleWhen(this::isOuterColorEnabled).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.RIGHT)
- );
+ 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")
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeamDual.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeamDual.java
index 690930987..5cdf297fb 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeamDual.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeamDual.java
@@ -7,7 +7,6 @@ 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.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
@@ -378,17 +377,23 @@ public class AbilityBeamDual extends Ability {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed).column(ColumnHint.RIGHT),
+ 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.floatField("ability.beamWidth", this::getBeamWidth, this::setBeamWidth).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.headSize", this::getHeadSize, this::setHeadSize).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ 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)
@@ -396,10 +401,12 @@ public class AbilityBeamDual extends Ability {
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).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.range", this::getHomingRange, this::setHomingRange)
- .visibleWhen(this::isHoming).column(ColumnHint.RIGHT),
+ 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"),
@@ -420,24 +427,28 @@ public class AbilityBeamDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(0), v -> setOuterColor(0, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(0), v -> setLightningDensity(0, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(0), v -> setLightningRadius(0, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.RIGHT),
+ 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.enumField("ability.anchorPoint", AnchorPoint.class,
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
@@ -452,25 +463,29 @@ public class AbilityBeamDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(1), v -> setOuterColor(1, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(1), v -> setLightningDensity(1, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(1), v -> setLightningRadius(1, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.RIGHT)
- );
+ 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
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java
index d8cc3408d..e09ff7e11 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java
@@ -16,7 +16,6 @@ import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityCharge;
@@ -398,13 +397,17 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.chargeSpeed", this::getChargeSpeed, this::setChargeSpeed).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.hitWidth", this::getHitWidth, this::setHitWidth).column(ColumnHint.RIGHT),
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("ability.chargeSpeed", this::getChargeSpeed, this::setChargeSpeed)
+ ),
+ FieldDef.row(
+ FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
+ FieldDef.floatField("ability.hitWidth", this::getHitWidth, this::setHitWidth)
+ ),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java
index f8419a1b8..405dac612 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java
@@ -13,7 +13,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityCutter;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -114,12 +113,16 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
switch (sweepMode) {
case SWIPE:
+ float prevRotation = currentRotation;
+ currentRotation += sweepSpeed;
if (currentRotation > arcAngle / 2.0f) {
- signalCompletion(); // Swipe arc complete
+ // Final sweep — check from prevRotation to end of arc
+ performSweepDamageRange(caster, world, innerRadius, range, prevRotation, arcAngle / 2.0f);
+ signalCompletion();
return;
}
- performSweepDamage(caster, world, innerRadius, range, currentRotation);
- currentRotation += sweepSpeed;
+ // Check entities between previous and current sweep position
+ performSweepDamageRange(caster, world, innerRadius, range, prevRotation, currentRotation);
break;
case SPIN:
@@ -127,15 +130,23 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
signalCompletion(); // Spin duration complete
return;
}
+ float prevSpin = currentRotation;
currentRotation = (currentRotation + sweepSpeed) % 360.0f;
hitEntities.clear();
- performSweepDamage(caster, world, innerRadius, range, currentRotation);
+ performSweepDamageRange(caster, world, innerRadius, range, prevSpin, currentRotation);
break;
}
}
- private void performSweepDamage(EntityLivingBase caster, World world, float minDist, float maxDist, float angleOffset) {
- float casterYaw = caster.rotationYaw + angleOffset;
+ /**
+ * Check for entities between two sweep angles, ensuring no gaps between ticks.
+ * For SWIPE mode, this covers the continuous arc from startAngle to endAngle.
+ */
+ private void performSweepDamageRange(EntityLivingBase caster, World world, float minDist, float maxDist,
+ float startAngle, float endAngle) {
+ // Calculate the angular range covered this tick
+ float minAngle = Math.min(startAngle, endAngle);
+ float maxAngle = Math.max(startAngle, endAngle);
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
caster.posX - maxDist, caster.posY - 1, caster.posZ - maxDist,
@@ -156,7 +167,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
double dist = Math.sqrt(dx * dx + dz * dz);
if (dist < minDist || dist > maxDist) continue;
- if (!isInArc(dx, dz, casterYaw, arcAngle)) continue;
+ if (!isInSweepRange(dx, dz, caster.rotationYaw, minAngle, maxAngle)) continue;
hitEntities.add(entity.getEntityId());
@@ -173,6 +184,16 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
}
}
+ /**
+ * Check if an entity falls within the angular range between two sweep positions.
+ * This ensures entities at long range can't slip between tick-gaps.
+ */
+ private boolean isInSweepRange(double dx, double dz, float casterYaw, float minAngle, float maxAngle) {
+ double angleToEntity = Math.toDegrees(Math.atan2(-dx, dz));
+ double entityRelative = normalizeAngle(angleToEntity - casterYaw);
+ return entityRelative >= minAngle && entityRelative <= maxAngle;
+ }
+
private boolean isInArc(double dx, double dz, float casterYaw, float arcWidth) {
double angleToEntity = Math.toDegrees(Math.atan2(-dx, dz));
double angleDiff = normalizeAngle(angleToEntity - casterYaw);
@@ -317,16 +338,20 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.range", this::getRange, this::setRange).column(ColumnHint.RIGHT),
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("gui.range", this::getRange, this::setRange)
+ ),
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
FieldDef.section("ability.section.sweep"),
FieldDef.enumField("ability.sweepMode", SweepMode.class, this::getSweepModeEnum, this::setSweepModeEnum)
.hover("ability.hover.sweepMode"),
- FieldDef.floatField("ability.arcAngle", this::getArcAngle, this::setArcAngle).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.innerRadius", this::getInnerRadius, this::setInnerRadius).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.arcAngle", this::getArcAngle, this::setArcAngle),
+ FieldDef.floatField("ability.innerRadius", this::getInnerRadius, this::setInnerRadius)
+ ),
FieldDef.floatField("ability.sweepSpeed", this::getSweepSpeed, this::setSweepSpeed),
FieldDef.intField("ability.spinDuration", this::getSpinDurationTicks, this::setSpinDurationTicks)
.range(1, 1000)
@@ -334,6 +359,6 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing)
.hover("ability.hover.piercing"),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java
index b045620a4..10cfd2978 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java
@@ -14,7 +14,6 @@ import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.api.ability.type.IAbilityDash;
@@ -93,6 +92,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
// Runtime state
private transient Vec3 dashDirection;
private transient double startX, startY, startZ;
+ private transient double prevTickX, prevTickZ;
private transient DashDirection chosenDirection;
public AbilityDash() {
@@ -131,6 +131,8 @@ public class AbilityDash extends Ability implements IAbilityDash {
startX = caster.posX;
startY = caster.posY;
startZ = caster.posZ;
+ prevTickX = caster.posX;
+ prevTickZ = caster.posZ;
// Choose random direction based on mode
DashDirection[] directions = dashMode == DashMode.AGGRESSIVE
@@ -164,6 +166,22 @@ public class AbilityDash extends Ability implements IAbilityDash {
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
if (dashDirection == null) return;
+ // Stall detection: if entity hasn't moved since last tick, it's stuck against a wall
+ if (tick > 1) {
+ double movedThisTick = Math.sqrt(
+ Math.pow(caster.posX - prevTickX, 2) +
+ Math.pow(caster.posZ - prevTickZ, 2));
+ if (movedThisTick < 0.01) {
+ caster.motionX = 0;
+ caster.motionZ = 0;
+ caster.velocityChanged = true;
+ signalCompletion();
+ return;
+ }
+ }
+ prevTickX = caster.posX;
+ prevTickZ = caster.posZ;
+
// Calculate distance traveled
double distanceTraveled = Math.sqrt(
Math.pow(caster.posX - startX, 2) +
@@ -352,12 +370,14 @@ public class AbilityDash extends Ability implements IAbilityDash {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.enumField("ability.dashMode", DashMode.class, this::getDashModeEnum, this::setDashModeEnum)
.hover("ability.hover.dashMode"),
- FieldDef.floatField("ability.dashDistance", this::getDashDistance, this::setDashDistance).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.dashSpeed", this::getDashSpeed, this::setDashSpeed).column(ColumnHint.RIGHT)
- );
+ FieldDef.row(
+ FieldDef.floatField("ability.dashDistance", this::getDashDistance, this::setDashDistance),
+ FieldDef.floatField("ability.dashSpeed", this::getDashSpeed, this::setDashSpeed)
+ )
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java
index a7d00eb90..7ee8a9260 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java
@@ -17,7 +17,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityDisc;
@@ -274,17 +273,23 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed).column(ColumnHint.RIGHT),
+ 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.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius),
+ FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness)
+ ),
+ 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"),
@@ -311,19 +316,23 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
.tab("ability.tab.visual"),
FieldDef.colorSubGui("ability.outerColor", this::getOuterColor, this::setOuterColor)
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled),
- FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
- .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
- .tab("ability.tab.visual").range(0, 1).visibleWhen(this::isOuterColorEnabled).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.RIGHT)
- );
+ 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")
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDiscDual.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDiscDual.java
index b7b3383c0..e2e07da15 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDiscDual.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDiscDual.java
@@ -7,7 +7,6 @@ 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.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
@@ -387,17 +386,23 @@ public class AbilityDiscDual extends Ability {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed).column(ColumnHint.RIGHT),
+ 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.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getDiscRadius, this::setDiscRadius),
+ FieldDef.floatField("gui.thickness", this::getDiscThickness, this::setDiscThickness)
+ ),
+ 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)
@@ -432,24 +437,28 @@ public class AbilityDiscDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(0), v -> setOuterColor(0, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(0), v -> setLightningDensity(0, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(0), v -> setLightningRadius(0, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.RIGHT),
+ 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.enumField("ability.anchorPoint", AnchorPoint.class,
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
@@ -464,25 +473,29 @@ public class AbilityDiscDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(1), v -> setOuterColor(1, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(1), v -> setLightningDensity(1, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(1), v -> setLightningRadius(1, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.RIGHT)
- );
+ 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
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java
index b545d32d9..eab84d138 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java
@@ -1,28 +1,29 @@
package kamkeel.npcs.controllers.data.ability.type;
-import kamkeel.npcs.controllers.data.ability.Ability;
-import kamkeel.npcs.controllers.data.ability.LockMovementType;
-import kamkeel.npcs.controllers.data.ability.TargetingMode;
-import kamkeel.npcs.controllers.data.ability.UserType;
+import kamkeel.npcs.controllers.data.ability.*;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
+import noppes.npcs.api.handler.data.IAnimation;
+import noppes.npcs.controllers.AnimationController;
+import noppes.npcs.controllers.data.Animation;
+import noppes.npcs.controllers.data.PlayerData;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityGuard;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
+import noppes.npcs.util.ValueUtil;
import java.util.Arrays;
import java.util.List;
-import java.util.Random;
/**
* Guard ability: Defensive stance that reduces incoming damage.
@@ -30,23 +31,40 @@ import java.util.Random;
*/
public class AbilityGuard extends Ability implements IAbilityGuard {
- private static final Random RANDOM = new Random();
-
- // Type-specific parameters
private int durationTicks = 60;
private float damageReduction = 0.5f;
- private boolean canCounter = false;
- private CounterType counterType = CounterType.FLAT;
+ private AbilityGuard.CounterType counterType = AbilityGuard.CounterType.FLAT;
private float counterValue = 6.0f;
- private float counterChance = 0.3f;
- private String counterSound = "random.wood_click";
+ private String counterSound = "random.anvil_land";
+ private boolean canCounter = false;
+ private int counterWindow = 10;
private int counterAnimationId = -1;
+ private String counterAnimationName;
// Runtime state
private transient EntityLivingBase lastAttacker;
private transient boolean counterTriggered;
+ private transient boolean counterEligible;
private transient float lastDamageTaken;
+ public AbilityGuard() {
+ this.typeId = "ability.cnpc.guard";
+ this.name = "Guard";
+ this.targetingMode = TargetingMode.SELF;
+ this.lockMovement = LockMovementType.ACTIVE;
+ this.cooldownTicks = 0;
+ this.windUpTicks = 0;
+ this.interruptible = false; // Hard to interrupt while guarding
+ // No telegraph for guard - it's a defensive stance
+ this.telegraphType = TelegraphType.NONE;
+ this.showTelegraph = false;
+ this.activeSound = "random.anvil_use";
+ this.allowedBy = UserType.BOTH;
+
+ this.activeAnimationName = "Ability_Guard_Active";
+ this.counterAnimationName = "Ability_Guard_Counter";
+ }
+
public enum CounterType {
FLAT,
PERCENT;
@@ -61,22 +79,6 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
}
}
- public AbilityGuard() {
- this.typeId = "ability.cnpc.guard";
- this.name = "Guard";
- this.targetingMode = TargetingMode.SELF;
- this.lockMovement = LockMovementType.ACTIVE;
- this.cooldownTicks = 0;
- this.windUpTicks = 10;
- this.interruptible = false; // Hard to interrupt while guarding
- // No telegraph for guard - it's a defensive stance
- this.telegraphType = TelegraphType.NONE;
- this.showTelegraph = false;
- this.windUpSound = "random.anvil_use";
- this.activeSound = "random.anvil_land";
- this.allowedBy = UserType.NPC_ONLY;
- }
-
@Override
public boolean isTargetingModeLocked() {
return true;
@@ -92,41 +94,56 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
lastAttacker = null;
counterTriggered = false;
lastDamageTaken = 0.0f;
+ counterEligible = true;
}
@Override
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
- // Counter attack logic if triggered
- if (canCounter && counterTriggered && lastAttacker != null && !world.isRemote) {
+ if (canCounter && counterEligible && counterTriggered && lastAttacker != null && !world.isRemote) {
performCounter(caster, world);
}
- // Check if guard duration has ended
+ if (tick > counterWindow && counterEligible) {
+ counterEligible = false;
+ }
+
if (tick >= durationTicks) {
signalCompletion();
}
+
+ // Just enough to see the counter animation
+ if (counterTriggered && tick >= counterWindow + 10) {
+ signalCompletion();
+ }
}
- /**
- * Called externally when the caster takes damage while guarding.
- * This should be called from the damage handling code.
- *
- * @param attacker The entity that attacked
- * @param damage The damage amount (after reduction)
- */
public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) {
- if (!canCounter || attacker == null) return;
- if (!isDirectHit(source)) return;
- if (RANDOM.nextFloat() >= counterChance) return;
+ if (!isGuarding() || attacker == null) return;
lastAttacker = attacker;
lastDamageTaken = damage;
- counterTriggered = true;
- // Counter will be performed in onActiveTick - don't self-interrupt
+
+ float reduction = ValueUtil.clamp(getDamageReductionFactor(), 0, damage);
+ float newHealth = ValueUtil.clamp(caster.getHealth() + reduction, 0, caster.getMaxHealth());
+
+ caster.setHealth(newHealth);
+
+ if (!canCounter || !counterEligible || counterTriggered || !isDirectHit(source)) {
+ caster.setHealth(newHealth);
+ } else {
+ caster.setHealth(ValueUtil.clamp(caster.getHealth() + damage, 0, caster.getMaxHealth()));
+ counterTriggered = true;
+ }
+ }
+
+ private boolean isDirectHit(DamageSource source) {
+ if (source == null) return false;
+ if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion()) return false;
+ return source.getEntity() instanceof EntityLivingBase;
}
private void performCounter(EntityLivingBase caster, World world) {
- float counterDamage = counterType == CounterType.PERCENT
+ float counterDamage = counterType == AbilityGuard.CounterType.PERCENT
? lastDamageTaken * (counterValue / 100.0f)
: counterValue;
@@ -136,18 +153,32 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
}
// Use shared animation utility - only available for NPCs
if (caster instanceof EntityNPCInterface) {
- ((EntityNPCInterface) caster).abilities.playAbilityAnimation(counterAnimationId);
+ if (getCounterAnimation() instanceof IAnimation) {
+ ((EntityNPCInterface) caster).abilities.playAbilityAnimation((Animation) getCounterAnimation());
+ }
+ } else if (caster instanceof EntityPlayer) {
+ if (getCounterAnimation() instanceof IAnimation) {
+ PlayerData.get((EntityPlayer) caster).abilityData.playAbilityAnimation((Animation) getCounterAnimation());
+ }
}
- counterTriggered = false;
+ counterEligible = false;
lastAttacker = null;
lastDamageTaken = 0.0f;
}
- private boolean isDirectHit(DamageSource source) {
- if (source == null) return false;
- if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion()) return false;
- return source.getEntity() instanceof EntityLivingBase;
+ private IAnimation getCounterAnimation() {
+ if (AnimationController.Instance == null) return null;
+
+ if (counterAnimationId > 0) {
+ return AnimationController.Instance.get(counterAnimationId);
+ }
+
+ if (!counterAnimationName.isEmpty()) {
+ return AnimationController.Instance.get(counterAnimationName, true);
+ }
+
+ return null;
}
/**
@@ -155,7 +186,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
* Returns 0.5 for 50% reduction.
*/
public float getDamageReductionFactor() {
- if (isExecuting() && getPhase() == kamkeel.npcs.controllers.data.ability.AbilityPhase.ACTIVE) {
+ if (isExecuting() && getPhase() == AbilityPhase.ACTIVE) {
return damageReduction;
}
return 0.0f;
@@ -165,7 +196,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
* Check if the caster is currently in guard stance.
*/
public boolean isGuarding() {
- return isExecuting() && getPhase() == kamkeel.npcs.controllers.data.ability.AbilityPhase.ACTIVE;
+ return isExecuting() && getPhase() == AbilityPhase.ACTIVE;
}
@Override
@@ -186,11 +217,12 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
nbt.setInteger("durationTicks", durationTicks);
nbt.setFloat("damageReduction", damageReduction);
nbt.setBoolean("canCounter", canCounter);
+ nbt.setInteger("counterWindow", counterWindow);
nbt.setString("counterType", counterType.name());
nbt.setFloat("counterValue", counterValue);
- nbt.setFloat("counterChance", counterChance);
nbt.setString("counterSound", counterSound);
nbt.setInteger("counterAnimationId", counterAnimationId);
+ nbt.setString("counterAnimationName", counterAnimationName);
}
@Override
@@ -198,6 +230,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
this.durationTicks = nbt.hasKey("durationTicks") ? nbt.getInteger("durationTicks") : 60;
this.damageReduction = nbt.hasKey("damageReduction") ? nbt.getFloat("damageReduction") : 0.5f;
this.canCounter = nbt.hasKey("canCounter") && nbt.getBoolean("canCounter");
+ this.counterWindow = nbt.hasKey("counterWindow") ? nbt.getInteger("counterWindow") : 10;
try {
this.counterType = CounterType.valueOf(nbt.getString("counterType"));
} catch (Exception e) {
@@ -210,9 +243,11 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
} else {
this.counterValue = 6.0f;
}
- this.counterChance = nbt.hasKey("counterChance") ? nbt.getFloat("counterChance") : 0.3f;
this.counterSound = nbt.hasKey("counterSound") ? nbt.getString("counterSound") : "random.wood_click";
this.counterAnimationId = nbt.hasKey("counterAnimationId") ? nbt.getInteger("counterAnimationId") : -1;
+ this.counterAnimationName = nbt.hasKey("counterAnimationName") ?
+ nbt.getString("counterAnimationName") :
+ "Ability_Guard_Counter";
}
// Getters & Setters
@@ -224,17 +259,26 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
this.durationTicks = Math.max(1, durationTicks);
}
+ @Override
+ public int getCounterWindow() {
+ return this.counterWindow;
+ }
+
+ @Override
+ public void setCounterWindow(int ticks) {
+ this.counterWindow = Math.min(durationTicks, ticks);
+ }
+
+ @Override
public float getDamageReduction() {
return damageReduction;
}
+ @Override
public void setDamageReduction(float damageReduction) {
this.damageReduction = damageReduction;
}
- public boolean isCanCounter() {
- return canCounter;
- }
@Override
public boolean canCounter() {
@@ -272,14 +316,6 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
this.counterValue = counterValue;
}
- public float getCounterChance() {
- return counterChance;
- }
-
- public void setCounterChance(float counterChance) {
- this.counterChance = counterChance;
- }
-
public String getCounterSound() {
return counterSound;
}
@@ -296,25 +332,47 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
this.counterAnimationId = counterAnimationId;
}
+ public String getCounterAnimationName() {
+ return counterAnimationName;
+ }
+
+ public void setCounterAnimationName(String name) {
+ this.counterAnimationName = name;
+ }
+
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.intField("ability.duration", this::getDurationTicks, this::setDurationTicks).range(1, 1000),
FieldDef.floatField("ability.damageReduction", this::getDamageReduction, this::setDamageReduction),
FieldDef.section("ability.section.counter"),
- FieldDef.boolField("gui.enabled", this::isCanCounter, this::setCanCounter).hover("ability.hover.canCounter"),
+ FieldDef.boolField("gui.enabled", this::canCounter, this::setCanCounter).hover("ability.hover.canCounter"),
FieldDef.enumField("gui.type", CounterType.class, this::getCounterTypeEnum, this::setCounterTypeEnum)
- .hover("ability.hover.counterType").visibleWhen(this::isCanCounter),
- FieldDef.floatField("gui.value", this::getCounterValue, this::setCounterValue)
- .visibleWhen(this::isCanCounter).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.chance", this::getCounterChance, this::setCounterChance)
- .visibleWhen(this::isCanCounter).column(ColumnHint.RIGHT),
+ .hover("ability.hover.counterType").visibleWhen(this::canCounter),
+ FieldDef.row(
+ FieldDef.floatField("gui.value", this::getCounterValue, this::setCounterValue)
+ .visibleWhen(this::canCounter),
+ FieldDef.intField("ability.counterWindow", this::getCounterWindow, this::setCounterWindow)
+ .visibleWhen(this::canCounter)
+ ),
FieldDef.stringField("gui.sound", this::getCounterSound, this::setCounterSound)
- .visibleWhen(this::isCanCounter),
- FieldDef.intField("gui.animation", this::getCounterAnimationId, this::setCounterAnimationId)
- .visibleWhen(this::isCanCounter),
+ .visibleWhen(this::canCounter),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+
+ ));
+
+ FieldDef.insertAfter(defs, "ability.activeSound",
+ FieldDef.soundSubGui("ability.counterSound", this::getCounterSound, this::setCounterSound)
+ .tab("Effects").visibleWhen(this::canCounter));
+
+ FieldDef.insertAfter(defs, "ability.activeAnimation",
+ FieldDef.animSubGui("ability.counterAnimation",
+ this::getCounterAnimationId, this::setCounterAnimationId,
+ this::getCounterAnimationName, this::setCounterAnimationName)
+ .tab("Effects").visibleWhen(this::canCounter));
+
+ FieldDef.modifyVisibility(defs, "ability.windUpAnimation", () -> false);
+ FieldDef.modifyVisibility(defs, "ability.windUpSound", () -> false);
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java
index a5996d487..99ea47172 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java
@@ -16,7 +16,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityHazard;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -631,26 +630,32 @@ public class AbilityHazard extends Ability implements IAbilityHazard {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.intField("ability.duration", this::getDurationTicks, this::setDurationTicks).range(1, 2000),
FieldDef.enumField("ability.shape", HazardShape.class, this::getShapeEnum, this::setShapeEnum)
.hover("ability.hover.shape"),
FieldDef.enumField("ability.placement", PlacementMode.class, this::getPlacementEnum, this::setPlacementEnum)
.hover("ability.hover.placement"),
FieldDef.section("ability.section.area"),
- FieldDef.floatField("gui.radius", this::getRadius, this::setRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.innerRadius", this::getInnerRadius, this::setInnerRadius).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getRadius, this::setRadius),
+ FieldDef.floatField("ability.innerRadius", this::getInnerRadius, this::setInnerRadius)
+ ),
FieldDef.section("ability.section.damage"),
- FieldDef.floatField("ability.damagePerSecond", this::getDamagePerSecond, this::setDamagePerSecond).column(ColumnHint.LEFT),
- FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.damagePerSecond", this::getDamagePerSecond, this::setDamagePerSecond),
+ FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval)
+ ),
FieldDef.section("ability.section.debuffs"),
- FieldDef.intField("ability.slownessLevel", this::getSlownessLevel, this::setSlownessLevel).column(ColumnHint.LEFT),
- FieldDef.intField("ability.debuffDuration", this::getDebuffDuration, this::setDebuffDuration).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.intField("ability.slownessLevel", this::getSlownessLevel, this::setSlownessLevel),
+ FieldDef.intField("ability.debuffDuration", this::getDebuffDuration, this::setDebuffDuration)
+ ),
FieldDef.intField("ability.poisonLevel", this::getPoisonLevel, this::setPoisonLevel),
FieldDef.boolField("ability.affectsCaster", this::isAffectsCaster, this::setAffectsCaster)
.hover("ability.hover.affectsCaster"),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java
index 72f3813c3..107db40c6 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java
@@ -11,7 +11,6 @@ import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.api.ability.type.IAbilityHeal;
@@ -282,20 +281,24 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.boolField("ability.instantHeal", this::isInstantHeal, this::setInstantHeal)
.hover("ability.hover.instant"),
FieldDef.intField("ability.duration", this::getDurationTicks, this::setDurationTicks)
.range(1, 1000).visibleWhen(() -> !this.isInstantHeal()),
FieldDef.section("ability.section.healing"),
- FieldDef.floatField("ability.healAmount", this::getHealAmount, this::setHealAmount).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.healPercent", this::getHealPercent, this::setHealPercent).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.healAmount", this::getHealAmount, this::setHealAmount),
+ FieldDef.floatField("ability.healPercent", this::getHealPercent, this::setHealPercent)
+ ),
FieldDef.floatField("ability.healRadius", this::getHealRadius, this::setHealRadius),
- FieldDef.boolField("ability.healSelf", this::isHealSelf, this::setHealSelf)
- .hover("ability.hover.healSelf").column(ColumnHint.LEFT),
- FieldDef.boolField("ability.healAllies", this::isHealAllies, this::setHealAllies)
- .hover("ability.hover.healAllies").column(ColumnHint.RIGHT)
- );
+ FieldDef.row(
+ FieldDef.boolField("ability.healSelf", this::isHealSelf, this::setHealSelf)
+ .hover("ability.hover.healSelf"),
+ FieldDef.boolField("ability.healAllies", this::isHealAllies, this::setHealAllies)
+ .hover("ability.hover.healAllies")
+ )
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java
index 8b6597b11..0e236de32 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java
@@ -6,38 +6,40 @@ import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
+import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityHeavyHit;
-import noppes.npcs.entity.EntityNPCInterface;
import java.util.Arrays;
import java.util.List;
/**
- * Heavy Hit ability: Single-target melee attack with optional stun.
- * Deals high damage to one target and can stun them.
+ * Heavy Hit ability: AOE rectangle melee attack in front of the caster.
+ * Deals high damage to all entities within a rectangular zone and can apply effects.
+ * Has a LINE telegraph showing the hit area during windup, making it dodgeable.
*/
public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
private float damage = 8.0f;
private float knockback = 2.0f;
+ private float hitLength = 4.0f; // How far in front of the caster the hit reaches
+ private float hitWidth = 3.0f; // How wide to each side (total width = hitWidth * 2)
public AbilityHeavyHit() {
this.typeId = "ability.cnpc.heavy_hit";
this.name = "Heavy Hit";
- this.targetingMode = TargetingMode.AGGRO_TARGET;
- this.maxRange = 3.0f;
+ this.targetingMode = TargetingMode.AOE_SELF;
+ this.maxRange = 5.0f;
this.minRange = 0.0f;
- this.lockMovement = LockMovementType.NO;
+ this.lockMovement = LockMovementType.WINDUP;
this.cooldownTicks = 0;
this.windUpTicks = 30;
- this.telegraphType = TelegraphType.POINT;
- this.showTelegraph = false;
+ this.telegraphType = TelegraphType.LINE;
+ this.showTelegraph = true;
this.windUpSound = "random.anvil_use";
this.activeSound = "random.anvil_land";
this.windUpAnimationName = "Ability_HeavyHit_Windup";
@@ -51,22 +53,66 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
@Override
public TargetingMode[] getAllowedTargetingModes() {
- return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
+ return new TargetingMode[]{TargetingMode.AOE_SELF};
+ }
+
+ @Override
+ public float getTelegraphLength() {
+ return hitLength;
+ }
+
+ @Override
+ public float getTelegraphWidth() {
+ return hitWidth * 2.0f; // Total width (hitWidth is distance to each side)
}
@Override
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
- if (world.isRemote || target == null) {
+ if (world.isRemote) {
signalCompletion();
return;
}
- // Apply damage with scripted event support
- boolean wasHit = applyAbilityDamage(caster, target, damage, knockback);
+ // Calculate forward and right vectors from caster yaw
+ float yawRad = (float) Math.toRadians(caster.rotationYaw);
+ double forwardX = -Math.sin(yawRad);
+ double forwardZ = Math.cos(yawRad);
+ double rightX = forwardZ; // perpendicular right
+ double rightZ = -forwardX;
- // Apply effects if hit wasn't cancelled
- if (wasHit) {
- applyEffects(target);
+ // Search area: AABB that encompasses the rectangle
+ float searchDist = Math.max(hitLength, hitWidth) + 1.0f;
+ @SuppressWarnings("unchecked")
+ List entities = world.getEntitiesWithinAABBExcludingEntity(
+ caster, caster.boundingBox.expand(searchDist, 2, searchDist));
+
+ boolean anyHit = false;
+ for (Entity entity : entities) {
+ if (!(entity instanceof EntityLivingBase) || entity == caster) continue;
+ EntityLivingBase livingTarget = (EntityLivingBase) entity;
+
+ double dx = livingTarget.posX - caster.posX;
+ double dz = livingTarget.posZ - caster.posZ;
+
+ // Project onto forward direction (must be in front, within hitLength)
+ double forwardDist = dx * forwardX + dz * forwardZ;
+ if (forwardDist < 0 || forwardDist > hitLength) continue;
+
+ // Project onto right direction (must be within hitWidth to each side)
+ double sideDist = dx * rightX + dz * rightZ;
+ if (Math.abs(sideDist) > hitWidth) continue;
+
+ // Entity is within the rectangle - apply damage
+ boolean wasHit = applyAbilityDamage(caster, livingTarget, damage, knockback);
+ if (wasHit) {
+ applyEffects(livingTarget);
+ anyHit = true;
+ }
+ }
+
+ // Play hit sound even if nothing was hit (the attack still happens)
+ if (!anyHit) {
+ world.playSoundAtEntity(caster, "random.anvil_land", 0.5f, 1.2f);
}
}
@@ -81,12 +127,16 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
public void writeTypeNBT(NBTTagCompound nbt) {
nbt.setFloat("damage", damage);
nbt.setFloat("knockback", knockback);
+ nbt.setFloat("hitLength", hitLength);
+ nbt.setFloat("hitWidth", hitWidth);
}
@Override
public void readTypeNBT(NBTTagCompound nbt) {
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 2.0f;
+ this.hitLength = nbt.hasKey("hitLength") ? nbt.getFloat("hitLength") : 4.0f;
+ this.hitWidth = nbt.hasKey("hitWidth") ? nbt.getFloat("hitWidth") : 3.0f;
}
// Getters & Setters
@@ -106,13 +156,36 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
this.knockback = knockback;
}
+ public float getHitLength() {
+ return hitLength;
+ }
+
+ public void setHitLength(float hitLength) {
+ this.hitLength = hitLength;
+ }
+
+ public float getHitWidth() {
+ return hitWidth;
+ }
+
+ public void setHitWidth(float hitWidth) {
+ this.hitWidth = hitWidth;
+ }
+
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.RIGHT),
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
+ ),
+ FieldDef.section("ability.section.hitZone"),
+ FieldDef.row(
+ FieldDef.floatField("ability.hitLength", this::getHitLength, this::setHitLength),
+ FieldDef.floatField("ability.hitWidth", this::getHitWidth, this::setHitWidth)
+ ),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java
index 39377a19d..031598207 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java
@@ -7,7 +7,6 @@ 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.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -248,38 +247,49 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockbackUp", this::getKnockbackUp, this::setKnockbackUp).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
+ FieldDef.floatField("ability.knockbackUp", this::getKnockbackUp, this::setKnockbackUp)
+ ),
FieldDef.section("ability.section.beam"),
- FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed).column(ColumnHint.RIGHT),
- FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth),
+ FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed)
+ ),
+ FieldDef.row(
+ FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks),
+ FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
+ ),
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
FieldDef.section("ability.section.explosive"),
FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"),
FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects),
+
// Visual tab
FieldDef.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.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
- .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
- .tab("ability.tab.visual").range(0, 1).visibleWhen(this::isOuterColorEnabled).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
+ .visibleWhen(this::isOuterColorEnabled),
+ FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
+ .range(0, 1).visibleWhen(this::isOuterColorEnabled)
+ ).tab("ability.tab.visual"),
FieldDef.section("ability.section.effects").tab("ability.tab.visual"),
FieldDef.boolField("ability.lightning", this::hasLightningEffect, this::setLightningEffect).tab("ability.tab.visual"),
- FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.RIGHT)
- );
+ 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")
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java
index e8e48a732..b45bf87af 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java
@@ -17,7 +17,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityOrb;
@@ -413,15 +412,21 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed).column(ColumnHint.RIGHT),
- FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ 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),
@@ -439,17 +444,21 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
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.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
- .tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha", this::getOuterColorAlpha, this::setOuterColorAlpha)
- .tab("ability.tab.visual").range(0, 1).visibleWhen(this::isOuterColorEnabled).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getLightningRadius, this::setLightningRadius)
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(this::hasLightningEffect).column(ColumnHint.RIGHT)
- );
+ 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")
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbBarrage.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbBarrage.java
new file mode 100644
index 000000000..79641ae41
--- /dev/null
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbBarrage.java
@@ -0,0 +1,603 @@
+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 EnergyColorData colorData = new EnergyColorData();
+ 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)
+ };
+
+ // Array to store entities
+ private transient List 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) {
+ 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) return;
+
+ // Spawn orb in charging mode on first tick of windup
+ if (tick == 1) {
+ // Create orb in charging mode - follows caster based on anchor point during windup
+
+ 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
+ );
+
+ orbEntity.setupCharging(anchorData[i], windUpTicks);
+
+ chargingEntities[i] = orbEntity;
+ world.spawnEntityInWorld(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
+ );
+
+ 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 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);
+
+ 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);
+
+ 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
+ @SideOnly(Side.CLIENT)
+ public Entity createPreviewEntity(EntityNPCInterface npc) {
+ if (npc == null || npc.worldObj == null) return null;
+
+ EntityAbilityOrb orb = new EntityAbilityOrb(npc.worldObj);
+ orb.setupPreview(npc, orbSize, colorData, lightningData, anchorData[0], windUpTicks);
+ return orb;
+ }
+
+ @Override
+ public int getPreviewActiveDuration() {
+ return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100;
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public void getAbilityDefinitions(List 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.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")
+ ));
+ }
+}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbDual.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbDual.java
index 658d4092f..52b5355ea 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbDual.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrbDual.java
@@ -7,7 +7,6 @@ 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.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
@@ -458,15 +457,21 @@ public class AbilityOrbDual extends Ability {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
// Type tab
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed).column(ColumnHint.RIGHT),
- FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.RIGHT),
- FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).column(ColumnHint.LEFT),
- FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).column(ColumnHint.RIGHT),
+ 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)
@@ -492,24 +497,28 @@ public class AbilityOrbDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(0), v -> setOuterColor(0, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(0), v -> setOuterColorWidth(0, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(0), v -> setOuterColorAlpha(0, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(0)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(0), v -> setLightningDensity(0, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(0), v -> setLightningRadius(0, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(0)).column(ColumnHint.RIGHT),
+ 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.enumField("ability.anchorPoint", AnchorPoint.class,
() -> getAnchorPointEnum(1), v -> setAnchorPointEnum(1, v))
@@ -524,25 +533,29 @@ public class AbilityOrbDual extends Ability {
FieldDef.colorSubGui("ability.outerColor",
() -> getOuterColor(1), v -> setOuterColor(1, v))
.tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)),
- FieldDef.floatField("ability.outerWidth",
- () -> getOuterColorWidth(1), v -> setOuterColorWidth(1, v))
- .tab("ability.tab.visual").visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.outerAlpha",
- () -> getOuterColorAlpha(1), v -> setOuterColorAlpha(1, v))
- .tab("ability.tab.visual").range(0, 1).visibleWhen(() -> isOuterColorEnabled(1)).column(ColumnHint.RIGHT),
+ 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.floatField("gui.density",
- () -> getLightningDensity(1), v -> setLightningDensity(1, v))
- .tab("ability.tab.visual").range(0.01f, 5.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius",
- () -> getLightningRadius(1), v -> setLightningRadius(1, v))
- .tab("ability.tab.visual").range(0.1f, 10.0f).visibleWhen(() -> hasLightningEffect(1)).column(ColumnHint.RIGHT)
- );
+ 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
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java
index 579887826..e5f6f5df6 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java
@@ -14,7 +14,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityProjectile;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -252,10 +251,12 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("stats.speed", this::getSpeed, this::setSpeed).column(ColumnHint.RIGHT),
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
+ 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.stringEnumField("ability.projectileType", new String[]{"fireball", "arrow", "magic"}, this::getProjectileType, this::setProjectileType),
FieldDef.section("ability.section.homing"),
@@ -265,6 +266,6 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
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)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java
index 626cc6fc5..775c15515 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java
@@ -13,7 +13,6 @@ import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityShockwave;
@@ -41,8 +40,10 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
this.cooldownTicks = 0;
this.windUpTicks = 25;
this.telegraphType = TelegraphType.CIRCLE;
- this.windUpSound = "random.explode";
+ this.windUpSound = "game.tnt.primed";
this.activeSound = "random.explode";
+ this.windUpAnimationName = "Ability_Shockwave_Windup";
+ this.activeAnimationName = "Ability_Shockwave_Active";
}
@Override
@@ -106,14 +107,12 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
applyEffects(entity);
}
}
-
- // Shockwave completes instantly
- signalCompletion();
}
@Override
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
- // Shockwave is instant - nothing to do per-tick
+ if (tick == 10)
+ signalCompletion();
}
@Override
@@ -167,14 +166,16 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
FieldDef.section("ability.section.push"),
- FieldDef.floatField("gui.radius", this::getPushRadius, this::setPushRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.strength", this::getPushStrength, this::setPushStrength).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getPushRadius, this::setPushRadius),
+ FieldDef.floatField("gui.strength", this::getPushStrength, this::setPushStrength)
+ ),
FieldDef.intField("ability.maxTargets", this::getMaxTargets, this::setMaxTargets),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java
index 7b4310030..30b57bf6d 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java
@@ -18,7 +18,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilitySlam;
import somehussar.gui.annotationHandling.GuiEditable;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import java.util.Arrays;
@@ -131,9 +130,46 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
launchTowardTarget(caster);
}
+ /**
+ * Calculate the initial vertical velocity needed to reach a target height
+ * in Minecraft's physics (gravity=0.08, vertical drag=0.98).
+ * Uses iterative simulation since the drag makes a closed-form solution impractical.
+ */
+ private static double calculateLaunchVelocity(double targetHeight) {
+ // Binary search for the initial velocity that reaches targetHeight
+ double lo = 0.0, hi = 10.0;
+ for (int iter = 0; iter < 30; iter++) {
+ double mid = (lo + hi) / 2.0;
+ double maxY = simulateMaxHeight(mid);
+ if (maxY < targetHeight) {
+ lo = mid;
+ } else {
+ hi = mid;
+ }
+ }
+ return (lo + hi) / 2.0;
+ }
+
+ /**
+ * Simulate Minecraft vertical physics to find peak height reached
+ * with a given initial upward velocity.
+ */
+ private static double simulateMaxHeight(double vy) {
+ double y = 0;
+ double maxY = 0;
+ for (int t = 0; t < 200 && vy > 0; t++) {
+ y += vy;
+ vy -= 0.08; // gravity
+ vy *= 0.98; // vertical drag
+ if (y > maxY) maxY = y;
+ }
+ return maxY;
+ }
+
/**
* Calculate ballistic arc and launch caster toward target.
- * Accounts for Minecraft's drag physics to actually reach the destination.
+ * leapHeight = exact peak height in blocks above launch point.
+ * leapSpeed = velocity multiplier (faster motion, same height).
*/
private void launchTowardTarget(EntityLivingBase caster) {
double dx = targetX - caster.posX;
@@ -141,11 +177,15 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
double dz = targetZ - caster.posZ;
double horizontalDist = Math.sqrt(dx * dx + dz * dz);
+ // Calculate vertical velocity to reach exact leapHeight
+ double arcHeight = Math.max(1.0, leapHeight);
+ double vy = calculateLaunchVelocity(arcHeight);
+
// For AOE_SELF or very close targets, just hop in place
if (horizontalDist < 0.5) {
caster.motionX = 0;
caster.motionZ = 0;
- caster.motionY = 0.8 * leapSpeed;
+ caster.motionY = vy;
hasLaunched = true;
if (caster instanceof EntityNPCInterface) {
((EntityNPCInterface) caster).setNpcJumpingState(true);
@@ -165,8 +205,9 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
targetZ = caster.posZ + dz;
}
- // Choose flight time based on distance - shorter distances = faster
- int flightTicks = (int) Math.max(15, Math.min(horizontalDist * 1.5, 30));
+ // Choose flight time based on distance, scaled by speed (faster = fewer ticks)
+ double speedFactor = Math.max(0.1, leapSpeed);
+ int flightTicks = (int) Math.max(10, Math.min(horizontalDist * 1.5 / speedFactor, 40));
// Minecraft applies drag of 0.91 per tick to horizontal motion in air
// Total distance traveled with initial velocity v0 over n ticks:
@@ -176,34 +217,16 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
double dragPowN = Math.pow(drag, flightTicks);
double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN);
- // Scale by leap speed
- vHorizontal *= leapSpeed;
+ // Speed scales horizontal velocity to cover ground faster
+ vHorizontal *= speedFactor;
- // Vertical velocity calculation:
- // Minecraft gravity is 0.08 per tick, with 0.98 drag on vertical motion
- // For a nice arc, we want to peak roughly in the middle of the flight
- double gravity = 0.08;
- double verticalDrag = 0.98;
-
- // Calculate required upward velocity to create a nice arc and land at target height
- // Peak height should be proportional to horizontal distance for visual appeal
- double arcHeight = Math.max(1.0, leapHeight) * leapSpeed;
-
- // Approximate the required initial vertical velocity
- // Account for gravity and drag - use a simplified model
- double peakTicks = flightTicks * 0.4; // Peak around 40% through flight
- double vy = (arcHeight * 2.0 / peakTicks) + (gravity * peakTicks * 0.5);
-
- // Adjust for height difference
+ // Adjust vertical velocity for height difference between start and target
if (dy > 0) {
vy += dy / flightTicks * 1.5; // Going up - need more velocity
} else if (dy < 0) {
vy += dy / flightTicks * 0.3; // Going down - gravity helps
}
- // Ensure minimum upward velocity for visible jump
- vy = Math.max(vy, 0.6 * leapSpeed);
-
// Normalize horizontal direction and apply velocity
double dirX = dx / horizontalDist;
double dirZ = dz / horizontalDist;
@@ -222,7 +245,6 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0D / Math.PI);
caster.rotationYaw = targetYaw;
caster.rotationYawHead = targetYaw;
-
}
@Override
@@ -396,10 +418,14 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
double dz = tz - npc.posZ;
double horizontalDist = Math.sqrt(dx * dx + dz * dz);
+ // Calculate vertical velocity for exact leapHeight
+ double arcHeight = Math.max(1.0, leapHeight);
+ double vy = calculateLaunchVelocity(arcHeight);
+
if (horizontalDist < 0.5) {
previewVelX = 0;
previewVelZ = 0;
- previewVelY = 0.8 * leapSpeed;
+ previewVelY = vy;
} else {
if (horizontalDist > maxRange) {
double scale = maxRange / horizontalDist;
@@ -408,15 +434,11 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
horizontalDist = maxRange;
}
- int flightTicks = (int) Math.max(15, Math.min(horizontalDist * 1.5, 30));
+ double speedFactor = Math.max(0.1, leapSpeed);
+ int flightTicks = (int) Math.max(10, Math.min(horizontalDist * 1.5 / speedFactor, 40));
double drag = 0.91;
double dragPowN = Math.pow(drag, flightTicks);
- double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN) * leapSpeed;
-
- double arcHeight = Math.max(1.0, leapHeight) * leapSpeed;
- double peakTicks = flightTicks * 0.4;
- double vy = (arcHeight * 2.0 / peakTicks) + (0.08 * peakTicks * 0.5);
- vy = Math.max(vy, 0.6 * leapSpeed);
+ double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN) * speedFactor;
double dirX = dx / horizontalDist;
double dirZ = dz / horizontalDist;
@@ -575,15 +597,19 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
- FieldDef.floatField("gui.radius", this::getRadius, this::setRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockback", this::getKnockbackStrength, this::setKnockbackStrength).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getRadius, this::setRadius),
+ FieldDef.floatField("ability.knockback", this::getKnockbackStrength, this::setKnockbackStrength)
+ ),
FieldDef.section("ability.section.leap"),
- FieldDef.floatField("gui.speed", this::getLeapSpeed, this::setLeapSpeed).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.height", this::getLeapHeight, this::setLeapHeight).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.speed", this::getLeapSpeed, this::setLeapSpeed),
+ FieldDef.floatField("gui.height", this::getLeapHeight, this::setLeapHeight)
+ ),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java
index aad2c47c2..1ef4f7df4 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java
@@ -6,7 +6,6 @@ import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.data.EnergyColorData;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
@@ -200,21 +199,29 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.intField("ability.damageInterval", this::getDamageInterval, this::setDamageInterval)
+ ),
FieldDef.section("ability.section.beam"),
- FieldDef.floatField("gui.length", this::getBeamLength, this::setBeamLength).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.width", this::getBeamWidth, this::setBeamWidth).column(ColumnHint.RIGHT),
- FieldDef.floatField("gui.height", this::getBeamHeight, this::setBeamHeight).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.sweepSpeed", this::getSweepSpeed, this::setSweepSpeed).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.length", this::getBeamLength, this::setBeamLength),
+ FieldDef.floatField("gui.width", this::getBeamWidth, this::setBeamWidth)
+ ),
+ FieldDef.row(
+ FieldDef.floatField("gui.height", this::getBeamHeight, this::setBeamHeight),
+ FieldDef.floatField("ability.sweepSpeed", this::getSweepSpeed, this::setSweepSpeed)
+ ),
FieldDef.intField("ability.rotations", this::getNumberOfRotations, this::setNumberOfRotations),
- FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing)
- .hover("ability.hover.piercing").column(ColumnHint.LEFT),
- FieldDef.boolField("ability.lockTarget", this::isLockOnTarget, this::setLockOnTarget)
- .hover("ability.hover.lockTarget").column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing)
+ .hover("ability.hover.piercing"),
+ FieldDef.boolField("ability.lockTarget", this::isLockOnTarget, this::setLockOnTarget)
+ .hover("ability.hover.lockTarget")
+ ),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects),
// Visual tab
@@ -227,6 +234,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled),
FieldDef.floatField("ability.outerWidth", this::getOuterColorWidth, this::setOuterColorWidth)
.tab("ability.tab.visual").visibleWhen(this::isOuterColorEnabled)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java
index 414586b80..887ef25fc 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java
@@ -16,7 +16,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityTeleport;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -559,28 +558,34 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.enumField("ability.mode", TeleportMode.class, this::getModeEnum, this::setModeEnum),
FieldDef.floatField("ability.blinkRadius", this::getBlinkRadius, this::setBlinkRadius)
.visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK || this.getModeEnum() == TeleportMode.SINGLE),
- FieldDef.intField("ability.blinkCount", this::getBlinkCount, this::setBlinkCount)
- .visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK).column(ColumnHint.LEFT),
- FieldDef.intField("ability.blinkDelay", this::getBlinkDelayTicks, this::setBlinkDelayTicks)
- .visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.intField("ability.blinkCount", this::getBlinkCount, this::setBlinkCount)
+ .visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK),
+ FieldDef.intField("ability.blinkDelay", this::getBlinkDelayTicks, this::setBlinkDelayTicks)
+ .visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK)
+ ),
FieldDef.floatField("ability.behindDistance", this::getBehindDistance, this::setBehindDistance)
.visibleWhen(() -> this.getModeEnum() == TeleportMode.BEHIND),
FieldDef.boolField("ability.lineOfSight", this::isRequireLineOfSight, this::setRequireLineOfSight)
.hover("ability.hover.lineOfSight")
.visibleWhen(() -> this.getModeEnum() == TeleportMode.BLINK || this.getModeEnum() == TeleportMode.SINGLE),
FieldDef.section("ability.section.damage"),
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getDamageRadius, this::setDamageRadius).column(ColumnHint.RIGHT),
- FieldDef.boolField("ability.damageAtStart", this::isDamageAtStart, this::setDamageAtStart)
- .hover("ability.hover.dmgAtStart").column(ColumnHint.LEFT),
- FieldDef.boolField("ability.damageAtEnd", this::isDamageAtEnd, this::setDamageAtEnd)
- .hover("ability.hover.dmgAtEnd").column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("gui.radius", this::getDamageRadius, this::setDamageRadius)
+ ),
+ FieldDef.row(
+ FieldDef.boolField("ability.damageAtStart", this::isDamageAtStart, this::setDamageAtStart)
+ .hover("ability.hover.dmgAtStart"),
+ FieldDef.boolField("ability.damageAtEnd", this::isDamageAtEnd, this::setDamageAtEnd)
+ .hover("ability.hover.dmgAtEnd")
+ ),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java
index 65673f861..102c4395f 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java
@@ -14,7 +14,6 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityTrap;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
@@ -469,22 +468,28 @@ public class AbilityTrap extends Ability implements IAbilityTrap {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
FieldDef.intField("ability.duration", this::getDurationTicks, this::setDurationTicks)
.range(1, 2000),
FieldDef.enumField("ability.placement", TrapPlacement.class, this::getPlacementEnum, this::setPlacementEnum)
.hover("ability.hover.placement"),
FieldDef.section("ability.section.trigger"),
- FieldDef.floatField("gui.radius", this::getTriggerRadius, this::setTriggerRadius).column(ColumnHint.LEFT),
- FieldDef.intField("ability.armTime", this::getArmTime, this::setArmTime).column(ColumnHint.RIGHT),
- FieldDef.intField("ability.maxTriggers", this::getMaxTriggers, this::setMaxTriggers).column(ColumnHint.LEFT),
- FieldDef.intField("ability.triggerCooldown", this::getTriggerCooldown, this::setTriggerCooldown).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("gui.radius", this::getTriggerRadius, this::setTriggerRadius),
+ FieldDef.intField("ability.armTime", this::getArmTime, this::setArmTime)
+ ),
+ FieldDef.row(
+ FieldDef.intField("ability.maxTriggers", this::getMaxTriggers, this::setMaxTriggers),
+ FieldDef.intField("ability.triggerCooldown", this::getTriggerCooldown, this::setTriggerCooldown)
+ ),
FieldDef.section("ability.section.damage"),
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("gui.radius", this::getDamageRadius, this::setDamageRadius).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("gui.radius", this::getDamageRadius, this::setDamageRadius)
+ ),
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java
index 0df622235..e31a1e6ce 100644
--- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java
+++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java
@@ -10,7 +10,6 @@ import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import noppes.npcs.entity.EntityNPCInterface;
-import noppes.npcs.client.gui.builder.ColumnHint;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.api.ability.type.IAbilityVortex;
@@ -139,7 +138,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
double dz = destZ - entity.posZ;
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
- if (dist <= 1.0f) {
+ if (dist <= 1.5f) {
getPulledEntities().remove(uuid);
onTargetArrived(caster, entity, world);
continue;
@@ -147,7 +146,10 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
anyStillPulling = true;
- double factor = pullStrength / dist;
+ // Clamp speed to never exceed half the remaining distance, preventing overshoot/slingshot
+ double maxSpeed = dist * 0.5;
+ double effectiveSpeed = Math.min(pullStrength, maxSpeed);
+ double factor = effectiveSpeed / dist;
double nextX = dx * factor;
double nextY = dy * factor * 0.5;
double nextZ = dz * factor;
@@ -302,13 +304,17 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
@SideOnly(Side.CLIENT)
@Override
- public List getFieldDefinitions() {
- return Arrays.asList(
- FieldDef.floatField("ability.pullRadius", this::getPullRadius, this::setPullRadius).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.pullStrength", this::getPullStrength, this::setPullStrength).column(ColumnHint.RIGHT),
+ public void getAbilityDefinitions(List defs) {
+ defs.addAll(Arrays.asList(
+ FieldDef.row(
+ FieldDef.floatField("ability.pullRadius", this::getPullRadius, this::setPullRadius),
+ FieldDef.floatField("ability.pullStrength", this::getPullStrength, this::setPullStrength)
+ ),
FieldDef.section("ability.section.damage"),
- FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage).column(ColumnHint.LEFT),
- FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback).column(ColumnHint.RIGHT),
+ FieldDef.row(
+ FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
+ FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
+ ),
FieldDef.section("ability.section.aoe"),
FieldDef.boolField("gui.enabled", this::isAoe, this::setAoe)
.hover("ability.hover.aoe"),
@@ -320,6 +326,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
FieldDef.floatField("enchantment.damage", this::getPullDamage, this::setPullDamage)
.visibleWhen(this::isDamageOnPull),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects)
- );
+ ));
}
}
diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java
index 57f99e1c8..10dd0d953 100644
--- a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java
+++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java
@@ -192,6 +192,19 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
// Skip lifetime/distance checks in preview mode
if (!previewMode) {
+ // Failsafe: if owner entity is gone, dead, or NPC was killed/reset, self-destruct
+ if (ownerEntityId >= 0 && ticksExisted > 5) {
+ Entity owner = worldObj.getEntityByID(ownerEntityId);
+ if (owner == null || owner.isDead) {
+ this.setDead();
+ return;
+ }
+ if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
+ this.setDead();
+ return;
+ }
+ }
+
// Set death time on first tick if not already set (handles chunk load/unload)
if (deathWorldTime < 0 && worldObj != null) {
deathWorldTime = worldObj.getTotalWorldTime() + maxLifetime;
@@ -392,6 +405,10 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA
// ==================== ENTITY HELPERS ====================
+ public int getOwnerEntityId() {
+ return ownerEntityId;
+ }
+
protected Entity getOwner() {
// In preview mode, use direct reference (no world lookup)
if (previewMode && previewOwner != null) {
diff --git a/src/main/java/kamkeel/npcs/util/BukkitUtil.java b/src/main/java/kamkeel/npcs/util/BukkitUtil.java
index 78cd49c82..29f4a522f 100644
--- a/src/main/java/kamkeel/npcs/util/BukkitUtil.java
+++ b/src/main/java/kamkeel/npcs/util/BukkitUtil.java
@@ -33,6 +33,8 @@ public class BukkitUtil {
private static Method getPluginManager;
private static Method isPluginEnabled;
+ private static Method getPlugin;
+
/**
* Initializes Bukkit integration using reflection.
* Should be called during FMLServerStartedEvent to ensure Bukkit is fully loaded.
@@ -63,6 +65,7 @@ public class BukkitUtil {
Class> pluginManagerClass = Class.forName("org.bukkit.plugin.PluginManager");
isPluginEnabled = pluginManagerClass.getMethod("isPluginEnabled", String.class);
+ getPlugin = pluginManagerClass.getMethod("getPlugin", String.class);
bukkitEnabled = true;
logger.info("Bukkit integration enabled");
@@ -182,4 +185,13 @@ public class BukkitUtil {
public static Class> getPlayerClass() {
return playerClass;
}
+
+ public static Object getPlugin(String plugin) {
+ try {
+ return getPlugin.invoke(getPluginManager.invoke(null), plugin);
+ } catch (Throwable ignored) {
+
+ }
+ return null;
+ }
}
diff --git a/src/main/java/kamkeel/npcs/util/VaultUtil.java b/src/main/java/kamkeel/npcs/util/VaultUtil.java
index c7ef92e97..c25ea3f3a 100644
--- a/src/main/java/kamkeel/npcs/util/VaultUtil.java
+++ b/src/main/java/kamkeel/npcs/util/VaultUtil.java
@@ -75,8 +75,12 @@ public class VaultUtil {
}
// Load Vault classes
- economyClass = Class.forName("net.milkbowl.vault.economy.Economy");
- economyResponseClass = Class.forName("net.milkbowl.vault.economy.EconomyResponse");
+ Object vault = BukkitUtil.getPlugin("Vault");
+ if (vault == null)
+ return;
+ ClassLoader vaultLoader = vault.getClass().getClassLoader();
+ economyClass = vaultLoader.loadClass("net.milkbowl.vault.economy.Economy");
+ economyResponseClass = vaultLoader.loadClass("net.milkbowl.vault.economy.EconomyResponse");
// Get Economy methods (using deprecated String methods for broader compatibility)
isEnabled = economyClass.getMethod("isEnabled");
diff --git a/src/main/java/noppes/npcs/CustomNpcsPermissions.java b/src/main/java/noppes/npcs/CustomNpcsPermissions.java
index a8ee213ae..af9b2b9af 100644
--- a/src/main/java/noppes/npcs/CustomNpcsPermissions.java
+++ b/src/main/java/noppes/npcs/CustomNpcsPermissions.java
@@ -7,7 +7,6 @@ import kamkeel.npcs.util.BukkitUtil;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import org.apache.logging.log4j.LogManager;
-//import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -67,8 +66,8 @@ public class CustomNpcsPermissions {
public static final Permission GLOBAL_NATURALSPAWN = new Permission("customnpcs.global.naturalspawn");
public static final Permission GLOBAL_TAG = new Permission("customnpcs.global.tag");
public static final Permission GLOBAL_ANIMATION = new Permission("customnpcs.global.animation");
- public static final Permission GLOBAL_ABILITY = new Permission("customnpcs.global.ability");
public static final Permission GLOBAL_MAGIC = new Permission("customnpcs.global.magic");
+ public static final Permission GLOBAL_ABILITY = new Permission("customnpcs.global.ability");
public static final Permission GLOBAL_EFFECT = new Permission("customnpcs.global.effect");
public static final Permission SPAWNER_MOB = new Permission("customnpcs.spawner.mob");
@@ -112,11 +111,8 @@ public class CustomNpcsPermissions {
public static final Permission PROFILE_REGION_BYPASS = new Permission("customnpcs.profile.region-bypass");
public static final Permission PROFILE_MAX = new Permission("customnpcs.profile.max.*");
- // Auction
- public static final Permission AUCTION_TRADES_MAX = new Permission("customnpcs.auction.trades.*");
-
public static CustomNpcsPermissions Instance;
- private static boolean isEnabled;
+ private Method hasPermission;
public CustomNpcsPermissions() {
Instance = this;
@@ -132,16 +128,16 @@ public class CustomNpcsPermissions {
}
try {
- Class.forName("org.bukkit.entity.Player").getMethod("hasPermission", String.class);
- isEnabled = true;
-
+ hasPermission = BukkitUtil.getPlayerClass().getMethod("hasPermission", String.class);
LogManager.getLogger(CustomNpcs.class).info("Bukkit permissions enabled");
LogManager.getLogger(CustomNpcs.class).info("Permissions available:");
Collections.sort(Permission.permissions, String.CASE_INSENSITIVE_ORDER);
for (String p : Permission.permissions) {
LogManager.getLogger(CustomNpcs.class).info(p);
}
- } catch (Exception e) {
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
e.printStackTrace();
}
}
@@ -175,13 +171,17 @@ public class CustomNpcsPermissions {
}
private boolean bukkitPermission(String username, String permission) {
- if (!BukkitUtil.isEnabled() || !isEnabled) return false;
+ if (hasPermission == null) return false;
try {
Object player = BukkitUtil.getPlayer(username);
if (player == null) return false;
- return false;//((Player) player).hasPermission(permission);
- } catch (Exception e) {
+ return (Boolean) hasPermission.invoke(player, permission);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
e.printStackTrace();
}
return false;
@@ -206,6 +206,6 @@ public class CustomNpcsPermissions {
}
public static boolean enabled() {
- return BukkitUtil.isEnabled() && isEnabled;
+ return BukkitUtil.isEnabled();
}
}
diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java
index 8c44c482e..635b741ac 100644
--- a/src/main/java/noppes/npcs/DataAbilities.java
+++ b/src/main/java/noppes/npcs/DataAbilities.java
@@ -429,6 +429,14 @@ public class DataAbilities {
playAbilityAnimation((Animation) AnimationController.Instance.get(animation));
}
+ public void playAbilityAnimation(String animation) {
+ if (animation.isEmpty()) return;
+ if (AnimationController.Instance == null) return;
+ if (AnimationController.Instance.get(animation, true) == null) return;
+
+ playAbilityAnimation((Animation) AnimationController.Instance.get(animation, true));
+ }
+
/**
* Stop any currently playing ability animation.
*/
@@ -501,18 +509,12 @@ public class DataAbilities {
// Track hit for hit count condition
recordHit();
- // Check if currently executing a Guard ability and notify it of damage taken
- if (currentAbility instanceof AbilityGuard) {
- AbilityGuard guard = (AbilityGuard) currentAbility;
- if (guard.isGuarding() && source.getEntity() instanceof EntityLivingBase) {
- guard.onDamageTaken(npc, (EntityLivingBase) source.getEntity(), source, amount);
- }
- }
-
if (currentAbility == null || !currentAbility.isExecuting()) {
return false;
}
+ currentAbility.onDamageTaken(npc, (EntityLivingBase) source.getEntity(), source, amount);
+
if (currentAbility.canInterrupt(source)) {
interruptCurrentAbility(source, amount);
return true;
diff --git a/src/main/java/noppes/npcs/ai/CombatHandler.java b/src/main/java/noppes/npcs/ai/CombatHandler.java
index 750725551..ff81e4821 100644
--- a/src/main/java/noppes/npcs/ai/CombatHandler.java
+++ b/src/main/java/noppes/npcs/ai/CombatHandler.java
@@ -1,6 +1,7 @@
package noppes.npcs.ai;
import kamkeel.npcs.addon.DBCAddon;
+import kamkeel.npcs.controllers.data.ability.type.AbilityGuard;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -80,13 +81,14 @@ public class CombatHandler {
}
// Handle guard counter before interruption
- if (npc.abilities.getCurrentAbility() instanceof kamkeel.npcs.controllers.data.ability.type.AbilityGuard) {
- kamkeel.npcs.controllers.data.ability.type.AbilityGuard guard =
- (kamkeel.npcs.controllers.data.ability.type.AbilityGuard) npc.abilities.getCurrentAbility();
- if (guard.isGuarding() && e instanceof EntityLivingBase) {
- guard.onDamageTaken(npc, (EntityLivingBase) e, source, damageAmount);
- }
- }
+
+// if (npc.abilities.getCurrentAbility() instanceof AbilityGuard) {
+// AbilityGuard guard =
+// (AbilityGuard) npc.abilities.getCurrentAbility();
+// if (guard.isGuarding() && e instanceof EntityLivingBase) {
+// guard.onDamageTaken(npc, (EntityLivingBase) e, source, damageAmount);
+// }
+// }
// Check for ability interruption
npc.abilities.onDamage(source, damageAmount);
diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java
index 6954b36f1..d2e7d5dc4 100644
--- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java
+++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java
@@ -1,11 +1,9 @@
package noppes.npcs.client.gui.advanced;
import kamkeel.npcs.controllers.data.ability.Ability;
-import kamkeel.npcs.controllers.data.ability.AbilityEffect;
import kamkeel.npcs.controllers.data.ability.Condition;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldBuilder;
import noppes.npcs.client.gui.builder.FieldDef;
-import noppes.npcs.client.gui.builder.FieldType;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.StatCollector;
@@ -55,7 +53,6 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
private int editingConditionIndex = -1;
private AbilityFieldBuilder builder;
- private FieldDef activeSubGuiField = null;
// Scroll position preservation per tab
private float[] tabScrollY;
@@ -65,7 +62,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
this.callback = callback;
this.conditions = new ArrayList<>(ability.getConditions());
- this.fieldDefs = ability.getAllFieldDefinitions();
+ this.fieldDefs = ability.getAllDefinitions();
discoverCustomTabs();
setBackground("menubg.png", 217);
@@ -263,7 +260,6 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
// ═══════════════════════════════════════════════════════════════════════════
@Override
- @SuppressWarnings({"unchecked", "rawtypes"})
public void buttonEvent(GuiButton guibutton) {
int id = guibutton.id;
@@ -281,6 +277,20 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
if (id == -1000) { close(); return; }
// Condition buttons (50-80)
+ if (handleConditionButton(id)) return;
+
+ // Declarative field handling (effects list, booleans, enums, sub-guis, clear buttons)
+ if (builder.handleButtonEvent(id, guibutton)) {
+ // SUB_GUI fields are opened directly by the builder via setSubGuiWithResult().
+ // Only rebuild if no sub-gui was opened (e.g. boolean toggle, enum change).
+ if (!hasSubGui()) {
+ initGui();
+ }
+ return;
+ }
+ }
+
+ private boolean handleConditionButton(int id) {
if (id >= 50 && id < 80) {
int condIndex = (id - 50) / 10;
int action = (id - 50) % 10;
@@ -295,109 +305,16 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
initGui();
}
}
- return;
+ return true;
}
if (id == 80) {
if (conditions.size() < 3) {
editingConditionIndex = conditions.size();
setSubGui(new SubGuiConditionEdit(null));
}
- return;
- }
-
- // Clear buttons (effects delete or SUB_GUI clear)
- if (id >= CLEAR_ID_START) {
- // Check effects list delete first
- int[] meta = builder.getEffectWidgetMeta().get(id);
- if (meta != null && meta[1] == 3) {
- FieldDef def = builder.getClearFieldMap().get(id);
- if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
- handleEffectButton(def, meta, guibutton);
- return;
- }
- }
- // SUB_GUI clear
- FieldDef def = builder.getClearFieldMap().get(id);
- if (def != null && def.hasClearAction()) {
- def.getClearAction().run();
- initGui();
- }
- return;
- }
-
- // Declarative field buttons
- if (id >= DECLARATIVE_ID_START) {
- FieldDef def = builder.getButtonFieldMap().get(id);
- if (def == null) return;
-
- // Effects list buttons (type, amp, add)
- int[] meta = builder.getEffectWidgetMeta().get(id);
- if (meta != null && def.getType() == FieldType.EFFECTS_LIST) {
- handleEffectButton(def, meta, guibutton);
- return;
- }
-
- switch (def.getType()) {
- case BOOLEAN:
- def.setValue(((GuiNpcButton) guibutton).getValue() == 1);
- initGui();
- break;
- case ENUM: {
- int idx = ((GuiNpcButton) guibutton).getValue();
- Class extends Enum>> ec = def.getEnumClass();
- if (ec != null) {
- Enum>[] constants = ec.getEnumConstants();
- if (idx >= 0 && idx < constants.length) def.setValue(constants[idx]);
- }
- initGui();
- break;
- }
- case STRING_ENUM: {
- int idx = ((GuiNpcButton) guibutton).getValue();
- String[] values = def.getStringEnumValues();
- if (values != null && idx >= 0 && idx < values.length) def.setStringEnumValue(values[idx]);
- initGui();
- break;
- }
- case SUB_GUI:
- activeSubGuiField = def;
- if (def.getSubGuiFactory() != null) setSubGui(def.getSubGuiFactory().get());
- break;
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private void handleEffectButton(FieldDef def, int[] meta, GuiButton btn) {
- List effects = (List) def.getValue();
- if (effects == null) return;
- int effectIdx = meta[0];
- int action = meta[1];
-
- switch (action) {
- case 0: // Type changed
- if (effectIdx < effects.size()) {
- effects.get(effectIdx).setType(AbilityEffect.EffectType.fromOrdinal(((GuiNpcButton) btn).getValue()));
- }
- break;
- case 2: // Amp changed
- if (effectIdx < effects.size()) {
- effects.get(effectIdx).setAmplifier(((GuiNpcButton) btn).getValue());
- }
- break;
- case 3: // Delete
- if (effectIdx < effects.size()) {
- effects.remove(effectIdx);
- initGui();
- }
- break;
- case 4: // Add
- if (effects.size() < 5) {
- effects.add(new AbilityEffect(AbilityEffect.EffectType.SLOWNESS, 60, 0));
- initGui();
- }
- break;
+ return true;
}
+ return false;
}
// ═══════════════════════════════════════════════════════════════════════════
@@ -405,38 +322,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
// ═══════════════════════════════════════════════════════════════════════════
@Override
- @SuppressWarnings("unchecked")
public void unFocused(GuiNpcTextField textField) {
- int id = textField.id;
- if (id < DECLARATIVE_ID_START) return;
-
- // Check effects list duration fields
- int[] meta = builder.getEffectWidgetMeta().get(id);
- if (meta != null && meta[1] == 1) {
- FieldDef def = builder.getTextFieldMap().get(id);
- if (def != null && def.getType() == FieldType.EFFECTS_LIST) {
- List effects = (List) def.getValue();
- if (effects != null && meta[0] < effects.size()) {
- effects.get(meta[0]).setDurationTicks(textField.getInteger());
- }
- return;
- }
- }
-
- FieldDef def = builder.getTextFieldMap().get(id);
- if (def == null) return;
-
- switch (def.getType()) {
- case FLOAT:
- try { def.setValue(Float.parseFloat(textField.getText())); } catch (NumberFormatException ignored) {}
- break;
- case INT:
- try { def.setValue(Integer.parseInt(textField.getText())); } catch (NumberFormatException ignored) {}
- break;
- case STRING:
- def.setValue(textField.getText());
- break;
- }
+ if (textField.id < DECLARATIVE_ID_START) return;
+ builder.handleTextFieldEvent(textField.id, textField);
}
// ═══════════════════════════════════════════════════════════════════════════
@@ -445,12 +333,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
@Override
public void subGuiClosed(SubGuiInterface subgui) {
- if (activeSubGuiField != null) {
- FieldDef def = activeSubGuiField;
- activeSubGuiField = null;
- if (def.getSubGuiResultHandler() != null) {
- def.getSubGuiResultHandler().accept(subgui);
- }
+ if (builder.handleSubGuiClosed(subgui)) {
initGui();
return;
}
@@ -493,7 +376,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
ability.readNBT(nbt);
this.conditions = new ArrayList<>(ability.getConditions());
- this.fieldDefs = ability.getAllFieldDefinitions();
+ this.fieldDefs = ability.getAllDefinitions();
discoverCustomTabs();
initGui();
}
diff --git a/src/main/java/noppes/npcs/client/gui/builder/ColumnHint.java b/src/main/java/noppes/npcs/client/gui/builder/ColumnHint.java
deleted file mode 100644
index c076b6e6d..000000000
--- a/src/main/java/noppes/npcs/client/gui/builder/ColumnHint.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package noppes.npcs.client.gui.builder;
-
-import cpw.mods.fml.relauncher.Side;
-import cpw.mods.fml.relauncher.SideOnly;
-
-@SideOnly(Side.CLIENT)
-public enum ColumnHint {
- FULL,
- LEFT,
- RIGHT
-}
diff --git a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java
index c632f85a2..9263cc880 100644
--- a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java
+++ b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java
@@ -9,6 +9,7 @@ import noppes.npcs.client.gui.select.GuiAnimationSelection;
import noppes.npcs.client.gui.select.GuiSoundSelection;
import noppes.npcs.client.gui.util.SubGuiInterface;
+import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -24,12 +25,10 @@ public class FieldDef {
private final String label;
private final FieldType type;
private String tab = null;
- private ColumnHint column = ColumnHint.FULL;
private Supplier