From 80009e839a2fa1168300816bf2c3806e5fd1b0cd Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:31:50 +0200 Subject: [PATCH 1/8] 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 10f03a5802963a293bc3dedd54a0adfebc498513 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:35:10 +0200 Subject: [PATCH 2/8] 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 cc18ef752..a43fab1f3 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 a6e2276af16f8d2a9cbd6c645395b3a3c1e46971 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:35:47 +0200 Subject: [PATCH 3/8] 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 a43fab1f3..7b132e051 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 4022a542be98426bdcfaaa520842c84163cefa43 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:36:52 +0200 Subject: [PATCH 4/8] 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 7b132e051..9ceb6de60 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 5b2bad79496063f392cb3ccabf9a6fcd52c229b6 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:38:28 +0200 Subject: [PATCH 5/8] 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 2a1cd3221fc8e67f85ee68b4abe3583dcab6347f Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:41:31 +0200 Subject: [PATCH 6/8] 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 60fb1436b36af9d5c55e1ae48081c66b7476d434 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Wed, 19 Aug 2026 23:47:11 +0200 Subject: [PATCH 7/8] 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 f582949d36b7abbfd3e52ddeaae396ec995755f4 Mon Sep 17 00:00:00 2001 From: ChronoKeeper Date: Thu, 20 Aug 2026 00:00:10 +0200 Subject: [PATCH 8/8] 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 8d8fe3b74..2995cbdb0 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { shadow("org.apache.commons:commons-math4-core:4.0-beta1") shadow("com.github.ben-manes.caffeine:caffeine:3.2.2") - shadow("com.github.Chronoken:EffectLib:1da888c") + shadow("com.github.Chronoken:EffectLib:071cc3c") shadow("org.incendo:cloud-paper:2.0.0-beta.16") shadow("org.incendo:cloud-minecraft-extras:2.0.0-beta.16") shadow("org.incendo:cloud-processors-requirements:1.0.0-rc.1")