From 5fe45f37d71d97cc652116b3db297c36b9c21464 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:31:50 +0200 Subject: [PATCH 01/15] perf: change the check order in `onSpellTarget` for the `MagicSpellListener` --- .../magicspells/listeners/MagicSpellListener.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java index e4945103f..ce6fe9e25 100644 --- a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java +++ b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java @@ -33,9 +33,17 @@ public void onSpellTarget(SpellTargetEvent event) { Spell spell = event.getSpell(); if (target == null) return; + if (isMSEntity(target)) { + event.setCancelled(true); + return; + } + + if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) { + event.setCancelled(true); + return; + } + if (Perm.NO_TARGET.has(target)) event.setCancelled(true); - if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) event.setCancelled(true); - if (isMSEntity(target)) event.setCancelled(true); } @EventHandler From fbf61e1033a990a6b62271c4c23a7bfd4119a2b1 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:35:10 +0200 Subject: [PATCH 02/15] perf: check for the `NO_COOLDOWN` permission when the spell actually has cooldown. --- .../java/com/nisovin/magicspells/Spell.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 415e2d1f0..07a2be30a 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1331,15 +1331,24 @@ public ConfigData getCooldown() { * @return whether the spell is on cooldown */ public boolean onCooldown(LivingEntity livingEntity) { - if (Perm.NO_COOLDOWN.has(livingEntity)) return false; + UUID uuid = livingEntity.getUniqueId(); + long timeMillis = System.currentTimeMillis(); - ChargeState state = chargeStates.get(livingEntity.getUniqueId()); - if (state != null && state.isDepleted()) return true; + ChargeState state = chargeStates.get(uuid); + boolean depleted = state != null && state.isDepleted(); - if (serverCooldown > 0 && nextCastServer > System.currentTimeMillis()) return true; + if (!depleted && serverCooldown > 0 && nextCastServer > timeMillis) { + depleted = true; + } - Long next = nextCast.get(livingEntity.getUniqueId()); - return next != null && next > System.currentTimeMillis(); + if (!depleted) { + Long next = nextCast.get(uuid); + if (next != null && next > timeMillis) { + depleted = true; + } + } + + return depleted && !Perm.NO_COOLDOWN.has(livingEntity); } /** From 82b73e24cc894591394c465e3a56a01fbb93300f Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:35:47 +0200 Subject: [PATCH 03/15] perf: check for the `NO_CAST_TIME` permission when the spell actually has cast time. --- core/src/main/java/com/nisovin/magicspells/Spell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 07a2be30a..846c880a3 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1145,7 +1145,7 @@ public SpellCastEvent preCast(@NotNull SpellData data) { } if (castEvent.hasSpellCastStateChanged()) debug(2, " Spell cast state changed: " + state); - if (Perm.NO_CAST_TIME.has(data.caster())) castEvent.setCastTime(0); + if (castEvent.getCastTime() > 0 && Perm.NO_CAST_TIME.has(data.caster())) castEvent.setCastTime(0); return castEvent; } From 4d798ccdd379a0d65447e13fe55db8f9bf7ed5b9 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:36:52 +0200 Subject: [PATCH 04/15] perf: check for `NO_MAGIC_ZONE` at the very end of `getCastState`. --- core/src/main/java/com/nisovin/magicspells/Spell.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 846c880a3..a3b10fe5e 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1078,14 +1078,17 @@ public PostCastAction castSpell(LivingEntity caster, SpellCastState state, float } protected SpellCastState getCastState(LivingEntity caster) { - if (caster instanceof Player player && !MagicSpells.getSpellbook(player).canCast(this)) + if (caster instanceof Player player && !MagicSpells.getSpellbook(player).canCast(this)) { return SpellCastState.CANT_CAST; - if (worldRestrictions != null && !worldRestrictions.contains(caster.getWorld().getName())) + } + if (worldRestrictions != null && !worldRestrictions.contains(caster.getWorld().getName())) { return SpellCastState.WRONG_WORLD; - if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) - return SpellCastState.NO_MAGIC_ZONE; + } if (onCooldown(caster)) return SpellCastState.ON_COOLDOWN; if (!hasReagents(caster)) return SpellCastState.MISSING_REAGENTS; + if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) { + return SpellCastState.NO_MAGIC_ZONE; + } return SpellCastState.NORMAL; } From 8499c8bea0d4df0814e6119ab58dd00cf1d46d52 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:38:28 +0200 Subject: [PATCH 05/15] perf: check for `NO_REAGENTS` permission when there are actually any reagents. --- .../main/java/com/nisovin/magicspells/util/SpellUtil.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java index fca83d43d..facdfa169 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java @@ -63,9 +63,10 @@ public static boolean hasReagents(LivingEntity livingEntity, SpellReagents reage * @return true if the player has all the reagents, false otherwise */ public static boolean hasReagents(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { - // Is the livingEntity exempt from reagent costs? - if (Perm.NO_REAGENTS.has(livingEntity)) return true; + return meetsCosts(livingEntity, reagents, healthCost, manaCost, hungerCost, experienceCost, levelsCost, durabilityCost, moneyCost, variables) || Perm.NO_REAGENTS.has(livingEntity); + } + private static boolean meetsCosts(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { // player reagents if (livingEntity instanceof Player player) { // Mana costs From 568c5b48d64ef53d7e2396a2cf0b67162e38bbec Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:41:31 +0200 Subject: [PATCH 06/15] perf: check for `NO_REAGENTS` permission when there are actually any reagents. --- .../main/java/com/nisovin/magicspells/util/SpellUtil.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java index facdfa169..734cd933f 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java @@ -148,7 +148,12 @@ public static void removeReagents(LivingEntity livingEntity, SpellReagents reage * @param manaCost the mana to remove */ public static void removeReagents(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { - if (Perm.NO_REAGENTS.has(livingEntity)) return; + boolean hasCosts = (reagents != null && reagents.length > 0) + || healthCost != 0 || manaCost != 0 || hungerCost != 0 + || experienceCost != 0 || levelsCost != 0 || durabilityCost != 0 + || moneyCost != 0 || (variables != null && !variables.isEmpty()); + + if (!hasCosts || Perm.NO_REAGENTS.has(livingEntity)) return; if (reagents != null) { for (SpellReagents.ReagentItem item : reagents) { From bb794f7ffd4bce3c10761c6719fa4a3c5c4eb4ad Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:47:11 +0200 Subject: [PATCH 07/15] perf: replace reflection dispatch with `MethodHandle` in `registerEvents()`. --- .../com/nisovin/magicspells/MagicSpells.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index cec7b0fe1..2a757c1a8 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -13,7 +13,11 @@ import java.util.function.Predicate; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.annotation.Annotation; import java.net.URL; @@ -1955,20 +1959,30 @@ public static void registerEvents(final Listener listener, EventPriority customP } for (final Method method : methods) { - final EventHandler eh = method.getAnnotation(EventHandler.class); - if (eh == null) continue; - EventPriority priority = eh.priority(); + final EventHandler handler = method.getAnnotation(EventHandler.class); + if (handler == null) continue; + EventPriority priority = handler.priority(); if (hasAnnotation(method, OverridePriority.class)) priority = customPriority; - final Class checkClass = method.getParameterTypes()[0]; - if (!Event.class.isAssignableFrom(checkClass) || method.getParameterTypes().length != 1) { + final Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1 || !Event.class.isAssignableFrom(paramTypes[0])) { plugin.getLogger().severe("Wrong method arguments used for event type registered"); continue; } - final Class eventClass = checkClass.asSubclass(Event.class); + final Class eventClass = paramTypes[0].asSubclass(Event.class); method.setAccessible(true); + final MethodHandle methodHandle; + try { + MethodHandle handle = MethodHandles.lookup().unreflect(method); + if (!Modifier.isStatic(method.getModifiers())) handle = handle.bindTo(listener); + methodHandle = handle.asType(MethodType.methodType(void.class, Event.class)); + } catch (IllegalAccessException e) { + plugin.getLogger().severe("Failed to create method handle for " + method.getName() + ": " + e.getMessage()); + continue; + } + EventExecutor executor = new EventExecutor() { final String eventKey = plugin.enableProfiling ? "Event:" + listener.getClass().getName().replace("com.nisovin.magicspells.", "") + '.' + method.getName() + '(' + eventClass.getSimpleName() + ')' : null; @@ -1977,23 +1991,24 @@ public void execute(@NotNull Listener listener, @NotNull Event event) { try { if (!eventClass.isAssignableFrom(event.getClass())) return; long start = System.nanoTime(); - method.invoke(listener, event); + methodHandle.invokeExact((Event) event); if (plugin.enableProfiling) { Long total = plugin.profilingTotalTime.get(eventKey); - if (total == null) total = (long) 0; + if (total == null) total = 0L; total += System.nanoTime() - start; plugin.profilingTotalTime.put(eventKey, total); Integer runs = plugin.profilingRuns.get(eventKey); if (runs == null) runs = 0; - runs += 1; + runs++; plugin.profilingRuns.put(eventKey, runs); } - } catch (Exception ex) { - handleException(ex); + } catch (Throwable ex) { + if (ex instanceof Error e) throw e; + handleException(ex instanceof Exception exception ? exception : new RuntimeException(ex)); } } }; - Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, eh.ignoreCancelled()); + Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, handler.ignoreCancelled()); } } From 625ff80f546231e9c7d5c8a20a2d449da22bab5f Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Sun, 23 Aug 2026 23:35:51 +0200 Subject: [PATCH 08/15] change the formatting for `getCastState`. --- .../java/com/nisovin/magicspells/Spell.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index a3b10fe5e..79c681887 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1078,17 +1078,21 @@ public PostCastAction castSpell(LivingEntity caster, SpellCastState state, float } protected SpellCastState getCastState(LivingEntity caster) { - if (caster instanceof Player player && !MagicSpells.getSpellbook(player).canCast(this)) { + if (caster instanceof Player player && !MagicSpells.getSpellbook(player).canCast(this)) return SpellCastState.CANT_CAST; - } - if (worldRestrictions != null && !worldRestrictions.contains(caster.getWorld().getName())) { + + if (worldRestrictions != null && !worldRestrictions.contains(caster.getWorld().getName())) return SpellCastState.WRONG_WORLD; - } - if (onCooldown(caster)) return SpellCastState.ON_COOLDOWN; - if (!hasReagents(caster)) return SpellCastState.MISSING_REAGENTS; - if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) { + + if (onCooldown(caster)) + return SpellCastState.ON_COOLDOWN; + + if (!hasReagents(caster)) + return SpellCastState.MISSING_REAGENTS; + + if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) return SpellCastState.NO_MAGIC_ZONE; - } + return SpellCastState.NORMAL; } From d75a85d724595e1866256e1d7dfd0565da306f8e Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Mon, 24 Aug 2026 00:04:44 +0200 Subject: [PATCH 09/15] refactor the reagent check --- .../nisovin/magicspells/util/SpellUtil.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java index 734cd933f..ef8ce2536 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/SpellUtil.java @@ -63,10 +63,8 @@ public static boolean hasReagents(LivingEntity livingEntity, SpellReagents reage * @return true if the player has all the reagents, false otherwise */ public static boolean hasReagents(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { - return meetsCosts(livingEntity, reagents, healthCost, manaCost, hungerCost, experienceCost, levelsCost, durabilityCost, moneyCost, variables) || Perm.NO_REAGENTS.has(livingEntity); - } + if (!hasCosts(reagents, healthCost, manaCost, hungerCost, experienceCost, levelsCost, durabilityCost, moneyCost, variables) || Perm.NO_REAGENTS.has(livingEntity)) return true; - private static boolean meetsCosts(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { // player reagents if (livingEntity instanceof Player player) { // Mana costs @@ -103,7 +101,7 @@ private static boolean meetsCosts(LivingEntity livingEntity, SpellReagents.Reage // Health costs if (healthCost > 0 && livingEntity.getHealth() <= healthCost) return false; - // Durabilty costs + // Durability costs if (durabilityCost > 0) { // Durability cost is charged from the main hand item EntityEquipment equipment = livingEntity.getEquipment(); @@ -135,6 +133,13 @@ private static boolean meetsCosts(LivingEntity livingEntity, SpellReagents.Reage return true; } + private static boolean hasCosts(SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { + return (reagents != null && reagents.length > 0) + || healthCost != 0 || manaCost != 0 || hungerCost != 0 + || experienceCost != 0 || levelsCost != 0 || durabilityCost != 0 + || moneyCost != 0 || (variables != null && !variables.isEmpty()); + } + public static void removeReagents(LivingEntity livingEntity, SpellReagents reagents) { removeReagents(livingEntity, reagents.getItemsAsArray(), reagents.getHealth(), reagents.getMana(), reagents.getHunger(), reagents.getExperience(), reagents.getLevels(), reagents.getDurability(), reagents.getMoney(), reagents.getVariables()); } @@ -148,12 +153,7 @@ public static void removeReagents(LivingEntity livingEntity, SpellReagents reage * @param manaCost the mana to remove */ public static void removeReagents(LivingEntity livingEntity, SpellReagents.ReagentItem[] reagents, double healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map variables) { - boolean hasCosts = (reagents != null && reagents.length > 0) - || healthCost != 0 || manaCost != 0 || hungerCost != 0 - || experienceCost != 0 || levelsCost != 0 || durabilityCost != 0 - || moneyCost != 0 || (variables != null && !variables.isEmpty()); - - if (!hasCosts || Perm.NO_REAGENTS.has(livingEntity)) return; + if (!hasCosts(reagents, healthCost, manaCost, hungerCost, experienceCost, levelsCost, durabilityCost, moneyCost, variables) || Perm.NO_REAGENTS.has(livingEntity)) return; if (reagents != null) { for (SpellReagents.ReagentItem item : reagents) { From fcd3652cdf7227d20b12139dcf9aac9baa3c4650 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Mon, 24 Aug 2026 00:40:14 +0200 Subject: [PATCH 10/15] refactor `onCooldown` function for better readability --- .../java/com/nisovin/magicspells/Spell.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 79c681887..77a8bb8bf 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1338,24 +1338,20 @@ public ConfigData getCooldown() { * @return whether the spell is on cooldown */ public boolean onCooldown(LivingEntity livingEntity) { + return hasActiveCooldown(livingEntity) && !Perm.NO_COOLDOWN.has(livingEntity); + } + + private boolean hasActiveCooldown(LivingEntity livingEntity) { UUID uuid = livingEntity.getUniqueId(); long timeMillis = System.currentTimeMillis(); ChargeState state = chargeStates.get(uuid); - boolean depleted = state != null && state.isDepleted(); - - if (!depleted && serverCooldown > 0 && nextCastServer > timeMillis) { - depleted = true; - } + if (state != null && state.isDepleted()) return true; - if (!depleted) { - Long next = nextCast.get(uuid); - if (next != null && next > timeMillis) { - depleted = true; - } - } + if (serverCooldown > 0 && nextCastServer > timeMillis) return true; - return depleted && !Perm.NO_COOLDOWN.has(livingEntity); + Long next = nextCast.get(uuid); + return next != null && next > timeMillis; } /** From 2d9d7ad64c77ba4a15dfa8d067aa3ed41553005e Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Mon, 24 Aug 2026 00:52:38 +0200 Subject: [PATCH 11/15] reorder the checks in `MagicSpellListener` --- .../magicspells/listeners/MagicSpellListener.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java index ce6fe9e25..5c9a87538 100644 --- a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java +++ b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java @@ -31,19 +31,22 @@ public void onSpellTarget(SpellTargetEvent event) { // Check if target has noTarget permission / is in noMagicZone / is an invisible marker armorstand LivingEntity target = event.getTarget(); Spell spell = event.getSpell(); - if (target == null) return; + + if (target == null) + return; if (isMSEntity(target)) { event.setCancelled(true); return; } - if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) { + if (Perm.NO_TARGET.has(target)) { event.setCancelled(true); return; } - if (Perm.NO_TARGET.has(target)) event.setCancelled(true); + if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) + event.setCancelled(true); } @EventHandler From 0069e55dff14ca8c9f26b9fa6315d7adc3de273a Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Mon, 24 Aug 2026 09:38:32 +0200 Subject: [PATCH 12/15] restore the check order in `getCastState` --- core/src/main/java/com/nisovin/magicspells/Spell.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 77a8bb8bf..d40ff1cc2 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1084,15 +1084,15 @@ protected SpellCastState getCastState(LivingEntity caster) { if (worldRestrictions != null && !worldRestrictions.contains(caster.getWorld().getName())) return SpellCastState.WRONG_WORLD; + if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) + return SpellCastState.NO_MAGIC_ZONE; + if (onCooldown(caster)) return SpellCastState.ON_COOLDOWN; if (!hasReagents(caster)) return SpellCastState.MISSING_REAGENTS; - if (MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(caster, this)) - return SpellCastState.NO_MAGIC_ZONE; - return SpellCastState.NORMAL; } From e1d63c53320b5609e1fac1a66a5e5a17afae3c59 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Tue, 25 Aug 2026 02:04:46 +0200 Subject: [PATCH 13/15] perf: Use private lookup instead --- core/src/main/java/com/nisovin/magicspells/MagicSpells.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index 2a757c1a8..10d371df2 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -1975,7 +1975,7 @@ public static void registerEvents(final Listener listener, EventPriority customP method.setAccessible(true); final MethodHandle methodHandle; try { - MethodHandle handle = MethodHandles.lookup().unreflect(method); + MethodHandle handle = MethodHandles.privateLookupIn(listener.getClass(), MethodHandles.lookup()).unreflect(method); if (!Modifier.isStatic(method.getModifiers())) handle = handle.bindTo(listener); methodHandle = handle.asType(MethodType.methodType(void.class, Event.class)); } catch (IllegalAccessException e) { From 5338418082451d55f2cb05d413bfbc0a971b9260 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Tue, 25 Aug 2026 02:11:05 +0200 Subject: [PATCH 14/15] perf: update `EffectLib` with performance improvements. --- core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle b/core/build.gradle index 9ba478520..efeefe0a4 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { implementation("org.apache.commons:commons-math4-core:4.0-beta1") implementation("com.github.ben-manes.caffeine:caffeine:3.2.2") - implementation("com.github.Chronoken:EffectLib:1da888c") + implementation("com.github.Chronoken:EffectLib:071cc3c") implementation("org.incendo:cloud-paper:2.0.0-beta.16") implementation("org.incendo:cloud-minecraft-extras:2.0.0-beta.16") implementation("org.incendo:cloud-processors-requirements:1.0.0-rc.1") From 351edbae0906fa19aad4a6f747028823cf573ad3 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Tue, 25 Aug 2026 07:17:40 +0200 Subject: [PATCH 15/15] perf: Create one private lookup per listener --- .../com/nisovin/magicspells/MagicSpells.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index 10d371df2..c9df605c6 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -1946,18 +1946,27 @@ public static void registerEvents(final Listener listener) { public static void registerEvents(final Listener listener, EventPriority customPriority) { if (customPriority == null) customPriority = EventPriority.NORMAL; + Class listenerClass = listener.getClass(); + Set methods; try { - Class listenerClazz = listener.getClass(); methods = Sets.union( - Set.of(listenerClazz.getMethods()), - Set.of(listenerClazz.getDeclaredMethods()) + Set.of(listenerClass.getMethods()), + Set.of(listenerClass.getDeclaredMethods()) ); } catch (NoClassDefFoundError e) { DebugHandler.debugNoClassDefFoundError(e); return; } + MethodHandles.Lookup lookup; + try { + lookup = MethodHandles.privateLookupIn(listenerClass, MethodHandles.lookup()); + } catch (IllegalAccessException e) { + DebugHandler.debugIllegalAccessException(e); + return; + } + for (final Method method : methods) { final EventHandler handler = method.getAnnotation(EventHandler.class); if (handler == null) continue; @@ -1972,10 +1981,9 @@ public static void registerEvents(final Listener listener, EventPriority customP } final Class eventClass = paramTypes[0].asSubclass(Event.class); - method.setAccessible(true); final MethodHandle methodHandle; try { - MethodHandle handle = MethodHandles.privateLookupIn(listener.getClass(), MethodHandles.lookup()).unreflect(method); + MethodHandle handle = lookup.unreflect(method); if (!Modifier.isStatic(method.getModifiers())) handle = handle.bindTo(listener); methodHandle = handle.asType(MethodType.methodType(void.class, Event.class)); } catch (IllegalAccessException e) { @@ -1984,7 +1992,7 @@ public static void registerEvents(final Listener listener, EventPriority customP } EventExecutor executor = new EventExecutor() { - final String eventKey = plugin.enableProfiling ? "Event:" + listener.getClass().getName().replace("com.nisovin.magicspells.", "") + '.' + method.getName() + '(' + eventClass.getSimpleName() + ')' : null; + final String eventKey = plugin.enableProfiling ? "Event:" + listenerClass.getName().replace("com.nisovin.magicspells.", "") + '.' + method.getName() + '(' + eventClass.getSimpleName() + ')' : null; @Override public void execute(@NotNull Listener listener, @NotNull Event event) {