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") diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index cec7b0fe1..c9df605c6 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; @@ -1942,58 +1946,77 @@ 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 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); - method.setAccessible(true); + final Class eventClass = paramTypes[0].asSubclass(Event.class); + final MethodHandle methodHandle; + try { + 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) { + 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; + 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) { 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()); } } diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 415e2d1f0..d40ff1cc2 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1080,12 +1080,19 @@ public PostCastAction castSpell(LivingEntity caster, SpellCastState state, float protected SpellCastState getCastState(LivingEntity caster) { if (caster instanceof Player player && !MagicSpells.getSpellbook(player).canCast(this)) return SpellCastState.CANT_CAST; + 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 (onCooldown(caster)) + return SpellCastState.ON_COOLDOWN; + + if (!hasReagents(caster)) + return SpellCastState.MISSING_REAGENTS; + return SpellCastState.NORMAL; } @@ -1145,7 +1152,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; } @@ -1331,15 +1338,20 @@ public ConfigData getCooldown() { * @return whether the spell is on cooldown */ public boolean onCooldown(LivingEntity livingEntity) { - if (Perm.NO_COOLDOWN.has(livingEntity)) return false; + return hasActiveCooldown(livingEntity) && !Perm.NO_COOLDOWN.has(livingEntity); + } - ChargeState state = chargeStates.get(livingEntity.getUniqueId()); + private boolean hasActiveCooldown(LivingEntity livingEntity) { + UUID uuid = livingEntity.getUniqueId(); + long timeMillis = System.currentTimeMillis(); + + ChargeState state = chargeStates.get(uuid); if (state != null && state.isDepleted()) return true; - if (serverCooldown > 0 && nextCastServer > System.currentTimeMillis()) return true; + if (serverCooldown > 0 && nextCastServer > timeMillis) return true; - Long next = nextCast.get(livingEntity.getUniqueId()); - return next != null && next > System.currentTimeMillis(); + Long next = nextCast.get(uuid); + return next != null && next > timeMillis; } /** 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..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,11 +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 (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); + if (target == null) + return; + + if (isMSEntity(target)) { + event.setCancelled(true); + return; + } + + if (Perm.NO_TARGET.has(target)) { + event.setCancelled(true); + return; + } + + if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) + event.setCancelled(true); } @EventHandler 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..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,8 +63,7 @@ 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; + if (!hasCosts(reagents, healthCost, manaCost, hungerCost, experienceCost, levelsCost, durabilityCost, moneyCost, variables) || Perm.NO_REAGENTS.has(livingEntity)) return true; // player reagents if (livingEntity instanceof Player player) { @@ -102,7 +101,7 @@ public static boolean hasReagents(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(); @@ -134,6 +133,13 @@ public static boolean hasReagents(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()); } @@ -147,7 +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) { - if (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) {