Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/postInit/custom/experiments.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

// no_run

def ore_iron = ore('ingotIron')
def item_iron = item('minecraft:iron_ingot')
log.info(item_iron in ore_iron) // true
log.info(item_iron in item_iron) // true
log.info(ore_iron in item_iron) // false
log.info(item_iron << ore_iron) // true
log.info((item_iron * 3) << ore_iron) // false
log.info(ore_iron >> item_iron) // true
log.info(ore_iron >> (item_iron * 3)) // false

file('config/').eachFile { file ->
println file.path
}
17 changes: 17 additions & 0 deletions examples/postInit/custom/reflection.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

// side: client

import net.minecraft.client.gui.GuiMainMenu
import net.minecraftforge.client.event.GuiOpenEvent

// not a typo
GuiMainMenu.metaClass.makePublic('minceraftRoll')
GuiMainMenu.metaClass.makeMutable('minceraftRoll')

eventManager.listen(GuiOpenEvent) {
if (gui instanceof GuiMainMenu) {
// value is randomly set in constructor and checked if its smaller than 1e-4 during rendering
// this forces the minceraft title to always activate
gui.minceraftRoll = 0.00001
}
}
16 changes: 0 additions & 16 deletions examples/postInit/custom/vanilla.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,6 @@ import net.minecraftforge.event.entity.living.EnderTeleportEvent
import net.minecraftforge.event.world.BlockEvent
import net.minecraft.util.text.TextComponentString

/*
def ore_iron = ore('ingotIron')
def item_iron = item('minecraft:iron_ingot')
log.info(item_iron in ore_iron) // true
log.info(item_iron in item_iron) // true
log.info(ore_iron in item_iron) // false
log.info(item_iron << ore_iron) // true
log.info((item_iron * 3) << ore_iron) // false
log.info(ore_iron >> item_iron) // true
log.info(ore_iron >> (item_iron * 3)) // false
*/

/*file('config/').eachFile { file ->
println file.path
}*/

for (var stack in mods.minecraft.allItems[5..12]) {
log.info stack
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.cleanroommc.groovyscript.helper;

import com.cleanroommc.groovyscript.api.GroovyLog;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;

public class MetaClassExpansion {

/**
* Allows making any fields or methods with a specific name public. Logs an error if there is no method or field with that name. The member must be a normal
* field/method from java and not some special cases like {@link groovy.lang.MetaBeanProperty} or other injected members via groovy.
* Note that the {@link java.lang.reflect.Field Field} instance groovy stores and the one you get from {@link Class#getDeclaredField(String)} are different.
* This means that calling this method will make the member only public for groovy, but not for java.
*
* @param mc self meta class
* @param memberName name of members to make public
*/
public static void makePublic(MetaClass mc, String memberName) {
boolean success = false;
MetaProperty mp = mc.getMetaProperty(memberName);
if (mp instanceof CachedField cachedField) {
ReflectionHelper.makeFieldPublic(cachedField.getCachedField());
success = true;
}
for (MetaMethod mm : mc.getMethods()) {
if (memberName.equals(mm.getName()) && mm instanceof CachedMethod cachedMethod) {
ReflectionHelper.makeMethodPublic(cachedMethod.getCachedMethod());
success = true;
}
}
if (!success) {
GroovyLog.get().error("Failed to make member '{}' of class {} public, because no member was found!", memberName, getName(mc));
}
}

/**
* Allows making a field with a specific name non-final. Does nothing if the field is already non-final.
* Logs an error if there is no field with that name. The field must be a normal field from java and not some special cases like
* {@link groovy.lang.MetaBeanProperty} or other injected members via groovy. Note that the {@link java.lang.reflect.Field Field} instance groovy stores and
* the one you get from {@link Class#getDeclaredField(String)} are different. This means that calling this method will make the member only non-final for
* groovy, but not for java.
*
* @param mc self meta class
* @param fieldName name of field to make non-final
*/
public static void makeMutable(MetaClass mc, String fieldName) {
MetaProperty mp = mc.getMetaProperty(fieldName);
if (mp instanceof CachedField cachedField) {
ReflectionHelper.setFinal(cachedField.getCachedField(), false);
return;
}
GroovyLog.get().error("Failed to make member '{}' of class {} mutable, because no field was found!", fieldName, getName(mc));
}

public static String getName(MetaClass mc) {
ClassNode cn = mc.getClassNode();
return cn == null ? "Unknown" : cn.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,91 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectionHelper {

private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static Field modifiersField;
private static Field fieldModifiersField;
private static Field methodModifiersField;
private static MethodHandle fieldModifiersSetter;
private static MethodHandle methodModifiersSetter;

public static boolean setFinal(Field field, boolean isFinal) throws Throwable {
public static Field getFieldModifiersField() {
if (fieldModifiersField == null) {
try {
fieldModifiersField = Field.class.getDeclaredField("modifiers");
} catch (NoSuchFieldException e) {
// something is very wrong if this crashes
throw new RuntimeException(e);
}
fieldModifiersField.setAccessible(true);
}
return fieldModifiersField;
}

public static Field getMethodModifiersField() {
if (methodModifiersField == null) {
try {
methodModifiersField = Method.class.getDeclaredField("modifiers");
} catch (NoSuchFieldException e) {
// something is very wrong if this crashes
throw new RuntimeException(e);
}
methodModifiersField.setAccessible(true);
}
return methodModifiersField;
}

private static MethodHandle getFieldModifiersSetter() {
if (fieldModifiersSetter == null) {
try {
fieldModifiersSetter = LOOKUP.unreflectSetter(getFieldModifiersField());
} catch (IllegalAccessException e) {
// something is very wrong if this crashes
throw new RuntimeException(e);
}
}
return fieldModifiersSetter;
}

public static MethodHandle getMethodModifiersSetter() {
if (methodModifiersSetter == null) {
try {
methodModifiersSetter = LOOKUP.unreflectSetter(getMethodModifiersField());
} catch (IllegalAccessException e) {
// something is very wrong if this crashes
throw new RuntimeException(e);
}
}
return methodModifiersSetter;
}

public static void setModifiers(Field field, int modifiers) {
try {
getFieldModifiersSetter().invokeExact(field, modifiers);
} catch (Throwable e) {
// unlikely to crash
throw new RuntimeException(e);
}
}

public static void setModifiers(Method method, int modifiers) {
try {
getMethodModifiersSetter().invokeExact(method, modifiers);
} catch (Throwable e) {
// unlikely to crash
throw new RuntimeException(e);
}
}

public static boolean setFinal(Field field, boolean isFinal) {
int m = field.getModifiers();
if (Modifier.isFinal(m) == isFinal) return false;
if (modifiersField == null) {
modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
}
if (isFinal) m |= Modifier.FINAL;
else m &= ~Modifier.FINAL;
LOOKUP.unreflectSetter(modifiersField).invokeExact(field, m);
setModifiers(field, m);
return true;
}

Expand Down Expand Up @@ -77,4 +145,23 @@ public static Object getField(Object owner, String name) {
return null;
}
}

public static int makeModifiersPublic(int modifiers) {
if (!Modifier.isPublic(modifiers)) modifiers |= Modifier.PUBLIC;
if (Modifier.isProtected(modifiers)) modifiers &= ~Modifier.PROTECTED;
if (Modifier.isPrivate(modifiers)) modifiers &= ~Modifier.PRIVATE;
return modifiers;
}

public static void makeFieldPublic(Field field) {
int mod = field.getModifiers();
int newMod = makeModifiersPublic(mod);
if (mod != newMod) setModifiers(field, newMod);
}

public static void makeMethodPublic(Method method) {
int mod = method.getModifiers();
int newMod = makeModifiersPublic(mod);
if (mod != newMod) setModifiers(method, newMod);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@
import com.cleanroommc.groovyscript.event.ScriptRunEvent;
import com.cleanroommc.groovyscript.helper.Alias;
import com.cleanroommc.groovyscript.helper.GroovyHelper;
import com.cleanroommc.groovyscript.helper.MetaClassExpansion;
import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager;
import com.cleanroommc.groovyscript.sandbox.expand.ExpansionHelper;
import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler;
import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptEarlyCompiler;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.Script;
import groovy.lang.*;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
Expand Down Expand Up @@ -60,6 +59,8 @@ public GroovyScriptSandbox() {
registerBinding("Log", GroovyLog.get());
registerBinding("EventManager", GroovyEventManager.INSTANCE);

ExpansionHelper.mixinClass(MetaClass.class, MetaClassExpansion.class);

getImportCustomizer().addStaticStars(GroovyHelper.class.getName(), MathHelper.class.getName());
getImportCustomizer().addImports(
"net.minecraft.world.World",
Expand Down