diff --git a/dependencies.gradle b/dependencies.gradle index 2e3e5db52..acf5c1bec 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -38,7 +38,7 @@ dependencies { runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.5.4-GTNH:dev") runtimeOnlyNonPublishable("curse.maven:Nashorn-374413:2924946") - compileOnly(files("./tools/bukkit-api/spigot-api-ONLY.jar")) +// compileOnly(files("./tools/bukkit-api/spigot-api-ONLY.jar")) // compileOnly(files("./tools/bukkit-api/Vault-1.4.1.jar")) shadowImplementation(project.files("tools/standalone-JaninoLoader-1.0.1-ALPHA.jar")) diff --git a/src/api b/src/api index 2054012fa..03be7a5ca 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 2054012fae799fa233b99824b4f649d311eff86f +Subproject commit 03be7a5cac601cd19509df9dc7bac17081aea591 diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index c75d7d5d6..015f6361b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -29,15 +29,12 @@ import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.AbilityEvent; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; -import noppes.npcs.client.gui.builder.ColumnHint; import noppes.npcs.client.gui.builder.FieldDef; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import net.minecraft.entity.Entity; -import somehussar.gui.annotationHandling.GuiEditable; public abstract class Ability implements IAbility { @@ -149,6 +146,9 @@ public abstract class Ability implements IAbility { public void onComplete(EntityLivingBase caster, EntityLivingBase target) { } + public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) { + } + /** * Apply damage to an entity with ability hit event support. * Fires the abilityHit script event, allowing scripts to modify or cancel the damage. @@ -342,40 +342,51 @@ public abstract class Ability implements IAbility { } /** - * Returns the declarative field definitions for this ability's GUI. - * Override in subclasses to declare type-specific and visual fields. - * The GUI rendering engine in SubGuiAbilityConfig uses this list to - * automatically build the Type and Visual tabs. + * Add field definitions for this ability's GUI. + * Override in subclasses to add type-specific fields. The list already contains + * the base fields (General, Target, Effects tabs), so subclasses can also + * modify, insert into, or remove base fields using {@link FieldDef#insertBefore}, + * {@link FieldDef#insertAfter}, or direct list manipulation. + *

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

- * 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. + *

+ * 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> 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(). + *

+ * 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 // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java index f417f854a..82ffd330e 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface.java @@ -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 closeOnEscSupplier = null; public int guiLeft, guiTop, xSize, ySize; private SubGuiInterface subgui; + private Consumer 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 resultHandler) { + this.pendingSubGuiResult = resultHandler; + setSubGui(gui); + } + public void closeSubGui(SubGuiInterface gui) { + if (pendingSubGuiResult != null) { + Consumer handler = pendingSubGuiResult; + pendingSubGuiResult = null; + handler.accept(gui); + } subgui = null; initGui(); } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java index f620e1e63..60b4afdb6 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java @@ -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 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); + } } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java index fa33a9a09..ab6e612d4 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiScrollWindow.java @@ -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() { diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 45f9dfe44..724b054c0 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -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(); diff --git a/src/main/java/noppes/npcs/items/ItemElementalStaff.java b/src/main/java/noppes/npcs/items/ItemElementalStaff.java index d786222b1..03384f76a 100644 --- a/src/main/java/noppes/npcs/items/ItemElementalStaff.java +++ b/src/main/java/noppes/npcs/items/ItemElementalStaff.java @@ -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 diff --git a/src/main/java/noppes/npcs/items/ItemStaff.java b/src/main/java/noppes/npcs/items/ItemStaff.java index 89ce879cf..9957b195b 100644 --- a/src/main/java/noppes/npcs/items/ItemStaff.java +++ b/src/main/java/noppes/npcs/items/ItemStaff.java @@ -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]; + } + } } diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Guard_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_Guard_Active.json new file mode 100644 index 000000000..258320341 --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_Guard_Active.json @@ -0,0 +1,50 @@ +{ + "Loop": 0, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 2.0f, + "FrameParts": [ + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -80.0f, + "Rotation0": -120.0f, + "Pivot0": 2.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 80.0f, + "Rotation0": -120.0f, + "Pivot0": -2.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 10 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 0, + "Smooth": 0b, + "ID": 18, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_Guard_Active", + "WhileStanding": 1b +} diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Guard_Counter.json b/src/main/resources/assets/customnpcs/animations/Ability_Guard_Counter.json new file mode 100644 index 000000000..9194dfd72 --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_Guard_Counter.json @@ -0,0 +1,203 @@ +{ + "Loop": 2, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 2.0f, + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -80.0f, + "Rotation0": -120.0f, + "Pivot0": 2.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 80.0f, + "Rotation0": -120.0f, + "Pivot0": -2.0f, + "Pivot1": 2.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 3 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 2.0f, + "FrameParts": [ + { + "Pivot2": -3.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -20.0f, + "Rotation0": 90.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 60.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 20.0f, + "Rotation0": 90.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": -60.0f + }, + { + "Pivot2": -6.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 31.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 31.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 3 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 0.8f, + "FrameParts": [ + { + "Pivot2": -3.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -20.0f, + "Rotation0": 90.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 60.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 20.0f, + "Rotation0": 90.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": -60.0f + }, + { + "Pivot2": -6.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 31.0f, + "Pivot0": 0.0f, + "Pivot1": 3.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 31.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 10 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 0, + "Smooth": 0b, + "ID": 17, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_Guard_Counter", + "WhileStanding": 1b +} diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Active.json new file mode 100644 index 000000000..8ea8fc18b --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Active.json @@ -0,0 +1,77 @@ +{ + "Loop": 0, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 1.0f, + "FrameParts": [ + { + "Pivot2": -7.0f, + "Part": "LEFT_ARM", + "Rotation1": -12.0f, + "Rotation0": 12.0f, + "Pivot0": -1.0f, + "Pivot1": 7.0f, + "Rotation2": -63.0f + }, + { + "Pivot2": -9.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 69.0f, + "Pivot0": 0.0f, + "Pivot1": 7.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "RIGHT_ARM", + "Rotation1": 12.0f, + "Rotation0": 12.0f, + "Pivot0": 1.0f, + "Pivot1": 7.0f, + "Rotation2": 63.0f + }, + { + "Pivot2": -4.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 56.0f, + "Pivot0": 0.0f, + "Pivot1": 4.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -9.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 50.0f, + "Pivot0": 0.0f, + "Pivot1": 7.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 2 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 0, + "Smooth": 0b, + "ID": 20, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_Shockwave_Active", + "WhileStanding": 1b +} diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Windup.json b/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Windup.json new file mode 100644 index 000000000..2d45359b3 --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_Shockwave_Windup.json @@ -0,0 +1,203 @@ +{ + "Loop": -1, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 2.0f, + "FrameParts": [ + { + "Pivot2": 4.0f, + "Part": "LEFT_ARM", + "Rotation1": -12.0f, + "Rotation0": -183.0f, + "Pivot0": -1.0f, + "Pivot1": -2.0f, + "Rotation2": -12.0f + }, + { + "Pivot2": 3.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": -18.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 4.0f, + "Part": "RIGHT_ARM", + "Rotation1": 12.0f, + "Rotation0": -183.0f, + "Pivot0": 1.0f, + "Pivot1": -2.0f, + "Rotation2": 12.0f + }, + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -2.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": -25.0f, + "Pivot0": 0.0f, + "Pivot1": -5.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 3.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": -18.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 5 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 0.5f, + "FrameParts": [ + { + "Pivot2": 4.0f, + "Part": "LEFT_ARM", + "Rotation1": -12.0f, + "Rotation0": -183.0f, + "Pivot0": -1.0f, + "Pivot1": -2.0f, + "Rotation2": -12.0f + }, + { + "Pivot2": 3.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": -18.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 4.0f, + "Part": "RIGHT_ARM", + "Rotation1": 12.0f, + "Rotation0": -183.0f, + "Pivot0": 1.0f, + "Pivot1": -2.0f, + "Rotation2": 12.0f + }, + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -2.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": -25.0f, + "Pivot0": 0.0f, + "Pivot1": -5.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 3.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": -18.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 18 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": -7.0f, + "Part": "LEFT_ARM", + "Rotation1": -12.0f, + "Rotation0": 12.0f, + "Pivot0": -1.0f, + "Pivot1": 7.0f, + "Rotation2": -63.0f + }, + { + "Pivot2": -9.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 69.0f, + "Pivot0": 0.0f, + "Pivot1": 7.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -7.0f, + "Part": "RIGHT_ARM", + "Rotation1": 12.0f, + "Rotation0": 12.0f, + "Pivot0": 1.0f, + "Pivot1": 7.0f, + "Rotation2": 63.0f + }, + { + "Pivot2": -4.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 56.0f, + "Pivot0": 0.0f, + "Pivot1": 4.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -6.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -9.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 50.0f, + "Pivot0": 0.0f, + "Pivot1": 7.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 2 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 1, + "Smooth": 0b, + "ID": 19, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_Shockwave_Windup", + "WhileStanding": 1b +} diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 8f5af1340..ea433dc7e 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1608,6 +1608,7 @@ ability.cnpc.beam_dual=Beam Dual ability.cnpc.projectile=Projectile ability.cnpc.orb=Orb ability.cnpc.orb_dual=Orb Dual +ability.cnpc.orb_barrage=Orb Barrage ability.cnpc.charge=Charge ability.cnpc.dash=Dash ability.cnpc.teleport=Teleport @@ -1643,8 +1644,10 @@ ability.conditions=Conditions # Effects Tab ability.windUpSound=Wind Up Sound ability.activeSound=Active Sound +ability.counterSound=Counter Sound ability.windUpAnimation=Wind Up Animation ability.activeAnimation=Active Animation +ability.counterAnimation=Counter Animation ability.showTelegraph=Show Telegraph ability.windUpColor=Wind Up Color ability.activeColor=Active Color @@ -1672,6 +1675,7 @@ ability.section.disc1=Disc 1 ability.section.disc2=Disc 2 ability.section.orb1=Orb 1 ability.section.orb2=Orb 2 +ability.section.barrage=Barrage ability.section.counter=Counter ability.section.area=Area ability.section.damage=Damage @@ -1698,6 +1702,7 @@ gui.sound=Sound gui.animation=Animation gui.duration=Duration gui.amp=Amp +gui.speed=Speed # Common Type Settings ability.radius=Radius @@ -1735,9 +1740,13 @@ ability.firingSpeed=Firing Speed ability.amount=Amount ability.dualCharge=Dual Charging -# Charge (no unique keys) +# Charge +ability.chargeSpeed=Charge Speed -# Dash (no unique keys) +# Dash +ability.dashMode=Dash Mode +ability.dashDistance=Dash Distance +ability.dashSpeed=Dash Speed # Teleport ability.blinkRadius=Blink Radius @@ -1758,6 +1767,7 @@ ability.pushStrength=Push Strength # Guard ability.damageReduction=Damage Reduction +ability.counterWindow=Counter Ticks # Heal ability.instantHeal=Instant Heal