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