From d7fe6572f118e5fbe694e660ad18eb1b47e25a99 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Thu, 13 Mar 2025 13:12:34 +0100 Subject: [PATCH 01/13] custom engine --- .../groovyscript/command/GSCommand.java | 2 +- .../mixin/groovy/ClassCollectorMixin.java | 25 - .../groovyscript/sandbox/CompiledClass.java | 12 +- .../groovyscript/sandbox/CompiledScript.java | 23 + .../sandbox/CustomGroovyScriptEngine.java | 545 ++++++++++++++++++ .../groovyscript/sandbox/GroovyLogImpl.java | 2 +- .../sandbox/GroovyScriptSandbox.java | 367 ++++++------ .../compiler/ILanguageServerContext.java | 4 +- src/main/resources/mixin.groovyscript.json | 2 - 9 files changed, 755 insertions(+), 227 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/ClassCollectorMixin.java create mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java diff --git a/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java b/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java index f5023df9f..6a69ffaa8 100644 --- a/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java +++ b/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java @@ -102,7 +102,7 @@ public GSCommand() { })); addSubcommand(new SimpleCommand("deleteScriptCache", (server, sender, args) -> { - if (GroovyScript.getSandbox().deleteScriptCache()) { + if (GroovyScript.getSandbox().getEngine().deleteScriptCache()) { sender.sendMessage(new TextComponentString("Deleted groovy script cache").setStyle(StyleConstant.getSuccessStyle())); } else { sender.sendMessage(new TextComponentString("An error occurred while deleting groovy script cache").setStyle(StyleConstant.getErrorStyle())); diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/ClassCollectorMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/ClassCollectorMixin.java deleted file mode 100644 index 6d2757f76..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/ClassCollectorMixin.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cleanroommc.groovyscript.core.mixin.groovy; - -import com.cleanroommc.groovyscript.GroovyScript; -import groovy.lang.GroovyClassLoader; -import org.codehaus.groovy.ast.ClassNode; -import org.codehaus.groovy.control.SourceUnit; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(value = GroovyClassLoader.ClassCollector.class, remap = false) -public class ClassCollectorMixin { - - @Shadow - @Final - private SourceUnit su; - - @Inject(method = "createClass", at = @At("RETURN")) - public void onCreateClass(byte[] code, ClassNode classNode, CallbackInfoReturnable> cir) { - GroovyScript.getSandbox().onCompileClass(su, su.getName(), cir.getReturnValue(), code, classNode.getName().contains("$")); - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java index 0ca08784d..47f7e47fc 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java @@ -1,6 +1,7 @@ package com.cleanroommc.groovyscript.sandbox; import com.cleanroommc.groovyscript.api.GroovyLog; +import groovy.lang.GroovyClassLoader; import org.apache.commons.lang3.builder.ToStringBuilder; import java.io.File; @@ -34,7 +35,7 @@ public void onCompile(Class clazz, String basePath) { GroovyLog.get().errorMC("The class doesnt seem to be compiled yet. (" + name + ")"); return; } - if (!GroovyScriptSandbox.ENABLE_CACHE) return; + if (!CustomGroovyScriptEngine.ENABLE_CACHE) return; try { File file = getDataFile(basePath); file.getParentFile().mkdirs(); @@ -47,14 +48,21 @@ public void onCompile(Class clazz, String basePath) { } } + @Deprecated protected void ensureLoaded(CachedClassLoader classLoader, String basePath) { if (this.clazz == null) { this.clazz = classLoader.defineClass(this.name, this.data); } } + protected void ensureLoaded(GroovyClassLoader classLoader, String basePath) { + if (this.clazz == null) { + this.clazz = classLoader.defineClass(this.name, this.data); + } + } + public boolean readData(String basePath) { - if (this.data != null && GroovyScriptSandbox.ENABLE_CACHE) return true; + if (this.data != null && CustomGroovyScriptEngine.ENABLE_CACHE) return true; File file = getDataFile(basePath); if (!file.exists()) return false; try { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java index 4db460301..d6bd3e7e5 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java @@ -5,6 +5,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import groovy.lang.GroovyClassLoader; import org.apache.commons.lang3.builder.ToStringBuilder; import org.jetbrains.annotations.NotNull; @@ -17,6 +18,8 @@ class CompiledScript extends CompiledClass { final List innerClasses = new ArrayList<>(); long lastEdited; List preprocessors; + boolean preprocessorCheckFailed; + boolean requiresReload; public CompiledScript(String path, long lastEdited) { this(path, null, lastEdited); @@ -31,6 +34,12 @@ public boolean isClosure() { return lastEdited < 0; } + @Override + public void onCompile(Class clazz, String basePath) { + this.requiresReload = this.data == null; + super.onCompile(clazz, basePath); + } + public CompiledClass findInnerClass(String clazz) { for (CompiledClass comp : this.innerClasses) { if (comp.name.equals(clazz)) { @@ -42,6 +51,7 @@ public CompiledClass findInnerClass(String clazz) { return comp; } + @Deprecated public void ensureLoaded(CachedClassLoader classLoader, String basePath) { for (CompiledClass comp : this.innerClasses) { if (comp.clazz == null) { @@ -55,6 +65,19 @@ public void ensureLoaded(CachedClassLoader classLoader, String basePath) { super.ensureLoaded(classLoader, basePath); } + public void ensureLoaded(GroovyClassLoader classLoader, String basePath) { + for (CompiledClass comp : this.innerClasses) { + if (comp.clazz == null) { + if (comp.readData(basePath)) { + comp.ensureLoaded(classLoader, basePath); + } else { + GroovyLog.get().error("Error loading inner class {} for class {}", comp.name, this.name); + } + } + } + super.ensureLoaded(classLoader, basePath); + } + public @NotNull JsonObject toJson() { JsonObject jsonEntry = new JsonObject(); jsonEntry.addProperty("name", this.name); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java new file mode 100644 index 000000000..fbac95b29 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -0,0 +1,545 @@ +package com.cleanroommc.groovyscript.sandbox; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.helper.JsonHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyCodeSource; +import groovy.lang.GroovyResourceLoader; +import groovy.util.ResourceConnector; +import groovy.util.ResourceException; +import groovy.util.ScriptException; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.apache.commons.io.FileUtils; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.*; +import org.codehaus.groovy.runtime.IOGroovyMethods; +import org.codehaus.groovy.tools.gse.DependencyTracker; +import org.codehaus.groovy.tools.gse.StringSetMap; +import org.codehaus.groovy.vmplugin.VMPlugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.security.CodeSource; +import java.util.HashMap; +import java.util.Map; + +public class CustomGroovyScriptEngine implements ResourceConnector { + + /** + * Changing this number will force the cache to be deleted and every script has to be recompiled. + * Useful when changes to the compilation process were made. + */ + public static final int CACHE_VERSION = 3; + /** + * Setting this to false will cause compiled classes to never be cached. + * As a side effect some compilation behaviour might change. Can be useful for debugging. + */ + public static final boolean ENABLE_CACHE = true; + /** + * Setting this to true will cause the cache to be deleted before each script run. + * Useful for debugging. + */ + public static final boolean DELETE_CACHE_ON_RUN = Boolean.parseBoolean(System.getProperty("groovyscript.disable_cache")); + + private static WeakReference> localData = new WeakReference<>(null); + + private static synchronized ThreadLocal getLocalData() { + ThreadLocal local = localData.get(); + if (local != null) return local; + local = new ThreadLocal<>(); + localData = new WeakReference<>(local); + return local; + } + + private final URL[] scriptEnvironment; + private final File cacheRoot; + private final File scriptRoot; + private final CompilerConfiguration config; + private final ScriptClassLoader classLoader; + private final Map index = new Object2ObjectOpenHashMap<>(); + private final Map> loadedScripts = new Object2ObjectOpenHashMap<>(); + + public CustomGroovyScriptEngine(URL[] scriptEnvironment, File cacheRoot, File scriptRoot, CompilerConfiguration config) { + this.scriptEnvironment = scriptEnvironment; + this.cacheRoot = cacheRoot; + this.scriptRoot = scriptRoot; + this.config = config; + this.classLoader = new ScriptClassLoader(CustomGroovyScriptEngine.class.getClassLoader(), config); + readIndex(); + } + + public File getScriptRoot() { + return scriptRoot; + } + + public File getCacheRoot() { + return cacheRoot; + } + + public CompilerConfiguration getConfig() { + return config; + } + + public GroovyClassLoader getClassLoader() { + return classLoader; + } + + void readIndex() { + this.index.clear(); + JsonElement jsonElement = JsonHelper.loadJson(new File(this.cacheRoot, "_index.json")); + if (jsonElement == null || !jsonElement.isJsonObject()) return; + JsonObject json = jsonElement.getAsJsonObject(); + int cacheVersion = json.get("version").getAsInt(); + String java = json.has("java") ? json.get("java").getAsString() : ""; + if (cacheVersion != CACHE_VERSION || !java.equals(VMPlugin.getJavaVersion())) { + // cache version changed -> force delete cache + deleteScriptCache(); + return; + } + for (JsonElement element : json.getAsJsonArray("index")) { + if (element.isJsonObject()) { + CompiledScript cs = CompiledScript.fromJson(element.getAsJsonObject(), this.scriptRoot.getPath(), this.cacheRoot.getPath()); + if (cs != null) { + this.index.put(cs.path, cs); + } + } + } + } + + void writeIndex() { + if (!ENABLE_CACHE) return; + JsonObject json = new JsonObject(); + json.addProperty("!DANGER!", "DO NOT EDIT THIS FILE!!!"); + json.addProperty("version", CACHE_VERSION); + json.addProperty("java", VMPlugin.getJavaVersion()); + JsonArray index = new JsonArray(); + json.add("index", index); + for (Map.Entry entry : this.index.entrySet()) { + index.add(entry.getValue().toJson()); + } + JsonHelper.saveJson(new File(this.cacheRoot, "_index.json"), json); + } + + @ApiStatus.Internal + public boolean deleteScriptCache() { + this.index.clear(); + getClassLoader().clearCache(); + try { + FileUtils.cleanDirectory(this.cacheRoot); + return true; + } catch (IOException e) { + GroovyScript.LOGGER.throwing(e); + return false; + } + } + + CompiledScript loadScriptClass(File file) { + CompiledScript compiledScript = checkScriptLoadability(file); + if (compiledScript.requiresReload && !compiledScript.preprocessorCheckFailed) { + Class clazz = loadScriptClassInternal(new File(compiledScript.path), true); + if (compiledScript.clazz == null) { + // should not happen + GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", compiledScript.path); + if (ENABLE_CACHE) compiledScript.clazz = clazz; + } + } + return compiledScript; + } + + CompiledScript checkScriptLoadability(File file) { + String relativeFileName = FileUtil.relativize(this.scriptRoot.getPath(), file.getPath()); + File relativeFile = new File(relativeFileName); + long lastModified = file.lastModified(); + CompiledScript comp = this.index.get(relativeFileName); + + if (ENABLE_CACHE && comp != null && lastModified <= comp.lastEdited && comp.clazz == null && comp.readData(this.cacheRoot.getPath())) { + // class is not loaded, but the cached class bytes are still valid + comp.requiresReload = false; + if (!comp.checkPreprocessors(this.scriptRoot)) { + comp.preprocessorCheckFailed = true; + return comp; + } + comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + } else if (!ENABLE_CACHE || (comp == null || comp.clazz == null || lastModified > comp.lastEdited)) { + // class is not loaded and class bytes don't exist yet or script has been edited + if (comp == null) { + comp = new CompiledScript(relativeFileName, 0); + this.index.put(relativeFileName, comp); + } + comp.requiresReload = true; + if (lastModified > comp.lastEdited || comp.preprocessors == null) { + // recompile preprocessors if there is no data or script was edited + comp.preprocessors = Preprocessor.parsePreprocessors(file); + } + comp.lastEdited = lastModified; + if (!comp.checkPreprocessors(this.scriptRoot)) { + // delete class bytes to make sure it's recompiled once the preprocessors returns true + comp.deleteCache(this.cacheRoot.getPath()); + comp.clazz = null; + comp.data = null; + comp.preprocessorCheckFailed = true; + return comp; + } + } else { + // class is loaded and script wasn't edited + comp.requiresReload = false; + if (!comp.checkPreprocessors(this.scriptRoot)) { + comp.preprocessorCheckFailed = true; + return comp; + } + comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + } + comp.preprocessorCheckFailed = false; + return comp; + } + + protected Class loadScriptClassInternal(File file, boolean isFileRelative) { + Class scriptClass = null; + try { + scriptClass = parseDynamicScript(file, isFileRelative); + } catch (Exception e) { + GroovyLog.get().exception("An error occurred while trying to load script class " + file.toString(), e); + } + return scriptClass; + } + + @Nullable + private File findScriptFileOfClass(String className) { + for (String ending : this.config.getScriptExtensions()) { + File file = findScriptFile(className + "." + ending); + if (file != null) return file; + } + return null; + } + + @Nullable + private File findScriptFile(String scriptName) { + File file; + for (URL root : this.scriptEnvironment) { + try { + File rootFile = new File(root.toURI()); + // try to combine the root with the file ending + file = new File(rootFile, scriptName); + if (file.exists()) { + // found a valid file + return file; + } + } catch (URISyntaxException e) { + GroovyScript.LOGGER.throwing(e); + } + } + return null; + } + + private @Nullable Class parseDynamicScript(File file, boolean isFileRelative) { + if (isFileRelative) { + file = findScriptFile(file.getPath()); + if (file == null) return null; + } + Class clazz = null; + try { + String encoding = config.getSourceEncoding(); + String content = IOGroovyMethods.getText(new FileInputStream(file), encoding); + clazz = this.classLoader.parseClassRaw(content, file.toPath().toUri().toURL().toExternalForm()); + // manually load the file as a groovy script + //clazz = this.classLoader.parseClassRaw(path.toFile()); + } catch (IOException e) { + GroovyScript.LOGGER.throwing(e); + } + return clazz; + } + + /** + * Called via mixin when groovy compiled a class from scripts. + */ + @ApiStatus.Internal + public void onCompileClass(SourceUnit su, String path, Class clazz, byte[] code, boolean inner) { + String shortPath = FileUtil.relativize(this.scriptRoot.getPath(), path); + // if the script was compiled because another script depends on it, the source unit is wrong + // we need to find the source unit of the compiled class + SourceUnit trueSource = su.getAST().getUnit().getScriptSourceLocation(mainClassName(clazz.getName())); + String truePath = trueSource == null ? shortPath : FileUtil.relativize(this.scriptRoot.getPath(), trueSource.getName()); + if (shortPath.equals(truePath) && su.getAST().getMainClassName() != null && !su.getAST().getMainClassName().equals(clazz.getName())) { + inner = true; + } + + boolean finalInner = inner; + CompiledScript comp = this.index.computeIfAbsent(truePath, k -> new CompiledScript(k, finalInner ? -1 : 0)); + CompiledClass innerClass = comp; + if (inner) innerClass = comp.findInnerClass(clazz.getName()); + innerClass.onCompile(code, clazz, this.cacheRoot.getPath()); + this.loadedScripts.put(clazz.getName(), clazz); + } + + /** + * Called via mixin when a script class needs to be recompiled. This happens when a script was loaded because another script depends on + * it. Groovy will then try to compile the script again. If we already compiled the class we just stop the compilation process. + */ + @ApiStatus.Internal + public Class onRecompileClass(URL source, String className) { + String path = source.toExternalForm(); + String rel = FileUtil.relativize(this.scriptRoot.getPath(), path); + CompiledScript cs = this.index.get(rel); + Class c = null; + if (cs != null) { + if (cs.clazz == null && cs.readData(this.cacheRoot.getPath())) { + cs.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + } + c = cs.clazz; + } + return c; + } + + private static String mainClassName(String name) { + return name.contains("$") ? name.split("\\$", 2)[0] : name; + } + + + @Override + public URLConnection getResourceConnection(String resourceName) throws ResourceException { + // Get the URLConnection + URLConnection groovyScriptConn = null; + + ResourceException se = null; + for (URL root : this.scriptEnvironment) { + URL scriptURL = null; + try { + scriptURL = new URL(root, resourceName); + groovyScriptConn = openConnection(scriptURL); + + break; // Now this is a bit unusual + } catch (MalformedURLException e) { + String message = "Malformed URL: with context=" + root + " and spec=" + resourceName + " because " + e.getMessage(); + if (se == null) { + se = new ResourceException(message); + } else { + se = new ResourceException(message, se); + } + } catch (IOException e1) { + String message = "Cannot open URL: " + scriptURL; + groovyScriptConn = null; + if (se == null) { + se = new ResourceException(message); + } else { + se = new ResourceException(message, se); + } + } + } + + if (se == null) se = new ResourceException("No resource for " + resourceName + " was found"); + + // If we didn't find anything, report on all the exceptions that occurred. + if (groovyScriptConn == null) throw se; + return groovyScriptConn; + } + + private static URLConnection openConnection(URL scriptURL) throws IOException { + URLConnection urlConnection = scriptURL.openConnection(); + verifyInputStream(urlConnection); + + return scriptURL.openConnection(); + } + + private static void forceClose(URLConnection urlConnection) { + if (urlConnection != null) { + // We need to get the input stream and close it to force the open + // file descriptor to be released. Otherwise, we will reach the limit + // for number of files open at one time. + + try { + verifyInputStream(urlConnection); + } catch (Exception e) { + // Do nothing: We were not going to use it anyway. + } + } + } + + private static void verifyInputStream(URLConnection urlConnection) throws IOException { + try (InputStream in = urlConnection.getInputStream()) {} + } + + private static class LocalData { + + CompilationUnit cu; + final StringSetMap dependencyCache = new StringSetMap(); + final Map precompiledEntries = new HashMap<>(); + } + + private class ScriptClassLoader extends GroovyClassLoader { + + + public ScriptClassLoader(GroovyClassLoader loader) { + super(loader); + } + + public ScriptClassLoader(ClassLoader loader, CompilerConfiguration config) { + super(loader, config, false); + setResLoader(); + } + + private void setResLoader() { + final GroovyResourceLoader rl = getResourceLoader(); + setResourceLoader(className -> { + File file = CustomGroovyScriptEngine.this.findScriptFileOfClass(className); + if (file != null) { + return file.toURI().toURL(); + } + return rl.loadGroovySource(className); + }); + } + + @Override + protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { + return new CustomClassCollector(new InnerLoader(this), unit, su, CustomGroovyScriptEngine.this); + } + + @Override + protected CompilationUnit createCompilationUnit(CompilerConfiguration configuration, CodeSource source) { + CompilationUnit cu = super.createCompilationUnit(configuration, source); + LocalData local = getLocalData().get(); + local.cu = cu; + final StringSetMap cache = local.dependencyCache; + final Map precompiledEntries = local.precompiledEntries; + + // "." is used to transfer compilation dependencies, which will be + // recollected later during compilation + for (String depSourcePath : cache.get(".")) { + try { + cache.get(depSourcePath); + cu.addSource(getResourceConnection(depSourcePath).getURL()); // todo remove usage of resource connection + } catch (ResourceException e) { + /* ignore */ + } + } + + // remove all old entries including the "." entry + cache.clear(); + + cu.addPhaseOperation((final SourceUnit sourceUnit, final GeneratorContext context, final ClassNode classNode) -> { + // GROOVY-4013: If it is an inner class, tracking its dependencies doesn't really + // serve any purpose and also interferes with the caching done to track dependencies + if (classNode.getOuterClass() != null) return; + DependencyTracker dt = new DependencyTracker(sourceUnit, cache, precompiledEntries); + dt.visitClass(classNode); + }, Phases.CLASS_GENERATION); + + cu.setClassNodeResolver(new ClassNodeResolver() { + @Override + public LookupResult findClassNode(String origName, CompilationUnit compilationUnit) { + String name = origName.replace('.', '/'); + File scriptFile = CustomGroovyScriptEngine.this.findScriptFileOfClass(name); + if (scriptFile != null) { + CompiledScript result = checkScriptLoadability(scriptFile); + if (result.requiresReload || result.clazz == null) { + try { + return new LookupResult(compilationUnit.addSource(scriptFile.toURI().toURL()), null); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } else { + return new LookupResult(null, ClassHelper.make(result.clazz)); + } + } + return super.findClassNode(origName, compilationUnit); + } + }); + + return cu; + } + + @Override + protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException { + if (source != null && oldClass == null) { + Class c = CustomGroovyScriptEngine.this.onRecompileClass(source, className); + if (c != null) { + return c; + } + } + return super.recompile(source, className, oldClass); + } + + @Override + public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException { + synchronized (sourceCache) { + File file = codeSource.getFile(); + if (file == null) { + file = new File(codeSource.getName()); + } + CompiledScript compiledScript = loadScriptClass(file); + if (compiledScript.preprocessorCheckFailed) { + throw new IllegalStateException("Figure this out"); + } + if (compiledScript.requiresReload || compiledScript.clazz == null) { + compiledScript.clazz = CustomGroovyScriptEngine.this.parseDynamicScript(file, false); + } + return compiledScript.clazz; + } + } + + public Class parseClassRaw(GroovyCodeSource source) { + synchronized (sourceCache) { + return doParseClass(source); + } + } + + public Class parseClassRaw(File file) throws IOException { + return parseClassRaw(new GroovyCodeSource(file, CustomGroovyScriptEngine.this.config.getSourceEncoding())); + } + + public Class parseClassRaw(final String text, final String fileName) throws CompilationFailedException { + GroovyCodeSource gcs = new GroovyCodeSource(text, fileName, "/groovy/script"); + gcs.setCachable(false); + return parseClassRaw(gcs); + } + + private Class doParseClass(GroovyCodeSource codeSource) { + // local is kept as hard reference to avoid garbage collection + ThreadLocal localTh = getLocalData(); + LocalData localData = new LocalData(); + localTh.set(localData); + StringSetMap cache = localData.dependencyCache; + Class answer = null; + try { + answer = super.parseClass(codeSource, false); + } finally { + cache.clear(); + localTh.remove(); + } + return answer; + } + } + + public static class CustomClassCollector extends GroovyClassLoader.ClassCollector { + + private final SourceUnit su; + private final CustomGroovyScriptEngine engine; + + protected CustomClassCollector(GroovyClassLoader.InnerLoader cl, CompilationUnit unit, SourceUnit su, CustomGroovyScriptEngine engine) { + super(cl, unit, su); + this.su = su; + this.engine = engine; + } + + @Override + protected Class createClass(byte[] code, ClassNode classNode) { + Class clz = super.createClass(code, classNode); + this.engine.onCompileClass(this.su, this.su.getName(), clz, code, classNode.getName().contains("$")); + return clz; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java index cf38f96e3..020e1d4d7 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java @@ -283,7 +283,7 @@ public void exception(String msg, Throwable throwable) { private List prepareStackTrace(StackTraceElement[] stackTrace) { List lines = Arrays.stream(stackTrace).map(StackTraceElement::toString).collect(Collectors.toList()); - String engineCause = "com.cleanroommc.groovyscript.sandbox.GroovySandbox.loadScripts"; + String engineCause = "com.cleanroommc.groovyscript.sandbox.GroovyScriptSandbox.loadScripts"; int i = 0; for (String line : lines) { i++; diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 811ba19e3..912f5d13b 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -3,71 +3,59 @@ import com.cleanroommc.groovyscript.GroovyScript; import com.cleanroommc.groovyscript.api.GroovyBlacklist; import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.INamed; import com.cleanroommc.groovyscript.compat.mods.ModSupport; import com.cleanroommc.groovyscript.event.GroovyEventManager; import com.cleanroommc.groovyscript.event.GroovyReloadEvent; import com.cleanroommc.groovyscript.event.ScriptRunEvent; +import com.cleanroommc.groovyscript.helper.Alias; import com.cleanroommc.groovyscript.helper.GroovyHelper; -import com.cleanroommc.groovyscript.helper.JsonHelper; import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptEarlyCompiler; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import groovy.lang.*; -import groovy.util.GroovyScriptEngine; +import groovy.lang.Binding; +import groovy.lang.Closure; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.Script; import groovy.util.ResourceException; import groovy.util.ScriptException; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.util.math.MathHelper; import net.minecraftforge.common.MinecraftForge; -import org.apache.commons.io.FileUtils; import org.apache.groovy.internal.util.UncheckedThrow; import org.codehaus.groovy.control.CompilerConfiguration; -import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.customizers.ImportCustomizer; +import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.InvokerInvocationException; -import org.codehaus.groovy.vmplugin.VMPlugin; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.net.URL; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -public class GroovyScriptSandbox extends GroovySandbox { - - /** - * Changing this number will force the cache to be deleted and every script has to be recompiled. - * Useful when changes to the compilation process were made. - */ - public static final int CACHE_VERSION = 3; - /** - * Setting this to false will cause compiled classes to never be cached. - * As a side effect some compilation behaviour might change. Can be useful for debugging. - */ - public static final boolean ENABLE_CACHE = true; - /** - * Setting this to true will cause the cache to be deleted before each script run. - * Useful for debugging. - */ - public static final boolean DELETE_CACHE_ON_RUN = Boolean.parseBoolean(System.getProperty("groovyscript.disable_cache")); - - private final File cacheRoot; - private final File scriptRoot; - private final Map, AtomicInteger> storedExceptions; - private final Map index = new Object2ObjectOpenHashMap<>(); +public class GroovyScriptSandbox { + private final CustomGroovyScriptEngine engine; + + private String currentScript; private LoadStage currentLoadStage; - @ApiStatus.Internal + private final ThreadLocal running = ThreadLocal.withInitial(() -> false); + private final Map bindings = new Object2ObjectOpenHashMap<>(); + private final ImportCustomizer importCustomizer = new ImportCustomizer(); + private final Map, AtomicInteger> storedExceptions = new Object2ObjectOpenHashMap<>(); + + private long compileTime; + private long runTime; + public GroovyScriptSandbox() { - super(SandboxData.getRootUrls()); - this.scriptRoot = SandboxData.getScriptFile(); - this.cacheRoot = SandboxData.getCachePath(); + CompilerConfiguration config = new CompilerConfiguration(); + initEngine(config); + this.engine = new CustomGroovyScriptEngine(SandboxData.getRootUrls(), SandboxData.getCachePath(), SandboxData.getScriptFile(), config); registerBinding("Mods", ModSupport.INSTANCE); registerBinding("Log", GroovyLog.get()); registerBinding("EventManager", GroovyEventManager.INSTANCE); @@ -104,88 +92,65 @@ public GroovyScriptSandbox() { "com.cleanroommc.groovyscript.event.EventBusType", "net.minecraftforge.fml.relauncher.Side", "net.minecraftforge.fml.relauncher.SideOnly"); - this.storedExceptions = new Object2ObjectOpenHashMap<>(); - readIndex(); - } - - private void readIndex() { - this.index.clear(); - JsonElement jsonElement = JsonHelper.loadJson(new File(this.cacheRoot, "_index.json")); - if (jsonElement == null || !jsonElement.isJsonObject()) return; - JsonObject json = jsonElement.getAsJsonObject(); - int cacheVersion = json.get("version").getAsInt(); - String java = json.has("java") ? json.get("java").getAsString() : ""; - if (cacheVersion != CACHE_VERSION || !java.equals(VMPlugin.getJavaVersion())) { - // cache version changed -> force delete cache - deleteScriptCache(); - return; - } - for (JsonElement element : json.getAsJsonArray("index")) { - if (element.isJsonObject()) { - CompiledScript cs = CompiledScript.fromJson(element.getAsJsonObject(), this.scriptRoot.getPath(), this.cacheRoot.getPath()); - if (cs != null) { - this.index.put(cs.path, cs); - } - } - } } - private void writeIndex() { - if (!ENABLE_CACHE) return; - JsonObject json = new JsonObject(); - json.addProperty("!DANGER!", "DO NOT EDIT THIS FILE!!!"); - json.addProperty("version", CACHE_VERSION); - json.addProperty("java", VMPlugin.getJavaVersion()); - JsonArray index = new JsonArray(); - json.add("index", index); - for (Map.Entry entry : this.index.entrySet()) { - index.add(entry.getValue().toJson()); - } - JsonHelper.saveJson(new File(this.cacheRoot, "_index.json"), json); + protected Binding createBindings() { + Binding binding = new Binding(this.bindings); + postInitBindings(binding); + return binding; } - public void checkSyntax() { - GroovyScriptEngine engine = createScriptEngine(); - Binding binding = createBindings(); - Set executedClasses = new ObjectOpenHashSet<>(); + public void registerBinding(String name, Object obj) { + Objects.requireNonNull(name); + Objects.requireNonNull(obj); + for (String alias : Alias.generateOf(name)) { + bindings.put(alias, obj); + } + } - for (LoadStage loadStage : LoadStage.getLoadStages()) { - GroovyLog.get().info("Checking syntax in loader '{}'", this.currentLoadStage); - this.currentLoadStage = loadStage; - load(engine, binding, executedClasses, false); + public void registerBinding(INamed named) { + Objects.requireNonNull(named); + for (String alias : named.getAliases()) { + bindings.put(alias, named); } } public void run(LoadStage currentLoadStage) { this.currentLoadStage = Objects.requireNonNull(currentLoadStage); try { - super.load(); + load(); } catch (IOException | ScriptException | ResourceException e) { GroovyLog.get().exception("An exception occurred while trying to run groovy code! This is might be a internal groovy issue.", e); } catch (Throwable t) { GroovyLog.get().exception(t); } finally { + GroovyLog.get().infoMC("Groovy scripts took {}ms to compile and {}ms to run in {}.", this.compileTime, this.runTime, currentLoadStage.getName()); this.currentLoadStage = null; if (currentLoadStage == LoadStage.POST_INIT) { - writeIndex(); + engine.writeIndex(); } } } - @Override protected void runScript(Script script) { GroovyLog.get().info(" - running {}", script.getClass().getName()); - super.runScript(script); + setCurrentScript(script.getClass().getName()); + script.run(); + setCurrentScript(null); } - @ApiStatus.Internal - @Override - public void load() throws Exception { - throw new UnsupportedOperationException("Use run(Loader loader) instead!"); + public void checkSyntax() { + Binding binding = createBindings(); + Set executedClasses = new ObjectOpenHashSet<>(); + + for (LoadStage loadStage : LoadStage.getLoadStages()) { + GroovyLog.get().info("Checking syntax in loader '{}'", this.currentLoadStage); + this.currentLoadStage = loadStage; + load(binding, executedClasses, false); + } } @ApiStatus.Internal - @Override public T runClosure(Closure closure, Object... args) { boolean wasRunning = isRunning(); if (!wasRunning) startRunning(); @@ -226,113 +191,111 @@ private static T runClosureInternal(Closure closure, Object[] args) throw } } - private static String mainClassName(String name) { - return name.contains("$") ? name.split("\\$", 2)[0] : name; - } + private void load() throws Exception { + preRun(); - /** - * Called via mixin when groovy compiled a class from scripts. - */ - @ApiStatus.Internal - public void onCompileClass(SourceUnit su, String path, Class clazz, byte[] code, boolean inner) { - String shortPath = FileUtil.relativize(this.scriptRoot.getPath(), path); - // if the script was compiled because another script depends on it, the source unit is wrong - // we need to find the source unit of the compiled class - SourceUnit trueSource = su.getAST().getUnit().getScriptSourceLocation(mainClassName(clazz.getName())); - String truePath = trueSource == null ? shortPath : FileUtil.relativize(this.scriptRoot.getPath(), trueSource.getName()); - if (shortPath.equals(truePath) && su.getAST().getMainClassName() != null && !su.getAST().getMainClassName().equals(clazz.getName())) { - inner = true; + Binding binding = createBindings(); + Set executedClasses = new ObjectOpenHashSet<>(); + + this.running.set(true); + try { + load(binding, executedClasses, true); + } finally { + this.running.set(false); + postRun(); + setCurrentScript(null); } + } - boolean finalInner = inner; - CompiledScript comp = this.index.computeIfAbsent(truePath, k -> new CompiledScript(k, finalInner ? -1 : 0)); - CompiledClass innerClass = comp; - if (inner) innerClass = comp.findInnerClass(clazz.getName()); - innerClass.onCompile(code, clazz, this.cacheRoot.getPath()); + protected void load(Binding binding, Set executedClasses, boolean run) { + this.compileTime = 0L; + this.runTime = 0L; + // load and run any configured class files + loadClassScripts(binding, executedClasses, run); + // now run all script files + loadScripts(binding, executedClasses, run); } - /** - * Called via mixin when a script class needs to be recompiled. This happens when a script was loaded because another script depends on - * it. Groovy will then try to compile the script again. If we already compiled the class we just stop the compilation process. - */ - @ApiStatus.Internal - public Class onRecompileClass(URL source, String className) { - String path = source.toExternalForm(); - String rel = FileUtil.relativize(this.scriptRoot.getPath(), path); - CompiledScript cs = this.index.get(rel); - Class c = null; - if (cs != null) { - if (cs.clazz == null && cs.readData(this.cacheRoot.getPath())) { - cs.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + protected void loadScripts(Binding binding, Set executedClasses, boolean run) { + for (File scriptFile : getScriptFiles()) { + if (!executedClasses.contains(scriptFile)) { + long t = System.currentTimeMillis(); + CompiledScript compiledScript = engine.loadScriptClass(scriptFile); + this.compileTime += System.currentTimeMillis() - t; + if (compiledScript.preprocessorCheckFailed) continue; + if (compiledScript.clazz == null) { + GroovyLog.get().errorMC("Error loading script for {}", scriptFile.getPath()); + GroovyLog.get().errorMC("Did you forget to register your class file in your run config?"); + continue; + } + if (compiledScript.clazz.getSuperclass() != Script.class) { + GroovyLog.get().errorMC("Class file '{}' should be defined in the runConfig in the classes property!", scriptFile); + continue; + } + if (run && shouldRunFile(scriptFile)) { + Script script = InvokerHelper.createScript(compiledScript.clazz, binding); + t = System.currentTimeMillis(); + runScript(script); + this.runTime += System.currentTimeMillis() - t; + } } - c = cs.clazz; } - return c; } - @Override - protected Class loadScriptClass(GroovyScriptEngine engine, File file) { - String relativeFileName = FileUtil.relativize(this.scriptRoot.getPath(), file.getPath()); - File relativeFile = new File(relativeFileName); - long lastModified = file.lastModified(); - CompiledScript comp = this.index.get(relativeFileName); - - if (ENABLE_CACHE && comp != null && lastModified <= comp.lastEdited && comp.clazz == null && comp.readData(this.cacheRoot.getPath())) { - // class is not loaded, but the cached class bytes are still valid - if (!comp.checkPreprocessors(this.scriptRoot)) { - return GroovyLog.class; // failed preprocessor check - } - comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); - - } else if (!ENABLE_CACHE || (comp == null || comp.clazz == null || lastModified > comp.lastEdited)) { - // class is not loaded and class bytes don't exist yet or script has been edited - if (comp == null) { - comp = new CompiledScript(relativeFileName, 0); - this.index.put(relativeFileName, comp); - } - if (lastModified > comp.lastEdited || comp.preprocessors == null) { - // recompile preprocessors if there is no data or script was edited - comp.preprocessors = Preprocessor.parsePreprocessors(file); - } - comp.lastEdited = lastModified; - if (!comp.checkPreprocessors(this.scriptRoot)) { - // delete class bytes to make sure it's recompiled once the preprocessors returns true - comp.deleteCache(this.cacheRoot.getPath()); - comp.clazz = null; - comp.data = null; - return GroovyLog.class; // failed preprocessor check + protected void loadClassScripts(Binding binding, Set executedClasses, boolean run) { + for (File classFile : getClassFiles()) { + if (executedClasses.contains(classFile)) continue; + long t = System.currentTimeMillis(); + CompiledScript compiledScript = engine.loadScriptClass(classFile); + this.compileTime += System.currentTimeMillis() - t; + if (compiledScript.preprocessorCheckFailed) continue; + if (compiledScript.clazz == null) { + // loading script fails if the file is a script that depends on a class file that isn't loaded yet + // we cant determine if the file is a script or a class + continue; } - Class clazz = super.loadScriptClass(engine, relativeFile); - if (comp.clazz == null) { - // should not happen - GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", relativeFileName); - if (ENABLE_CACHE) comp.clazz = clazz; - } - } else { - // class is loaded and script wasn't edited - if (!comp.checkPreprocessors(this.scriptRoot)) { - return GroovyLog.class; // failed preprocessor check + // the superclass of class files is Object + if (compiledScript.clazz.getSuperclass() != Script.class) { + if (run && shouldRunFile(classFile)) { + try { + // $getLookup is present on all groovy created classes + // call it cause the class to be initialised + Method m = compiledScript.clazz.getMethod("$getLookup"); + t = System.currentTimeMillis(); + m.invoke(null); + this.runTime += System.currentTimeMillis() - t; + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + GroovyLog.get().errorMC("Error initialising class '{}'", compiledScript.clazz.getName()); + } + } + executedClasses.add(classFile); } - comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); } - return comp.clazz; } - @Override + protected void startRunning() { + this.running.set(true); + } + + protected void stopRunning() { + this.running.set(false); + } + + @ApiStatus.OverrideOnly protected void postInitBindings(Binding binding) { binding.setProperty("out", GroovyLog.get().getWriter()); binding.setVariable("globals", getBindings()); } - @Override - protected void initEngine(GroovyScriptEngine engine, CompilerConfiguration config) { + @ApiStatus.OverrideOnly + protected void initEngine(CompilerConfiguration config) { config.addCompilationCustomizers(new GroovyScriptCompiler()); config.addCompilationCustomizers(new GroovyScriptEarlyCompiler()); } - @Override + @ApiStatus.OverrideOnly protected void preRun() { - if (DELETE_CACHE_ON_RUN) deleteScriptCache(); + if (CustomGroovyScriptEngine.DELETE_CACHE_ON_RUN) engine.deleteScriptCache(); // first clear all added events GroovyEventManager.INSTANCE.reset(); if (this.currentLoadStage.isReloadable() && !ReloadableRegistryManager.isFirstLoad()) { @@ -346,13 +309,12 @@ protected void preRun() { MinecraftForge.EVENT_BUS.post(new ScriptRunEvent.Pre(this.currentLoadStage)); } - @Override + @ApiStatus.OverrideOnly protected boolean shouldRunFile(File file) { - //GroovyLog.get().info(" - executing {}", file.toString()); return true; } - @Override + @ApiStatus.OverrideOnly protected void postRun() { if (this.currentLoadStage == LoadStage.POST_INIT) { ReloadableRegistryManager.afterScriptRun(); @@ -363,34 +325,51 @@ protected void postRun() { } } - @Override + public File getScriptRoot() { + return SandboxData.getScriptFile(); + } + public Collection getClassFiles() { - return GroovyScript.getRunConfig().getClassFiles(this.scriptRoot, this.currentLoadStage.getName()); + return GroovyScript.getRunConfig().getClassFiles(getScriptRoot(), this.currentLoadStage.getName()); } - @Override public Collection getScriptFiles() { - return GroovyScript.getRunConfig().getSortedFiles(this.scriptRoot, this.currentLoadStage.getName()); + return GroovyScript.getRunConfig().getSortedFiles(getScriptRoot(), this.currentLoadStage.getName()); + } + + public boolean isRunning() { + return this.running.get(); + } + + public Map getBindings() { + return bindings; + } + + public ImportCustomizer getImportCustomizer() { + return importCustomizer; + } + + public CustomGroovyScriptEngine getEngine() { + return engine; } - public @Nullable LoadStage getCurrentLoader() { + public String getCurrentScript() { + return currentScript; + } + + protected void setCurrentScript(String currentScript) { + this.currentScript = currentScript; + } + + public LoadStage getCurrentLoader() { return currentLoadStage; } - public File getScriptRoot() { - return scriptRoot; + public long getLastCompileTime() { + return compileTime; } - @ApiStatus.Internal - public boolean deleteScriptCache() { - this.index.clear(); - getClassLoader().clearCache(); - try { - FileUtils.cleanDirectory(this.cacheRoot); - return true; - } catch (IOException e) { - GroovyScript.LOGGER.throwing(e); - return false; - } + public long getLastRunTime() { + return runTime; } } diff --git a/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java b/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java index 60470c34d..328d45a3f 100644 --- a/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java +++ b/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java @@ -1,13 +1,13 @@ package net.prominic.groovyls.compiler; -import com.cleanroommc.groovyscript.sandbox.GroovySandbox; +import com.cleanroommc.groovyscript.sandbox.GroovyScriptSandbox; import io.github.classgraph.ScanResult; import net.prominic.groovyls.compiler.documentation.DocumentationFactory; import net.prominic.groovyls.util.FileContentsTracker; public interface ILanguageServerContext { - GroovySandbox getSandbox(); + GroovyScriptSandbox getSandbox(); ScanResult getScanResult(); diff --git a/src/main/resources/mixin.groovyscript.json b/src/main/resources/mixin.groovyscript.json index 6b3d2a423..5ca5064f1 100644 --- a/src/main/resources/mixin.groovyscript.json +++ b/src/main/resources/mixin.groovyscript.json @@ -23,10 +23,8 @@ "TileEntityPistonMixin", "VillagerProfessionAccessor", "groovy.AsmDecompilerMixin", - "groovy.ClassCollectorMixin", "groovy.ClosureMixin", "groovy.CompUnitClassGenMixin", - "groovy.GroovyClassLoaderMixin", "groovy.Java8Mixin", "groovy.MetaClassImplMixin", "groovy.ModuleNodeAccessor", From f724f994140e87c99a6f0f04e38295cbcb924a2d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Thu, 13 Mar 2025 13:15:32 +0100 Subject: [PATCH 02/13] remove mixin --- .../mixin/groovy/GroovyClassLoaderMixin.java | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/GroovyClassLoaderMixin.java diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/GroovyClassLoaderMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/GroovyClassLoaderMixin.java deleted file mode 100644 index 3e1ab7182..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/GroovyClassLoaderMixin.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cleanroommc.groovyscript.core.mixin.groovy; - -import com.cleanroommc.groovyscript.GroovyScript; -import groovy.lang.GroovyClassLoader; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.net.URL; - -/** - * If a script depends on another script and the there is a compiled cache for the script, it needs to be loaded manually. - */ -@Mixin(value = GroovyClassLoader.class, remap = false) -public class GroovyClassLoaderMixin { - - @Inject(method = "recompile", at = @At("HEAD"), cancellable = true) - public void onRecompile(URL source, String className, Class oldClass, CallbackInfoReturnable> cir) { - if (source != null && oldClass == null) { - Class c = GroovyScript.getSandbox().onRecompileClass(source, className); - if (c != null) { - cir.setReturnValue(c); - } - } - } -} From c4bf4ec64102d07b692504e46b77d731ebfc80b8 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Thu, 13 Mar 2025 13:49:55 +0100 Subject: [PATCH 03/13] spotless --- .../groovyscript/sandbox/CustomGroovyScriptEngine.java | 5 +++-- .../groovyscript/sandbox/GroovyScriptSandbox.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index fbac95b29..602609da4 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -11,7 +11,6 @@ import groovy.lang.GroovyResourceLoader; import groovy.util.ResourceConnector; import groovy.util.ResourceException; -import groovy.util.ScriptException; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.apache.commons.io.FileUtils; import org.codehaus.groovy.ast.ClassHelper; @@ -370,7 +369,8 @@ private static void forceClose(URLConnection urlConnection) { } private static void verifyInputStream(URLConnection urlConnection) throws IOException { - try (InputStream in = urlConnection.getInputStream()) {} + try (InputStream in = urlConnection.getInputStream()) { + } } private static class LocalData { @@ -439,6 +439,7 @@ protected CompilationUnit createCompilationUnit(CompilerConfiguration configurat }, Phases.CLASS_GENERATION); cu.setClassNodeResolver(new ClassNodeResolver() { + @Override public LookupResult findClassNode(String origName, CompilationUnit compilationUnit) { String name = origName.replace('.', '/'); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 912f5d13b..58cfc6ed2 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -234,9 +234,9 @@ protected void loadScripts(Binding binding, Set executedClasses, boolean r } if (run && shouldRunFile(scriptFile)) { Script script = InvokerHelper.createScript(compiledScript.clazz, binding); - t = System.currentTimeMillis(); - runScript(script); - this.runTime += System.currentTimeMillis() - t; + t = System.currentTimeMillis(); + runScript(script); + this.runTime += System.currentTimeMillis() - t; } } } From 5d1d583e3c7a6f5ee731b88d2cf699bdec1f0f52 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Thu, 13 Mar 2025 17:01:59 +0100 Subject: [PATCH 04/13] forgor default imports --- .../cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 58cfc6ed2..36e23fa33 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -289,6 +289,7 @@ protected void postInitBindings(Binding binding) { @ApiStatus.OverrideOnly protected void initEngine(CompilerConfiguration config) { + config.addCompilationCustomizers(this.importCustomizer); config.addCompilationCustomizers(new GroovyScriptCompiler()); config.addCompilationCustomizers(new GroovyScriptEarlyCompiler()); } From 2811a38477f629142f061b2de649330357237170 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Mon, 31 Mar 2025 22:38:54 +0200 Subject: [PATCH 05/13] custom groovy class loader --- examples/runConfig.json | 15 +- .../groovyscript/GroovyScript.java | 2 +- .../groovyscript/helper/GroovyHelper.java | 3 +- .../groovyscript/sandbox/CompiledClass.java | 13 +- .../groovyscript/sandbox/CompiledScript.java | 15 +- .../sandbox/CustomGroovyScriptEngine.java | 120 +++--- .../groovyscript/sandbox/GroovySandbox.java | 1 + .../sandbox/GroovyScriptClassLoader.java | 373 ++++++++++++++++++ .../sandbox/GroovyScriptSandbox.java | 67 +++- .../groovyscript/sandbox/RunConfig.java | 30 +- .../groovyscript/sandbox/ScriptCache.java | 55 +++ .../transformer/GroovyCodeFactory.java | 24 +- .../GroovyScriptCompilationUnitFactory.java | 13 +- 13 files changed, 611 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java create mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java diff --git a/examples/runConfig.json b/examples/runConfig.json index 6c7e947fd..488858b7c 100644 --- a/examples/runConfig.json +++ b/examples/runConfig.json @@ -1,18 +1,19 @@ { - "packName": "PlaceHolder name", - "packId": "placeholdername", + "packName": "Technological Journey CEu", + "packId": "tjceu", "version": "1.0.0", "debug": true, - "classes": [ - "classes/" - ], "loaders": { "preInit": [ + "classes/", "preInit/" ], - "init": [], + "init": [ + "init/" + ], "postInit": [ - "postInit/" + "postInit/", + "recipes/" ] }, "packmode": { diff --git a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java index 52d0470d5..2678ef162 100644 --- a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java @@ -275,7 +275,7 @@ private static RunConfig createRunConfig(JsonObject json) { if (!Files.exists(main.toPath())) { try { main.getParentFile().mkdirs(); - Files.write(main.toPath(), "\nprintln('Hello World!')\n".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + Files.write(main.toPath(), "\nlog.info('Hello World!')\n".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.WRITE); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/GroovyHelper.java b/src/main/java/com/cleanroommc/groovyscript/helper/GroovyHelper.java index 2c7533c76..b7e319bcd 100644 --- a/src/main/java/com/cleanroommc/groovyscript/helper/GroovyHelper.java +++ b/src/main/java/com/cleanroommc/groovyscript/helper/GroovyHelper.java @@ -24,7 +24,8 @@ public static LoadStage getLoadStage() { } public static boolean isReloading() { - return getLoadStage().isReloadable() && !ReloadableRegistryManager.isFirstLoad(); + LoadStage loadStage = getLoadStage(); + return loadStage != null && loadStage.isReloadable() && !ReloadableRegistryManager.isFirstLoad(); } public static String getMinecraftVersion() { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java index 47f7e47fc..b2e274e60 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java @@ -3,11 +3,13 @@ import com.cleanroommc.groovyscript.api.GroovyLog; import groovy.lang.GroovyClassLoader; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.codehaus.groovy.runtime.InvokerHelper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; +import java.util.Map; class CompiledClass { @@ -30,7 +32,8 @@ public void onCompile(byte[] data, Class clazz, String basePath) { public void onCompile(Class clazz, String basePath) { this.clazz = clazz; - this.name = clazz.getName(); + if (!this.name.equals(clazz.getName())) throw new IllegalArgumentException(); + //this.name = clazz.getName(); if (this.data == null) { GroovyLog.get().errorMC("The class doesnt seem to be compiled yet. (" + name + ")"); return; @@ -55,9 +58,10 @@ protected void ensureLoaded(CachedClassLoader classLoader, String basePath) { } } - protected void ensureLoaded(GroovyClassLoader classLoader, String basePath) { + protected void ensureLoaded(GroovyClassLoader classLoader, Map cache, String basePath) { if (this.clazz == null) { this.clazz = classLoader.defineClass(this.name, this.data); + cache.put(this.name, this); } } @@ -79,6 +83,11 @@ public void deleteCache(String cachePath) { } catch (IOException e) { throw new RuntimeException(e); } + if (this.clazz != null) { + InvokerHelper.removeClass(this.clazz); + this.clazz = null; + } + this.data = null; } protected File getDataFile(String basePath) { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java index d6bd3e7e5..1a0d910f6 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java @@ -12,9 +12,16 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; class CompiledScript extends CompiledClass { + public static String classNameFromPath(String path) { + int i = path.lastIndexOf('.'); + path = path.substring(0, i); + return path.replace('/', '.'); + } + final List innerClasses = new ArrayList<>(); long lastEdited; List preprocessors; @@ -22,7 +29,7 @@ class CompiledScript extends CompiledClass { boolean requiresReload; public CompiledScript(String path, long lastEdited) { - this(path, null, lastEdited); + this(path, classNameFromPath(path), lastEdited); } public CompiledScript(String path, String name, long lastEdited) { @@ -65,17 +72,17 @@ public void ensureLoaded(CachedClassLoader classLoader, String basePath) { super.ensureLoaded(classLoader, basePath); } - public void ensureLoaded(GroovyClassLoader classLoader, String basePath) { + public void ensureLoaded(GroovyClassLoader classLoader, Map cache, String basePath) { for (CompiledClass comp : this.innerClasses) { if (comp.clazz == null) { if (comp.readData(basePath)) { - comp.ensureLoaded(classLoader, basePath); + comp.ensureLoaded(classLoader, cache, basePath); } else { GroovyLog.get().error("Error loading inner class {} for class {}", comp.name, this.name); } } } - super.ensureLoaded(classLoader, basePath); + super.ensureLoaded(classLoader, cache, basePath); } public @NotNull JsonObject toJson() { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index 602609da4..62a081f67 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -8,7 +8,6 @@ import com.google.gson.JsonObject; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyCodeSource; -import groovy.lang.GroovyResourceLoader; import groovy.util.ResourceConnector; import groovy.util.ResourceException; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -18,10 +17,12 @@ import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.control.*; import org.codehaus.groovy.runtime.IOGroovyMethods; +import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.tools.gse.DependencyTracker; import org.codehaus.groovy.tools.gse.StringSetMap; import org.codehaus.groovy.vmplugin.VMPlugin; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -34,8 +35,7 @@ import java.net.URL; import java.net.URLConnection; import java.security.CodeSource; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class CustomGroovyScriptEngine implements ResourceConnector { @@ -71,14 +71,14 @@ private static synchronized ThreadLocal getLocalData() { private final CompilerConfiguration config; private final ScriptClassLoader classLoader; private final Map index = new Object2ObjectOpenHashMap<>(); - private final Map> loadedScripts = new Object2ObjectOpenHashMap<>(); + private final Map loadedClasses = new Object2ObjectOpenHashMap<>(); public CustomGroovyScriptEngine(URL[] scriptEnvironment, File cacheRoot, File scriptRoot, CompilerConfiguration config) { this.scriptEnvironment = scriptEnvironment; this.cacheRoot = cacheRoot; this.scriptRoot = scriptRoot; this.config = config; - this.classLoader = new ScriptClassLoader(CustomGroovyScriptEngine.class.getClassLoader(), config); + this.classLoader = new ScriptClassLoader(CustomGroovyScriptEngine.class.getClassLoader(), config, Collections.unmodifiableMap(this.loadedClasses)); readIndex(); } @@ -94,7 +94,7 @@ public CompilerConfiguration getConfig() { return config; } - public GroovyClassLoader getClassLoader() { + public GroovyScriptClassLoader getClassLoader() { return classLoader; } @@ -115,6 +115,10 @@ void readIndex() { CompiledScript cs = CompiledScript.fromJson(element.getAsJsonObject(), this.scriptRoot.getPath(), this.cacheRoot.getPath()); if (cs != null) { this.index.put(cs.path, cs); + this.loadedClasses.put(cs.name, cs); + for (CompiledClass cc : cs.innerClasses) { + this.loadedClasses.put(cc.name, cc); + } } } } @@ -136,7 +140,9 @@ void writeIndex() { @ApiStatus.Internal public boolean deleteScriptCache() { + this.index.values().forEach(script -> script.deleteCache(this.cacheRoot.getPath())); this.index.clear(); + this.loadedClasses.clear(); getClassLoader().clearCache(); try { FileUtils.cleanDirectory(this.cacheRoot); @@ -147,20 +153,33 @@ public boolean deleteScriptCache() { } } - CompiledScript loadScriptClass(File file) { - CompiledScript compiledScript = checkScriptLoadability(file); - if (compiledScript.requiresReload && !compiledScript.preprocessorCheckFailed) { - Class clazz = loadScriptClassInternal(new File(compiledScript.path), true); - if (compiledScript.clazz == null) { + List findScripts(Collection files) { + List scripts = new ArrayList<>(files.size()); + for (File file : files) { + CompiledScript cs = checkScriptLoadability(file); + if (!cs.preprocessorCheckFailed) scripts.add(cs); + } + return scripts; + } + + void loadScript(CompiledScript script) { + if (script.requiresReload && !script.preprocessorCheckFailed) { + Class clazz = loadScriptClassInternal(new File(script.path), true); + if (script.clazz == null) { // should not happen - GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", compiledScript.path); - if (ENABLE_CACHE) compiledScript.clazz = clazz; + GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", script.path); + if (ENABLE_CACHE) script.clazz = clazz; } } + } + + CompiledScript loadScriptClass(File file) { + CompiledScript compiledScript = checkScriptLoadability(file); + loadScript(compiledScript); return compiledScript; } - CompiledScript checkScriptLoadability(File file) { + @NotNull CompiledScript checkScriptLoadability(File file) { String relativeFileName = FileUtil.relativize(this.scriptRoot.getPath(), file.getPath()); File relativeFile = new File(relativeFileName); long lastModified = file.lastModified(); @@ -173,13 +192,17 @@ CompiledScript checkScriptLoadability(File file) { comp.preprocessorCheckFailed = true; return comp; } - comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + comp.ensureLoaded(getClassLoader(), this.loadedClasses, this.cacheRoot.getPath()); } else if (!ENABLE_CACHE || (comp == null || comp.clazz == null || lastModified > comp.lastEdited)) { // class is not loaded and class bytes don't exist yet or script has been edited if (comp == null) { comp = new CompiledScript(relativeFileName, 0); this.index.put(relativeFileName, comp); } + if (comp.clazz != null) { + InvokerHelper.removeClass(comp.clazz); + comp.clazz = null; + } comp.requiresReload = true; if (lastModified > comp.lastEdited || comp.preprocessors == null) { // recompile preprocessors if there is no data or script was edited @@ -189,8 +212,6 @@ CompiledScript checkScriptLoadability(File file) { if (!comp.checkPreprocessors(this.scriptRoot)) { // delete class bytes to make sure it's recompiled once the preprocessors returns true comp.deleteCache(this.cacheRoot.getPath()); - comp.clazz = null; - comp.data = null; comp.preprocessorCheckFailed = true; return comp; } @@ -201,7 +222,7 @@ CompiledScript checkScriptLoadability(File file) { comp.preprocessorCheckFailed = true; return comp; } - comp.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + comp.ensureLoaded(getClassLoader(), this.loadedClasses, this.cacheRoot.getPath()); } comp.preprocessorCheckFailed = false; return comp; @@ -282,7 +303,7 @@ public void onCompileClass(SourceUnit su, String path, Class clazz, byte[] co CompiledClass innerClass = comp; if (inner) innerClass = comp.findInnerClass(clazz.getName()); innerClass.onCompile(code, clazz, this.cacheRoot.getPath()); - this.loadedScripts.put(clazz.getName(), clazz); + this.loadedClasses.put(innerClass.name, innerClass); } /** @@ -297,7 +318,7 @@ public Class onRecompileClass(URL source, String className) { Class c = null; if (cs != null) { if (cs.clazz == null && cs.readData(this.cacheRoot.getPath())) { - cs.ensureLoaded(getClassLoader(), this.cacheRoot.getPath()); + cs.ensureLoaded(getClassLoader(), this.loadedClasses, this.cacheRoot.getPath()); } c = cs.clazz; } @@ -380,32 +401,26 @@ private static class LocalData { final Map precompiledEntries = new HashMap<>(); } - private class ScriptClassLoader extends GroovyClassLoader { - + private class ScriptClassLoader extends GroovyScriptClassLoader { - public ScriptClassLoader(GroovyClassLoader loader) { - super(loader); + public ScriptClassLoader(ClassLoader loader, CompilerConfiguration config, Map cache) { + super(loader, config, cache); } - public ScriptClassLoader(ClassLoader loader, CompilerConfiguration config) { - super(loader, config, false); - setResLoader(); - } - - private void setResLoader() { - final GroovyResourceLoader rl = getResourceLoader(); - setResourceLoader(className -> { - File file = CustomGroovyScriptEngine.this.findScriptFileOfClass(className); - if (file != null) { - return file.toURI().toURL(); - } - return rl.loadGroovySource(className); - }); + @Override + public URL loadResource(String name) throws MalformedURLException { + File file = CustomGroovyScriptEngine.this.findScriptFileOfClass(name); + if (file != null) { + return file.toURI().toURL(); + } + return null; } @Override - protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { - return new CustomClassCollector(new InnerLoader(this), unit, su, CustomGroovyScriptEngine.this); + protected ClassCollector createCustomCollector(CompilationUnit unit, SourceUnit su) { + return super.createCustomCollector(unit, su).creatClassCallback((code, clz) -> { + onCompileClass(su, su.getName(), clz, code, clz.getName().contains("$")); + }); } @Override @@ -464,14 +479,14 @@ public LookupResult findClassNode(String origName, CompilationUnit compilationUn } @Override - protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException { - if (source != null && oldClass == null) { + protected Class recompile(URL source, String className) throws CompilationFailedException, IOException { + if (source != null) { Class c = CustomGroovyScriptEngine.this.onRecompileClass(source, className); if (c != null) { return c; } } - return super.recompile(source, className, oldClass); + return super.recompile(source, className); } @Override @@ -524,23 +539,4 @@ private Class doParseClass(GroovyCodeSource codeSource) { return answer; } } - - public static class CustomClassCollector extends GroovyClassLoader.ClassCollector { - - private final SourceUnit su; - private final CustomGroovyScriptEngine engine; - - protected CustomClassCollector(GroovyClassLoader.InnerLoader cl, CompilationUnit unit, SourceUnit su, CustomGroovyScriptEngine engine) { - super(cl, unit, su); - this.su = su; - this.engine = engine; - } - - @Override - protected Class createClass(byte[] code, ClassNode classNode) { - Class clz = super.createClass(code, classNode); - this.engine.onCompileClass(this.su, this.su.getName(), clz, code, classNode.getName().contains("$")); - return clz; - } - } } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java index 886d224eb..0590ce365 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java @@ -150,6 +150,7 @@ protected void loadClassScripts(GroovyScriptEngine engine, Binding binding, Set< } // the superclass of class files is Object if (clazz.getSuperclass() != Script.class && shouldRunFile(classFile)) { + GroovyLog.get().info(" - loading {}", clazz.getName()); try { // $getLookup is present on all groovy created classes // call it cause the class to be initialised diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java new file mode 100644 index 000000000..0f8fb19c4 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java @@ -0,0 +1,373 @@ +package com.cleanroommc.groovyscript.sandbox; + +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyCodeSource; +import groovy.util.CharsetToolkit; +import groovyjarjarasm.asm.ClassVisitor; +import groovyjarjarasm.asm.ClassWriter; +import net.minecraft.launchwrapper.Launch; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.control.*; +import org.codehaus.groovy.util.URLStreams; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.function.BiConsumer; + +public abstract class GroovyScriptClassLoader extends GroovyClassLoader { + + private final CompilerConfiguration config; + private final String sourceEncoding; + + private final Map cache; + + GroovyScriptClassLoader(ClassLoader parent, CompilerConfiguration config, Map cache) { + super(parent, config, false); + this.config = config; + this.sourceEncoding = initSourceEncoding(config); + this.cache = cache; + setResourceLoader(this::loadResource); + setShouldRecompile(false); + } + + private String initSourceEncoding(CompilerConfiguration config) { + String sourceEncoding = config.getSourceEncoding(); + if (null == sourceEncoding) { + // Keep the same default source encoding with the one used by #parseClass(InputStream, String) + // TODO should we use org.codehaus.groovy.control.CompilerConfiguration.DEFAULT_SOURCE_ENCODING instead? + return CharsetToolkit.getDefaultSystemCharset().name(); + } + return sourceEncoding; + } + + public abstract @Nullable URL loadResource(String name) throws MalformedURLException; + + @Override + protected void setClassCacheEntry(Class cls) {} + + @Override + protected Class getClassCacheEntry(String name) { + CompiledClass cc = this.cache.get(name); + return cc != null ? cc.clazz : null; + } + + @Override + public Class defineClass(ClassNode classNode, String file, String newCodeBase) { + CodeSource codeSource = null; + try { + codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null); + } catch (MalformedURLException e) { + //swallow + } + + CompilationUnit unit = createCompilationUnit(config, codeSource); + ClassCollector collector = createCustomCollector(unit, classNode.getModule().getContext()); + try { + unit.addClassNode(classNode); + unit.setClassgenCallback(collector); + unit.compile(Phases.CLASS_GENERATION); + definePackageInternal(collector.generatedClass.getName()); + return collector.generatedClass; + } catch (CompilationFailedException e) { + throw new RuntimeException(e); + } + } + + /** + * Parses the given code source into a Java class. If there is a class file + * for the given code source, then no parsing is done, instead the cached class is returned. + * + * @param shouldCacheSource if true then the generated class will be stored in the source cache + * @return the main class defined in the given script + */ + @Override + public Class parseClass(final GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException { + return doParseClass(codeSource); + } + + private Class doParseClass(GroovyCodeSource codeSource) { + validate(codeSource); + Class answer; // Was neither already loaded nor compiling, so compile and add to cache. + CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource()); + /*if (recompile!=null && recompile || recompile==null && config.getRecompileGroovySource()) { + unit.addFirstPhaseOperation(GroovyClassLoader.TimestampAdder.INSTANCE, CompilePhase.CLASS_GENERATION.getPhaseNumber()); + }*/ + SourceUnit su = null; + File file = codeSource.getFile(); + if (file != null) { + su = unit.addSource(file); + } else { + URL url = codeSource.getURL(); + if (url != null) { + su = unit.addSource(url); + } else { + su = unit.addSource(codeSource.getName(), codeSource.getScriptText()); + } + } + + ClassCollector collector = createCustomCollector(unit, su); + unit.setClassgenCallback(collector); + int goalPhase = Phases.CLASS_GENERATION; + if (config != null && config.getTargetDirectory() != null) goalPhase = Phases.OUTPUT; + unit.compile(goalPhase); + + answer = collector.generatedClass; + String mainClass = su.getAST().getMainClassName(); + for (Class clazz : collector.getLoadedClasses()) { + String clazzName = clazz.getName(); + definePackageInternal(clazzName); + setClassCacheEntry(clazz); + if (clazzName.equals(mainClass)) answer = clazz; + } + return answer; + } + + /** + * loads a class from a file or a parent classloader. + * + * @param name of the class to be loaded + * @param lookupScriptFiles if false no lookup at files is done at all + * @param preferClassOverScript if true the file lookup is only done if there is no class + * @param resolve see {@link java.lang.ClassLoader#loadClass(java.lang.String, boolean)} + * @return the class found or the class created from a file lookup + * @throws ClassNotFoundException if the class could not be found + * @throws CompilationFailedException if the source file could not be compiled + */ + @Override + public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) + throws ClassNotFoundException, CompilationFailedException { + // look into cache + Class cls = getClassCacheEntry(name); + + // enable recompilation? + boolean recompile = isRecompilable(cls); + if (!recompile) return cls; + + // try parent loader + ClassNotFoundException last = null; + try { + Class parentClassLoaderClass = Launch.classLoader.findClass(name);//super.loadClass(name, resolve); + // always return if the parent loader was successful + if (parentClassLoaderClass != null) return parentClassLoaderClass; + } catch (ClassNotFoundException cnfe) { + last = cnfe; + } catch (NoClassDefFoundError ncdfe) { + if (ncdfe.getMessage().indexOf("wrong name") > 0) { + last = new ClassNotFoundException(name); + } else { + throw ncdfe; + } + } + + // at this point the loading from a parent loader failed, + // and we want to recompile if needed. + if (lookupScriptFiles) { + // try groovy file + try { + // check if recompilation already happened. + final Class classCacheEntry = null; + URL source = loadResource(name); + // if recompilation fails, we want cls==null + cls = recompile(source, name); + } catch (IOException ioe) { + last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe); + } finally { + if (cls == null) { + removeClassCacheEntry(name); + } else { + setClassCacheEntry(cls); + } + } + } + + if (cls == null) { + // no class found, there should have been an exception before now + if (last == null) throw new AssertionError(true); + throw last; + } + return cls; + } + + @Override + protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException { + throw new UnsupportedOperationException(); + } + + /** + * (Re)Compiles the given source. + * This method starts the compilation of a given source, if + * the source has changed since the class was created. For + * this isSourceNewer is called. + * + * @param source the source pointer for the compilation + * @param className the name of the class to be generated + * @return the old class if the source wasn't new enough, the new class else + * @throws CompilationFailedException if the compilation failed + * @throws IOException if the source is not readable + * @see #isSourceNewer(URL, Class) + */ + protected Class recompile(URL source, String className) throws CompilationFailedException, IOException { + if (source != null) { + String name = source.toExternalForm(); + if (isFile(source)) { + try { + return parseClass(new GroovyCodeSource(new File(source.toURI()), sourceEncoding)); + } catch (URISyntaxException e) { + // do nothing and fall back to the other version + } + } + return parseClass(new InputStreamReader(URLStreams.openUncachedStream(source), sourceEncoding), name); + } + return null; + } + + /** + * gets the time stamp of a given class. For groovy + * generated classes this usually means to return the value + * of the static field __timeStamp. If the parameter doesn't + * have such a field, then Long.MAX_VALUE is returned + * + * @param cls the class + * @return the time stamp + */ + @Override + protected long getTimeStamp(Class cls) { + return Long.MAX_VALUE; + } + + /** + * This method will take a file name and try to "decode" any URL encoded characters. For example + * if the file name contains any spaces this method call will take the resulting %20 encoded values + * and convert them to spaces. + *

+ * This method was added specifically to fix defect: Groovy-1787. The defect involved a situation + * where two scripts were sitting in a directory with spaces in its name. The code would fail + * when the class loader tried to resolve the file name and would choke on the URLEncoded space values. + */ + private static String decodeFileName(String fileName) { + String decodedFile = fileName; + try { + decodedFile = URLDecoder.decode(fileName, "UTF-8"); + } catch (UnsupportedEncodingException e) { + System.err.println("Encountered an invalid encoding scheme when trying to use URLDecoder.decode() inside of the GroovyClassLoader.decodeFileName() method. Returning the unencoded URL."); + System.err.println("Please note that if you encounter this error and you have spaces in your directory you will run into issues. Refer to GROOVY-1787 for description of this bug."); + } + + return decodedFile; + } + + private static boolean isFile(URL ret) { + return ret != null && ret.getProtocol().equals("file"); + } + + private static void validate(GroovyCodeSource codeSource) { + if (codeSource.getFile() == null && codeSource.getScriptText() == null) { + throw new IllegalArgumentException("Script text to compile cannot be null!"); + } + } + + @SuppressWarnings("deprecation") // TODO replace getPackage with getDefinedPackage once min JDK version >= 9 + private void definePackageInternal(String className) { + int i = className.lastIndexOf('.'); + if (i != -1) { + String pkgName = className.substring(0, i); + java.lang.Package pkg = getPackage(pkgName); + if (pkg == null) { + definePackage(pkgName, null, null, null, null, null, null, null); + } + } + } + + /** + * creates a ClassCollector for a new compilation. + * + * @param unit the compilationUnit + * @param su the SourceUnit + * @return the ClassCollector + */ + protected ClassCollector createCustomCollector(CompilationUnit unit, SourceUnit su) { + return new ClassCollector(this, unit, su); + } + + @Override + protected GroovyClassLoader.ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { + throw new UnsupportedOperationException(); + } + + public static class ClassCollector implements CompilationUnit.ClassgenCallback { + + private Class generatedClass; + private final GroovyScriptClassLoader cl; + private final SourceUnit su; + private final CompilationUnit unit; + private final Collection> loadedClasses; + private BiConsumer> creatClassCallback; + + protected ClassCollector(GroovyScriptClassLoader cl, CompilationUnit unit, SourceUnit su) { + this.cl = cl; + this.unit = unit; + this.loadedClasses = new ArrayList<>(); + this.su = su; + } + + public GroovyScriptClassLoader getDefiningClassLoader() { + return cl; + } + + protected Class createClass(byte[] code, ClassNode classNode) { + BytecodeProcessor bytecodePostprocessor = unit.getConfiguration().getBytecodePostprocessor(); + byte[] fcode = code; + if (bytecodePostprocessor != null) { + fcode = bytecodePostprocessor.processBytecode(classNode.getName(), fcode); + } + GroovyScriptClassLoader cl = getDefiningClassLoader(); + Class theClass = cl.defineClass(classNode.getName(), fcode, 0, fcode.length, unit.getAST().getCodeSource()); + this.loadedClasses.add(theClass); + + if (generatedClass == null) { + ModuleNode mn = classNode.getModule(); + SourceUnit msu = null; + if (mn != null) msu = mn.getContext(); + ClassNode main = null; + if (mn != null) main = mn.getClasses().get(0); + if (msu == su && main == classNode) generatedClass = theClass; + } + + if (this.creatClassCallback != null) { + this.creatClassCallback.accept(code, theClass); + } + return theClass; + } + + protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) { + byte[] code = classWriter.toByteArray(); + return createClass(code, classNode); + } + + @Override + public void call(ClassVisitor classWriter, ClassNode classNode) { + onClassNode((ClassWriter) classWriter, classNode); + } + + public Collection> getLoadedClasses() { + return this.loadedClasses; + } + + public ClassCollector creatClassCallback(BiConsumer> creatClassCallback) { + this.creatClassCallback = creatClassCallback; + return this; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 36e23fa33..3821f209c 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -133,15 +133,33 @@ public void run(LoadStage currentLoadStage) { } protected void runScript(Script script) { - GroovyLog.get().info(" - running {}", script.getClass().getName()); + GroovyLog.get().info(" - running script {}", script.getClass().getName()); setCurrentScript(script.getClass().getName()); - script.run(); - setCurrentScript(null); + try { + script.run(); + } finally { + setCurrentScript(null); + } + } + + protected void runClass(Class script) { + GroovyLog.get().info(" - loading class {}", script.getName()); + setCurrentScript(script.getName()); + try { + // $getLookup is present on all groovy created classes + // call it cause the class to be initialised + Method m = script.getMethod("$getLookup"); + m.invoke(null); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + GroovyLog.get().errorMC("Error initialising class '{}'", script); + } finally { + setCurrentScript(null); + } } public void checkSyntax() { Binding binding = createBindings(); - Set executedClasses = new ObjectOpenHashSet<>(); + Set executedClasses = new ObjectOpenHashSet<>(); for (LoadStage loadStage : LoadStage.getLoadStages()) { GroovyLog.get().info("Checking syntax in loader '{}'", this.currentLoadStage); @@ -195,7 +213,7 @@ private void load() throws Exception { preRun(); Binding binding = createBindings(); - Set executedClasses = new ObjectOpenHashSet<>(); + Set executedClasses = new ObjectOpenHashSet<>(); this.running.set(true); try { @@ -207,7 +225,7 @@ private void load() throws Exception { } } - protected void load(Binding binding, Set executedClasses, boolean run) { + protected void load(Binding binding, Set executedClasses, boolean run) { this.compileTime = 0L; this.runTime = 0L; // load and run any configured class files @@ -216,23 +234,29 @@ protected void load(Binding binding, Set executedClasses, boolean run) { loadScripts(binding, executedClasses, run); } - protected void loadScripts(Binding binding, Set executedClasses, boolean run) { - for (File scriptFile : getScriptFiles()) { - if (!executedClasses.contains(scriptFile)) { + protected void loadScripts(Binding binding, Set executedClasses, boolean run) { + for (CompiledScript compiledScript : this.engine.findScripts(getScriptFiles())) { + if (!executedClasses.contains(compiledScript.path)) { long t = System.currentTimeMillis(); - CompiledScript compiledScript = engine.loadScriptClass(scriptFile); + this.engine.loadScript(compiledScript); this.compileTime += System.currentTimeMillis() - t; if (compiledScript.preprocessorCheckFailed) continue; if (compiledScript.clazz == null) { - GroovyLog.get().errorMC("Error loading script for {}", scriptFile.getPath()); + GroovyLog.get().errorMC("Error loading script for {}", compiledScript.path); GroovyLog.get().errorMC("Did you forget to register your class file in your run config?"); continue; } if (compiledScript.clazz.getSuperclass() != Script.class) { - GroovyLog.get().errorMC("Class file '{}' should be defined in the runConfig in the classes property!", scriptFile); + //GroovyLog.get().errorMC("Class file '{}' should be defined in the runConfig in the classes property!", scriptFile); + if (run && shouldRunFile(compiledScript.path)) { + t = System.currentTimeMillis(); + runClass(compiledScript.clazz); + this.runTime += System.currentTimeMillis() - t; + } + executedClasses.add(compiledScript.path); continue; } - if (run && shouldRunFile(scriptFile)) { + if (run && shouldRunFile(compiledScript.path)) { Script script = InvokerHelper.createScript(compiledScript.clazz, binding); t = System.currentTimeMillis(); runScript(script); @@ -242,9 +266,9 @@ protected void loadScripts(Binding binding, Set executedClasses, boolean r } } - protected void loadClassScripts(Binding binding, Set executedClasses, boolean run) { + protected void loadClassScripts(Binding binding, Set executedClasses, boolean run) { for (File classFile : getClassFiles()) { - if (executedClasses.contains(classFile)) continue; + if (executedClasses.contains(classFile.getPath())) continue; long t = System.currentTimeMillis(); CompiledScript compiledScript = engine.loadScriptClass(classFile); this.compileTime += System.currentTimeMillis() - t; @@ -256,7 +280,7 @@ protected void loadClassScripts(Binding binding, Set executedClasses, bool } // the superclass of class files is Object if (compiledScript.clazz.getSuperclass() != Script.class) { - if (run && shouldRunFile(classFile)) { + if (run && shouldRunFile(classFile.getPath())) { try { // $getLookup is present on all groovy created classes // call it cause the class to be initialised @@ -268,7 +292,7 @@ protected void loadClassScripts(Binding binding, Set executedClasses, bool GroovyLog.get().errorMC("Error initialising class '{}'", compiledScript.clazz.getName()); } } - executedClasses.add(classFile); + executedClasses.add(classFile.getPath()); } } } @@ -296,7 +320,7 @@ protected void initEngine(CompilerConfiguration config) { @ApiStatus.OverrideOnly protected void preRun() { - if (CustomGroovyScriptEngine.DELETE_CACHE_ON_RUN) engine.deleteScriptCache(); + if (CustomGroovyScriptEngine.DELETE_CACHE_ON_RUN) this.engine.deleteScriptCache(); // first clear all added events GroovyEventManager.INSTANCE.reset(); if (this.currentLoadStage.isReloadable() && !ReloadableRegistryManager.isFirstLoad()) { @@ -306,12 +330,13 @@ protected void preRun() { MinecraftForge.EVENT_BUS.post(new GroovyReloadEvent()); } GroovyLog.get().infoMC("Running scripts in loader '{}'", this.currentLoadStage); + //this.engine.prepareEngine(this.currentLoadStage); // and finally invoke pre script run event MinecraftForge.EVENT_BUS.post(new ScriptRunEvent.Pre(this.currentLoadStage)); } @ApiStatus.OverrideOnly - protected boolean shouldRunFile(File file) { + protected boolean shouldRunFile(String file) { return true; } @@ -330,8 +355,10 @@ public File getScriptRoot() { return SandboxData.getScriptFile(); } + @Deprecated public Collection getClassFiles() { - return GroovyScript.getRunConfig().getClassFiles(getScriptRoot(), this.currentLoadStage.getName()); + return Collections.emptyList(); + //return GroovyScript.getRunConfig().getClassFiles(getScriptRoot(), this.currentLoadStage.getName()); } public Collection getScriptFiles() { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java index bc3d5173b..8aaa28b1a 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java @@ -31,14 +31,15 @@ public static JsonObject createDefaultJson() { json.addProperty("packId", "placeholdername"); json.addProperty("version", "1.0.0"); json.addProperty("debug", false); - JsonObject classes = new JsonObject(); - JsonArray preInit = new JsonArray(); - classes.add("preInit", preInit); - json.add("classes", classes); + //JsonObject classes = new JsonObject(); + //JsonArray preInit = new JsonArray(); + //classes.add("preInit", preInit); + //json.add("classes", classes); JsonObject loaders = new JsonObject(); json.add("loaders", loaders); - preInit = new JsonArray(); + JsonArray preInit = new JsonArray(); loaders.add("preInit", preInit); + preInit.add("classes/"); preInit.add("preInit/"); JsonArray postInit = new JsonArray(); loaders.add("postInit", postInit); @@ -65,7 +66,7 @@ public static JsonObject createDefaultJson() { private final String packName; private final String packId; private final String version; - private final Map> classes = new Object2ObjectOpenHashMap<>(); + //private final Map> classes = new Object2ObjectOpenHashMap<>(); private final Map> loaderPaths = new Object2ObjectOpenHashMap<>(); private final List packmodeList = new ArrayList<>(); private final Set packmodeSet = new ObjectOpenHashSet<>(); @@ -109,7 +110,7 @@ public void reload(JsonObject json, boolean init) { throw new RuntimeException(); } this.debug = JsonHelper.getBoolean(json, false, "debug"); - this.classes.clear(); + //this.classes.clear(); this.loaderPaths.clear(); this.packmodeList.clear(); this.packmodeSet.clear(); @@ -119,7 +120,8 @@ public void reload(JsonObject json, boolean init) { String regex = File.separatorChar == '\\' ? "/" : "\\\\"; String replacement = getSeparator(); if (json.has("classes")) { - JsonElement jsonClasses = json.get("classes"); + throw new IllegalStateException("GroovyScript classes definition in runConfig is deprecated!"); + /*JsonElement jsonClasses = json.get("classes"); if (jsonClasses.isJsonArray()) { List classes = this.classes.computeIfAbsent("all", key -> new ArrayList<>()); @@ -140,7 +142,7 @@ public void reload(JsonObject json, boolean init) { this.classes.remove(entry.getKey()); } } - } + }*/ } @@ -279,20 +281,20 @@ public int getPackmodeConfigState() { } public boolean isLoaderConfigured(String loader) { - List path = this.classes.get(loader); - if (path != null && !path.isEmpty()) return true; - path = this.loaderPaths.get(loader); + //List path = this.classes.get(loader); + //if (path != null && !path.isEmpty()) return true; + List path = this.loaderPaths.get(loader); return path != null && !path.isEmpty(); } - public Collection getClassFiles(File root, String loader) { + /*public Collection getClassFiles(File root, String loader) { List paths = this.classes.get("all"); paths = paths == null ? new ArrayList<>() : new ArrayList<>(paths); if (this.classes.containsKey(loader)) { paths.addAll(this.classes.get(loader)); } return SandboxData.getSortedFilesOf(root, paths); - } + }*/ public Collection getSortedFiles(File root, String loader) { List paths = loaderPaths.get(loader); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java new file mode 100644 index 000000000..03c0cce05 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java @@ -0,0 +1,55 @@ +package com.cleanroommc.groovyscript.sandbox; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.Map; + +class ScriptCache { + + private final Map loadedScripts = new Object2ObjectOpenHashMap<>(); + private final ScriptCache parent; + private final boolean reloadable; + + public ScriptCache(ScriptCache parent, boolean reloadable) { + this.parent = parent; + this.reloadable = reloadable; + } + + public void setScriptLoaded(CompiledClass script) { + this.loadedScripts.put(script.getName(), script); + } + + public void setClassLoaded(Class clz) { + CompiledClass script = this.loadedScripts.get(clz.getName()); + if (script != null) { + setScriptLoaded(script); + } + } + + public Class getClassForName(String className) { + CompiledClass cs = this.loadedScripts.get(className); + if (cs != null) { + return cs.getClass(); + } + if (this.parent != null) { + return this.parent.getClassForName(className); + } + return null; + } + + public void onReload() { + if (!this.reloadable) return; + this.loadedScripts.clear(); + if (this.parent != null) { + this.parent.onReload(); + } + } + + public boolean isReloadable() { + return reloadable; + } + + public ScriptCache getParent() { + return parent; + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyCodeFactory.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyCodeFactory.java index 995dff1d5..d6c07dbe5 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyCodeFactory.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyCodeFactory.java @@ -7,6 +7,7 @@ import com.cleanroommc.groovyscript.sandbox.security.GroovySecurityManager; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.reflection.*; @@ -88,17 +89,34 @@ private static CachedMethod makeMethod(CachedClass cachedClass, Method method, b return new CachedMethod(cachedClass, method); } + public static boolean inheritsMCClas(ClassNode classNode) { + do { + if (classNode.getName().startsWith(MC_CLASS)) { + return true; + } + ClassNode[] interfaces = classNode.getInterfaces(); + if (interfaces != null) { + for (ClassNode iface : interfaces) { + if (inheritsMCClas(iface)) { + return true; + } + } + } + classNode = classNode.getSuperClass(); + } while (classNode != null && classNode != ClassHelper.OBJECT_TYPE); + return false; + } + /** * This bad boy is responsible for remapping overriden methods. Called via Mixin */ public static void remapOverrides(ClassNode classNode) { if (FMLLaunchHandler.isDeobfuscatedEnvironment()) return; - ClassNode superClass = classNode.getSuperClass(); - if (superClass == null || !superClass.getName().startsWith(MC_CLASS)) return; + if (!inheritsMCClas(classNode)) return; List methodNodes = classNode.getMethods(); for (int i = 0, methodNodesSize = methodNodes.size(); i < methodNodesSize; i++) { MethodNode methodNode = methodNodes.get(i); - String obf = GroovyDeobfMapper.getObfuscatedMethodName(superClass, methodNode.getName(), methodNode.getParameters()); + String obf = GroovyDeobfMapper.getObfuscatedMethodName(classNode, methodNode.getName(), methodNode.getParameters()); if (obf != null) { classNode.addMethod(copyRemappedMethodNode(obf, methodNode)); } diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java index c21fd7232..1f2081c38 100644 --- a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java @@ -74,11 +74,11 @@ public GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context) }); // add all other classes too - getAllClasses() + /*getAllClasses() .filter(path -> !languageServerContext.getFileContentsTracker().isOpen(path.toUri())) .forEach(path -> { addOpenFileToCompilationUnit(path.toUri(), languageServerContext.getFileContentsTracker().getContents(path.toUri()), unit); - }); + });*/ if (context != null) { var contents = languageServerContext.getFileContentsTracker().getContents(context); @@ -91,19 +91,20 @@ public GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context) } protected boolean isInClassesContext(URI uri) { - var file = Paths.get(uri).getParent(); + return false; + //var file = Paths.get(uri).getParent(); - return getAllClasses().anyMatch(file::startsWith); + //return getAllClasses().anyMatch(file::startsWith); } - protected Stream getAllClasses() { + /*protected Stream getAllClasses() { return LoadStage.getLoadStages() .stream() .map(LoadStage::getName) .flatMap(loader -> GroovyScript.getRunConfig().getClassFiles(this.root, loader).stream()) .map(File::toPath) .map(path -> GroovyScript.getScriptFile().toPath().resolve(path)); - } + }*/ protected void removeSources(GroovyLSCompilationUnit unit, Set urisToRemove) { List sourcesToRemove = new ArrayList<>(); From 45335e86c6665f9465a1888f33eb218934362702 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Mon, 31 Mar 2025 22:54:36 +0200 Subject: [PATCH 06/13] remove unused clases --- .../sandbox/CustomGroovyScriptEngine.java | 4 +- .../groovyscript/sandbox/GroovySandbox.java | 297 ------------------ .../sandbox/GroovyScriptClassLoader.java | 3 +- .../groovyscript/sandbox/RunConfig.java | 2 +- .../groovyscript/sandbox/ScriptCache.java | 55 ---- .../GroovyScriptCompilationUnitFactory.java | 3 - 6 files changed, 4 insertions(+), 360 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java delete mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index 62a081f67..7c2e46dbc 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -6,7 +6,6 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyCodeSource; import groovy.util.ResourceConnector; import groovy.util.ResourceException; @@ -179,7 +178,8 @@ CompiledScript loadScriptClass(File file) { return compiledScript; } - @NotNull CompiledScript checkScriptLoadability(File file) { + @NotNull + CompiledScript checkScriptLoadability(File file) { String relativeFileName = FileUtil.relativize(this.scriptRoot.getPath(), file.getPath()); File relativeFile = new File(relativeFileName); long lastModified = file.lastModified(); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java deleted file mode 100644 index 0590ce365..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java +++ /dev/null @@ -1,297 +0,0 @@ -package com.cleanroommc.groovyscript.sandbox; - -import com.cleanroommc.groovyscript.GroovyScript; -import com.cleanroommc.groovyscript.api.GroovyLog; -import com.cleanroommc.groovyscript.api.INamed; -import com.cleanroommc.groovyscript.helper.Alias; -import groovy.lang.Binding; -import groovy.lang.Closure; -import groovy.lang.Script; -import groovy.util.GroovyScriptEngine; -import groovy.util.ResourceException; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import org.codehaus.groovy.control.CompilerConfiguration; -import org.codehaus.groovy.control.customizers.ImportCustomizer; -import org.codehaus.groovy.runtime.InvokerHelper; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -/** - * @author brachy84 - */ -public abstract class GroovySandbox { - - private String currentScript; - - private final URL[] scriptEnvironment; - private final ThreadLocal running = ThreadLocal.withInitial(() -> false); - private final Map bindings = new Object2ObjectOpenHashMap<>(); - private final ImportCustomizer importCustomizer = new ImportCustomizer(); - private final CachedClassLoader ccl = new CachedClassLoader(); - - protected GroovySandbox(URL[] scriptEnvironment) { - if (scriptEnvironment == null || scriptEnvironment.length == 0) { - throw new NullPointerException("Script Environment must be non null and at least contain one URL!"); - } - this.scriptEnvironment = scriptEnvironment; - } - - protected GroovySandbox(List scriptEnvironment) { - this(scriptEnvironment.toArray(new URL[0])); - } - - public void registerBinding(String name, Object obj) { - Objects.requireNonNull(name); - Objects.requireNonNull(obj); - for (String alias : Alias.generateOf(name)) { - bindings.put(alias, obj); - } - } - - public void registerBinding(INamed named) { - Objects.requireNonNull(named); - for (String alias : named.getAliases()) { - bindings.put(alias, named); - } - } - - protected void startRunning() { - this.running.set(true); - } - - protected void stopRunning() { - this.running.set(false); - } - - protected GroovyScriptEngine createScriptEngine() { - GroovyScriptEngine engine = new GroovyScriptEngine(this.scriptEnvironment, this.ccl); - CompilerConfiguration config = new CompilerConfiguration(CompilerConfiguration.DEFAULT); - config.setSourceEncoding("UTF-8"); - config.addCompilationCustomizers(this.importCustomizer); - engine.setConfig(config); - initEngine(engine, config); - return engine; - } - - protected Binding createBindings() { - Binding binding = new Binding(this.bindings); - postInitBindings(binding); - return binding; - } - - public void load() throws Exception { - preRun(); - - GroovyScriptEngine engine = createScriptEngine(); - Binding binding = createBindings(); - Set executedClasses = new ObjectOpenHashSet<>(); - - this.running.set(true); - try { - load(engine, binding, executedClasses, true); - } finally { - this.running.set(false); - postRun(); - setCurrentScript(null); - } - } - - protected void load(GroovyScriptEngine engine, Binding binding, Set executedClasses, boolean run) { - // load and run any configured class files - loadClassScripts(engine, binding, executedClasses, run); - // now run all script files - loadScripts(engine, binding, executedClasses, run); - } - - protected void loadScripts(GroovyScriptEngine engine, Binding binding, Set executedClasses, boolean run) { - for (File scriptFile : getScriptFiles()) { - if (!executedClasses.contains(scriptFile)) { - Class clazz = loadScriptClass(engine, scriptFile); - if (clazz == GroovyLog.class) continue; // preprocessor returned false - if (clazz == null) { - GroovyLog.get().errorMC("Error loading script for {}", scriptFile.getPath()); - GroovyLog.get().errorMC("Did you forget to register your class file in your run config?"); - continue; - } - if (clazz.getSuperclass() != Script.class) { - GroovyLog.get().errorMC("Class file '{}' should be defined in the runConfig in the classes property!", scriptFile); - continue; - } - if (shouldRunFile(scriptFile)) { - Script script = InvokerHelper.createScript(clazz, binding); - if (run) runScript(script); - } - } - } - } - - protected void loadClassScripts(GroovyScriptEngine engine, Binding binding, Set executedClasses, boolean run) { - for (File classFile : getClassFiles()) { - if (executedClasses.contains(classFile)) continue; - Class clazz = loadScriptClass(engine, classFile); - if (clazz == GroovyLog.class) continue; // preprocessor returned false - if (clazz == null) { - // loading script fails if the file is a script that depends on a class file that isn't loaded yet - // we cant determine if the file is a script or a class - continue; - } - // the superclass of class files is Object - if (clazz.getSuperclass() != Script.class && shouldRunFile(classFile)) { - GroovyLog.get().info(" - loading {}", clazz.getName()); - try { - // $getLookup is present on all groovy created classes - // call it cause the class to be initialised - Method m = clazz.getMethod("$getLookup"); - m.invoke(null); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - GroovyLog.get().errorMC("Error initialising class '{}'", clazz.getName()); - } - executedClasses.add(classFile); - } - } - } - - protected void runScript(Script script) { - setCurrentScript(script.getClass().getName()); - script.run(); - setCurrentScript(null); - } - - public T runClosure(Closure closure, Object... args) { - boolean wasRunning = isRunning(); - if (!wasRunning) startRunning(); - T result = null; - try { - result = closure.call(args); - } catch (Exception e) { - GroovyScript.LOGGER.error("Caught an exception trying to run a closure:"); - e.printStackTrace(); - } finally { - if (!wasRunning) stopRunning(); - } - return result; - } - - @ApiStatus.OverrideOnly - protected void postInitBindings(Binding binding) {} - - @ApiStatus.OverrideOnly - protected void initEngine(GroovyScriptEngine engine, CompilerConfiguration config) {} - - @ApiStatus.OverrideOnly - protected void preRun() {} - - @ApiStatus.OverrideOnly - protected boolean shouldRunFile(File file) { - return true; - } - - @ApiStatus.OverrideOnly - protected void postRun() {} - - public abstract Collection getClassFiles(); - - public abstract Collection getScriptFiles(); - - public boolean isRunning() { - return this.running.get(); - } - - public Map getBindings() { - return bindings; - } - - public ImportCustomizer getImportCustomizer() { - return importCustomizer; - } - - public String getCurrentScript() { - return currentScript; - } - - protected void setCurrentScript(String currentScript) { - this.currentScript = currentScript; - } - - public CachedClassLoader getClassLoader() { - return ccl; - } - - public static String getRelativePath(String source) { - try { - Path path = Paths.get(new URL(source).toURI()); - Path mainPath = new File(GroovyScript.getScriptPath()).toPath(); - return mainPath.relativize(path).toString(); - } catch (URISyntaxException | MalformedURLException e) { - GroovyScript.LOGGER.error("Error parsing script source '{}'", source); - // don't log to GroovyLog here since it will cause a StackOverflow - return source; - } - } - - protected Class loadScriptClass(GroovyScriptEngine engine, File file) { - Class scriptClass = null; - try { - try { - // this will only work for files that existed when the game launches - scriptClass = engine.loadScriptByName(file.toString()); - // extra safety - if (scriptClass == null) { - scriptClass = tryLoadDynamicFile(engine, file); - } - } catch (ResourceException e) { - // file was added later, causing a ResourceException - // try to manually load the file - scriptClass = tryLoadDynamicFile(engine, file); - } - - // if the file is still not found something went wrong - } catch (Exception e) { - GroovyLog.get().exception("An error occurred while trying to load script class " + file.toString(), e); - } - return scriptClass; - } - - private @Nullable Class tryLoadDynamicFile(GroovyScriptEngine engine, File file) throws ResourceException { - Path path = null; - for (URL root : this.scriptEnvironment) { - try { - File rootFile = new File(root.toURI()); - // try to combine the root with the file ending - path = new File(rootFile, file.toString()).toPath(); - if (Files.exists(path)) { - // found a valid file - break; - } - } catch (URISyntaxException e) { - e.printStackTrace(); - } - } - - if (path == null) return null; - - GroovyLog.get().debugMC("Found path '{}' for dynamic file {}", path, file.toString()); - - Class clazz = null; - try { - // manually load the file as a groovy script - clazz = engine.getGroovyClassLoader().parseClass(path.toFile()); - } catch (IOException e) { - e.printStackTrace(); - } - return clazz; - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java index 0f8fb19c4..ca78fa2f3 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java @@ -146,8 +146,7 @@ private Class doParseClass(GroovyCodeSource codeSource) { * @throws CompilationFailedException if the source file could not be compiled */ @Override - public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) - throws ClassNotFoundException, CompilationFailedException { + public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException { // look into cache Class cls = getClassCacheEntry(name); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java index 8aaa28b1a..40413c740 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java @@ -122,7 +122,7 @@ public void reload(JsonObject json, boolean init) { if (json.has("classes")) { throw new IllegalStateException("GroovyScript classes definition in runConfig is deprecated!"); /*JsonElement jsonClasses = json.get("classes"); - + if (jsonClasses.isJsonArray()) { List classes = this.classes.computeIfAbsent("all", key -> new ArrayList<>()); for (JsonElement element : jsonClasses.getAsJsonArray()) { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java deleted file mode 100644 index 03c0cce05..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/ScriptCache.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.cleanroommc.groovyscript.sandbox; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; - -import java.util.Map; - -class ScriptCache { - - private final Map loadedScripts = new Object2ObjectOpenHashMap<>(); - private final ScriptCache parent; - private final boolean reloadable; - - public ScriptCache(ScriptCache parent, boolean reloadable) { - this.parent = parent; - this.reloadable = reloadable; - } - - public void setScriptLoaded(CompiledClass script) { - this.loadedScripts.put(script.getName(), script); - } - - public void setClassLoaded(Class clz) { - CompiledClass script = this.loadedScripts.get(clz.getName()); - if (script != null) { - setScriptLoaded(script); - } - } - - public Class getClassForName(String className) { - CompiledClass cs = this.loadedScripts.get(className); - if (cs != null) { - return cs.getClass(); - } - if (this.parent != null) { - return this.parent.getClassForName(className); - } - return null; - } - - public void onReload() { - if (!this.reloadable) return; - this.loadedScripts.clear(); - if (this.parent != null) { - this.parent.onReload(); - } - } - - public boolean isReloadable() { - return reloadable; - } - - public ScriptCache getParent() { - return parent; - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java index 1f2081c38..2f356194c 100644 --- a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java @@ -1,7 +1,5 @@ package com.cleanroommc.groovyscript.server; -import com.cleanroommc.groovyscript.GroovyScript; -import com.cleanroommc.groovyscript.sandbox.LoadStage; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptEarlyCompiler; import groovy.lang.GroovyClassLoader; @@ -17,7 +15,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import java.util.stream.Stream; public class GroovyScriptCompilationUnitFactory extends CompilationUnitFactoryBase { From aa2f4f2c67160c297b23eef24d5b9f91ce19a64a Mon Sep 17 00:00:00 2001 From: brachy84 Date: Tue, 1 Apr 2025 10:06:25 +0200 Subject: [PATCH 07/13] add back inner cl --- .../sandbox/CustomGroovyScriptEngine.java | 2 + .../sandbox/GroovyScriptClassLoader.java | 193 +++++++++++++++++- 2 files changed, 190 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index 7c2e46dbc..ac82276d7 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -164,6 +164,7 @@ List findScripts(Collection files) { void loadScript(CompiledScript script) { if (script.requiresReload && !script.preprocessorCheckFailed) { Class clazz = loadScriptClassInternal(new File(script.path), true); + script.requiresReload = false; if (script.clazz == null) { // should not happen GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", script.path); @@ -405,6 +406,7 @@ private class ScriptClassLoader extends GroovyScriptClassLoader { public ScriptClassLoader(ClassLoader loader, CompilerConfiguration config, Map cache) { super(loader, config, cache); + init(); } @Override diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java index ca78fa2f3..6db764403 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java @@ -2,6 +2,7 @@ import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyCodeSource; +import groovy.lang.GroovyResourceLoader; import groovy.util.CharsetToolkit; import groovyjarjarasm.asm.ClassVisitor; import groovyjarjarasm.asm.ClassWriter; @@ -12,10 +13,7 @@ import org.codehaus.groovy.util.URLStreams; import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; @@ -23,6 +21,7 @@ import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; +import java.util.Enumeration; import java.util.Map; import java.util.function.BiConsumer; @@ -33,11 +32,18 @@ public abstract class GroovyScriptClassLoader extends GroovyClassLoader { private final Map cache; + private GroovyScriptClassLoader(GroovyScriptClassLoader parent) { + this(parent, parent.config, parent.cache); + } + GroovyScriptClassLoader(ClassLoader parent, CompilerConfiguration config, Map cache) { super(parent, config, false); this.config = config; this.sourceEncoding = initSourceEncoding(config); this.cache = cache; + } + + protected void init() { setResourceLoader(this::loadResource); setShouldRecompile(false); } @@ -297,7 +303,7 @@ private void definePackageInternal(String className) { * @return the ClassCollector */ protected ClassCollector createCustomCollector(CompilationUnit unit, SourceUnit su) { - return new ClassCollector(this, unit, su); + return new ClassCollector(new InnerLoader(this), unit, su); } @Override @@ -369,4 +375,181 @@ public ClassCollector creatClassCallback(BiConsumer> creatClass return this; } } + + public static class InnerLoader extends GroovyScriptClassLoader { + + private final GroovyScriptClassLoader delegate; + + public InnerLoader(GroovyScriptClassLoader delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public @Nullable URL loadResource(String name) throws MalformedURLException { + return delegate.loadResource(name); + } + + @Override + public void addClasspath(String path) { + delegate.addClasspath(path); + } + + @Override + public void clearCache() { + delegate.clearCache(); + } + + @Override + public URL findResource(String name) { + return delegate.findResource(name); + } + + @Override + public Enumeration findResources(String name) throws IOException { + return delegate.findResources(name); + } + + @Override + public Class[] getLoadedClasses() { + return delegate.getLoadedClasses(); + } + + @Override + public URL getResource(String name) { + return delegate.getResource(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + return delegate.getResourceAsStream(name); + } + + @Override + public GroovyResourceLoader getResourceLoader() { + return delegate.getResourceLoader(); + } + + @Override + public URL[] getURLs() { + return delegate.getURLs(); + } + + @Override + public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException { + Class c = findLoadedClass(name); + if (c != null) return c; + return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve); + } + + @Override + public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException { + return delegate.parseClass(codeSource, shouldCache); + } + + @Override + public void setResourceLoader(GroovyResourceLoader resourceLoader) { + // no need to set a rl + // it's delegated anyway + } + + @Override + public void addURL(URL url) { + delegate.addURL(url); + } + + @Override + public Class defineClass(ClassNode classNode, String file, String newCodeBase) { + return delegate.defineClass(classNode, file, newCodeBase); + } + + @Override + public Class parseClass(File file) throws CompilationFailedException, IOException { + return delegate.parseClass(file); + } + + @Override + public Class parseClass(String text, String fileName) throws CompilationFailedException { + return delegate.parseClass(text, fileName); + } + + @Override + public Class parseClass(String text) throws CompilationFailedException { + return delegate.parseClass(text); + } + + @Override + public String generateScriptName() { + return delegate.generateScriptName(); + } + + @Override + public Class parseClass(Reader reader, String fileName) throws CompilationFailedException { + return delegate.parseClass(reader, fileName); + } + + @Override + public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException { + return delegate.parseClass(codeSource); + } + + @Override + public Class defineClass(String name, byte[] b) { + return delegate.defineClass(name, b); + } + + @Override + public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript) throws ClassNotFoundException, CompilationFailedException { + return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript); + } + + @Override + public void setShouldRecompile(Boolean mode) { + // it's delegated anyway + } + + @Override + public Boolean isShouldRecompile() { + return delegate.isShouldRecompile(); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return delegate.loadClass(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return delegate.getResources(name); + } + + @Override + public void setDefaultAssertionStatus(boolean enabled) { + delegate.setDefaultAssertionStatus(enabled); + } + + @Override + public void setPackageAssertionStatus(String packageName, boolean enabled) { + delegate.setPackageAssertionStatus(packageName, enabled); + } + + @Override + public void setClassAssertionStatus(String className, boolean enabled) { + delegate.setClassAssertionStatus(className, enabled); + } + + @Override + public void clearAssertionStatus() { + delegate.clearAssertionStatus(); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + delegate.close(); + } + } + } } From 139fded452740a1a09d4c4d395ae1fce73c05c2f Mon Sep 17 00:00:00 2001 From: brachy84 Date: Tue, 1 Apr 2025 12:43:27 +0200 Subject: [PATCH 08/13] more cleanup --- examples/runConfig.json | 4 +- .../sandbox/CachedClassLoader.java | 50 ------------------- .../groovyscript/sandbox/CompiledClass.java | 7 --- .../groovyscript/sandbox/CompiledScript.java | 14 ------ .../sandbox/GroovyScriptClassLoader.java | 25 +--------- .../sandbox/GroovyScriptSandbox.java | 44 +--------------- .../groovyscript/sandbox/RunConfig.java | 37 +------------- 7 files changed, 7 insertions(+), 174 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/sandbox/CachedClassLoader.java diff --git a/examples/runConfig.json b/examples/runConfig.json index 488858b7c..c807aeeaa 100644 --- a/examples/runConfig.json +++ b/examples/runConfig.json @@ -1,6 +1,6 @@ { - "packName": "Technological Journey CEu", - "packId": "tjceu", + "packName": "GroovyScript Dev", + "packId": "groovyscriptdev", "version": "1.0.0", "debug": true, "loaders": { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CachedClassLoader.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CachedClassLoader.java deleted file mode 100644 index 89f759427..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CachedClassLoader.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.cleanroommc.groovyscript.sandbox; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.launchwrapper.Launch; - -import java.util.Map; - -public class CachedClassLoader extends ClassLoader { - - private final Map> cache = new Object2ObjectOpenHashMap<>(); - - public CachedClassLoader() { - super(Launch.classLoader); - } - - public Class defineClass(String name, byte[] bytes) { - Class clz = super.defineClass(name, bytes, 0, bytes.length); - resolveClass(clz); - this.cache.put(clz.getName(), clz); - return clz; - } - - public void clearCache() { - this.cache.clear(); - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - Class clz = tryLoadClass(name); - if (clz != null) return clz; - return super.loadClass(name, resolve); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - Class clz = tryLoadClass(name); - if (clz != null) return clz; - return super.findClass(name); - } - - public Class tryLoadClass(String name) { - Class clz = this.cache.get(name); - if (clz != null) return clz; - try { - return Launch.classLoader.findClass(name); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java index b2e274e60..c65240e41 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledClass.java @@ -51,13 +51,6 @@ public void onCompile(Class clazz, String basePath) { } } - @Deprecated - protected void ensureLoaded(CachedClassLoader classLoader, String basePath) { - if (this.clazz == null) { - this.clazz = classLoader.defineClass(this.name, this.data); - } - } - protected void ensureLoaded(GroovyClassLoader classLoader, Map cache, String basePath) { if (this.clazz == null) { this.clazz = classLoader.defineClass(this.name, this.data); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java index 1a0d910f6..db37af7b2 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java @@ -58,20 +58,6 @@ public CompiledClass findInnerClass(String clazz) { return comp; } - @Deprecated - public void ensureLoaded(CachedClassLoader classLoader, String basePath) { - for (CompiledClass comp : this.innerClasses) { - if (comp.clazz == null) { - if (comp.readData(basePath)) { - comp.ensureLoaded(classLoader, basePath); - } else { - GroovyLog.get().error("Error loading inner class {} for class {}", comp.name, this.name); - } - } - } - super.ensureLoaded(classLoader, basePath); - } - public void ensureLoaded(GroovyClassLoader classLoader, Map cache, String basePath) { for (CompiledClass comp : this.innerClasses) { if (comp.clazz == null) { diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java index 6db764403..fce4c9fb8 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptClassLoader.java @@ -17,7 +17,6 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLDecoder; import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; @@ -182,7 +181,8 @@ public Class loadClass(final String name, boolean lookupScriptFiles, boolean // try groovy file try { // check if recompilation already happened. - final Class classCacheEntry = null; + final Class classCacheEntry = getClassCacheEntry(name); + if (classCacheEntry != cls) return classCacheEntry; URL source = loadResource(name); // if recompilation fails, we want cls==null cls = recompile(source, name); @@ -252,27 +252,6 @@ protected long getTimeStamp(Class cls) { return Long.MAX_VALUE; } - /** - * This method will take a file name and try to "decode" any URL encoded characters. For example - * if the file name contains any spaces this method call will take the resulting %20 encoded values - * and convert them to spaces. - *

- * This method was added specifically to fix defect: Groovy-1787. The defect involved a situation - * where two scripts were sitting in a directory with spaces in its name. The code would fail - * when the class loader tried to resolve the file name and would choke on the URLEncoded space values. - */ - private static String decodeFileName(String fileName) { - String decodedFile = fileName; - try { - decodedFile = URLDecoder.decode(fileName, "UTF-8"); - } catch (UnsupportedEncodingException e) { - System.err.println("Encountered an invalid encoding scheme when trying to use URLDecoder.decode() inside of the GroovyClassLoader.decodeFileName() method. Returning the unencoded URL."); - System.err.println("Please note that if you encounter this error and you have spaces in your directory you will run into issues. Refer to GROOVY-1787 for description of this bug."); - } - - return decodedFile; - } - private static boolean isFile(URL ret) { return ret != null && ret.getProtocol().equals("file"); } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 3821f209c..ab3ef8f7e 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -228,8 +228,6 @@ private void load() throws Exception { protected void load(Binding binding, Set executedClasses, boolean run) { this.compileTime = 0L; this.runTime = 0L; - // load and run any configured class files - loadClassScripts(binding, executedClasses, run); // now run all script files loadScripts(binding, executedClasses, run); } @@ -242,12 +240,11 @@ protected void loadScripts(Binding binding, Set executedClasses, boolean this.compileTime += System.currentTimeMillis() - t; if (compiledScript.preprocessorCheckFailed) continue; if (compiledScript.clazz == null) { - GroovyLog.get().errorMC("Error loading script for {}", compiledScript.path); - GroovyLog.get().errorMC("Did you forget to register your class file in your run config?"); + GroovyLog.get().errorMC("Error loading script {}", compiledScript.path); continue; } if (compiledScript.clazz.getSuperclass() != Script.class) { - //GroovyLog.get().errorMC("Class file '{}' should be defined in the runConfig in the classes property!", scriptFile); + // script is a class if (run && shouldRunFile(compiledScript.path)) { t = System.currentTimeMillis(); runClass(compiledScript.clazz); @@ -266,37 +263,6 @@ protected void loadScripts(Binding binding, Set executedClasses, boolean } } - protected void loadClassScripts(Binding binding, Set executedClasses, boolean run) { - for (File classFile : getClassFiles()) { - if (executedClasses.contains(classFile.getPath())) continue; - long t = System.currentTimeMillis(); - CompiledScript compiledScript = engine.loadScriptClass(classFile); - this.compileTime += System.currentTimeMillis() - t; - if (compiledScript.preprocessorCheckFailed) continue; - if (compiledScript.clazz == null) { - // loading script fails if the file is a script that depends on a class file that isn't loaded yet - // we cant determine if the file is a script or a class - continue; - } - // the superclass of class files is Object - if (compiledScript.clazz.getSuperclass() != Script.class) { - if (run && shouldRunFile(classFile.getPath())) { - try { - // $getLookup is present on all groovy created classes - // call it cause the class to be initialised - Method m = compiledScript.clazz.getMethod("$getLookup"); - t = System.currentTimeMillis(); - m.invoke(null); - this.runTime += System.currentTimeMillis() - t; - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - GroovyLog.get().errorMC("Error initialising class '{}'", compiledScript.clazz.getName()); - } - } - executedClasses.add(classFile.getPath()); - } - } - } - protected void startRunning() { this.running.set(true); } @@ -355,12 +321,6 @@ public File getScriptRoot() { return SandboxData.getScriptFile(); } - @Deprecated - public Collection getClassFiles() { - return Collections.emptyList(); - //return GroovyScript.getRunConfig().getClassFiles(getScriptRoot(), this.currentLoadStage.getName()); - } - public Collection getScriptFiles() { return GroovyScript.getRunConfig().getSortedFiles(getScriptRoot(), this.currentLoadStage.getName()); } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java index 40413c740..fe3eb6329 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java @@ -66,7 +66,6 @@ public static JsonObject createDefaultJson() { private final String packName; private final String packId; private final String version; - //private final Map> classes = new Object2ObjectOpenHashMap<>(); private final Map> loaderPaths = new Object2ObjectOpenHashMap<>(); private final List packmodeList = new ArrayList<>(); private final Set packmodeSet = new ObjectOpenHashSet<>(); @@ -120,32 +119,9 @@ public void reload(JsonObject json, boolean init) { String regex = File.separatorChar == '\\' ? "/" : "\\\\"; String replacement = getSeparator(); if (json.has("classes")) { - throw new IllegalStateException("GroovyScript classes definition in runConfig is deprecated!"); - /*JsonElement jsonClasses = json.get("classes"); - - if (jsonClasses.isJsonArray()) { - List classes = this.classes.computeIfAbsent("all", key -> new ArrayList<>()); - for (JsonElement element : jsonClasses.getAsJsonArray()) { - classes.add(sanitizePath(element.getAsString().replaceAll(regex, replacement))); - } - } else if (jsonClasses.isJsonObject()) { - for (Map.Entry entry : jsonClasses.getAsJsonObject().entrySet()) { - List classes = this.classes.computeIfAbsent(entry.getKey(), key -> new ArrayList<>()); - if (entry.getValue().isJsonPrimitive()) { - classes.add(sanitizePath(entry.getValue().getAsString().replaceAll(regex, replacement))); - } else if (entry.getValue().isJsonArray()) { - for (JsonElement element : entry.getValue().getAsJsonArray()) { - classes.add(sanitizePath(element.getAsString().replaceAll(regex, replacement))); - } - } - if (classes.isEmpty()) { - this.classes.remove(entry.getKey()); - } - } - }*/ + throw new IllegalStateException("GroovyScript classes definition in runConfig is deprecated! Classes are now treated as normal scripts."); } - JsonObject jsonLoaders = JsonHelper.getJsonObject(json, "loaders"); List> pathsList = new ArrayList<>(); @@ -281,21 +257,10 @@ public int getPackmodeConfigState() { } public boolean isLoaderConfigured(String loader) { - //List path = this.classes.get(loader); - //if (path != null && !path.isEmpty()) return true; List path = this.loaderPaths.get(loader); return path != null && !path.isEmpty(); } - /*public Collection getClassFiles(File root, String loader) { - List paths = this.classes.get("all"); - paths = paths == null ? new ArrayList<>() : new ArrayList<>(paths); - if (this.classes.containsKey(loader)) { - paths.addAll(this.classes.get(loader)); - } - return SandboxData.getSortedFilesOf(root, paths); - }*/ - public Collection getSortedFiles(File root, String loader) { List paths = loaderPaths.get(loader); if (paths == null || paths.isEmpty()) return Collections.emptyList(); From 9899735540585f8b1f433b6e2f29bf85d69b549d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 2 Apr 2025 13:35:16 +0200 Subject: [PATCH 09/13] move assets --- .../blockstates/amongium.json | 0 .../blockstates/dragon_egg.json | 2 +- .../blockstates/dragon_egg_lamp.json | 7 + .../blockstates/generic_block.json | 7 + .../lang/en_us.lang | 0 .../models/block/dragon_egg_lamp.json | 2 +- .../models/block/generic_block.json | 2 +- .../models/item/clay_2.json | 2 +- .../models/item/clay_3.json | 2 +- .../models/item/dragon_egg_lamp.json | 3 + .../models/item/generic_block.json | 3 + .../models/item/heartofauniverse.json | 6 + .../models/item/prodigy_stick.json | 2 +- .../models/item/snack.json | 2 +- .../textures/blocks/dragon_egg_lamp.png | Bin .../textures/blocks/generic_block.png | Bin .../blocks/mekanism_infusion_texture.png | Bin .../textures/items/clay_2.png | Bin .../textures/items/clay_3.png | Bin .../textures/items/heartofauniverse.png | Bin .../items/heartofauniverse.png.mcmeta | 0 .../textures/items/prodigy_stick.png | Bin .../textures/items/snack.png | Bin .../blockstates/dragon_egg_lamp.json | 7 - .../blockstates/generic_block.json | 7 - .../models/item/dragon_egg_lamp.json | 3 - .../models/item/generic_block.json | 3 - .../models/item/heartofauniverse.json | 6 - examples/postInit/arcaneworld.groovy | 89 --- examples/postInit/betterwithaddons.groovy | 260 ------- examples/postInit/bloodarsenal.groovy | 50 -- examples/postInit/custom/vanilla.groovy | 2 + examples/postInit/inspirations.groovy | 92 --- examples/postInit/integrateddynamics.groovy | 76 -- examples/postInit/pneumaticcraft.groovy | 213 ------ examples/postInit/quarryplus.groovy | 19 - examples/postInit/thaumcraft.groovy | 205 ------ examples/postInit/theaurorian.groovy | 33 - examples/postInit/thebetweenlands.groovy | 209 ------ examples/postInit/thermalexpansion.groovy | 652 ------------------ .../groovyscript/sandbox/CompiledScript.java | 29 +- .../providers/CompletionProvider.java | 29 +- 42 files changed, 80 insertions(+), 1944 deletions(-) rename examples/assets/{placeholdername => groovyscriptdev}/blockstates/amongium.json (100%) rename examples/assets/{placeholdername => groovyscriptdev}/blockstates/dragon_egg.json (50%) create mode 100644 examples/assets/groovyscriptdev/blockstates/dragon_egg_lamp.json create mode 100644 examples/assets/groovyscriptdev/blockstates/generic_block.json rename examples/assets/{placeholdername => groovyscriptdev}/lang/en_us.lang (100%) rename examples/assets/{placeholdername => groovyscriptdev}/models/block/dragon_egg_lamp.json (51%) rename examples/assets/{placeholdername => groovyscriptdev}/models/block/generic_block.json (51%) rename examples/assets/{placeholdername => groovyscriptdev}/models/item/clay_2.json (54%) rename examples/assets/{placeholdername => groovyscriptdev}/models/item/clay_3.json (54%) create mode 100644 examples/assets/groovyscriptdev/models/item/dragon_egg_lamp.json create mode 100644 examples/assets/groovyscriptdev/models/item/generic_block.json create mode 100644 examples/assets/groovyscriptdev/models/item/heartofauniverse.json rename examples/assets/{placeholdername => groovyscriptdev}/models/item/prodigy_stick.json (50%) rename examples/assets/{placeholdername => groovyscriptdev}/models/item/snack.json (54%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/blocks/dragon_egg_lamp.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/blocks/generic_block.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/blocks/mekanism_infusion_texture.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/clay_2.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/clay_3.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/heartofauniverse.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/heartofauniverse.png.mcmeta (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/prodigy_stick.png (100%) rename examples/assets/{placeholdername => groovyscriptdev}/textures/items/snack.png (100%) delete mode 100644 examples/assets/placeholdername/blockstates/dragon_egg_lamp.json delete mode 100644 examples/assets/placeholdername/blockstates/generic_block.json delete mode 100644 examples/assets/placeholdername/models/item/dragon_egg_lamp.json delete mode 100644 examples/assets/placeholdername/models/item/generic_block.json delete mode 100644 examples/assets/placeholdername/models/item/heartofauniverse.json diff --git a/examples/assets/placeholdername/blockstates/amongium.json b/examples/assets/groovyscriptdev/blockstates/amongium.json similarity index 100% rename from examples/assets/placeholdername/blockstates/amongium.json rename to examples/assets/groovyscriptdev/blockstates/amongium.json diff --git a/examples/assets/placeholdername/blockstates/dragon_egg.json b/examples/assets/groovyscriptdev/blockstates/dragon_egg.json similarity index 50% rename from examples/assets/placeholdername/blockstates/dragon_egg.json rename to examples/assets/groovyscriptdev/blockstates/dragon_egg.json index e10df24ce..d6c7fcb74 100644 --- a/examples/assets/placeholdername/blockstates/dragon_egg.json +++ b/examples/assets/groovyscriptdev/blockstates/dragon_egg.json @@ -1,7 +1,7 @@ { "variants": { "normal": { - "model": "placeholdername:dragon_egg" + "model": "groovyscriptdev:dragon_egg" } } } \ No newline at end of file diff --git a/examples/assets/groovyscriptdev/blockstates/dragon_egg_lamp.json b/examples/assets/groovyscriptdev/blockstates/dragon_egg_lamp.json new file mode 100644 index 000000000..e61545f1d --- /dev/null +++ b/examples/assets/groovyscriptdev/blockstates/dragon_egg_lamp.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "groovyscriptdev:dragon_egg_lamp" + } + } +} \ No newline at end of file diff --git a/examples/assets/groovyscriptdev/blockstates/generic_block.json b/examples/assets/groovyscriptdev/blockstates/generic_block.json new file mode 100644 index 000000000..4b7e82e31 --- /dev/null +++ b/examples/assets/groovyscriptdev/blockstates/generic_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "normal": { + "model": "groovyscriptdev:generic_block" + } + } +} \ No newline at end of file diff --git a/examples/assets/placeholdername/lang/en_us.lang b/examples/assets/groovyscriptdev/lang/en_us.lang similarity index 100% rename from examples/assets/placeholdername/lang/en_us.lang rename to examples/assets/groovyscriptdev/lang/en_us.lang diff --git a/examples/assets/placeholdername/models/block/dragon_egg_lamp.json b/examples/assets/groovyscriptdev/models/block/dragon_egg_lamp.json similarity index 51% rename from examples/assets/placeholdername/models/block/dragon_egg_lamp.json rename to examples/assets/groovyscriptdev/models/block/dragon_egg_lamp.json index 24dd3f896..f4e7bb6d4 100644 --- a/examples/assets/placeholdername/models/block/dragon_egg_lamp.json +++ b/examples/assets/groovyscriptdev/models/block/dragon_egg_lamp.json @@ -1,6 +1,6 @@ { "parent": "block/dragon_egg", "textures": { - "all": "placeholdername:blocks/dragon_egg_lamp" + "all": "groovyscriptdev:blocks/dragon_egg_lamp" } } \ No newline at end of file diff --git a/examples/assets/placeholdername/models/block/generic_block.json b/examples/assets/groovyscriptdev/models/block/generic_block.json similarity index 51% rename from examples/assets/placeholdername/models/block/generic_block.json rename to examples/assets/groovyscriptdev/models/block/generic_block.json index 3c4e7b328..e8046b380 100644 --- a/examples/assets/placeholdername/models/block/generic_block.json +++ b/examples/assets/groovyscriptdev/models/block/generic_block.json @@ -1,6 +1,6 @@ { "parent": "block/cube_all", "textures": { - "all": "placeholdername:blocks/generic_block" + "all": "groovyscriptdev:blocks/generic_block" } } \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/clay_2.json b/examples/assets/groovyscriptdev/models/item/clay_2.json similarity index 54% rename from examples/assets/placeholdername/models/item/clay_2.json rename to examples/assets/groovyscriptdev/models/item/clay_2.json index 73272f6ed..7cc85e2e5 100644 --- a/examples/assets/placeholdername/models/item/clay_2.json +++ b/examples/assets/groovyscriptdev/models/item/clay_2.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "placeholdername:items/clay_2" + "layer0": "groovyscriptdev:items/clay_2" } } \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/clay_3.json b/examples/assets/groovyscriptdev/models/item/clay_3.json similarity index 54% rename from examples/assets/placeholdername/models/item/clay_3.json rename to examples/assets/groovyscriptdev/models/item/clay_3.json index 96d36b2b8..0a1783c6b 100644 --- a/examples/assets/placeholdername/models/item/clay_3.json +++ b/examples/assets/groovyscriptdev/models/item/clay_3.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "placeholdername:items/clay_3" + "layer0": "groovyscriptdev:items/clay_3" } } \ No newline at end of file diff --git a/examples/assets/groovyscriptdev/models/item/dragon_egg_lamp.json b/examples/assets/groovyscriptdev/models/item/dragon_egg_lamp.json new file mode 100644 index 000000000..e1d2b2a39 --- /dev/null +++ b/examples/assets/groovyscriptdev/models/item/dragon_egg_lamp.json @@ -0,0 +1,3 @@ +{ + "parent": "groovyscriptdev:block/dragon_egg_lamp" +} \ No newline at end of file diff --git a/examples/assets/groovyscriptdev/models/item/generic_block.json b/examples/assets/groovyscriptdev/models/item/generic_block.json new file mode 100644 index 000000000..2bc77231b --- /dev/null +++ b/examples/assets/groovyscriptdev/models/item/generic_block.json @@ -0,0 +1,3 @@ +{ + "parent": "groovyscriptdev:block/generic_block" +} \ No newline at end of file diff --git a/examples/assets/groovyscriptdev/models/item/heartofauniverse.json b/examples/assets/groovyscriptdev/models/item/heartofauniverse.json new file mode 100644 index 000000000..ec7469b28 --- /dev/null +++ b/examples/assets/groovyscriptdev/models/item/heartofauniverse.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "groovyscriptdev:items/heartofauniverse" + } +} \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/prodigy_stick.json b/examples/assets/groovyscriptdev/models/item/prodigy_stick.json similarity index 50% rename from examples/assets/placeholdername/models/item/prodigy_stick.json rename to examples/assets/groovyscriptdev/models/item/prodigy_stick.json index 10bfc9abb..6b7b44b27 100644 --- a/examples/assets/placeholdername/models/item/prodigy_stick.json +++ b/examples/assets/groovyscriptdev/models/item/prodigy_stick.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "placeholdername:items/prodigy_stick" + "layer0": "groovyscriptdev:items/prodigy_stick" } } \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/snack.json b/examples/assets/groovyscriptdev/models/item/snack.json similarity index 54% rename from examples/assets/placeholdername/models/item/snack.json rename to examples/assets/groovyscriptdev/models/item/snack.json index 758109e6b..24feccbaf 100644 --- a/examples/assets/placeholdername/models/item/snack.json +++ b/examples/assets/groovyscriptdev/models/item/snack.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "placeholdername:items/snack" + "layer0": "groovyscriptdev:items/snack" } } \ No newline at end of file diff --git a/examples/assets/placeholdername/textures/blocks/dragon_egg_lamp.png b/examples/assets/groovyscriptdev/textures/blocks/dragon_egg_lamp.png similarity index 100% rename from examples/assets/placeholdername/textures/blocks/dragon_egg_lamp.png rename to examples/assets/groovyscriptdev/textures/blocks/dragon_egg_lamp.png diff --git a/examples/assets/placeholdername/textures/blocks/generic_block.png b/examples/assets/groovyscriptdev/textures/blocks/generic_block.png similarity index 100% rename from examples/assets/placeholdername/textures/blocks/generic_block.png rename to examples/assets/groovyscriptdev/textures/blocks/generic_block.png diff --git a/examples/assets/placeholdername/textures/blocks/mekanism_infusion_texture.png b/examples/assets/groovyscriptdev/textures/blocks/mekanism_infusion_texture.png similarity index 100% rename from examples/assets/placeholdername/textures/blocks/mekanism_infusion_texture.png rename to examples/assets/groovyscriptdev/textures/blocks/mekanism_infusion_texture.png diff --git a/examples/assets/placeholdername/textures/items/clay_2.png b/examples/assets/groovyscriptdev/textures/items/clay_2.png similarity index 100% rename from examples/assets/placeholdername/textures/items/clay_2.png rename to examples/assets/groovyscriptdev/textures/items/clay_2.png diff --git a/examples/assets/placeholdername/textures/items/clay_3.png b/examples/assets/groovyscriptdev/textures/items/clay_3.png similarity index 100% rename from examples/assets/placeholdername/textures/items/clay_3.png rename to examples/assets/groovyscriptdev/textures/items/clay_3.png diff --git a/examples/assets/placeholdername/textures/items/heartofauniverse.png b/examples/assets/groovyscriptdev/textures/items/heartofauniverse.png similarity index 100% rename from examples/assets/placeholdername/textures/items/heartofauniverse.png rename to examples/assets/groovyscriptdev/textures/items/heartofauniverse.png diff --git a/examples/assets/placeholdername/textures/items/heartofauniverse.png.mcmeta b/examples/assets/groovyscriptdev/textures/items/heartofauniverse.png.mcmeta similarity index 100% rename from examples/assets/placeholdername/textures/items/heartofauniverse.png.mcmeta rename to examples/assets/groovyscriptdev/textures/items/heartofauniverse.png.mcmeta diff --git a/examples/assets/placeholdername/textures/items/prodigy_stick.png b/examples/assets/groovyscriptdev/textures/items/prodigy_stick.png similarity index 100% rename from examples/assets/placeholdername/textures/items/prodigy_stick.png rename to examples/assets/groovyscriptdev/textures/items/prodigy_stick.png diff --git a/examples/assets/placeholdername/textures/items/snack.png b/examples/assets/groovyscriptdev/textures/items/snack.png similarity index 100% rename from examples/assets/placeholdername/textures/items/snack.png rename to examples/assets/groovyscriptdev/textures/items/snack.png diff --git a/examples/assets/placeholdername/blockstates/dragon_egg_lamp.json b/examples/assets/placeholdername/blockstates/dragon_egg_lamp.json deleted file mode 100644 index d575e5727..000000000 --- a/examples/assets/placeholdername/blockstates/dragon_egg_lamp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "variants": { - "normal": { - "model": "placeholdername:dragon_egg_lamp" - } - } -} \ No newline at end of file diff --git a/examples/assets/placeholdername/blockstates/generic_block.json b/examples/assets/placeholdername/blockstates/generic_block.json deleted file mode 100644 index 7b1e585c7..000000000 --- a/examples/assets/placeholdername/blockstates/generic_block.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "variants": { - "normal": { - "model": "placeholdername:generic_block" - } - } -} \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/dragon_egg_lamp.json b/examples/assets/placeholdername/models/item/dragon_egg_lamp.json deleted file mode 100644 index 1fb046c4e..000000000 --- a/examples/assets/placeholdername/models/item/dragon_egg_lamp.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "parent": "placeholdername:block/dragon_egg_lamp" -} \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/generic_block.json b/examples/assets/placeholdername/models/item/generic_block.json deleted file mode 100644 index 8b218e06f..000000000 --- a/examples/assets/placeholdername/models/item/generic_block.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "parent": "placeholdername:block/generic_block" -} \ No newline at end of file diff --git a/examples/assets/placeholdername/models/item/heartofauniverse.json b/examples/assets/placeholdername/models/item/heartofauniverse.json deleted file mode 100644 index 767d717d1..000000000 --- a/examples/assets/placeholdername/models/item/heartofauniverse.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "item/generated", - "textures": { - "layer0": "placeholdername:items/heartofauniverse" - } -} \ No newline at end of file diff --git a/examples/postInit/arcaneworld.groovy b/examples/postInit/arcaneworld.groovy index fc901398b..e69de29bb 100644 --- a/examples/postInit/arcaneworld.groovy +++ b/examples/postInit/arcaneworld.groovy @@ -1,89 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: arcaneworld - -log.info 'mod \'arcaneworld\' detected, running script' - -// Ritual: -// Converts up to 5 input itemstacks into a wide number of possible effects, including spawning entities, opening a portal -// to a dungeon dimension to fight a mob, awarding an output itemstack, running commands, and even entirely customized -// effects. - -mods.arcaneworld.ritual.removeByInput(item('minecraft:gold_nugget')) -mods.arcaneworld.ritual.removeByOutput(item('arcaneworld:biome_crystal')) -// mods.arcaneworld.ritual.removeAll() - -mods.arcaneworld.ritual.recipeBuilder() - .ritualCreateItem() - .input(item('minecraft:stone') * 5, item('minecraft:diamond'), item('minecraft:clay')) - .output(item('minecraft:clay')) - .translationKey('groovyscript.demo_output') - .name('groovyscript:custom_name') - .register() - -mods.arcaneworld.ritual.recipeBuilderArena() - .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:clay')) - .translationKey('groovyscript.demo_arena') - .entity(entity('minecraft:chicken')) - .register() - -mods.arcaneworld.ritual.recipeBuilderCommand() - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:clay')) - .translationKey('groovyscript.demo_command') - .command('say hi', - 'give @p minecraft:coal 5') - .register() - -mods.arcaneworld.ritual.recipeBuilderCreateItem() - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond')) - .translationKey('groovyscript.demo_create_item') - .output(item('minecraft:diamond')) - .register() - -mods.arcaneworld.ritual.recipeBuilderCustom() - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay')) - .translationKey('groovyscript.demo_custom') - .onActivate({ World world, BlockPos blockPos, EntityPlayer player, ItemStack... itemStacks -> { log.info blockPos } }) - .register() - -mods.arcaneworld.ritual.recipeBuilderDragonBreath() - .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) - .translationKey('groovyscript.demo_dragon_breath') - .register() - -mods.arcaneworld.ritual.recipeBuilderDungeon() - .input(item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay')) - .translationKey('groovyscript.demo_dungeon') - .register() - -mods.arcaneworld.ritual.recipeBuilderSummon() - .input(item('minecraft:stone'), item('minecraft:clay'), item('minecraft:clay')) - .translationKey('groovyscript.demo_summon') - .entity(entity('minecraft:chicken')) - .register() - -mods.arcaneworld.ritual.recipeBuilderTime() - .input(item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) - .translationKey('groovyscript.demo_time') - .time(5000) - .register() - -mods.arcaneworld.ritual.recipeBuilderWeather() - .input(item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:clay')) - .translationKey('groovyscript.demo_weather_clear') - .weatherClear() - .register() - -mods.arcaneworld.ritual.recipeBuilderWeather() - .input(item('minecraft:gold_ingot'), item('minecraft:diamond'), item('minecraft:clay')) - .translationKey('groovyscript.demo_weather_rain') - .weatherRain() - .register() - -mods.arcaneworld.ritual.recipeBuilderWeather() - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:gold_ingot')) - .translationKey('groovyscript.demo_weather_thunder') - .weatherThunder() - .register() - - diff --git a/examples/postInit/betterwithaddons.groovy b/examples/postInit/betterwithaddons.groovy index 09363b5b0..e69de29bb 100644 --- a/examples/postInit/betterwithaddons.groovy +++ b/examples/postInit/betterwithaddons.groovy @@ -1,260 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: betterwithaddons - -log.info 'mod \'betterwithaddons\' detected, running script' - -// Drying Unit: -// Converts an input item into an output itemstack if placed within the appropriate multiblock. The multiblock is Sandstone -// directly below the Drying Box, 8 Sand around the Drying Box, and a Dead Bush placed on the Sand. Only functions in a -// non-snowy biome with sky access during the day, and functions twice as fast when in a hot biome. - -mods.betterwithaddons.drying_box.removeByInput(item('betterwithaddons:japanmat:2')) -mods.betterwithaddons.drying_box.removeByOutput(item('minecraft:sponge')) -// mods.betterwithaddons.drying_box.removeAll() - -mods.betterwithaddons.drying_box.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.drying_box.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4) - .register() - - -// Fire Net: -// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock -// is Lava or Fire directly below the Netted Screen, 8 Stone Brick around the Lava or Fire, and 8 Slat Blocks placed around -// the Netted Screen. - -mods.betterwithaddons.fire_net.removeByInput(item('betterwithaddons:iron_sand')) -mods.betterwithaddons.fire_net.removeByOutput(item('betterwithaddons:japanmat:12')) -// mods.betterwithaddons.fire_net.removeAll() - -mods.betterwithaddons.fire_net.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.fire_net.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) - .register() - - -// Ancestral Infusion Crafting: -// Converts a custom crafting recipe an output itemstack, consuming Spirits from the Infused Soul Sand placed below the -// Ancestral Infuser if placed within the appropriate multiblock. The multiblock is either Soul Sand or Infused Soul Sand -// placed below the Ancestral Infuser and exclusively air blocks adjacent to the Infuser and Soul Sand blocks. - -mods.betterwithaddons.infuser.removeByInput(item('betterwithaddons:japanmat:16')) -mods.betterwithaddons.infuser.removeByOutput(item('betterwithaddons:ya')) -// mods.betterwithaddons.infuser.removeAll() - -mods.betterwithaddons.infuser.shapedBuilder() - .output(item('minecraft:stone')) - .matrix('BXX', - 'X B') - .key('B', item('minecraft:stone')) - .key('X', item('minecraft:gold_ingot')) - .spirits(1) - .mirrored() - .register() - -mods.betterwithaddons.infuser.shapedBuilder() - .output(item('minecraft:diamond') * 32) - .matrix([[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], - [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], - [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')]]) - .spirits(6) - .register() - -mods.betterwithaddons.infuser.shapelessBuilder() - .output(item('minecraft:clay') * 8) - .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')) - .register() - -mods.betterwithaddons.infuser.shapelessBuilder() - .output(item('minecraft:clay') * 32) - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond')) - .spirits(8) - .register() - - -// Alicio Tree Foods: -// Converts an input item into an amount of food for the tree to gradually consume, eventually summoning a random creature -// nearby. - -mods.betterwithaddons.lure_tree.removeByInput(item('minecraft:rotten_flesh')) -// mods.betterwithaddons.lure_tree.removeAll() - -mods.betterwithaddons.lure_tree.recipeBuilder() - .input(item('minecraft:diamond')) - .food(1000) - .register() - -mods.betterwithaddons.lure_tree.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .food(4) - .register() - - -mods.betterwithaddons.lure_tree.addBlacklist(entity('minecraft:chicken')) - -// Rotting Food: -// Converts an input item into an output itemstack after the given time has passed. Has the ability to customize the -// terminology used to indicate the age. - -mods.betterwithaddons.rotting.removeByInput(item('betterwithaddons:food_cooked_rice')) -mods.betterwithaddons.rotting.removeByOutput(item('minecraft:rotten_flesh')) -// mods.betterwithaddons.rotting.removeAll() - -mods.betterwithaddons.rotting.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .register() - -mods.betterwithaddons.rotting.recipeBuilder() - .input(item('placeholdername:snack')) - .time(100) - .key('groovy_example') - .rotted(item('minecraft:clay') * 4) - .register() - - -// Sand Net: -// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock -// is a Slat Block directly below the Netted Screen, 8 Water Blocks around the Water, and 8 Slat Blocks placed around the -// Netted Screen. - -mods.betterwithaddons.sand_net.removeByInput(item('minecraft:iron_ingot')) -mods.betterwithaddons.sand_net.removeByOutput(item('minecraft:sand')) -mods.betterwithaddons.sand_net.removeByOutput(item('betterwithaddons:iron_sand')) -// mods.betterwithaddons.sand_net.removeAll() - -mods.betterwithaddons.sand_net.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.sand_net.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:gold_ingot')) - .sand(2) - .register() - -mods.betterwithaddons.sand_net.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) - .sand(5) - .register() - - -// Soaking Unit: -// Converts an input item into an output itemstack if placed within the appropriate multiblock. The multiblock is Ice -// directly above the Soaking Box, 8 Water around the Soaking Box, and Water directly below the Soaking Box. - -mods.betterwithaddons.soaking_box.removeByInput(item('betterwithaddons:bamboo')) -mods.betterwithaddons.soaking_box.removeByOutput(item('betterwithaddons:japanmat:8')) -// mods.betterwithaddons.soaking_box.removeAll() - -mods.betterwithaddons.soaking_box.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.soaking_box.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4) - .register() - - -// Spindle: -// Converts an input itemstack into an output itemstack, with the ability to consume the Spindle, when placed against a -// Spinning Wheel powered by Mechanical Power. - -mods.betterwithaddons.spindle.removeByInput(item('minecraft:vine')) -mods.betterwithaddons.spindle.removeByOutput(item('betterwithaddons:bolt')) -// mods.betterwithaddons.spindle.removeAll() - -mods.betterwithaddons.spindle.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.spindle.recipeBuilder() - .input(item('minecraft:clay') * 3) - .output(item('minecraft:diamond')) - .popoff() - .register() - -mods.betterwithaddons.spindle.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4) - .register() - - -// Tatara: -// Converts an input item into an output itemstack if placed within the appropriate multiblock while fueled by Rice Ashes. -// The multiblock is Lava or Fire directly below the Tatara, 8 Clay around the Lava or Fire, 9 Nether Brick above the -// Tatara, 4 Stone Brick diagonal to the Tatara and two Iron Blocks across from each other adjacent to the Tatara. - -mods.betterwithaddons.tatara.removeByInput(item('betterwithaddons:japanmat:20')) -mods.betterwithaddons.tatara.removeByOutput(item('betterwithaddons:kera')) -// mods.betterwithaddons.tatara.removeAll() - -mods.betterwithaddons.tatara.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.tatara.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4) - .register() - - -// Ancestral Infusion Transmutation: -// Converts an input item into an output itemstack, consuming Spirits from the Infused Soul Sand placed below the Ancestral -// Infuser if placed within the appropriate multiblock. The multiblock is either Soul Sand or Infused Soul Sand placed -// below the Ancestral Infuser and exclusively air blocks adjacent to the Infuser and Soul Sand blocks. - -mods.betterwithaddons.transmutation.removeByInput(item('minecraft:reeds')) -mods.betterwithaddons.transmutation.removeByOutput(item('betterwithaddons:crop_rice')) -// mods.betterwithaddons.transmutation.removeAll() - -mods.betterwithaddons.transmutation.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .spirits(0) - .register() - -mods.betterwithaddons.transmutation.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4) - .spirits(5) - .register() - - -// Water Net: -// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock -// is a Water Block directly below the Netted Screen, 8 Sakura Planks around the Water Block, and 8 Slat Blocks placed -// around the Netted Screen. - -mods.betterwithaddons.water_net.removeByInput(item('betterwithaddons:iron_sand')) -mods.betterwithaddons.water_net.removeByOutput(item('betterwithaddons:food_sashimi')) -mods.betterwithaddons.water_net.removeByOutput(item('betterwithaddons:food_fugu_sac')) -// mods.betterwithaddons.water_net.removeAll() - -mods.betterwithaddons.water_net.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .register() - -mods.betterwithaddons.water_net.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) - .register() - - diff --git a/examples/postInit/bloodarsenal.groovy b/examples/postInit/bloodarsenal.groovy index fd974a264..e69de29bb 100644 --- a/examples/postInit/bloodarsenal.groovy +++ b/examples/postInit/bloodarsenal.groovy @@ -1,50 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: bloodarsenal - -log.info 'mod \'bloodarsenal\' detected, running script' - -// Sanguine Infusion: -// Converts an input infusion itemstack and up to 8 input surrounding itemstacks into an output itemstack, consuming Life -// Essence from the network to do so when the Infusion de Sanguine Ritual is activated. Alternatively, instead of consuming -// an infusion item, adds or upgrades a modifier to the given stasis tool, with the ability to increase the quantity of -// inputs consumed based on level. - -// mods.bloodarsenal.sanguine_infusion.removeBlacklist(WayofTime.bloodmagic.iface.ISigil.class) -mods.bloodarsenal.sanguine_infusion.removeByInput(item('minecraft:feather')) -mods.bloodarsenal.sanguine_infusion.removeByInput(item('bloodmagic:bound_axe')) -// mods.bloodarsenal.sanguine_infusion.removeByModifierKey('beneficial_potion') -mods.bloodarsenal.sanguine_infusion.removeByOutput(item('bloodarsenal:stasis_pickaxe')) -// mods.bloodarsenal.sanguine_infusion.removeAll() -// mods.bloodarsenal.sanguine_infusion.removeAllBlacklist() - -mods.bloodarsenal.sanguine_infusion.recipeBuilder() - .infuse(item('minecraft:gold_ingot')) - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .cost(1000) - .register() - -mods.bloodarsenal.sanguine_infusion.recipeBuilder() - .infuse(item('minecraft:emerald')) - .input(item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64) - .output(item('minecraft:diamond') * 64) - .cost(5000) - .register() - -mods.bloodarsenal.sanguine_infusion.recipeBuilder() - .infuse(item('minecraft:gold_ingot')) - .input(item('minecraft:clay'), item('minecraft:diamond')) - .output(item('minecraft:diamond')) - .register() - -mods.bloodarsenal.sanguine_infusion.recipeBuilder() - .input(item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2) - .modifier('xperienced') - .levelMultiplier(3) - .cost(3000) - .register() - - -// mods.bloodarsenal.sanguine_infusion.addBlacklist(WayofTime.bloodmagic.iface.ISigil.class) - diff --git a/examples/postInit/custom/vanilla.groovy b/examples/postInit/custom/vanilla.groovy index 4c3405ebc..d53ca64ad 100644 --- a/examples/postInit/custom/vanilla.groovy +++ b/examples/postInit/custom/vanilla.groovy @@ -4,6 +4,8 @@ import net.minecraftforge.event.entity.living.EnderTeleportEvent import net.minecraftforge.event.world.BlockEvent import net.minecraft.util.text.TextComponentString +import classes.SimpleConversionRecipe +import postInit.custom.normal_mode /* def ore_iron = ore('ingotIron') diff --git a/examples/postInit/inspirations.groovy b/examples/postInit/inspirations.groovy index 3d7641d68..e69de29bb 100644 --- a/examples/postInit/inspirations.groovy +++ b/examples/postInit/inspirations.groovy @@ -1,92 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: inspirations - -log.info 'mod \'inspirations\' detected, running script' - -// Anvil Smashing: -// Converts a Block or IBlockState into an IBlockState when an anvil falls on top of it (from any height). - -mods.inspirations.anvil_smashing.removeByInput(blockstate('minecraft:packed_ice')) -mods.inspirations.anvil_smashing.removeByOutput(blockstate('minecraft:cobblestone')) -// mods.inspirations.anvil_smashing.removeAll() - -mods.inspirations.anvil_smashing.recipeBuilder() - .input(blockstate('minecraft:diamond_block')) - .output(blockstate('minecraft:clay')) - .register() - -mods.inspirations.anvil_smashing.recipeBuilder() - .input(blockstate('minecraft:clay')) - .output(blockstate('minecraft:air')) - .register() - - -// Cauldron: -// Converts up to 1 itemstack and up to 1 fluid into up to 1 itemstack or up to 1 fluid, with a boiling boolean and -// variable amount of fluid consumed or produced. - -mods.inspirations.cauldron.removeByFluidInput(fluid('mushroom_stew')) -mods.inspirations.cauldron.removeByFluidOutput(fluid('beetroot_soup')) -mods.inspirations.cauldron.removeByInput(item('minecraft:ghast_tear')) -mods.inspirations.cauldron.removeByOutput(item('minecraft:piston')) -// mods.inspirations.cauldron.removeAll() - -mods.inspirations.cauldron.recipeBuilder() - .standard() - .input(item('minecraft:gold_ingot')) - .fluidInput(fluid('lava')) - .output(item('minecraft:clay')) - .boiling() - .sound(sound('minecraft:block.anvil.destroy')) - .levels(3) - .register() - -mods.inspirations.cauldron.recipeBuilderBrewing() - .input(item('minecraft:diamond_block')) - .inputPotion(potionType('minecraft:fire_resistance')) - .outputPotion(potionType('minecraft:strength')) - .register() - -mods.inspirations.cauldron.recipeBuilderDye() - .input(item('minecraft:gold_block')) - .output(item('minecraft:diamond_block')) - .dye('blue') - .levels(2) - .register() - -mods.inspirations.cauldron.recipeBuilderFill() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .fluidInput(fluid('milk')) - .sound(sound('minecraft:block.anvil.destroy')) - .register() - -mods.inspirations.cauldron.recipeBuilderMix() - .output(item('minecraft:clay')) - .fluidInput(fluid('milk'), fluid('lava')) - .register() - -mods.inspirations.cauldron.recipeBuilderPotion() - .input(item('minecraft:gold_block')) - .output(item('minecraft:diamond_block')) - .inputPotion(potionType('minecraft:fire_resistance')) - .levels(2) - .register() - -mods.inspirations.cauldron.recipeBuilderStandard() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .fluidInput(fluid('lava')) - .levels(3) - .sound(sound('minecraft:block.anvil.destroy')) - .register() - -mods.inspirations.cauldron.recipeBuilderTransform() - .input(item('minecraft:stone:3')) - .fluidInput(fluid('water')) - .fluidOutput(fluid('milk')) - .levels(2) - .register() - - diff --git a/examples/postInit/integrateddynamics.groovy b/examples/postInit/integrateddynamics.groovy index 77d5233f3..e69de29bb 100644 --- a/examples/postInit/integrateddynamics.groovy +++ b/examples/postInit/integrateddynamics.groovy @@ -1,76 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: integrateddynamics - -log.info 'mod \'integrateddynamics\' detected, running script' - -// Drying Basin: -// Takes either an item or fluid input and gives either an item or fluid output after a duration. - -// mods.integrateddynamics.drying_basin.removeAll() - -mods.integrateddynamics.drying_basin.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .fluidInput(fluid('water') * 500) - .fluidOutput(fluid('lava') * 2000) - .mechanical() - .duration(5) - .register() - -mods.integrateddynamics.drying_basin.recipeBuilder() - .output(item('minecraft:clay')) - .fluidInput(fluid('water') * 2000) - .register() - - -// Mechanical Drying Basin: -// Takes either an item or fluid input and gives either an item or fluid output after a duration. - -// mods.integrateddynamics.mechanical_drying_basin.removeAll() - -mods.integrateddynamics.mechanical_drying_basin.recipeBuilder() - .input(item('minecraft:diamond')) - .fluidInput(fluid('water') * 50) - .fluidOutput(fluid('lava') * 20000) - .duration(300) - .register() - - -// Mechanical Squeezer: -// Takes an item and can give up to 3 chanced item outputs and a fluid. - -// mods.integrateddynamics.mechanical_squeezer.removeAll() - -mods.integrateddynamics.mechanical_squeezer.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay') * 16, 0.9F) - .register() - - -// Squeezer: -// Takes an item and can give up to 3 chanced item outputs and a fluid. - -// mods.integrateddynamics.squeezer.removeAll() - -mods.integrateddynamics.squeezer.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:clay_ball'), 1F) - .output(item('minecraft:clay_ball') * 2, 0.7F) - .output(item('minecraft:clay_ball') * 10, 0.2F) - .fluidOutput(fluid('lava') * 2000) - .mechanical() - .duration(5) - .register() - -mods.integrateddynamics.squeezer.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay'), 0.5F) - .register() - -mods.integrateddynamics.squeezer.recipeBuilder() - .input(item('minecraft:diamond')) - .fluidOutput(fluid('lava') * 10) - .register() - - diff --git a/examples/postInit/pneumaticcraft.groovy b/examples/postInit/pneumaticcraft.groovy index f5eaac5ec..e69de29bb 100644 --- a/examples/postInit/pneumaticcraft.groovy +++ b/examples/postInit/pneumaticcraft.groovy @@ -1,213 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: pneumaticcraft - -log.info 'mod \'pneumaticcraft\' detected, running script' - -// Amadron: -// Uses an Amadron Tablet and linked inventories in world to trade via drones. - -mods.pneumaticcraft.amadron.removeByInput(item('minecraft:rotten_flesh')) -mods.pneumaticcraft.amadron.removeByOutput(item('minecraft:emerald')) -// mods.pneumaticcraft.amadron.removeAll() -// mods.pneumaticcraft.amadron.removeAllPeriodic() -// mods.pneumaticcraft.amadron.removeAllStatic() - -mods.pneumaticcraft.amadron.recipeBuilder() - .input(item('minecraft:clay') * 3) - .output(item('minecraft:gold_ingot')) - .register() - -mods.pneumaticcraft.amadron.recipeBuilder() - .fluidInput(fluid('water') * 50) - .output(item('minecraft:clay') * 3) - .register() - -mods.pneumaticcraft.amadron.recipeBuilder() - .fluidInput(fluid('water') * 50) - .fluidOutput(fluid('lava') * 10) - .periodic() - .register() - - -// Assembly Controller: -// Uses a given Program to convert an input itemstack into an output itemstack. Drill recipes that output an itemstack used -// for the input itemstack of a Laser recipe can be chained via the Drill & Laser Program. - -mods.pneumaticcraft.assembly_controller.removeByInput(item('minecraft:redstone')) -mods.pneumaticcraft.assembly_controller.removeByOutput(item('pneumaticcraft:pressure_chamber_valve')) -// mods.pneumaticcraft.assembly_controller.removeAll() -// mods.pneumaticcraft.assembly_controller.removeAllDrill() -// mods.pneumaticcraft.assembly_controller.removeAllLaser() - -mods.pneumaticcraft.assembly_controller.recipeBuilder() - .input(item('minecraft:clay') * 3) - .output(item('minecraft:gold_ingot') * 6) - .drill() - .register() - -mods.pneumaticcraft.assembly_controller.recipeBuilder() - .input(item('minecraft:gold_ingot') * 6) - .output(item('minecraft:diamond')) - .laser() - .register() - -mods.pneumaticcraft.assembly_controller.recipeBuilder() - .input(item('minecraft:stone')) - .output(item('minecraft:clay') * 5) - .laser() - .register() - - -// Explosion: -// Converts an input item into an output item, with a chance to fail and destroy the item. - -// mods.pneumaticcraft.explosion.removeByInput(item('minecraft:iron_block')) -mods.pneumaticcraft.explosion.removeByOutput(item('pneumaticcraft:compressed_iron_block')) -// mods.pneumaticcraft.explosion.removeAll() - -mods.pneumaticcraft.explosion.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:gold_ingot')) - .lossRate(40) - .register() - -mods.pneumaticcraft.explosion.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:obsidian')) - .register() - - -// Heat Frame Cooling: -// Converts an input itemstack into an output itemstack when inside an inventory placed within a Heat Frame which is -// cooled. - -mods.pneumaticcraft.heat_frame_cooling.removeByInput(item('minecraft:water_bucket')) -mods.pneumaticcraft.heat_frame_cooling.removeByOutput(item('minecraft:obsidian')) -// mods.pneumaticcraft.heat_frame_cooling.removeAll() - -mods.pneumaticcraft.heat_frame_cooling.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:gold_ingot')) - .register() - -mods.pneumaticcraft.heat_frame_cooling.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:obsidian')) - .register() - - -// Liquid Fuel: -// Converts fluid into Pressure in a Liquid Compressor. - -mods.pneumaticcraft.liquid_fuel.remove(fluid('lava')) -// mods.pneumaticcraft.liquid_fuel.removeAll() - -mods.pneumaticcraft.liquid_fuel.recipeBuilder() - .fluidInput(fluid('water')) - .pressure(100_000_000) - .register() - - -// Plastic Mixer: -// Converts a fluidstack and an item with a variable damage value into each other, requiring temperature to operate the -// process, optionally consuming dye, and allowing either only melting or only solidifying. - -// mods.pneumaticcraft.plastic_mixer.removeByFluid(fluid('plastic')) -// mods.pneumaticcraft.plastic_mixer.removeByItem(item('pneumaticcraft:plastic')) -// mods.pneumaticcraft.plastic_mixer.removeAll() - -mods.pneumaticcraft.plastic_mixer.recipeBuilder() - .fluidInput(fluid('lava') * 100) - .output(item('minecraft:clay')) - .allowMelting() - .allowSolidifying() - .requiredTemperature(323) - .register() - -mods.pneumaticcraft.plastic_mixer.recipeBuilder() - .fluidInput(fluid('water') * 50) - .output(item('minecraft:sapling')) - .allowSolidifying() - .requiredTemperature(298) - .meta(-1) - .register() - - -// Pressure Chamber: -// Converts any number of input itemstacks into any number of output itemstacks, either generating Pressure or consuming -// Pressure from the Pressure Chamber. - -mods.pneumaticcraft.pressure_chamber.removeByInput(item('minecraft:iron_block')) -mods.pneumaticcraft.pressure_chamber.removeByOutput(item('minecraft:diamond')) -// mods.pneumaticcraft.pressure_chamber.removeAll() - -mods.pneumaticcraft.pressure_chamber.recipeBuilder() - .input(item('minecraft:clay') * 3) - .output(item('minecraft:gold_ingot')) - .pressure(4) - .register() - -mods.pneumaticcraft.pressure_chamber.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:gold_ingot'), item('minecraft:gold_block'), item('minecraft:gold_nugget'), item('minecraft:diamond'), item('minecraft:diamond_block'), item('minecraft:obsidian'), item('minecraft:stone'), item('minecraft:stone:1'), item('minecraft:stone:2'), item('minecraft:stone:3'), item('minecraft:stone:4'), item('minecraft:stone:5'), item('minecraft:stone:6')) - .output(item('minecraft:cobblestone')) - .pressure(4) - .register() - - -// Refinery: -// Converts an input fluidstack into between 2 and 4 fluidstacks, consuming Temperature, with the number of fluidstacks -// output depending on the recipe and the number of Refineries vertically stacked. - -// mods.pneumaticcraft.refinery.removeByInput(fluid('oil')) -mods.pneumaticcraft.refinery.removeByOutput(fluid('kerosene')) -// mods.pneumaticcraft.refinery.removeAll() - -mods.pneumaticcraft.refinery.recipeBuilder() - .fluidInput(fluid('water') * 1000) - .fluidOutput(fluid('lava') * 750, fluid('lava') * 250, fluid('lava') * 100, fluid('lava') * 50) - .register() - -mods.pneumaticcraft.refinery.recipeBuilder() - .fluidInput(fluid('lava') * 100) - .fluidOutput(fluid('water') * 50, fluid('kerosene') * 25) - .register() - - -// Thermopneumatic Processing Plant: -// Converts an input fluidstack into an output fluidstack, consuming Pressure and Temperature, with an optional itemstack -// being consumed. - -mods.pneumaticcraft.thermopneumatic_processing_plant.removeByInput(fluid('diesel')) -mods.pneumaticcraft.thermopneumatic_processing_plant.removeByInput(item('minecraft:coal')) -mods.pneumaticcraft.thermopneumatic_processing_plant.removeByOutput(fluid('lpg')) -// mods.pneumaticcraft.thermopneumatic_processing_plant.removeAll() - -mods.pneumaticcraft.thermopneumatic_processing_plant.recipeBuilder() - .input(item('minecraft:clay') * 3) - .fluidInput(fluid('water') * 100) - .fluidOutput(fluid('kerosene') * 100) - .pressure(4) - .requiredTemperature(323) - .register() - -mods.pneumaticcraft.thermopneumatic_processing_plant.recipeBuilder() - .fluidInput(fluid('water') * 100) - .fluidOutput(fluid('lava') * 100) - .pressure(4) - .requiredTemperature(323) - .register() - - -// XP Fluid: -// Controls what fluids are considered XP Fluids and how much experience they provide. - -// mods.pneumaticcraft.xp_fluid.remove(fluid('xpjuice')) -// mods.pneumaticcraft.xp_fluid.removeAll() - -mods.pneumaticcraft.xp_fluid.recipeBuilder() - .fluidInput(fluid('lava')) - .ratio(5) - .register() - - diff --git a/examples/postInit/quarryplus.groovy b/examples/postInit/quarryplus.groovy index 66c8f1d7c..e69de29bb 100644 --- a/examples/postInit/quarryplus.groovy +++ b/examples/postInit/quarryplus.groovy @@ -1,19 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: quarryplus - -log.info 'mod \'quarryplus\' detected, running script' - -// Workbench Plus: -// Converts up to 27 itemstacks into an output itemstack at the cost of power. - -mods.quarryplus.workbench_plus.removeByOutput(item('quarryplus:quarry')) -// mods.quarryplus.workbench_plus.removeAll() - -mods.quarryplus.workbench_plus.recipeBuilder() - .output(item('minecraft:nether_star')) - .input(item('minecraft:diamond'),item('minecraft:gold_ingot')) - .energy(10000) - .register() - - diff --git a/examples/postInit/thaumcraft.groovy b/examples/postInit/thaumcraft.groovy index a5863b13b..e69de29bb 100644 --- a/examples/postInit/thaumcraft.groovy +++ b/examples/postInit/thaumcraft.groovy @@ -1,205 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: thaumcraft - -log.info 'mod \'thaumcraft\' detected, running script' - -// Arcane Workbench: -// A special crafting table, allowing additional requirements in the form of Vis Crystals, Vis, and having a specific -// research. - -mods.thaumcraft.arcane_workbench.removeByOutput(item('thaumcraft:mechanism_simple')) -// mods.thaumcraft.arcane_workbench.removeAll() - -mods.thaumcraft.arcane_workbench.shapedBuilder() - .researchKey('UNLOCKALCHEMY@3') - .output(item('minecraft:pumpkin')) - .row('SS ') - .row(' ') - .row(' ') - .key('S', item('minecraft:pumpkin_seeds')) - .aspect('terra') - .vis(5) - .register() - -mods.thaumcraft.arcane_workbench.shapedBuilder() - .researchKey('UNLOCKALCHEMY@3') - .output(item('minecraft:clay')) - .matrix('SS ', - ' ', - ' ') - .key('S', item('minecraft:pumpkin')) - .aspect(aspect('terra')) - .vis(5) - .register() - -mods.thaumcraft.arcane_workbench.shapelessBuilder() - .researchKey('UNLOCKALCHEMY@3') - .input(item('minecraft:pumpkin')) - .input(item('minecraft:stick')) - .input(item('minecraft:stick')) - .output(item('thaumcraft:void_hoe')) - .vis(0) - .register() - - -// Aspect Creator: -// Creates a custom Aspect. - -// mods.thaumcraft.aspect.removeAll() - -mods.thaumcraft.aspect.aspectBuilder() - .tag('clay') - .chatColor(0xD5D4EC) - .image(resource('placeholdername:textures/items/clay_2.png')) - .register() - -mods.thaumcraft.aspect.aspectBuilder() - .tag('snack') - .chatColor(0xD5D4EC) - .component(aspect('cognitio')) - .component('clay') - .image(resource('placeholdername:textures/items/snack.png')) - .register() - - -// Entity/Block Aspects: -// Controls what Aspects are attached to entities or items. - -mods.thaumcraft.aspect_helper.aspectBuilder() - .object(item('minecraft:stone')) - .stripAspects() - .aspect(aspect('ignis') * 20) - .aspect('ordo', 5) - .register() - -mods.thaumcraft.aspect_helper.aspectBuilder() - .object(ore('cropPumpkin')) - .stripAspects() - .aspect(aspect('herba') * 20) - .register() - -mods.thaumcraft.aspect_helper.aspectBuilder() - .entity(entity('minecraft:chicken')) - .stripAspects() - .aspect('bestia', 20) - .register() - - -// Crucible: -// Combines an item with any number of Aspects to drop an output itemstack, potentially requiring a specific research to be -// completed. - -mods.thaumcraft.crucible.removeByOutput(item('minecraft:gunpowder')) -// mods.thaumcraft.crucible.removeAll() - -mods.thaumcraft.crucible.recipeBuilder() - .researchKey('UNLOCKALCHEMY@3') - .catalyst(item('minecraft:rotten_flesh')) - .output(item('minecraft:gold_ingot')) - .aspect(aspect('metallum') * 10) - .register() - - -// Dust Trigger: -// Converts a block in-world into an item, when interacting with it with Salis Mundus, potentially requiring a specific -// research to be completed. - -mods.thaumcraft.dust_trigger.removeByOutput(item('thaumcraft:arcane_workbench')) - -mods.thaumcraft.dust_trigger.triggerBuilder() - .researchKey('UNLOCKALCHEMY@3') - .target(block('minecraft:obsidian')) - .output(item('minecraft:enchanting_table')) - .register() - -mods.thaumcraft.dust_trigger.triggerBuilder() - .researchKey('UNLOCKALCHEMY@3') - .target(ore('cropPumpkin')) - .output(item('minecraft:lit_pumpkin')) - .register() - - -// Infusion Crafting: -// Combines any number of items and aspects together in the Infusion Altar, potentially requiring a specific research to be -// completed. - -mods.thaumcraft.infusion_crafting.removeByOutput(item('thaumcraft:crystal_terra')) -// mods.thaumcraft.infusion_crafting.removeAll() - -mods.thaumcraft.infusion_crafting.recipeBuilder() - .researchKey('UNLOCKALCHEMY@3') - .mainInput(item('minecraft:gunpowder')) - .output(item('minecraft:gold_ingot')) - .aspect(aspect('terra') * 20) - .aspect('ignis', 30) - .input(crystal('aer')) - .input(crystal('ignis')) - .input(crystal('aqua')) - .input(crystal('terra')) - .input(crystal('ordo')) - .instability(10) - .register() - - -// Lootbag: -// Control what the different rarities of lootbag drop when opened. - -mods.thaumcraft.loot_bag.remove(item('minecraft:ender_pearl'), 0) -mods.thaumcraft.loot_bag.removeAll(2) - -mods.thaumcraft.loot_bag.add(item('minecraft:dirt'), 100, 0) -mods.thaumcraft.loot_bag.add(item('minecraft:diamond_block'), 100, 2) - -// Research: -// Create or modify existing research entries, which contain helpful information and unlock recipes, and can be gated -// behind specific items or events. - -// mods.thaumcraft.research.removeCategory('BASICS') -// mods.thaumcraft.research.removeAllCategories() - -mods.thaumcraft.research.researchCategoryBuilder() - .key('BASICS2') - .researchKey('UNLOCKAUROMANCY') - .formulaAspect(aspect('herba') * 5) - .formulaAspect(aspect('ordo') * 5) - .formulaAspect(aspect('perditio') * 5) - .formulaAspect('aer', 5) - .formulaAspect('ignis', 5) - .formulaAspect(aspect('terra') * 5) - .formulaAspect('aqua', 5) - .icon(resource('thaumcraft:textures/aspects/humor.png')) - .background(resource('thaumcraft:textures/gui/gui_research_back_1.jpg')) - .background2(resource('thaumcraft:textures/gui/gui_research_back_over.png')) - .register() - - -// mods.thaumcraft.research.addResearchLocation(resource('thaumcraft:research/new.json')) -mods.thaumcraft.research.addScannable('KNOWLEDGETYPEHUMOR', item('minecraft:pumpkin')) - -// Smelting Bonus: -// Additional item output when smelting a given item in the Infernal Furnace Multiblock. - -mods.thaumcraft.smelting_bonus.removeByOutput(item('minecraft:gold_nugget')) -// mods.thaumcraft.smelting_bonus.removeAll() - -mods.thaumcraft.smelting_bonus.recipeBuilder() - .input(item('minecraft:cobblestone')) - .output(item('minecraft:stone_button')) - .chance(0.2F) - .register() - -mods.thaumcraft.smelting_bonus.recipeBuilder() - .input(ore('stone')) - .output(item('minecraft:obsidian')) - .register() - - -// Warp: -// Determines if holding an item or equipping a piece of armor or a bauble gives warp, and how much warp it gives. - -mods.thaumcraft.warp.removeWarp(item('thaumcraft:void_hoe')) -// mods.thaumcraft.warp.removeAll() - -mods.thaumcraft.warp.addWarp(item('minecraft:pumpkin'), 3) - diff --git a/examples/postInit/theaurorian.groovy b/examples/postInit/theaurorian.groovy index cd3c50e97..e69de29bb 100644 --- a/examples/postInit/theaurorian.groovy +++ b/examples/postInit/theaurorian.groovy @@ -1,33 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: theaurorian - -log.info 'mod \'theaurorian\' detected, running script' - -// Moonlight Forge: -// Combines two items to get a third item. Only works at night, and works faster the higher it is placed in the world. - -mods.theaurorian.moonlight_forge.removeByInput(item('theaurorian:moonstonesword'), item('theaurorian:aurorianiteingot')) -mods.theaurorian.moonlight_forge.removeByOutput(item('theaurorian:queenschipper')) -// mods.theaurorian.moonlight_forge.removeAll() - -mods.theaurorian.moonlight_forge.recipeBuilder() - .input(item('minecraft:stone_sword'), item('minecraft:diamond')) - .output(item('minecraft:diamond_sword')) - .register() - - -// Scrapper: -// Turns an input item into an output item. Can be sped up by placing a Crystal on top of it. The crystal has a chance to -// break every time a recipe is executed. - -mods.theaurorian.scrapper.removeByInput(item('minecraft:iron_sword')) -mods.theaurorian.scrapper.removeByOutput(item('theaurorian:scrapaurorianite')) -// mods.theaurorian.scrapper.removeAll() - -mods.theaurorian.scrapper.recipeBuilder() - .input(item('minecraft:stone_sword')) - .output(item('minecraft:cobblestone')) - .register() - - diff --git a/examples/postInit/thebetweenlands.groovy b/examples/postInit/thebetweenlands.groovy index c910efbfd..e69de29bb 100644 --- a/examples/postInit/thebetweenlands.groovy +++ b/examples/postInit/thebetweenlands.groovy @@ -1,209 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: thebetweenlands - -log.info 'mod \'thebetweenlands\' detected, running script' - -// Animator: -// Converts an input item, Life amount from Life Crystals, and Fuel from Sulfur into an output itemstack, summoning an -// entity, a random item from a loottable, or summoning an entity and outputting an itemstack. - -mods.thebetweenlands.animator.removeByEntity(entity('thebetweenlands:sporeling')) -mods.thebetweenlands.animator.removeByInput(item('thebetweenlands:bone_leggings')) -mods.thebetweenlands.animator.removeByLootTable(resource('thebetweenlands:animator/scroll')) -mods.thebetweenlands.animator.removeByOutput(item('thebetweenlands:items_misc:46')) -// mods.thebetweenlands.animator.removeAll() - -mods.thebetweenlands.animator.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .life(1) - .fuel(1) - .register() - -mods.thebetweenlands.animator.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .lootTable(resource('minecraft:entities/zombie')) - .life(5) - .fuel(1) - .register() - -mods.thebetweenlands.animator.recipeBuilder() - .input(item('minecraft:gold_block')) - .entity(entity('minecraft:zombie').getEntityClass()) - .life(1) - .fuel(5) - .register() - -mods.thebetweenlands.animator.recipeBuilder() - .input(item('minecraft:diamond')) - .entity(entity('minecraft:enderman')) - .output(item('minecraft:clay')) - .life(3) - .fuel(10) - .register() - - -// Compost: -// Converts an input itemstack into an amount of compost. - -mods.thebetweenlands.compost.removeByInput(item('thebetweenlands:items_misc:13')) -// mods.thebetweenlands.compost.removeAll() - -mods.thebetweenlands.compost.recipeBuilder() - .input(item('minecraft:clay')) - .amount(20) - .time(30) - .register() - -mods.thebetweenlands.compost.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .amount(1) - .time(5) - .register() - - -// Crab Pot Filter Bubbler: -// Converts an input item into an output itemstack when a Bubbler Crab is placed inside a Crab Pot Filter. - -mods.thebetweenlands.crab_pot_filter_bubbler.removeByInput(item('thebetweenlands:silt')) -mods.thebetweenlands.crab_pot_filter_bubbler.removeByOutput(item('thebetweenlands:swamp_dirt')) -// mods.thebetweenlands.crab_pot_filter_bubbler.removeAll() - -mods.thebetweenlands.crab_pot_filter_bubbler.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.crab_pot_filter_bubbler.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .register() - - -// Crab Pot Filter Silt: -// Converts an input item into an output itemstack when a Silt Crab is placed inside a Crab Pot Filter. - -mods.thebetweenlands.crab_pot_filter_silt.removeByInput(item('thebetweenlands:mud')) -mods.thebetweenlands.crab_pot_filter_silt.removeByOutput(item('thebetweenlands:mud')) -// mods.thebetweenlands.crab_pot_filter_silt.removeAll() - -mods.thebetweenlands.crab_pot_filter_silt.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.crab_pot_filter_silt.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .register() - - -// Druid Altar: -// Converts 4 input items into an output itemstack. - -// mods.thebetweenlands.druid_altar.removeByInput(item('thebetweenlands:swamp_talisman:1')) -mods.thebetweenlands.druid_altar.removeByOutput(item('thebetweenlands:swamp_talisman')) -// mods.thebetweenlands.druid_altar.removeAll() - -mods.thebetweenlands.druid_altar.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.druid_altar.recipeBuilder() - .input(item('minecraft:diamond'), item('minecraft:gold_block'), item('minecraft:gold_ingot'), item('minecraft:clay')) - .output(item('minecraft:clay')) - .register() - - -// Pestle And Mortar: -// Converts an input item into an output itemstack in a Pestle and Mortar by using a Pestle tool in the Mortar. - -mods.thebetweenlands.pestle_and_mortar.removeByInput(item('thebetweenlands:limestone')) -mods.thebetweenlands.pestle_and_mortar.removeByOutput(item('thebetweenlands:fish_bait')) -// mods.thebetweenlands.pestle_and_mortar.removeAll() - -mods.thebetweenlands.pestle_and_mortar.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.pestle_and_mortar.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .register() - - -// Purifier: -// Converts an input item into an output itemstack, consuming Sulfur and Swamp Water as fuel. - -mods.thebetweenlands.purifier.removeByInput(item('thebetweenlands:items_misc:64')) -mods.thebetweenlands.purifier.removeByOutput(item('thebetweenlands:cragrock')) -// mods.thebetweenlands.purifier.removeAll() - -mods.thebetweenlands.purifier.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.purifier.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .register() - - -// Smoking Rack: -// Converts an input item into an output itemstack over a configurable period of time, consuming Fallen Leaves to do so. - -mods.thebetweenlands.smoking_rack.removeByInput(item('thebetweenlands:anadia')) -mods.thebetweenlands.smoking_rack.removeByOutput(item('thebetweenlands:barnacle_smoked')) -// mods.thebetweenlands.smoking_rack.removeAll() - -mods.thebetweenlands.smoking_rack.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .register() - -mods.thebetweenlands.smoking_rack.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .time(50) - .register() - - -// Steeping Pot: -// Converts a 1,000mb of fluid into either 1,000mb of a fluid, an output itemstack, or both, consuming up to 4 items from a -// Silk Bundle placed inside the Steeping Pot to do so. The Silk Bundle is converted into a Dirty Silk Bundle in the -// process. The Silk Bundle can only hold specific items, which are also configurable. - -mods.thebetweenlands.steeping_pot.removeAcceptedItem(item('thebetweenlands:items_crushed:5')) -mods.thebetweenlands.steeping_pot.removeByInput(fluid('clean_water')) -mods.thebetweenlands.steeping_pot.removeByInput(item('thebetweenlands:items_crushed:13')) -mods.thebetweenlands.steeping_pot.removeByOutput(fluid('dye_fluid').withNbt(['type': 14])) -// mods.thebetweenlands.steeping_pot.removeByOutput(item('thebetweenlands:limestone')) -// mods.thebetweenlands.steeping_pot.removeAll() -// mods.thebetweenlands.steeping_pot.removeAllAcceptedItem() - -mods.thebetweenlands.steeping_pot.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) - .fluidInput(fluid('lava')) - .fluidOutput(fluid('water')) - .register() - -mods.thebetweenlands.steeping_pot.recipeBuilder() - .input(item('minecraft:diamond')) - .fluidInput(fluid('lava')) - .fluidOutput(fluid('dye_fluid')) - .meta(5) - .register() - -mods.thebetweenlands.steeping_pot.recipeBuilder() - .input(item('minecraft:emerald')) - .fluidInput(fluid('lava')) - .fluidOutput(fluid('water')) - .register() - - -mods.thebetweenlands.steeping_pot.addAcceptedItem(item('minecraft:gold_block')) - diff --git a/examples/postInit/thermalexpansion.groovy b/examples/postInit/thermalexpansion.groovy index 138401923..e69de29bb 100644 --- a/examples/postInit/thermalexpansion.groovy +++ b/examples/postInit/thermalexpansion.groovy @@ -1,652 +0,0 @@ - -// Auto generated groovyscript example file -// MODS_LOADED: thermalexpansion - -import cofh.thermalexpansion.util.managers.machine.InsolatorManager - -log.info 'mod \'thermalexpansion\' detected, running script' - -// Alchemical Imbuer: -// Converts an input fluidstack and input itemstack into an output fluidstack, costing power and taking time based on the -// power cost. - -mods.thermalexpansion.brewer.removeByInput(item('minecraft:glowstone_dust')) -mods.thermalexpansion.brewer.removeByInput(fluid('potion').withNbt(['Potion': 'minecraft:leaping'])) -mods.thermalexpansion.brewer.removeByOutput(fluid('potion_splash').withNbt(['Potion': 'cofhcore:luck2'])) -// mods.thermalexpansion.brewer.removeAll() - -mods.thermalexpansion.brewer.recipeBuilder() - .input(item('minecraft:clay')) - .fluidInput(fluid('water') * 100) - .fluidOutput(fluid('lava') * 100) - .register() - -mods.thermalexpansion.brewer.recipeBuilder() - .input(item('minecraft:diamond') * 2) - .fluidInput(fluid('water') * 1000) - .fluidOutput(fluid('steam') * 100) - .energy(1000) - .register() - - -// mods.thermalexpansion.brewer.add(1000, item('minecraft:obsidian') * 2, fluid('water') * 1000, fluid('steam') * 100) - -// Centrifugal Separator: -// Converts an input itemstack into an optional output fluidstack and up to four output itemstacks with chance, costing -// power and taking time based on the power cost. - -mods.thermalexpansion.centrifuge.removeByInput(item('minecraft:reeds')) -mods.thermalexpansion.centrifuge.removeByOutput(fluid('redstone')) -mods.thermalexpansion.centrifuge.removeByOutput(item('minecraft:redstone')) -// mods.thermalexpansion.centrifuge.removeAll() - -mods.thermalexpansion.centrifuge.recipeBuilder() - .input(item('minecraft:clay')) - .fluidOutput(fluid('water') * 100) - .output(item('minecraft:diamond') * 2, item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) - .chance(50, 100, 1) - .register() - -mods.thermalexpansion.centrifuge.recipeBuilder() - .input(item('minecraft:diamond') * 3) - .output(item('minecraft:clay')) - .chance(100) - .energy(1000) - .register() - - -// mods.thermalexpansion.centrifuge.add(1000, item('minecraft:obsidian') * 3, [item('minecraft:clay')], [100], null) - -// Centrifugal Separator - Enstabulation Apparatus: -// Converts an input itemstack into an optional output fluidstack and up to four output itemstacks with chance, costing -// power and taking time based on the power cost. - -mods.thermalexpansion.centrifuge_mobs.removeByInput(item('thermalexpansion:morb').withNbt(['id': 'minecraft:slime'])) -mods.thermalexpansion.centrifuge_mobs.removeByOutput(item('minecraft:fish')) -// mods.thermalexpansion.centrifuge_mobs.removeByOutput(fluid('experience')) -// mods.thermalexpansion.centrifuge_mobs.removeAll() - -mods.thermalexpansion.centrifuge_mobs.recipeBuilder() - .input(item('thermalexpansion:morb').withNbt(['id': 'minecraft:slime'])) - .fluidOutput(fluid('water') * 100) - .output(item('minecraft:diamond') * 2, item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) - .chance(50, 100, 1) - .register() - -mods.thermalexpansion.centrifuge_mobs.recipeBuilder() - .input(item('minecraft:diamond') * 3) - .output(item('minecraft:clay')) - .chance(100) - .energy(1000) - .register() - - -// mods.thermalexpansion.centrifuge_mobs.add(1000, item('minecraft:obsidian') * 3, item('minecraft:clay'), 100) - -// Energetic Infuser: -// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. - -mods.thermalexpansion.charger.removeByInput(item('thermalfoundation:bait:1')) -mods.thermalexpansion.charger.removeByOutput(item('thermalfoundation:fertilizer:2')) -// mods.thermalexpansion.charger.removeAll() - -mods.thermalexpansion.charger.recipeBuilder() - .input(item('minecraft:diamond') * 5) - .output(item('minecraft:clay')) - .register() - -mods.thermalexpansion.charger.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond') * 2) - .energy(1000) - .register() - - -// mods.thermalexpansion.charger.add(1000, item('minecraft:obsidian'), item('minecraft:diamond') * 2) - -// Compactor: -// Converts an input itemstack into an output itemstack, with different modes each requiring a different augment to be -// installed, costing power and taking time based on the power cost. - -mods.thermalexpansion.compactor.removeByInput(compactorMode('coin'), item('thermalfoundation:material:130')) -mods.thermalexpansion.compactor.removeByInput(item('minecraft:iron_ingot')) -// mods.thermalexpansion.compactor.removeByMode(compactorMode('plate')) -mods.thermalexpansion.compactor.removeByOutput(compactorMode('coin'), item('thermalfoundation:coin:102')) -mods.thermalexpansion.compactor.removeByOutput(item('minecraft:blaze_rod')) -mods.thermalexpansion.compactor.removeByOutput(item('thermalfoundation:material:24')) -// mods.thermalexpansion.compactor.removeAll() - -mods.thermalexpansion.compactor.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond') * 2) - .mode(compactorMode('coin')) - .register() - -mods.thermalexpansion.compactor.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) - .mode(compactorMode('all')) - .register() - -mods.thermalexpansion.compactor.recipeBuilder() - .input(item('minecraft:diamond') * 2) - .output(item('minecraft:gold_ingot')) - .mode(compactorMode('plate')) - .energy(1000) - .register() - - -// mods.thermalexpansion.compactor.add(1000, compactorMode('plate'), item('minecraft:obsidian') * 2, item('minecraft:gold_ingot')) - -// Compression Dynamo: -// Converts an input fluidstack into power, taking time based on the power. - -mods.thermalexpansion.compression.removeByInput(fluid('seed_oil')) -// mods.thermalexpansion.compression.removeAll() - -mods.thermalexpansion.compression.add(fluid('steam'), 100) - -// Thermal Mediator: -// Consumes fluid to speed up the tick rate of adjacent machines and devices and generate power in the Compression Dynamo. - -mods.thermalexpansion.coolant.remove(fluid('cryotheum')) -// mods.thermalexpansion.coolant.removeAll() - -mods.thermalexpansion.coolant.add(fluid('lava'), 4000, 30) - -// Magma Crucible: -// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. - -mods.thermalexpansion.crucible.removeByInput(item('minecraft:glowstone_dust')) -mods.thermalexpansion.crucible.removeByOutput(fluid('lava')) -// mods.thermalexpansion.crucible.removeAll() - -mods.thermalexpansion.crucible.recipeBuilder() - .input(item('minecraft:clay')) - .fluidOutput(fluid('lava') * 25) - .register() - -mods.thermalexpansion.crucible.recipeBuilder() - .input(item('minecraft:diamond')) - .fluidOutput(fluid('water') * 1000) - .energy(1000) - .register() - - -mods.thermalexpansion.crucible.add(1000, item('minecraft:obsidian'), fluid('water') * 1000) - -// Decorative Diffuser: -// Controls what items can be used in to boost the potion time and level in the Decorative Diffuser. - -mods.thermalexpansion.diffuser.remove(item('minecraft:redstone')) -// mods.thermalexpansion.diffuser.removeAll() - -mods.thermalexpansion.diffuser.add(item('minecraft:clay'), 2, 30) - -// Arcane Ensorcellator: -// Converts two input itemstacks and liquid experience into an output itemstack, costing power and taking time based on the -// power cost. - -mods.thermalexpansion.enchanter.removeByInput(item('minecraft:blaze_rod')) -// mods.thermalexpansion.enchanter.removeByInput(item('minecraft:book')) -mods.thermalexpansion.enchanter.removeByOutput(item('minecraft:enchanted_book').withNbt(['StoredEnchantments': [['lvl': 1, 'id': 34]]])) -// mods.thermalexpansion.enchanter.removeAll() - -mods.thermalexpansion.enchanter.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 4) - .output(item('minecraft:diamond')) - .register() - -mods.thermalexpansion.enchanter.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:gold_ingot')) - .output(item('minecraft:diamond')) - .experience(1000) - .energy(1000) - .register() - - -mods.thermalexpansion.enchanter.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot'), item('minecraft:diamond'), 1000) -mods.thermalexpansion.enchanter.addArcana(item('minecraft:clay')) - -// Enervation Dynamo: -// Converts an input itemstack into power, taking time based on the power. - -mods.thermalexpansion.enervation.removeByInput(item('minecraft:redstone')) -// mods.thermalexpansion.enervation.removeAll() - -mods.thermalexpansion.enervation.add(item('minecraft:clay'), 100) - -// Igneous Extruder: -// Converts a variable amount of lava and water into a specific output itemstack. - -// mods.thermalexpansion.extruder.removeByInput(false, fluid('lava')) -// mods.thermalexpansion.extruder.removeByInput(fluid('water')) -mods.thermalexpansion.extruder.removeByOutput(true, item('minecraft:gravel')) -mods.thermalexpansion.extruder.removeByOutput(item('minecraft:obsidian')) -// mods.thermalexpansion.extruder.removeByType(true) -// mods.thermalexpansion.extruder.removeAll() - -mods.thermalexpansion.extruder.recipeBuilder() - .fluidHot(100) - .fluidCold(1000) - .output(item('minecraft:clay')) - .register() - -mods.thermalexpansion.extruder.recipeBuilder() - .fluidHot(100) - .fluidCold(1000) - .output(item('minecraft:gold_ingot')) - .sedimentary() - .energy(1000) - .register() - - -mods.thermalexpansion.extruder.add(1000, item('minecraft:gold_block'), 100, 1000, false) - -// Factorizer: -// Converts an input itemstack into an output itemstack, with the ability to undo the the recipe. Mainly used for -// compressing ingots into blocks and splitting blocks into ingots. - -mods.thermalexpansion.factorizer.removeByInput(false, item('minecraft:diamond')) -mods.thermalexpansion.factorizer.removeByInput(item('minecraft:coal:1')) -// mods.thermalexpansion.factorizer.removeByOutput(false, item('minecraft:coal:1')) -mods.thermalexpansion.factorizer.removeByOutput(item('minecraft:emerald_block')) -// mods.thermalexpansion.factorizer.removeByType(true) -// mods.thermalexpansion.factorizer.removeAll() - -mods.thermalexpansion.factorizer.recipeBuilder() - .input(item('minecraft:clay') * 7) - .output(item('minecraft:book') * 2) - .combine() - .split() - .register() - -mods.thermalexpansion.factorizer.recipeBuilder() - .input(item('minecraft:planks:*') * 4) - .output(item('minecraft:crafting_table')) - .combine() - .register() - - -// Aquatic Entangler: -// Controls what itemstacks can be gained and how likely each is to be obtained. - -mods.thermalexpansion.fisher.remove(item('minecraft:fish:0')) -// mods.thermalexpansion.fisher.removeAll() - -mods.thermalexpansion.fisher.add(item('minecraft:clay'), 100) - -// Aquatic Entangler Bait: -// Controls what items can be used in the bait slot of the Aquatic Entangler and how effective they are. - -mods.thermalexpansion.fisher_bait.remove(item('thermalfoundation:bait:2')) -// mods.thermalexpansion.fisher_bait.removeAll() - -mods.thermalexpansion.fisher_bait.add(item('minecraft:clay'), 100) - -// Redstone Furnace: -// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. - -mods.thermalexpansion.furnace.removeByInput(item('minecraft:cactus:*')) -mods.thermalexpansion.furnace.removeByOutput(item('minecraft:cooked_porkchop')) -mods.thermalexpansion.furnace.removeFood(item('minecraft:rabbit:*')) -// mods.thermalexpansion.furnace.removeAll() -// mods.thermalexpansion.furnace.removeAllFood() - -mods.thermalexpansion.furnace.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay') * 2) - .register() - -mods.thermalexpansion.furnace.recipeBuilder() - .input(item('minecraft:gold_ingot') * 2) - .output(item('minecraft:clay')) - .energy(1000) - .register() - - -mods.thermalexpansion.furnace.add(1000, item('minecraft:obsidian') * 2, item('minecraft:clay')) -mods.thermalexpansion.furnace.addFood(item('minecraft:emerald_ore')) - -// Redstone Furnace - Pyrolytic Conversion: -// Converts an input itemstack into an output itemstack and creosote amount, costing power and taking time based on the -// power cost. - -mods.thermalexpansion.furnace_pyrolysis.removeByInput(item('minecraft:cactus:*')) -mods.thermalexpansion.furnace_pyrolysis.removeByOutput(item('thermalfoundation:storage_resource:1')) -// mods.thermalexpansion.furnace_pyrolysis.removeAll() - -mods.thermalexpansion.furnace_pyrolysis.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond') * 2) - .creosote(100) - .register() - -mods.thermalexpansion.furnace_pyrolysis.recipeBuilder() - .input(item('minecraft:gold_ingot') * 2) - .output(item('minecraft:clay')) - .creosote(1000) - .energy(1000) - .register() - - -mods.thermalexpansion.furnace_pyrolysis.add(1000, item('minecraft:obsidian') * 2, item('minecraft:clay'), 1000) - -// Phytogenic Insolator: -// Converts two input itemstacks into an output itemstack and optional output itemstack with a chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.insolator.removeByInput(item('minecraft:double_plant:4')) -mods.thermalexpansion.insolator.removeByInput(item('thermalfoundation:fertilizer')) -mods.thermalexpansion.insolator.removeByOutput(item('minecraft:melon_seeds')) -mods.thermalexpansion.insolator.removeByOutput(item('minecraft:red_flower:6')) -// mods.thermalexpansion.insolator.removeAll() - -mods.thermalexpansion.insolator.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:diamond')) - .output(item('minecraft:diamond') * 4) - .register() - -mods.thermalexpansion.insolator.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 2) - .output(item('minecraft:clay'), item('minecraft:diamond')) - .chance(5) - .water(100) - .tree() - .energy(1000) - .register() - - -mods.thermalexpansion.insolator.add(1000, 100, item('minecraft:obsidian'), item('minecraft:gold_ingot') * 2, item('minecraft:clay'), item('minecraft:diamond'), 5, InsolatorManager.Type.TREE) - -// Numismatic Dynamo - Lapidary Calibration: -// Converts an input itemstack into power, taking time based on the power. - -mods.thermalexpansion.lapidary.removeByInput(item('minecraft:diamond')) -// mods.thermalexpansion.lapidary.removeAll() - -mods.thermalexpansion.lapidary.add(item('minecraft:clay'), 1000) - -// Magmatic Dynamo: -// Converts an input fluidstack into power, taking time based on the power. - -mods.thermalexpansion.magmatic.removeByInput(fluid('lava')) -// mods.thermalexpansion.magmatic.removeAll() - -mods.thermalexpansion.magmatic.add(fluid('steam'), 100) - -// Numismatic Dynamo: -// Converts an input itemstack into power, taking time based on the power. - -mods.thermalexpansion.numismatic.removeByInput(item('thermalfoundation:coin:69')) -// mods.thermalexpansion.numismatic.removeAll() - -mods.thermalexpansion.numismatic.add(item('minecraft:clay'), 100) - -// Glacial Precipitator: -// Converts an amount of water into a specific output itemstack, costing power and taking time based on the power cost. - -// mods.thermalexpansion.precipitator.removeByInput(fluid('water')) -mods.thermalexpansion.precipitator.removeByOutput(item('minecraft:snowball')) -// mods.thermalexpansion.precipitator.removeAll() - -mods.thermalexpansion.precipitator.recipeBuilder() - .output(item('minecraft:clay')) - .register() - -mods.thermalexpansion.precipitator.recipeBuilder() - .water(100) - .output(item('minecraft:clay')) - .energy(1000) - .register() - - -mods.thermalexpansion.precipitator.add(1000, item('minecraft:obsidian'), 100) - -// Pulverizer: -// Converts an input itemstack into an output itemstack and optional output itemstack with a chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.pulverizer.removeByInput(item('minecraft:emerald_ore')) -mods.thermalexpansion.pulverizer.removeByOutput(item('minecraft:diamond')) -mods.thermalexpansion.pulverizer.removeByOutput(item('thermalfoundation:material:772')) -// mods.thermalexpansion.pulverizer.removeAll() - -mods.thermalexpansion.pulverizer.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay'), item('minecraft:diamond')) - .chance(1) - .register() - -mods.thermalexpansion.pulverizer.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) - .energy(1000) - .register() - - -mods.thermalexpansion.pulverizer.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), 100) - -// Reactant Dynamo: -// Converts an input itemstack and input fluidstack into power, taking time based on the power. - -mods.thermalexpansion.reactant.removeByInput(fluid('redstone')) -mods.thermalexpansion.reactant.removeByInput(item('minecraft:blaze_powder')) -mods.thermalexpansion.reactant.removeElementalFluid(fluid('cryotheum')) -mods.thermalexpansion.reactant.removeElementalReactant(item('thermalfoundation:material:1024')) -// mods.thermalexpansion.reactant.removeAll() - -mods.thermalexpansion.reactant.recipeBuilder() - .input(item('minecraft:diamond')) - .fluidInput(fluid('steam')) - .register() - -mods.thermalexpansion.reactant.recipeBuilder() - .input(item('minecraft:clay')) - .fluidInput(fluid('glowstone')) - .energy(100) - .register() - - -mods.thermalexpansion.reactant.add(item('minecraft:clay'), fluid('steam'), 100) -mods.thermalexpansion.reactant.addElementalFluid(fluid('glowstone')) -mods.thermalexpansion.reactant.addElementalReactant(item('minecraft:clay')) -mods.thermalexpansion.reactant.addElementalReactant(item('minecraft:gunpowder')) - -// Fractionating Still: -// Converts an input fluidstack into an output fluidstack and optional output itemstack with chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.refinery.removeBioFuel(fluid('resin')) -mods.thermalexpansion.refinery.removeByInput(fluid('resin')) -mods.thermalexpansion.refinery.removeByOutput(fluid('refined_biofuel')) -// mods.thermalexpansion.refinery.removeByOutput(item('thermalfoundation:material:771')) -mods.thermalexpansion.refinery.removeFossilFuel(fluid('coal')) -// mods.thermalexpansion.refinery.removeAll() -// mods.thermalexpansion.refinery.removeAllBioFuels() -// mods.thermalexpansion.refinery.removeAllFossilFuels() - -mods.thermalexpansion.refinery.recipeBuilder() - .fluidInput(fluid('water') * 100) - .fluidOutput(fluid('steam') * 80) - .register() - -mods.thermalexpansion.refinery.recipeBuilder() - .fluidInput(fluid('lava') * 100) - .fluidOutput(fluid('steam') * 150) - .output(item('minecraft:clay')) - .chance(25) - .energy(1000) - .register() - - -mods.thermalexpansion.refinery.add(1000, fluid('ender') * 100, fluid('steam') * 150, item('minecraft:clay'), 25) -mods.thermalexpansion.refinery.addBioFuel(fluid('coal')) -mods.thermalexpansion.refinery.addFossilFuel(fluid('crude_oil')) - -// Fractionating Still - Alchemical Retort: -// Converts an input fluidstack into an output fluidstack and optional output itemstack with chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.refinery_potion.removeByInput(fluid('potion_lingering').withNbt(['Potion': 'cofhcore:healing3'])) -mods.thermalexpansion.refinery_potion.removeByOutput(fluid('potion_splash').withNbt(['Potion': 'cofhcore:leaping4'])) -// mods.thermalexpansion.refinery_potion.removeAll() - -mods.thermalexpansion.refinery_potion.recipeBuilder() - .fluidInput(fluid('water') * 100) - .fluidOutput(fluid('steam') * 200) - .register() - -mods.thermalexpansion.refinery_potion.recipeBuilder() - .fluidInput(fluid('lava') * 100) - .fluidOutput(fluid('steam') * 30) - .output(item('minecraft:clay')) - .chance(75) - .energy(1000) - .register() - - -mods.thermalexpansion.refinery_potion.add(1000, fluid('ender') * 100, fluid('steam') * 30, item('minecraft:clay'), 75) - -// Sawmill: -// Converts an input itemstack into an output itemstack and optional output itemstack with a chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.sawmill.removeByInput(item('minecraft:pumpkin')) -mods.thermalexpansion.sawmill.removeByOutput(item('minecraft:leather')) -mods.thermalexpansion.sawmill.removeByOutput(item('thermalfoundation:material:800')) -// mods.thermalexpansion.sawmill.removeAll() - -mods.thermalexpansion.sawmill.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:gold_ingot') * 2) - .register() - -mods.thermalexpansion.sawmill.recipeBuilder() - .input(item('minecraft:clay') * 4) - .output(item('minecraft:gold_ingot'), item('minecraft:diamond')) - .chance(25) - .energy(1000) - .register() - - -mods.thermalexpansion.sawmill.add(1000, item('minecraft:obsidian') * 4, item('minecraft:gold_ingot'), item('minecraft:diamond'), 25) - -// Induction Smelter: -// Converts two input itemstacks into an output itemstack and optional output itemstack with a chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.smelter.removeByInput(ore('sand')) -mods.thermalexpansion.smelter.removeByInput(item('minecraft:iron_ingot')) -mods.thermalexpansion.smelter.removeByOutput(item('thermalfoundation:material:166')) -// mods.thermalexpansion.smelter.removeAll() - -mods.thermalexpansion.smelter.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:diamond')) - .output(item('minecraft:diamond') * 4) - .register() - -mods.thermalexpansion.smelter.recipeBuilder() - .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 2) - .output(item('minecraft:clay'), item('minecraft:diamond')) - .chance(5) - .energy(1000) - .register() - - -// mods.thermalexpansion.smelter.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot') * 2, item('minecraft:clay'), item('minecraft:diamond'), 5) - -// Steam Dynamo: -// Converts an input itemstack into power, taking time based on the power. - -mods.thermalexpansion.steam.removeByInput(item('minecraft:coal:1')) -// mods.thermalexpansion.steam.removeAll() - -mods.thermalexpansion.steam.add(item('minecraft:clay'), 100) - -// Arboreal Extractor: -// Controls what items and blocks can be turned into what fluids. Output can be boosted via Fertilizer items. - -mods.thermalexpansion.tapper.removeBlockByInput(item('minecraft:log')) -mods.thermalexpansion.tapper.removeItemByInput(item('minecraft:log:1')) -// mods.thermalexpansion.tapper.removeAll() -// mods.thermalexpansion.tapper.removeAllBlocks() -// mods.thermalexpansion.tapper.removeAllItems() - -mods.thermalexpansion.tapper.addBlock(item('minecraft:clay'), fluid('lava') * 150) -mods.thermalexpansion.tapper.addItem(item('minecraft:clay'), fluid('lava') * 300) - -// Arboreal Extractor Fertilizer: -// Controls what items can be used in the fertilizer slot of the Arboreal Extractor Fertilizer and how effective they are. - -mods.thermalexpansion.tapper_fertilizer.remove(item('thermalfoundation:fertilizer:2')) -// mods.thermalexpansion.tapper_fertilizer.removeAll() - -mods.thermalexpansion.tapper_fertilizer.add(item('minecraft:clay'), 1000) - -// Arboreal Extractor Tree Structures: -// Controls what valid log blocks and leaf blocks are to define a tree structure which the Arboreal Extractor can function -// on. The \"tree\" must contain some number of leaves adjacent to the log blocks to be valid. - -mods.thermalexpansion.tapper_tree.removeByLeaf(blockstate('minecraft:leaves', 'variant=birch')) -mods.thermalexpansion.tapper_tree.removeByLog(blockstate('minecraft:log', 'variant=spruce')) -// mods.thermalexpansion.tapper_tree.removeAll() - -mods.thermalexpansion.tapper_tree.add(blockstate('minecraft:clay'), blockstate('minecraft:gold_block')) - -// Fluid Transposer - Empty: -// Converts an input itemstack into an output fluidstack and optional output itemstack with chance, costing power and -// taking time based on the power cost. - -mods.thermalexpansion.transposer_extract.removeByInput(item('minecraft:sponge:1')) -mods.thermalexpansion.transposer_extract.removeByOutput(fluid('seed_oil')) -mods.thermalexpansion.transposer_extract.removeByOutput(item('minecraft:bowl')) -// mods.thermalexpansion.transposer_extract.removeAll() - -mods.thermalexpansion.transposer_extract.recipeBuilder() - .input(item('minecraft:diamond') * 2) - .fluidOutput(fluid('water') * 100) - .register() - -mods.thermalexpansion.transposer_extract.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond') * 2) - .fluidOutput(fluid('water') * 50) - .energy(1000) - .register() - - -mods.thermalexpansion.transposer_extract.add(1000, item('minecraft:obsidian'), fluid('water') * 50, item('minecraft:diamond') * 2, 100) - -// Fluid Transposer - Fill: -// Converts an input itemstack and input fluidstack into an output itemstack with chance, costing power and taking time -// based on the power cost. - -mods.thermalexpansion.transposer_fill.removeByInput(fluid('glowstone')) -mods.thermalexpansion.transposer_fill.removeByInput(item('minecraft:concrete_powder:3')) -mods.thermalexpansion.transposer_fill.removeByOutput(item('minecraft:ice')) -// mods.thermalexpansion.transposer_fill.removeAll() - -mods.thermalexpansion.transposer_fill.recipeBuilder() - .input(item('minecraft:diamond') * 2) - .fluidInput(fluid('water') * 100) - .register() - -mods.thermalexpansion.transposer_fill.recipeBuilder() - .input(item('minecraft:clay')) - .output(item('minecraft:diamond') * 2) - .fluidInput(fluid('water') * 50) - .energy(1000) - .register() - - -mods.thermalexpansion.transposer_fill.add(1000, item('minecraft:obsidian'), fluid('water') * 50, item('minecraft:diamond') * 2, 100) - -// Insightful Condenser: -// Collects experience orbs nearby, with the ability to increase the XP gained via catalyst itemstacks. - -mods.thermalexpansion.xp_collector.remove(item('minecraft:soul_sand')) -// mods.thermalexpansion.xp_collector.removeAll() - -mods.thermalexpansion.xp_collector.add(item('minecraft:clay'), 100, 30) - diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java index db37af7b2..36defa117 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CompiledScript.java @@ -25,8 +25,8 @@ public static String classNameFromPath(String path) { final List innerClasses = new ArrayList<>(); long lastEdited; List preprocessors; - boolean preprocessorCheckFailed; - boolean requiresReload; + private boolean preprocessorCheckFailed; + private boolean requiresReload; public CompiledScript(String path, long lastEdited) { this(path, classNameFromPath(path), lastEdited); @@ -43,7 +43,7 @@ public boolean isClosure() { @Override public void onCompile(Class clazz, String basePath) { - this.requiresReload = this.data == null; + setRequiresReload(this.data == null); super.onCompile(clazz, basePath); } @@ -125,10 +125,25 @@ public void deleteCache(String cachePath) { } } - public boolean checkPreprocessors(File basePath) { - return this.preprocessors == null || this.preprocessors.isEmpty() || Preprocessor.validatePreprocessor( - new File(basePath, this.path), - this.preprocessors); + public boolean checkPreprocessorsFailed(File basePath) { + setPreprocessorCheckFailed(this.preprocessors != null && !this.preprocessors.isEmpty() && !Preprocessor.validatePreprocessor(new File(basePath, this.path), this.preprocessors)); + return preprocessorCheckFailed(); + } + + public boolean requiresReload() { + return this.requiresReload; + } + + public boolean preprocessorCheckFailed() { + return this.preprocessorCheckFailed; + } + + protected void setRequiresReload(boolean requiresReload) { + this.requiresReload = requiresReload; + } + + protected void setPreprocessorCheckFailed(boolean preprocessorCheckFailed) { + this.preprocessorCheckFailed = preprocessorCheckFailed; } @Override diff --git a/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java b/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java index c55352ad0..86e3af874 100644 --- a/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java +++ b/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java @@ -19,11 +19,13 @@ //////////////////////////////////////////////////////////////////////////////// package net.prominic.groovyls.providers; +import com.cleanroommc.groovyscript.GroovyScript; import com.cleanroommc.groovyscript.mapper.AbstractObjectMapper; import com.cleanroommc.groovyscript.server.CompletionParams; import com.cleanroommc.groovyscript.server.Completions; import groovy.lang.Closure; import groovy.lang.DelegatesTo; +import groovy.lang.Script; import io.github.classgraph.ClassInfo; import io.github.classgraph.FieldInfo; import io.github.classgraph.MethodInfo; @@ -289,9 +291,9 @@ private void populateItemsFromClassNode(ClassNode classNode, Position position, if (classRange == null) return; String className = getMemberName(classNode.getUnresolvedName(), classRange, position); if (classNode.equals(parentClassNode.getUnresolvedSuperClass())) { - populateTypes(classNode, className, new HashSet<>(), true, false, false, items); + populateTypes(classNode, className, new ObjectOpenHashSet<>(), true, false, false, items); } else if (Arrays.asList(parentClassNode.getUnresolvedInterfaces()).contains(classNode)) { - populateTypes(classNode, className, new HashSet<>(), false, true, false, items); + populateTypes(classNode, className, new ObjectOpenHashSet<>(), false, true, false, items); } } @@ -301,7 +303,7 @@ private void populateItemsFromConstructorCallExpression(ConstructorCallExpressio Range typeRange = GroovyLSUtils.astNodeToRange(constructorCallExpr.getType()); if (typeRange == null) return; String typeName = getMemberName(constructorCallExpr.getType().getNameWithoutPackage(), typeRange, position); - populateTypes(constructorCallExpr, typeName, new HashSet<>(), true, false, false, items); + populateTypes(constructorCallExpr, typeName, new ObjectOpenHashSet<>(), true, false, false, items); } private void populateItemsFromVariableExpression(VariableExpression varExpr, Position position, Completions items) { @@ -319,6 +321,7 @@ private void populateItemsFromPropertiesAndFields(List properties, String name = p.getName(); if (!p.isPublic() || existingNames.contains(name)) return null; existingNames.add(name); + if (p.getDeclaringClass().isDerivedFrom(ClassHelper.makeCached(Script.class)) && p.getName().equals("__$stMC")) return null; CompletionItem item = CompletionItemFactory.createCompletion(p, p.getName(), astContext); if (!p.isDynamicTyped()) { var details = new CompletionItemLabelDetails(); @@ -331,6 +334,7 @@ private void populateItemsFromPropertiesAndFields(List properties, String name = f.getName(); if (!f.isPublic() || existingNames.contains(name)) return null; existingNames.add(name); + if (f.getDeclaringClass().isDerivedFrom(ClassHelper.makeCached(Script.class)) && f.getName().equals("__$stMC")) return null; CompletionItem item = CompletionItemFactory.createCompletion(f, f.getName(), astContext); if (!f.isDynamicTyped()) { var details = new CompletionItemLabelDetails(); @@ -346,6 +350,9 @@ private void populateItemsFromMethods(List methods, Set exis String name = getDescriptor(method, true, false, false); if (!method.isPublic() || existingNames.contains(name)) return null; existingNames.add(name); + if (method.getDeclaringClass().isDerivedFrom(ClassHelper.makeCached(Script.class))) { + if (method.getName().equals("$getLookup") || method.getName().equals("main")) return null; + } if (method.getDeclaringClass().isResolved() && (method.getModifiers() & GroovyASTUtils.EXPANSION_MARKER) == 0 && GroovyReflectionUtils.resolveMethodFromMethodNode(method, astContext) == null) { return null; } @@ -440,7 +447,9 @@ public static String getDescriptor(MethodInfo node, boolean includeName, boolean } builder.append(")"); if (!includeReturn) return builder.toString(); - var ret = display ? node.getTypeSignatureOrTypeDescriptor().getResultType().toStringWithSimpleNames() : node.getTypeDescriptor().getResultType().toString(); + var ret = display + ? node.getTypeSignatureOrTypeDescriptor().getResultType().toStringWithSimpleNames() + : node.getTypeDescriptor().getResultType().toString(); if (!ret.equals("void")) { if (display) builder.append(" -> "); builder.append(ret); @@ -449,7 +458,8 @@ public static String getDescriptor(MethodInfo node, boolean includeName, boolean } public static StringBuilder appendParameter(MethodParameterInfo param, StringBuilder builder, boolean display, boolean maybeVarargs) { - builder.append(display ? param.getTypeSignatureOrTypeDescriptor().toStringWithSimpleNames() : param.getTypeDescriptor().toString()); // don't use generic types + builder.append( + display ? param.getTypeSignatureOrTypeDescriptor().toStringWithSimpleNames() : param.getTypeDescriptor().toString()); // don't use generic types if (maybeVarargs && builder.charAt(builder.length() - 1) == ']' && builder.charAt(builder.length() - 2) == '[') { builder.delete(builder.length() - 2, builder.length()).append("..."); } @@ -627,7 +637,14 @@ private void populateTypes(ASTNode offsetNode, ModuleNode enclosingModule = getModule(); String enclosingPackageName = enclosingModule.getPackageName(); - items.addAll(astContext.getVisitor().getClassNodes(), classNode -> { + List classNodes = astContext.getVisitor().getClassNodes(); + Set all = new ObjectOpenHashSet<>(); + all.addAll(classNodes); + for (Class clz : GroovyScript.getSandbox().getEngine().getAllLoadedScriptClasses()) { + //if (Script.class.isAssignableFrom(clz)) continue; + all.add(ClassHelper.makeCached(clz)); + } + items.addAll(all, classNode -> { if (!includeEnums && classNode.isEnum()) return null; if (!includeInterfaces && classNode.isInterface()) return null; if (!includeClasses && (!classNode.isInterface() && !classNode.isEnum())) return null; From 9fd9ae94ccd17e06d750f4ba84de053e021d6a0e Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 2 Apr 2025 14:31:28 +0200 Subject: [PATCH 10/13] include current loaded scripts in ls --- .../sandbox/CustomGroovyScriptEngine.java | 57 +++++++++++++------ .../sandbox/GroovyScriptSandbox.java | 2 +- .../GroovyScriptCompilationUnitFactory.java | 5 +- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index ac82276d7..7535f73c8 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -3,6 +3,7 @@ import com.cleanroommc.groovyscript.GroovyScript; import com.cleanroommc.groovyscript.api.GroovyLog; import com.cleanroommc.groovyscript.helper.JsonHelper; +import com.google.common.collect.AbstractIterator; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -97,6 +98,33 @@ public GroovyScriptClassLoader getClassLoader() { return classLoader; } + public Iterable> getAllLoadedScriptClasses() { + return () -> new AbstractIterator<>() { + + private final Iterator it = loadedClasses.values().iterator(); + private Iterator innerClassesIt; + + @Override + protected Class computeNext() { + if (innerClassesIt != null && innerClassesIt.hasNext()) { + return innerClassesIt.next().clazz; + } + innerClassesIt = null; + CompiledClass cc; + while (it.hasNext()) { + cc = it.next(); + if (cc instanceof CompiledScript cs && !cs.preprocessorCheckFailed() && cs.clazz != null) { + if (!cs.innerClasses.isEmpty()) { + innerClassesIt = cs.innerClasses.iterator(); + } + return cs.clazz; + } + } + return endOfData(); + } + }; + } + void readIndex() { this.index.clear(); JsonElement jsonElement = JsonHelper.loadJson(new File(this.cacheRoot, "_index.json")); @@ -156,15 +184,15 @@ List findScripts(Collection files) { List scripts = new ArrayList<>(files.size()); for (File file : files) { CompiledScript cs = checkScriptLoadability(file); - if (!cs.preprocessorCheckFailed) scripts.add(cs); + if (!cs.preprocessorCheckFailed()) scripts.add(cs); } return scripts; } void loadScript(CompiledScript script) { - if (script.requiresReload && !script.preprocessorCheckFailed) { + if (script.requiresReload() && !script.preprocessorCheckFailed()) { Class clazz = loadScriptClassInternal(new File(script.path), true); - script.requiresReload = false; + script.setRequiresReload(false); if (script.clazz == null) { // should not happen GroovyLog.get().errorMC("Class for {} was loaded, but didn't receive class created callback!", script.path); @@ -188,9 +216,8 @@ CompiledScript checkScriptLoadability(File file) { if (ENABLE_CACHE && comp != null && lastModified <= comp.lastEdited && comp.clazz == null && comp.readData(this.cacheRoot.getPath())) { // class is not loaded, but the cached class bytes are still valid - comp.requiresReload = false; - if (!comp.checkPreprocessors(this.scriptRoot)) { - comp.preprocessorCheckFailed = true; + comp.setRequiresReload(false); + if (comp.checkPreprocessorsFailed(this.scriptRoot)) { return comp; } comp.ensureLoaded(getClassLoader(), this.loadedClasses, this.cacheRoot.getPath()); @@ -204,28 +231,26 @@ CompiledScript checkScriptLoadability(File file) { InvokerHelper.removeClass(comp.clazz); comp.clazz = null; } - comp.requiresReload = true; + comp.setRequiresReload(true); if (lastModified > comp.lastEdited || comp.preprocessors == null) { // recompile preprocessors if there is no data or script was edited comp.preprocessors = Preprocessor.parsePreprocessors(file); } comp.lastEdited = lastModified; - if (!comp.checkPreprocessors(this.scriptRoot)) { + if (comp.checkPreprocessorsFailed(this.scriptRoot)) { // delete class bytes to make sure it's recompiled once the preprocessors returns true comp.deleteCache(this.cacheRoot.getPath()); - comp.preprocessorCheckFailed = true; return comp; } } else { // class is loaded and script wasn't edited - comp.requiresReload = false; - if (!comp.checkPreprocessors(this.scriptRoot)) { - comp.preprocessorCheckFailed = true; + comp.setRequiresReload(false); + if (comp.checkPreprocessorsFailed(this.scriptRoot)) { return comp; } comp.ensureLoaded(getClassLoader(), this.loadedClasses, this.cacheRoot.getPath()); } - comp.preprocessorCheckFailed = false; + comp.setPreprocessorCheckFailed(false); return comp; } @@ -463,7 +488,7 @@ public LookupResult findClassNode(String origName, CompilationUnit compilationUn File scriptFile = CustomGroovyScriptEngine.this.findScriptFileOfClass(name); if (scriptFile != null) { CompiledScript result = checkScriptLoadability(scriptFile); - if (result.requiresReload || result.clazz == null) { + if (result.requiresReload() || result.clazz == null) { try { return new LookupResult(compilationUnit.addSource(scriptFile.toURI().toURL()), null); } catch (MalformedURLException e) { @@ -499,10 +524,10 @@ public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSourc file = new File(codeSource.getName()); } CompiledScript compiledScript = loadScriptClass(file); - if (compiledScript.preprocessorCheckFailed) { + if (compiledScript.preprocessorCheckFailed()) { throw new IllegalStateException("Figure this out"); } - if (compiledScript.requiresReload || compiledScript.clazz == null) { + if (compiledScript.requiresReload() || compiledScript.clazz == null) { compiledScript.clazz = CustomGroovyScriptEngine.this.parseDynamicScript(file, false); } return compiledScript.clazz; diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index ab3ef8f7e..3545b98ab 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -238,7 +238,7 @@ protected void loadScripts(Binding binding, Set executedClasses, boolean long t = System.currentTimeMillis(); this.engine.loadScript(compiledScript); this.compileTime += System.currentTimeMillis() - t; - if (compiledScript.preprocessorCheckFailed) continue; + if (compiledScript.preprocessorCheckFailed()) continue; if (compiledScript.clazz == null) { GroovyLog.get().errorMC("Error loading script {}", compiledScript.path); continue; diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java index 2f356194c..00a87e1f4 100644 --- a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java @@ -1,5 +1,6 @@ package com.cleanroommc.groovyscript.server; +import com.cleanroommc.groovyscript.GroovyScript; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptEarlyCompiler; import groovy.lang.GroovyClassLoader; @@ -51,7 +52,7 @@ public GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context) context = null; // actions on classes are going into classes only unit } - var unit = compilationUnitsByScript.computeIfAbsent(context, uri -> new GroovyLSCompilationUnit(getConfiguration(), null, getClassLoader(), languageServerContext)); + var unit = compilationUnitsByScript.computeIfAbsent(context, uri -> new GroovyLSCompilationUnit(getConfiguration(), null, GroovyScript.getSandbox().getEngine().getClassLoader(), languageServerContext)); var changedUris = languageServerContext.getFileContentsTracker().getChangedURIs(); @@ -112,7 +113,7 @@ protected void removeSources(GroovyLSCompilationUnit unit, Set urisToRemove } }); - // if an URI has changed, we remove it from the compilation unit so + // if a URI has changed, we remove it from the compilation unit so // that a new version can be built from the updated source file unit.removeSources(sourcesToRemove); } From 7518afb14f76f0866d4089c78913ce5e3c31c95d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 2 Apr 2025 15:12:46 +0200 Subject: [PATCH 11/13] why did it do that --- examples/postInit/arcaneworld.groovy | 89 +++ examples/postInit/betterwithaddons.groovy | 285 +++++++++ examples/postInit/bloodarsenal.groovy | 50 ++ examples/postInit/custom/vanilla.groovy | 2 - examples/postInit/inspirations.groovy | 92 +++ examples/postInit/integrateddynamics.groovy | 76 +++ examples/postInit/pneumaticcraft.groovy | 213 +++++++ examples/postInit/quarryplus.groovy | 19 + examples/postInit/thaumcraft.groovy | 205 ++++++ examples/postInit/theaurorian.groovy | 33 + examples/postInit/thebetweenlands.groovy | 0 examples/postInit/thermalexpansion.groovy | 652 ++++++++++++++++++++ 12 files changed, 1714 insertions(+), 2 deletions(-) delete mode 100644 examples/postInit/thebetweenlands.groovy diff --git a/examples/postInit/arcaneworld.groovy b/examples/postInit/arcaneworld.groovy index e69de29bb..fc901398b 100644 --- a/examples/postInit/arcaneworld.groovy +++ b/examples/postInit/arcaneworld.groovy @@ -0,0 +1,89 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: arcaneworld + +log.info 'mod \'arcaneworld\' detected, running script' + +// Ritual: +// Converts up to 5 input itemstacks into a wide number of possible effects, including spawning entities, opening a portal +// to a dungeon dimension to fight a mob, awarding an output itemstack, running commands, and even entirely customized +// effects. + +mods.arcaneworld.ritual.removeByInput(item('minecraft:gold_nugget')) +mods.arcaneworld.ritual.removeByOutput(item('arcaneworld:biome_crystal')) +// mods.arcaneworld.ritual.removeAll() + +mods.arcaneworld.ritual.recipeBuilder() + .ritualCreateItem() + .input(item('minecraft:stone') * 5, item('minecraft:diamond'), item('minecraft:clay')) + .output(item('minecraft:clay')) + .translationKey('groovyscript.demo_output') + .name('groovyscript:custom_name') + .register() + +mods.arcaneworld.ritual.recipeBuilderArena() + .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:clay')) + .translationKey('groovyscript.demo_arena') + .entity(entity('minecraft:chicken')) + .register() + +mods.arcaneworld.ritual.recipeBuilderCommand() + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:clay')) + .translationKey('groovyscript.demo_command') + .command('say hi', + 'give @p minecraft:coal 5') + .register() + +mods.arcaneworld.ritual.recipeBuilderCreateItem() + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond')) + .translationKey('groovyscript.demo_create_item') + .output(item('minecraft:diamond')) + .register() + +mods.arcaneworld.ritual.recipeBuilderCustom() + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay')) + .translationKey('groovyscript.demo_custom') + .onActivate({ World world, BlockPos blockPos, EntityPlayer player, ItemStack... itemStacks -> { log.info blockPos } }) + .register() + +mods.arcaneworld.ritual.recipeBuilderDragonBreath() + .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) + .translationKey('groovyscript.demo_dragon_breath') + .register() + +mods.arcaneworld.ritual.recipeBuilderDungeon() + .input(item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay')) + .translationKey('groovyscript.demo_dungeon') + .register() + +mods.arcaneworld.ritual.recipeBuilderSummon() + .input(item('minecraft:stone'), item('minecraft:clay'), item('minecraft:clay')) + .translationKey('groovyscript.demo_summon') + .entity(entity('minecraft:chicken')) + .register() + +mods.arcaneworld.ritual.recipeBuilderTime() + .input(item('minecraft:diamond'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) + .translationKey('groovyscript.demo_time') + .time(5000) + .register() + +mods.arcaneworld.ritual.recipeBuilderWeather() + .input(item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:clay')) + .translationKey('groovyscript.demo_weather_clear') + .weatherClear() + .register() + +mods.arcaneworld.ritual.recipeBuilderWeather() + .input(item('minecraft:gold_ingot'), item('minecraft:diamond'), item('minecraft:clay')) + .translationKey('groovyscript.demo_weather_rain') + .weatherRain() + .register() + +mods.arcaneworld.ritual.recipeBuilderWeather() + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:gold_ingot')) + .translationKey('groovyscript.demo_weather_thunder') + .weatherThunder() + .register() + + diff --git a/examples/postInit/betterwithaddons.groovy b/examples/postInit/betterwithaddons.groovy index e69de29bb..f99d870f2 100644 --- a/examples/postInit/betterwithaddons.groovy +++ b/examples/postInit/betterwithaddons.groovy @@ -0,0 +1,285 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: betterwithaddons + +log.info 'mod \'betterwithaddons\' detected, running script' + +// Drying Unit: +// Converts an input item into an output itemstack if placed within the appropriate multiblock. The multiblock is Sandstone +// directly below the Drying Box, 8 Sand around the Drying Box, and a Dead Bush placed on the Sand. Only functions in a +// non-snowy biome with sky access during the day, and functions twice as fast when in a hot biome. + +mods.betterwithaddons.drying_box.removeByInput(item('betterwithaddons:japanmat:2')) +mods.betterwithaddons.drying_box.removeByOutput(item('minecraft:sponge')) +// mods.betterwithaddons.drying_box.removeAll() + +mods.betterwithaddons.drying_box.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.drying_box.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4) + .register() + + +// Fire Net: +// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock +// is Lava or Fire directly below the Netted Screen, 8 Stone Brick around the Lava or Fire, and 8 Slat Blocks placed around +// the Netted Screen. + +mods.betterwithaddons.fire_net.removeByInput(item('betterwithaddons:iron_sand')) +mods.betterwithaddons.fire_net.removeByOutput(item('betterwithaddons:japanmat:12')) +// mods.betterwithaddons.fire_net.removeAll() + +mods.betterwithaddons.fire_net.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.fire_net.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) + .register() + + +// Ancestral Infusion Crafting: +// Converts a custom crafting recipe an output itemstack, consuming Spirits from the Infused Soul Sand placed below the +// Ancestral Infuser if placed within the appropriate multiblock. The multiblock is either Soul Sand or Infused Soul Sand +// placed below the Ancestral Infuser and exclusively air blocks adjacent to the Infuser and Soul Sand blocks. + +mods.betterwithaddons.infuser.removeByInput(item('betterwithaddons:japanmat:16')) +mods.betterwithaddons.infuser.removeByOutput(item('betterwithaddons:ya')) +// mods.betterwithaddons.infuser.removeAll() + +mods.betterwithaddons.infuser.shapedBuilder() + .output(item('minecraft:stone')) + .matrix('BXX', + 'X B') + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .spirits(1) + .mirrored() + .register() + +mods.betterwithaddons.infuser.shapedBuilder() + .output(item('minecraft:diamond') * 32) + .matrix([[item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], + [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')], + [item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')]]) + .spirits(6) + .register() + +mods.betterwithaddons.infuser.shapelessBuilder() + .output(item('minecraft:clay') * 8) + .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')) + .register() + +mods.betterwithaddons.infuser.shapelessBuilder() + .output(item('minecraft:clay') * 32) + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:diamond')) + .spirits(8) + .register() + + +// Alicio Tree Foods: +// Converts an input item into an amount of food for the tree to gradually consume, eventually summoning a random creature +// nearby. + +mods.betterwithaddons.lure_tree.removeByInput(item('minecraft:rotten_flesh')) +// mods.betterwithaddons.lure_tree.removeAll() + +mods.betterwithaddons.lure_tree.recipeBuilder() + .input(item('minecraft:diamond')) + .food(1000) + .register() + +mods.betterwithaddons.lure_tree.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .food(4) + .register() + + +mods.betterwithaddons.lure_tree.addBlacklist(entity('minecraft:chicken')) + +// Packing: +// Converts an input itemstack in the form of a EntityItems into an IBlockState after a piston extends if the piston and +// location the EntityItems are in are fully surrounded by solid blocks. + +mods.betterwithaddons.packing.removeByInput(item('minecraft:clay_ball')) +mods.betterwithaddons.packing.removeByOutput(blockstate('minecraft:gravel')) +// mods.betterwithaddons.packing.removeAll() + +mods.betterwithaddons.packing.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .compress(blockstate('minecraft:clay')) + .register() + +mods.betterwithaddons.packing.recipeBuilder() + .input(item('minecraft:clay') * 10) + .compress(blockstate('minecraft:diamond_block')) + .register() + +mods.betterwithaddons.packing.recipeBuilder() + .input(item('minecraft:diamond')) + .compress(blockstate('minecraft:dirt')) + .jeiOutput(item('minecraft:diamond') * 64) + .register() + + +// Rotting Food: +// Converts an input item into an output itemstack after the given time has passed. Has the ability to customize the +// terminology used to indicate the age. + +mods.betterwithaddons.rotting.removeByInput(item('betterwithaddons:food_cooked_rice')) +mods.betterwithaddons.rotting.removeByOutput(item('minecraft:rotten_flesh')) +// mods.betterwithaddons.rotting.removeAll() + +mods.betterwithaddons.rotting.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .register() + +mods.betterwithaddons.rotting.recipeBuilder() + .input(item('placeholdername:snack')) + .time(100) + .key('groovy_example') + .rotted(item('minecraft:clay') * 4) + .register() + + +// Sand Net: +// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock +// is a Slat Block directly below the Netted Screen, 8 Water Blocks around the Water, and 8 Slat Blocks placed around the +// Netted Screen. + +mods.betterwithaddons.sand_net.removeByInput(item('minecraft:iron_ingot')) +mods.betterwithaddons.sand_net.removeByOutput(item('minecraft:sand')) +mods.betterwithaddons.sand_net.removeByOutput(item('betterwithaddons:iron_sand')) +// mods.betterwithaddons.sand_net.removeAll() + +mods.betterwithaddons.sand_net.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.sand_net.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:gold_ingot')) + .sand(2) + .register() + +mods.betterwithaddons.sand_net.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) + .sand(5) + .register() + + +// Soaking Unit: +// Converts an input item into an output itemstack if placed within the appropriate multiblock. The multiblock is Ice +// directly above the Soaking Box, 8 Water around the Soaking Box, and Water directly below the Soaking Box. + +mods.betterwithaddons.soaking_box.removeByInput(item('betterwithaddons:bamboo')) +mods.betterwithaddons.soaking_box.removeByOutput(item('betterwithaddons:japanmat:8')) +// mods.betterwithaddons.soaking_box.removeAll() + +mods.betterwithaddons.soaking_box.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.soaking_box.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4) + .register() + + +// Spindle: +// Converts an input itemstack into an output itemstack, with the ability to consume the Spindle, when placed against a +// Spinning Wheel powered by Mechanical Power. + +mods.betterwithaddons.spindle.removeByInput(item('minecraft:vine')) +mods.betterwithaddons.spindle.removeByOutput(item('betterwithaddons:bolt')) +// mods.betterwithaddons.spindle.removeAll() + +mods.betterwithaddons.spindle.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.spindle.recipeBuilder() + .input(item('minecraft:clay') * 3) + .output(item('minecraft:diamond')) + .popoff() + .register() + +mods.betterwithaddons.spindle.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4) + .register() + + +// Tatara: +// Converts an input item into an output itemstack if placed within the appropriate multiblock while fueled by Rice Ashes. +// The multiblock is Lava or Fire directly below the Tatara, 8 Clay around the Lava or Fire, 9 Nether Brick above the +// Tatara, 4 Stone Brick diagonal to the Tatara and two Iron Blocks across from each other adjacent to the Tatara. + +mods.betterwithaddons.tatara.removeByInput(item('betterwithaddons:japanmat:20')) +mods.betterwithaddons.tatara.removeByOutput(item('betterwithaddons:kera')) +// mods.betterwithaddons.tatara.removeAll() + +mods.betterwithaddons.tatara.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.tatara.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4) + .register() + + +// Ancestral Infusion Transmutation: +// Converts an input item into an output itemstack, consuming Spirits from the Infused Soul Sand placed below the Ancestral +// Infuser if placed within the appropriate multiblock. The multiblock is either Soul Sand or Infused Soul Sand placed +// below the Ancestral Infuser and exclusively air blocks adjacent to the Infuser and Soul Sand blocks. + +mods.betterwithaddons.transmutation.removeByInput(item('minecraft:reeds')) +mods.betterwithaddons.transmutation.removeByOutput(item('betterwithaddons:crop_rice')) +// mods.betterwithaddons.transmutation.removeAll() + +mods.betterwithaddons.transmutation.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .spirits(0) + .register() + +mods.betterwithaddons.transmutation.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4) + .spirits(5) + .register() + + +// Water Net: +// Converts an input item into any number of output itemstacks if placed within the appropriate multiblock. The multiblock +// is a Water Block directly below the Netted Screen, 8 Sakura Planks around the Water Block, and 8 Slat Blocks placed +// around the Netted Screen. + +mods.betterwithaddons.water_net.removeByInput(item('betterwithaddons:iron_sand')) +mods.betterwithaddons.water_net.removeByOutput(item('betterwithaddons:food_sashimi')) +mods.betterwithaddons.water_net.removeByOutput(item('betterwithaddons:food_fugu_sac')) +// mods.betterwithaddons.water_net.removeAll() + +mods.betterwithaddons.water_net.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .register() + +mods.betterwithaddons.water_net.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay') * 4, item('minecraft:diamond'), item('minecraft:diamond') * 2) + .register() + + diff --git a/examples/postInit/bloodarsenal.groovy b/examples/postInit/bloodarsenal.groovy index e69de29bb..fd974a264 100644 --- a/examples/postInit/bloodarsenal.groovy +++ b/examples/postInit/bloodarsenal.groovy @@ -0,0 +1,50 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: bloodarsenal + +log.info 'mod \'bloodarsenal\' detected, running script' + +// Sanguine Infusion: +// Converts an input infusion itemstack and up to 8 input surrounding itemstacks into an output itemstack, consuming Life +// Essence from the network to do so when the Infusion de Sanguine Ritual is activated. Alternatively, instead of consuming +// an infusion item, adds or upgrades a modifier to the given stasis tool, with the ability to increase the quantity of +// inputs consumed based on level. + +// mods.bloodarsenal.sanguine_infusion.removeBlacklist(WayofTime.bloodmagic.iface.ISigil.class) +mods.bloodarsenal.sanguine_infusion.removeByInput(item('minecraft:feather')) +mods.bloodarsenal.sanguine_infusion.removeByInput(item('bloodmagic:bound_axe')) +// mods.bloodarsenal.sanguine_infusion.removeByModifierKey('beneficial_potion') +mods.bloodarsenal.sanguine_infusion.removeByOutput(item('bloodarsenal:stasis_pickaxe')) +// mods.bloodarsenal.sanguine_infusion.removeAll() +// mods.bloodarsenal.sanguine_infusion.removeAllBlacklist() + +mods.bloodarsenal.sanguine_infusion.recipeBuilder() + .infuse(item('minecraft:gold_ingot')) + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .cost(1000) + .register() + +mods.bloodarsenal.sanguine_infusion.recipeBuilder() + .infuse(item('minecraft:emerald')) + .input(item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64, item('minecraft:clay') * 64) + .output(item('minecraft:diamond') * 64) + .cost(5000) + .register() + +mods.bloodarsenal.sanguine_infusion.recipeBuilder() + .infuse(item('minecraft:gold_ingot')) + .input(item('minecraft:clay'), item('minecraft:diamond')) + .output(item('minecraft:diamond')) + .register() + +mods.bloodarsenal.sanguine_infusion.recipeBuilder() + .input(item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2, item('minecraft:gold_ingot') * 2) + .modifier('xperienced') + .levelMultiplier(3) + .cost(3000) + .register() + + +// mods.bloodarsenal.sanguine_infusion.addBlacklist(WayofTime.bloodmagic.iface.ISigil.class) + diff --git a/examples/postInit/custom/vanilla.groovy b/examples/postInit/custom/vanilla.groovy index d53ca64ad..4c3405ebc 100644 --- a/examples/postInit/custom/vanilla.groovy +++ b/examples/postInit/custom/vanilla.groovy @@ -4,8 +4,6 @@ import net.minecraftforge.event.entity.living.EnderTeleportEvent import net.minecraftforge.event.world.BlockEvent import net.minecraft.util.text.TextComponentString -import classes.SimpleConversionRecipe -import postInit.custom.normal_mode /* def ore_iron = ore('ingotIron') diff --git a/examples/postInit/inspirations.groovy b/examples/postInit/inspirations.groovy index e69de29bb..3d7641d68 100644 --- a/examples/postInit/inspirations.groovy +++ b/examples/postInit/inspirations.groovy @@ -0,0 +1,92 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: inspirations + +log.info 'mod \'inspirations\' detected, running script' + +// Anvil Smashing: +// Converts a Block or IBlockState into an IBlockState when an anvil falls on top of it (from any height). + +mods.inspirations.anvil_smashing.removeByInput(blockstate('minecraft:packed_ice')) +mods.inspirations.anvil_smashing.removeByOutput(blockstate('minecraft:cobblestone')) +// mods.inspirations.anvil_smashing.removeAll() + +mods.inspirations.anvil_smashing.recipeBuilder() + .input(blockstate('minecraft:diamond_block')) + .output(blockstate('minecraft:clay')) + .register() + +mods.inspirations.anvil_smashing.recipeBuilder() + .input(blockstate('minecraft:clay')) + .output(blockstate('minecraft:air')) + .register() + + +// Cauldron: +// Converts up to 1 itemstack and up to 1 fluid into up to 1 itemstack or up to 1 fluid, with a boiling boolean and +// variable amount of fluid consumed or produced. + +mods.inspirations.cauldron.removeByFluidInput(fluid('mushroom_stew')) +mods.inspirations.cauldron.removeByFluidOutput(fluid('beetroot_soup')) +mods.inspirations.cauldron.removeByInput(item('minecraft:ghast_tear')) +mods.inspirations.cauldron.removeByOutput(item('minecraft:piston')) +// mods.inspirations.cauldron.removeAll() + +mods.inspirations.cauldron.recipeBuilder() + .standard() + .input(item('minecraft:gold_ingot')) + .fluidInput(fluid('lava')) + .output(item('minecraft:clay')) + .boiling() + .sound(sound('minecraft:block.anvil.destroy')) + .levels(3) + .register() + +mods.inspirations.cauldron.recipeBuilderBrewing() + .input(item('minecraft:diamond_block')) + .inputPotion(potionType('minecraft:fire_resistance')) + .outputPotion(potionType('minecraft:strength')) + .register() + +mods.inspirations.cauldron.recipeBuilderDye() + .input(item('minecraft:gold_block')) + .output(item('minecraft:diamond_block')) + .dye('blue') + .levels(2) + .register() + +mods.inspirations.cauldron.recipeBuilderFill() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .fluidInput(fluid('milk')) + .sound(sound('minecraft:block.anvil.destroy')) + .register() + +mods.inspirations.cauldron.recipeBuilderMix() + .output(item('minecraft:clay')) + .fluidInput(fluid('milk'), fluid('lava')) + .register() + +mods.inspirations.cauldron.recipeBuilderPotion() + .input(item('minecraft:gold_block')) + .output(item('minecraft:diamond_block')) + .inputPotion(potionType('minecraft:fire_resistance')) + .levels(2) + .register() + +mods.inspirations.cauldron.recipeBuilderStandard() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .fluidInput(fluid('lava')) + .levels(3) + .sound(sound('minecraft:block.anvil.destroy')) + .register() + +mods.inspirations.cauldron.recipeBuilderTransform() + .input(item('minecraft:stone:3')) + .fluidInput(fluid('water')) + .fluidOutput(fluid('milk')) + .levels(2) + .register() + + diff --git a/examples/postInit/integrateddynamics.groovy b/examples/postInit/integrateddynamics.groovy index e69de29bb..77d5233f3 100644 --- a/examples/postInit/integrateddynamics.groovy +++ b/examples/postInit/integrateddynamics.groovy @@ -0,0 +1,76 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: integrateddynamics + +log.info 'mod \'integrateddynamics\' detected, running script' + +// Drying Basin: +// Takes either an item or fluid input and gives either an item or fluid output after a duration. + +// mods.integrateddynamics.drying_basin.removeAll() + +mods.integrateddynamics.drying_basin.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .fluidInput(fluid('water') * 500) + .fluidOutput(fluid('lava') * 2000) + .mechanical() + .duration(5) + .register() + +mods.integrateddynamics.drying_basin.recipeBuilder() + .output(item('minecraft:clay')) + .fluidInput(fluid('water') * 2000) + .register() + + +// Mechanical Drying Basin: +// Takes either an item or fluid input and gives either an item or fluid output after a duration. + +// mods.integrateddynamics.mechanical_drying_basin.removeAll() + +mods.integrateddynamics.mechanical_drying_basin.recipeBuilder() + .input(item('minecraft:diamond')) + .fluidInput(fluid('water') * 50) + .fluidOutput(fluid('lava') * 20000) + .duration(300) + .register() + + +// Mechanical Squeezer: +// Takes an item and can give up to 3 chanced item outputs and a fluid. + +// mods.integrateddynamics.mechanical_squeezer.removeAll() + +mods.integrateddynamics.mechanical_squeezer.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay') * 16, 0.9F) + .register() + + +// Squeezer: +// Takes an item and can give up to 3 chanced item outputs and a fluid. + +// mods.integrateddynamics.squeezer.removeAll() + +mods.integrateddynamics.squeezer.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:clay_ball'), 1F) + .output(item('minecraft:clay_ball') * 2, 0.7F) + .output(item('minecraft:clay_ball') * 10, 0.2F) + .fluidOutput(fluid('lava') * 2000) + .mechanical() + .duration(5) + .register() + +mods.integrateddynamics.squeezer.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay'), 0.5F) + .register() + +mods.integrateddynamics.squeezer.recipeBuilder() + .input(item('minecraft:diamond')) + .fluidOutput(fluid('lava') * 10) + .register() + + diff --git a/examples/postInit/pneumaticcraft.groovy b/examples/postInit/pneumaticcraft.groovy index e69de29bb..f5eaac5ec 100644 --- a/examples/postInit/pneumaticcraft.groovy +++ b/examples/postInit/pneumaticcraft.groovy @@ -0,0 +1,213 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: pneumaticcraft + +log.info 'mod \'pneumaticcraft\' detected, running script' + +// Amadron: +// Uses an Amadron Tablet and linked inventories in world to trade via drones. + +mods.pneumaticcraft.amadron.removeByInput(item('minecraft:rotten_flesh')) +mods.pneumaticcraft.amadron.removeByOutput(item('minecraft:emerald')) +// mods.pneumaticcraft.amadron.removeAll() +// mods.pneumaticcraft.amadron.removeAllPeriodic() +// mods.pneumaticcraft.amadron.removeAllStatic() + +mods.pneumaticcraft.amadron.recipeBuilder() + .input(item('minecraft:clay') * 3) + .output(item('minecraft:gold_ingot')) + .register() + +mods.pneumaticcraft.amadron.recipeBuilder() + .fluidInput(fluid('water') * 50) + .output(item('minecraft:clay') * 3) + .register() + +mods.pneumaticcraft.amadron.recipeBuilder() + .fluidInput(fluid('water') * 50) + .fluidOutput(fluid('lava') * 10) + .periodic() + .register() + + +// Assembly Controller: +// Uses a given Program to convert an input itemstack into an output itemstack. Drill recipes that output an itemstack used +// for the input itemstack of a Laser recipe can be chained via the Drill & Laser Program. + +mods.pneumaticcraft.assembly_controller.removeByInput(item('minecraft:redstone')) +mods.pneumaticcraft.assembly_controller.removeByOutput(item('pneumaticcraft:pressure_chamber_valve')) +// mods.pneumaticcraft.assembly_controller.removeAll() +// mods.pneumaticcraft.assembly_controller.removeAllDrill() +// mods.pneumaticcraft.assembly_controller.removeAllLaser() + +mods.pneumaticcraft.assembly_controller.recipeBuilder() + .input(item('minecraft:clay') * 3) + .output(item('minecraft:gold_ingot') * 6) + .drill() + .register() + +mods.pneumaticcraft.assembly_controller.recipeBuilder() + .input(item('minecraft:gold_ingot') * 6) + .output(item('minecraft:diamond')) + .laser() + .register() + +mods.pneumaticcraft.assembly_controller.recipeBuilder() + .input(item('minecraft:stone')) + .output(item('minecraft:clay') * 5) + .laser() + .register() + + +// Explosion: +// Converts an input item into an output item, with a chance to fail and destroy the item. + +// mods.pneumaticcraft.explosion.removeByInput(item('minecraft:iron_block')) +mods.pneumaticcraft.explosion.removeByOutput(item('pneumaticcraft:compressed_iron_block')) +// mods.pneumaticcraft.explosion.removeAll() + +mods.pneumaticcraft.explosion.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:gold_ingot')) + .lossRate(40) + .register() + +mods.pneumaticcraft.explosion.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:obsidian')) + .register() + + +// Heat Frame Cooling: +// Converts an input itemstack into an output itemstack when inside an inventory placed within a Heat Frame which is +// cooled. + +mods.pneumaticcraft.heat_frame_cooling.removeByInput(item('minecraft:water_bucket')) +mods.pneumaticcraft.heat_frame_cooling.removeByOutput(item('minecraft:obsidian')) +// mods.pneumaticcraft.heat_frame_cooling.removeAll() + +mods.pneumaticcraft.heat_frame_cooling.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:gold_ingot')) + .register() + +mods.pneumaticcraft.heat_frame_cooling.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:obsidian')) + .register() + + +// Liquid Fuel: +// Converts fluid into Pressure in a Liquid Compressor. + +mods.pneumaticcraft.liquid_fuel.remove(fluid('lava')) +// mods.pneumaticcraft.liquid_fuel.removeAll() + +mods.pneumaticcraft.liquid_fuel.recipeBuilder() + .fluidInput(fluid('water')) + .pressure(100_000_000) + .register() + + +// Plastic Mixer: +// Converts a fluidstack and an item with a variable damage value into each other, requiring temperature to operate the +// process, optionally consuming dye, and allowing either only melting or only solidifying. + +// mods.pneumaticcraft.plastic_mixer.removeByFluid(fluid('plastic')) +// mods.pneumaticcraft.plastic_mixer.removeByItem(item('pneumaticcraft:plastic')) +// mods.pneumaticcraft.plastic_mixer.removeAll() + +mods.pneumaticcraft.plastic_mixer.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .output(item('minecraft:clay')) + .allowMelting() + .allowSolidifying() + .requiredTemperature(323) + .register() + +mods.pneumaticcraft.plastic_mixer.recipeBuilder() + .fluidInput(fluid('water') * 50) + .output(item('minecraft:sapling')) + .allowSolidifying() + .requiredTemperature(298) + .meta(-1) + .register() + + +// Pressure Chamber: +// Converts any number of input itemstacks into any number of output itemstacks, either generating Pressure or consuming +// Pressure from the Pressure Chamber. + +mods.pneumaticcraft.pressure_chamber.removeByInput(item('minecraft:iron_block')) +mods.pneumaticcraft.pressure_chamber.removeByOutput(item('minecraft:diamond')) +// mods.pneumaticcraft.pressure_chamber.removeAll() + +mods.pneumaticcraft.pressure_chamber.recipeBuilder() + .input(item('minecraft:clay') * 3) + .output(item('minecraft:gold_ingot')) + .pressure(4) + .register() + +mods.pneumaticcraft.pressure_chamber.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:gold_ingot'), item('minecraft:gold_block'), item('minecraft:gold_nugget'), item('minecraft:diamond'), item('minecraft:diamond_block'), item('minecraft:obsidian'), item('minecraft:stone'), item('minecraft:stone:1'), item('minecraft:stone:2'), item('minecraft:stone:3'), item('minecraft:stone:4'), item('minecraft:stone:5'), item('minecraft:stone:6')) + .output(item('minecraft:cobblestone')) + .pressure(4) + .register() + + +// Refinery: +// Converts an input fluidstack into between 2 and 4 fluidstacks, consuming Temperature, with the number of fluidstacks +// output depending on the recipe and the number of Refineries vertically stacked. + +// mods.pneumaticcraft.refinery.removeByInput(fluid('oil')) +mods.pneumaticcraft.refinery.removeByOutput(fluid('kerosene')) +// mods.pneumaticcraft.refinery.removeAll() + +mods.pneumaticcraft.refinery.recipeBuilder() + .fluidInput(fluid('water') * 1000) + .fluidOutput(fluid('lava') * 750, fluid('lava') * 250, fluid('lava') * 100, fluid('lava') * 50) + .register() + +mods.pneumaticcraft.refinery.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .fluidOutput(fluid('water') * 50, fluid('kerosene') * 25) + .register() + + +// Thermopneumatic Processing Plant: +// Converts an input fluidstack into an output fluidstack, consuming Pressure and Temperature, with an optional itemstack +// being consumed. + +mods.pneumaticcraft.thermopneumatic_processing_plant.removeByInput(fluid('diesel')) +mods.pneumaticcraft.thermopneumatic_processing_plant.removeByInput(item('minecraft:coal')) +mods.pneumaticcraft.thermopneumatic_processing_plant.removeByOutput(fluid('lpg')) +// mods.pneumaticcraft.thermopneumatic_processing_plant.removeAll() + +mods.pneumaticcraft.thermopneumatic_processing_plant.recipeBuilder() + .input(item('minecraft:clay') * 3) + .fluidInput(fluid('water') * 100) + .fluidOutput(fluid('kerosene') * 100) + .pressure(4) + .requiredTemperature(323) + .register() + +mods.pneumaticcraft.thermopneumatic_processing_plant.recipeBuilder() + .fluidInput(fluid('water') * 100) + .fluidOutput(fluid('lava') * 100) + .pressure(4) + .requiredTemperature(323) + .register() + + +// XP Fluid: +// Controls what fluids are considered XP Fluids and how much experience they provide. + +// mods.pneumaticcraft.xp_fluid.remove(fluid('xpjuice')) +// mods.pneumaticcraft.xp_fluid.removeAll() + +mods.pneumaticcraft.xp_fluid.recipeBuilder() + .fluidInput(fluid('lava')) + .ratio(5) + .register() + + diff --git a/examples/postInit/quarryplus.groovy b/examples/postInit/quarryplus.groovy index e69de29bb..66c8f1d7c 100644 --- a/examples/postInit/quarryplus.groovy +++ b/examples/postInit/quarryplus.groovy @@ -0,0 +1,19 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: quarryplus + +log.info 'mod \'quarryplus\' detected, running script' + +// Workbench Plus: +// Converts up to 27 itemstacks into an output itemstack at the cost of power. + +mods.quarryplus.workbench_plus.removeByOutput(item('quarryplus:quarry')) +// mods.quarryplus.workbench_plus.removeAll() + +mods.quarryplus.workbench_plus.recipeBuilder() + .output(item('minecraft:nether_star')) + .input(item('minecraft:diamond'),item('minecraft:gold_ingot')) + .energy(10000) + .register() + + diff --git a/examples/postInit/thaumcraft.groovy b/examples/postInit/thaumcraft.groovy index e69de29bb..a5863b13b 100644 --- a/examples/postInit/thaumcraft.groovy +++ b/examples/postInit/thaumcraft.groovy @@ -0,0 +1,205 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: thaumcraft + +log.info 'mod \'thaumcraft\' detected, running script' + +// Arcane Workbench: +// A special crafting table, allowing additional requirements in the form of Vis Crystals, Vis, and having a specific +// research. + +mods.thaumcraft.arcane_workbench.removeByOutput(item('thaumcraft:mechanism_simple')) +// mods.thaumcraft.arcane_workbench.removeAll() + +mods.thaumcraft.arcane_workbench.shapedBuilder() + .researchKey('UNLOCKALCHEMY@3') + .output(item('minecraft:pumpkin')) + .row('SS ') + .row(' ') + .row(' ') + .key('S', item('minecraft:pumpkin_seeds')) + .aspect('terra') + .vis(5) + .register() + +mods.thaumcraft.arcane_workbench.shapedBuilder() + .researchKey('UNLOCKALCHEMY@3') + .output(item('minecraft:clay')) + .matrix('SS ', + ' ', + ' ') + .key('S', item('minecraft:pumpkin')) + .aspect(aspect('terra')) + .vis(5) + .register() + +mods.thaumcraft.arcane_workbench.shapelessBuilder() + .researchKey('UNLOCKALCHEMY@3') + .input(item('minecraft:pumpkin')) + .input(item('minecraft:stick')) + .input(item('minecraft:stick')) + .output(item('thaumcraft:void_hoe')) + .vis(0) + .register() + + +// Aspect Creator: +// Creates a custom Aspect. + +// mods.thaumcraft.aspect.removeAll() + +mods.thaumcraft.aspect.aspectBuilder() + .tag('clay') + .chatColor(0xD5D4EC) + .image(resource('placeholdername:textures/items/clay_2.png')) + .register() + +mods.thaumcraft.aspect.aspectBuilder() + .tag('snack') + .chatColor(0xD5D4EC) + .component(aspect('cognitio')) + .component('clay') + .image(resource('placeholdername:textures/items/snack.png')) + .register() + + +// Entity/Block Aspects: +// Controls what Aspects are attached to entities or items. + +mods.thaumcraft.aspect_helper.aspectBuilder() + .object(item('minecraft:stone')) + .stripAspects() + .aspect(aspect('ignis') * 20) + .aspect('ordo', 5) + .register() + +mods.thaumcraft.aspect_helper.aspectBuilder() + .object(ore('cropPumpkin')) + .stripAspects() + .aspect(aspect('herba') * 20) + .register() + +mods.thaumcraft.aspect_helper.aspectBuilder() + .entity(entity('minecraft:chicken')) + .stripAspects() + .aspect('bestia', 20) + .register() + + +// Crucible: +// Combines an item with any number of Aspects to drop an output itemstack, potentially requiring a specific research to be +// completed. + +mods.thaumcraft.crucible.removeByOutput(item('minecraft:gunpowder')) +// mods.thaumcraft.crucible.removeAll() + +mods.thaumcraft.crucible.recipeBuilder() + .researchKey('UNLOCKALCHEMY@3') + .catalyst(item('minecraft:rotten_flesh')) + .output(item('minecraft:gold_ingot')) + .aspect(aspect('metallum') * 10) + .register() + + +// Dust Trigger: +// Converts a block in-world into an item, when interacting with it with Salis Mundus, potentially requiring a specific +// research to be completed. + +mods.thaumcraft.dust_trigger.removeByOutput(item('thaumcraft:arcane_workbench')) + +mods.thaumcraft.dust_trigger.triggerBuilder() + .researchKey('UNLOCKALCHEMY@3') + .target(block('minecraft:obsidian')) + .output(item('minecraft:enchanting_table')) + .register() + +mods.thaumcraft.dust_trigger.triggerBuilder() + .researchKey('UNLOCKALCHEMY@3') + .target(ore('cropPumpkin')) + .output(item('minecraft:lit_pumpkin')) + .register() + + +// Infusion Crafting: +// Combines any number of items and aspects together in the Infusion Altar, potentially requiring a specific research to be +// completed. + +mods.thaumcraft.infusion_crafting.removeByOutput(item('thaumcraft:crystal_terra')) +// mods.thaumcraft.infusion_crafting.removeAll() + +mods.thaumcraft.infusion_crafting.recipeBuilder() + .researchKey('UNLOCKALCHEMY@3') + .mainInput(item('minecraft:gunpowder')) + .output(item('minecraft:gold_ingot')) + .aspect(aspect('terra') * 20) + .aspect('ignis', 30) + .input(crystal('aer')) + .input(crystal('ignis')) + .input(crystal('aqua')) + .input(crystal('terra')) + .input(crystal('ordo')) + .instability(10) + .register() + + +// Lootbag: +// Control what the different rarities of lootbag drop when opened. + +mods.thaumcraft.loot_bag.remove(item('minecraft:ender_pearl'), 0) +mods.thaumcraft.loot_bag.removeAll(2) + +mods.thaumcraft.loot_bag.add(item('minecraft:dirt'), 100, 0) +mods.thaumcraft.loot_bag.add(item('minecraft:diamond_block'), 100, 2) + +// Research: +// Create or modify existing research entries, which contain helpful information and unlock recipes, and can be gated +// behind specific items or events. + +// mods.thaumcraft.research.removeCategory('BASICS') +// mods.thaumcraft.research.removeAllCategories() + +mods.thaumcraft.research.researchCategoryBuilder() + .key('BASICS2') + .researchKey('UNLOCKAUROMANCY') + .formulaAspect(aspect('herba') * 5) + .formulaAspect(aspect('ordo') * 5) + .formulaAspect(aspect('perditio') * 5) + .formulaAspect('aer', 5) + .formulaAspect('ignis', 5) + .formulaAspect(aspect('terra') * 5) + .formulaAspect('aqua', 5) + .icon(resource('thaumcraft:textures/aspects/humor.png')) + .background(resource('thaumcraft:textures/gui/gui_research_back_1.jpg')) + .background2(resource('thaumcraft:textures/gui/gui_research_back_over.png')) + .register() + + +// mods.thaumcraft.research.addResearchLocation(resource('thaumcraft:research/new.json')) +mods.thaumcraft.research.addScannable('KNOWLEDGETYPEHUMOR', item('minecraft:pumpkin')) + +// Smelting Bonus: +// Additional item output when smelting a given item in the Infernal Furnace Multiblock. + +mods.thaumcraft.smelting_bonus.removeByOutput(item('minecraft:gold_nugget')) +// mods.thaumcraft.smelting_bonus.removeAll() + +mods.thaumcraft.smelting_bonus.recipeBuilder() + .input(item('minecraft:cobblestone')) + .output(item('minecraft:stone_button')) + .chance(0.2F) + .register() + +mods.thaumcraft.smelting_bonus.recipeBuilder() + .input(ore('stone')) + .output(item('minecraft:obsidian')) + .register() + + +// Warp: +// Determines if holding an item or equipping a piece of armor or a bauble gives warp, and how much warp it gives. + +mods.thaumcraft.warp.removeWarp(item('thaumcraft:void_hoe')) +// mods.thaumcraft.warp.removeAll() + +mods.thaumcraft.warp.addWarp(item('minecraft:pumpkin'), 3) + diff --git a/examples/postInit/theaurorian.groovy b/examples/postInit/theaurorian.groovy index e69de29bb..cd3c50e97 100644 --- a/examples/postInit/theaurorian.groovy +++ b/examples/postInit/theaurorian.groovy @@ -0,0 +1,33 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: theaurorian + +log.info 'mod \'theaurorian\' detected, running script' + +// Moonlight Forge: +// Combines two items to get a third item. Only works at night, and works faster the higher it is placed in the world. + +mods.theaurorian.moonlight_forge.removeByInput(item('theaurorian:moonstonesword'), item('theaurorian:aurorianiteingot')) +mods.theaurorian.moonlight_forge.removeByOutput(item('theaurorian:queenschipper')) +// mods.theaurorian.moonlight_forge.removeAll() + +mods.theaurorian.moonlight_forge.recipeBuilder() + .input(item('minecraft:stone_sword'), item('minecraft:diamond')) + .output(item('minecraft:diamond_sword')) + .register() + + +// Scrapper: +// Turns an input item into an output item. Can be sped up by placing a Crystal on top of it. The crystal has a chance to +// break every time a recipe is executed. + +mods.theaurorian.scrapper.removeByInput(item('minecraft:iron_sword')) +mods.theaurorian.scrapper.removeByOutput(item('theaurorian:scrapaurorianite')) +// mods.theaurorian.scrapper.removeAll() + +mods.theaurorian.scrapper.recipeBuilder() + .input(item('minecraft:stone_sword')) + .output(item('minecraft:cobblestone')) + .register() + + diff --git a/examples/postInit/thebetweenlands.groovy b/examples/postInit/thebetweenlands.groovy deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/postInit/thermalexpansion.groovy b/examples/postInit/thermalexpansion.groovy index e69de29bb..138401923 100644 --- a/examples/postInit/thermalexpansion.groovy +++ b/examples/postInit/thermalexpansion.groovy @@ -0,0 +1,652 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: thermalexpansion + +import cofh.thermalexpansion.util.managers.machine.InsolatorManager + +log.info 'mod \'thermalexpansion\' detected, running script' + +// Alchemical Imbuer: +// Converts an input fluidstack and input itemstack into an output fluidstack, costing power and taking time based on the +// power cost. + +mods.thermalexpansion.brewer.removeByInput(item('minecraft:glowstone_dust')) +mods.thermalexpansion.brewer.removeByInput(fluid('potion').withNbt(['Potion': 'minecraft:leaping'])) +mods.thermalexpansion.brewer.removeByOutput(fluid('potion_splash').withNbt(['Potion': 'cofhcore:luck2'])) +// mods.thermalexpansion.brewer.removeAll() + +mods.thermalexpansion.brewer.recipeBuilder() + .input(item('minecraft:clay')) + .fluidInput(fluid('water') * 100) + .fluidOutput(fluid('lava') * 100) + .register() + +mods.thermalexpansion.brewer.recipeBuilder() + .input(item('minecraft:diamond') * 2) + .fluidInput(fluid('water') * 1000) + .fluidOutput(fluid('steam') * 100) + .energy(1000) + .register() + + +// mods.thermalexpansion.brewer.add(1000, item('minecraft:obsidian') * 2, fluid('water') * 1000, fluid('steam') * 100) + +// Centrifugal Separator: +// Converts an input itemstack into an optional output fluidstack and up to four output itemstacks with chance, costing +// power and taking time based on the power cost. + +mods.thermalexpansion.centrifuge.removeByInput(item('minecraft:reeds')) +mods.thermalexpansion.centrifuge.removeByOutput(fluid('redstone')) +mods.thermalexpansion.centrifuge.removeByOutput(item('minecraft:redstone')) +// mods.thermalexpansion.centrifuge.removeAll() + +mods.thermalexpansion.centrifuge.recipeBuilder() + .input(item('minecraft:clay')) + .fluidOutput(fluid('water') * 100) + .output(item('minecraft:diamond') * 2, item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) + .chance(50, 100, 1) + .register() + +mods.thermalexpansion.centrifuge.recipeBuilder() + .input(item('minecraft:diamond') * 3) + .output(item('minecraft:clay')) + .chance(100) + .energy(1000) + .register() + + +// mods.thermalexpansion.centrifuge.add(1000, item('minecraft:obsidian') * 3, [item('minecraft:clay')], [100], null) + +// Centrifugal Separator - Enstabulation Apparatus: +// Converts an input itemstack into an optional output fluidstack and up to four output itemstacks with chance, costing +// power and taking time based on the power cost. + +mods.thermalexpansion.centrifuge_mobs.removeByInput(item('thermalexpansion:morb').withNbt(['id': 'minecraft:slime'])) +mods.thermalexpansion.centrifuge_mobs.removeByOutput(item('minecraft:fish')) +// mods.thermalexpansion.centrifuge_mobs.removeByOutput(fluid('experience')) +// mods.thermalexpansion.centrifuge_mobs.removeAll() + +mods.thermalexpansion.centrifuge_mobs.recipeBuilder() + .input(item('thermalexpansion:morb').withNbt(['id': 'minecraft:slime'])) + .fluidOutput(fluid('water') * 100) + .output(item('minecraft:diamond') * 2, item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) + .chance(50, 100, 1) + .register() + +mods.thermalexpansion.centrifuge_mobs.recipeBuilder() + .input(item('minecraft:diamond') * 3) + .output(item('minecraft:clay')) + .chance(100) + .energy(1000) + .register() + + +// mods.thermalexpansion.centrifuge_mobs.add(1000, item('minecraft:obsidian') * 3, item('minecraft:clay'), 100) + +// Energetic Infuser: +// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. + +mods.thermalexpansion.charger.removeByInput(item('thermalfoundation:bait:1')) +mods.thermalexpansion.charger.removeByOutput(item('thermalfoundation:fertilizer:2')) +// mods.thermalexpansion.charger.removeAll() + +mods.thermalexpansion.charger.recipeBuilder() + .input(item('minecraft:diamond') * 5) + .output(item('minecraft:clay')) + .register() + +mods.thermalexpansion.charger.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond') * 2) + .energy(1000) + .register() + + +// mods.thermalexpansion.charger.add(1000, item('minecraft:obsidian'), item('minecraft:diamond') * 2) + +// Compactor: +// Converts an input itemstack into an output itemstack, with different modes each requiring a different augment to be +// installed, costing power and taking time based on the power cost. + +mods.thermalexpansion.compactor.removeByInput(compactorMode('coin'), item('thermalfoundation:material:130')) +mods.thermalexpansion.compactor.removeByInput(item('minecraft:iron_ingot')) +// mods.thermalexpansion.compactor.removeByMode(compactorMode('plate')) +mods.thermalexpansion.compactor.removeByOutput(compactorMode('coin'), item('thermalfoundation:coin:102')) +mods.thermalexpansion.compactor.removeByOutput(item('minecraft:blaze_rod')) +mods.thermalexpansion.compactor.removeByOutput(item('thermalfoundation:material:24')) +// mods.thermalexpansion.compactor.removeAll() + +mods.thermalexpansion.compactor.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond') * 2) + .mode(compactorMode('coin')) + .register() + +mods.thermalexpansion.compactor.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .mode(compactorMode('all')) + .register() + +mods.thermalexpansion.compactor.recipeBuilder() + .input(item('minecraft:diamond') * 2) + .output(item('minecraft:gold_ingot')) + .mode(compactorMode('plate')) + .energy(1000) + .register() + + +// mods.thermalexpansion.compactor.add(1000, compactorMode('plate'), item('minecraft:obsidian') * 2, item('minecraft:gold_ingot')) + +// Compression Dynamo: +// Converts an input fluidstack into power, taking time based on the power. + +mods.thermalexpansion.compression.removeByInput(fluid('seed_oil')) +// mods.thermalexpansion.compression.removeAll() + +mods.thermalexpansion.compression.add(fluid('steam'), 100) + +// Thermal Mediator: +// Consumes fluid to speed up the tick rate of adjacent machines and devices and generate power in the Compression Dynamo. + +mods.thermalexpansion.coolant.remove(fluid('cryotheum')) +// mods.thermalexpansion.coolant.removeAll() + +mods.thermalexpansion.coolant.add(fluid('lava'), 4000, 30) + +// Magma Crucible: +// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. + +mods.thermalexpansion.crucible.removeByInput(item('minecraft:glowstone_dust')) +mods.thermalexpansion.crucible.removeByOutput(fluid('lava')) +// mods.thermalexpansion.crucible.removeAll() + +mods.thermalexpansion.crucible.recipeBuilder() + .input(item('minecraft:clay')) + .fluidOutput(fluid('lava') * 25) + .register() + +mods.thermalexpansion.crucible.recipeBuilder() + .input(item('minecraft:diamond')) + .fluidOutput(fluid('water') * 1000) + .energy(1000) + .register() + + +mods.thermalexpansion.crucible.add(1000, item('minecraft:obsidian'), fluid('water') * 1000) + +// Decorative Diffuser: +// Controls what items can be used in to boost the potion time and level in the Decorative Diffuser. + +mods.thermalexpansion.diffuser.remove(item('minecraft:redstone')) +// mods.thermalexpansion.diffuser.removeAll() + +mods.thermalexpansion.diffuser.add(item('minecraft:clay'), 2, 30) + +// Arcane Ensorcellator: +// Converts two input itemstacks and liquid experience into an output itemstack, costing power and taking time based on the +// power cost. + +mods.thermalexpansion.enchanter.removeByInput(item('minecraft:blaze_rod')) +// mods.thermalexpansion.enchanter.removeByInput(item('minecraft:book')) +mods.thermalexpansion.enchanter.removeByOutput(item('minecraft:enchanted_book').withNbt(['StoredEnchantments': [['lvl': 1, 'id': 34]]])) +// mods.thermalexpansion.enchanter.removeAll() + +mods.thermalexpansion.enchanter.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 4) + .output(item('minecraft:diamond')) + .register() + +mods.thermalexpansion.enchanter.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:gold_ingot')) + .output(item('minecraft:diamond')) + .experience(1000) + .energy(1000) + .register() + + +mods.thermalexpansion.enchanter.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot'), item('minecraft:diamond'), 1000) +mods.thermalexpansion.enchanter.addArcana(item('minecraft:clay')) + +// Enervation Dynamo: +// Converts an input itemstack into power, taking time based on the power. + +mods.thermalexpansion.enervation.removeByInput(item('minecraft:redstone')) +// mods.thermalexpansion.enervation.removeAll() + +mods.thermalexpansion.enervation.add(item('minecraft:clay'), 100) + +// Igneous Extruder: +// Converts a variable amount of lava and water into a specific output itemstack. + +// mods.thermalexpansion.extruder.removeByInput(false, fluid('lava')) +// mods.thermalexpansion.extruder.removeByInput(fluid('water')) +mods.thermalexpansion.extruder.removeByOutput(true, item('minecraft:gravel')) +mods.thermalexpansion.extruder.removeByOutput(item('minecraft:obsidian')) +// mods.thermalexpansion.extruder.removeByType(true) +// mods.thermalexpansion.extruder.removeAll() + +mods.thermalexpansion.extruder.recipeBuilder() + .fluidHot(100) + .fluidCold(1000) + .output(item('minecraft:clay')) + .register() + +mods.thermalexpansion.extruder.recipeBuilder() + .fluidHot(100) + .fluidCold(1000) + .output(item('minecraft:gold_ingot')) + .sedimentary() + .energy(1000) + .register() + + +mods.thermalexpansion.extruder.add(1000, item('minecraft:gold_block'), 100, 1000, false) + +// Factorizer: +// Converts an input itemstack into an output itemstack, with the ability to undo the the recipe. Mainly used for +// compressing ingots into blocks and splitting blocks into ingots. + +mods.thermalexpansion.factorizer.removeByInput(false, item('minecraft:diamond')) +mods.thermalexpansion.factorizer.removeByInput(item('minecraft:coal:1')) +// mods.thermalexpansion.factorizer.removeByOutput(false, item('minecraft:coal:1')) +mods.thermalexpansion.factorizer.removeByOutput(item('minecraft:emerald_block')) +// mods.thermalexpansion.factorizer.removeByType(true) +// mods.thermalexpansion.factorizer.removeAll() + +mods.thermalexpansion.factorizer.recipeBuilder() + .input(item('minecraft:clay') * 7) + .output(item('minecraft:book') * 2) + .combine() + .split() + .register() + +mods.thermalexpansion.factorizer.recipeBuilder() + .input(item('minecraft:planks:*') * 4) + .output(item('minecraft:crafting_table')) + .combine() + .register() + + +// Aquatic Entangler: +// Controls what itemstacks can be gained and how likely each is to be obtained. + +mods.thermalexpansion.fisher.remove(item('minecraft:fish:0')) +// mods.thermalexpansion.fisher.removeAll() + +mods.thermalexpansion.fisher.add(item('minecraft:clay'), 100) + +// Aquatic Entangler Bait: +// Controls what items can be used in the bait slot of the Aquatic Entangler and how effective they are. + +mods.thermalexpansion.fisher_bait.remove(item('thermalfoundation:bait:2')) +// mods.thermalexpansion.fisher_bait.removeAll() + +mods.thermalexpansion.fisher_bait.add(item('minecraft:clay'), 100) + +// Redstone Furnace: +// Converts an input itemstack into an output itemstack, costing power and taking time based on the power cost. + +mods.thermalexpansion.furnace.removeByInput(item('minecraft:cactus:*')) +mods.thermalexpansion.furnace.removeByOutput(item('minecraft:cooked_porkchop')) +mods.thermalexpansion.furnace.removeFood(item('minecraft:rabbit:*')) +// mods.thermalexpansion.furnace.removeAll() +// mods.thermalexpansion.furnace.removeAllFood() + +mods.thermalexpansion.furnace.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay') * 2) + .register() + +mods.thermalexpansion.furnace.recipeBuilder() + .input(item('minecraft:gold_ingot') * 2) + .output(item('minecraft:clay')) + .energy(1000) + .register() + + +mods.thermalexpansion.furnace.add(1000, item('minecraft:obsidian') * 2, item('minecraft:clay')) +mods.thermalexpansion.furnace.addFood(item('minecraft:emerald_ore')) + +// Redstone Furnace - Pyrolytic Conversion: +// Converts an input itemstack into an output itemstack and creosote amount, costing power and taking time based on the +// power cost. + +mods.thermalexpansion.furnace_pyrolysis.removeByInput(item('minecraft:cactus:*')) +mods.thermalexpansion.furnace_pyrolysis.removeByOutput(item('thermalfoundation:storage_resource:1')) +// mods.thermalexpansion.furnace_pyrolysis.removeAll() + +mods.thermalexpansion.furnace_pyrolysis.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond') * 2) + .creosote(100) + .register() + +mods.thermalexpansion.furnace_pyrolysis.recipeBuilder() + .input(item('minecraft:gold_ingot') * 2) + .output(item('minecraft:clay')) + .creosote(1000) + .energy(1000) + .register() + + +mods.thermalexpansion.furnace_pyrolysis.add(1000, item('minecraft:obsidian') * 2, item('minecraft:clay'), 1000) + +// Phytogenic Insolator: +// Converts two input itemstacks into an output itemstack and optional output itemstack with a chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.insolator.removeByInput(item('minecraft:double_plant:4')) +mods.thermalexpansion.insolator.removeByInput(item('thermalfoundation:fertilizer')) +mods.thermalexpansion.insolator.removeByOutput(item('minecraft:melon_seeds')) +mods.thermalexpansion.insolator.removeByOutput(item('minecraft:red_flower:6')) +// mods.thermalexpansion.insolator.removeAll() + +mods.thermalexpansion.insolator.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:diamond')) + .output(item('minecraft:diamond') * 4) + .register() + +mods.thermalexpansion.insolator.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 2) + .output(item('minecraft:clay'), item('minecraft:diamond')) + .chance(5) + .water(100) + .tree() + .energy(1000) + .register() + + +mods.thermalexpansion.insolator.add(1000, 100, item('minecraft:obsidian'), item('minecraft:gold_ingot') * 2, item('minecraft:clay'), item('minecraft:diamond'), 5, InsolatorManager.Type.TREE) + +// Numismatic Dynamo - Lapidary Calibration: +// Converts an input itemstack into power, taking time based on the power. + +mods.thermalexpansion.lapidary.removeByInput(item('minecraft:diamond')) +// mods.thermalexpansion.lapidary.removeAll() + +mods.thermalexpansion.lapidary.add(item('minecraft:clay'), 1000) + +// Magmatic Dynamo: +// Converts an input fluidstack into power, taking time based on the power. + +mods.thermalexpansion.magmatic.removeByInput(fluid('lava')) +// mods.thermalexpansion.magmatic.removeAll() + +mods.thermalexpansion.magmatic.add(fluid('steam'), 100) + +// Numismatic Dynamo: +// Converts an input itemstack into power, taking time based on the power. + +mods.thermalexpansion.numismatic.removeByInput(item('thermalfoundation:coin:69')) +// mods.thermalexpansion.numismatic.removeAll() + +mods.thermalexpansion.numismatic.add(item('minecraft:clay'), 100) + +// Glacial Precipitator: +// Converts an amount of water into a specific output itemstack, costing power and taking time based on the power cost. + +// mods.thermalexpansion.precipitator.removeByInput(fluid('water')) +mods.thermalexpansion.precipitator.removeByOutput(item('minecraft:snowball')) +// mods.thermalexpansion.precipitator.removeAll() + +mods.thermalexpansion.precipitator.recipeBuilder() + .output(item('minecraft:clay')) + .register() + +mods.thermalexpansion.precipitator.recipeBuilder() + .water(100) + .output(item('minecraft:clay')) + .energy(1000) + .register() + + +mods.thermalexpansion.precipitator.add(1000, item('minecraft:obsidian'), 100) + +// Pulverizer: +// Converts an input itemstack into an output itemstack and optional output itemstack with a chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.pulverizer.removeByInput(item('minecraft:emerald_ore')) +mods.thermalexpansion.pulverizer.removeByOutput(item('minecraft:diamond')) +mods.thermalexpansion.pulverizer.removeByOutput(item('thermalfoundation:material:772')) +// mods.thermalexpansion.pulverizer.removeAll() + +mods.thermalexpansion.pulverizer.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay'), item('minecraft:diamond')) + .chance(1) + .register() + +mods.thermalexpansion.pulverizer.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:gold_ingot'), item('minecraft:gold_ingot')) + .energy(1000) + .register() + + +mods.thermalexpansion.pulverizer.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), 100) + +// Reactant Dynamo: +// Converts an input itemstack and input fluidstack into power, taking time based on the power. + +mods.thermalexpansion.reactant.removeByInput(fluid('redstone')) +mods.thermalexpansion.reactant.removeByInput(item('minecraft:blaze_powder')) +mods.thermalexpansion.reactant.removeElementalFluid(fluid('cryotheum')) +mods.thermalexpansion.reactant.removeElementalReactant(item('thermalfoundation:material:1024')) +// mods.thermalexpansion.reactant.removeAll() + +mods.thermalexpansion.reactant.recipeBuilder() + .input(item('minecraft:diamond')) + .fluidInput(fluid('steam')) + .register() + +mods.thermalexpansion.reactant.recipeBuilder() + .input(item('minecraft:clay')) + .fluidInput(fluid('glowstone')) + .energy(100) + .register() + + +mods.thermalexpansion.reactant.add(item('minecraft:clay'), fluid('steam'), 100) +mods.thermalexpansion.reactant.addElementalFluid(fluid('glowstone')) +mods.thermalexpansion.reactant.addElementalReactant(item('minecraft:clay')) +mods.thermalexpansion.reactant.addElementalReactant(item('minecraft:gunpowder')) + +// Fractionating Still: +// Converts an input fluidstack into an output fluidstack and optional output itemstack with chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.refinery.removeBioFuel(fluid('resin')) +mods.thermalexpansion.refinery.removeByInput(fluid('resin')) +mods.thermalexpansion.refinery.removeByOutput(fluid('refined_biofuel')) +// mods.thermalexpansion.refinery.removeByOutput(item('thermalfoundation:material:771')) +mods.thermalexpansion.refinery.removeFossilFuel(fluid('coal')) +// mods.thermalexpansion.refinery.removeAll() +// mods.thermalexpansion.refinery.removeAllBioFuels() +// mods.thermalexpansion.refinery.removeAllFossilFuels() + +mods.thermalexpansion.refinery.recipeBuilder() + .fluidInput(fluid('water') * 100) + .fluidOutput(fluid('steam') * 80) + .register() + +mods.thermalexpansion.refinery.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .fluidOutput(fluid('steam') * 150) + .output(item('minecraft:clay')) + .chance(25) + .energy(1000) + .register() + + +mods.thermalexpansion.refinery.add(1000, fluid('ender') * 100, fluid('steam') * 150, item('minecraft:clay'), 25) +mods.thermalexpansion.refinery.addBioFuel(fluid('coal')) +mods.thermalexpansion.refinery.addFossilFuel(fluid('crude_oil')) + +// Fractionating Still - Alchemical Retort: +// Converts an input fluidstack into an output fluidstack and optional output itemstack with chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.refinery_potion.removeByInput(fluid('potion_lingering').withNbt(['Potion': 'cofhcore:healing3'])) +mods.thermalexpansion.refinery_potion.removeByOutput(fluid('potion_splash').withNbt(['Potion': 'cofhcore:leaping4'])) +// mods.thermalexpansion.refinery_potion.removeAll() + +mods.thermalexpansion.refinery_potion.recipeBuilder() + .fluidInput(fluid('water') * 100) + .fluidOutput(fluid('steam') * 200) + .register() + +mods.thermalexpansion.refinery_potion.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .fluidOutput(fluid('steam') * 30) + .output(item('minecraft:clay')) + .chance(75) + .energy(1000) + .register() + + +mods.thermalexpansion.refinery_potion.add(1000, fluid('ender') * 100, fluid('steam') * 30, item('minecraft:clay'), 75) + +// Sawmill: +// Converts an input itemstack into an output itemstack and optional output itemstack with a chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.sawmill.removeByInput(item('minecraft:pumpkin')) +mods.thermalexpansion.sawmill.removeByOutput(item('minecraft:leather')) +mods.thermalexpansion.sawmill.removeByOutput(item('thermalfoundation:material:800')) +// mods.thermalexpansion.sawmill.removeAll() + +mods.thermalexpansion.sawmill.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:gold_ingot') * 2) + .register() + +mods.thermalexpansion.sawmill.recipeBuilder() + .input(item('minecraft:clay') * 4) + .output(item('minecraft:gold_ingot'), item('minecraft:diamond')) + .chance(25) + .energy(1000) + .register() + + +mods.thermalexpansion.sawmill.add(1000, item('minecraft:obsidian') * 4, item('minecraft:gold_ingot'), item('minecraft:diamond'), 25) + +// Induction Smelter: +// Converts two input itemstacks into an output itemstack and optional output itemstack with a chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.smelter.removeByInput(ore('sand')) +mods.thermalexpansion.smelter.removeByInput(item('minecraft:iron_ingot')) +mods.thermalexpansion.smelter.removeByOutput(item('thermalfoundation:material:166')) +// mods.thermalexpansion.smelter.removeAll() + +mods.thermalexpansion.smelter.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:diamond')) + .output(item('minecraft:diamond') * 4) + .register() + +mods.thermalexpansion.smelter.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:gold_ingot') * 2) + .output(item('minecraft:clay'), item('minecraft:diamond')) + .chance(5) + .energy(1000) + .register() + + +// mods.thermalexpansion.smelter.add(1000, item('minecraft:obsidian'), item('minecraft:gold_ingot') * 2, item('minecraft:clay'), item('minecraft:diamond'), 5) + +// Steam Dynamo: +// Converts an input itemstack into power, taking time based on the power. + +mods.thermalexpansion.steam.removeByInput(item('minecraft:coal:1')) +// mods.thermalexpansion.steam.removeAll() + +mods.thermalexpansion.steam.add(item('minecraft:clay'), 100) + +// Arboreal Extractor: +// Controls what items and blocks can be turned into what fluids. Output can be boosted via Fertilizer items. + +mods.thermalexpansion.tapper.removeBlockByInput(item('minecraft:log')) +mods.thermalexpansion.tapper.removeItemByInput(item('minecraft:log:1')) +// mods.thermalexpansion.tapper.removeAll() +// mods.thermalexpansion.tapper.removeAllBlocks() +// mods.thermalexpansion.tapper.removeAllItems() + +mods.thermalexpansion.tapper.addBlock(item('minecraft:clay'), fluid('lava') * 150) +mods.thermalexpansion.tapper.addItem(item('minecraft:clay'), fluid('lava') * 300) + +// Arboreal Extractor Fertilizer: +// Controls what items can be used in the fertilizer slot of the Arboreal Extractor Fertilizer and how effective they are. + +mods.thermalexpansion.tapper_fertilizer.remove(item('thermalfoundation:fertilizer:2')) +// mods.thermalexpansion.tapper_fertilizer.removeAll() + +mods.thermalexpansion.tapper_fertilizer.add(item('minecraft:clay'), 1000) + +// Arboreal Extractor Tree Structures: +// Controls what valid log blocks and leaf blocks are to define a tree structure which the Arboreal Extractor can function +// on. The \"tree\" must contain some number of leaves adjacent to the log blocks to be valid. + +mods.thermalexpansion.tapper_tree.removeByLeaf(blockstate('minecraft:leaves', 'variant=birch')) +mods.thermalexpansion.tapper_tree.removeByLog(blockstate('minecraft:log', 'variant=spruce')) +// mods.thermalexpansion.tapper_tree.removeAll() + +mods.thermalexpansion.tapper_tree.add(blockstate('minecraft:clay'), blockstate('minecraft:gold_block')) + +// Fluid Transposer - Empty: +// Converts an input itemstack into an output fluidstack and optional output itemstack with chance, costing power and +// taking time based on the power cost. + +mods.thermalexpansion.transposer_extract.removeByInput(item('minecraft:sponge:1')) +mods.thermalexpansion.transposer_extract.removeByOutput(fluid('seed_oil')) +mods.thermalexpansion.transposer_extract.removeByOutput(item('minecraft:bowl')) +// mods.thermalexpansion.transposer_extract.removeAll() + +mods.thermalexpansion.transposer_extract.recipeBuilder() + .input(item('minecraft:diamond') * 2) + .fluidOutput(fluid('water') * 100) + .register() + +mods.thermalexpansion.transposer_extract.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond') * 2) + .fluidOutput(fluid('water') * 50) + .energy(1000) + .register() + + +mods.thermalexpansion.transposer_extract.add(1000, item('minecraft:obsidian'), fluid('water') * 50, item('minecraft:diamond') * 2, 100) + +// Fluid Transposer - Fill: +// Converts an input itemstack and input fluidstack into an output itemstack with chance, costing power and taking time +// based on the power cost. + +mods.thermalexpansion.transposer_fill.removeByInput(fluid('glowstone')) +mods.thermalexpansion.transposer_fill.removeByInput(item('minecraft:concrete_powder:3')) +mods.thermalexpansion.transposer_fill.removeByOutput(item('minecraft:ice')) +// mods.thermalexpansion.transposer_fill.removeAll() + +mods.thermalexpansion.transposer_fill.recipeBuilder() + .input(item('minecraft:diamond') * 2) + .fluidInput(fluid('water') * 100) + .register() + +mods.thermalexpansion.transposer_fill.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond') * 2) + .fluidInput(fluid('water') * 50) + .energy(1000) + .register() + + +mods.thermalexpansion.transposer_fill.add(1000, item('minecraft:obsidian'), fluid('water') * 50, item('minecraft:diamond') * 2, 100) + +// Insightful Condenser: +// Collects experience orbs nearby, with the ability to increase the XP gained via catalyst itemstacks. + +mods.thermalexpansion.xp_collector.remove(item('minecraft:soul_sand')) +// mods.thermalexpansion.xp_collector.removeAll() + +mods.thermalexpansion.xp_collector.add(item('minecraft:clay'), 100, 30) + From d4a0d3d3c6f534961f85d1b5e21b274ad3b39b1e Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 2 Apr 2025 15:15:36 +0200 Subject: [PATCH 12/13] why did it do that 2 --- examples/postInit/thebetweenlands.groovy | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 examples/postInit/thebetweenlands.groovy diff --git a/examples/postInit/thebetweenlands.groovy b/examples/postInit/thebetweenlands.groovy new file mode 100644 index 000000000..fa1addab5 --- /dev/null +++ b/examples/postInit/thebetweenlands.groovy @@ -0,0 +1,208 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: thebetweenlands + +log.info 'mod \'thebetweenlands\' detected, running script' + +// Animator: +// Converts an input item, Life amount from Life Crystals, and Fuel from Sulfur into an output itemstack, summoning an +// entity, a random item from a loottable, or summoning an entity and outputting an itemstack. + +mods.thebetweenlands.animator.removeByEntity(entity('thebetweenlands:sporeling')) +mods.thebetweenlands.animator.removeByInput(item('thebetweenlands:bone_leggings')) +mods.thebetweenlands.animator.removeByLootTable(resource('thebetweenlands:animator/scroll')) +mods.thebetweenlands.animator.removeByOutput(item('thebetweenlands:items_misc:46')) +// mods.thebetweenlands.animator.removeAll() + +mods.thebetweenlands.animator.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .life(1) + .fuel(1) + .register() + +mods.thebetweenlands.animator.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .lootTable(resource('minecraft:entities/zombie')) + .life(5) + .fuel(1) + .register() + +mods.thebetweenlands.animator.recipeBuilder() + .input(item('minecraft:gold_block')) + .entity(entity('minecraft:zombie').getEntityClass()) + .life(1) + .fuel(5) + .register() + +mods.thebetweenlands.animator.recipeBuilder() + .input(item('minecraft:diamond')) + .entity(entity('minecraft:enderman')) + .output(item('minecraft:clay')) + .life(3) + .fuel(10) + .register() + + +// Compost: +// Converts an input itemstack into an amount of compost. + +mods.thebetweenlands.compost.removeByInput(item('thebetweenlands:items_misc:13')) +// mods.thebetweenlands.compost.removeAll() + +mods.thebetweenlands.compost.recipeBuilder() + .input(item('minecraft:clay')) + .amount(20) + .time(30) + .register() + +mods.thebetweenlands.compost.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .amount(1) + .time(5) + .register() + + +// Crab Pot Filter Bubbler: +// Converts an input item into an output itemstack when a Bubbler Crab is placed inside a Crab Pot Filter. + +mods.thebetweenlands.crab_pot_filter_bubbler.removeByInput(item('thebetweenlands:silt')) +mods.thebetweenlands.crab_pot_filter_bubbler.removeByOutput(item('thebetweenlands:swamp_dirt')) +// mods.thebetweenlands.crab_pot_filter_bubbler.removeAll() + +mods.thebetweenlands.crab_pot_filter_bubbler.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.crab_pot_filter_bubbler.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .register() + + +// Crab Pot Filter Silt: +// Converts an input item into an output itemstack when a Silt Crab is placed inside a Crab Pot Filter. + +mods.thebetweenlands.crab_pot_filter_silt.removeByInput(item('thebetweenlands:mud')) +mods.thebetweenlands.crab_pot_filter_silt.removeByOutput(item('thebetweenlands:mud')) +// mods.thebetweenlands.crab_pot_filter_silt.removeAll() + +mods.thebetweenlands.crab_pot_filter_silt.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.crab_pot_filter_silt.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .register() + + +// Druid Altar: +// Converts 4 input items into an output itemstack. + +// mods.thebetweenlands.druid_altar.removeByInput(item('thebetweenlands:swamp_talisman:1')) +mods.thebetweenlands.druid_altar.removeByOutput(item('thebetweenlands:swamp_talisman')) +// mods.thebetweenlands.druid_altar.removeAll() + +mods.thebetweenlands.druid_altar.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.druid_altar.recipeBuilder() + .input(item('minecraft:diamond'), item('minecraft:gold_block'), item('minecraft:gold_ingot'), item('minecraft:clay')) + .output(item('minecraft:clay')) + .register() + + +// Pestle And Mortar: +// Converts an input item into an output itemstack in a Pestle and Mortar by using a Pestle tool in the Mortar. + +mods.thebetweenlands.pestle_and_mortar.removeByInput(item('thebetweenlands:limestone')) +mods.thebetweenlands.pestle_and_mortar.removeByOutput(item('thebetweenlands:fish_bait')) +// mods.thebetweenlands.pestle_and_mortar.removeAll() + +mods.thebetweenlands.pestle_and_mortar.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.pestle_and_mortar.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .register() + + +// Purifier: +// Converts an input item into an output itemstack, consuming Sulfur and Swamp Water as fuel. + +mods.thebetweenlands.purifier.removeByInput(item('thebetweenlands:items_misc:64')) +mods.thebetweenlands.purifier.removeByOutput(item('thebetweenlands:cragrock')) +// mods.thebetweenlands.purifier.removeAll() + +mods.thebetweenlands.purifier.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.purifier.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .register() + + +// Smoking Rack: +// Converts an input item into an output itemstack over a configurable period of time, consuming Fallen Leaves to do so. + +mods.thebetweenlands.smoking_rack.removeByInput(item('thebetweenlands:anadia')) +mods.thebetweenlands.smoking_rack.removeByOutput(item('thebetweenlands:barnacle_smoked')) +// mods.thebetweenlands.smoking_rack.removeAll() + +mods.thebetweenlands.smoking_rack.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .register() + +mods.thebetweenlands.smoking_rack.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .time(50) + .register() + + +// Steeping Pot: +// Converts a 1,000mb of fluid into either 1,000mb of a fluid, an output itemstack, or both, consuming up to 4 items from a +// Silk Bundle placed inside the Steeping Pot to do so. The Silk Bundle is converted into a Dirty Silk Bundle in the +// process. The Silk Bundle can only hold specific items, which are also configurable. + +mods.thebetweenlands.steeping_pot.removeAcceptedItem(item('thebetweenlands:items_crushed:5')) +mods.thebetweenlands.steeping_pot.removeByInput(fluid('clean_water')) +mods.thebetweenlands.steeping_pot.removeByInput(item('thebetweenlands:items_crushed:13')) +mods.thebetweenlands.steeping_pot.removeByOutput(fluid('dye_fluid').withNbt(['type': 14])) +// mods.thebetweenlands.steeping_pot.removeByOutput(item('thebetweenlands:limestone')) +// mods.thebetweenlands.steeping_pot.removeAll() +// mods.thebetweenlands.steeping_pot.removeAllAcceptedItem() + +mods.thebetweenlands.steeping_pot.recipeBuilder() + .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) + .fluidInput(fluid('lava')) + .fluidOutput(fluid('water')) + .register() + +mods.thebetweenlands.steeping_pot.recipeBuilder() + .input(item('minecraft:diamond')) + .fluidInput(fluid('lava')) + .fluidOutput(fluid('dye_fluid')) + .meta(5) + .register() + +mods.thebetweenlands.steeping_pot.recipeBuilder() + .input(item('minecraft:emerald')) + .fluidInput(fluid('lava')) + .fluidOutput(fluid('water')) + .register() + + +mods.thebetweenlands.steeping_pot.addAcceptedItem(item('minecraft:gold_block')) From 00f1a45351a80df5f396cff34ec90865b55fa587 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 2 Apr 2025 15:27:36 +0200 Subject: [PATCH 13/13] increase cache version --- .../groovyscript/sandbox/CustomGroovyScriptEngine.java | 2 +- .../java/com/cleanroommc/groovyscript/sandbox/RunConfig.java | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java index 7535f73c8..701be4738 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/CustomGroovyScriptEngine.java @@ -43,7 +43,7 @@ public class CustomGroovyScriptEngine implements ResourceConnector { * Changing this number will force the cache to be deleted and every script has to be recompiled. * Useful when changes to the compilation process were made. */ - public static final int CACHE_VERSION = 3; + public static final int CACHE_VERSION = 4; /** * Setting this to false will cause compiled classes to never be cached. * As a side effect some compilation behaviour might change. Can be useful for debugging. diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java index fe3eb6329..4b215d81e 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/RunConfig.java @@ -31,10 +31,6 @@ public static JsonObject createDefaultJson() { json.addProperty("packId", "placeholdername"); json.addProperty("version", "1.0.0"); json.addProperty("debug", false); - //JsonObject classes = new JsonObject(); - //JsonArray preInit = new JsonArray(); - //classes.add("preInit", preInit); - //json.add("classes", classes); JsonObject loaders = new JsonObject(); json.add("loaders", loaders); JsonArray preInit = new JsonArray(); @@ -109,7 +105,6 @@ public void reload(JsonObject json, boolean init) { throw new RuntimeException(); } this.debug = JsonHelper.getBoolean(json, false, "debug"); - //this.classes.clear(); this.loaderPaths.clear(); this.packmodeList.clear(); this.packmodeSet.clear();