mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
1 parent
06aa0ce22e
commit
c82f7517de
2 files changed
+43
-203
No files matched your search
@@ -2,49 +2,65 @@ package noppes.npcs.constants;
|
||||
|
||||
import noppes.npcs.api.event.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* Represents a script context that maps to one or more event interface namespaces.
|
||||
*
|
||||
* Script contexts are automatically discovered by scanning for {@link ScriptContextMarker}
|
||||
* annotations on event interfaces. For example:
|
||||
* A script context can have multiple namespaces because script editors often
|
||||
* support hooks from multiple event types. For example, Player scripts support:
|
||||
* - IPlayerEvent (core player hooks)
|
||||
* - IAnimationEvent (animation hooks)
|
||||
* - IPartyEvent (party hooks)
|
||||
* - ICustomGuiEvent (custom GUI hooks)
|
||||
* - etc.
|
||||
*
|
||||
* <pre>
|
||||
* {@literal @}ScriptContextMarker("NPC")
|
||||
* public interface INpcEvent extends ICustomNPCsEvent { }
|
||||
* </pre>
|
||||
* This is a registerable system - any mod can register their own contexts:
|
||||
*
|
||||
* Multiple interfaces can share the same context ID - they will be grouped together.
|
||||
* This is a registerable system - any mod can register their own contexts by annotating
|
||||
* their event interfaces.
|
||||
* // Register a new context with multiple namespaces:
|
||||
* ScriptContext.register("DBC", "IDBCEvent", "IDBCPlayerEvent", "IDBCFormEvent");
|
||||
*
|
||||
* // Or add namespaces to an existing context:
|
||||
* ScriptContext.PLAYER.addNamespace("IDBCPlayerEvent");
|
||||
*/
|
||||
public class ScriptContext {
|
||||
|
||||
// Registry of all script contexts
|
||||
private static final Map<String, ScriptContext> REGISTRY = new ConcurrentHashMap<>();
|
||||
|
||||
// Static initializer to scan and register annotated classes
|
||||
static {
|
||||
scanAndRegisterAnnotatedClasses();
|
||||
}
|
||||
|
||||
// ==================== BUILT-IN CONTEXTS ====================
|
||||
// These are loaded dynamically from annotations, but we keep references for convenience
|
||||
|
||||
public static final ScriptContext NPC = byId("NPC");
|
||||
public static final ScriptContext PLAYER = byId("PLAYER");
|
||||
public static final ScriptContext BLOCK = byId("BLOCK");
|
||||
public static final ScriptContext ITEM = byId("ITEM");
|
||||
public static final ScriptContext FORGE = byId("FORGE");
|
||||
public static final ScriptContext GLOBAL = byId("GLOBAL");
|
||||
public static final ScriptContext NPC = register("NPC",
|
||||
INpcEvent.class,
|
||||
IProjectileEvent.class
|
||||
);
|
||||
|
||||
public static final ScriptContext PLAYER = register("PLAYER",
|
||||
IPlayerEvent.class,
|
||||
IAnimationEvent.class,
|
||||
IPartyEvent.class,
|
||||
IDialogEvent.class,
|
||||
IQuestEvent.class,
|
||||
IFactionEvent.class,
|
||||
ICustomGuiEvent.class
|
||||
);
|
||||
|
||||
public static final ScriptContext BLOCK = register("BLOCK",
|
||||
IBlockEvent.class
|
||||
);
|
||||
|
||||
public static final ScriptContext ITEM = register("ITEM",
|
||||
IItemEvent.class
|
||||
);
|
||||
|
||||
public static final ScriptContext FORGE = register("FORGE",
|
||||
IForgeEvent.class
|
||||
);
|
||||
|
||||
public static final ScriptContext GLOBAL = register("GLOBAL",
|
||||
"Global" // Special case: Global namespace doesn't have a corresponding event class
|
||||
);
|
||||
|
||||
// ==================== INSTANCE FIELDS ====================
|
||||
|
||||
@@ -106,155 +122,6 @@ public class ScriptContext {
|
||||
|
||||
// ==================== REGISTRATION API ====================
|
||||
|
||||
/**
|
||||
* Scan the classpath for classes annotated with {@link ScriptContextMarker}
|
||||
* and automatically register them.
|
||||
*/
|
||||
private static void scanAndRegisterAnnotatedClasses() {
|
||||
try {
|
||||
// Scan the noppes.npcs.api.event package for annotated interfaces
|
||||
List<Class<?>> annotatedClasses = findAnnotatedClasses("noppes.npcs.api.event", ScriptContextMarker.class);
|
||||
|
||||
// Group classes by their context ID
|
||||
Map<String, List<Class<?>>> contextGroups = new HashMap<>();
|
||||
|
||||
for (Class<?> eventClass : annotatedClasses) {
|
||||
ScriptContextMarker annotation = eventClass.getAnnotation(ScriptContextMarker.class);
|
||||
if (annotation != null) {
|
||||
String contextId = annotation.value();
|
||||
contextGroups.computeIfAbsent(contextId, k -> new ArrayList<>()).add(eventClass);
|
||||
}
|
||||
}
|
||||
|
||||
// Register each context with its grouped classes
|
||||
for (Map.Entry<String, List<Class<?>>> entry : contextGroups.entrySet()) {
|
||||
String contextId = entry.getKey();
|
||||
List<Class<?>> classes = entry.getValue();
|
||||
register(contextId, classes.toArray(new Class<?>[0]));
|
||||
}
|
||||
|
||||
// Special case: GLOBAL context (no annotation needed)
|
||||
if (!REGISTRY.containsKey("GLOBAL")) {
|
||||
register("GLOBAL", "Global");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fallback: if scanning fails, at least register known core events
|
||||
System.err.println("[ScriptContext] Failed to scan for annotated classes: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fallbackRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all classes in a package that have a specific annotation.
|
||||
*/
|
||||
private static List<Class<?>> findAnnotatedClasses(String packageName, Class<? extends java.lang.annotation.Annotation> annotationClass) throws Exception {
|
||||
List<Class<?>> annotatedClasses = new ArrayList<>();
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
String packagePath = packageName.replace('.', '/');
|
||||
|
||||
// Get resources for the package
|
||||
Enumeration<URL> resources = classLoader.getResources(packagePath);
|
||||
|
||||
while (resources.hasMoreElements()) {
|
||||
URL resource = resources.nextElement();
|
||||
String protocol = resource.getProtocol();
|
||||
|
||||
if ("file".equals(protocol)) {
|
||||
// Scanning from file system (development environment)
|
||||
String filePath = URLDecoder.decode(resource.getFile(), "UTF-8");
|
||||
File directory = new File(filePath);
|
||||
if (directory.exists()) {
|
||||
findClassesInDirectory(directory, packageName, annotationClass, annotatedClasses);
|
||||
}
|
||||
} else if ("jar".equals(protocol)) {
|
||||
// Scanning from JAR file (production)
|
||||
String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));
|
||||
jarPath = URLDecoder.decode(jarPath, "UTF-8");
|
||||
findClassesInJar(jarPath, packageName, annotationClass, annotatedClasses);
|
||||
}
|
||||
}
|
||||
|
||||
return annotatedClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find classes with annotation in a directory (file system).
|
||||
*/
|
||||
private static void findClassesInDirectory(File directory, String packageName,
|
||||
Class<? extends java.lang.annotation.Annotation> annotationClass,
|
||||
List<Class<?>> result) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null) return;
|
||||
|
||||
for (File file : files) {
|
||||
String fileName = file.getName();
|
||||
if (file.isDirectory()) {
|
||||
findClassesInDirectory(file, packageName + "." + fileName, annotationClass, result);
|
||||
} else if (fileName.endsWith(".class")) {
|
||||
String className = packageName + '.' + fileName.substring(0, fileName.length() - 6);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
if (clazz.isAnnotationPresent(annotationClass)) {
|
||||
result.add(clazz);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Skip classes that can't be loaded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find classes with annotation in a JAR file.
|
||||
*/
|
||||
private static void findClassesInJar(String jarPath, String packageName,
|
||||
Class<? extends java.lang.annotation.Annotation> annotationClass,
|
||||
List<Class<?>> result) {
|
||||
try {
|
||||
JarFile jarFile = new JarFile(jarPath);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
String packagePath = packageName.replace('.', '/');
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String entryName = entry.getName();
|
||||
|
||||
if (entryName.startsWith(packagePath) && entryName.endsWith(".class")) {
|
||||
String className = entryName.replace('/', '.').substring(0, entryName.length() - 6);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
if (clazz.isAnnotationPresent(annotationClass)) {
|
||||
result.add(clazz);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Skip classes that can't be loaded
|
||||
}
|
||||
}
|
||||
}
|
||||
jarFile.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("[ScriptContext] Failed to scan JAR: " + jarPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback registration if classpath scanning fails.
|
||||
*/
|
||||
private static void fallbackRegistration() {
|
||||
try {
|
||||
register("NPC", INpcEvent.class, IProjectileEvent.class);
|
||||
register("PLAYER", IPlayerEvent.class, IAnimationEvent.class, IPartyEvent.class,
|
||||
IDialogEvent.class, IQuestEvent.class, IFactionEvent.class, ICustomGuiEvent.class);
|
||||
register("BLOCK", IBlockEvent.class);
|
||||
register("ITEM", IItemEvent.class);
|
||||
register("FORGE", IForgeEvent.class);
|
||||
register("GLOBAL", "Global");
|
||||
} catch (Exception e) {
|
||||
System.err.println("[ScriptContext] Fallback registration also failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new script context with one or more namespaces.
|
||||
*
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package noppes.npcs.constants;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Marks an event interface as belonging to a specific script context.
|
||||
*
|
||||
* Usage:
|
||||
* <pre>
|
||||
* {@literal @}ScriptContextMarker("NPC")
|
||||
* public interface INpcEvent extends ICustomNPCsEvent {
|
||||
* // ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Multiple interfaces can share the same context name - they will all be
|
||||
* registered as namespaces for that context.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ScriptContextMarker {
|
||||
/**
|
||||
* The script context ID this event interface belongs to.
|
||||
* Examples: "NPC", "PLAYER", "BLOCK", "ITEM", "FORGE", "GLOBAL"
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
Reference in new issue
Block a user