Merge remote-tracking branch 'origin/dev' into syntax-highlighter-unified

# Conflicts:
#	src/api
#	src/main/java/kamkeel/npcs/util/BukkitUtil.java
#	src/main/java/kamkeel/npcs/util/VaultUtil.java
#	src/main/java/noppes/npcs/CustomNpcsPermissions.java
This commit is contained in:
bigguy345 committed 2026-02-03 18:26:30 +02:00
commit ba7e4cc3b2
50 files changed
+2663 -766

No files matched your search

@@ -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.
* <p>
* 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<FieldDef> getFieldDefinitions() {
return Collections.emptyList();
public void getAbilityDefinitions(List<FieldDef> 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<FieldDef> getBaseFieldDefinitions() {
public final List<FieldDef> getAllDefinitions() {
List<FieldDef> 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<FieldDef> getAllFieldDefinitions() {
List<FieldDef> all = new ArrayList<>(getBaseFieldDefinitions());
List<FieldDef> 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
@@ -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
@@ -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;
@@ -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<AbilityEffect> effects = (List<AbilityEffect>) 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<AbilityEffect> effects = (List<AbilityEffect>) 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<Integer, int[]> getEffectWidgetMeta() { return effectWidgetMeta; }
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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")
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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
@@ -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<FieldDef> 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<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> 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<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
)
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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")
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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);
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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")
)
));
}
}
@@ -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<Entity> 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<FieldDef> 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<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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")
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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")
));
}
}
@@ -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<EntityAbilityOrb> orbEntities = new ArrayList<>();
private transient EntityAbilityOrb[] chargingEntities = null;
// Field to keep track of the currently firing orb
private int currentOrb = 0;
public AbilityOrbBarrage() {
this.typeId = "ability.cnpc.orb_barrage";
this.name = "Orb Barrage";
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 75.0f;
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.lockMovement = LockMovementType.WINDUP;
this.telegraphType = TelegraphType.CIRCLE;
this.showTelegraph = true;
// Default built-in animation
this.windUpAnimationName = "Ability_BeamDual_Windup";
this.activeAnimationName = "Ability_BeamDual_Active";
}
// @Override
// @SideOnly(Side.CLIENT)
// public SubGuiAbilityConfig createConfigGui(IAbilityConfigCallback callback) {
// return new SubGuiAbilityOrbBarrage(this, callback);
// }
@Override
public boolean isTargetingModeLocked() {
return true;
}
@Override
public TargetingMode[] getAllowedTargetingModes() {
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
}
@Override
public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) {
if (world.isRemote) {
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<FieldDef> defs) {
defs.addAll(Arrays.asList(
// Type tab
FieldDef.row(
FieldDef.floatField("enchantment.damage", this::getDamage, this::setDamage),
FieldDef.floatField("stats.speed", this::getOrbSpeed, this::setOrbSpeed)
),
FieldDef.row(
FieldDef.floatField("stats.size", this::getOrbSize, this::setOrbSize),
FieldDef.floatField("ability.knockback", this::getKnockback, this::setKnockback)
),
FieldDef.row(
FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance),
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime)
),
FieldDef.section("ability.section.barrage"),
FieldDef.row(
FieldDef.intField("ability.amount", this::getOrbAmount, this::setOrbAmount),
FieldDef.intField("ability.firingSpeed", this::getFiringSpeed, this::setFiringSpeed)
),
FieldDef.boolField("ability.dualCharge", this::isDualCharging, this::setDualCharging),
FieldDef.section("ability.section.homing"),
FieldDef.boolField("gui.enabled", this::isHoming, this::setHoming).hover("ability.hover.homing"),
FieldDef.floatField("gui.strength", this::getHomingStrength, this::setHomingStrength).visibleWhen(this::isHoming),
FieldDef.section("ability.section.explosive"),
FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"),
FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive),
AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects),
// Visual tab
FieldDef.enumField("ability.anchorPoint", AnchorPoint.class, this::getAnchorPointEnum, this::setAnchorPointEnum)
.tab("ability.tab.visual"),
FieldDef.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")
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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
@@ -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<FieldDef> 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<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> getFieldDefinitions() {
return Arrays.asList(
public void getAbilityDefinitions(List<FieldDef> 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)
);
));
}
}
@@ -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<FieldDef> 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<FieldDef> 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)
);
));
}
}
@@ -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) {
@@ -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;
}
}
@@ -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");
@@ -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();
}
}
+10 -8
View File
@@ -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;
@@ -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);
@@ -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<AbilityEffect> effects = (List<AbilityEffect>) 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<AbilityEffect> effects = (List<AbilityEffect>) 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();
}
@@ -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
}
@@ -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<Object> getter;
private Consumer<Object> setter;
private BooleanSupplier visibleWhen = () -> true;
private BooleanSupplier enabledWhen = () -> true;
private String tooltip = null;
private String hoverText = null;
// Numeric range
@@ -51,6 +50,10 @@ public class FieldDef {
private Supplier<Integer> buttonTextColorSupplier;
private Runnable clearAction;
// Row pairing (ROW type only)
private FieldDef leftChild;
private FieldDef rightChild;
private FieldDef(String label, FieldType type) {
this.label = label;
this.type = type;
@@ -134,6 +137,17 @@ public class FieldDef {
return def;
}
/**
* Creates a two-column row pairing two fields side by side.
* If one child is hidden (via visibleWhen), the other renders full-width.
*/
public static FieldDef row(FieldDef left, FieldDef right) {
FieldDef def = new FieldDef("", FieldType.ROW);
def.leftChild = left;
def.rightChild = right;
return def;
}
// ═══════════════════════════════════════════════════════════════════
// CONVENIENCE FACTORIES (SUB_GUI wrappers)
// ═══════════════════════════════════════════════════════════════════
@@ -206,8 +220,13 @@ public class FieldDef {
return this;
}
public FieldDef column(ColumnHint column) {
this.column = column;
public FieldDef min(float min) {
this.min = min;
return this;
}
public FieldDef max(float max) {
this.max = max;
return this;
}
@@ -221,11 +240,6 @@ public class FieldDef {
return this;
}
public FieldDef tooltip(String tooltip) {
this.tooltip = tooltip;
return this;
}
public FieldDef hover(String hoverText) {
this.hoverText = hoverText;
return this;
@@ -253,10 +267,8 @@ public class FieldDef {
public String getLabel() { return label; }
public FieldType getType() { return type; }
public String getTab() { return tab; }
public ColumnHint getColumn() { return column; }
public boolean isVisible() { return visibleWhen.getAsBoolean(); }
public boolean isEnabled() { return enabledWhen.getAsBoolean(); }
public String getTooltip() { return tooltip; }
public String getHoverText() { return hoverText; }
public float getMin() { return min; }
public float getMax() { return max; }
@@ -293,4 +305,77 @@ public class FieldDef {
return buttonTextColorSupplier != null ? buttonTextColorSupplier.get() : null;
}
public FieldDef getLeftChild() { return leftChild; }
public FieldDef getRightChild() { return rightChild; }
// ═══════════════════════════════════════════════════════════════════
// LIST MANIPULATION (for mod injection)
// ═══════════════════════════════════════════════════════════════════
/**
* Insert a field before the first field matching the target label.
* Searches top-level labels and ROW children.
*
* @param fields The field list to modify
* @param targetLabel The label of the field to insert before
* @param newField The field to insert
* @return true if the target was found and the field was inserted
*/
public static boolean insertBefore(java.util.List<FieldDef> fields, String targetLabel, FieldDef newField) {
for (int i = 0; i < fields.size(); i++) {
if (matchesLabel(fields.get(i), targetLabel)) {
fields.add(i, newField);
return true;
}
}
return false;
}
/**
* Insert a field after the first field matching the target label.
* Searches top-level labels and ROW children.
*
* @param fields The field list to modify
* @param targetLabel The label of the field to insert after
* @param newField The field to insert
* @return true if the target was found and the field was inserted
*/
public static boolean insertAfter(List<FieldDef> fields, String targetLabel, FieldDef newField) {
for (int i = 0; i < fields.size(); i++) {
if (matchesLabel(fields.get(i), targetLabel)) {
fields.add(i + 1, newField);
return true;
}
}
return false;
}
/**
* Modify the visibility condition of a field.
* Searches top-level labels and ROW children.
*
* @param fields The field list to modify
* @param targetLabel The label of the field to modify
* @param condition The condition to replace
* @return true if the target was found and the condition was modified
*/
public static boolean modifyVisibility(List<FieldDef> fields, String targetLabel, BooleanSupplier condition) {
for (FieldDef field : fields) {
if (matchesLabel(field, targetLabel)) {
field.visibleWhen(condition);
return true;
}
}
return false;
}
private static boolean matchesLabel(FieldDef def, String targetLabel) {
if (targetLabel.equals(def.label)) return true;
if (def.type == FieldType.ROW) {
if (def.leftChild != null && targetLabel.equals(def.leftChild.label)) return true;
if (def.rightChild != null && targetLabel.equals(def.rightChild.label)) return true;
}
return false;
}
}
@@ -14,5 +14,6 @@ public enum FieldType {
STRING,
SECTION_HEADER,
LABEL,
EFFECTS_LIST
EFFECTS_LIST,
ROW
}
@@ -3,6 +3,7 @@ package noppes.npcs.client.gui.builder;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.gui.util.GuiNPCInterface;
import noppes.npcs.client.gui.util.GuiNpcButton;
@@ -10,6 +11,7 @@ import noppes.npcs.client.gui.util.GuiNpcButtonYesNo;
import noppes.npcs.client.gui.util.GuiNpcLabel;
import noppes.npcs.client.gui.util.GuiNpcTextField;
import noppes.npcs.client.gui.util.GuiScrollWindow;
import noppes.npcs.client.gui.util.SubGuiInterface;
import java.util.HashMap;
import java.util.List;
@@ -19,8 +21,8 @@ import java.util.Map;
* General-purpose builder that converts a list of {@link FieldDef} into positioned
* GUI widgets inside a {@link GuiScrollWindow}.
* <p>
* Supports 1-column and 2-column layouts. Subclasses can override {@link #buildField}
* to handle custom field types.
* Uses {@link FieldDef#row(FieldDef, FieldDef)} for two-column layouts.
* Subclasses can override {@link #buildField} to handle custom field types.
*/
@SideOnly(Side.CLIENT)
public class GuiFieldBuilder {
@@ -36,9 +38,6 @@ public class GuiFieldBuilder {
protected int rowHeight = 24;
protected int labelPadding = 8;
// Column mode
protected int columnCount = 2;
// Dependencies
protected final GuiNPCInterface parent;
protected GuiScrollWindow sw;
@@ -71,7 +70,6 @@ public class GuiFieldBuilder {
// ═══════════════════════════════════════════════════════════════════
public GuiFieldBuilder scrollWindowId(int id) { this.scrollWindowId = id; return this; }
public GuiFieldBuilder columns(int count) { this.columnCount = Math.max(1, Math.min(2, count)); return this; }
public GuiFieldBuilder contentRight(int v) { this.contentRight = v; return this; }
public GuiFieldBuilder startIds(int widget, int clear, int label) {
this.widgetId = widget; this.clearId = clear; this.labelId = label; return this;
@@ -125,38 +123,37 @@ public class GuiFieldBuilder {
// Section header
if (def.getType() == FieldType.SECTION_HEADER) {
y += 3;
String sectionText = StatCollector.translateToLocal(def.getLabel());
if (def.getTooltip() != null && !def.getTooltip().isEmpty()) {
sectionText += " (" + StatCollector.translateToLocal(def.getTooltip()) + ")";
GuiNpcLabel sectionLabel = new GuiNpcLabel(labelId++, def.getLabel(), colLLabel, y + 2, 0xFFFF55);
if (def.getHoverText() != null && !def.getHoverText().isEmpty()) {
sectionLabel.setHoverText(def.getHoverText());
}
sw.addLabel(new GuiNpcLabel(labelId++, sectionText, colLLabel, y + 2, 0xFFFF55));
sw.addLabel(sectionLabel);
y += 15;
continue;
}
// Two-column pair: LEFT followed by RIGHT (only in 2-column mode)
if (columnCount == 2
&& def.getColumn() == ColumnHint.LEFT
&& i + 1 < fields.size()
&& fields.get(i + 1).getColumn() == ColumnHint.RIGHT
&& fields.get(i + 1).getType() != FieldType.SECTION_HEADER) {
renderFieldAt(def, colLLabel, colLField, colLWidth, y);
// ROW: explicit two-column pair
if (def.getType() == FieldType.ROW) {
FieldDef left = def.getLeftChild();
FieldDef right = def.getRightChild();
boolean leftVis = left != null && left.isVisible();
boolean rightVis = right != null && right.isVisible();
i++;
FieldDef rightDef = fields.get(i);
renderFieldAt(rightDef, colRLabel, colRField, colRWidth, y);
y += rowHeight;
} else {
// Full-width: compute fieldX from label pixel width
int fieldW = getFullFieldWidth(def);
String translated = StatCollector.translateToLocal(def.getLabel());
int labelW = fontRenderer.getStringWidth(translated);
int fieldX = colLLabel + labelW + labelPadding;
renderFieldAt(def, colLLabel, fieldX, fieldW, y);
y += rowHeight;
if (leftVis && rightVis) {
renderFieldAt(left, colLLabel, colLField, colLWidth, y);
renderFieldAt(right, colRLabel, colRField, colRWidth, y);
} else if (leftVis) {
renderFullWidth(left, y);
} else if (rightVis) {
renderFullWidth(right, y);
}
if (leftVis || rightVis) y += rowHeight;
continue;
}
// Full-width field
renderFullWidth(def, y);
y += rowHeight;
}
lastBuildY = y;
@@ -182,9 +179,12 @@ public class GuiFieldBuilder {
@SuppressWarnings({"unchecked", "rawtypes"})
protected void renderFieldAt(FieldDef def, int labelX, int fieldX, int fieldW, int y) {
sw.addLabel(new GuiNpcLabel(labelId++, def.getLabel(), labelX, y + 5, 0xFFFFFF));
String hover = def.getHoverText() != null ? def.getHoverText() : def.getTooltip();
GuiNpcLabel fieldLabel = new GuiNpcLabel(labelId++, def.getLabel(), labelX, y + 5, 0xFFFFFF);
String hover = def.getHoverText();
if (hover != null && !hover.isEmpty()) {
fieldLabel.setHoverText(hover);
}
sw.addLabel(fieldLabel);
switch (def.getType()) {
case FLOAT: {
@@ -307,6 +307,14 @@ public class GuiFieldBuilder {
}
}
private void renderFullWidth(FieldDef def, int y) {
int fieldW = getFullFieldWidth(def);
String translated = StatCollector.translateToLocal(def.getLabel());
int labelW = fontRenderer.getStringWidth(translated);
int fieldX = colLLabel + labelW + labelPadding;
renderFieldAt(def, colLLabel, fieldX, fieldW, y);
}
protected int getFullFieldWidth(FieldDef def) {
switch (def.getType()) {
case FLOAT:
@@ -321,6 +329,101 @@ public class GuiFieldBuilder {
}
}
// ═══════════════════════════════════════════════════════════════════
// EVENT HANDLING
// ═══════════════════════════════════════════════════════════════════
/**
* Handles button events for declarative fields.
* Call from parent GUI's buttonEvent(). Returns true if the event was handled.
* <p>
* For SUB_GUI fields, the sub-gui is opened directly via {@code parent.setSubGuiWithResult()},
* so the result handler survives initGui() rebuilds. After this returns true, check
* {@code parent.hasSubGui()} to determine if initGui() should be called.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public boolean handleButtonEvent(int buttonId, GuiButton button) {
// Check clear buttons first
if (clearFieldMap.containsKey(buttonId)) {
FieldDef def = clearFieldMap.get(buttonId);
if (def != null && def.hasClearAction()) {
def.getClearAction().run();
return true;
}
}
// Check field buttons
FieldDef def = buttonFieldMap.get(buttonId);
if (def == null) return false;
switch (def.getType()) {
case BOOLEAN:
def.setValue(((GuiNpcButton) button).getValue() == 1);
return true;
case ENUM: {
int idx = ((GuiNpcButton) button).getValue();
Class<? extends Enum<?>> ec = def.getEnumClass();
if (ec != null) {
Enum<?>[] constants = ec.getEnumConstants();
if (idx >= 0 && idx < constants.length) def.setValue(constants[idx]);
}
return true;
}
case STRING_ENUM: {
int idx = ((GuiNpcButton) button).getValue();
String[] values = def.getStringEnumValues();
if (values != null && idx >= 0 && idx < values.length) def.setStringEnumValue(values[idx]);
return true;
}
case SUB_GUI:
if (def.getSubGuiFactory() != null) {
parent.setSubGuiWithResult(def.getSubGuiFactory().get(), sub -> {
if (def.getSubGuiResultHandler() != null) {
def.getSubGuiResultHandler().accept(sub);
}
});
}
return true;
default:
return false;
}
}
/**
* Handles text field unfocus events for declarative fields.
* Call from parent GUI's unFocused(). Returns true if the event was handled.
*/
public boolean handleTextFieldEvent(int textFieldId, GuiNpcTextField field) {
FieldDef def = textFieldMap.get(textFieldId);
if (def == null) return false;
switch (def.getType()) {
case FLOAT:
try { def.setValue(Float.parseFloat(field.getText())); } catch (NumberFormatException ignored) {}
return true;
case INT:
try { def.setValue(Integer.parseInt(field.getText())); } catch (NumberFormatException ignored) {}
return true;
case STRING:
def.setValue(field.getText());
return true;
default:
return false;
}
}
/**
* Handles sub-gui close events. Call from parent GUI's subGuiClosed().
* <p>
* Note: SUB_GUI fields opened via handleButtonEvent use
* {@code parent.setSubGuiWithResult()}, so their results are handled
* automatically by {@code GuiNPCInterface.closeSubGui()}. This method
* only handles sub-guis opened through other means.
*/
public boolean handleSubGuiClosed(SubGuiInterface subgui) {
return false;
}
// ═══════════════════════════════════════════════════════════════════
// GETTERS
// ═══════════════════════════════════════════════════════════════════
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
public abstract class GuiNPCInterface extends GuiScreen {
@@ -52,6 +53,7 @@ public abstract class GuiNPCInterface extends GuiScreen {
public Supplier<Boolean> closeOnEscSupplier = null;
public int guiLeft, guiTop, xSize, ySize;
private SubGuiInterface subgui;
private Consumer<SubGuiInterface> pendingSubGuiResult;
public int mouseX, mouseY, mouseScroll;
public float bgScale = 1;
public float bgScaleX = 1;
@@ -518,7 +520,21 @@ public abstract class GuiNPCInterface extends GuiScreen {
initGui();
}
/**
* Open a sub-gui with a result handler that will be called when the sub-gui closes.
* The handler survives initGui() rebuilds since it lives on the parent, not the builder.
*/
public void setSubGuiWithResult(SubGuiInterface gui, Consumer<SubGuiInterface> resultHandler) {
this.pendingSubGuiResult = resultHandler;
setSubGui(gui);
}
public void closeSubGui(SubGuiInterface gui) {
if (pendingSubGuiResult != null) {
Consumer<SubGuiInterface> handler = pendingSubGuiResult;
pendingSubGuiResult = null;
handler.accept(gui);
}
subgui = null;
initGui();
}
@@ -1,18 +1,32 @@
package noppes.npcs.client.gui.util;
import kamkeel.npcs.util.TextSplitter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.CustomNpcResourceListener;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public class GuiNpcLabel {
import java.util.Iterator;
import java.util.List;
public class GuiNpcLabel extends Gui {
public String label;
public int x, y, color;
public boolean enabled = true;
public int id;
public boolean drawShadow = false;
// Hover text support
public String hoverableText = "";
private boolean wasHovered = false;
private int hoverCount = 0;
public GuiNpcLabel(int id, Object label, int x, int y, int color) {
this.id = id;
this.label = StatCollector.translateToLocal(label.toString());
@@ -25,6 +39,10 @@ public class GuiNpcLabel {
this(id, label, x, y, CustomNpcResourceListener.DefaultTextColor);
}
public void setHoverText(String text) {
this.hoverableText = text;
}
public void drawLabel(GuiScreen gui, FontRenderer fontRenderer) {
if (enabled)
fontRenderer.drawString(label, x, y, color, drawShadow);
@@ -34,4 +52,119 @@ public class GuiNpcLabel {
int size = Minecraft.getMinecraft().fontRenderer.getStringWidth(label);
x += (width - size) / 2;
}
public boolean isMouseOver(int mouseX, int mouseY, FontRenderer fr) {
int width = fr.getStringWidth(label);
return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + 10;
}
/**
* Draws hover tooltip if the mouse is over this label.
* @param localMouseX Mouse X in scroll-local coordinates (for hit testing)
* @param localMouseY Mouse Y in scroll-local coordinates (for hit testing)
* @param screenMouseX Mouse X in screen coordinates (for tooltip positioning)
* @param screenMouseY Mouse Y in screen coordinates (for tooltip positioning)
* @param hasSubGui Whether a sub-gui is open
* @param fr FontRenderer for measuring label width
*/
public void drawHover(int localMouseX, int localMouseY, int screenMouseX, int screenMouseY, boolean hasSubGui, FontRenderer fr) {
if (hasSubGui || hoverableText.isEmpty()) return;
boolean hovered = isMouseOver(localMouseX, localMouseY, fr);
if (!hovered) {
wasHovered = false;
hoverCount = 0;
return;
}
if (!wasHovered) {
wasHovered = true;
hoverCount = 0;
}
if (hoverCount < 65) hoverCount++;
if (hoverCount > 60) {
GL11.glPushMatrix();
Minecraft mc = Minecraft.getMinecraft();
String displayString = StatCollector.translateToLocal(hoverableText);
GL11.glColor4f(1, 1, 1, 1);
List<String> lines = TextSplitter.splitText(displayString, 30);
drawHoveringText(lines, screenMouseX, screenMouseY, mc);
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glPopMatrix();
}
}
@SuppressWarnings("rawtypes")
protected void drawHoveringText(List textLines, int x, int y, Minecraft mc) {
if (mc.fontRenderer == null || textLines.isEmpty())
return;
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
RenderHelper.disableStandardItemLighting();
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_DEPTH_TEST);
int maxWidth = 0;
Iterator iterator = textLines.iterator();
while (iterator.hasNext()) {
String s = (String) iterator.next();
int lineWidth = mc.fontRenderer.getStringWidth(s);
if (lineWidth > maxWidth) {
maxWidth = lineWidth;
}
}
int j2 = x + 12;
int k2 = y - 12;
int maxHeight = 8;
if (textLines.size() > 1) {
maxHeight += 2 + (textLines.size() - 1) * 10;
}
ScaledResolution scaledRes = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int screenWidth = scaledRes.getScaledWidth();
int screenHeight = scaledRes.getScaledHeight();
if (j2 + maxWidth + 6 > screenWidth) {
j2 = x - maxWidth - 16;
}
if (j2 < 4) {
j2 = 4;
}
if (k2 + maxHeight + 6 > screenHeight) {
k2 = screenHeight - maxHeight - 6;
}
if (k2 < 4) {
k2 = 4;
}
this.zLevel = 300.0F;
int j1 = -267386864;
this.drawGradientRect(j2 - 3, k2 - 4, j2 + maxWidth + 3, k2 - 3, j1, j1);
this.drawGradientRect(j2 - 3, k2 + maxHeight + 3, j2 + maxWidth + 3, k2 + maxHeight + 4, j1, j1);
this.drawGradientRect(j2 - 3, k2 - 3, j2 + maxWidth + 3, k2 + maxHeight + 3, j1, j1);
this.drawGradientRect(j2 - 4, k2 - 3, j2 - 3, k2 + maxHeight + 3, j1, j1);
this.drawGradientRect(j2 + maxWidth + 3, k2 - 3, j2 + maxWidth + 4, k2 + maxHeight + 3, j1, j1);
int k1 = 1347420415;
int l1 = (k1 & 16711422) >> 1 | k1 & -16777216;
this.drawGradientRect(j2 - 3, k2 - 3 + 1, j2 - 3 + 1, k2 + maxHeight + 3 - 1, k1, l1);
this.drawGradientRect(j2 + maxWidth + 2, k2 - 3 + 1, j2 + maxWidth + 3, k2 + maxHeight + 3 - 1, k1, l1);
this.drawGradientRect(j2 - 3, k2 - 3, j2 + maxWidth + 3, k2 - 3 + 1, k1, k1);
this.drawGradientRect(j2 - 3, k2 + maxHeight + 2, j2 + maxWidth + 3, k2 + maxHeight + 3, l1, l1);
for (int i2 = 0; i2 < textLines.size(); ++i2) {
String s1 = (String) textLines.get(i2);
mc.fontRenderer.drawStringWithShadow(s1, j2, k2, -1);
if (i2 == 0) {
k2 += 2;
}
k2 += 10;
}
this.zLevel = 0.0F;
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST);
RenderHelper.enableStandardItemLighting();
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
}
}
@@ -300,6 +300,14 @@ public class GuiScrollWindow extends GuiScreen implements ITextfieldListener, IC
button.drawHover(mouseX, mouseY, subGui);
}
}
// Label hover: convert screen mouse to scroll-local coords for hit testing
int localMouseX = mouseX - xPos;
int localMouseY = (int) (mouseY - yPos + scrollY);
for (GuiNpcLabel label : labels.values()) {
if (!label.hoverableText.isEmpty()) {
label.drawHover(localMouseX, localMouseY, mouseX, mouseY, subGui, fontRendererObj);
}
}
}
public void elementClicked() {
@@ -453,13 +453,29 @@ public class PlayerAbilityData implements IPlayerAbilityData {
// ANIMATION
// ═══════════════════════════════════════════════════════════════════
private void playAbilityAnimation(Animation animation) {
public void playAbilityAnimation(Animation animation) {
if (animation == null || AnimationController.Instance == null) return;
playerData.animationData.setEnabled(true);
playerData.animationData.setAnimation(animation);
playerData.animationData.updateClient();
}
public void playAbilityAnimation(int animation) {
if (animation < 0) return;
if (AnimationController.Instance == null) return;
if (AnimationController.Instance.get(animation) == null) return;
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));
}
private void stopAbilityAnimation() {
playerData.animationData.setAnimation(null);
playerData.animationData.updateClient();
@@ -16,6 +16,7 @@ public class ItemElementalStaff extends ItemStaff {
public ItemElementalStaff(int par1, EnumNpcToolMaterial material) {
super(par1, material);
setHasSubtypes(true);
this.color = OrbColor.GENERIC;
}
@Override
+103 -35
View File
@@ -1,7 +1,11 @@
package noppes.npcs.items;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.entity.EntityAbilityOrb;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.passive.EntitySheep;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
@@ -20,13 +24,18 @@ import noppes.npcs.entity.EntityProjectile;
import noppes.npcs.util.IProjectileCallback;
import org.lwjgl.opengl.GL11;
import java.awt.*;
public class ItemStaff extends ItemNpcInterface implements IProjectileCallback {
private EnumNpcToolMaterial material;
protected OrbColor color;
public ItemStaff(int par1, EnumNpcToolMaterial material) {
super(par1);
this.material = material;
this.color = OrbColor.get(material.ordinal());;
setCreativeTab(CustomItems.tabWeapon);
}
@@ -36,70 +45,92 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback {
}
@Override
public void onPlayerStoppedUsing(ItemStack stack, World worldObj, EntityPlayer player, int par4) {
if (worldObj.isRemote) {
public void onPlayerStoppedUsing(ItemStack stack, World world, EntityPlayer player, int par4) {
if (world.isRemote)
return;
}
if (stack.stackTagCompound == null)
return;
Entity entity = ((WorldServer) player.worldObj).getEntityByID(stack.stackTagCompound.getInteger("MagicProjectile"));
if (entity == null || !(entity instanceof EntityProjectile))
Entity entity = ((WorldServer) world)
.getEntityByID(stack.stackTagCompound.getInteger("MagicProjectile"));
if (!(entity instanceof EntityAbilityOrb))
return;
EntityProjectile item = (EntityProjectile) entity;
item.callback = this;
item.callbackItem = stack;
item.explosive = true;
item.explosiveDamage = false;
item.explosiveRadius = 1;
item.prevRotationYaw = item.rotationYaw = player.rotationYaw;
item.prevRotationPitch = item.rotationPitch = player.rotationPitch;
item.shoot(2);
player.worldObj.playSoundAtEntity(player, "customnpcs:magic.shot", 1.0F, 1);
EntityAbilityOrb orb = (EntityAbilityOrb) entity;
orb.startMoving(null);
world.playSoundAtEntity(
player,
"customnpcs:magic.shot",
1.0F,
1.0F
);
}
@Override
public void onUsingTick(ItemStack stack, EntityPlayer player, int count) {
int tick = getMaxItemUseDuration(stack) - count;
if (player.worldObj.isRemote) {
spawnParticle(stack, player);
return;
}
int chargeTime = 20 + material.getHarvestLevel() * 8;
if (tick == chargeTime) {
if (!player.capabilities.isCreativeMode && !hasInfinite(stack)) {
if (!hasItem(player, CustomItems.mana))
return;
consumeItem(player, CustomItems.mana);
}
player.worldObj.playSoundAtEntity(player, "customnpcs:magic.charge", 1.0F, 1);
player.worldObj.playSoundAtEntity(
player,
"customnpcs:magic.charge",
1.0F,
1.0F
);
if (stack.stackTagCompound == null) {
stack.stackTagCompound = new NBTTagCompound();
}
int damage = 6 + material.getDamageVsEntity() + player.worldObj.rand.nextInt(4);
damage += damage * EnchantInterface.getLevel(EnchantInterface.Damage, stack) * 0.5f;
EntityProjectile projectile = new EntityMagicProjectile(player.worldObj, player, getProjectile(stack), false);
projectile.damage = damage;
projectile.setSpeed(25);
double dx = -MathHelper.sin((float) ((player.rotationYaw / 180F) * Math.PI)) * MathHelper.cos((float) ((player.rotationPitch / 180F) * Math.PI));
double dz = MathHelper.cos((float) ((player.rotationYaw / 180F) * Math.PI)) * MathHelper.cos((float) ((player.rotationPitch / 180F) * Math.PI));
projectile.setPosition(player.posX + dx * 0.8, player.posY + 1.5 - player.rotationPitch / 80, player.posZ + dz * 0.8);
player.worldObj.spawnEntityInWorld(projectile);
stack.stackTagCompound.setInteger("MagicProjectile", projectile.getEntityId());//entityid
}
if (tick > chargeTime && stack.stackTagCompound != null) {
Entity entity = ((WorldServer) player.worldObj).getEntityByID(stack.stackTagCompound.getInteger("MagicProjectile"));
if (entity == null || !(entity instanceof EntityProjectile))
return;
EntityProjectile item = (EntityProjectile) entity;
item.ticksInAir = 0;
double dx = -MathHelper.sin((float) ((player.rotationYaw / 180F) * Math.PI)) * MathHelper.cos((float) ((player.rotationPitch / 180F) * Math.PI));
double dz = MathHelper.cos((float) ((player.rotationYaw / 180F) * Math.PI)) * MathHelper.cos((float) ((player.rotationPitch / 180F) * Math.PI));
item.setPosition(player.posX + dx * 0.8, player.posY + 1.5 - player.rotationPitch / 80, player.posZ + dz * 0.8);
}
EnergyCombatData combat = new EnergyCombatData();
combat.damage = damage;
combat.explosive = true;
EnergyColorData colorData = new EnergyColorData(getOrbColor(stack, false), getOrbColor(stack, true));
EnergyLightningData lightning = new EnergyLightningData();
EnergyLifespanData lifespan = new EnergyLifespanData(100, 72000);
EnergyHomingData homing = new EnergyHomingData();
homing.speed = 0.5f;
homing.homingStrength = 0.35f;
homing.homingRange = 20f;
EntityAbilityOrb orb = new EntityAbilityOrb(
player.worldObj, player, null,
player.posX, player.posY + player.getEyeHeight(), player.posZ,
1.0f, colorData, combat, homing, lightning, lifespan
);
orb.setupCharging(
new EnergyAnchorData(AnchorPoint.FRONT, 0, -1, 0),
chargeTime
);
player.worldObj.spawnEntityInWorld(orb);
stack.stackTagCompound.setInteger("MagicProjectile", orb.getEntityId());
}
}
@Override
@@ -118,6 +149,15 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback {
return EnumAction.bow;
}
public int getOrbColor(ItemStack stack, boolean outer) {
if (color == OrbColor.GENERIC) {
float[] color = EntitySheep.fleeceColorTable[stack.getItemDamage()];
return new Color(color[0], color[1], color[2]).getRGB();
}
return outer ? color.outer : color.inner;
}
public ItemStack getProjectile(ItemStack stack) {
if (stack.getItem() == CustomItems.staffWood) {
return new ItemStack(CustomItems.spellNature);
@@ -196,4 +236,32 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback {
public boolean hasInfinite(ItemStack stack) {
return EnchantInterface.getLevel(EnchantInterface.Infinite, stack) > 0;
}
public enum OrbColor {
WOOD(0x7cc818, 0x644a32),
STONE(0x532d5b, 0x515151),
BRONZE(0x83F7F6, 0x5CF0FF),
IRON(0xf7fac5, 0xeafa94),
DIA(0x44B6FF, 0x94DFED),
GOLD(0xb3011e, 0xd47c32),
EMERALD(0xdd6df7, 0xcf1dfa),
DEMONIC(0xb8144a, 0x422643),
FROST(0xffffff, 0xaccbfe),
MITHRIL(0xf7fac5, 0xffffff),
GENERIC(0xFFFFFF, 0x88FFFF);
public final int inner;
public final int outer;
OrbColor(int inner, int outer) {
this.inner = inner;
this.outer = outer;
}
public static OrbColor get(int id) {
if (id >= OrbColor.values().length || id < 0) return GENERIC;
return OrbColor.values()[id];
}
}
}