Merge remote-tracking branch 'origin/dev' into new-abilities

This commit is contained in:
Vidal committed 2026-03-15 01:51:22 -03:00
commit fb3a392f2e
16 files changed
+261 -51

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
id "dts.typescript-generator"
}
version = "1.11"
version = "1.11.1"
group= "kamkeel.customnpc-plus"
archivesBaseName = "CustomNPC-Plus"
@@ -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
@@ -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());
}
/**
@@ -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));
+1 -1
View File
@@ -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")
+18 -5
View File
@@ -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;
@@ -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));
@@ -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<String> list;
private List<String> navList = new ArrayList<String>();
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<String> list = new ArrayList<String>();
this.list = new ArrayList<String>(ClientCloneController.Instance.getClones(activeTab));
if (activeFolder != null && ClientCloneController.Instance != null) {
this.list = new ArrayList<String>(ClientCloneController.Instance.getClones(activeFolder));
} else if (ClientCloneController.Instance != null) {
this.list = new ArrayList<String>(ClientCloneController.Instance.getClones(activeTab));
} else {
this.list = new ArrayList<String>();
}
scroll.setList(getSearchList());
}
@@ -97,6 +123,7 @@ public class GuiNpcMobSpawnerSelector extends SubGuiInterface implements IGuiDat
}
private List<String> getSearchList() {
if (list == null) list = new ArrayList<String>();
if (search.isEmpty())
return new ArrayList<String>(list);
List<String> list = new ArrayList<String>();
@@ -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();
}
}
@@ -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) {
@@ -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();
}
@@ -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) {
@@ -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"),
@@ -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);
@@ -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;
@@ -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;
@@ -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