diff --git a/build.gradle b/build.gradle index a76186d6d..3dfbb49ae 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id "dts.typescript-generator" } -version = "1.11" +version = "1.11.1" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" 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/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java index ac51ca92f..376c4ad4c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java @@ -10,9 +10,11 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; +import noppes.npcs.EventHooks; import noppes.npcs.api.ability.type.IAbilityDefend; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.controllers.data.Animation; +import noppes.npcs.scripted.event.AbilityEvent; import java.util.List; @@ -147,8 +149,14 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { } } + //inject defend event + AbilityEvent.DefendEvent event = new AbilityEvent.DefendEvent(caster, this, attacker, lastAttacker, amount); + if (EventHooks.onAbilityDefend(this, event)) { + return event.getDamage(); + } + // Always calculate the correct reduction for the caller's damage amount - return performDefend(attacker, amount); + return performDefend(attacker, event.getDamage()); } /** diff --git a/src/main/java/kamkeel/npcs/network/packets/request/jobs/JobSpawnerAddPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/jobs/JobSpawnerAddPacket.java index f6b7fa2dc..8143aeef9 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/jobs/JobSpawnerAddPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/jobs/JobSpawnerAddPacket.java @@ -28,6 +28,7 @@ public final class JobSpawnerAddPacket extends AbstractPacket { private String selected; private int activeTab; + private String folderName; private int slot; private NBTTagCompound compound; @@ -39,6 +40,15 @@ public final class JobSpawnerAddPacket extends AbstractPacket { this.isServer = isServer; this.selected = selected; this.activeTab = activeTab; + this.folderName = null; + this.slot = slot; + } + + public JobSpawnerAddPacket(boolean isServer, String selected, String folderName, int slot) { + this.isServer = isServer; + this.selected = selected; + this.activeTab = -1; + this.folderName = folderName; this.slot = slot; } @@ -75,6 +85,9 @@ public final class JobSpawnerAddPacket extends AbstractPacket { if (this.isServer) { ByteBufUtils.writeString(out, selected); out.writeInt(this.activeTab); + if (this.activeTab == -1) { + ByteBufUtils.writeString(out, folderName != null ? folderName : ""); + } out.writeInt(this.slot); } else { out.writeInt(this.slot); @@ -96,7 +109,15 @@ public final class JobSpawnerAddPacket extends AbstractPacket { JobSpawner job = (JobSpawner) npc.jobInterface; boolean useServerClone = in.readBoolean(); if (useServerClone) { - NBTTagCompound compound = ServerCloneController.Instance.getCloneData(null, ByteBufUtils.readString(in), in.readInt()); + String selected = ByteBufUtils.readString(in); + int tab = in.readInt(); + NBTTagCompound compound; + if (tab == -1) { + String folder = ByteBufUtils.readString(in); + compound = ServerCloneController.Instance.getCloneData(null, selected, folder); + } else { + compound = ServerCloneController.Instance.getCloneData(null, selected, tab); + } job.setJobCompound(in.readInt(), compound); } else { job.setJobCompound(in.readInt(), ByteBufUtils.readNBT(in)); diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 52812b2aa..17d935d9f 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -115,7 +115,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11.1") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy") diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 74b8b057f..7772f6e22 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -199,11 +199,19 @@ public class DataAbilities extends AbstractDataAbilities { @Override protected void rollChainCooldown(ChainedAbility chain) { - int baseCooldown = minCooldown; - if (maxCooldown > minCooldown) { - baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1); + if (!chain.isIgnoreCooldown()) { + int baseCooldown = minCooldown; + if (maxCooldown > minCooldown) { + baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1); + } + cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown + chain.getCooldownTicks(); + } + + // Additionally set per-ability cooldown if enabled + if (chain.isPerAbilityCooldown() && chain.getCooldownTicks() > 0) { + long endTime = npc.worldObj.getTotalWorldTime() + chain.getCooldownTicks(); + setPerAbilityCooldown(chain.getName(), endTime, chain.getCooldownTicks()); } - cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown + chain.getCooldownTicks(); } @Override @@ -471,12 +479,17 @@ public class DataAbilities extends AbstractDataAbilities { return false; } - // Per-ability cooldown filter: ability may have its own independent cooldown + // Per-ability cooldown filter: ability/chain may have its own independent cooldown if (!action.isChain()) { Ability ab = (Ability) action; if (ab.isPerAbilityCooldown() && isOnPerAbilityCooldown(ab.getName())) { return false; } + } else { + ChainedAbility ch = (ChainedAbility) action; + if (ch.isPerAbilityCooldown() && isOnPerAbilityCooldown(ch.getName())) { + return false; + } } return true; diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index e07facff0..3507a3c04 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -975,6 +975,11 @@ public class EventHooks { return postAbilityEvent(ability, event); } + public static boolean onAbilityDefend(Ability ability, AbilityEvent.DefendEvent event) { + if (ability == null || event == null) return false; + return postAbilityEvent(ability, event); + } + public static boolean onAbilityToggle(Ability ability, EntityLivingBase entity, int oldState, int newState) { if (ability == null || entity == null) return false; return postAbilityEvent(ability, new AbilityEvent.ToggleEvent(entity, ability, oldState, newState)); diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerSelector.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerSelector.java index 9c13af815..6762c5384 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerSelector.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerSelector.java @@ -8,22 +8,26 @@ import net.minecraft.nbt.NBTTagDouble; import net.minecraft.nbt.NBTTagList; import noppes.npcs.client.controllers.ClientCloneController; import noppes.npcs.client.gui.util.GuiCustomScroll; -import noppes.npcs.client.gui.util.GuiMenuSideButton; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.data.CloneFolder; import java.util.ArrayList; import java.util.List; -public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiData { +public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiData, ICustomScrollListener { private GuiCustomScroll scroll; + private GuiCustomScroll navScroll; private List list; + private List navList = new ArrayList(); private static String search = ""; public int activeTab = 1; + public String activeFolder = null; public boolean isServer = false; public GuiNpcMobSpawnerSelector() { @@ -50,40 +54,62 @@ public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiDat addButton(new GuiNpcButton(0, guiLeft + 171, guiTop + 80, 80, 20, "gui.done")); addButton(new GuiNpcButton(1, guiLeft + 171, guiTop + 103, 80, 20, "gui.cancel")); - addSideButton(new GuiMenuSideButton(21, guiLeft - 90, this.guiTop + 2, 90, 22, "1")); - addSideButton(new GuiMenuSideButton(22, guiLeft - 90, this.guiTop + 23, 90, 22, "2")); - addSideButton(new GuiMenuSideButton(23, guiLeft - 90, this.guiTop + 44, 90, 22, "3")); - addSideButton(new GuiMenuSideButton(24, guiLeft - 90, this.guiTop + 65, 90, 22, "4")); - addSideButton(new GuiMenuSideButton(25, guiLeft - 90, this.guiTop + 86, 90, 22, "5")); - addSideButton(new GuiMenuSideButton(26, guiLeft - 45, this.guiTop + 107, 45, 22, "6")); - addSideButton(new GuiMenuSideButton(27, guiLeft - 90, this.guiTop + 107, 45, 22, "7")); - addSideButton(new GuiMenuSideButton(28, guiLeft - 45, this.guiTop + 128, 45, 22, "8")); - addSideButton(new GuiMenuSideButton(29, guiLeft - 90, this.guiTop + 128, 45, 22, "9")); - addSideButton(new GuiMenuSideButton(30, guiLeft - 45, this.guiTop + 149, 45, 22, "10")); - addSideButton(new GuiMenuSideButton(31, guiLeft - 90, this.guiTop + 149, 45, 22, "11")); - addSideButton(new GuiMenuSideButton(32, guiLeft - 45, this.guiTop + 170, 45, 22, "12")); - addSideButton(new GuiMenuSideButton(33, guiLeft - 90, this.guiTop + 170, 45, 22, "13")); - addSideButton(new GuiMenuSideButton(34, guiLeft - 45, this.guiTop + 191, 45, 22, "14")); - addSideButton(new GuiMenuSideButton(35, guiLeft - 90, this.guiTop + 191, 45, 22, "15")); + // Navigation scroll (tabs + folders) replacing hardcoded side buttons + buildNavList(); + if (navScroll == null) { + navScroll = new GuiCustomScroll(this, 1); + } else { + navScroll.clear(); + } + navScroll.setSize(90, 210); + navScroll.guiLeft = guiLeft - 94; + navScroll.guiTop = guiTop + 2; + navScroll.setList(navList); + navScroll.setSelected(getNavSelection()); + addScroll(navScroll); - getSideButton(20 + activeTab).active = true; showClones(); } + private void buildNavList() { + navList.clear(); + for (int i = 1; i <= 15; i++) { + navList.add("Tab " + i); + } + if (ClientCloneController.Instance != null) { + for (CloneFolder folder : ClientCloneController.Instance.getFolderList()) { + navList.add(folder.name); + } + } + } + + private String getNavSelection() { + if (activeFolder != null) return activeFolder; + if (activeTab >= 1 && activeTab <= 15) return "Tab " + activeTab; + return "Tab 1"; + } + public String getSelected() { return scroll.getSelected(); } private void showClones() { - if (isServer) { - PacketClient.sendClient(new CloneListPacket(activeTab)); + if (activeFolder != null) { + PacketClient.sendClient(new CloneListPacket(activeFolder)); + } else { + PacketClient.sendClient(new CloneListPacket(activeTab)); + } return; } - ArrayList list = new ArrayList(); - - this.list = new ArrayList(ClientCloneController.Instance.getClones(activeTab)); + if (activeFolder != null && ClientCloneController.Instance != null) { + this.list = new ArrayList(ClientCloneController.Instance.getClones(activeFolder)); + } else if (ClientCloneController.Instance != null) { + this.list = new ArrayList(ClientCloneController.Instance.getClones(activeTab)); + } else { + this.list = new ArrayList(); + } scroll.setList(getSearchList()); } @@ -97,6 +123,7 @@ public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiDat } private List getSearchList() { + if (list == null) list = new ArrayList(); if (search.isEmpty()) return new ArrayList(list); List list = new ArrayList(); @@ -112,8 +139,15 @@ public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiDat if (sel == null) return null; - NBTTagCompound compound = ClientCloneController.Instance.getCloneData(player, sel, activeTab); - compound.setString("ClonedName", sel); + NBTTagCompound compound; + if (activeFolder != null && ClientCloneController.Instance != null) { + compound = ClientCloneController.Instance.getCloneData(player, sel, activeFolder); + } else { + compound = ClientCloneController.Instance.getCloneData(player, sel, activeTab); + } + if (compound != null) { + compound.setString("ClonedName", sel); + } return compound; } @@ -126,9 +160,28 @@ public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiDat scroll.clear(); close(); } - if (id > 20) { - activeTab = id - 20; - initGui(); + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 1) { + String selected = navScroll.getSelected(); + if (selected == null) return; + + if (selected.startsWith("Tab ")) { + try { + int tabNum = Integer.parseInt(selected.substring(4)); + if (tabNum >= 1 && tabNum <= 15) { + activeTab = tabNum; + activeFolder = null; + } + } catch (NumberFormatException ignored) { + } + } else { + activeFolder = selected; + activeTab = -1; + } + showClones(); } } 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/client/gui/roles/GuiNpcSpawner.java b/src/main/java/noppes/npcs/client/gui/roles/GuiNpcSpawner.java index d57dd417c..5c6140d8b 100644 --- a/src/main/java/noppes/npcs/client/gui/roles/GuiNpcSpawner.java +++ b/src/main/java/noppes/npcs/client/gui/roles/GuiNpcSpawner.java @@ -184,8 +184,13 @@ public class GuiNpcSpawner extends GuiNPCInterface2 implements ITextfieldListene GuiNpcMobSpawnerSelector selector = (GuiNpcMobSpawnerSelector) gui; if (selector.isServer) { String selected = selector.getSelected(); - if (selected != null) - PacketClient.sendClient(new JobSpawnerAddPacket(selector.isServer, selected, selector.activeTab, slot)); + if (selected != null) { + if (selector.activeFolder != null) { + PacketClient.sendClient(new JobSpawnerAddPacket(selector.isServer, selected, selector.activeFolder, slot)); + } else { + PacketClient.sendClient(new JobSpawnerAddPacket(selector.isServer, selected, selector.activeTab, slot)); + } + } } else { NBTTagCompound compound = selector.getCompound(); if (compound != null) { diff --git a/src/main/java/noppes/npcs/constants/EnumScriptType.java b/src/main/java/noppes/npcs/constants/EnumScriptType.java index f6d05d65b..0de53db10 100644 --- a/src/main/java/noppes/npcs/constants/EnumScriptType.java +++ b/src/main/java/noppes/npcs/constants/EnumScriptType.java @@ -125,6 +125,7 @@ public enum EnumScriptType { ABILITY_INTERRUPT("abilityInterrupt"), ABILITY_COMPLETE("abilityComplete"), ABILITY_HIT("abilityHit"), + ABILITY_DEFEND("abilityDefend"), ABILITY_TICK("abilityTick"), ABILITY_TOGGLE("abilityToggle"), ABILITY_TOGGLE_TICK("abilityToggleTick"), diff --git a/src/main/java/noppes/npcs/controllers/ScriptHookController.java b/src/main/java/noppes/npcs/controllers/ScriptHookController.java index 5547fb203..332d2f1e9 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptHookController.java +++ b/src/main/java/noppes/npcs/controllers/ScriptHookController.java @@ -124,6 +124,7 @@ public class ScriptHookController implements IScriptHookHandler { hook(NPC, ABILITY_START, IAbilityEvent.StartEvent.class); hook(NPC, ABILITY_EXECUTE, IAbilityEvent.ExecuteEvent.class); hook(NPC, ABILITY_HIT, IAbilityEvent.HitEvent.class); + hook(NPC, ABILITY_DEFEND, IAbilityEvent.DefendEvent.class); hook(NPC, ABILITY_TICK, IAbilityEvent.TickEvent.class); hook(NPC, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class); hook(NPC, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class); @@ -259,6 +260,7 @@ public class ScriptHookController implements IScriptHookHandler { hook(PLAYER, ABILITY_START, IAbilityEvent.StartEvent.class); hook(PLAYER, ABILITY_EXECUTE, IAbilityEvent.ExecuteEvent.class); hook(PLAYER, ABILITY_HIT, IAbilityEvent.HitEvent.class); + hook(PLAYER, ABILITY_DEFEND, IAbilityEvent.DefendEvent.class); hook(PLAYER, ABILITY_TICK, IAbilityEvent.TickEvent.class); hook(PLAYER, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class); hook(PLAYER, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class); @@ -357,6 +359,7 @@ public class ScriptHookController implements IScriptHookHandler { hook(ABILITY, ABILITY_START, IAbilityEvent.StartEvent.class); hook(ABILITY, ABILITY_EXECUTE, IAbilityEvent.ExecuteEvent.class); hook(ABILITY, ABILITY_HIT, IAbilityEvent.HitEvent.class); + hook(ABILITY, ABILITY_DEFEND, IAbilityEvent.DefendEvent.class); hook(ABILITY, ABILITY_TICK, IAbilityEvent.TickEvent.class); hook(ABILITY, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class); hook(ABILITY, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class); 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/java/noppes/npcs/scripted/event/AbilityEvent.java b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java index a5d5c5bf4..f71cb8b2a 100644 --- a/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java @@ -214,6 +214,46 @@ public class AbilityEvent extends CustomNPCsEvent implements IAbilityEvent { } } + @Cancelable + public static class DefendEvent extends AbilityEvent implements IAbilityEvent.DefendEvent { + private final IEntityLivingBase attacker; + private final IEntityLivingBase lastAttacker; + private float damage; + + public DefendEvent(EntityLivingBase entity, Ability ability, EntityLivingBase attacker, + EntityLivingBase lastAttacker, float damage) { + super(entity, ability, null); + this.attacker = attacker != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(attacker) : null; + this.lastAttacker = lastAttacker != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(lastAttacker) : null; + this.damage = damage; + } + + @Override + public IEntityLivingBase getAttacker() { + return attacker; + } + + @Override + public IEntityLivingBase getLastAttacker() { + return lastAttacker; + } + + @Override + public float getDamage() { + return damage; + } + + @Override + public void setDamage(float damage) { + this.damage = damage; + } + + @Override + public String getHookName() { + return EnumScriptType.ABILITY_DEFEND.function; + } + } + @Cancelable public static class ToggleEvent extends AbilityEvent implements IAbilityEvent.ToggleEvent { private final int oldState; 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