diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java index bdf95508f..dce3e274e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java @@ -51,6 +51,16 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { */ private int cooldownTicks = 100; + /** + * If true, only this chain goes on cooldown (not the global cooldown). + */ + private boolean perAbilityCooldown = false; + + /** + * If true, skip cooldown entirely after chain completes. + */ + private boolean ignoreCooldown = false; + /** * Minimum range for NPC eligibility check. */ @@ -159,6 +169,22 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { this.cooldownTicks = Math.max(0, cooldownTicks); } + public boolean isPerAbilityCooldown() { + return perAbilityCooldown; + } + + public void setPerAbilityCooldown(boolean perAbilityCooldown) { + this.perAbilityCooldown = perAbilityCooldown; + } + + public boolean isIgnoreCooldown() { + return ignoreCooldown; + } + + public void setIgnoreCooldown(boolean ignoreCooldown) { + this.ignoreCooldown = ignoreCooldown; + } + public float getMinRange() { return minRange; } @@ -402,6 +428,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { nbt.setInteger("Weight", weight); nbt.setBoolean("WindUpAll", windUpAll); nbt.setInteger("CooldownTicks", cooldownTicks); + nbt.setBoolean("PerAbilityCooldown", perAbilityCooldown); + nbt.setBoolean("IgnoreCooldown", ignoreCooldown); nbt.setFloat("MinRange", minRange); nbt.setFloat("MaxRange", maxRange); @@ -443,6 +471,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { weight = nbt.hasKey("Weight") ? nbt.getInteger("Weight") : 10; windUpAll = !nbt.hasKey("WindUpAll") || nbt.getBoolean("WindUpAll"); cooldownTicks = nbt.hasKey("CooldownTicks") ? nbt.getInteger("CooldownTicks") : 100; + perAbilityCooldown = nbt.getBoolean("PerAbilityCooldown"); + ignoreCooldown = nbt.getBoolean("IgnoreCooldown"); minRange = nbt.getFloat("MinRange"); maxRange = nbt.hasKey("MaxRange") ? nbt.getFloat("MaxRange") : 20; // AllowedBy is computed from child abilities diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index 4e271893c..2ffec82c9 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -33,6 +33,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.BooleanSupplier; import static kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition.MAX_CONDITIONS; @@ -153,7 +154,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText fieldDefs.add(FieldDef.boolField("gui.enabled", chain::isEnabled, chain::setEnabled).tab("General")); fieldDefs.add(FieldDef.intField("ability.weight", chain::getWeight, chain::setWeight).range(1, 100).tab("General")); fieldDefs.add(FieldDef.boolField("ability.windUpAll", chain::isWindUpAll, chain::setWindUpAll).tab("General")); - fieldDefs.add(FieldDef.intField("ability.cooldown", chain::getCooldownTicks, chain::setCooldownTicks).range(0, 12000).tab("General")); + fieldDefs.add(FieldDef.row( + FieldDef.intField("ability.cooldown", chain::getCooldownTicks, chain::setCooldownTicks).range(0, 12000), + FieldDef.boolField("ability.perAbilityCooldown", chain::isPerAbilityCooldown, chain::setPerAbilityCooldown) + ).tab("General")); + fieldDefs.add(FieldDef.boolField("ability.ignoreCooldown", chain::isIgnoreCooldown, chain::setIgnoreCooldown).tab("General")); // ── Target tab ─────────────────────────────────────────────── fieldDefs.add(FieldDef.row( @@ -163,16 +168,20 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // ── Icon tab ────────────────────────────────────────────── AbilityIconData chainIcon = AbilityIconData.fromChainedAbility(chain); + BooleanSupplier isCustomIcon = chainIcon::isEnabled; + + fieldDefs.add(FieldDef.section("ability.section.icon").tab("Icon")); + fieldDefs.add(FieldDef.boolField("ability.hasIcon", chainIcon::isEnabled, chainIcon::setEnabled).tab("Icon")); fieldDefs.add(FieldDef.row( FieldDef.intField("gui.width", chainIcon::getWidth, chainIcon::setWidth).range(1, 256), FieldDef.intField("gui.height", chainIcon::getHeight, chainIcon::setHeight).range(1, 256) - ).tab("Icon")); - fieldDefs.add(FieldDef.floatField("gui.scale", chainIcon::getScale, chainIcon::setScale).tab("Icon").range(0.1f, 10.0f)); + ).tab("Icon").visibleWhen(isCustomIcon)); + fieldDefs.add(FieldDef.floatField("gui.scale", chainIcon::getScale, chainIcon::setScale).tab("Icon").range(0.1f, 10.0f).visibleWhen(isCustomIcon)); fieldDefs.add(FieldDef.intField("ability.icon.layers", chainIcon::getLayerCount, chainIcon::setLayerCount) - .tab("Icon").range(1, AbilityIconData.MAX_LAYERS)); + .tab("Icon").range(1, AbilityIconData.MAX_LAYERS).visibleWhen(isCustomIcon)); for (int i = 0; i < AbilityIconData.MAX_LAYERS; i++) { final int idx = i; - java.util.function.BooleanSupplier layerVisible = () -> chainIcon.getLayerCount() > idx; + java.util.function.BooleanSupplier layerVisible = () -> isCustomIcon.getAsBoolean() && chainIcon.getLayerCount() > idx; String sectionLabel = net.minecraft.util.StatCollector.translateToLocal("ability.icon.layer") + " " + (idx + 1); fieldDefs.add(FieldDef.section(sectionLabel).tab("Icon").visibleWhen(layerVisible)); fieldDefs.add(FieldDef.textureSubGui("gui.texture", @@ -194,12 +203,12 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } // Animation - fieldDefs.add(FieldDef.section("Animation").tab("Icon")); - fieldDefs.add(FieldDef.boolField("gui.animated", chainIcon::isAnimated, chainIcon::setAnimated).tab("Icon").hover("gui.animated.hover")); + fieldDefs.add(FieldDef.section("Animation").tab("Icon").visibleWhen(isCustomIcon)); + fieldDefs.add(FieldDef.boolField("gui.animated", chainIcon::isAnimated, chainIcon::setAnimated).tab("Icon").hover("gui.animated.hover").visibleWhen(isCustomIcon)); fieldDefs.add(FieldDef.intField("gui.frameCount", chainIcon::getFrameCount, chainIcon::setFrameCount) - .tab("Icon").range(1, 256).visibleWhen(chainIcon::isAnimated)); + .tab("Icon").range(1, 256).visibleWhen(() -> isCustomIcon.getAsBoolean() && chainIcon.isAnimated())); fieldDefs.add(FieldDef.intField("gui.frameTime", chainIcon::getFrameTime, chainIcon::setFrameTime) - .tab("Icon").range(1, 100).visibleWhen(chainIcon::isAnimated)); + .tab("Icon").range(1, 100).visibleWhen(() -> isCustomIcon.getAsBoolean() && chainIcon.isAnimated())); // External field providers (e.g., DBC Addon injecting a "DBC" tab) if (AbilityController.Instance != null) { diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index 201abfcd0..974224960 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; @@ -638,9 +639,12 @@ public class AbilityHotbarComponent extends HudComponent { String key = slots[slotIndex].abilityKey; Ability ability = slots[slotIndex].ability; + IAbilityAction action = slots[slotIndex].action; if (ability != null && ability.isPerAbilityCooldown()) { return playerData.abilityData.getPerAbilityCooldownProgress(key); + } else if (action instanceof ChainedAbility && ((ChainedAbility) action).isPerAbilityCooldown()) { + return playerData.abilityData.getPerAbilityCooldownProgress(key); } else { return playerData.abilityData.getGlobalCooldownProgress(); } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 35d6c4239..916f2949a 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -237,9 +237,19 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA @Override protected void rollChainCooldown(ChainedAbility chain) { + if (chain.isIgnoreCooldown()) return; + int duration = chain.getCooldownTicks(); - cooldownEndTime = getWorldTime() + duration; - globalCooldownDuration = duration; + long endTime = getWorldTime() + duration; + + if (chain.isPerAbilityCooldown()) { + if (currentAbilityKey != null) { + setPerAbilityCooldown(currentAbilityKey, endTime, duration); + } + } else { + cooldownEndTime = endTime; + globalCooldownDuration = duration; + } } @Override @@ -403,7 +413,14 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Dispatch: chain vs ability if (action.isChain()) { - if (isOnCooldown()) return false; + ChainedAbility chain = (ChainedAbility) action; + if (!chain.isIgnoreCooldown()) { + if (chain.isPerAbilityCooldown()) { + if (isOnPerAbilityCooldown(key)) return false; + } else { + if (isOnCooldown()) return false; + } + } currentAbilityKey = key; currentTarget = null; diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 5eb898151..3f46be5a3 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1736,6 +1736,7 @@ ability.weight=Weight ability.windUpTicks=Wind Up ability.cooldownTicks=Cooldown ability.perAbilityCooldown=Per-Ability +ability.ignoreCooldown=Ignore Cooldown ability.freeOnCast=Free on Cast ability.hover.freeOnCast=Ability enters cooldown immediately after entities are spawned, allowing the caster to use other abilities while summoned entities remain active ability.freeAim=Free Aim