diff --git a/java/noppes/npcs/api/handler/IHookDefinition.java b/java/noppes/npcs/api/handler/IHookDefinition.java new file mode 100644 index 0000000..6068203 --- /dev/null +++ b/java/noppes/npcs/api/handler/IHookDefinition.java @@ -0,0 +1,106 @@ +package noppes.npcs.api.handler; + +/** + * Defines metadata for a script hook, enabling rich hook registration with proper + * type information for stub generation, imports, and documentation. + * + *

Design Decision

+ * Uses {@code String eventClassName} instead of {@code Class} in the API to avoid + * classloading issues with addons (addon classes may not be loaded when hooks are registered). + * + *

Usage Example

+ *
{@code
+ * // Addon registration with full metadata
+ * IScriptHookHandler hooks = NpcAPI.Instance().getScriptHooks();
+ * hooks.registerHookDefinition("player", HookDefinition.builder("onDBCTransform")
+ *     .eventClass("com.dbc.api.event.IDBCEvent$TransformEvent")
+ *     .paramNames("event")
+ *     .requiredImports("com.dbc.api.event.IDBCEvent")
+ *     .cancelable(true)
+ *     .build());
+ * }
+ */ +public interface IHookDefinition { + + /** + * The hook name (function name in scripts). + * + * @return Hook name, e.g., "questStart", "onDBCTransform" + */ + String hookName(); + + /** + * Full qualified class name of the event type. + * Uses '$' for nested classes (e.g., "noppes.npcs.api.event.IQuestEvent$QuestStartEvent"). + * + * @return Fully qualified event class name, or null if unknown + */ + String eventClassName(); + + /** + * Parameter names for stub generation. + * + * @return Array of parameter names, defaults to ["event"] + */ + String[] paramNames(); + + /** + * Imports required for this hook's event type. + * These will be added to the script's default imports. + * + * @return Array of fully qualified class/package names to import + */ + String[] requiredImports(); + + /** + * Whether the event is cancelable (has @Cancelable annotation). + * + * @return true if event can be canceled + */ + boolean isCancelable(); + + /** + * Lazily resolve the event class. Returns null if class is not available + * (e.g., addon not loaded). + * + * @return The event class, or null if unavailable + */ + default Class getEventClass() { + String className = eventClassName(); + if (className == null || className.isEmpty()) { + return null; + } + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * Generate a usable type name for stub generation. + * Handles nested classes by converting '$' to '.'. + * + *

Examples:

+ * + * + * @return The usable type name for code generation, or null if no event class + */ + default String getUsableTypeName() { + String className = eventClassName(); + if (className == null || className.isEmpty()) { + return null; + } + + int lastDot = className.lastIndexOf('.'); + if (lastDot < 0) { + return className.replace('$', '.'); + } + + String simplePart = className.substring(lastDot + 1); + return simplePart.replace('$', '.'); + } +} diff --git a/java/noppes/npcs/api/handler/IScriptHookHandler.java b/java/noppes/npcs/api/handler/IScriptHookHandler.java index 3c7f74e..178239d 100644 --- a/java/noppes/npcs/api/handler/IScriptHookHandler.java +++ b/java/noppes/npcs/api/handler/IScriptHookHandler.java @@ -6,21 +6,72 @@ import java.util.List; * Handler for registering and retrieving script hooks. * Addon mods can register custom hooks that will appear in the script editor GUIs. * - * Available contexts: - * - "npc" - NPC script editor - * - "player" - Player scripts editor - * - "block" - Scripted block editor - * - "item" - Scripted item editor - * - "linked_item" - Linked item editor - * - "forge" - Forge scripts editor - * - "global_npc" - Global NPC scripts editor - * - "recipe" - Recipe scripts editor - * - "effect" - Custom effect scripts editor + *

Available contexts:

+ * + * + *

Rich Hook Registration (Recommended)

+ * Use {@link #registerHookDefinition(String, IHookDefinition)} for full metadata support: + *
{@code
+ * hooks.registerHookDefinition("player", HookDefinition.builder("onDBCTransform")
+ *     .eventClass(IDBCEvent.TransformEvent.class)
+ *     .requiredImports("com.dbc.api.event.IDBCEvent")
+ *     .cancelable(true)
+ *     .build());
+ * }
*/ public interface IScriptHookHandler { + // ==================== RICH HOOK REGISTRATION (NEW) ==================== + /** - * Register a hook for a single context. + * Register a hook with full metadata including event type, imports, and cancelability. + * This is the recommended way to register addon hooks. + * + * @param context The context identifier (e.g., "npc", "player", "block") + * @param definition The hook definition containing all metadata + */ + void registerHookDefinition(String context, IHookDefinition definition); + + /** + * Get the hook definition for a specific hook. + * + * @param context The context identifier + * @param hookName The hook function name + * @return The hook definition, or null if not found or no metadata available + */ + IHookDefinition getHookDefinition(String context, String hookName); + + /** + * Get all hook definitions for a context. + * + * @param context The context identifier + * @return List of all hook definitions (may be empty, never null) + */ + List getAllHookDefinitions(String context); + + /** + * Get the current hook revision number. + * This increments each time a hook is registered, allowing caches to invalidate. + * + * @return The current revision number + */ + int getHookRevision(); + + // ==================== LEGACY HOOK REGISTRATION ==================== + + /** + * Register a hook for a single context (legacy method). + * Creates a minimal definition without event type metadata. * * @param context The context identifier (e.g., "npc", "player", "block") * @param functionName The script function name (e.g., "onMyCustomEvent")