diff --git a/src/api b/src/api
index b90df20fc..7a337a570 160000
--- a/src/api
+++ b/src/api
@@ -1 +1 @@
-Subproject commit b90df20fc42f11f7ff69e79792881ebc209735eb
+Subproject commit 7a337a5704c442fb98a0fd4e0ba26210cb54af98
diff --git a/src/main/java/noppes/npcs/controllers/HookDefinition.java b/src/main/java/noppes/npcs/controllers/HookDefinition.java
new file mode 100644
index 000000000..b8b78fe70
--- /dev/null
+++ b/src/main/java/noppes/npcs/controllers/HookDefinition.java
@@ -0,0 +1,307 @@
+package noppes.npcs.controllers;
+
+import cpw.mods.fml.common.eventhandler.Cancelable;
+import noppes.npcs.api.handler.IHookDefinition;
+import noppes.npcs.janino.annotations.ParamName;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+/**
+ * Implementation of {@link IHookDefinition} with a fluent builder API.
+ *
+ *
Usage Examples
+ * {@code
+ * // Full definition with class reference
+ * HookDefinition.builder("onDBCTransform")
+ * .eventClass(IDBCEvent.TransformEvent.class)
+ * .paramNames("event")
+ * .requiredImports("com.dbc.api.event.IDBCEvent")
+ * .cancelable(true)
+ * .build();
+ *
+ * // String-based (avoids classloading issues)
+ * HookDefinition.builder("onDBCPowerUp")
+ * .eventClass("com.dbc.api.event.IDBCEvent$PowerUpEvent")
+ * .requiredImports("com.dbc.api.event.IDBCEvent")
+ * .build();
+ *
+ * // Simple hook (backward compat, no metadata)
+ * HookDefinition.simple("legacyHook");
+ * }
+ */
+public class HookDefinition implements IHookDefinition {
+
+ private final String hookName;
+ private final String eventClassName;
+ private final String[] paramNames;
+ private final String[] requiredImports;
+ private final boolean cancelable;
+
+ // Cached resolved class (transient - not serialized)
+ private transient Class> cachedEventClass;
+ private transient boolean classResolutionAttempted;
+
+ private HookDefinition(Builder builder) {
+ this.hookName = builder.hookName;
+ this.eventClassName = builder.eventClassName;
+ this.paramNames = builder.paramNames != null ? builder.paramNames : new String[]{"event"};
+ this.requiredImports = builder.requiredImports != null ? builder.requiredImports : new String[0];
+ this.cancelable = builder.cancelable;
+ }
+
+ // ==================== IHookDefinition Implementation ====================
+
+ @Override
+ public String hookName() {
+ return hookName;
+ }
+
+ @Override
+ public String eventClassName() {
+ return eventClassName;
+ }
+
+ @Override
+ public String[] paramNames() {
+ return paramNames;
+ }
+
+ @Override
+ public String[] requiredImports() {
+ return requiredImports;
+ }
+
+ @Override
+ public boolean isCancelable() {
+ return cancelable;
+ }
+
+ @Override
+ public Class> getEventClass() {
+ if (classResolutionAttempted) {
+ return cachedEventClass;
+ }
+
+ classResolutionAttempted = true;
+ if (eventClassName == null || eventClassName.isEmpty()) {
+ return null;
+ }
+
+ try {
+ cachedEventClass = Class.forName(eventClassName);
+ } catch (ClassNotFoundException e) {
+ cachedEventClass = null;
+ }
+
+ return cachedEventClass;
+ }
+
+ // ==================== Factory Methods ====================
+
+ /**
+ * Create a new builder for a hook definition.
+ *
+ * @param hookName The hook function name
+ * @return A new builder instance
+ */
+ public static Builder builder(String hookName) {
+ return new Builder(hookName);
+ }
+
+ /**
+ * Create a simple hook definition with just a name (no metadata).
+ * For backward compatibility with legacy hook registration.
+ *
+ * @param hookName The hook function name
+ * @return A minimal hook definition
+ */
+ public static HookDefinition simple(String hookName) {
+ return builder(hookName).build();
+ }
+
+ /**
+ * Create a hook definition from an interface method.
+ * Automatically extracts metadata from the method signature and annotations.
+ *
+ * @param hookName The hook name (may differ from method name)
+ * @param method The method to extract metadata from
+ * @return A hook definition with extracted metadata
+ */
+ public static HookDefinition fromMethod(String hookName, Method method) {
+ Builder builder = builder(hookName);
+
+ // Extract event type from first parameter
+ if (method.getParameterCount() > 0) {
+ Class> eventType = method.getParameterTypes()[0];
+ builder.eventClass(eventType);
+
+ // Extract @Cancelable annotation
+ if (eventType.isAnnotationPresent(Cancelable.class)) {
+ builder.cancelable(true);
+ }
+
+ // Build required imports from event type
+ String importName = getImportForClass(eventType);
+ if (importName != null) {
+ builder.requiredImports(importName);
+ }
+ }
+
+ // Extract parameter names from @ParamName annotations
+ Parameter[] params = method.getParameters();
+ if (params.length > 0) {
+ String[] names = new String[params.length];
+ for (int i = 0; i < params.length; i++) {
+ ParamName annotation = params[i].getAnnotation(ParamName.class);
+ if (annotation != null) {
+ names[i] = annotation.value();
+ } else {
+ names[i] = params[i].getType().getSimpleName().toLowerCase();
+ }
+ }
+ builder.paramNames(names);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Get the import statement needed for a class.
+ * For nested classes, returns the enclosing class.
+ */
+ private static String getImportForClass(Class> clazz) {
+ if (clazz == null || clazz.isPrimitive()) {
+ return null;
+ }
+
+ // For nested classes, get the top-level enclosing class
+ Class> enclosing = clazz;
+ while (enclosing.getEnclosingClass() != null) {
+ enclosing = enclosing.getEnclosingClass();
+ }
+
+ String name = enclosing.getName();
+ // Skip java.lang classes
+ if (name.startsWith("java.lang.")) {
+ return null;
+ }
+
+ return name;
+ }
+
+ // ==================== Builder ====================
+
+ /**
+ * Builder for creating HookDefinition instances with a fluent API.
+ */
+ public static class Builder {
+ private final String hookName;
+ private String eventClassName;
+ private String[] paramNames;
+ private String[] requiredImports;
+ private boolean cancelable;
+
+ private Builder(String hookName) {
+ if (hookName == null || hookName.isEmpty()) {
+ throw new IllegalArgumentException("hookName cannot be null or empty");
+ }
+ this.hookName = hookName;
+ }
+
+ /**
+ * Set the event class by name (avoids classloading issues).
+ *
+ * @param className Full qualified class name (use $ for nested classes)
+ * @return this builder
+ */
+ public Builder eventClass(String className) {
+ this.eventClassName = className;
+ return this;
+ }
+
+ /**
+ * Set the event class directly (convenience method).
+ * The class name will be extracted automatically.
+ *
+ * @param clazz The event class
+ * @return this builder
+ */
+ public Builder eventClass(Class> clazz) {
+ if (clazz != null) {
+ this.eventClassName = clazz.getName();
+ // Auto-detect cancelable
+ if (clazz.isAnnotationPresent(Cancelable.class)) {
+ this.cancelable = true;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set parameter names for stub generation.
+ *
+ * @param names Parameter names
+ * @return this builder
+ */
+ public Builder paramNames(String... names) {
+ this.paramNames = names;
+ return this;
+ }
+
+ /**
+ * Set required imports for this hook's event type.
+ *
+ * @param imports Fully qualified class names to import
+ * @return this builder
+ */
+ public Builder requiredImports(String... imports) {
+ this.requiredImports = imports;
+ return this;
+ }
+
+ /**
+ * Set whether the event is cancelable.
+ *
+ * @param cancelable true if the event can be canceled
+ * @return this builder
+ */
+ public Builder cancelable(boolean cancelable) {
+ this.cancelable = cancelable;
+ return this;
+ }
+
+ /**
+ * Build the hook definition.
+ *
+ * @return A new HookDefinition instance
+ */
+ public HookDefinition build() {
+ return new HookDefinition(this);
+ }
+ }
+
+ // ==================== Object Methods ====================
+
+ @Override
+ public String toString() {
+ return "HookDefinition{" +
+ "hookName='" + hookName + '\'' +
+ ", eventClassName='" + eventClassName + '\'' +
+ ", cancelable=" + cancelable +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof HookDefinition)) return false;
+ HookDefinition other = (HookDefinition) obj;
+ return hookName.equals(other.hookName);
+ }
+
+ @Override
+ public int hashCode() {
+ return hookName.hashCode();
+ }
+}
diff --git a/src/main/java/noppes/npcs/controllers/ScriptHookController.java b/src/main/java/noppes/npcs/controllers/ScriptHookController.java
index c5af0929b..abefb0930 100644
--- a/src/main/java/noppes/npcs/controllers/ScriptHookController.java
+++ b/src/main/java/noppes/npcs/controllers/ScriptHookController.java
@@ -1,5 +1,6 @@
package noppes.npcs.controllers;
+import noppes.npcs.api.handler.IHookDefinition;
import noppes.npcs.api.handler.IScriptHookHandler;
import noppes.npcs.constants.EnumScriptType;
import noppes.npcs.constants.ScriptContext;
@@ -14,17 +15,33 @@ import java.util.Map;
/**
* Controller for managing script hooks.
* Allows addon mods to register custom hooks that appear in script editor GUIs.
+ *
+ * Rich Hook Registration
+ * Supports full hook definitions with metadata for proper stub generation:
+ * {@code
+ * ScriptHookController.Instance.registerHookDefinition("player",
+ * HookDefinition.builder("onDBCTransform")
+ * .eventClass(IDBCEvent.TransformEvent.class)
+ * .requiredImports("com.dbc.api.event.IDBCEvent")
+ * .build());
+ * }
*/
public class ScriptHookController implements IScriptHookHandler {
public static ScriptHookController Instance;
- // Built-in hooks per context
+ // Built-in hooks per context (legacy string list)
private final Map> builtInHooks = new HashMap<>();
- // Addon-registered hooks per context
+ // Addon-registered hooks per context (legacy string list)
private final Map> addonHooks = new HashMap<>();
+ // Rich hook definitions per context (NEW)
+ private final Map> hookDefinitions = new HashMap<>();
+
+ // Revision counter for cache invalidation (NEW)
+ private int hookRevision = 0;
+
public ScriptHookController() {
Instance = this;
initializeBuiltInHooks();
@@ -36,160 +53,24 @@ public class ScriptHookController implements IScriptHookHandler {
if (!context.hookContext.isEmpty()) {
builtInHooks.put(context.hookContext, new ArrayList<>());
addonHooks.put(context.hookContext, new ArrayList<>());
+ hookDefinitions.put(context.hookContext, new HashMap<>());
}
}
- // NPC hooks
- registerBuiltIn(ScriptContext.NPC,
- EnumScriptType.INIT.function,
- EnumScriptType.TICK.function,
- EnumScriptType.INTERACT.function,
- EnumScriptType.DIALOG.function,
- EnumScriptType.DAMAGED.function,
- EnumScriptType.KILLED.function,
- EnumScriptType.ATTACK_MELEE.function,
- EnumScriptType.ATTACK_SWING.function,
- EnumScriptType.RANGED_LAUNCHED.function,
- EnumScriptType.TARGET.function,
- EnumScriptType.COLLIDE.function,
- EnumScriptType.KILLS.function,
- EnumScriptType.DIALOG_CLOSE.function,
- EnumScriptType.TIMER.function,
- EnumScriptType.TARGET_LOST.function,
- EnumScriptType.PROJECTILE_TICK.function,
- EnumScriptType.PROJECTILE_IMPACT.function,
- EnumScriptType.ABILITY_TICK.function,
- EnumScriptType.ABILITY_EXECUTE.function,
- EnumScriptType.ABILITY_HIT.function,
- EnumScriptType.ABILITY_START.function,
- EnumScriptType.ABILITY_INTERRUPT.function,
- EnumScriptType.ABILITY_COMPLETE.function
- );
+ // NPC hooks with full definitions
+ initializeNpcHooks();
- // Player hooks
- registerBuiltIn(ScriptContext.PLAYER,
- EnumScriptType.INIT.function,
- EnumScriptType.TICK.function,
- EnumScriptType.INTERACT.function,
- EnumScriptType.RIGHT_CLICK.function,
- EnumScriptType.ATTACK.function,
- EnumScriptType.ATTACKED.function,
- EnumScriptType.DAMAGED_ENTITY.function,
- EnumScriptType.DAMAGED.function,
- EnumScriptType.KILLS.function,
- EnumScriptType.KILLED.function,
- EnumScriptType.DROP.function,
- EnumScriptType.RESPAWN.function,
- EnumScriptType.BREAK_BLOCK.function,
- EnumScriptType.CHAT.function,
- EnumScriptType.LOGIN.function,
- EnumScriptType.LOGOUT.function,
- EnumScriptType.KEY_PRESSED.function,
- EnumScriptType.MOUSE_CLICKED.function,
- EnumScriptType.TOSS.function,
- EnumScriptType.PICKUP.function,
- EnumScriptType.PICKUP_XP.function,
- EnumScriptType.RANGED_CHARGE.function,
- EnumScriptType.RANGED_LAUNCHED.function,
- EnumScriptType.TIMER.function,
- EnumScriptType.START_USING_ITEM.function,
- EnumScriptType.USING_ITEM.function,
- EnumScriptType.STOP_USING_ITEM.function,
- EnumScriptType.FINISH_USING_ITEM.function,
- EnumScriptType.CONTAINER_OPEN.function,
- EnumScriptType.USE_HOE.function,
- EnumScriptType.BONEMEAL.function,
- EnumScriptType.FILL_BUCKET.function,
- EnumScriptType.JUMP.function,
- EnumScriptType.FALL.function,
- EnumScriptType.WAKE_UP.function,
- EnumScriptType.SLEEP.function,
- EnumScriptType.PLAYSOUND.function,
- EnumScriptType.LIGHTNING.function,
- EnumScriptType.CHANGED_DIM.function,
- EnumScriptType.QUEST_START.function,
- EnumScriptType.QUEST_COMPLETED.function,
- EnumScriptType.QUEST_TURNIN.function,
- EnumScriptType.FACTION_POINTS.function,
- EnumScriptType.DIALOG_OPEN.function,
- EnumScriptType.DIALOG_OPTION.function,
- EnumScriptType.DIALOG_CLOSE.function,
- EnumScriptType.SCRIPT_COMMAND.function,
- EnumScriptType.CUSTOM_GUI_CLOSED.function,
- EnumScriptType.CUSTOM_GUI_BUTTON.function,
- EnumScriptType.CUSTOM_GUI_SLOT.function,
- EnumScriptType.CUSTOM_GUI_SLOT_CLICKED.function,
- EnumScriptType.CUSTOM_GUI_SCROLL.function,
- EnumScriptType.CUSTOM_GUI_TEXTFIELD.function,
- EnumScriptType.PARTY_QUEST_COMPLETED.function,
- EnumScriptType.PARTY_QUEST_SET.function,
- EnumScriptType.PARTY_QUEST_TURNED_IN.function,
- EnumScriptType.PARTY_INVITE.function,
- EnumScriptType.PARTY_KICK.function,
- EnumScriptType.PARTY_LEAVE.function,
- EnumScriptType.PARTY_DISBAND.function,
- EnumScriptType.ANIMATION_START.function,
- EnumScriptType.ANIMATION_END.function,
- EnumScriptType.ANIMATION_FRAME_ENTER.function,
- EnumScriptType.ANIMATION_FRAME_EXIT.function,
- EnumScriptType.PROFILE_CHANGE.function,
- EnumScriptType.PROFILE_REMOVE.function,
- EnumScriptType.PROFILE_CREATE.function,
- EnumScriptType.ON_EFFECT_ADD.function,
- EnumScriptType.ON_EFFECT_TICK.function,
- EnumScriptType.ON_EFFECT_REMOVE.function
- );
+ // Player hooks with full definitions
+ initializePlayerHooks();
- // Block hooks
- registerBuiltIn(ScriptContext.BLOCK,
- EnumScriptType.INIT.function,
- EnumScriptType.TICK.function,
- EnumScriptType.INTERACT.function,
- EnumScriptType.FALLEN_UPON.function,
- EnumScriptType.REDSTONE.function,
- EnumScriptType.BROKEN.function,
- EnumScriptType.EXPLODED.function,
- EnumScriptType.RAIN_FILLED.function,
- EnumScriptType.NEIGHBOR_CHANGED.function,
- EnumScriptType.CLICKED.function,
- EnumScriptType.HARVESTED.function,
- EnumScriptType.COLLIDE.function,
- EnumScriptType.TIMER.function
- );
+ // Block hooks with full definitions
+ initializeBlockHooks();
- // Item hooks
- registerBuiltIn(ScriptContext.ITEM,
- EnumScriptType.INIT.function,
- EnumScriptType.TICK.function,
- EnumScriptType.TOSSED.function,
- EnumScriptType.PICKEDUP.function,
- EnumScriptType.SPAWN.function,
- EnumScriptType.INTERACT.function,
- EnumScriptType.RIGHT_CLICK.function,
- EnumScriptType.ATTACK.function,
- EnumScriptType.START_USING_ITEM.function,
- EnumScriptType.USING_ITEM.function,
- EnumScriptType.STOP_USING_ITEM.function,
- EnumScriptType.FINISH_USING_ITEM.function
- );
+ // Item hooks with full definitions
+ initializeItemHooks();
- // Linked item hooks
- registerBuiltIn(ScriptContext.LINKED_ITEM,
- EnumScriptType.LINKED_ITEM_BUILD.function,
- EnumScriptType.LINKED_ITEM_VERSION.function,
- EnumScriptType.INIT.function,
- EnumScriptType.TICK.function,
- EnumScriptType.TOSSED.function,
- EnumScriptType.PICKEDUP.function,
- EnumScriptType.SPAWN.function,
- EnumScriptType.INTERACT.function,
- EnumScriptType.RIGHT_CLICK.function,
- EnumScriptType.ATTACK.function,
- EnumScriptType.START_USING_ITEM.function,
- EnumScriptType.USING_ITEM.function,
- EnumScriptType.STOP_USING_ITEM.function,
- EnumScriptType.FINISH_USING_ITEM.function
- );
+ // Linked item hooks with full definitions
+ initializeLinkedItemHooks();
// Recipe hooks
registerBuiltIn(ScriptContext.RECIPE,
@@ -211,6 +92,372 @@ public class ScriptHookController implements IScriptHookHandler {
);
}
+ private void initializeNpcHooks() {
+ String ctx = ScriptContext.NPC.hookContext;
+ String npcImport = "noppes.npcs.api.event.INpcEvent";
+ String projImport = "noppes.npcs.api.event.IProjectileEvent";
+ String abilityImport = "noppes.npcs.api.event.IAbilityEvent";
+
+ // Core lifecycle
+ registerBuiltInDefinition(ctx, EnumScriptType.INIT.function,
+ "noppes.npcs.api.event.INpcEvent$InitEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TICK.function,
+ "noppes.npcs.api.event.INpcEvent$UpdateEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TIMER.function,
+ "noppes.npcs.api.event.INpcEvent$TimerEvent", npcImport);
+
+ // Interaction
+ registerBuiltInDefinition(ctx, EnumScriptType.INTERACT.function,
+ "noppes.npcs.api.event.INpcEvent$InteractEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DIALOG.function,
+ "noppes.npcs.api.event.INpcEvent$DialogEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DIALOG_CLOSE.function,
+ "noppes.npcs.api.event.INpcEvent$DialogClosedEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.COLLIDE.function,
+ "noppes.npcs.api.event.INpcEvent$CollideEvent", npcImport);
+
+ // Combat
+ registerBuiltInDefinition(ctx, EnumScriptType.DAMAGED.function,
+ "noppes.npcs.api.event.INpcEvent$DamagedEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.KILLED.function,
+ "noppes.npcs.api.event.INpcEvent$DiedEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.KILLS.function,
+ "noppes.npcs.api.event.INpcEvent$KilledEntityEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACK_MELEE.function,
+ "noppes.npcs.api.event.INpcEvent$MeleeAttackEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACK_SWING.function,
+ "noppes.npcs.api.event.INpcEvent$SwingEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RANGED_LAUNCHED.function,
+ "noppes.npcs.api.event.INpcEvent$RangedLaunchedEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TARGET.function,
+ "noppes.npcs.api.event.INpcEvent$TargetEvent", npcImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TARGET_LOST.function,
+ "noppes.npcs.api.event.INpcEvent$TargetLostEvent", npcImport);
+
+ // Projectile events
+ registerBuiltInDefinition(ctx, EnumScriptType.PROJECTILE_TICK.function,
+ "noppes.npcs.api.event.IProjectileEvent$UpdateEvent", projImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PROJECTILE_IMPACT.function,
+ "noppes.npcs.api.event.IProjectileEvent$ImpactEvent", projImport);
+
+ // Ability events
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_START.function,
+ "noppes.npcs.api.event.IAbilityEvent$StartEvent", abilityImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_EXECUTE.function,
+ "noppes.npcs.api.event.IAbilityEvent$ExecuteEvent", abilityImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_HIT.function,
+ "noppes.npcs.api.event.IAbilityEvent$HitEvent", abilityImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_TICK.function,
+ "noppes.npcs.api.event.IAbilityEvent$TickEvent", abilityImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_INTERRUPT.function,
+ "noppes.npcs.api.event.IAbilityEvent$InterruptEvent", abilityImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ABILITY_COMPLETE.function,
+ "noppes.npcs.api.event.IAbilityEvent$CompleteEvent", abilityImport);
+ }
+
+ private void initializePlayerHooks() {
+ String ctx = ScriptContext.PLAYER.hookContext;
+ String playerImport = "noppes.npcs.api.event.IPlayerEvent";
+ String questImport = "noppes.npcs.api.event.IQuestEvent";
+ String dialogImport = "noppes.npcs.api.event.IDialogEvent";
+ String factionImport = "noppes.npcs.api.event.IFactionEvent";
+ String partyImport = "noppes.npcs.api.event.IPartyEvent";
+ String guiImport = "noppes.npcs.api.event.ICustomGuiEvent";
+ String animImport = "noppes.npcs.api.event.IAnimationEvent";
+
+ // Core lifecycle
+ registerBuiltInDefinition(ctx, EnumScriptType.INIT.function,
+ "noppes.npcs.api.event.IPlayerEvent$InitEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TICK.function,
+ "noppes.npcs.api.event.IPlayerEvent$UpdateEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TIMER.function,
+ "noppes.npcs.api.event.IPlayerEvent$TimerEvent", playerImport);
+
+ // Combat
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACK.function,
+ "noppes.npcs.api.event.IPlayerEvent$AttackEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACKED.function,
+ "noppes.npcs.api.event.IPlayerEvent$AttackedEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DAMAGED.function,
+ "noppes.npcs.api.event.IPlayerEvent$DamagedEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DAMAGED_ENTITY.function,
+ "noppes.npcs.api.event.IPlayerEvent$DamagedEntityEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.KILLS.function,
+ "noppes.npcs.api.event.IPlayerEvent$KilledEntityEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.KILLED.function,
+ "noppes.npcs.api.event.IPlayerEvent$DiedEvent", playerImport);
+
+ // Interaction
+ registerBuiltInDefinition(ctx, EnumScriptType.INTERACT.function,
+ "noppes.npcs.api.event.IPlayerEvent$InteractEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RIGHT_CLICK.function,
+ "noppes.npcs.api.event.IPlayerEvent$RightClickEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.BREAK_BLOCK.function,
+ "noppes.npcs.api.event.IPlayerEvent$BreakEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CHAT.function,
+ "noppes.npcs.api.event.IPlayerEvent$ChatEvent", playerImport);
+
+ // Connection
+ registerBuiltInDefinition(ctx, EnumScriptType.LOGIN.function,
+ "noppes.npcs.api.event.IPlayerEvent$LoginEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.LOGOUT.function,
+ "noppes.npcs.api.event.IPlayerEvent$LogoutEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RESPAWN.function,
+ "noppes.npcs.api.event.IPlayerEvent$RespawnEvent", playerImport);
+
+ // Input
+ registerBuiltInDefinition(ctx, EnumScriptType.KEY_PRESSED.function,
+ "noppes.npcs.api.event.IPlayerEvent$KeyPressedEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.MOUSE_CLICKED.function,
+ "noppes.npcs.api.event.IPlayerEvent$MouseClickedEvent", playerImport);
+
+ // Items
+ registerBuiltInDefinition(ctx, EnumScriptType.PICKUP.function,
+ "noppes.npcs.api.event.IPlayerEvent$PickUpEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TOSS.function,
+ "noppes.npcs.api.event.IPlayerEvent$TossEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DROP.function,
+ "noppes.npcs.api.event.IPlayerEvent$DropEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PICKUP_XP.function,
+ "noppes.npcs.api.event.IPlayerEvent$PickupXPEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.START_USING_ITEM.function,
+ "noppes.npcs.api.event.IPlayerEvent$StartUsingItem", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.USING_ITEM.function,
+ "noppes.npcs.api.event.IPlayerEvent$UsingItem", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.STOP_USING_ITEM.function,
+ "noppes.npcs.api.event.IPlayerEvent$StopUsingItem", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FINISH_USING_ITEM.function,
+ "noppes.npcs.api.event.IPlayerEvent$FinishUsingItem", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CONTAINER_OPEN.function,
+ "noppes.npcs.api.event.IPlayerEvent$ContainerOpen", playerImport);
+
+ // Movement
+ registerBuiltInDefinition(ctx, EnumScriptType.JUMP.function,
+ "noppes.npcs.api.event.IPlayerEvent$JumpEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FALL.function,
+ "noppes.npcs.api.event.IPlayerEvent$FallEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CHANGED_DIM.function,
+ "noppes.npcs.api.event.IPlayerEvent$ChangedDimension", playerImport);
+
+ // World
+ registerBuiltInDefinition(ctx, EnumScriptType.RANGED_CHARGE.function,
+ "noppes.npcs.api.event.IPlayerEvent$RangedChargeEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RANGED_LAUNCHED.function,
+ "noppes.npcs.api.event.IPlayerEvent$RangedLaunchedEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.USE_HOE.function,
+ "noppes.npcs.api.event.IPlayerEvent$UseHoeEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.BONEMEAL.function,
+ "noppes.npcs.api.event.IPlayerEvent$BonemealEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FILL_BUCKET.function,
+ "noppes.npcs.api.event.IPlayerEvent$FillBucketEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.WAKE_UP.function,
+ "noppes.npcs.api.event.IPlayerEvent$WakeUpEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.SLEEP.function,
+ "noppes.npcs.api.event.IPlayerEvent$SleepEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PLAYSOUND.function,
+ "noppes.npcs.api.event.IPlayerEvent$SoundEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.LIGHTNING.function,
+ "noppes.npcs.api.event.IPlayerEvent$LightningEvent", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.SCRIPT_COMMAND.function,
+ "noppes.npcs.api.event.ICustomNPCsEvent$ScriptedCommandEvent", "noppes.npcs.api.event.ICustomNPCsEvent");
+
+ // Quest events
+ registerBuiltInDefinition(ctx, EnumScriptType.QUEST_START.function,
+ "noppes.npcs.api.event.IQuestEvent$QuestStartEvent", questImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.QUEST_COMPLETED.function,
+ "noppes.npcs.api.event.IQuestEvent$QuestCompletedEvent", questImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.QUEST_TURNIN.function,
+ "noppes.npcs.api.event.IQuestEvent$QuestTurnedInEvent", questImport);
+
+ // Dialog events
+ registerBuiltInDefinition(ctx, EnumScriptType.DIALOG_OPEN.function,
+ "noppes.npcs.api.event.IDialogEvent$DialogOpen", dialogImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DIALOG_OPTION.function,
+ "noppes.npcs.api.event.IDialogEvent$DialogOption", dialogImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.DIALOG_CLOSE.function,
+ "noppes.npcs.api.event.IDialogEvent$DialogClosed", dialogImport);
+
+ // Faction events
+ registerBuiltInDefinition(ctx, EnumScriptType.FACTION_POINTS.function,
+ "noppes.npcs.api.event.IFactionEvent$FactionPoints", factionImport);
+
+ // Custom GUI events
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_CLOSED.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$CloseEvent", guiImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_BUTTON.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$ButtonEvent", guiImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_SLOT.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$SlotEvent", guiImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_SLOT_CLICKED.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$SlotClickEvent", guiImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_SCROLL.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$ScrollEvent", guiImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CUSTOM_GUI_TEXTFIELD.function,
+ "noppes.npcs.api.event.ICustomGuiEvent$UnfocusedEvent", guiImport);
+
+ // Party events
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_QUEST_COMPLETED.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyQuestCompletedEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_QUEST_SET.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyQuestSetEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_QUEST_TURNED_IN.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyQuestTurnedInEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_INVITE.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyInviteEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_KICK.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyKickEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_LEAVE.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyLeaveEvent", partyImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PARTY_DISBAND.function,
+ "noppes.npcs.api.event.IPartyEvent$PartyDisbandEvent", partyImport);
+
+ // Animation events
+ registerBuiltInDefinition(ctx, EnumScriptType.ANIMATION_START.function,
+ "noppes.npcs.api.event.IAnimationEvent$Started", animImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ANIMATION_END.function,
+ "noppes.npcs.api.event.IAnimationEvent$Ended", animImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ANIMATION_FRAME_ENTER.function,
+ "noppes.npcs.api.event.IAnimationEvent$IFrameEvent$Entered", animImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ANIMATION_FRAME_EXIT.function,
+ "noppes.npcs.api.event.IAnimationEvent$IFrameEvent$Exited", animImport);
+
+ // Profile events
+ registerBuiltInDefinition(ctx, EnumScriptType.PROFILE_CHANGE.function,
+ "noppes.npcs.api.event.IPlayerEvent$ProfileEvent$Changed", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PROFILE_REMOVE.function,
+ "noppes.npcs.api.event.IPlayerEvent$ProfileEvent$Removed", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PROFILE_CREATE.function,
+ "noppes.npcs.api.event.IPlayerEvent$ProfileEvent$Create", playerImport);
+
+ // Effect events (for player context)
+ registerBuiltInDefinition(ctx, EnumScriptType.ON_EFFECT_ADD.function,
+ "noppes.npcs.api.event.IPlayerEvent$EffectEvent$Added", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ON_EFFECT_TICK.function,
+ "noppes.npcs.api.event.IPlayerEvent$EffectEvent$Ticked", playerImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ON_EFFECT_REMOVE.function,
+ "noppes.npcs.api.event.IPlayerEvent$EffectEvent$Removed", playerImport);
+ }
+
+ private void initializeBlockHooks() {
+ String ctx = ScriptContext.BLOCK.hookContext;
+ String blockImport = "noppes.npcs.api.event.IBlockEvent";
+
+ registerBuiltInDefinition(ctx, EnumScriptType.INIT.function,
+ "noppes.npcs.api.event.IBlockEvent$InitEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TICK.function,
+ "noppes.npcs.api.event.IBlockEvent$UpdateEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.INTERACT.function,
+ "noppes.npcs.api.event.IBlockEvent$InteractEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FALLEN_UPON.function,
+ "noppes.npcs.api.event.IBlockEvent$EntityFallenUponEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.REDSTONE.function,
+ "noppes.npcs.api.event.IBlockEvent$RedstoneEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.BROKEN.function,
+ "noppes.npcs.api.event.IBlockEvent$BreakEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.EXPLODED.function,
+ "noppes.npcs.api.event.IBlockEvent$ExplodedEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RAIN_FILLED.function,
+ "noppes.npcs.api.event.IBlockEvent$RainFillEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.NEIGHBOR_CHANGED.function,
+ "noppes.npcs.api.event.IBlockEvent$NeighborChangedEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.CLICKED.function,
+ "noppes.npcs.api.event.IBlockEvent$ClickedEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.HARVESTED.function,
+ "noppes.npcs.api.event.IBlockEvent$HarvestedEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.COLLIDE.function,
+ "noppes.npcs.api.event.IBlockEvent$CollidedEvent", blockImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TIMER.function,
+ "noppes.npcs.api.event.IBlockEvent$TimerEvent", blockImport);
+ }
+
+ private void initializeItemHooks() {
+ String ctx = ScriptContext.ITEM.hookContext;
+ String itemImport = "noppes.npcs.api.event.IItemEvent";
+
+ registerBuiltInDefinition(ctx, EnumScriptType.INIT.function,
+ "noppes.npcs.api.event.IItemEvent$InitEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TICK.function,
+ "noppes.npcs.api.event.IItemEvent$UpdateEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TOSSED.function,
+ "noppes.npcs.api.event.IItemEvent$TossedEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PICKEDUP.function,
+ "noppes.npcs.api.event.IItemEvent$PickedUpEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.SPAWN.function,
+ "noppes.npcs.api.event.IItemEvent$SpawnEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.INTERACT.function,
+ "noppes.npcs.api.event.IItemEvent$InteractEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RIGHT_CLICK.function,
+ "noppes.npcs.api.event.IItemEvent$RightClickEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACK.function,
+ "noppes.npcs.api.event.IItemEvent$AttackEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.START_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$StartUsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$UsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.STOP_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$StopUsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FINISH_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$FinishUsingItem", itemImport);
+ }
+
+ private void initializeLinkedItemHooks() {
+ String ctx = ScriptContext.LINKED_ITEM.hookContext;
+ String itemImport = "noppes.npcs.api.event.IItemEvent";
+ String linkedImport = "noppes.npcs.api.event.ILinkedItemEvent";
+
+ // Linked item specific hooks
+ registerBuiltInDefinition(ctx, EnumScriptType.LINKED_ITEM_BUILD.function,
+ "noppes.npcs.api.event.ILinkedItemEvent$BuildEvent", linkedImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.LINKED_ITEM_VERSION.function,
+ "noppes.npcs.api.event.ILinkedItemEvent$VersionChangeEvent", linkedImport);
+
+ // Standard item hooks
+ registerBuiltInDefinition(ctx, EnumScriptType.INIT.function,
+ "noppes.npcs.api.event.IItemEvent$InitEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TICK.function,
+ "noppes.npcs.api.event.IItemEvent$UpdateEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.TOSSED.function,
+ "noppes.npcs.api.event.IItemEvent$TossedEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.PICKEDUP.function,
+ "noppes.npcs.api.event.IItemEvent$PickedUpEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.SPAWN.function,
+ "noppes.npcs.api.event.IItemEvent$SpawnEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.INTERACT.function,
+ "noppes.npcs.api.event.IItemEvent$InteractEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.RIGHT_CLICK.function,
+ "noppes.npcs.api.event.IItemEvent$RightClickEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.ATTACK.function,
+ "noppes.npcs.api.event.IItemEvent$AttackEvent", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.START_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$StartUsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$UsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.STOP_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$StopUsingItem", itemImport);
+ registerBuiltInDefinition(ctx, EnumScriptType.FINISH_USING_ITEM.function,
+ "noppes.npcs.api.event.IItemEvent$FinishUsingItem", itemImport);
+ }
+
+ /**
+ * Register a built-in hook with full definition metadata.
+ */
+ private void registerBuiltInDefinition(String context, String hookName, String eventClassName, String... imports) {
+ HookDefinition def = HookDefinition.builder(hookName)
+ .eventClass(eventClassName)
+ .requiredImports(imports)
+ .build();
+
+ Map contextDefs = hookDefinitions.get(context);
+ if (contextDefs == null) {
+ contextDefs = new HashMap<>();
+ hookDefinitions.put(context, contextDefs);
+ }
+ contextDefs.put(hookName, def);
+
+ // Also register in legacy string list
+ registerBuiltIn(context, hookName);
+ }
+
/**
* Register built-in hooks for a ScriptContext.
*/
@@ -229,6 +476,72 @@ public class ScriptHookController implements IScriptHookHandler {
}
}
+ // ==================== RICH HOOK REGISTRATION (NEW API) ====================
+
+ @Override
+ public void registerHookDefinition(String context, IHookDefinition definition) {
+ if (context == null || definition == null || definition.hookName() == null) {
+ return;
+ }
+
+ Map contextDefs = hookDefinitions.get(context);
+ if (contextDefs == null) {
+ contextDefs = new HashMap<>();
+ hookDefinitions.put(context, contextDefs);
+ }
+
+ // Store as HookDefinition (implementation)
+ if (definition instanceof HookDefinition) {
+ contextDefs.put(definition.hookName(), (HookDefinition) definition);
+ } else {
+ // Convert foreign IHookDefinition to HookDefinition
+ HookDefinition def = HookDefinition.builder(definition.hookName())
+ .eventClass(definition.eventClassName())
+ .paramNames(definition.paramNames())
+ .requiredImports(definition.requiredImports())
+ .cancelable(definition.isCancelable())
+ .build();
+ contextDefs.put(definition.hookName(), def);
+ }
+
+ // Also add to legacy addon hooks list for backward compatibility
+ registerHook(context, definition.hookName());
+
+ // Increment revision for cache invalidation
+ hookRevision++;
+ }
+
+ @Override
+ public IHookDefinition getHookDefinition(String context, String hookName) {
+ if (context == null || hookName == null) {
+ return null;
+ }
+
+ Map contextDefs = hookDefinitions.get(context);
+ return contextDefs != null ? contextDefs.get(hookName) : null;
+ }
+
+ @Override
+ public List getAllHookDefinitions(String context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+
+ Map contextDefs = hookDefinitions.get(context);
+ if (contextDefs == null || contextDefs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return new ArrayList<>(contextDefs.values());
+ }
+
+ @Override
+ public int getHookRevision() {
+ return hookRevision;
+ }
+
+ // ==================== CONVENIENCE METHODS ====================
+
/**
* Convenience method to register a hook using ScriptContext.
* @param context The script context
@@ -248,7 +561,18 @@ public class ScriptHookController implements IScriptHookHandler {
registerHook(context.hookContext, fn);
}
}
-
+
+ /**
+ * Convenience method to register a hook definition using ScriptContext.
+ * @param context The script context
+ * @param definition The hook definition
+ */
+ public void registerHookDefinition(ScriptContext context, IHookDefinition definition) {
+ registerHookDefinition(context.hookContext, definition);
+ }
+
+ // ==================== LEGACY HOOK REGISTRATION ====================
+
@Override
public void registerHook(String context, String functionName) {
if (context == null || functionName == null || functionName.isEmpty()) {
diff --git a/src/main/java/noppes/npcs/janino/JaninoScript.java b/src/main/java/noppes/npcs/janino/JaninoScript.java
index 8734304d9..6f6d6e55b 100644
--- a/src/main/java/noppes/npcs/janino/JaninoScript.java
+++ b/src/main/java/noppes/npcs/janino/JaninoScript.java
@@ -6,9 +6,11 @@ import io.github.somehussar.janinoloader.api.script.IScriptClassBody;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcs;
import noppes.npcs.NBTTags;
+import noppes.npcs.api.handler.IHookDefinition;
import noppes.npcs.config.ConfigScript;
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;
@@ -52,6 +54,10 @@ public abstract class JaninoScript implements IScriptUnit {
private final String[] defaultImports;
+ // Hook definition cache for fast lookup (NEW)
+ private Map hookDefinitionCache;
+ private int lastHookRevision = -1;
+
protected JaninoScript(Class type, String[] defaultImports, boolean isClient) {
this.type = type;
this.defaultImports = defaultImports != null ? defaultImports : new String[0];
@@ -71,6 +77,79 @@ public abstract class JaninoScript implements IScriptUnit {
this(type, defaultImports, false);
}
+ // ==================== HOOK CONTEXT (Abstract) ====================
+
+ /**
+ * Get the hook context identifier for this script type.
+ * Used to look up hook definitions from the registry.
+ *
+ * @return Context string, e.g., "npc", "player", "block"
+ */
+ protected abstract String getHookContext();
+
+ // ==================== HOOK DEFINITION CACHE ====================
+
+ /**
+ * Get hook definition from cache, rebuilding cache if revision changed.
+ *
+ * @param hookName The hook name to look up
+ * @return The hook definition, or null if not found
+ */
+ protected IHookDefinition getHookDefinition(String hookName) {
+ if (ScriptHookController.Instance == null) {
+ return null;
+ }
+
+ int currentRevision = ScriptHookController.Instance.getHookRevision();
+ if (hookDefinitionCache == null || currentRevision != lastHookRevision) {
+ rebuildHookDefinitionCache();
+ lastHookRevision = currentRevision;
+ }
+
+ return hookDefinitionCache.get(hookName);
+ }
+
+ /**
+ * Rebuild the hook definition cache from the registry.
+ */
+ private void rebuildHookDefinitionCache() {
+ hookDefinitionCache = new HashMap<>();
+ String context = getHookContext();
+
+ if (context == null || context.isEmpty() || ScriptHookController.Instance == null) {
+ return;
+ }
+
+ for (IHookDefinition def : ScriptHookController.Instance.getAllHookDefinitions(context)) {
+ hookDefinitionCache.put(def.hookName(), def);
+ }
+ }
+
+ /**
+ * Get all required imports from hook definitions plus default imports.
+ *
+ * @return Array of all import statements needed
+ */
+ protected String[] collectAllImports() {
+ Set allImports = new LinkedHashSet<>();
+
+ // Add default imports first
+ Collections.addAll(allImports, defaultImports);
+
+ // Add imports from all hook definitions for this context
+ String context = getHookContext();
+ if (context != null && !context.isEmpty() && ScriptHookController.Instance != null) {
+ for (IHookDefinition def : ScriptHookController.Instance.getAllHookDefinitions(context)) {
+ String[] imports = def.requiredImports();
+ if (imports != null) {
+ Collections.addAll(allImports, imports);
+ }
+ }
+ }
+
+ return allImports.toArray(new String[0]);
+ }
+
protected T getUnsafe() {
return scriptBody.get();
}
@@ -119,11 +198,16 @@ public abstract class JaninoScript implements IScriptUnit {
}
/**
- * Feed the code into the engine and compile it
+ * Feed the code into the engine and compile it.
+ * Rebuilds imports from hook definitions before compilation.
*/
public void compileScript(String code) {
try {
+ // Rebuild imports from current hook definitions + defaults
+ String[] allImports = collectAllImports();
+ builder.setDefaultImports(allImports);
+
scriptBody.setScript(code);
} catch (InternalCompilerException e ) {
Throwable parentCause = null;
@@ -330,65 +414,43 @@ public abstract class JaninoScript implements IScriptUnit {
return script != null && !script.isEmpty();
}
- @Override
- public String generateHookStub(String hookName, Object hookData) {
- hookResolver.clearResolutionCaches();
+ @Override
+ public String generateHookStub(String hookName, Object hookData) {
+ hookResolver.clearResolutionCaches();
- Method method = hookResolver.getMethodForDisplayName(hookName);
- if (method != null)
- return generateMethodStub(method);
+ // 1. Try interface method (existing @ScriptHook annotated methods)
+ Method method = hookResolver.getMethodForDisplayName(hookName);
+ if (method != null)
+ return generateMethodStub(method);
- // Fallback: try to find the event class by naming convention
- String eventType = findEventTypeForHook(hookName);
- if (eventType != null) {
- return String.format("public void %s(%s event) {\n \n}\n", hookName, eventType);
+ // 2. Look up in hook definition registry (addon hooks, dynamic hooks)
+ IHookDefinition def = getHookDefinition(hookName);
+ if (def != null) {
+ return generateStubFromDefinition(def);
}
- // Last resort: generic Object parameter
+ // 3. Last resort: generic Object parameter
return String.format("public void %s(Object event) {\n \n}\n", hookName);
}
/**
- * Tries to find the event type for a hook by searching API event interfaces.
- * Uses naming convention: hookName "questStart" -> event class "QuestStartEvent"
+ * Generate a method stub from a hook definition.
*
- * @param hookName The hook name (e.g., "questStart", "dialogOpen")
- * @return The usable type name (e.g., "IQuestEvent.QuestStartEvent") or null if not found
+ * @param def The hook definition containing type info
+ * @return Generated stub code
*/
- private String findEventTypeForHook(String hookName) {
- if (hookName == null || hookName.isEmpty())
- return null;
+ private String generateStubFromDefinition(IHookDefinition def) {
+ String typeName = def.getUsableTypeName();
+ String[] params = def.paramNames();
+ String paramName = (params != null && params.length > 0) ? params[0] : "event";
- // Convert hook name to expected event class name
- // questStart -> QuestStartEvent
- String expectedEventName = Character.toUpperCase(hookName.charAt(0)) + hookName.substring(1) + "Event";
-
- // Search in known API event interfaces
- Class>[] eventInterfaces = {
- noppes.npcs.api.event.IPlayerEvent.class,
- noppes.npcs.api.event.INpcEvent.class,
- noppes.npcs.api.event.IQuestEvent.class,
- noppes.npcs.api.event.IDialogEvent.class,
- noppes.npcs.api.event.IFactionEvent.class,
- noppes.npcs.api.event.IPartyEvent.class,
- noppes.npcs.api.event.ICustomGuiEvent.class,
- noppes.npcs.api.event.IAnimationEvent.class,
- noppes.npcs.api.event.IBlockEvent.class,
- noppes.npcs.api.event.IItemEvent.class,
- noppes.npcs.api.event.IProjectileEvent.class,
- noppes.npcs.api.event.IAbilityEvent.class,
- noppes.npcs.api.event.IForgeEvent.class
- };
-
- for (Class> eventInterface : eventInterfaces) {
- for (Class> nested : eventInterface.getDeclaredClasses()) {
- if (nested.getSimpleName().equals(expectedEventName)) {
- return getUsableTypeName(nested);
- }
- }
+ if (typeName == null || typeName.isEmpty()) {
+ return String.format("public void %s(Object %s) {\n \n}\n",
+ def.hookName(), paramName);
}
- return null;
+ return String.format("public void %s(%s %s) {\n \n}\n",
+ def.hookName(), typeName, paramName);
}
/**
diff --git a/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java b/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java
index 1af8dea35..637374775 100644
--- a/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java
+++ b/src/main/java/noppes/npcs/janino/impl/JaninoNpcScript.java
@@ -17,6 +17,7 @@ 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",
@@ -31,6 +32,11 @@ public class JaninoNpcScript extends JaninoScript {
super(Functions.class, DEFAULT_IMPORTS);
}
+ @Override
+ protected String getHookContext() {
+ return "npc";
+ }
+
/**
* Interface defining the hook methods available for NPC scripts.
* Each method corresponds to an NPC event from ScriptHookController CONTEXT_NPC.
diff --git a/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java b/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java
index c755c700c..d29b1b15e 100644
--- a/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java
+++ b/src/main/java/noppes/npcs/janino/impl/JaninoPlayerScript.java
@@ -43,6 +43,7 @@ public class JaninoPlayerScript extends JaninoScript