From ee593c62c0ca33c04f3fe3f8a30dac23ec339c61 Mon Sep 17 00:00:00 2001 From: Kam Date: Thu, 22 Jan 2026 18:44:41 -0500 Subject: [PATCH] No @ScriptHook needed anymore --- .../npcs/janino/JaninoHookResolver.java | 273 +++++------------- .../java/noppes/npcs/janino/JaninoScript.java | 154 ++-------- .../npcs/janino/annotations/ScriptHook.java | 22 -- .../npcs/janino/impl/JaninoNpcScript.java | 114 +------- .../npcs/janino/impl/JaninoPlayerScript.java | 154 +--------- 5 files changed, 108 insertions(+), 609 deletions(-) delete mode 100644 src/main/java/noppes/npcs/janino/annotations/ScriptHook.java diff --git a/src/main/java/noppes/npcs/janino/JaninoHookResolver.java b/src/main/java/noppes/npcs/janino/JaninoHookResolver.java index b77233b12..87b969e25 100644 --- a/src/main/java/noppes/npcs/janino/JaninoHookResolver.java +++ b/src/main/java/noppes/npcs/janino/JaninoHookResolver.java @@ -1,47 +1,24 @@ package noppes.npcs.janino; -import noppes.npcs.constants.EnumScriptType; -import noppes.npcs.janino.annotations.ScriptHook; - +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; -import java.util.stream.Collectors; /** - * Resolves script hook methods for a given interface type using runtime + * Resolves script hook methods for compiled script classes using runtime * argument values. Resolution favors exact matches and then the "closest" * assignable parameter types (measured by inheritance distance). * - * The resolver keeps simple caches for previously resolved signatures and - * supports mapping enum-based hook names (via the {@code @ScriptHook} - * annotation) to methods. - * - *

Dynamic Hook Support

- * This resolver supports two modes: - * - * Use {@link #resolveHookMethod(String, Object[], Object)} to search the compiled instance. + * Methods are resolved dynamically from the compiled class - no interface + * definitions required. */ public final class JaninoHookResolver { private final Class interfaceType; - /* Methods explicitly mapped from EnumScriptType via @ScriptHook */ - private final Map enumHookLookup = new HashMap<>(); - /** - * Cache for resolved hook methods. Keys are generated by {@link #buildSignatureKey} - * and represent the unique combination of hook name and runtime argument types. - * This avoids expensive recursive reflection lookups on every hook call. - */ - private final Map signatureCache = new HashMap<>(); - - /** - * Cache for methods found on compiled classes (keyed by class + signature). - * Separate from signatureCache to handle recompilation. + * Cache for methods found on compiled classes (keyed by signature). */ private final Map compiledClassCache = new HashMap<>(); @@ -51,12 +28,13 @@ public final class JaninoHookResolver { */ private Class lastCompiledClass; - /* Lazily built list of available hooks and a mapping for display names */ - private boolean hookListInitialized; - /* List of names displayed in GUI hooks list (accounts for overload names) */ - private final List displayHookList = new ArrayList<>(); - /* Display name -> Method map */ - private final Map displayHookMethods = new HashMap<>(); + // MethodHandle caches + private final Map compiledHandleCache = new HashMap<>(); + + // Method -> MethodHandle for identity-based deduplication + private final Map methodToHandle = new IdentityHashMap<>(); + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); /** * Create a resolver for the provided script interface type. @@ -64,7 +42,6 @@ public final class JaninoHookResolver { */ public JaninoHookResolver(Class interfaceType) { this.interfaceType = Objects.requireNonNull(interfaceType, "interfaceType"); - initializeEnumHookMethods(); } /** @@ -72,114 +49,64 @@ public final class JaninoHookResolver { * method resolution. Call when scripts or available methods change. */ public void clearResolutionCaches() { - signatureCache.clear(); compiledClassCache.clear(); + compiledHandleCache.clear(); + methodToHandle.clear(); lastCompiledClass = null; } /** - * Resolve a hook method by name and runtime argument values (interface-only mode). - * - * This first checks any enum-mapped methods (via {@code @ScriptHook}), - * then a signature-based cache and finally attempts full candidate - * resolution using {@link #resolveHookMethodInternal}. - * - * @param hookName hook method name (or enum function name) - * @param args runtime arguments to use for overload resolution - * @return the resolved {@link Method} or {@code null} if none matched - */ - public Method resolveHookMethod(String hookName, Object[] args) { - return resolveHookMethod(hookName, args, null); - } - - /** - * Resolve a hook method by name, searching both the compiled class and interface. - * - *

This enables dynamic hook support: users can define methods for any hook - * (Forge events, addon hooks, etc.) without needing them in the interface definition.

+ * Resolve a hook method by name, searching the compiled class. * *

Resolution order:

*
    - *
  1. Enum-mapped methods (via {@code @ScriptHook})
  2. - *
  3. Compiled class methods (user-defined, including custom hooks)
  4. - *
  5. Interface methods (base hooks with defaults)
  6. + *
  7. Compiled class methods (user-defined)
  8. *
  9. Fallback to no-arg versions
  10. *
* - *

Performance: Method lookups are cached. First call per hook does reflection - * (~microseconds), subsequent calls use cache (essentially free).

- * - * @param hookName hook method name (or enum function name) + * @param hookName hook method name * @param args runtime arguments to use for overload resolution - * @param compiledInstance the compiled script instance (or null for interface-only) + * @param compiledInstance the compiled script instance * @return the resolved {@link Method} or {@code null} if none matched */ public Method resolveHookMethod(String hookName, Object[] args, Object compiledInstance) { - if (hookName == null || hookName.isEmpty()) + if (hookName == null || hookName.isEmpty() || compiledInstance == null) return null; - // 1. Check enum-mapped methods first (fastest path) - Method method = enumHookLookup.get(hookName); - if (method != null) - return method; - Object[] safeArgs = args == null ? new Object[0] : args; String signatureKey = buildSignatureKey(hookName, safeArgs); - // 2. If we have a compiled instance, search its class first - if (compiledInstance != null) { - Class compiledClass = compiledInstance.getClass(); + Class compiledClass = compiledInstance.getClass(); - // Clear cache if compiled class changed (recompilation) - if (lastCompiledClass != compiledClass) { - compiledClassCache.clear(); - lastCompiledClass = compiledClass; - } - - // Check compiled class cache - method = compiledClassCache.get(signatureKey); - if (method != null) - return method; - - // Search compiled class for matching method - method = resolveHookMethodInternal(compiledClass, hookName, safeArgs); - if (method != null) { - compiledClassCache.put(signatureKey, method); - return method; - } - - // Try no-arg version on compiled class - String noArgKey = buildSignatureKey(hookName, new Object[0]); - method = compiledClassCache.get(noArgKey); - if (method != null) - return method; - - method = resolveHookMethodInternal(compiledClass, hookName, new Object[0]); - if (method != null) { - compiledClassCache.put(noArgKey, method); - return method; - } + // Clear cache if compiled class changed (recompilation) + if (lastCompiledClass != compiledClass) { + compiledClassCache.clear(); + compiledHandleCache.clear(); + methodToHandle.clear(); + lastCompiledClass = compiledClass; } - // 3. Fall back to interface-only search - method = signatureCache.get(signatureKey); + // Check compiled class cache + Method method = compiledClassCache.get(signatureKey); if (method != null) return method; - method = resolveHookMethodInternal(interfaceType, hookName, safeArgs); + // Search compiled class for matching method + method = resolveHookMethodInternal(compiledClass, hookName, safeArgs); if (method != null) { - signatureCache.put(signatureKey, method); + compiledClassCache.put(signatureKey, method); return method; } - String noArgSignatureKey = buildSignatureKey(hookName, new Object[0]); - method = signatureCache.get(noArgSignatureKey); + // Try no-arg version on compiled class + String noArgKey = buildSignatureKey(hookName, new Object[0]); + method = compiledClassCache.get(noArgKey); if (method != null) return method; - method = resolveHookMethodInternal(interfaceType, hookName, new Object[0]); + method = resolveHookMethodInternal(compiledClass, hookName, new Object[0]); if (method != null) { - signatureCache.put(noArgSignatureKey, method); + compiledClassCache.put(noArgKey, method); return method; } @@ -187,83 +114,55 @@ public final class JaninoHookResolver { } /** - * Returns an immutable copy of the available hook display names. + * Resolve a MethodHandle for the given hook. Faster than Method.invoke(). + * Uses existing Method resolution, then converts to cached MethodHandle. */ - public List getDisplayHookList() { - ensureHookListCreated(); - return new ArrayList<>(displayHookList); - } - - public Method getMethodForDisplayName(String displayName) { - if (displayName == null || displayName.isEmpty()) + public MethodHandle resolveHookHandle(String hookName, Object[] args, Object compiledInstance) { + if (hookName == null || hookName.isEmpty() || compiledInstance == null) return null; - ensureHookListCreated(); - return displayHookMethods.get(displayName); - } + Object[] safeArgs = args == null ? new Object[0] : args; + String signatureKey = buildSignatureKey(hookName, safeArgs); - /** - * Builds the human-readable hook list and display-name -> Method map. - * The list is computed lazily on first access and cached. - */ - private void ensureHookListCreated() { - if (hookListInitialized) - return; + Class compiledClass = compiledInstance.getClass(); - Map> grouped = Arrays.stream(interfaceType.getDeclaredMethods()) - .collect(Collectors.groupingBy(Method::getName)); + // Clear cache if compiled class changed (recompilation) + if (lastCompiledClass != compiledClass) { + compiledClassCache.clear(); + compiledHandleCache.clear(); + methodToHandle.clear(); + lastCompiledClass = compiledClass; + } - for (Map.Entry> entry : grouped.entrySet()) { - List methods = entry.getValue(); + // Check handle cache + MethodHandle handle = compiledHandleCache.get(signatureKey); + if (handle != null) + return handle; - methods.sort(Comparator.comparingInt(Method::getParameterCount)); + // Resolve Method first + Method method = resolveHookMethod(hookName, args, compiledInstance); + if (method == null) + return null; - boolean first = true; - for (Method m : methods) { - if (Modifier.isFinal(m.getModifiers())) - continue; - - String displayName = first ? m.getName() : - m.getName() + "(" + Arrays.stream(m.getParameterTypes()) - .map(Class::getSimpleName) - .collect(Collectors.joining(", ")) + ")"; - - displayHookList.add(displayName); - displayHookMethods.put(displayName, m); - first = false; + // Convert Method to MethodHandle (deduplicated by Method identity) + handle = methodToHandle.get(method); + if (handle == null) { + try { + handle = LOOKUP.unreflect(method); + methodToHandle.put(method, handle); + } catch (IllegalAccessException e) { + return null; } } - hookListInitialized = true; + compiledHandleCache.put(signatureKey, handle); + return handle; } /** - * Maps EnumScriptType common names to their respective methods using - * the {@code @ScriptHook} annotation. + * Build a cache key for a hook name and runtime argument list. */ - private void initializeEnumHookMethods() { - for (Method method : interfaceType.getDeclaredMethods()) { - ScriptHook annotation = method.getAnnotation(ScriptHook.class); - if (annotation == null) - continue; - - for (EnumScriptType hookType : annotation.value()) { - enumHookLookup.put(hookType.function, method); - } - } - } - - /** - * Build a cache key for a hook name and runtime argument list. This key - * encodes the runtime classes of the provided arguments and is used for - * quick cache lookups. Note: keys are based on concrete runtime classes - * which may cause fragmentation when many subclasses are used. - * - * @param hookName hook name - * @param args runtime arguments - * @return a string key representing the hook signature - */ - public static String buildSignatureKey(String hookName, Object[] args) { + private static String buildSignatureKey(String hookName, Object[] args) { StringBuilder sb = new StringBuilder(); sb.append(hookName).append('#'); @@ -284,15 +183,7 @@ public final class JaninoHookResolver { /** * Internal resolution routine. Attempts to find the best matching method * on the provided class by comparing parameter counts and a simple - * match scoring function (see {@link #scoreMethodMatch}). - * - *

This searches all public methods (including inherited) to support - * both interface-defined hooks and user-defined custom methods.

- * - * @param targetClass the class to search (interface or compiled class) - * @param hookName the method name to find - * @param args runtime arguments for overload resolution - * @return the best matching method, or null if none found + * match scoring function. */ private static Method resolveHookMethodInternal(Class targetClass, String hookName, Object[] args) { Object[] safeArgs = args == null ? new Object[0] : args; @@ -300,7 +191,6 @@ public final class JaninoHookResolver { Method best = null; int bestScore = Integer.MAX_VALUE; - // Use getMethods() to include inherited methods (important for compiled classes) for (Method method : targetClass.getMethods()) { if (!method.getName().equals(hookName)) continue; @@ -331,13 +221,8 @@ public final class JaninoHookResolver { } /** - * Score how well a method's parameter types match the provided runtime - * arguments. Lower scores are better; {@link Integer#MAX_VALUE} indicates - * an incompatible parameter. - * - * @param paramTypes method parameter types - * @param args runtime argument values - * @return score (lower is better) or Integer.MAX_VALUE for no match + * Score how well a method's parameter types match the provided runtime arguments. + * Lower scores are better; Integer.MAX_VALUE indicates an incompatible parameter. */ private static int scoreMethodMatch(Class[] paramTypes, Object[] args) { int score = 0; @@ -392,16 +277,6 @@ public final class JaninoHookResolver { return type; } - /** - * Compute a simple inheritance distance between a child and parent class - * by walking superclasses. Returns a large value if {@code parent} is - * not assignable from {@code child}. - * - * Note: the matching algorithm uses superclass traversal to compute a - * distance metric; interface inheritance is not explored which can make - * matches against interfaces less optimal. Consider improving - * `inheritanceDistance` if interface-based scoring is required. - */ private static int inheritanceDistance(Class child, Class parent) { if (child.equals(parent)) return 0; diff --git a/src/main/java/noppes/npcs/janino/JaninoScript.java b/src/main/java/noppes/npcs/janino/JaninoScript.java index 6f6d6e55b..8093b8224 100644 --- a/src/main/java/noppes/npcs/janino/JaninoScript.java +++ b/src/main/java/noppes/npcs/janino/JaninoScript.java @@ -12,18 +12,15 @@ import noppes.npcs.constants.EnumScriptType; import noppes.npcs.controllers.ScriptController; import noppes.npcs.controllers.ScriptHookController; import noppes.npcs.controllers.data.IScriptUnit; -import noppes.npcs.janino.annotations.ParamName; import org.codehaus.commons.compiler.InternalCompilerException; import org.codehaus.commons.compiler.Sandbox; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.lang.invoke.MethodHandle; import java.security.*; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; public abstract class JaninoScript implements IScriptUnit { public boolean errored = false; @@ -286,26 +283,27 @@ public abstract class JaninoScript implements IScriptUnit { if (hookName == null || hookName.isEmpty()) return null; - // Ensure compiled first so we can search the compiled class ensureCompiled(); T t = getUnsafe(); if (t == null) return null; - // Pass compiled instance to enable dynamic hook resolution - // This allows users to define methods for ANY hook (Forge events, addon hooks, etc.) - Method method = hookResolver.resolveHookMethod(hookName, args, t); - if (method == null) - return null; - - Method invokeMethod = method; Object[] invokeArgs = args == null ? new Object[0] : args; + MethodHandle handle = hookResolver.resolveHookHandle(hookName, invokeArgs, t); + if (handle == null) + return null; + + final MethodHandle finalHandle = handle; try { return sandbox.confine((PrivilegedAction) () -> { try { - return invokeMethod.invoke(t, invokeArgs); - } catch (Exception e) { + // Prepend receiver to args for virtual method call + Object[] fullArgs = new Object[invokeArgs.length + 1]; + fullArgs[0] = t; + System.arraycopy(invokeArgs, 0, fullArgs, 1, invokeArgs.length); + return finalHandle.invokeWithArguments(fullArgs); + } catch (Throwable e) { appendConsole("Error calling hook " + hookName + ": " + e.getMessage()); return null; } @@ -416,127 +414,31 @@ public abstract class JaninoScript implements IScriptUnit { @Override public String generateHookStub(String hookName, Object hookData) { - hookResolver.clearResolutionCaches(); - - // 1. Try interface method (existing @ScriptHook annotated methods) - Method method = hookResolver.getMethodForDisplayName(hookName); - if (method != null) - return generateMethodStub(method); - - // 2. Look up in hook definition registry (addon hooks, dynamic hooks) + // Look up in hook definition registry IHookDefinition def = getHookDefinition(hookName); if (def != null) { - return generateStubFromDefinition(def); - } + String typeName = def.getUsableTypeName(); + String[] params = def.paramNames(); + String paramName = (params != null && params.length > 0) ? params[0] : "event"; - // 3. Last resort: generic Object parameter - return String.format("public void %s(Object event) {\n \n}\n", hookName); - } - - /** - * Generate a method stub from a hook definition. - * - * @param def The hook definition containing type info - * @return Generated stub code - */ - private String generateStubFromDefinition(IHookDefinition def) { - String typeName = def.getUsableTypeName(); - String[] params = def.paramNames(); - String paramName = (params != null && params.length > 0) ? params[0] : "event"; - - if (typeName == null || typeName.isEmpty()) { - return String.format("public void %s(Object %s) {\n \n}\n", - def.hookName(), paramName); - } - - return String.format("public void %s(%s %s) {\n \n}\n", - def.hookName(), typeName, paramName); - } - - /** - * Generates a stub string for a given Method. - * Handles nested classes properly (e.g., IPlayerEvent.InitEvent instead of just InitEvent). - */ - public static String generateMethodStub(Method method) { - String mods = Modifier.toString(method.getModifiers()); - // Remove 'abstract' from modifiers for implementation - mods = mods.replace("abstract ", "").replace("abstract", "").trim(); - if (!mods.isEmpty()) - mods += " "; - - String returnTypeStr = getUsableTypeName(method.getReturnType()); - String name = method.getName(); - - Map typeCount = new HashMap<>(); - String params = Arrays.stream(method.getParameters()) - .map(p -> { - String typeName = getUsableTypeName(p.getType()); - String simpleTypeName = p.getType().getSimpleName(); - - // Check for @ParamName annotation first - ParamName paramNameAnnotation = p.getAnnotation(ParamName.class); - String paramName; - - if (paramNameAnnotation != null) { - // Use annotated name - paramName = paramNameAnnotation.value(); - } else { - // Fall back to generated name based on simple name - String baseName = Character.toLowerCase(simpleTypeName.charAt(0)) + simpleTypeName.substring(1); - int count = typeCount.getOrDefault(baseName, 0) + 1; - typeCount.put(baseName, count); - paramName = count == 1 ? baseName : baseName + (count - 1); - } - - return typeName + " " + paramName; - }) - .collect(Collectors.joining(", ")); - - String body; - Class returnType = method.getReturnType(); - if (returnType == void.class) - body = ""; - else if (returnType.isPrimitive()) { - if (returnType == boolean.class) - body = " return false;"; - else if (returnType == char.class) - body = " return '\\0';"; - else - body = " return 0;"; - } else { - body = " return null;"; - } - - return String.format("%s%s %s(%s) {\n%s\n}\n", mods, returnTypeStr, name, params, body); - } - - /** - * Gets a usable type name for stub generation. - * For nested classes like IPlayerEvent.InitEvent, returns "IPlayerEvent.InitEvent" - * For top-level classes, returns the simple name. - * For primitives, returns the primitive name. - */ - private static String getUsableTypeName(Class type) { - if (type.isPrimitive() || type.getEnclosingClass() == null) { - return type.getSimpleName(); - } - - // Build the nested class chain: OuterClass.InnerClass.DeeperClass - StringBuilder sb = new StringBuilder(); - Class current = type; - while (current != null) { - if (sb.length() > 0) { - sb.insert(0, "."); + if (typeName != null && !typeName.isEmpty()) { + return String.format("public void %s(%s %s) {\n \n}\n", + def.hookName(), typeName, paramName); } - sb.insert(0, current.getSimpleName()); - current = current.getEnclosingClass(); } - return sb.toString(); + + // Fallback: generic Object parameter + return String.format("public void %s(Object event) {\n \n}\n", hookName); } public List getHookList() { - return hookResolver.getDisplayHookList(); + // Get hooks from registry based on context + String context = getHookContext(); + if (context != null && !context.isEmpty() && ScriptHookController.Instance != null) { + return ScriptHookController.Instance.getAllHooks(context); + } + return Collections.emptyList(); } diff --git a/src/main/java/noppes/npcs/janino/annotations/ScriptHook.java b/src/main/java/noppes/npcs/janino/annotations/ScriptHook.java deleted file mode 100644 index d4720b5d8..000000000 --- a/src/main/java/noppes/npcs/janino/annotations/ScriptHook.java +++ /dev/null @@ -1,22 +0,0 @@ -package noppes.npcs.janino.annotations; - -import noppes.npcs.constants.EnumScriptType; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark script hook methods and associate them with their EnumScriptType. - * Used by JaninoScript to automatically build a map of hook types to methods. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface ScriptHook { - /** - * The script type(s) this method handles. - * Multiple types can be specified for methods that handle multiple hooks. - */ - EnumScriptType[] value(); -} diff --git a/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java b/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java index 637374775..4445ed8c6 100644 --- a/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java +++ b/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java @@ -1,35 +1,15 @@ package noppes.npcs.janino.impl; -import noppes.npcs.api.event.IAbilityEvent; -import noppes.npcs.api.event.INpcEvent; -import noppes.npcs.api.event.IProjectileEvent; -import noppes.npcs.constants.EnumScriptType; import noppes.npcs.janino.JaninoScript; -import noppes.npcs.janino.annotations.ParamName; -import noppes.npcs.janino.annotations.ScriptHook; /** * Janino (Java) script implementation for NPC events. - * This allows NPCs to use compiled Java code instead of ECMAScript. + * Hook methods are resolved dynamically by name - no interface definition required. */ public class JaninoNpcScript extends JaninoScript { - /** - * Default imports for NPC scripts. - * These are automatically available in user code without explicit import statements. - * Additional imports from registered hook definitions are added at compile time. - */ - private static final String[] DEFAULT_IMPORTS = { - "noppes.npcs.api.event.INpcEvent", - "noppes.npcs.api.event.IProjectileEvent", - "noppes.npcs.api.event.IAbilityEvent", - "noppes.npcs.api.entity.ICustomNpc", - "noppes.npcs.api.entity.IPlayer", - "noppes.npcs.api.NpcAPI" - }; - public JaninoNpcScript() { - super(Functions.class, DEFAULT_IMPORTS); + super(Functions.class, null); } @Override @@ -37,93 +17,5 @@ public class JaninoNpcScript extends JaninoScript { return "npc"; } - /** - * Interface defining the hook methods available for NPC scripts. - * Each method corresponds to an NPC event from ScriptHookController CONTEXT_NPC. - * Methods are annotated with @ScriptHook to map them to their EnumScriptType. - */ - public interface Functions { - // Core lifecycle events - @ScriptHook(EnumScriptType.INIT) - default void init(@ParamName("event") INpcEvent.InitEvent event) {} - - @ScriptHook(EnumScriptType.TICK) - default void tick(@ParamName("event") INpcEvent.UpdateEvent event) {} - - - // Interaction events - @ScriptHook(EnumScriptType.INTERACT) - default void interact(@ParamName("event") INpcEvent.InteractEvent event) {} - - @ScriptHook(EnumScriptType.DIALOG) - default void dialog(@ParamName("event") INpcEvent.DialogEvent event) {} - - @ScriptHook(EnumScriptType.DIALOG_CLOSE) - default void dialogClose(@ParamName("event") INpcEvent.DialogClosedEvent event) {} - - - // Combat events - damage - @ScriptHook(EnumScriptType.DAMAGED) - default void damaged(@ParamName("event") INpcEvent.DamagedEvent event) {} - - @ScriptHook(EnumScriptType.KILLED) - default void killed(@ParamName("event") INpcEvent.DiedEvent event) {} - - @ScriptHook(EnumScriptType.KILLS) - default void kills(@ParamName("event") INpcEvent.KilledEntityEvent event) {} - - - // Combat events - attacks - @ScriptHook(EnumScriptType.ATTACK_MELEE) - default void meleeAttack(@ParamName("event") INpcEvent.MeleeAttackEvent event) {} - - @ScriptHook(EnumScriptType.ATTACK_SWING) - default void meleeSwing(@ParamName("event") INpcEvent.SwingEvent event) {} - - @ScriptHook(EnumScriptType.RANGED_LAUNCHED) - default void rangedLaunched(@ParamName("event") INpcEvent.RangedLaunchedEvent event) {} - - - // Targeting events - @ScriptHook(EnumScriptType.TARGET) - default void target(@ParamName("event") INpcEvent.TargetEvent event) {} - - @ScriptHook(EnumScriptType.TARGET_LOST) - default void targetLost(@ParamName("event") INpcEvent.TargetLostEvent event) {} - - // Other NPC events - @ScriptHook(EnumScriptType.COLLIDE) - default void collide(@ParamName("event") INpcEvent.CollideEvent event) {} - - @ScriptHook(EnumScriptType.TIMER) - default void timer(@ParamName("event") INpcEvent.TimerEvent event) {} - - - // Projectile events - @ScriptHook(EnumScriptType.PROJECTILE_TICK) - default void projectileTick(@ParamName("event") IProjectileEvent.UpdateEvent event) {} - - @ScriptHook(EnumScriptType.PROJECTILE_IMPACT) - default void projectileImpact(@ParamName("event") IProjectileEvent.ImpactEvent event) {} - - - // Ability events - @ScriptHook(EnumScriptType.ABILITY_START) - default void abilityStart(@ParamName("event") IAbilityEvent.StartEvent event) {} - - @ScriptHook(EnumScriptType.ABILITY_EXECUTE) - default void abilityExecute(@ParamName("event") IAbilityEvent.ExecuteEvent event) {} - - @ScriptHook(EnumScriptType.ABILITY_HIT) - default void abilityHit(@ParamName("event") IAbilityEvent.HitEvent event) {} - - @ScriptHook(EnumScriptType.ABILITY_TICK) - default void abilityTick(@ParamName("event") IAbilityEvent.TickEvent event) {} - - @ScriptHook(EnumScriptType.ABILITY_INTERRUPT) - default void abilityInterrupt(@ParamName("event") IAbilityEvent.InterruptEvent event) {} - - @ScriptHook(EnumScriptType.ABILITY_COMPLETE) - default void abilityComplete(@ParamName("event") IAbilityEvent.CompleteEvent event) {} - } + public interface Functions {} } diff --git a/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java b/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java index d29b1b15e..79c7718b6 100644 --- a/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java +++ b/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java @@ -1,65 +1,15 @@ package noppes.npcs.janino.impl; -import noppes.npcs.api.event.IPlayerEvent; -import noppes.npcs.constants.EnumScriptType; import noppes.npcs.janino.JaninoScript; -import noppes.npcs.janino.annotations.ParamName; -import noppes.npcs.janino.annotations.ScriptHook; /** * Janino (Java) script implementation for player events. - * - *

Dynamic Hook Support

- * With the new dynamic hook resolution, users can define methods for ANY hook - - * not just those defined in the Functions interface. Simply write a public method - * with the hook name and matching parameter type: - * - *
{@code
- * // Standard hook (defined in interface)
- * public void init(IPlayerEvent.InitEvent event) {
- *     // ...
- * }
- *
- * // Custom addon hook (NOT in interface - still works!)
- * public void myAddonHook(MyAddonEvent event) {
- *     // ...
- * }
- *
- * // Any hook by name
- * public void someCustomHook(Object event) {
- *     // ...
- * }
- * }
- * - * The interface below defines core hooks for: - *
    - *
  • EnumScriptType mapping (so "init" maps to init() method)
  • - *
  • GUI hook list display
  • - *
  • Stub generation with correct parameter types
  • - *
+ * Hook methods are resolved dynamically by name - no interface definition required. */ public class JaninoPlayerScript extends JaninoScript { - /** - * Default imports for player scripts. - * These are automatically available in user code without explicit import statements. - * Additional imports from registered hook definitions are added at compile time. - */ - private static final String[] DEFAULT_IMPORTS = { - "noppes.npcs.api.event.IPlayerEvent", - "noppes.npcs.api.event.IQuestEvent", - "noppes.npcs.api.event.IDialogEvent", - "noppes.npcs.api.event.IFactionEvent", - "noppes.npcs.api.event.IPartyEvent", - "noppes.npcs.api.event.ICustomGuiEvent", - "noppes.npcs.api.event.IAnimationEvent", - "noppes.npcs.api.event.ICustomNPCsEvent", - "noppes.npcs.api.entity.IPlayer", - "noppes.npcs.api.NpcAPI" - }; - public JaninoPlayerScript() { - super(Functions.class, DEFAULT_IMPORTS, false); + super(Functions.class, null, false); } @Override @@ -67,103 +17,5 @@ public class JaninoPlayerScript extends JaninoScript