This commit is contained in:
Kam committed 2026-02-21 01:10:06 -05:00
commit 99de894d3f
14 files changed
+495 -233

No files matched your search

@@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.data.ability.*;
import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition;
import kamkeel.npcs.controllers.data.ability.conditions.ConditionHPThreshold;
import kamkeel.npcs.controllers.data.ability.conditions.ConditionHitCount;
import kamkeel.npcs.controllers.data.ability.conditions.ConditionItem;
import kamkeel.npcs.controllers.data.ability.type.AbilityCharge;
import kamkeel.npcs.controllers.data.ability.type.AbilityCutter;
import kamkeel.npcs.controllers.data.ability.type.AbilityDash;
@@ -152,6 +153,7 @@ public class AbilityController implements IAbilityHandler {
private void registerBuiltinConditionTypes() {
registerCondition(ConditionHPThreshold::new);
registerCondition(ConditionHitCount::new);
registerCondition(ConditionItem::new);
}
// ═══════════════════════════════════════════════════════════════════════════
@@ -14,6 +14,8 @@ import java.util.List;
import java.util.function.Supplier;
public abstract class AbilityCondition {
public static final int MAX_CONDITIONS = 5;
protected String typeId = "";
protected String name = "";
protected UserType userType = UserType.BOTH;
@@ -69,7 +71,7 @@ public abstract class AbilityCondition {
nbt.setString("typeId", getTypeId());
nbt.setString("name", getName());
nbt.setInteger("userType", getUserType().ordinal());
nbt.setInteger("filter", getUserType().ordinal());
nbt.setInteger("filter", getFilter().ordinal());
writeTypeNBT(nbt);
return nbt;
}
@@ -8,19 +8,26 @@ import java.util.List;
public class ConditionHPThreshold extends AbilityCondition{
private float threshold = 0.5f; // 50%
private boolean percent = true;
private ThresholdType thresholdType = ThresholdType.ABOVE;
public enum ThresholdType {
ABOVE {
@Override
public boolean test(float value, float threshold) {
return value >= threshold;
return value > threshold;
}
},
BELOW {
@Override
public boolean test(float value, float threshold) {
return value <= threshold;
return value < threshold;
}
},
EQUAL {
@Override
public boolean test(float value, float threshold) {
return value == threshold;
}
};
@@ -41,6 +48,8 @@ public class ConditionHPThreshold extends AbilityCondition{
return "condition.hp_above";
case BELOW:
return "condition.hp_below";
case EQUAL:
return "condition.hp_equal";
default:
return name();
}
@@ -54,8 +63,8 @@ public class ConditionHPThreshold extends AbilityCondition{
@Override
public boolean check(EntityLivingBase caster, EntityLivingBase target) {
float casterHP = caster.getHealth() / caster.getMaxHealth();
float targetHP = target.getHealth() / target.getMaxHealth();
float casterHP = isPercent() ? caster.getHealth() / caster.getMaxHealth() : caster.getHealth();
float targetHP = isPercent() ? target.getHealth() / target.getMaxHealth() : target.getHealth();
return compare(casterHP, targetHP);
@@ -75,6 +84,24 @@ public class ConditionHPThreshold extends AbilityCondition{
}
}
@Override
public void getConditionDefinitions(List<FieldDef> defs) {
defs.add(FieldDef.floatField("condition.threshold", this::getThreshold, this::setThreshold).min(0));
defs.add(FieldDef.boolField("condition.percent", this::isPercent, this::setIsPercent));
defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class,
this::getThresholdType, this::setThresholdType));
}
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
nbt.setFloat("threshold", threshold);
}
@Override
public void readTypeNBT(NBTTagCompound nbt) {
threshold = nbt.getFloat("threshold");
}
public float getThreshold() {
return threshold;
}
@@ -91,20 +118,11 @@ public class ConditionHPThreshold extends AbilityCondition{
this.thresholdType = thresholdType;
}
@Override
public void getConditionDefinitions(List<FieldDef> defs) {
defs.add(FieldDef.floatField("condition.threshold", this::getThreshold, this::setThreshold).min(0));
defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class,
this::getThresholdType, this::setThresholdType));
public boolean isPercent() {
return percent;
}
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
nbt.setFloat("threshold", threshold);
}
@Override
public void readTypeNBT(NBTTagCompound nbt) {
threshold = nbt.getFloat("threshold");
public void setIsPercent(boolean percent) {
this.percent = percent;
}
}
@@ -0,0 +1,201 @@
package kamkeel.npcs.controllers.data.ability.conditions;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.List;
public class ConditionItem extends AbilityCondition {
public enum UsageType {
ARMOR, HOLDING, AMMO, OFFHAND;
public static UsageType fromOrdinal(int ordinal) {
UsageType[] values = values();
return (ordinal >= 0 && ordinal < values.length) ? values[ordinal] : HOLDING;
}
}
public enum ArmorSlot {
BOOTS, LEGS, CHEST, HELMET;
public static ArmorSlot fromOrdinal(int ordinal) {
ArmorSlot[] values = values();
return (ordinal >= 0 && ordinal < values.length) ? values[ordinal] : BOOTS;
}
}
// Shared
private UsageType usageType = UsageType.HOLDING;
private String itemName = "";
// ARMOR only
private ArmorSlot armorSlot = ArmorSlot.BOOTS;
private boolean fullArmorSet = false;
// AMMO + PLAYER only
private int requiredCount = 1;
public ConditionItem() {
this.typeId = "condition.cnpc.item";
this.name = "condition.item";
}
@Override
public boolean check(EntityLivingBase caster, EntityLivingBase target) {
switch (getFilter()) {
case CASTER:
return checkEntity(caster);
case TARGET:
return target != null && checkEntity(target);
case BOTH:
return checkEntity(caster) && (target != null && checkEntity(target));
default:
return checkEntity(caster);
}
}
private boolean checkEntity(EntityLivingBase entity) {
switch (usageType) {
case HOLDING:
return checkHolding(entity);
case ARMOR:
return checkArmor(entity);
case AMMO:
return checkAmmo(entity);
case OFFHAND:
return checkOffhand(entity);
default:
return false;
}
}
private boolean checkHolding(EntityLivingBase entity) {
ItemStack held = entity.getHeldItem();
return matchesItem(held);
}
private boolean checkArmor(EntityLivingBase entity) {
// MC getEquipmentInSlot: 1=boots, 2=legs, 3=chest, 4=helmet
int slot = armorSlot.ordinal() + 1; // BOOTS=0, LEGS=1, CHEST=2, HELMET=3
return matchesItem(entity.getEquipmentInSlot(slot));
}
private boolean checkFullArmor(EntityLivingBase entity) {
for (int i = 0; i < 4; i++) {
int slot = i + 1;
if (!matchesItem(i, entity.getEquipmentInSlot(slot)))
return false;
}
return true;
}
private boolean checkAmmo(EntityLivingBase entity) {
if (entity instanceof EntityPlayer) {
return checkPlayerInventoryCount((EntityPlayer) entity);
}
if (entity instanceof EntityNPCInterface) {
return checkNPCProjectile((EntityNPCInterface) entity);
}
return false;
}
private boolean checkOffhand(EntityLivingBase entity) {
// No offhand for players
if (entity instanceof EntityPlayer) return false;
ItemStack item = ((EntityNPCInterface) entity).getOffHand();
return matchesItem(item);
}
private boolean checkPlayerInventoryCount(EntityPlayer player) {
int count = 0;
for (ItemStack stack : player.inventory.mainInventory) {
if (matchesItem(stack)) {
count += stack.stackSize;
}
}
return count >= requiredCount;
}
private boolean checkNPCProjectile(EntityNPCInterface npc) {
ItemStack projectile = npc.inventory.getProjectile();
return matchesItem(projectile);
}
private boolean matchesItem(ItemStack stack) {
return matchesItem(0, stack);
}
private boolean matchesItem(int index, ItemStack stack) {
if (stack == null || itemName == null || itemName.isEmpty()) return false;
String registryName = net.minecraft.item.Item.itemRegistry.getNameForObject(stack.getItem());
return itemName.equals(registryName);
}
@Override
public void getConditionDefinitions(List<FieldDef> defs) {
defs.add(FieldDef.enumField("condition.usage_type", UsageType.class,
this::getUsageType, this::setUsageType));
defs.add(FieldDef.stringField("condition.item_name",
this::getItemName, this::setItemName));
// .hover("condition.hover.item_name"));
defs.add(FieldDef.enumField("condition.armor_slot", ArmorSlot.class,
this::getArmorSlot, this::setArmorSlot)
.visibleWhen(() -> usageType == UsageType.ARMOR));
// .hover("condition.hover.armor_slots"));
defs.add(FieldDef.intField("condition.item_count",
this::getRequiredCount, this::setRequiredCount)
.range(1, 64)
.visibleWhen(() -> usageType == UsageType.AMMO && userType.allowsPlayer()));
// .hover("condition.hover.required_count"));
}
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
nbt.setString("itemName", itemName);
nbt.setInteger("usageType", usageType.ordinal());
nbt.setInteger("armorSlot", armorSlot.ordinal());
nbt.setInteger("requiredCount", requiredCount);
}
@Override
public void readTypeNBT(NBTTagCompound nbt) {
itemName = nbt.getString("itemName");
usageType = UsageType.fromOrdinal(nbt.getInteger("usageType"));
armorSlot = ArmorSlot.fromOrdinal(nbt.getInteger("armorSlot"));
requiredCount = nbt.getInteger("requiredCount");
}
public UsageType getUsageType() { return usageType; }
public void setUsageType(UsageType usageType) { this.usageType = usageType; }
public String getItemName() { return itemName; }
public void setItemName(String itemName) { this.itemName = itemName; }
public ArmorSlot getArmorSlot() { return armorSlot; }
public void setArmorSlot(ArmorSlot armorSlot) { this.armorSlot = armorSlot; }
public boolean isFullArmorSet() {
return fullArmorSet;
}
public void setFullArmorSet(boolean fullArmorSet) {
this.fullArmorSet = fullArmorSet;
}
public int getRequiredCount() { return requiredCount; }
public void setRequiredCount(int requiredCount) { this.requiredCount = Math.max(1, requiredCount); }
}
@@ -52,7 +52,7 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge {
this.windUpSound = "mob.zombie.wood";
this.activeSound = "mob.zombie.attack";
this.windUpAnimationName = "Ability_Charge_Windup";
this.activeAnimationName = "Ability_Active_Windup";
this.activeAnimationName = "Ability_Charge_Active";
}
@Override
@@ -48,8 +48,8 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
this.cooldownTicks = 0;
this.windUpTicks = 40;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.rotationMode = RotationMode.TRACK;
this.rotationPhase = LockMovementType.WINDUP_AND_ACTIVE;
this.rotationMode = RotationMode.LOCKED;
this.rotationPhase = LockMovementType.ACTIVE;
this.telegraphType = TelegraphType.CIRCLE;
this.showTelegraph = true;
this.windUpAnimationName = "Ability_Beam_Windup";
@@ -115,6 +115,9 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
orb.setDamage(4.0f);
orb.setKnockback(0.0f);
orb.setKnockbackUp(0.0f);
orb.setProjectileCount(2);
orb.setFireDelay(2);
a.setLockMovement(LockMovementType.WINDUP_AND_ACTIVE);
a.setMaxRange(75.0f);
a.setBurstEnabled(true);
a.setBurstAmount(15);
@@ -20,6 +20,8 @@ import noppes.npcs.client.gui.util.SubGuiInterface;
import java.util.ArrayList;
import java.util.List;
import static kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition.MAX_CONDITIONS;
public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener {
private static final int DECLARATIVE_ID_START = 1000;
@@ -42,6 +44,12 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
private static final int L_LABEL_X = 5;
private static final int ROW_H = 24;
// Condition buttons
private static final int COND_BASE = 50;
private static final int COND_STRIDE = 10;
private static final int COND_END = COND_BASE + MAX_CONDITIONS * COND_STRIDE; // 50 + N*10
private static final int BTN_ADD_COND = COND_END;
private final Ability ability;
private final IAbilityConfigCallback callback;
private int activeTab = TAB_GENERAL;
@@ -234,17 +242,17 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
sw.addLabel(new GuiNpcLabel(labelCounter, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55));
y += 15;
for (int i = 0; i < conditions.size() && i < 3; i++) {
for (int i = 0; i < conditions.size() && i < MAX_CONDITIONS; i++) {
AbilityCondition cond = conditions.get(i);
String condName = getConditionDisplayName(cond);
sw.addButton(new GuiNpcButton(50 + i * 10, L_LABEL_X, y, 140, 20, condName));
sw.addButton(new GuiNpcButton(51 + i * 10, L_LABEL_X + 145, y, 40, 20, "gui.edit"));
sw.addButton(new GuiNpcButton(52 + i * 10, L_LABEL_X + 190, y, 20, 20, "X"));
sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName));
sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 1, L_LABEL_X + 145, y, 40, 20, "gui.edit"));
sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 2, L_LABEL_X + 190, y, 20, 20, "X"));
y += 22;
}
if (conditions.size() < 3) {
sw.addButton(new GuiNpcButton(80, L_LABEL_X, y, 50, 20, "gui.add"));
if (conditions.size() < MAX_CONDITIONS) {
sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));;
y += ROW_H;
}
@@ -312,9 +320,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
}
private boolean handleConditionButton(int id) {
if (id >= 50 && id < 80) {
int condIndex = (id - 50) / 10;
int action = (id - 50) % 10;
if (id >= COND_BASE && id < COND_END) {
int condIndex = (id - COND_BASE) / COND_STRIDE;
int action = (id - COND_BASE) % COND_STRIDE;
if (action == 0 || action == 1) {
if (condIndex < conditions.size()) {
editingConditionIndex = condIndex;
@@ -328,8 +336,8 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
}
return true;
}
if (id == 80) {
if (conditions.size() < 3) {
if (id == BTN_ADD_COND) {
if (conditions.size() < MAX_CONDITIONS) {
editingConditionIndex = conditions.size();
setSubGui(new SubGuiConditionEdit(null));
}
@@ -32,6 +32,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition.MAX_CONDITIONS;
/**
* SubGui for editing a {@link ChainedAbility}.
@@ -70,9 +71,9 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
// Actual ID = COND_BASE + i * COND_STRIDE + offset
// offset 0 = name/click, 1 = edit, 2 = delete
private static final int COND_BASE = 50;
private static final int COND_END = 80;
private static final int COND_STRIDE = 10;
private static final int BTN_ADD_COND = 80;
private static final int COND_END = COND_BASE + MAX_CONDITIONS * COND_STRIDE; // 50 + N*10
private static final int BTN_ADD_COND = COND_END;
// ── Layout constants ──────────────────────────────────────────────────────
private static final int L_LABEL_X = 5;
@@ -427,7 +428,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
sw.addLabel(new GuiNpcLabel(labelCounter, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55));
y += 15;
for (int i = 0; i < conditions.size() && i < 3; i++) {
for (int i = 0; i < conditions.size() && i < MAX_CONDITIONS; i++) {
AbilityCondition cond = conditions.get(i);
String condName = getConditionDisplayName(cond);
sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName));
@@ -436,7 +437,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
y += 22;
}
if (conditions.size() < 3) {
if (conditions.size() < MAX_CONDITIONS) {
sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));
y += ROW_H;
}
@@ -615,7 +616,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
return true;
}
if (id == BTN_ADD_COND) {
if (conditions.size() < 3) {
if (conditions.size() < MAX_CONDITIONS) {
editingConditionIndex = conditions.size();
setSubGui(new SubGuiConditionEdit(null));
}
@@ -23,6 +23,7 @@ import net.minecraft.util.DamageSource;
import noppes.npcs.AbstractDataAbilities;
import noppes.npcs.EventHooks;
import noppes.npcs.LogWriter;
import noppes.npcs.NoppesUtilPlayer;
import noppes.npcs.api.ability.IPlayerAbilityData;
import noppes.npcs.api.entity.IPlayer;
import noppes.npcs.controllers.ScriptController;
@@ -297,6 +298,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
// Apply rotation and position locks after ability tick
applyRotationControl(player);
applyPositionLock(player);
applyAnimationRotationSync(player);
// Sync lock state to client (only sends when flags change)
syncAbilityStateIfNeeded(player);
@@ -844,6 +846,17 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
}
}
private void applyAnimationRotationSync(EntityPlayer player) {
if (rotationLocked || currentAbility == null) return;
if (currentAbility.getPhase() == AbilityPhase.IDLE || currentAbility.getPhase() == AbilityPhase.DAZED)
return;
// TODO This is a temporary duct-tape solution to fix the animation not rotating with head rotation
if (!currentAbility.isRotationLockedForCurrentPhase() && player instanceof EntityPlayerMP) {
NoppesUtilPlayer.swingPlayerArm((EntityPlayerMP) player);
}
}
// ═══════════════════════════════════════════════════════════════════
// POSITION LOCKING (Player-specific application)
// ═══════════════════════════════════════════════════════════════════