Merge remote-tracking branch 'origin/syntax-highlighter-unified' into syntax-highlighter-unified

This commit is contained in:
bigguy345 committed 2026-01-10 06:04:07 +02:00
commit 0d11db729b
13 files changed
+831 -67

No files matched your search

@@ -13,6 +13,7 @@ import noppes.npcs.NoppesStringUtils;
import noppes.npcs.client.NoppesUtil;
import noppes.npcs.client.gui.script.GuiNPCEventScripts;
import noppes.npcs.client.gui.util.*;
import noppes.npcs.constants.ScriptContext;
import noppes.npcs.controllers.ScriptContainer;
import noppes.npcs.controllers.ScriptController;
import noppes.npcs.controllers.data.DataScript;
@@ -100,6 +101,10 @@ public class GuiScript extends GuiNPCInterface implements IGuiData, GuiYesNoCall
activeArea.init(guiLeft + 74, guiTop + 4, 239, 208, container == null ? "" : container.script);
}
// Set language and script context for proper syntax highlighting and autocomplete
activeArea.setLanguage(script.getLanguage());
activeArea.setScriptContext(ScriptContext.NPC);
addButton(new GuiNpcButton(102, guiLeft + 315, guiTop + 4, 50, 20, "gui.clear"));
addButton(new GuiNpcButton(101, guiLeft + 366, guiTop + 4, 50, 20, "gui.paste"));
addButton(new GuiNpcButton(100, guiLeft + 315, guiTop + 25, 50, 20, "gui.copy"));
@@ -3,6 +3,7 @@ package noppes.npcs.client.gui.script;
import kamkeel.npcs.network.packets.request.script.BlockScriptPacket;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.blocks.tiles.TileScripted;
import noppes.npcs.constants.ScriptContext;
public class GuiScriptBlock extends GuiScriptInterface {
@@ -33,6 +34,11 @@ public class GuiScriptBlock extends GuiScriptInterface {
loaded = true;
}
@Override
protected ScriptContext getScriptContext() {
return ScriptContext.BLOCK;
}
public void save() {
if (loaded) {
super.save();
@@ -25,6 +25,7 @@ import noppes.npcs.client.gui.util.ITextChangeListener;
import noppes.npcs.client.gui.util.ITextfieldListener;
import noppes.npcs.controllers.ScriptContainer;
import noppes.npcs.controllers.ScriptController;
import noppes.npcs.constants.ScriptContext;
import noppes.npcs.controllers.data.ForgeDataScript;
import noppes.npcs.controllers.data.IScriptHandler;
import noppes.npcs.scripted.item.ScriptCustomItem;
@@ -197,6 +198,9 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
// Set the scripting language for proper syntax highlighting
activeArea.setLanguage(this.handler.getLanguage());
// Set the script context for context-aware hook autocomplete
activeArea.setScriptContext(getScriptContext());
// Setup fullscreen key binding
GuiScriptTextArea.KEYS.FULLSCREEN.setTask(e -> {
if (e.isPress()) {
@@ -513,6 +517,16 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
this.initGui();
}
/**
* Get the script context for this GUI.
* Override in subclasses to return the appropriate context.
*
* @return The script context (default: GLOBAL)
*/
protected ScriptContext getScriptContext() {
return ScriptContext.GLOBAL;
}
public void save() {
if (loaded)
this.setScript();
@@ -6,6 +6,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomItems;
import noppes.npcs.NBTTags;
import noppes.npcs.constants.ScriptContext;
import noppes.npcs.scripted.item.ScriptCustomItem;
import java.util.Date;
@@ -48,6 +49,11 @@ public class GuiScriptItem extends GuiScriptInterface {
}
}
@Override
protected ScriptContext getScriptContext() {
return ScriptContext.ITEM;
}
public void save() {
if (loaded) {
super.save();
@@ -4,6 +4,7 @@ import kamkeel.npcs.network.packets.request.script.PlayerScriptPacket;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.NBTTags;
import noppes.npcs.constants.ScriptContext;
import noppes.npcs.constants.EnumScriptType;
import noppes.npcs.controllers.ScriptContainer;
import noppes.npcs.controllers.data.EffectScript;
@@ -114,6 +115,11 @@ public class GuiScriptPlayers extends GuiScriptInterface {
}
}
@Override
protected ScriptContext getScriptContext() {
return ScriptContext.PLAYER;
}
public void save() {
if (loaded) {
super.save();
@@ -22,6 +22,7 @@ import noppes.npcs.client.gui.util.script.interpreter.hover.HoverState;
import noppes.npcs.client.gui.util.script.interpreter.hover.TokenHoverRenderer;
import noppes.npcs.client.gui.util.script.autocomplete.AutocompleteManager;
import noppes.npcs.client.key.impl.ScriptEditorKeys;
import noppes.npcs.constants.ScriptContext;
import noppes.npcs.util.ValueUtil;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
@@ -2629,6 +2630,27 @@ public class GuiScriptTextArea extends GuiNpcTextField {
return this.container != null ? this.container.getLanguage() : "ECMAScript";
}
/**
* Set the script context (NPC, PLAYER, BLOCK, ITEM, etc.).
* This determines which hooks and event types are available for autocomplete.
*
* @param context The script context
*/
public void setScriptContext(ScriptContext context) {
if (this.container != null) {
this.container.setScriptContext(context);
}
}
/**
* Get the current script context.
*
* @return The script context (NPC, PLAYER, BLOCK, ITEM, etc.)
*/
public ScriptContext getScriptContext() {
return this.container != null ? this.container.getScriptContext() : ScriptContext.GLOBAL;
}
public void setListener(ITextChangeListener listener) {
this.listener = listener;
}
@@ -23,6 +23,7 @@ import noppes.npcs.client.gui.util.script.interpreter.type.*;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticField;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticMethod;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticType;
import noppes.npcs.constants.ScriptContext;
import java.util.*;
import java.util.regex.Matcher;
@@ -125,6 +126,10 @@ public class ScriptDocument {
// Script language: "ECMAScript", "Groovy", etc.
private String language = "ECMAScript";
// Script context: NPC, PLAYER, BLOCK, ITEM, etc.
// Determines which hooks and event types are available
private ScriptContext scriptContext = ScriptContext.GLOBAL;
// Layout properties
public int lineHeight = 13;
public int totalHeight;
@@ -170,6 +175,25 @@ public class ScriptDocument {
return "ECMAScript".equalsIgnoreCase(language);
}
/**
* Set the script context (NPC, PLAYER, BLOCK, ITEM, etc.).
* This determines which hooks and event types are available for autocomplete.
*
* @param context The script context
*/
public void setScriptContext(ScriptContext context) {
this.scriptContext = context != null ? context : ScriptContext.GLOBAL;
}
/**
* Get the current script context.
*
* @return The script context (NPC, PLAYER, BLOCK, ITEM, etc.)
*/
public ScriptContext getScriptContext() {
return scriptContext;
}
// ==================== TEXT MANAGEMENT ====================
public void setText(String text) {
@@ -686,11 +710,13 @@ public class ScriptDocument {
JSDocInfo jsDoc = jsDocParser.extractJSDocBefore(text, m.start());
// For JS hooks, infer parameter types from registry
// Use the script context's namespaces (e.g., ["IPlayerEvent", "IAnimationEvent"]) for lookup
List<FieldInfo> params = new ArrayList<>();
TypeInfo returnType = TypeInfo.fromPrimitive("void");
List<String> namespaces = scriptContext != null ? scriptContext.getNamespaces() : Collections.singletonList("Global");
if (typeResolver.isJSHook(funcName)) {
List<JSTypeRegistry.HookSignature> sigs = typeResolver.getJSHookSignatures(funcName);
if (typeResolver.isJSHook(namespaces, funcName)) {
List<JSTypeRegistry.HookSignature> sigs = typeResolver.getJSHookSignatures(namespaces, funcName);
if (!sigs.isEmpty()) {
JSTypeRegistry.HookSignature sig = sigs.get(0);
documentation = sig.doc;
@@ -4,6 +4,7 @@ import noppes.npcs.client.ClientProxy;
import noppes.npcs.client.gui.util.script.JavaTextContainer;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.constants.ScriptContext;
import java.util.ArrayList;
import java.util.List;
@@ -31,10 +32,13 @@ public class ScriptTextContainer extends JavaTextContainer {
public static boolean USE_NEW_INTERPRETER = true;
private ScriptDocument document;
/** The scripting language: "ECMAScript", "Groovy", etc. */
private String language = "ECMAScript";
/** The script context: NPC, PLAYER, BLOCK, ITEM, etc. */
private ScriptContext scriptContext = ScriptContext.GLOBAL;
public ScriptTextContainer(String text) {
super(text);
if (USE_NEW_INTERPRETER) {
@@ -68,6 +72,28 @@ public class ScriptTextContainer extends JavaTextContainer {
return language;
}
/**
* Set the script context (NPC, PLAYER, BLOCK, ITEM, etc.).
* This determines which hooks and event types are available for autocomplete.
*
* @param context The script context
*/
public void setScriptContext(ScriptContext context) {
this.scriptContext = context != null ? context : ScriptContext.GLOBAL;
if (document != null) {
document.setScriptContext(this.scriptContext);
}
}
/**
* Get the current script context.
*
* @return The script context (NPC, PLAYER, BLOCK, ITEM, etc.)
*/
public ScriptContext getScriptContext() {
return scriptContext;
}
@Override
public void init(int width, int height) {
if (!USE_NEW_INTERPRETER) {
@@ -103,6 +129,7 @@ public class ScriptTextContainer extends JavaTextContainer {
}
document = new ScriptDocument(this.text, this.language);
document.setScriptContext(this.scriptContext);
init(width, height);
}
@@ -27,10 +27,18 @@ public class JSTypeRegistry {
// Type aliases (simple name -> full type name)
private final Map<String, String> typeAliases = new HashMap<>();
// Hook function signatures: functionName -> list of (paramName, paramType) pairs
// Multiple entries for overloaded hooks
// Context-aware hook function signatures: namespace -> functionName -> list of signatures
// The namespace is the event interface name (e.g., "INpcEvent", "IPlayerEvent")
// This allows any mod to register hooks without modifying an enum
private final Map<String, Map<String, List<HookSignature>>> contextHooks = new LinkedHashMap<>();
// Legacy hook storage: functionName -> list of (paramName, paramType) pairs
// Kept for backward compatibility with code that doesn't use contexts
private final Map<String, List<HookSignature>> hooks = new LinkedHashMap<>();
// Fallback namespace for hooks that don't match a specific context
private static final String GLOBAL_NAMESPACE = "Global";
// Global object instances: name -> type (e.g., "API" -> "AbstractNpcAPI")
// These are treated as instance objects, not static classes
private final Map<String, String> globalEngineObjects = new LinkedHashMap<>();
@@ -275,11 +283,23 @@ public class JSTypeRegistry {
}
/**
* Register a hook function signature.
* Register a hook function signature with a namespace.
*
* @param namespace The event interface namespace (e.g., "INpcEvent", "IPlayerEvent")
* @param functionName The hook function name (e.g., "interact", "damaged")
* @param paramName The parameter name (e.g., "event")
* @param paramType The parameter type (e.g., "INpcEvent.InteractEvent")
*/
public void registerHook(String functionName, String paramName, String paramType) {
hooks.computeIfAbsent(functionName, k -> new ArrayList<>())
.add(new HookSignature(paramName, paramType));
public void registerHook(String namespace, String functionName, String paramName, String paramType) {
HookSignature sig = new HookSignature(paramName, paramType, null, namespace);
// Add to context-specific map
contextHooks.computeIfAbsent(namespace, k -> new LinkedHashMap<>())
.computeIfAbsent(functionName, k -> new ArrayList<>())
.add(sig);
// Also add to legacy hooks map for backward compatibility
hooks.computeIfAbsent(functionName, k -> new ArrayList<>()).add(sig);
}
/**
@@ -369,7 +389,190 @@ public class JSTypeRegistry {
}
return null;
}
// ==================== Context-Aware Hook Methods ====================
/**
* Get hook signatures for a specific script context.
* Falls back to GLOBAL context if not found in the specified context.
*
* @param namespace The event interface namespace (e.g., "INpcEvent", "IPlayerEvent")
* @param functionName The hook function name
* @return List of hook signatures, or empty list if not found
*/
public List<HookSignature> getHookSignatures(String namespace, String functionName) {
// First try the specific namespace
Map<String, List<HookSignature>> namespaceMap = contextHooks.get(namespace);
if (namespaceMap != null) {
List<HookSignature> sigs = namespaceMap.get(functionName);
if (sigs != null && !sigs.isEmpty()) {
return sigs;
}
}
// Fall back to GLOBAL namespace
if (!GLOBAL_NAMESPACE.equals(namespace)) {
Map<String, List<HookSignature>> globalMap = contextHooks.get(GLOBAL_NAMESPACE);
if (globalMap != null) {
List<HookSignature> sigs = globalMap.get(functionName);
if (sigs != null && !sigs.isEmpty()) {
return sigs;
}
}
}
return Collections.emptyList();
}
/**
* Check if a function name is a known hook in a specific namespace.
*
* @param namespace The event interface namespace
* @param functionName The hook function name
* @return true if the hook exists in this namespace or GLOBAL
*/
public boolean isHook(String namespace, String functionName) {
return !getHookSignatures(namespace, functionName).isEmpty();
}
/**
* Get the parameter type for a hook function in a specific namespace.
* Falls back to GLOBAL namespace if not found.
*
* @param namespace The event interface namespace
* @param functionName The hook function name
* @return The parameter type, or null if not found
*/
public String getHookParameterType(String namespace, String functionName) {
List<HookSignature> sigs = getHookSignatures(namespace, functionName);
if (!sigs.isEmpty()) {
return sigs.get(0).paramType;
}
return null;
}
/**
* Get all hook names for a specific namespace.
*
* @param namespace The event interface namespace
* @return Set of hook function names available in this namespace
*/
public Set<String> getHookNames(String namespace) {
Set<String> names = new HashSet<>();
// Add hooks from the specific namespace
Map<String, List<HookSignature>> namespaceMap = contextHooks.get(namespace);
if (namespaceMap != null) {
names.addAll(namespaceMap.keySet());
}
// Also add GLOBAL hooks
if (!GLOBAL_NAMESPACE.equals(namespace)) {
Map<String, List<HookSignature>> globalMap = contextHooks.get(GLOBAL_NAMESPACE);
if (globalMap != null) {
names.addAll(globalMap.keySet());
}
}
return names;
}
/**
* Get all hooks organized by namespace.
*
* @return Map of namespace -> hookName -> signatures
*/
public Map<String, Map<String, List<HookSignature>>> getAllContextHooks() {
return Collections.unmodifiableMap(contextHooks);
}
// ==================== MULTI-NAMESPACE LOOKUP (for ScriptContext) ====================
/**
* Get hook signatures by searching through multiple namespaces.
* This is used when a ScriptContext has multiple event types (e.g., Player has
* IPlayerEvent, IAnimationEvent, IPartyEvent, etc.)
*
* @param namespaces List of namespaces to search (in priority order)
* @param functionName The hook function name
* @return List of hook signatures from the first matching namespace, or empty list
*/
public List<HookSignature> getHookSignatures(List<String> namespaces, String functionName) {
// Search through all namespaces in order
for (String namespace : namespaces) {
Map<String, List<HookSignature>> namespaceMap = contextHooks.get(namespace);
if (namespaceMap != null) {
List<HookSignature> sigs = namespaceMap.get(functionName);
if (sigs != null && !sigs.isEmpty()) {
return sigs;
}
}
}
// Fall back to GLOBAL namespace
Map<String, List<HookSignature>> globalMap = contextHooks.get(GLOBAL_NAMESPACE);
if (globalMap != null) {
List<HookSignature> sigs = globalMap.get(functionName);
if (sigs != null && !sigs.isEmpty()) {
return sigs;
}
}
return Collections.emptyList();
}
/**
* Check if a function name is a known hook in any of the given namespaces.
*
* @param namespaces List of namespaces to search
* @param functionName The hook function name
* @return true if the hook exists in any namespace or GLOBAL
*/
public boolean isHook(List<String> namespaces, String functionName) {
return !getHookSignatures(namespaces, functionName).isEmpty();
}
/**
* Get the parameter type for a hook function, searching through multiple namespaces.
*
* @param namespaces List of namespaces to search
* @param functionName The hook function name
* @return The parameter type, or null if not found
*/
public String getHookParameterType(List<String> namespaces, String functionName) {
List<HookSignature> sigs = getHookSignatures(namespaces, functionName);
if (!sigs.isEmpty()) {
return sigs.get(0).paramType;
}
return null;
}
/**
* Get all hook names available in any of the given namespaces.
*
* @param namespaces List of namespaces to search
* @return Set of hook function names available in these namespaces
*/
public Set<String> getHookNames(List<String> namespaces) {
Set<String> names = new HashSet<>();
// Add hooks from all specified namespaces
for (String namespace : namespaces) {
Map<String, List<HookSignature>> namespaceMap = contextHooks.get(namespace);
if (namespaceMap != null) {
names.addAll(namespaceMap.keySet());
}
}
// Also add GLOBAL hooks
Map<String, List<HookSignature>> globalMap = contextHooks.get(GLOBAL_NAMESPACE);
if (globalMap != null) {
names.addAll(globalMap.keySet());
}
return names;
}
/**
* Resolve all type parameters for all types.
* Called after all .d.ts files are loaded (Phase 2).
@@ -530,31 +733,38 @@ public class JSTypeRegistry {
types.clear();
typeAliases.clear();
hooks.clear();
contextHooks.clear();
globalEngineObjects.clear();
initialized = false;
}
/**
* Represents a hook function signature.
* Represents a hook function signature with its namespace.
*/
public static class HookSignature {
public final String paramName;
public final String paramType;
public final String doc;
public final String namespace; // The event interface namespace (e.g., "INpcEvent")
public HookSignature(String paramName, String paramType) {
this(paramName, paramType, null);
this(paramName, paramType, null, GLOBAL_NAMESPACE);
}
public HookSignature(String paramName, String paramType, String doc) {
this(paramName, paramType, doc, GLOBAL_NAMESPACE);
}
public HookSignature(String paramName, String paramType, String doc, String namespace) {
this.paramName = paramName;
this.paramType = paramType;
this.doc = doc;
this.namespace = namespace != null ? namespace : GLOBAL_NAMESPACE;
}
@Override
public String toString() {
return paramName + ": " + paramType;
return paramName + ": " + paramType + " [" + namespace + "]";
}
}
}
@@ -44,9 +44,15 @@ public class TypeScriptDefinitionParser {
private static final Pattern GLOBAL_FUNCTION_PATTERN = Pattern.compile(
"function\\s+(\\w+)\\s*\\(([^)]*)\\)\\s*:\\s*([^;]+);");
private static final Pattern GLOBAL_TYPE_ALIAS_PATTERN = Pattern.compile(
"type\\s+(\\w+)\\s*=\\s*import\\(['\"]([^'\"]+)['\"]\\)\\.([\\w.]+);");
// Pattern for context-namespaced hooks like: declare namespace INpcEvent { ... }
// Matches any "declare namespace <Name> {" where Name starts with I and contains Event,
// or any other namespace pattern used for hooks
private static final Pattern HOOKS_NAMESPACE_PATTERN = Pattern.compile(
"declare\\s+namespace\\s+(I\\w*Event|\\w+)\\s*\\{", Pattern.MULTILINE);
private final JSTypeRegistry registry;
@@ -129,21 +135,56 @@ public class TypeScriptDefinitionParser {
/**
* Parse hooks.d.ts to extract function signatures for JS hooks.
*
* Hooks are organized by their parent event interface, with the interface name
* used directly as the namespace. Any mod can register its own event interfaces
* and they will be automatically parsed:
*
* declare namespace INpcEvent {
* function interact(event: INpcEvent.InteractEvent): void;
* function init(event: INpcEvent.InitEvent): void;
* }
*
* declare namespace IPlayerEvent {
* function interact(event: IPlayerEvent.InteractEvent): void;
* }
*
* declare namespace IDBCEvent {
* function customHook(event: IDBCEvent.CustomEvent): void;
* }
*
* The namespace name is stored as a string, allowing dynamic registration
* without requiring enum modifications.
*/
private void parseHooksFile(String content) {
Matcher m = GLOBAL_FUNCTION_PATTERN.matcher(content);
while (m.find()) {
String funcName = m.group(1);
String params = m.group(2);
String returnType = m.group(3).trim();
// Parse parameter - format is "paramName: TypeName"
if (!params.isEmpty()) {
String[] parts = params.split(":\\s*", 2);
if (parts.length == 2) {
String paramName = parts[0].trim();
String paramType = parts[1].trim();
registry.registerHook(funcName, paramName, paramType);
// Parse namespaced hooks (e.g., declare namespace INpcEvent { ... })
// The namespace name is used directly - any event interface can register hooks
Matcher namespaceMatcher = HOOKS_NAMESPACE_PATTERN.matcher(content);
while (namespaceMatcher.find()) {
String namespace = namespaceMatcher.group(1); // e.g., "INpcEvent", "IPlayerEvent", "IDBCEvent"
// Find the body of this namespace
int bodyStart = namespaceMatcher.end();
int bodyEnd = findMatchingBrace(content, bodyStart - 1);
if (bodyEnd > bodyStart) {
String namespaceBody = content.substring(bodyStart, bodyEnd);
// Parse functions within this namespace
Matcher funcMatcher = GLOBAL_FUNCTION_PATTERN.matcher(namespaceBody);
while (funcMatcher.find()) {
String funcName = funcMatcher.group(1);
String params = funcMatcher.group(2);
// Parse parameter - format is "paramName: TypeName"
if (!params.isEmpty()) {
String[] parts = params.split(":\\s*", 2);
if (parts.length == 2) {
String paramName = parts[0].trim();
String paramType = parts[1].trim();
// Register hook with the namespace string directly
registry.registerHook(namespace, funcName, paramName, paramType);
}
}
}
}
}
@@ -166,6 +166,106 @@ public class TypeResolver {
return getJSTypeRegistry().getHookSignatures(functionName);
}
// ==================== CONTEXT-AWARE HOOK METHODS ====================
/**
* Check if a function name is a known JS hook in a specific script context.
* Falls back to GLOBAL context if not found in the specified context.
*
* @param namespace The event interface namespace (e.g., "INpcEvent", "IPlayerEvent")
* @param functionName The function name to check
* @return true if it's a known hook in this namespace
*/
public boolean isJSHook(String namespace, String functionName) {
return getJSTypeRegistry().isHook(namespace, functionName);
}
/**
* Get the parameter type for a JS hook function in a specific namespace.
* Falls back to "Global" namespace if not found in the specified namespace.
*
* @param namespace The event interface namespace (e.g., "INpcEvent", "IPlayerEvent")
* @param functionName The hook function name
* @return The parameter type, or null if not a hook
*/
public String getJSHookParameterType(String namespace, String functionName) {
return getJSTypeRegistry().getHookParameterType(namespace, functionName);
}
/**
* Get hook signatures for a JS function in a specific namespace.
* Falls back to "Global" namespace if not found in the specified namespace.
*
* @param namespace The event interface namespace (e.g., "INpcEvent", "IPlayerEvent")
* @param functionName The hook function name
* @return List of hook signatures
*/
public List<JSTypeRegistry.HookSignature> getJSHookSignatures(String namespace, String functionName) {
return getJSTypeRegistry().getHookSignatures(namespace, functionName);
}
/**
* Get all hook names available in a specific namespace.
*
* @param namespace The event interface namespace
* @return Set of hook function names
*/
public Set<String> getJSHookNames(String namespace) {
return getJSTypeRegistry().getHookNames(namespace);
}
// ==================== MULTI-NAMESPACE HOOK METHODS ====================
/**
* Check if a function name is a known JS hook in any of the provided namespaces.
* Checks namespaces in order, returning true on first match.
* Falls back to GLOBAL namespace if not found in any specified namespace.
*
* @param namespaces The list of event interface namespaces to search (e.g., ["IPlayerEvent", "IAnimationEvent"])
* @param functionName The function name to check
* @return true if it's a known hook in any of the namespaces
*/
public boolean isJSHook(List<String> namespaces, String functionName) {
return getJSTypeRegistry().isHook(namespaces, functionName);
}
/**
* Get the parameter type for a JS hook function, searching multiple namespaces.
* Checks namespaces in order, returning the first match found.
* Falls back to "Global" namespace if not found in any specified namespace.
*
* @param namespaces The list of event interface namespaces to search
* @param functionName The hook function name
* @return The parameter type, or null if not a hook
*/
public String getJSHookParameterType(List<String> namespaces, String functionName) {
return getJSTypeRegistry().getHookParameterType(namespaces, functionName);
}
/**
* Get hook signatures for a JS function, searching multiple namespaces.
* Checks namespaces in order, returning signatures from the first matching namespace.
* Falls back to "Global" namespace if not found in any specified namespace.
*
* @param namespaces The list of event interface namespaces to search
* @param functionName The hook function name
* @return List of hook signatures
*/
public List<JSTypeRegistry.HookSignature> getJSHookSignatures(List<String> namespaces, String functionName) {
return getJSTypeRegistry().getHookSignatures(namespaces, functionName);
}
/**
* Get all hook names available in any of the provided namespaces.
* Combines hook names from all specified namespaces.
*
* @param namespaces The list of event interface namespaces
* @return Combined set of hook function names from all namespaces
*/
public Set<String> getJSHookNames(List<String> namespaces) {
return getJSTypeRegistry().getHookNames(namespaces);
}
// ==================== CACHE MANAGEMENT ====================
/**
@@ -0,0 +1,201 @@
package noppes.npcs.constants;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a script context that maps to one or more event interface namespaces.
*
* 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.
*
* This is a registerable system - any mod can register their own contexts:
*
* // 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<>();
// ==================== BUILT-IN CONTEXTS ====================
public static final ScriptContext NPC = register("NPC",
"INpcEvent",
"IProjectileEvent"
);
public static final ScriptContext PLAYER = register("PLAYER",
"IPlayerEvent",
"IAnimationEvent",
"IPartyEvent",
"IDialogEvent",
"IQuestEvent",
"IFactionEvent",
"ICustomGuiEvent",
"IEffectEvent"
);
public static final ScriptContext BLOCK = register("BLOCK",
"IBlockEvent"
);
public static final ScriptContext ITEM = register("ITEM",
"IItemEvent"
);
public static final ScriptContext FORGE = register("FORGE",
"IForgeEvent"
);
public static final ScriptContext GLOBAL = register("GLOBAL",
"Global"
);
// ==================== INSTANCE FIELDS ====================
/** Unique identifier for this context (e.g., "NPC", "PLAYER", "DBC") */
public final String id;
/** The event interface namespaces this context supports */
private final List<String> namespaces;
// ==================== CONSTRUCTOR ====================
private ScriptContext(String id, String... namespaces) {
this.id = id;
this.namespaces = new ArrayList<>(Arrays.asList(namespaces));
}
// ==================== NAMESPACE ACCESS ====================
/**
* Get all namespaces this context supports.
*
* @return Unmodifiable list of namespace strings
*/
public List<String> getNamespaces() {
return Collections.unmodifiableList(namespaces);
}
/**
* Check if this context includes a specific namespace.
*
* @param namespace The namespace to check (e.g., "IPlayerEvent")
* @return true if this context supports hooks from that namespace
*/
public boolean hasNamespace(String namespace) {
return namespaces.contains(namespace);
}
/**
* Add a namespace to this context.
* Mods can use this to extend built-in contexts with their own event types.
*
* @param namespace The namespace to add (e.g., "IDBCPlayerEvent")
*/
public void addNamespace(String namespace) {
if (!namespaces.contains(namespace)) {
namespaces.add(namespace);
}
}
/**
* Get the primary namespace (first one registered).
* This is mainly for backward compatibility.
*
* @return The first namespace, or "Global" if none
*/
public String getPrimaryNamespace() {
return namespaces.isEmpty() ? "Global" : namespaces.get(0);
}
// ==================== REGISTRATION API ====================
/**
* Register a new script context with one or more namespaces.
*
* @param id Unique identifier (e.g., "DBC", "CUSTOM")
* @param namespaces The event interface names (e.g., "IDBCEvent", "IDBCPlayerEvent")
* @return The registered ScriptContext
*/
public static ScriptContext register(String id, String... namespaces) {
ScriptContext context = new ScriptContext(id, namespaces);
REGISTRY.put(id, context);
return context;
}
/**
* Get a script context by its ID.
*
* @param id The context ID (e.g., "NPC", "PLAYER", "DBC")
* @return The ScriptContext, or GLOBAL if not found
*/
public static ScriptContext byId(String id) {
return REGISTRY.getOrDefault(id, GLOBAL);
}
/**
* Find a script context that contains a specific namespace.
*
* @param namespace The event interface namespace (e.g., "INpcEvent")
* @return The first ScriptContext that contains this namespace, or GLOBAL if not found
*/
public static ScriptContext byNamespace(String namespace) {
if (namespace == null) return GLOBAL;
for (ScriptContext ctx : REGISTRY.values()) {
if (ctx.hasNamespace(namespace)) {
return ctx;
}
}
return GLOBAL;
}
/**
* Get all registered script contexts.
*
* @return Unmodifiable collection of all registered contexts
*/
public static Collection<ScriptContext> values() {
return Collections.unmodifiableCollection(REGISTRY.values());
}
/**
* Check if a context with the given ID exists.
*
* @param id The context ID to check
* @return true if registered
*/
public static boolean exists(String id) {
return REGISTRY.containsKey(id);
}
// ==================== OBJECT METHODS ====================
@Override
public String toString() {
return id + " -> " + namespaces;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ScriptContext)) return false;
ScriptContext other = (ScriptContext) obj;
return id.equals(other.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}