diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index 5e0f08da6..0f2f4730d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -1,5 +1,6 @@ package noppes.npcs.client.gui.util.script.interpreter; +import net.minecraft.client.Minecraft; import noppes.npcs.client.ClientProxy; import noppes.npcs.client.gui.util.script.interpreter.expression.CastExpressionResolver; import noppes.npcs.client.gui.util.script.interpreter.expression.ExpressionNode; @@ -3495,7 +3496,9 @@ public class ScriptDocument { currentType = null; // We'll use globalFields for the next segment } else if (Character.isUpperCase(firstSegment.charAt(0))) { // Static field access like SomeClass.field - currentType = resolveType(firstSegment); + TypeInfo classType = resolveType(firstSegment); + // Class names are always in static context (can only access static members) + currentType = (classType != null) ? classType.asStaticContext() : null; } else { // Variable or field access if (firstIsPrecededByDot) { @@ -3504,7 +3507,8 @@ public class ScriptDocument { TypeInfo receiverType = resolveReceiverChain(chainStart); if (receiverType != null && receiverType.hasField(firstSegment)) { FieldInfo varInfo = receiverType.getFieldInfo(firstSegment); - currentType = (varInfo != null) ? varInfo.getTypeInfo() : null; + // Field values are always instances, even if field itself is static + currentType = (varInfo != null) ? varInfo.getTypeInfo().asInstanceContext() : null; } else { currentType = null; // Can't resolve } @@ -3512,7 +3516,8 @@ public class ScriptDocument { // This is a variable starting the chain FieldInfo varInfo = resolveVariable(firstSegment, chainStart); if (varInfo != null) { - currentType = varInfo.getTypeInfo(); + // Variables are always instances + currentType = varInfo.getTypeInfo().asInstanceContext(); } else { currentType = null; } @@ -3544,7 +3549,8 @@ public class ScriptDocument { FieldAccessInfo accessInfo = createFieldAccessInfo(segment, segPos[0], segPos[1], receiverType, fieldInfo, isLastSegment, isStaticAccessSeg); marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.GLOBAL_FIELD, accessInfo)); - currentType = (fieldInfo != null) ? fieldInfo.getTypeInfo() : null; + // Field values are always instances + currentType = (fieldInfo != null) ? fieldInfo.getTypeInfo().asInstanceContext() : null; } else { marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR)); currentType = null; @@ -3554,29 +3560,51 @@ public class ScriptDocument { if (globalFields.containsKey(segment)) { FieldInfo fieldInfo = globalFields.get(segment); marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.GLOBAL_FIELD, fieldInfo)); - currentType = fieldInfo.getTypeInfo(); + // Field values are always instances + currentType = fieldInfo.getTypeInfo().asInstanceContext(); } else { marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR)); currentType = null; } } else if (currentType != null && currentType.isResolved()) { // Check if this type has this field - //TODO: THIS MAY NEED TO HANDLE STATIC VS INSTANCE FIELDS WITH DIFFERENT RENDERING - if (currentType.hasField(segment)) { - FieldInfo fieldInfo = currentType.getFieldInfo(segment); - + // First check if field exists at all (ignoring static context) + FieldInfo fieldInfo = null; + boolean fieldExists = false; + boolean isStaticAccessError = false; + + // Check if field exists by temporarily using instance context + TypeInfo instanceContext = currentType.asInstanceContext(); + if (instanceContext.hasField(segment)) { + fieldExists = true; + // Now check if accessible in actual context + if (currentType.hasField(segment)) { + fieldInfo = currentType.getFieldInfo(segment); + } else { + // Field exists but not accessible in static context + isStaticAccessError = true; + fieldInfo = instanceContext.getFieldInfo(segment); + } + } + + if (fieldInfo != null && !isStaticAccessError) { // Check if this is the last segment and if it's in an assignment context // Create and mark FieldAccessInfo for this segment (last or intermediate) FieldAccessInfo accessInfo = createFieldAccessInfo(segment, segPos[0], segPos[1], currentType, fieldInfo, isLastSegment, isStaticAccessSeg); marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.GLOBAL_FIELD, accessInfo)); - - // Update currentType for next segment - currentType = (fieldInfo != null) ? fieldInfo.getTypeInfo() : null; + + // Update currentType for next segment - field values are always instances + currentType = (fieldInfo != null) ? fieldInfo.getTypeInfo().asInstanceContext() : null; /*TODO THIS ELSE OVERRIDES FOR CHAINED METHOD CALLS LIKE "Minecraft.getMinecraft()" Do we resolve this by increasing METHOD_CALL PRIORITY or how exactly? Think of the best fitting solution that doesnt regress other parts of the whole framework. */ + } else if (isStaticAccessError && fieldInfo != null) { + // Mark as error - trying to access non-static field from static context + String errorMsg = "Cannot access non-static field '" + segment + "' from static context '" + currentType.getSimpleName() + "'"; + marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR, errorMsg)); + currentType = null; // Can't continue resolving } else { marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR)); currentType = null; // Can't continue resolving diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java index 9f1f04a53..094257938 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java @@ -34,8 +34,8 @@ public class ScriptTypeInfo extends TypeInfo { private ScriptTypeInfo outerClass; private ScriptTypeInfo(String simpleName, String fullName, Kind kind, - int declarationOffset, int bodyStart, int bodyEnd, int modifiers) { - super(simpleName, fullName, "", kind, null, true, null, true); + int declarationOffset, int bodyStart, int bodyEnd, int modifiers, boolean staticContext) { + super(simpleName, fullName, "", kind, null, true, null, staticContext, true); this.scriptClassName = simpleName; this.declarationOffset = declarationOffset; this.bodyStart = bodyStart; @@ -46,13 +46,13 @@ public class ScriptTypeInfo extends TypeInfo { // Factory method public static ScriptTypeInfo create(String simpleName, Kind kind, int declarationOffset, int bodyStart, int bodyEnd, int modifiers) { - return new ScriptTypeInfo(simpleName, simpleName, kind, declarationOffset, bodyStart, bodyEnd, modifiers); + return new ScriptTypeInfo(simpleName, simpleName, kind, declarationOffset, bodyStart, bodyEnd, modifiers, false); } public static ScriptTypeInfo createInner(String simpleName, Kind kind, ScriptTypeInfo outer, int declarationOffset, int bodyStart, int bodyEnd, int modifiers) { String fullName = outer.getFullName() + "$" + simpleName; - ScriptTypeInfo inner = new ScriptTypeInfo(simpleName, fullName, kind, declarationOffset, bodyStart, bodyEnd, modifiers); + ScriptTypeInfo inner = new ScriptTypeInfo(simpleName, fullName, kind, declarationOffset, bodyStart, bodyEnd, modifiers, false); inner.outerClass = outer; outer.innerClasses.add(inner); return inner; @@ -82,12 +82,26 @@ public class ScriptTypeInfo extends TypeInfo { @Override public boolean hasField(String fieldName) { - return fields.containsKey(fieldName); + FieldInfo field = fields.get(fieldName); + if (field == null) return false; + + // In static context, only static fields are accessible + if (isStaticContext() && !field.isStatic()) { + return false; + } + return true; } @Override public FieldInfo getFieldInfo(String fieldName) { - return fields.get(fieldName); + FieldInfo field = fields.get(fieldName); + if (field == null) return null; + + // In static context, only static fields are accessible + if (isStaticContext() && !field.isStatic()) { + return null; // Non-static field not accessible from static context + } + return field; } public Map getFields() { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index 9fdf8dac4..b9dfa1c2c 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -27,9 +27,10 @@ public class TypeInfo { private final Class javaClass; // The actual resolved Java class (null if unresolved) private final boolean resolved; // Whether this type was successfully resolved private final TypeInfo enclosingType; // For inner classes, the outer type (null if top-level) + private final boolean staticContext; // true if this is a class reference (can only access static members), false if instance private TypeInfo(String simpleName, String fullName, String packageName, - Kind kind, Class javaClass, boolean resolved, TypeInfo enclosingType) { + Kind kind, Class javaClass, boolean resolved, TypeInfo enclosingType, boolean staticContext) { this.simpleName = simpleName; this.fullName = fullName; this.packageName = packageName; @@ -37,12 +38,13 @@ public class TypeInfo { this.javaClass = javaClass; this.resolved = resolved; this.enclosingType = enclosingType; + this.staticContext = staticContext; } // Protected constructor for subclasses (like ScriptTypeInfo) protected TypeInfo(String simpleName, String fullName, String packageName, Kind kind, Class javaClass, boolean resolved, TypeInfo enclosingType, - @SuppressWarnings("unused") boolean subclass) { + boolean staticContext, @SuppressWarnings("unused") boolean subclass) { this.simpleName = simpleName; this.fullName = fullName; this.packageName = packageName; @@ -50,23 +52,24 @@ public class TypeInfo { this.javaClass = javaClass; this.resolved = resolved; this.enclosingType = enclosingType; + this.staticContext = staticContext; } // Factory methods public static TypeInfo resolved(String simpleName, String fullName, String packageName, Kind kind, Class javaClass) { - return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, true, null); + return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, true, null, false); } public static TypeInfo resolvedInner(String simpleName, String fullName, String packageName, Kind kind, Class javaClass, TypeInfo enclosing) { - return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, true, enclosing); + return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, true, enclosing, false); } public static TypeInfo unresolved(String simpleName, String fullPath) { int lastDot = fullPath.lastIndexOf('.'); String pkg = lastDot > 0 ? fullPath.substring(0, lastDot) : ""; - return new TypeInfo(simpleName, fullPath, pkg, Kind.UNKNOWN, null, false, null); + return new TypeInfo(simpleName, fullPath, pkg, Kind.UNKNOWN, null, false, null, false); } public static TypeInfo fromClass(Class clazz) { @@ -99,7 +102,7 @@ public class TypeInfo { enclosing = fromClass(clazz.getEnclosingClass()); } - return new TypeInfo(simpleName, fullName, packageName, kind, clazz, true, enclosing); + return new TypeInfo(simpleName, fullName, packageName, kind, clazz, true, enclosing, false); } /** @@ -118,7 +121,7 @@ public class TypeInfo { case "double": primitiveClass = double.class; break; case "void": primitiveClass = void.class; break; } - return new TypeInfo(typeName, typeName, "", Kind.CLASS, primitiveClass, true, null); + return new TypeInfo(typeName, typeName, "", Kind.CLASS, primitiveClass, true, null, false); } // Getters @@ -130,6 +133,25 @@ public class TypeInfo { public boolean isResolved() { return resolved; } public TypeInfo getEnclosingType() { return enclosingType; } public boolean isInnerClass() { return enclosingType != null; } + public boolean isStaticContext() { return staticContext; } + + /** + * Create a new TypeInfo with static context (for class references). + * Used when referencing a class name directly, which can only access static members. + */ + public TypeInfo asStaticContext() { + if (staticContext) return this; + return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, resolved, enclosingType, true); + } + + /** + * Create a new TypeInfo with instance context (for object instances). + * Used when referencing an instance of a class, which can access both static and instance members. + */ + public TypeInfo asInstanceContext() { + if (!staticContext) return this; + return new TypeInfo(simpleName, fullName, packageName, kind, javaClass, resolved, enclosingType, false); + } /** * Get the appropriate TokenType for highlighting this type. @@ -237,13 +259,19 @@ public class TypeInfo { } /** - * Check if this type has a field with the given name. + * Check if this type has a field with the given name that is accessible in the current context. + * In static context (class reference), only static fields are accessible. + * In instance context, both static and instance fields are accessible. */ public boolean hasField(String fieldName) { if (javaClass == null) return false; try { for (java.lang.reflect.Field f : javaClass.getFields()) { if (f.getName().equals(fieldName)) { + // In static context, only static fields are accessible + if (staticContext && !java.lang.reflect.Modifier.isStatic(f.getModifiers())) { + return false; + } return true; } } @@ -441,7 +469,9 @@ public class TypeInfo { } /** - * Get FieldInfo for a field by name. Returns null if not found. + * Get FieldInfo for a field by name. Returns null if not found or not accessible. + * In static context (class reference), only static fields are accessible. + * In instance context, both static and instance fields are accessible. * Creates a synthetic FieldInfo based on reflection data. */ public FieldInfo getFieldInfo(String fieldName) { @@ -449,8 +479,15 @@ public class TypeInfo { try { for (java.lang.reflect.Field f : javaClass.getFields()) { if (f.getName().equals(fieldName)) { + // In static context, only static fields are accessible + if (staticContext && !java.lang.reflect.Modifier.isStatic(f.getModifiers())) { + return null; // Non-static field not accessible from static context + } // Create a synthetic FieldInfo from reflection - return FieldInfo.fromReflection(f, this); + // Always return instance context for the field's type (field values are instances) + FieldInfo fieldInfo = FieldInfo.fromReflection(f, this); + // The field's type should be in instance context since we're accessing a value + return fieldInfo; } } } catch (Exception e) {